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 (