Responsive Dialog
A dialog component that adapts to screen size - side dialog on desktop, drawer on mobile.
npx shadcn@latest add https://shuip.plvo.dev/r/responsive-dialog.json
pnpm dlx shadcn@latest add https://shuip.plvo.dev/r/responsive-dialog.json
bun x shadcn@latest add https://shuip.plvo.dev/r/responsive-dialog.json
'use client';import * as React from 'react';import {Drawer,DrawerClose,DrawerContent,DrawerDescription,DrawerFooter,DrawerHeader,DrawerTitle,DrawerTrigger,} from '@/components/ui/drawer';import {SideDialog,SideDialogBody,SideDialogClose,SideDialogContent,SideDialogDescription,SideDialogFooter,SideDialogHeader,SideDialogTitle,SideDialogTrigger,} from '@/components/ui/shuip/side-dialog';import { cn } from '@/lib/utils';type ResponsiveDialogPosition = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'left' | 'right';type ResponsiveDialogSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';type ResponsiveDialogBreakpoint = 'sm' | 'md' | 'lg' | 'xl' | number;export interface ResponsiveDialogProps {open?: boolean;onOpenChange?: (open: boolean) => void;position?: ResponsiveDialogPosition;size?: ResponsiveDialogSize;breakpoint?: ResponsiveDialogBreakpoint;children?: React.ReactNode;}export interface ResponsiveDialogTriggerProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {asChild?: boolean;children: React.ReactNode;}export interface ResponsiveDialogContentProps extends React.HTMLAttributes<HTMLDivElement> {showCloseButton?: boolean;children: React.ReactNode;}export interface ResponsiveDialogHeaderProps extends React.HTMLAttributes<HTMLDivElement> {children: React.ReactNode;}export interface ResponsiveDialogTitleProps extends React.HTMLAttributes<HTMLHeadingElement> {children: React.ReactNode;}export interface ResponsiveDialogDescriptionProps extends React.HTMLAttributes<HTMLParagraphElement> {children: React.ReactNode;}export interface ResponsiveDialogFooterProps extends React.HTMLAttributes<HTMLDivElement> {children: React.ReactNode;}export interface ResponsiveDialogCloseProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {asChild?: boolean;children?: React.ReactNode;}export interface ResponsiveDialogBodyProps extends React.HTMLAttributes<HTMLDivElement> {children: React.ReactNode;}// Helper function to convert breakpoint to pixelsconst getBreakpointValue = (breakpoint: ResponsiveDialogBreakpoint): number => {if (typeof breakpoint === 'number') {return breakpoint;}const breakpoints = {sm: 640,md: 768,lg: 1024,xl: 1280,};return breakpoints[breakpoint];};// Custom hook for responsive breakpoint detectionconst useCustomBreakpoint = (breakpoint: ResponsiveDialogBreakpoint) => {const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined);React.useEffect(() => {const breakpointValue = getBreakpointValue(breakpoint);const mql = window.matchMedia(`(max-width: ${breakpointValue - 1}px)`);const onChange = () => {setIsMobile(window.innerWidth < breakpointValue);};mql.addEventListener('change', onChange);setIsMobile(window.innerWidth < breakpointValue);return () => mql.removeEventListener('change', onChange);}, [breakpoint]);return !!isMobile;};// Context for managing responsive stateconst ResponsiveDialogContext = React.createContext<{isMobile: boolean;}>({isMobile: false,});export function useResponsiveDialog() {return React.useContext(ResponsiveDialogContext);}export function ResponsiveDialog({open,onOpenChange,position = 'bottom-right',size = 'sm',breakpoint = 'md',children,}: ResponsiveDialogProps) {const isMobile = useCustomBreakpoint(breakpoint);return (<ResponsiveDialogContext.Provider value={{ isMobile }}>{isMobile ? (<Drawer open={open} onOpenChange={onOpenChange} repositionInputs={false}>{children}</Drawer>) : (<SideDialog open={open} onOpenChange={onOpenChange} position={position} size={size}>{children}</SideDialog>)}</ResponsiveDialogContext.Provider>);}export function ResponsiveDialogTrigger({ asChild, children, ...props }: ResponsiveDialogTriggerProps) {const { isMobile } = React.useContext(ResponsiveDialogContext);if (isMobile) {return (<DrawerTrigger asChild={asChild} {...props}>{children}</DrawerTrigger>);}return (<SideDialogTrigger asChild={asChild} {...props}>{children}</SideDialogTrigger>);}export function ResponsiveDialogContent({showCloseButton = true,className,children,...props}: ResponsiveDialogContentProps) {const { isMobile } = React.useContext(ResponsiveDialogContext);if (isMobile) {return (<DrawerContent {...props}><div className={cn('flex flex-col max-h-[75dvh]', className)}>{children}</div></DrawerContent>);}return (<SideDialogContent showCloseButton={showCloseButton} className={className} {...props}>{children}</SideDialogContent>);}export function ResponsiveDialogHeader({ className, children, ...props }: ResponsiveDialogHeaderProps) {const { isMobile } = React.useContext(ResponsiveDialogContext);if (isMobile) {return (<DrawerHeader className={cn('shrink-0', className)} {...props}>{children}</DrawerHeader>);}return (<SideDialogHeader className={className} {...props}>{children}</SideDialogHeader>);}export function ResponsiveDialogTitle({ className, children, ...props }: ResponsiveDialogTitleProps) {const { isMobile } = React.useContext(ResponsiveDialogContext);if (isMobile) {return (<DrawerTitle className={className} {...props}>{children}</DrawerTitle>);}return (<SideDialogTitle className={className} {...props}>{children}</SideDialogTitle>);}export function ResponsiveDialogDescription({ className, children, ...props }: ResponsiveDialogDescriptionProps) {const { isMobile } = React.useContext(ResponsiveDialogContext);if (isMobile) {return (<DrawerDescription className={className} {...props}>{children}</DrawerDescription>);}return (<SideDialogDescription className={className} {...props}>{children}</SideDialogDescription>);}export function ResponsiveDialogFooter({ className, children, ...props }: ResponsiveDialogFooterProps) {const { isMobile } = React.useContext(ResponsiveDialogContext);if (isMobile) {return (<DrawerFooter className={cn('shrink-0', className)} {...props}>{children}</DrawerFooter>);}return (<SideDialogFooter className={className} {...props}>{children}</SideDialogFooter>);}export function ResponsiveDialogBody({ className, children, ...props }: ResponsiveDialogBodyProps) {const { isMobile } = React.useContext(ResponsiveDialogContext);if (isMobile) {return (<div className={cn('flex-1 min-h-0 overflow-y-auto overflow-x-hidden px-4 py-3', className)} {...props}>{children}</div>);}return (<SideDialogBody className={className} {...props}>{children}</SideDialogBody>);}export function ResponsiveDialogClose({ asChild, children, ...props }: ResponsiveDialogCloseProps) {const { isMobile } = React.useContext(ResponsiveDialogContext);if (isMobile) {return (<DrawerClose asChild={asChild} {...props}>{children}</DrawerClose>);}return (<SideDialogClose asChild={asChild} {...props}>{children}</SideDialogClose>);}
About
ResponsiveDialog is a smart dialog component that automatically adapts to the user's screen size:
- Desktop (≥1024px): Renders as a
SideDialogwith flexible positioning - Mobile (<1024px): Renders as a
Drawerfor better mobile UX
Unlike traditional dialogs that force the same experience across all devices, ResponsiveDialog provides the optimal interface for each screen size while maintaining a consistent API.
Built-in features
- Automatic responsive behavior: Switches between side dialog and drawer based on screen size
- 6 positions (desktop): Choose from
top-left,top-right,bottom-left,bottom-right,left,right - Flexible sizes (desktop): Predefined sizes (
xs,sm,md,lg,xl,2xl) - Scrollable body:
ResponsiveDialogBodykeeps the header and footer anchored while the content scrolls on both desktop and mobile - Mobile-optimized: Uses native drawer with swipe gestures on mobile
- Lightweight: Combines existing components without additional overhead
- Accessible: Inherits accessibility features from both SideDialog and Drawer
Usage
Basic responsive dialog
<ResponsiveDialog>
<ResponsiveDialogTrigger asChild>
<Button>Open Dialog</Button>
</ResponsiveDialogTrigger>
<ResponsiveDialogContent>
<ResponsiveDialogHeader>
<ResponsiveDialogTitle>Settings</ResponsiveDialogTitle>
<ResponsiveDialogDescription>
Manage your preferences and account settings.
</ResponsiveDialogDescription>
</ResponsiveDialogHeader>
<ResponsiveDialogFooter>
<ResponsiveDialogClose asChild>
<Button variant="outline">Cancel</Button>
</ResponsiveDialogClose>
<Button>Save</Button>
</ResponsiveDialogFooter>
</ResponsiveDialogContent>
</ResponsiveDialog>With scrollable content
Use ResponsiveDialogBody when the dialog content may overflow. The header and footer stay anchored while the body scrolls — on both desktop and mobile.
<ResponsiveDialogContent>
<ResponsiveDialogHeader>
<ResponsiveDialogTitle>Activity Log</ResponsiveDialogTitle>
<ResponsiveDialogDescription>Recent actions across your workspace.</ResponsiveDialogDescription>
</ResponsiveDialogHeader>
<ResponsiveDialogBody>
{/* long list, form, or any overflowing content */}
</ResponsiveDialogBody>
<ResponsiveDialogFooter>
<ResponsiveDialogClose asChild>
<Button variant="outline">Close</Button>
</ResponsiveDialogClose>
</ResponsiveDialogFooter>
</ResponsiveDialogContent>On mobile, ResponsiveDialogBody automatically adds horizontal padding (px-4 py-3) to match the drawer's layout. On desktop, padding comes from SideDialogContent.
Wrapper elements (e.g.
<form>): Any element wrappingHeader/Body/FooterinsideResponsiveDialogContentmust carryclassName="flex flex-col flex-1 min-h-0"so it participates in the flex layout. Without it the body has no bounded height and cannot scroll.<ResponsiveDialogContent> <form onSubmit={handleSubmit} className="flex flex-col flex-1 min-h-0"> <ResponsiveDialogHeader>...</ResponsiveDialogHeader> <ResponsiveDialogBody>...</ResponsiveDialogBody> <ResponsiveDialogFooter>...</ResponsiveDialogFooter> </form> </ResponsiveDialogContent>
Different positions (desktop only)
Desktop positions are ignored on mobile where the drawer behavior takes precedence:
// Notification from bottom-right
<ResponsiveDialog position="bottom-right">
<ResponsiveDialogTrigger asChild>
<Button>Show Notification</Button>
</ResponsiveDialogTrigger>
<ResponsiveDialogContent>
<ResponsiveDialogHeader>
<ResponsiveDialogTitle>Success!</ResponsiveDialogTitle>
<ResponsiveDialogDescription>Your changes have been saved.</ResponsiveDialogDescription>
</ResponsiveDialogHeader>
</ResponsiveDialogContent>
</ResponsiveDialog>
// User menu from top-right
<ResponsiveDialog position="top-right" size="sm">
<ResponsiveDialogTrigger asChild>
<Button variant="ghost" size="icon">
<User className="size-4" />
</Button>
</ResponsiveDialogTrigger>
<ResponsiveDialogContent>
<ResponsiveDialogHeader>
<ResponsiveDialogTitle>Account</ResponsiveDialogTitle>
</ResponsiveDialogHeader>
<div className="space-y-2">
<Button variant="ghost" className="w-full justify-start">
Profile Settings
</Button>
<Button variant="ghost" className="w-full justify-start">
Sign out
</Button>
</div>
</ResponsiveDialogContent>
</ResponsiveDialog>Custom breakpoints
Control when the dialog switches between desktop and mobile modes:
// Use predefined breakpoints
<ResponsiveDialog breakpoint="sm"> {/* Switches at 640px */}
<ResponsiveDialogTrigger asChild>
<Button>Small Breakpoint</Button>
</ResponsiveDialogTrigger>
<ResponsiveDialogContent>
<ResponsiveDialogHeader>
<ResponsiveDialogTitle>Mobile at 640px</ResponsiveDialogTitle>
<ResponsiveDialogDescription>
This dialog becomes a drawer on screens smaller than 640px.
</ResponsiveDialogDescription>
</ResponsiveDialogHeader>
</ResponsiveDialogContent>
</ResponsiveDialog>
// Use custom pixel values
<ResponsiveDialog breakpoint={900}>
<ResponsiveDialogTrigger asChild>
<Button>Custom Breakpoint</Button>
</ResponsiveDialogTrigger>
<ResponsiveDialogContent>
<ResponsiveDialogHeader>
<ResponsiveDialogTitle>Custom at 900px</ResponsiveDialogTitle>
<ResponsiveDialogDescription>
Switches to mobile mode at exactly 900px and below.
</ResponsiveDialogDescription>
</ResponsiveDialogHeader>
</ResponsiveDialogContent>
</ResponsiveDialog>Different sizes (desktop only)
Size props affect the desktop side dialog appearance:
// Small dialog for quick actions
<ResponsiveDialog size="xs">
<ResponsiveDialogTrigger asChild>
<Button variant="outline">Quick Action</Button>
</ResponsiveDialogTrigger>
<ResponsiveDialogContent>
<ResponsiveDialogHeader>
<ResponsiveDialogTitle>Confirm</ResponsiveDialogTitle>
<ResponsiveDialogDescription>Are you sure?</ResponsiveDialogDescription>
</ResponsiveDialogHeader>
</ResponsiveDialogContent>
</ResponsiveDialog>
// Large dialog for complex content
<ResponsiveDialog position="left" size="lg">
<ResponsiveDialogTrigger asChild>
<Button variant="outline">Navigation Menu</Button>
</ResponsiveDialogTrigger>
<ResponsiveDialogContent>
<ResponsiveDialogHeader>
<ResponsiveDialogTitle>Navigation</ResponsiveDialogTitle>
</ResponsiveDialogHeader>
<nav className="space-y-2">
<Link href="/dashboard">Dashboard</Link>
<Link href="/projects">Projects</Link>
<Link href="/settings">Settings</Link>
</nav>
</ResponsiveDialogContent>
</ResponsiveDialog>Common use cases
Mobile-first navigation
<ResponsiveDialog position="left" size="lg">
<ResponsiveDialogTrigger asChild>
<Button variant="outline" size="icon">
<Menu className="size-4" />
</Button>
</ResponsiveDialogTrigger>
<ResponsiveDialogContent>
<ResponsiveDialogHeader>
<ResponsiveDialogTitle>Menu</ResponsiveDialogTitle>
</ResponsiveDialogHeader>
<nav className="space-y-3">
<Link href="/home" className="block p-2 hover:bg-muted rounded">
Home
</Link>
<Link href="/about" className="block p-2 hover:bg-muted rounded">
About
</Link>
<Link href="/contact" className="block p-2 hover:bg-muted rounded">
Contact
</Link>
</nav>
</ResponsiveDialogContent>
</ResponsiveDialog>Examples
Default
'use client';import { zodResolver } from '@hookform/resolvers/zod';import React from 'react';import { useForm } from 'react-hook-form';import z from 'zod';import {ResponsiveDialog,ResponsiveDialogBody,ResponsiveDialogClose,ResponsiveDialogContent,ResponsiveDialogDescription,ResponsiveDialogFooter,ResponsiveDialogHeader,ResponsiveDialogTitle,ResponsiveDialogTrigger,} from '@/components/block/shuip/responsive-dialog';import { Button } from '@/components/ui/button';import { Form } from '@/components/ui/form';import { InputField } from '@/components/ui/shuip/react-hook-form/input-field';import { SubmitButton } from '@/components/ui/shuip/submit-button';export default function ResponsiveDialogExample() {return (<div className='space-y-4'><h3 className='text-lg font-semibold'>Basic Responsive Dialog</h3><div className='flex flex-wrap gap-2'><ResponsiveDialog><ResponsiveDialogTrigger asChild><Button variant='outline'>Open Dialog</Button></ResponsiveDialogTrigger><ResponsiveDialogContent><ResponsiveDialogHeader><ResponsiveDialogTitle>Responsive Dialog</ResponsiveDialogTitle><ResponsiveDialogDescription>This dialog adapts to your screen size. On desktop, it's a side dialog. On mobile, it's a drawer.</ResponsiveDialogDescription></ResponsiveDialogHeader><div className='p-4'><p className='text-sm text-muted-foreground'>Try resizing your browser window or switching to mobile view to see the responsive behavior.</p></div><ResponsiveDialogFooter><ResponsiveDialogClose asChild><Button variant='outline'>Close</Button></ResponsiveDialogClose><Button>Continue</Button></ResponsiveDialogFooter></ResponsiveDialogContent></ResponsiveDialog></div><div className='space-y-2'><h3 className='text-lg font-semibold'>Different Positions (Desktop only)</h3><div className='flex flex-wrap gap-2'><ResponsiveDialog position='top-left'><ResponsiveDialogTrigger asChild><Button variant='outline'>Top Left</Button></ResponsiveDialogTrigger><ResponsiveDialogContent><ResponsiveDialogHeader><ResponsiveDialogTitle>Top Left Dialog</ResponsiveDialogTitle><ResponsiveDialogDescription>Positioned at top left on desktop.</ResponsiveDialogDescription></ResponsiveDialogHeader></ResponsiveDialogContent></ResponsiveDialog><ResponsiveDialog position='top-right'><ResponsiveDialogTrigger asChild><Button variant='outline'>Top Right</Button></ResponsiveDialogTrigger><ResponsiveDialogContent><ResponsiveDialogHeader><ResponsiveDialogTitle>Top Right Dialog</ResponsiveDialogTitle><ResponsiveDialogDescription>Positioned at top right on desktop.</ResponsiveDialogDescription></ResponsiveDialogHeader></ResponsiveDialogContent></ResponsiveDialog><ResponsiveDialog position='bottom-left'><ResponsiveDialogTrigger asChild><Button variant='outline'>Bottom Left</Button></ResponsiveDialogTrigger><ResponsiveDialogContent><ResponsiveDialogHeader><ResponsiveDialogTitle>Bottom Left Dialog</ResponsiveDialogTitle><ResponsiveDialogDescription>Positioned at bottom left on desktop.</ResponsiveDialogDescription></ResponsiveDialogHeader></ResponsiveDialogContent></ResponsiveDialog><ResponsiveDialog position='bottom-right'><ResponsiveDialogTrigger asChild><Button variant='outline'>Bottom Right</Button></ResponsiveDialogTrigger><ResponsiveDialogContent><ResponsiveDialogHeader><ResponsiveDialogTitle>Bottom Right Dialog</ResponsiveDialogTitle><ResponsiveDialogDescription>Positioned at bottom right on desktop.</ResponsiveDialogDescription></ResponsiveDialogHeader></ResponsiveDialogContent></ResponsiveDialog><ResponsiveDialog position='left'><ResponsiveDialogTrigger asChild><Button variant='outline'>Left</Button></ResponsiveDialogTrigger><ResponsiveDialogContent><ResponsiveDialogHeader><ResponsiveDialogTitle>Left Dialog</ResponsiveDialogTitle><ResponsiveDialogDescription>Positioned at left side on desktop.</ResponsiveDialogDescription></ResponsiveDialogHeader></ResponsiveDialogContent></ResponsiveDialog><ResponsiveDialog position='right'><ResponsiveDialogTrigger asChild><Button variant='outline'>Right</Button></ResponsiveDialogTrigger><ResponsiveDialogContent><ResponsiveDialogHeader><ResponsiveDialogTitle>Right Dialog</ResponsiveDialogTitle><ResponsiveDialogDescription>Positioned at right side on desktop.</ResponsiveDialogDescription></ResponsiveDialogHeader></ResponsiveDialogContent></ResponsiveDialog></div></div><div className='space-y-2'><h3 className='text-lg font-semibold'>Different Sizes (Desktop only)</h3><div className='flex flex-wrap gap-2'><ResponsiveDialog size='xs'><ResponsiveDialogTrigger asChild><Button variant='outline'>Extra Small</Button></ResponsiveDialogTrigger><ResponsiveDialogContent><ResponsiveDialogHeader><ResponsiveDialogTitle>XS Dialog</ResponsiveDialogTitle><ResponsiveDialogDescription>Extra small dialog size.</ResponsiveDialogDescription></ResponsiveDialogHeader></ResponsiveDialogContent></ResponsiveDialog><ResponsiveDialog size='sm'><ResponsiveDialogTrigger asChild><Button variant='outline'>Small</Button></ResponsiveDialogTrigger><ResponsiveDialogContent><ResponsiveDialogHeader><ResponsiveDialogTitle>Small Dialog</ResponsiveDialogTitle><ResponsiveDialogDescription>Small dialog size (default).</ResponsiveDialogDescription></ResponsiveDialogHeader></ResponsiveDialogContent></ResponsiveDialog><ResponsiveDialog size='md'><ResponsiveDialogTrigger asChild><Button variant='outline'>Medium</Button></ResponsiveDialogTrigger><ResponsiveDialogContent><ResponsiveDialogHeader><ResponsiveDialogTitle>Medium Dialog</ResponsiveDialogTitle><ResponsiveDialogDescription>Medium dialog size.</ResponsiveDialogDescription></ResponsiveDialogHeader></ResponsiveDialogContent></ResponsiveDialog><ResponsiveDialog size='lg'><ResponsiveDialogTrigger asChild><Button variant='outline'>Large</Button></ResponsiveDialogTrigger><ResponsiveDialogContent><ResponsiveDialogHeader><ResponsiveDialogTitle>Large Dialog</ResponsiveDialogTitle><ResponsiveDialogDescription>Large dialog size for more content.</ResponsiveDialogDescription></ResponsiveDialogHeader></ResponsiveDialogContent></ResponsiveDialog><ResponsiveDialog size='xl'><ResponsiveDialogTrigger asChild><Button variant='outline'>Extra Large</Button></ResponsiveDialogTrigger><ResponsiveDialogContent><ResponsiveDialogHeader><ResponsiveDialogTitle>XL Dialog</ResponsiveDialogTitle><ResponsiveDialogDescription>Extra large dialog size.</ResponsiveDialogDescription></ResponsiveDialogHeader></ResponsiveDialogContent></ResponsiveDialog><ResponsiveDialog size='2xl'><ResponsiveDialogTrigger asChild><Button variant='outline'>2X Large</Button></ResponsiveDialogTrigger><ResponsiveDialogContent><ResponsiveDialogHeader><ResponsiveDialogTitle>2XL Dialog</ResponsiveDialogTitle><ResponsiveDialogDescription>2X large dialog size for extensive content.</ResponsiveDialogDescription></ResponsiveDialogHeader></ResponsiveDialogContent></ResponsiveDialog></div></div><div className='space-y-2'><h3 className='text-lg font-semibold'>Custom Breakpoints</h3><div className='flex flex-wrap gap-2'><ResponsiveDialog breakpoint='sm'><ResponsiveDialogTrigger asChild><Button variant='outline'>Breakpoint SM (640px)</Button></ResponsiveDialogTrigger><ResponsiveDialogContent><ResponsiveDialogHeader><ResponsiveDialogTitle>Small Breakpoint</ResponsiveDialogTitle><ResponsiveDialogDescription>This dialog switches to mobile mode at 640px and below.</ResponsiveDialogDescription></ResponsiveDialogHeader></ResponsiveDialogContent></ResponsiveDialog><ResponsiveDialog breakpoint='md'><ResponsiveDialogTrigger asChild><Button variant='outline'>Breakpoint MD (768px)</Button></ResponsiveDialogTrigger><ResponsiveDialogContent><ResponsiveDialogHeader><ResponsiveDialogTitle>Medium Breakpoint</ResponsiveDialogTitle><ResponsiveDialogDescription>This dialog switches to mobile mode at 768px and below (default).</ResponsiveDialogDescription></ResponsiveDialogHeader></ResponsiveDialogContent></ResponsiveDialog><ResponsiveDialog breakpoint='lg'><ResponsiveDialogTrigger asChild><Button variant='outline'>Breakpoint LG (1024px)</Button></ResponsiveDialogTrigger><ResponsiveDialogContent><ResponsiveDialogHeader><ResponsiveDialogTitle>Large Breakpoint</ResponsiveDialogTitle><ResponsiveDialogDescription>This dialog switches to mobile mode at 1024px and below.</ResponsiveDialogDescription></ResponsiveDialogHeader></ResponsiveDialogContent></ResponsiveDialog><ResponsiveDialog breakpoint={900}><ResponsiveDialogTrigger asChild><Button variant='outline'>Custom 900px</Button></ResponsiveDialogTrigger><ResponsiveDialogContent><ResponsiveDialogHeader><ResponsiveDialogTitle>Custom Breakpoint</ResponsiveDialogTitle><ResponsiveDialogDescription>This dialog switches to mobile mode at exactly 900px and below.</ResponsiveDialogDescription></ResponsiveDialogHeader></ResponsiveDialogContent></ResponsiveDialog></div></div><h3 className='text-lg font-semibold'>Form Example</h3><ResponsiveDialogFormExample /></div>);}const formSchema = z.object({name: z.string().min(1),email: z.string().email(),});function ResponsiveDialogFormExample() {const [open, setOpen] = React.useState(false);const form = useForm({resolver: zodResolver(formSchema),});const onSubmit = async (data: z.infer<typeof formSchema>) => {alert(JSON.stringify(data));setOpen(false);form.reset();};return (<ResponsiveDialog open={open} onOpenChange={setOpen}><ResponsiveDialogTrigger asChild><Button variant='outline'>Open Dialog</Button></ResponsiveDialogTrigger><ResponsiveDialogContent><Form {...form}><form onSubmit={form.handleSubmit(onSubmit)}><ResponsiveDialogHeader><ResponsiveDialogTitle>Responsive Dialog</ResponsiveDialogTitle><ResponsiveDialogDescription>This dialog adapts to your screen size. On desktop, it's a side dialog. On mobile, it's a drawer.</ResponsiveDialogDescription></ResponsiveDialogHeader><ResponsiveDialogBody className='space-y-4'><InputField register={form.register('name')} name='name' label='Name' /><InputField register={form.register('email')} name='email' label='Email' /></ResponsiveDialogBody><ResponsiveDialogFooter><ResponsiveDialogClose asChild><Button variant='outline'>Close</Button></ResponsiveDialogClose><SubmitButton className='max-md:w-full'>Submit</SubmitButton></ResponsiveDialogFooter></form></Form></ResponsiveDialogContent></ResponsiveDialog>);}
Scrollable
'use client';import { zodResolver } from '@hookform/resolvers/zod';import React from 'react';import { useForm } from 'react-hook-form';import z from 'zod';import {ResponsiveDialog,ResponsiveDialogBody,ResponsiveDialogClose,ResponsiveDialogContent,ResponsiveDialogDescription,ResponsiveDialogFooter,ResponsiveDialogHeader,ResponsiveDialogTitle,ResponsiveDialogTrigger,} from '@/components/block/shuip/responsive-dialog';import { Button } from '@/components/ui/button';import { Form } from '@/components/ui/form';import { Separator } from '@/components/ui/separator';import { InputField } from '@/components/ui/shuip/react-hook-form/input-field';import { SelectField } from '@/components/ui/shuip/react-hook-form/select-field';import { TextareaField } from '@/components/ui/shuip/react-hook-form/textarea-field';import { SubmitButton } from '@/components/ui/shuip/submit-button';const ACTIVITY_LOG = [{ id: 1, user: 'Alice Martin', action: 'Updated billing address', time: '2 minutes ago', type: 'edit' },{ id: 2, user: 'Bob Chen', action: 'Exported 847 records to CSV', time: '14 minutes ago', type: 'export' },{ id: 3, user: 'Alice Martin', action: 'Invited carol@example.com', time: '1 hour ago', type: 'invite' },{ id: 4, user: 'System', action: 'Scheduled backup completed', time: '2 hours ago', type: 'system' },{ id: 5, user: 'Dave Wilson', action: 'Deleted workspace "Legacy"', time: '3 hours ago', type: 'delete' },{ id: 6, user: 'Alice Martin', action: 'Changed plan to Pro', time: '5 hours ago', type: 'edit' },{ id: 7, user: 'Bob Chen', action: 'Created API key "Production"', time: '6 hours ago', type: 'create' },{ id: 8, user: 'System', action: 'SSL certificate renewed', time: 'Yesterday at 11:42 PM', type: 'system' },{ id: 9, user: 'Dave Wilson', action: 'Added 3 new team members', time: 'Yesterday at 4:12 PM', type: 'invite' },{ id: 10, user: 'Carol Lee', action: 'Reset 2FA for bob@example.com', time: 'Yesterday at 2:05 PM', type: 'edit' },{ id: 11, user: 'Alice Martin', action: 'Enabled SSO for domain acme.com', time: '2 days ago', type: 'edit' },{ id: 12, user: 'System', action: 'Monthly invoice generated — $429.00', time: '3 days ago', type: 'system' },{ id: 13, user: 'Bob Chen', action: 'Revoked API key "Staging"', time: '3 days ago', type: 'delete' },{ id: 14, user: 'Carol Lee', action: 'Updated webhook endpoint', time: '4 days ago', type: 'edit' },{ id: 15, user: 'Dave Wilson', action: 'Archived project "Q3 Campaign"', time: '5 days ago', type: 'edit' },];const TYPE_COLORS: Record<string, string> = {edit: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400',export: 'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400',invite: 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400',system: 'bg-muted text-muted-foreground',delete: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400',create: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400',};export default function ResponsiveDialogScrollableExample() {return (<div className='space-y-4'><h3 className='text-lg font-semibold'>Scrollable Content</h3><div className='flex flex-wrap gap-2'><ResponsiveDialog position='bottom-right' size='md'><ResponsiveDialogTrigger asChild><Button variant='outline'>Activity Log</Button></ResponsiveDialogTrigger><ResponsiveDialogContent><ResponsiveDialogHeader><ResponsiveDialogTitle>Activity Log</ResponsiveDialogTitle><ResponsiveDialogDescription>Recent actions across your workspace.</ResponsiveDialogDescription></ResponsiveDialogHeader><ResponsiveDialogBody className='space-y-1'>{ACTIVITY_LOG.map((entry, i) => (<div key={entry.id}><div className='flex items-start gap-3 py-2.5'><spanclassName={`mt-0.5 shrink-0 rounded px-1.5 py-0.5 text-xs font-medium capitalize ${TYPE_COLORS[entry.type]}`}>{entry.type}</span><div className='min-w-0 flex-1'><p className='text-sm leading-snug'>{entry.action}</p><p className='text-muted-foreground mt-0.5 text-xs'>{entry.user} · {entry.time}</p></div></div>{i < ACTIVITY_LOG.length - 1 && <Separator />}</div>))}</ResponsiveDialogBody><ResponsiveDialogFooter><ResponsiveDialogClose asChild><Button variant='outline' className='max-md:w-full'>Close</Button></ResponsiveDialogClose></ResponsiveDialogFooter></ResponsiveDialogContent></ResponsiveDialog><LongFormDialog /></div></div>);}const longFormSchema = z.object({firstName: z.string().min(1),lastName: z.string().min(1),email: z.string().email(),phone: z.string().optional(),company: z.string().optional(),role: z.string().min(1),country: z.string().min(1),bio: z.string().optional(),});function LongFormDialog() {const [open, setOpen] = React.useState(false);const form = useForm({resolver: zodResolver(longFormSchema),});const onSubmit = async (data: z.infer<typeof longFormSchema>) => {await new Promise((resolve) => setTimeout(resolve, 1000));alert(JSON.stringify(data, null, 2));setOpen(false);form.reset();};return (<ResponsiveDialog open={open} onOpenChange={setOpen} position='right' size='md'><ResponsiveDialogTrigger asChild><Button variant='outline'>Long Form</Button></ResponsiveDialogTrigger><ResponsiveDialogContent><Form {...form}><form onSubmit={form.handleSubmit(onSubmit)} className='flex flex-col flex-1 min-h-0'><ResponsiveDialogHeader><ResponsiveDialogTitle>Create Profile</ResponsiveDialogTitle><ResponsiveDialogDescription>Fill in your details to create your team profile.</ResponsiveDialogDescription></ResponsiveDialogHeader><ResponsiveDialogBody className='space-y-4'><div className='grid grid-cols-2 gap-3'><InputField register={form.register('firstName')} name='firstName' label='First name' /><InputField register={form.register('lastName')} name='lastName' label='Last name' /></div><InputField register={form.register('email')} name='email' label='Email' type='email' /><InputField register={form.register('phone')} name='phone' label='Phone' type='tel' /><InputField register={form.register('company')} name='company' label='Company' /><SelectFieldregister={form.register('role')}name='role'label='Role'options={{Admin: 'admin',Developer: 'developer',Designer: 'designer',Manager: 'manager',Viewer: 'viewer',}}/><SelectFieldregister={form.register('country')}name='country'label='Country'options={{France: 'fr',Germany: 'de','United States': 'us','United Kingdom': 'gb',Canada: 'ca',Australia: 'au',}}/><TextareaField register={form.register('bio')} name='bio' label='Bio' rows={4} /></ResponsiveDialogBody><ResponsiveDialogFooter><ResponsiveDialogClose asChild><Button variant='outline' className='max-md:w-full'>Cancel</Button></ResponsiveDialogClose><SubmitButton className='max-md:w-full'>Create Profile</SubmitButton></ResponsiveDialogFooter></form></Form></ResponsiveDialogContent></ResponsiveDialog>);}
Props
Prop
Type
ResponsiveDialogBody Props
Prop
Type