Side Dialog
A lightweight dialog that opens from the sides of the screen, perfect for notifications, user menus, and contextual actions.
npx shadcn@latest add https://shuip.plvo.dev/r/side-dialog.json
pnpm dlx shadcn@latest add https://shuip.plvo.dev/r/side-dialog.json
bun x shadcn@latest add https://shuip.plvo.dev/r/side-dialog.json
'use client';import { XIcon } from 'lucide-react';import * as React from 'react';import { createPortal } from 'react-dom';import { cn } from '@/lib/utils';type SideDialogPosition = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'left' | 'right';type SideDialogSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';export interface SideDialogProps {open?: boolean;onOpenChange?: (open: boolean) => void;position?: SideDialogPosition;size?: SideDialogSize;children?: React.ReactNode;}export interface SideDialogTriggerProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {asChild?: boolean;children: React.ReactNode;}export interface SideDialogContentProps extends React.HTMLAttributes<HTMLDivElement> {showCloseButton?: boolean;children: React.ReactNode;}export interface SideDialogHeaderProps extends React.HTMLAttributes<HTMLDivElement> {children: React.ReactNode;}export interface SideDialogTitleProps extends React.HTMLAttributes<HTMLHeadingElement> {children: React.ReactNode;}export interface SideDialogDescriptionProps extends React.HTMLAttributes<HTMLParagraphElement> {children: React.ReactNode;}export interface SideDialogFooterProps extends React.HTMLAttributes<HTMLDivElement> {children: React.ReactNode;}export interface SideDialogCloseProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {asChild?: boolean;children?: React.ReactNode;}export interface SideDialogBodyProps extends React.HTMLAttributes<HTMLDivElement> {children: React.ReactNode;}// Context for managing dialog stateconst SideDialogContext = React.createContext<{open: boolean;onOpenChange: (open: boolean) => void;position: SideDialogPosition;size: SideDialogSize;}>({open: false,onOpenChange: () => {},position: 'bottom-right',size: 'sm',});export function SideDialog({open: controlledOpen,onOpenChange: controlledOnOpenChange,position = 'bottom-right',size = 'sm',children,}: SideDialogProps) {// Internal state for uncontrolled modeconst [internalOpen, setInternalOpen] = React.useState(false);// Use controlled props if provided, otherwise use internal stateconst open = controlledOpen !== undefined ? controlledOpen : internalOpen;const onOpenChange = controlledOnOpenChange !== undefined ? controlledOnOpenChange : setInternalOpen;const contextValue = React.useMemo(() => ({open,onOpenChange,position,size,}),[open, onOpenChange, position, size],);return <SideDialogContext.Provider value={contextValue}>{children}</SideDialogContext.Provider>;}export function SideDialogTrigger({ asChild, children, onClick, ...props }: SideDialogTriggerProps) {const { onOpenChange } = React.useContext(SideDialogContext);const handleClick = React.useCallback((e: React.MouseEvent<HTMLButtonElement>) => {onClick?.(e);onOpenChange(true);},[onClick, onOpenChange],);if (asChild) {return React.cloneElement(children as React.ReactElement, {onClick: handleClick,...(props as React.ButtonHTMLAttributes<HTMLButtonElement>),});}return (<button data-slot='side-dialog-trigger' onClick={handleClick} {...props}>{children}</button>);}export function SideDialogContent({ showCloseButton = true, className, children, ...props }: SideDialogContentProps) {const { open, onOpenChange, position, size } = React.useContext(SideDialogContext);const [mounted, setMounted] = React.useState(false);React.useEffect(() => {setMounted(true);}, []);React.useEffect(() => {const handleEscape = (e: KeyboardEvent) => {if (e.key === 'Escape' && open) {onOpenChange(false);}};if (open) {document.addEventListener('keydown', handleEscape);document.body.style.overflow = 'hidden';}return () => {document.removeEventListener('keydown', handleEscape);document.body.style.overflow = '';};}, [open, onOpenChange]);const handleOverlayClick = React.useCallback((e: React.MouseEvent) => {if (e.target === e.currentTarget) {onOpenChange(false);}},[onOpenChange],);const getSizeClasses = (size: SideDialogSize) => {const predefinedSizes = {xs: 'w-full max-w-[min(20rem,calc(100vw-2rem))]',sm: 'w-full max-w-[min(24rem,calc(100vw-2rem))]',md: 'w-full max-w-[min(28rem,calc(100vw-2rem))]',lg: 'w-full max-w-[min(32rem,calc(100vw-2rem))]',xl: 'w-full max-w-[min(36rem,calc(100vw-2rem))]','2xl': 'w-full max-w-[min(42rem,calc(100vw-2rem))]',};return predefinedSizes[size as keyof typeof predefinedSizes] || 'w-full max-w-[min(24rem,calc(100vw-2rem))]';};const getPositionClasses = (pos: SideDialogPosition, size: SideDialogSize) => {const baseClasses = 'fixed z-[100] bg-background border rounded-lg shadow-lg';const sizeClasses = getSizeClasses(size);switch (pos) {case 'top-left':return cn(baseClasses,sizeClasses,'top-4 left-4','data-[state=open]:animate-in data-[state=closed]:animate-out','data-[state=closed]:slide-out-to-top-52 data-[state=closed]:slide-out-to-left-52','data-[state=open]:slide-in-from-top-52 data-[state=open]:slide-in-from-left-52',);case 'top-right':return cn(baseClasses,sizeClasses,'top-4 right-4','data-[state=open]:animate-in data-[state=closed]:animate-out','data-[state=closed]:slide-out-to-top-52 data-[state=closed]:slide-out-to-right-52','data-[state=open]:slide-in-from-top-52 data-[state=open]:slide-in-from-right-52',);case 'bottom-left':return cn(baseClasses,sizeClasses,'bottom-4 left-4','data-[state=open]:animate-in data-[state=closed]:animate-out','data-[state=closed]:slide-out-to-bottom-52 data-[state=closed]:slide-out-to-left-52','data-[state=open]:slide-in-from-bottom-52 data-[state=open]:slide-in-from-left-52',);case 'bottom-right':return cn(baseClasses,sizeClasses,'bottom-4 right-4','data-[state=open]:animate-in data-[state=closed]:animate-out','data-[state=closed]:slide-out-to-bottom-52 data-[state=closed]:slide-out-to-right-52','data-[state=open]:slide-in-from-bottom-52 data-[state=open]:slide-in-from-right-52',);case 'left':return cn(baseClasses,sizeClasses,'left-4 top-[50%] translate-y-[-50%]','data-[state=open]:animate-in data-[state=closed]:animate-out','data-[state=closed]:slide-out-to-left-52','data-[state=open]:slide-in-from-left-52',);case 'right':return cn(baseClasses,sizeClasses,'right-4 top-[50%] translate-y-[-50%]','data-[state=open]:animate-in data-[state=closed]:animate-out','data-[state=closed]:slide-out-to-right-52','data-[state=open]:slide-in-from-right-52',);default:return cn(baseClasses, sizeClasses);}};if (!mounted) return null;if (!open) return null;return createPortal(<divdata-slot='side-dialog-overlay'className='fixed inset-0 z-40 bg-black/50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0'data-state={open ? 'open' : 'closed'}onClick={handleOverlayClick}><divdata-slot='side-dialog-content'data-state={open ? 'open' : 'closed'}className={cn(getPositionClasses(position, size),'flex flex-col max-h-[calc(100dvh-2rem)] p-6 duration-200',className,)}{...props}onClick={(e) => {e.stopPropagation();props.onClick?.(e);}}>{children}{showCloseButton && (<SideDialogClose className='absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden'><XIcon className='size-4' /><span className='sr-only'>Close</span></SideDialogClose>)}</div></div>,document.body,);}export function SideDialogHeader({ className, children, ...props }: SideDialogHeaderProps) {return (<div data-slot='side-dialog-header' className={cn('shrink-0 flex flex-col gap-2 mb-4', className)} {...props}>{children}</div>);}export function SideDialogTitle({ className, children, ...props }: SideDialogTitleProps) {return (<h2 data-slot='side-dialog-title' className={cn('text-lg font-semibold leading-none', className)} {...props}>{children}</h2>);}export function SideDialogDescription({ className, children, ...props }: SideDialogDescriptionProps) {return (<p data-slot='side-dialog-description' className={cn('text-muted-foreground text-sm', className)} {...props}>{children}</p>);}export function SideDialogFooter({ className, children, ...props }: SideDialogFooterProps) {return (<divdata-slot='side-dialog-footer'className={cn('shrink-0 flex flex-col-reverse gap-2 mt-4 sm:flex-row sm:justify-end', className)}{...props}>{children}</div>);}export function SideDialogBody({ className, children, ...props }: SideDialogBodyProps) {return (<div data-slot='side-dialog-body' className={cn('flex-1 min-h-0 overflow-y-auto', className)} {...props}>{children}</div>);}export function SideDialogClose({ asChild, children, onClick, ...props }: SideDialogCloseProps) {const { onOpenChange } = React.useContext(SideDialogContext);const handleClick = React.useCallback((e: React.MouseEvent<HTMLButtonElement>) => {onClick?.(e);onOpenChange(false);},[onClick, onOpenChange],);if (asChild && children) {return React.cloneElement(children as React.ReactElement, {onClick: handleClick,...(props as React.HTMLAttributes<HTMLButtonElement>),});}return (<button data-slot='side-dialog-close' onClick={handleClick} {...props}>{children}</button>);}
Contextual positioning
Unlike traditional centered dialogs, SideDialog provides a more contextual user experience by opening from the edges of the screen, perfect for notifications, user menus, and contextual actions.
Built-in features
- 6 positions: Choose from
top-left,top-right,bottom-left,bottom-right,left,right - Flexible sizes: Predefined sizes (
xs,sm,md,lg,xl,2xl) - Scrollable body:
SideDialogBodykeeps the header and footer anchored while the content scrolls - Lightweight: Custom implementation without depending on shadcn dialog
- Accessible: Keyboard support (Escape), focus management, and ARIA compliance
- Portal rendering: Renders in
document.bodyto avoid z-index issues - Smooth animations: Entry/exit animations with Tailwind CSS
- Controlled & Uncontrolled: Works with or without state management
Position options
The position prop controls where the dialog appears:
// Corner positions
<SideDialog position="top-left"> {/* Top left corner */}
<SideDialog position="top-right"> {/* Top right corner */}
<SideDialog position="bottom-left"> {/* Bottom left corner */}
<SideDialog position="bottom-right"> {/* Bottom right corner (default) */}
// Side positions (vertically centered)
<SideDialog position="left"> {/* Left side, centered */}
<SideDialog position="right"> {/* Right side, centered */}Size options
The size prop controls the dialog size with predefined responsive values:
// Predefined sizes (all automatically responsive)
<SideDialog size="xs"> {/* 320px max-width, adapts to small screens */}
<SideDialog size="sm"> {/* 384px max-width (default) */}
<SideDialog size="md"> {/* 448px max-width */}
<SideDialog size="lg"> {/* 512px max-width */}
<SideDialog size="xl"> {/* 576px max-width */}
<SideDialog size="2xl"> {/* 672px max-width */}
// For custom sizes, use className with responsive constraints
<SideDialogContent className="w-full max-w-[min(400px,calc(100vw-2rem))]">
{/* Custom size with responsive protection */}
</SideDialogContent>Responsive behavior
All predefined sizes automatically include responsive constraints using CSS min() function:
- Desktop: Uses the specified max-width (e.g.
lg= 32rem/512px) - Small screens: Automatically reduces to
calc(100vw - 2rem)when viewport is smaller - Never overflows: The dialog will always fit within the viewport with 1rem margin on each side
- CSS native: Uses
min(targetSize, calc(100vw - 2rem))for optimal performance
Scrollable content
Use SideDialogBody when the dialog content may overflow. The header and footer stay anchored while the body scrolls independently.
<SideDialogContent>
<SideDialogHeader>
<SideDialogTitle>Activity Log</SideDialogTitle>
<SideDialogDescription>Recent actions across your workspace.</SideDialogDescription>
</SideDialogHeader>
<SideDialogBody>
{/* long list or form content */}
</SideDialogBody>
<SideDialogFooter>
<SideDialogClose asChild>
<Button variant="outline">Close</Button>
</SideDialogClose>
</SideDialogFooter>
</SideDialogContent>Without SideDialogBody, content simply stacks inside the dialog — fine for short dialogs. Add it as soon as the content might exceed the viewport height.
Common use cases
Notifications and alerts
// Success notification - top right
<SideDialog position="top-right">
<SideDialogTrigger asChild>
<Button onClick={handleSave}>Save Changes</Button>
</SideDialogTrigger>
<SideDialogContent>
<SideDialogHeader>
<SideDialogTitle className="text-green-600">Success!</SideDialogTitle>
<SideDialogDescription>Your changes have been saved.</SideDialogDescription>
</SideDialogHeader>
</SideDialogContent>
</SideDialog>
// Error notification - top left
<SideDialog position="top-left">
<SideDialogContent>
<SideDialogHeader>
<SideDialogTitle className="text-red-600">Error</SideDialogTitle>
<SideDialogDescription>Something went wrong. Please try again.</SideDialogDescription>
</SideDialogHeader>
</SideDialogContent>
</SideDialog>Different sizes for different content
// Small notification - xs size
<SideDialog position="top-right" size="xs">
<SideDialogContent>
<SideDialogHeader>
<SideDialogTitle>Saved!</SideDialogTitle>
</SideDialogHeader>
</SideDialogContent>
</SideDialog>
// User profile - medium size
<SideDialog position="top-right" size="md">
<SideDialogContent>
<SideDialogHeader>
<SideDialogTitle>User Profile</SideDialogTitle>
</SideDialogHeader>
<div className="space-y-4">
<div className="flex items-center space-x-4">
<Avatar className="w-16 h-16">
<AvatarImage src="/avatar.jpg" />
<AvatarFallback>JD</AvatarFallback>
</Avatar>
<div>
<h3 className="font-semibold">John Doe</h3>
<p className="text-muted-foreground">john@example.com</p>
</div>
</div>
</div>
</SideDialogContent>
</SideDialog>
// Wide content panel - custom size with responsive constraints
<SideDialog position="right">
<SideDialogContent className="w-full max-w-[min(600px,calc(100vw-2rem))]">
<SideDialogHeader>
<SideDialogTitle>Content Editor</SideDialogTitle>
</SideDialogHeader>
<div className="space-y-4">
<Textarea placeholder="Write your content here..." rows={10} />
<div className="flex justify-end space-x-2">
<Button variant="outline">Save Draft</Button>
<Button>Publish</Button>
</div>
</div>
</SideDialogContent>
</SideDialog>Help and information
// Help panel - bottom left
<SideDialog position="bottom-left">
<SideDialogTrigger asChild>
<Button variant="outline" size="icon">
<HelpCircle className="size-4" />
</Button>
</SideDialogTrigger>
<SideDialogContent>
<SideDialogHeader>
<SideDialogTitle>Need Help?</SideDialogTitle>
<SideDialogDescription>
Find answers to common questions or contact support.
</SideDialogDescription>
</SideDialogHeader>
<div className="space-y-3">
<Button variant="outline" className="w-full">
<Book className="mr-2 size-4" />
Documentation
</Button>
<Button variant="outline" className="w-full">
<MessageCircle className="mr-2 size-4" />
Contact Support
</Button>
</div>
</SideDialogContent>
</SideDialog>State management
The component supports both controlled and uncontrolled modes, automatically detecting which mode to use based on the props provided:
// Uncontrolled: Component manages its own state
<SideDialog position="top-right">
<SideDialogTrigger asChild>
<Button>Open</Button>
</SideDialogTrigger>
<SideDialogContent>
<SideDialogHeader>
<SideDialogTitle>Self-managed State</SideDialogTitle>
</SideDialogHeader>
</SideDialogContent>
</SideDialog>
// Controlled: You manage the state
const [open, setOpen] = useState(false);
<SideDialog open={open} onOpenChange={setOpen} position="top-right">
<SideDialogTrigger asChild>
<Button>Open</Button>
</SideDialogTrigger>
<SideDialogContent>
<SideDialogHeader>
<SideDialogTitle>Controlled State</SideDialogTitle>
</SideDialogHeader>
</SideDialogContent>
</SideDialog>Advanced customization
Custom close behavior
// Disable the default close button
<SideDialogContent showCloseButton={false}>
<SideDialogHeader>
<SideDialogTitle>Custom Controls</SideDialogTitle>
</SideDialogHeader>
<SideDialogFooter>
<SideDialogClose asChild>
<Button variant="destructive">Cancel</Button>
</SideDialogClose>
<SideDialogClose asChild>
<Button>Confirm</Button>
</SideDialogClose>
</SideDialogFooter>
</SideDialogContent>Custom styling
// Custom dialog appearance
<SideDialogContent className="border-primary shadow-xl bg-gradient-to-b from-background to-muted">
<SideDialogHeader>
<SideDialogTitle className="text-primary">Custom Style</SideDialogTitle>
</SideDialogHeader>
</SideDialogContent>Examples
Default
'use client';import { useState } from 'react';import { Button } from '@/components/ui/button';import {SideDialog,SideDialogClose,SideDialogContent,SideDialogDescription,SideDialogFooter,SideDialogHeader,SideDialogTitle,SideDialogTrigger,} from '@/components/ui/shuip/side-dialog';export default function SideDialogExample() {const [open, setOpen] = useState(false);return (<div className='flex flex-wrap gap-4 p-8'><SideDialog open={open} onOpenChange={setOpen}><SideDialogTrigger asChild><Button variant='outline'>Open Dialog (Bottom Right)</Button></SideDialogTrigger><SideDialogContent><SideDialogHeader><SideDialogTitle>Side Dialog Example</SideDialogTitle><SideDialogDescription>This is a side dialog that opens on the bottom right by default. On mobile devices, it automaticallybecomes a drawer.</SideDialogDescription></SideDialogHeader><div className='py-4'><p className='text-sm text-muted-foreground'>Content goes here. This dialog is responsive and will adapt to mobile devices by showing as a drawerinstead.</p></div><SideDialogFooter><SideDialogClose asChild><Button variant='outline'>Cancel</Button></SideDialogClose><Button>Save changes</Button></SideDialogFooter></SideDialogContent></SideDialog><SideDialog position='top-left'><SideDialogTrigger asChild><Button variant='outline'>Top Left</Button></SideDialogTrigger><SideDialogContent><SideDialogHeader><SideDialogTitle>Top Left Dialog</SideDialogTitle><SideDialogDescription>This dialog opens from the top left corner.</SideDialogDescription></SideDialogHeader><div className='py-4'><p className='text-sm'>Perfect for notifications or quick actions.</p></div></SideDialogContent></SideDialog><SideDialog position='top-right'><SideDialogTrigger asChild><Button variant='outline'>Top Right</Button></SideDialogTrigger><SideDialogContent><SideDialogHeader><SideDialogTitle>Top Right Dialog</SideDialogTitle><SideDialogDescription>This dialog opens from the top right corner.</SideDialogDescription></SideDialogHeader><div className='py-4'><p className='text-sm'>Great for user menus or profile settings.</p></div></SideDialogContent></SideDialog><SideDialog position='bottom-left'><SideDialogTrigger asChild><Button variant='outline'>Bottom Left</Button></SideDialogTrigger><SideDialogContent><SideDialogHeader><SideDialogTitle>Bottom Left Dialog</SideDialogTitle><SideDialogDescription>This dialog opens from the bottom left corner.</SideDialogDescription></SideDialogHeader><div className='py-4'><p className='text-sm'>Ideal for help or support content.</p></div></SideDialogContent></SideDialog><SideDialog position='left'><SideDialogTrigger asChild><Button variant='outline'>Left Side</Button></SideDialogTrigger><SideDialogContent><SideDialogHeader><SideDialogTitle>Left Side Dialog</SideDialogTitle><SideDialogDescription>This dialog opens from the left side, centered vertically.</SideDialogDescription></SideDialogHeader><div className='py-4'><p className='text-sm'>Perfect for navigation or filters.</p></div></SideDialogContent></SideDialog><SideDialog position='right'><SideDialogTrigger asChild><Button variant='outline'>Right Side</Button></SideDialogTrigger><SideDialogContent><SideDialogHeader><SideDialogTitle>Right Side Dialog</SideDialogTitle><SideDialogDescription>This dialog opens from the right side, centered vertically.</SideDialogDescription></SideDialogHeader><div className='py-4'><p className='text-sm'>Great for sidebars or additional information.</p></div><SideDialogFooter><SideDialogClose asChild><Button variant='outline'>Close</Button></SideDialogClose></SideDialogFooter></SideDialogContent></SideDialog><SideDialog><SideDialogTrigger asChild><Button variant='outline'>No Close Button</Button></SideDialogTrigger><SideDialogContent showCloseButton={false}><SideDialogHeader><SideDialogTitle>Custom Close</SideDialogTitle><SideDialogDescription>This dialog doesn't show the default close button.</SideDialogDescription></SideDialogHeader><div className='py-4'><p className='text-sm'>You can control closing with custom buttons only.</p></div><SideDialogFooter><SideDialogClose asChild><Button>Done</Button></SideDialogClose></SideDialogFooter></SideDialogContent></SideDialog>{/* Different Sizes */}<SideDialog size='xs'><SideDialogTrigger asChild><Button variant='outline'>Extra Small (xs)</Button></SideDialogTrigger><SideDialogContent><SideDialogHeader><SideDialogTitle>XS Size</SideDialogTitle><SideDialogDescription>Extra small dialog size (320px max).</SideDialogDescription></SideDialogHeader></SideDialogContent></SideDialog><SideDialog size='md' position='top-left'><SideDialogTrigger asChild><Button variant='outline'>Medium (md)</Button></SideDialogTrigger><SideDialogContent><SideDialogHeader><SideDialogTitle>MD Size</SideDialogTitle><SideDialogDescription>Medium dialog size (448px max).</SideDialogDescription></SideDialogHeader></SideDialogContent></SideDialog><SideDialog size='xl' position='left'><SideDialogTrigger asChild><Button variant='outline'>Extra Large (xl)</Button></SideDialogTrigger><SideDialogContent><SideDialogHeader><SideDialogTitle>XL Size</SideDialogTitle><SideDialogDescription>Extra large dialog size (576px max).</SideDialogDescription></SideDialogHeader></SideDialogContent></SideDialog><SideDialog size='2xl' position='bottom-left'><SideDialogTrigger asChild><Button variant='outline'>2XL Size</Button></SideDialogTrigger><SideDialogContent><SideDialogHeader><SideDialogTitle>2XL Size</SideDialogTitle><SideDialogDescription>Extra extra large dialog size (672px max).</SideDialogDescription></SideDialogHeader></SideDialogContent></SideDialog><SideDialog position='right'><SideDialogTrigger asChild><Button variant='outline'>Custom 400px</Button></SideDialogTrigger><SideDialogContent className='w-full max-w-[min(400px,calc(100vw-2rem))]'><SideDialogHeader><SideDialogTitle>Custom Size</SideDialogTitle><SideDialogDescription>Custom size of 400px with responsive constraints.</SideDialogDescription></SideDialogHeader></SideDialogContent></SideDialog><SideDialog position='bottom-left'><SideDialogTrigger asChild><Button variant='outline'>50% Viewport</Button></SideDialogTrigger><SideDialogContent className='w-full max-w-[min(50vw,calc(100vw-2rem))]'><SideDialogHeader><SideDialogTitle>Viewport Size</SideDialogTitle><SideDialogDescription>50% of viewport width with responsive constraints.</SideDialogDescription></SideDialogHeader></SideDialogContent></SideDialog><SideDialog><SideDialogTrigger asChild><Button variant='outline' className='bg-gradient-to-r from-blue-300 to-purple-300'>Colored</Button></SideDialogTrigger><SideDialogContent className='bg-gradient-to-r from-blue-300 to-purple-300'><SideDialogHeader><SideDialogTitle>Colored</SideDialogTitle><SideDialogDescription>Colored dialog.</SideDialogDescription></SideDialogHeader></SideDialogContent></SideDialog></div>);}
Form
'use client';import { zodResolver } from '@hookform/resolvers/zod';import { useState } from 'react';import { useForm } from 'react-hook-form';import { z } from 'zod';import { Button } from '@/components/ui/button';import { Form } from '@/components/ui/form';import { InputField } from '@/components/ui/shuip/react-hook-form/input-field';import {SideDialog,SideDialogBody,SideDialogClose,SideDialogContent,SideDialogDescription,SideDialogFooter,SideDialogHeader,SideDialogTitle,SideDialogTrigger,} from '@/components/ui/shuip/side-dialog';import { SubmitButton } from '@/components/ui/shuip/submit-button';const formSchema = z.object({name: z.string().min(1, 'Name is required'),});export default function SideDialogSimpleExample() {const [open, setOpen] = useState(false);const form = useForm({resolver: zodResolver(formSchema),});const onSubmit = async (data: z.infer<typeof formSchema>) => {await new Promise((resolve) => setTimeout(resolve, 1500));alert(`Form submitted: ${JSON.stringify(data)}`);setOpen(false);form.reset();};return (<SideDialog open={open} onOpenChange={setOpen}><SideDialogTrigger asChild><Button variant='outline'>Open Form Side Dialog</Button></SideDialogTrigger><SideDialogContent><Form {...form}><form onSubmit={form.handleSubmit(onSubmit)}><SideDialogHeader><SideDialogTitle>Form Side Dialog</SideDialogTitle><SideDialogDescription>This is a form side dialog.</SideDialogDescription></SideDialogHeader><SideDialogBody className='space-y-4'><InputField register={form.register('name')} label='Name' description='This is a description' /></SideDialogBody><SideDialogFooter><SideDialogClose asChild><Button variant='outline'>Cancel</Button></SideDialogClose><SubmitButton className='w-fit'>Submit</SubmitButton></SideDialogFooter></form></Form></SideDialogContent></SideDialog>);}
Scrollable
'use client';import { Button } from '@/components/ui/button';import { Separator } from '@/components/ui/separator';import {SideDialog,SideDialogBody,SideDialogClose,SideDialogContent,SideDialogDescription,SideDialogFooter,SideDialogHeader,SideDialogTitle,SideDialogTrigger,} from '@/components/ui/shuip/side-dialog';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 SideDialogScrollableExample() {return (<div className='flex flex-wrap gap-3'><SideDialog position='bottom-right' size='md'><SideDialogTrigger asChild><Button variant='outline'>Activity Log</Button></SideDialogTrigger><SideDialogContent><SideDialogHeader><SideDialogTitle>Activity Log</SideDialogTitle><SideDialogDescription>Recent actions across your workspace.</SideDialogDescription></SideDialogHeader><SideDialogBody 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>))}</SideDialogBody><SideDialogFooter><SideDialogClose asChild><Button variant='outline' className='w-full'>Close</Button></SideDialogClose></SideDialogFooter></SideDialogContent></SideDialog><SideDialog position='right' size='sm'><SideDialogTrigger asChild><Button variant='outline'>Notifications</Button></SideDialogTrigger><SideDialogContent><SideDialogHeader><SideDialogTitle>Notifications</SideDialogTitle><SideDialogDescription>You have 8 unread notifications.</SideDialogDescription></SideDialogHeader><SideDialogBody className='space-y-3'>{Array.from({ length: 12 }, (_, i) => (<div key={i} className='flex items-start gap-3 rounded-md border p-3'><div className={`mt-1 size-2 shrink-0 rounded-full ${i < 8 ? 'bg-blue-500' : 'bg-transparent'}`} /><div className='min-w-0 flex-1'><p className='text-sm font-medium leading-snug'>{['New comment on your post','Alice mentioned you in a thread','Your export is ready to download','Dave accepted your invitation','Payment processed successfully','Your API key is expiring soon','New login from Chrome on macOS','Weekly report is available','Bob replied to your comment','Scheduled maintenance tonight','Your trial ends in 3 days','New feature: AI summaries',][i]}</p><p className='text-muted-foreground mt-0.5 text-xs'>{i === 0 ? 'Just now' : `${i * 2 + 1}h ago`}</p></div></div>))}</SideDialogBody><SideDialogFooter><Button variant='ghost' size='sm' className='w-full'>Mark all as read</Button></SideDialogFooter></SideDialogContent></SideDialog></div>);}
Props
Prop
Type
SideDialogContent Props
Prop
Type
SideDialogBody Props
Prop
Type
SideDialogTrigger Props
Prop
Type