From 98960f6ae76180f798f2933821fbb3942fd898b7 Mon Sep 17 00:00:00 2001 From: trinhdinhtai <142890841+trinhdinhtai@users.noreply.github.com> Date: Sat, 8 Jun 2024 11:54:18 +0700 Subject: [PATCH] feat: add responsive dialog --- components/auth/sign-in-modal.tsx | 25 +++-- components/ui/drawer.tsx | 118 ++++++++++++++++++++++++ components/ui/responsive-dialog.tsx | 136 ++++++++++++++++++++++++++++ hooks/use-media-query.ts | 19 ++++ package.json | 1 + pnpm-lock.yaml | 17 ++++ 6 files changed, 306 insertions(+), 10 deletions(-) create mode 100644 components/ui/drawer.tsx create mode 100644 components/ui/responsive-dialog.tsx create mode 100644 hooks/use-media-query.ts diff --git a/components/auth/sign-in-modal.tsx b/components/auth/sign-in-modal.tsx index 481ece9..95995f4 100644 --- a/components/auth/sign-in-modal.tsx +++ b/components/auth/sign-in-modal.tsx @@ -1,9 +1,6 @@ "use client" -import { SiGithub, SiGoogle } from "react-icons/si" - import { useSignInModal } from "@/hooks/use-sign-in-modal" -import { Button } from "@/components/ui/button" import { Dialog, DialogContent, @@ -11,23 +8,31 @@ import { DialogHeader, DialogTitle, } from "@/components/ui/dialog" +import { + ResponsiveDialog, + ResponsiveDialogBody, + ResponsiveDialogContent, + ResponsiveDialogHeader, +} from "@/components/ui/responsive-dialog" import SocialSignInButton from "@/components/auth/social-sign-in-button" export default function SignInModal() { const { open, setOpen } = useSignInModal() return ( - - - + + + Sign in to continue to taitd.io.vn - + - - - + + + + + ) } diff --git a/components/ui/drawer.tsx b/components/ui/drawer.tsx new file mode 100644 index 0000000..6a0ef53 --- /dev/null +++ b/components/ui/drawer.tsx @@ -0,0 +1,118 @@ +"use client" + +import * as React from "react" +import { Drawer as DrawerPrimitive } from "vaul" + +import { cn } from "@/lib/utils" + +const Drawer = ({ + shouldScaleBackground = true, + ...props +}: React.ComponentProps) => ( + +) +Drawer.displayName = "Drawer" + +const DrawerTrigger = DrawerPrimitive.Trigger + +const DrawerPortal = DrawerPrimitive.Portal + +const DrawerClose = DrawerPrimitive.Close + +const DrawerOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName + +const DrawerContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + +
+ {children} + + +)) +DrawerContent.displayName = "DrawerContent" + +const DrawerHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DrawerHeader.displayName = "DrawerHeader" + +const DrawerFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DrawerFooter.displayName = "DrawerFooter" + +const DrawerTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DrawerTitle.displayName = DrawerPrimitive.Title.displayName + +const DrawerDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DrawerDescription.displayName = DrawerPrimitive.Description.displayName + +export { + Drawer, + DrawerPortal, + DrawerOverlay, + DrawerTrigger, + DrawerClose, + DrawerContent, + DrawerHeader, + DrawerFooter, + DrawerTitle, + DrawerDescription, +} diff --git a/components/ui/responsive-dialog.tsx b/components/ui/responsive-dialog.tsx new file mode 100644 index 0000000..914e831 --- /dev/null +++ b/components/ui/responsive-dialog.tsx @@ -0,0 +1,136 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" +import { useMediaQuery } from "@/hooks/use-media-query" +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog" +import { + Drawer, + DrawerClose, + DrawerContent, + DrawerDescription, + DrawerFooter, + DrawerHeader, + DrawerTitle, + DrawerTrigger, +} from "@/components/ui/drawer" + +type ResponsiveDialogContextType = { + isDesktop: boolean +} + +const ResponsiveDialogContext = React.createContext< + ResponsiveDialogContextType | undefined +>(undefined) + +const useResponsiveDialog = () => { + const context = React.useContext(ResponsiveDialogContext) + + if (!context) { + throw new Error( + "useResponsiveDialog must be used within a ResponsiveDialog" + ) + } + + return context +} + +type ResponsiveDialogProps = { + children?: React.ReactNode + onOpenChange?: (isOpen: boolean) => void + open?: boolean +} + +const ResponsiveDialog = (props: ResponsiveDialogProps) => { + const isDesktop = useMediaQuery("(min-width: 768px)") + const Comp = isDesktop ? Dialog : Drawer + + return ( + + + + ) +} + +type BaseProps = { + children?: React.ReactNode + className?: React.ComponentProps<"div">["className"] + asChild?: boolean +} + +const ResponsiveDialogTrigger = (props: BaseProps) => { + const { isDesktop } = useResponsiveDialog() + const Comp = isDesktop ? DialogTrigger : DrawerTrigger + + return +} + +const ResponsiveDialogContent = (props: BaseProps) => { + const { isDesktop } = useResponsiveDialog() + const Comp = isDesktop ? DialogContent : DrawerContent + + return +} + +const ResponsiveDialogBody = ({ className, ...props }: BaseProps) => { + const { isDesktop } = useResponsiveDialog() + + return ( +
+ ) +} + +const ResponsiveDialogHeader = ({ className, ...props }: BaseProps) => { + const { isDesktop } = useResponsiveDialog() + const Comp = isDesktop ? DialogHeader : DrawerHeader + + return ( + + ) +} + +const ResponsiveDialogTitle = (props: BaseProps) => { + const { isDesktop } = useResponsiveDialog() + const Comp = isDesktop ? DialogTitle : DrawerTitle + + return +} + +const ResponsiveDialogDescription = (props: BaseProps) => { + const { isDesktop } = useResponsiveDialog() + const Comp = isDesktop ? DialogDescription : DrawerDescription + + return +} + +const ResponsiveDialogFooter = (props: BaseProps) => { + const { isDesktop } = useResponsiveDialog() + const Comp = isDesktop ? DialogFooter : DrawerFooter + + return +} + +const ResponsiveDialogClose = (props: BaseProps) => { + const { isDesktop } = useResponsiveDialog() + + return !isDesktop && +} + +export { + ResponsiveDialog, + ResponsiveDialogTrigger, + ResponsiveDialogContent, + ResponsiveDialogBody, + ResponsiveDialogHeader, + ResponsiveDialogTitle, + ResponsiveDialogDescription, + ResponsiveDialogFooter, + ResponsiveDialogClose, +} diff --git a/hooks/use-media-query.ts b/hooks/use-media-query.ts new file mode 100644 index 0000000..a3dfc79 --- /dev/null +++ b/hooks/use-media-query.ts @@ -0,0 +1,19 @@ +import { useEffect, useState } from "react" + +export function useMediaQuery(query: string) { + const [value, setValue] = useState(false) + + useEffect(() => { + function onChange(event: MediaQueryListEvent) { + setValue(event.matches) + } + + const result = matchMedia(query) + result.addEventListener("change", onChange) + setValue(result.matches) + + return () => result.removeEventListener("change", onChange) + }, [query]) + + return value +} diff --git a/package.json b/package.json index 9f01d06..b763dd9 100644 --- a/package.json +++ b/package.json @@ -87,6 +87,7 @@ "tailwindcss-animate": "^1.0.7", "typescript": "5.4.5", "unist-util-visit": "^5.0.0", + "vaul": "^0.9.1", "zod": "^3.21.4", "zustand": "^4.5.2" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eb7b25f..3b27064 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -221,6 +221,9 @@ dependencies: unist-util-visit: specifier: ^5.0.0 version: 5.0.0 + vaul: + specifier: ^0.9.1 + version: 0.9.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0)(react@18.2.0) zod: specifier: ^3.21.4 version: 3.21.4 @@ -9583,6 +9586,20 @@ packages: sade: 1.8.1 dev: false + /vaul@0.9.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-fAhd7i4RNMinx+WEm6pF3nOl78DFkAazcN04ElLPFF9BMCNGbY/kou8UMhIcicm0rJCNePJP0Yyza60gGOD0Jw==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@radix-ui/react-dialog': 1.0.4(@types/react-dom@18.3.0)(@types/react@18.3.3)(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 + /vfile-location@4.1.0: resolution: {integrity: sha512-YF23YMyASIIJXpktBa4vIGLJ5Gs88UB/XePgqPmTa7cDA+JeO3yclbpheQYCHjVHBn/yePzrXuygIL+xbvRYHw==} dependencies: