Files
WooNooW/admin-spa/src/components/ui/responsive-dialog.tsx
dwindown b1b4f56b47 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)
2025-11-06 10:14:26 +07:00

73 lines
1.7 KiB
TypeScript

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>
)
}