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 (
-
+
+
+
+
+
)
}
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: