feat: Add responsive Dialog/Drawer pattern
Created responsive dialog pattern for better mobile UX:
Components Added:
1. drawer.tsx - Vaul-based drawer component (bottom sheet)
2. responsive-dialog.tsx - Smart wrapper that switches based on screen size
3. use-media-query.ts - Hook to detect screen size
Pattern:
- Desktop (≥768px): Use Dialog (modal overlay)
- Mobile (<768px): Use Drawer (bottom sheet)
- Provides consistent API for both
Usage Example:
<ResponsiveDialog
open={isOpen}
onOpenChange={setIsOpen}
title="Settings"
description="Configure your options"
footer={<Button>Save</Button>}
>
<FormContent />
</ResponsiveDialog>
Benefits:
- Better mobile UX with native-feeling bottom sheet
- Easier to reach buttons on mobile
- Consistent desktop experience
- Single component API
Dependencies:
- npm install vaul (drawer library)
- @radix-ui/react-dialog (already installed)
Next Steps:
- Convert payment gateway modal to use ResponsiveDialog
- Use AlertDialog for confirmations
- Apply pattern to other modals in project
Note: Payment gateway modal needs custom implementation
due to complex layout (scrollable body + sticky footer)
This commit is contained in:
72
admin-spa/src/components/ui/responsive-dialog.tsx
Normal file
72
admin-spa/src/components/ui/responsive-dialog.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import * as React from "react"
|
||||
import { useMediaQuery } from "@/hooks/use-media-query"
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog"
|
||||
import {
|
||||
Drawer,
|
||||
DrawerClose,
|
||||
DrawerContent,
|
||||
DrawerDescription,
|
||||
DrawerFooter,
|
||||
DrawerHeader,
|
||||
DrawerTitle,
|
||||
} from "@/components/ui/drawer"
|
||||
|
||||
interface ResponsiveDialogProps {
|
||||
open: boolean
|
||||
onOpenChange: (open: boolean) => void
|
||||
children: React.ReactNode
|
||||
title?: string
|
||||
description?: string
|
||||
footer?: React.ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function ResponsiveDialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
children,
|
||||
title,
|
||||
description,
|
||||
footer,
|
||||
className,
|
||||
}: ResponsiveDialogProps) {
|
||||
const isDesktop = useMediaQuery("(min-width: 768px)")
|
||||
|
||||
if (isDesktop) {
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className={className}>
|
||||
{(title || description) && (
|
||||
<DialogHeader>
|
||||
{title && <DialogTitle>{title}</DialogTitle>}
|
||||
{description && <DialogDescription>{description}</DialogDescription>}
|
||||
</DialogHeader>
|
||||
)}
|
||||
{children}
|
||||
{footer}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Drawer open={open} onOpenChange={onOpenChange}>
|
||||
<DrawerContent className={className}>
|
||||
{(title || description) && (
|
||||
<DrawerHeader className="text-left">
|
||||
{title && <DrawerTitle>{title}</DrawerTitle>}
|
||||
{description && <DrawerDescription>{description}</DrawerDescription>}
|
||||
</DrawerHeader>
|
||||
)}
|
||||
<div className="px-4">{children}</div>
|
||||
{footer && <DrawerFooter>{footer}</DrawerFooter>}
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user