diff --git a/app/dashboard/layout.tsx b/app/dashboard/layout.tsx index 2b31458..fe1ae4a 100644 --- a/app/dashboard/layout.tsx +++ b/app/dashboard/layout.tsx @@ -46,7 +46,7 @@ export default async function DashboardLayout({ children }: { children: ReactNod -
+
{children}
}> diff --git a/components/ui/dialog.tsx b/components/ui/dialog.tsx index 0de850b..d9107e7 100644 --- a/components/ui/dialog.tsx +++ b/components/ui/dialog.tsx @@ -1,15 +1,37 @@ "use client"; -import * as DialogPrimitive from "@radix-ui/react-dialog"; -import * as React from "react"; - import { Icons } from "@/components/icons"; import { Button } from "@/components/ui/button"; +import { useWindowSize } from "@/hooks/use-window-size"; import { cn } from "@/lib/utils"; +import * as DialogPrimitive from "@radix-ui/react-dialog"; +import * as React from "react"; +import { Drawer } from "vaul"; -const Dialog = DialogPrimitive.Root; +const Dialog = ({ ...props }: DialogPrimitive.DialogProps) => { + const { isMobile } = useWindowSize(); + const Component = isMobile ? Drawer.Root : DialogPrimitive.Root; -const DialogTrigger = DialogPrimitive.Trigger; + return ; +}; + +Dialog.displayName = DialogPrimitive.Root.displayName; + +const DialogTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...other }, ref) => { + const { isMobile } = useWindowSize(); + const Component = isMobile ? Drawer.Trigger : DialogPrimitive.Trigger; + + return ( + + {children} + + ); +}); + +DialogTrigger.displayName = DialogPrimitive.Trigger.displayName; const DialogPortal = ({ className, children, ...other }: DialogPrimitive.DialogPortalProps) => ( @@ -18,6 +40,7 @@ const DialogPortal = ({ className, children, ...other }: DialogPrimitive.DialogP ); + DialogPortal.displayName = DialogPrimitive.Portal.displayName; const DialogOverlay = React.forwardRef< @@ -38,31 +61,50 @@ DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; const DialogContent = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef ->(({ className, children, ...other }, ref) => ( - - - - {children} - (({ className, children, ...other }, ref) => { + const { isMobile } = useWindowSize(); + + if (isMobile) { + return ( + <> + + + +
+ {children} + + + + + ); + } + + return ( + + + - - - - -)); + {children} + + + + + + ); +}); DialogContent.displayName = DialogPrimitive.Content.displayName; const DialogHeader = ({ className, ...other }: React.HTMLAttributes) => ( @@ -104,10 +146,10 @@ DialogDescription.displayName = DialogPrimitive.Description.displayName; export { Dialog, - DialogTrigger, DialogContent, - DialogHeader, + DialogDescription, DialogFooter, + DialogHeader, DialogTitle, - DialogDescription + DialogTrigger }; diff --git a/hooks/use-window-size.ts b/hooks/use-window-size.ts new file mode 100644 index 0000000..ced3204 --- /dev/null +++ b/hooks/use-window-size.ts @@ -0,0 +1,47 @@ +import { useEffect, useMemo, useState } from "react"; + +function useWindowSize(): { + width: number; + height: number; + isMobile: boolean; + isDesktop: boolean; +} { + // Initialize state with undefined width/height so server and client renders match + // Learn more here: https://joshwcomeau.com/react/the-perils-of-rehydration/ + const [windowSize, setWindowSize] = useState<{ + width: number; + height: number; + }>({ + width: 0, + height: 0 + }); + useEffect(() => { + // Handler to call on window resize + function handleResize() { + // Set window width/height to state + setWindowSize({ + width: window.innerWidth, + height: window.innerHeight + }); + } + // Add event listener + window.addEventListener("resize", handleResize); + // Call handler right away so state gets updated with initial window size + handleResize(); + // Remove event listener on cleanup + return () => window.removeEventListener("resize", handleResize); + }, []); // Empty array ensures that effect is only run on mount + + // Memoize the result of the calculations using useMemo + const memoizedWindowSize = useMemo(() => { + return { + ...windowSize, + isMobile: typeof windowSize?.width === "number" && windowSize.width < 768, + isDesktop: typeof windowSize?.width === "number" && windowSize.width >= 768 + }; + }, [windowSize]); + + return memoizedWindowSize; +} + +export { useWindowSize }; diff --git a/package.json b/package.json index 518006e..c3ad669 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "react-hook-form": "^7.45.1", "server-only": "^0.0.1", "tailwind-merge": "^1.13.2", + "vaul": "^0.0.6", "zod": "^3.21.4" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c53b980..0ab4b52 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -107,6 +107,9 @@ dependencies: tailwind-merge: specifier: ^1.13.2 version: 1.13.2 + vaul: + specifier: ^0.0.6 + version: 0.0.6(@types/react-dom@18.2.6)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0) zod: specifier: ^3.21.4 version: 3.21.4 @@ -4344,6 +4347,20 @@ packages: hasBin: true dev: false + /vaul@0.0.6(@types/react-dom@18.2.6)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-YTI1wkukcgw8EKrZ4cgnj/NDXue0Y6vicGkix1iu4j2oNX2Wcdr9FXdPlvXxhzBdDfEl+dIMu8zjgri3UIawNg==} + peerDependencies: + react: ^18.0.0 + react-dom: ^18.0.0 + dependencies: + '@radix-ui/react-dialog': 1.0.4(@types/react-dom@18.2.6)(@types/react@18.2.14)(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'}