Dialog primitives with styled wrappers
Edit profile
Make changes to your profile here. Click save when you're done.
import { Button } from "@/components/ui/button"
import {
...import { Button } from "@/components/ui/button"
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import { Field } from "@/components/ui/field"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
export default function DialogDemo() {
return (
<Dialog>
<form>
<DialogTrigger asChild>
<Button variant="outline">Open Dialog</Button>
</DialogTrigger>
<DialogContent class="sm:max-w-sm">
<DialogHeader>
<DialogTitle>Edit profile</DialogTitle>
<DialogDescription>
Make changes to your profile here. Click save when you're
done.
</DialogDescription>
</DialogHeader>
<div class="grid gap-4">
<Field>
<Label htmlFor="name-1">Name</Label>
<Input id="name-1" name="name" defaultValue="Pedro Duarte" />
</Field>
<Field>
<Label htmlFor="username-1">Username</Label>
<Input id="username-1" name="username" defaultValue="@peduarte" />
</Field>
</div>
<DialogFooter>
<DialogClose asChild>
<Button variant="outline">Cancel</Button>
</DialogClose>
<Button type="submit">Save changes</Button>
</DialogFooter>
</DialogContent>
</form>
</Dialog>
)
}
Installation
npx @fictjs/shadcn@latest add dialogInstall the runtime dependencies:
npm install @fictjs/radix-uiCopy the Fict shadcn component into your project.
Source
components/ui/dialog.tsx
src/components/ui/dialog.tsx
import { Dialog as DialogPrimitive } from '@fictjs/radix-ui'
import { cn } from '@/lib/cn'
export const Dialog = DialogPrimitive.Root
export const DialogPortal = DialogPrimitive.Portal
export const DialogTrigger = DialogPrimitive.Trigger
type GenericProps = {
class?: string
children?: unknown
[key: string]: unknown
}
export function DialogOverlay(props: GenericProps) {
const { class: className, ...rest } = props
return <DialogPrimitive.Overlay class={cn('fixed inset-0 z-50 bg-background/80 backdrop-blur-sm', className)} {...rest} />
}
export function DialogContent(props: GenericProps) {
const { class: className, children, ...rest } = props
return (
<>
<DialogOverlay />
<DialogPrimitive.Content
class={cn(
'fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2 gap-4 border bg-background p-6 shadow-lg duration-200 sm:rounded-lg',
className,
)}
{...rest}
>
{children}
<DialogPrimitive.Close class='absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring'>
<span aria-hidden='true'>×</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</>
)
}
export function DialogHeader(props: GenericProps) {
const { class: className, ...rest } = props
return <div class={cn('flex flex-col space-y-1.5 text-center sm:text-left', className)} {...rest} />
}
export function DialogFooter(props: GenericProps) {
const { class: className, ...rest } = props
return <div class={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)} {...rest} />
}
export function DialogTitle(props: GenericProps) {
const { class: className, ...rest } = props
return <DialogPrimitive.Title class={cn('text-lg font-semibold leading-none tracking-tight', className)} {...rest} />
}
export function DialogDescription(props: GenericProps) {
const { class: className, ...rest } = props
return <DialogPrimitive.Description class={cn('text-sm text-muted-foreground', className)} {...rest} />
}
export const DialogClose = DialogPrimitive.Close
Usage
Examples
Close Button
import { Button } from "@/components/ui/button"
import {
...import { Button } from "@/components/ui/button"
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
export default function DialogCloseButton() {
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">Share</Button>
</DialogTrigger>
<DialogContent class="sm:max-w-md">
<DialogHeader>
<DialogTitle>Share link</DialogTitle>
<DialogDescription>
Anyone who has this link will be able to view this.
</DialogDescription>
</DialogHeader>
<div class="flex items-center gap-2">
<div class="grid flex-1 gap-2">
<Label htmlFor="link" class="sr-only">
Link
</Label>
<Input
id="link"
defaultValue="https://ui.shadcn.com/docs/installation"
readOnly
/>
</div>
</div>
<DialogFooter class="sm:justify-start">
<DialogClose asChild>
<Button type="button">Close</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
)
}
No Close Button
import { Button } from "@/components/ui/button"
import {
...import { Button } from "@/components/ui/button"
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
export default function DialogNoCloseButton() {
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">No Close Button</Button>
</DialogTrigger>
<DialogContent showCloseButton={false}>
<DialogHeader>
<DialogTitle>No Close Button</DialogTitle>
<DialogDescription>
This dialog doesn't have a close button in the top-right
corner.
</DialogDescription>
</DialogHeader>
</DialogContent>
</Dialog>
)
}
Sticky Footer
import { Button } from "@/components/ui/button"
import {
...import { Button } from "@/components/ui/button"
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
export default function DialogStickyFooter() {
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">Sticky Footer</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Sticky Footer</DialogTitle>
<DialogDescription>
This dialog has a sticky footer that stays visible while the content
scrolls.
</DialogDescription>
</DialogHeader>
<div class="-mx-4 no-scrollbar max-h-[50vh] overflow-y-auto px-4">
{Array.from({ length: 10 }).map((_, index) => (
<p key={index} class="mb-4 leading-normal">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
enim ad minim veniam, quis nostrud exercitation ullamco laboris
nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
reprehenderit in voluptate velit esse cillum dolore eu fugiat
nulla pariatur. Excepteur sint occaecat cupidatat non proident,
sunt in culpa qui officia deserunt mollit anim id est laborum.
</p>
))}
</div>
<DialogFooter>
<DialogClose asChild>
<Button variant="outline">Close</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
)
}
Scrollable Content
Scrollable Content
This is a dialog with scrollable content.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
import { Button } from "@/components/ui/button"
import {
...import { Button } from "@/components/ui/button"
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
export default function DialogScrollableContent() {
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">Scrollable Content</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Scrollable Content</DialogTitle>
<DialogDescription>
This is a dialog with scrollable content.
</DialogDescription>
</DialogHeader>
<div class="-mx-4 no-scrollbar max-h-[50vh] overflow-y-auto px-4">
{Array.from({ length: 10 }).map((_, index) => (
<p key={index} class="mb-4 leading-normal">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
enim ad minim veniam, quis nostrud exercitation ullamco laboris
nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
reprehenderit in voluptate velit esse cillum dolore eu fugiat
nulla pariatur. Excepteur sint occaecat cupidatat non proident,
sunt in culpa qui officia deserunt mollit anim id est laborum.
</p>
))}
</div>
</DialogContent>
</Dialog>
)
}
Rtl
تعديل الملف الشخصي
قم بإجراء تغييرات على ملفك الشخصي هنا. انقر فوق حفظ عند الانتهاء.
import { Button } from '@/components/ui/button'
import {
...import { Button } from '@/components/ui/button'
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@/components/ui/dialog'
import { Field } from '@/components/ui/field'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
const translations = {
ar: { open: 'فتح الحوار', title: 'تعديل الملف الشخصي', description: 'قم بإجراء تغييرات على ملفك الشخصي هنا. انقر فوق حفظ عند الانتهاء.', name: 'الاسم', username: 'اسم المستخدم', cancel: 'إلغاء', save: 'حفظ التغييرات' },
he: { open: 'פתח דיאלוג', title: 'ערוך פרופיל', description: 'בצע שינויים בפרופיל שלך כאן. לחץ על שמור כשתסיים.', name: 'שם', username: 'שם משתמש', cancel: 'בטל', save: 'שמור שינויים' },
en: { open: 'Open Dialog', title: 'Edit profile', description: "Make changes to your profile here. Click save when you're done.", name: 'Name', username: 'Username', cancel: 'Cancel', save: 'Save changes' },
} as const
export default function DialogRtlExample() {
let language = $state<keyof typeof translations>('ar')
const text = () => translations[language]
const direction = () => language === 'en' ? 'ltr' : 'rtl'
return (
<div class="grid gap-4">
<select value={language} onChange={event => { language = event.currentTarget.value as keyof typeof translations }}>
<option value="ar">Arabic (العربية)</option><option value="he">Hebrew (עברית)</option><option value="en">English</option>
</select>
<Dialog>
<DialogTrigger asChild><Button variant="outline">{text().open}</Button></DialogTrigger>
<DialogContent class="sm:max-w-sm" dir={direction()}>
<DialogHeader><DialogTitle>{text().title}</DialogTitle><DialogDescription>{text().description}</DialogDescription></DialogHeader>
<div class="grid gap-4">
<Field><Label for="name-rtl">{text().name}</Label><Input id="name-rtl" value="Pedro Duarte" /></Field>
<Field><Label for="username-rtl">{text().username}</Label><Input id="username-rtl" value="@peduarte" /></Field>
</div>
<DialogFooter>
<DialogClose asChild><Button variant="outline">{text().cancel}</Button></DialogClose>
<DialogClose asChild><Button>{text().save}</Button></DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
)
}