diff --git a/package.json b/package.json
index 0a985abc08..b767b2dd6f 100644
--- a/package.json
+++ b/package.json
@@ -79,6 +79,7 @@
"socket.io-client": "4.7.2",
"tailwind-merge": "2.0.0",
"uniqolor": "1.1.0",
+ "vaul": "0.7.7",
"xss": "1.0.14"
},
"devDependencies": {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index f4f48e081a..b4a93d69df 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -149,6 +149,9 @@ dependencies:
uniqolor:
specifier: 1.1.0
version: 1.1.0
+ vaul:
+ specifier: 0.7.7
+ version: 0.7.7(@types/react-dom@18.2.14)(@types/react@18.2.33)(react-dom@18.2.0)(react@18.2.0)
xss:
specifier: 1.0.14
version: 1.0.14
@@ -7504,6 +7507,20 @@ packages:
sade: 1.8.1
dev: false
+ /vaul@0.7.7(@types/react-dom@18.2.14)(@types/react@18.2.33)(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-NAQTE8836Daxq+VC74srRfGqiOLQft4yc0x8YsO6Vowti0RC7LWzSpIxKd7RGegzgMMZOho+9ysH+uI6o+tUVw==}
+ peerDependencies:
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+ dependencies:
+ '@radix-ui/react-dialog': 1.0.5(@types/react-dom@18.2.14)(@types/react@18.2.33)(react-dom@18.2.0)(react@18.2.0)
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ transitivePeerDependencies:
+ - '@types/react'
+ - '@types/react-dom'
+ dev: false
+
/watchpack@2.4.0:
resolution: {integrity: sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==}
engines: {node: '>=10.13.0'}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index ba5eddf442..23f87c3784 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -155,7 +155,9 @@ export default async function RootLayout(props: Props) {
appConfig={themeConfig.config}
/>
- {children}
+
+ {children}
+
diff --git a/src/components/ui/sheet/Sheet.tsx b/src/components/ui/sheet/Sheet.tsx
new file mode 100644
index 0000000000..bcc0a3e4c6
--- /dev/null
+++ b/src/components/ui/sheet/Sheet.tsx
@@ -0,0 +1,87 @@
+import React, { useEffect, useMemo, useState } from 'react'
+import { atom, useStore } from 'jotai'
+import { Drawer } from 'vaul'
+import type { FC, PropsWithChildren } from 'react'
+
+export interface PresentSheetProps {
+ content: JSX.Element | FC
+ open?: boolean
+ onOpenChange?: (value: boolean) => void
+ title?: string
+ zIndex?: number
+ dismissible?: boolean
+}
+
+export const sheetStackAtom = atom([] as HTMLDivElement[])
+
+export const PresentSheet: FC> = (
+ props,
+) => {
+ const { content, children, zIndex = 998, title, dismissible = true } = props
+ const nextRootProps = useMemo(() => {
+ const nextProps = {} as any
+ if (props.open !== undefined) {
+ nextProps.open = props.open
+ }
+
+ if (props.onOpenChange !== undefined) {
+ nextProps.onOpenChange = props.onOpenChange
+ }
+
+ return nextProps
+ }, [props])
+ const [holderRef, setHolderRef] = useState()
+ const store = useStore()
+
+ useEffect(() => {
+ const holder = holderRef
+ if (!holder) return
+ store.set(sheetStackAtom, (p) => {
+ return p.concat(holder)
+ })
+
+ return () => {
+ store.set(sheetStackAtom, (p) => {
+ return p.filter((item) => item !== holder)
+ })
+ }
+ }, [holderRef, store])
+
+ const Root = Drawer.Root
+
+ const overlayZIndex = zIndex - 1
+ const contentZIndex = zIndex
+
+ return (
+
+ {children}
+
+
+ {dismissible && (
+
+ )}
+
+ {title && {title}}
+
+ {React.isValidElement(content)
+ ? content
+ : typeof content === 'function'
+ ? React.createElement(content)
+ : null}
+
+
+
+
+
+ )
+}
diff --git a/src/components/ui/sheet/index.ts b/src/components/ui/sheet/index.ts
new file mode 100644
index 0000000000..56bed6ce17
--- /dev/null
+++ b/src/components/ui/sheet/index.ts
@@ -0,0 +1 @@
+export * from './Sheet'
diff --git a/src/providers/root/modal-stack-provider.tsx b/src/providers/root/modal-stack-provider.tsx
index 33157e0d3e..26503e9294 100644
--- a/src/providers/root/modal-stack-provider.tsx
+++ b/src/providers/root/modal-stack-provider.tsx
@@ -16,9 +16,11 @@ import { usePathname } from 'next/navigation'
import type { Target, Transition } from 'framer-motion'
import type { FC, PropsWithChildren, SyntheticEvent } from 'react'
+import { useIsMobile } from '~/atoms'
import { CloseIcon } from '~/components/icons/close'
import { DialogOverlay } from '~/components/ui/dialog/DialogOverlay'
import { Divider } from '~/components/ui/divider'
+import { PresentSheet, sheetStackAtom } from '~/components/ui/sheet'
import { microReboundPreset } from '~/constants/spring'
import { useIsClient } from '~/hooks/common/use-is-client'
import { useIsUnMounted } from '~/hooks/common/use-is-unmounted'
@@ -195,6 +197,21 @@ const Modal: Component<{
dismiss: close,
}
+ const isMobile = useIsMobile()
+
+ if (isMobile) {
+ const drawerLength = jotaiStore.get(sheetStackAtom).length
+
+ return (
+
+ )
+ }
+
if (CustomModalComponent) {
return (