Component
Dialog
A modal framed in accent ticks, over an ink scrim.
stablev0.3.0 Chromium 103 ui-scale safe
Install
Copies the file into
components/ds/, with its registry dependencies and npm packages. See Installation for the one-time setup.bunx shadcn@3 add @fastrp/dialogExamples
Default
API
<Dialog />Takes the props of what it wrapsRadix's dialog, sounding `open` and `close` however it opens or closes.
<DialogContent />A menu window: a game panel landing in from slightly small and out of focus, an accent line along its top edge. Centred by auto margins rather than a translate, which the entrance's own transform would overwrite mid-animation.
| Prop | Type | Default | Notes |
|---|---|---|---|
| showClose | boolean | true | The ESC ✕ in the corner. On by default; turn it off when the footer has its own way out. |
<DialogOverlay />Takes the props of what it wrapsThe scrim: the world behind goes dark and out of focus, as a game pauses into a menu. The FiveM NUI turns backdrop-filter off, and the ink alone still separates the menu from the game.
Dependencies
npm
- @radix-ui/react-dialog@^1.1.4
- lucide-react@^0.469.0
Registry
- @fastrp/key-hint
- @fastrp/ui-sounds
- @fastrp/utils
Source
components/ds/dialog.tsxShowHide
'use client';
import * as DialogPrimitive from '@radix-ui/react-dialog';
import { X } from 'lucide-react';
import type * as React from 'react';
import { Kbd } from '@/components/ds/key-hint';
import { useSoundedOpen } from '@/components/ds/ui-sounds';
import { cn } from '@/lib/utils';
// @fast-ds [email protected]
/**
* Radix's dialog, sounding `open` and `close` however it opens or closes.
*/
function Dialog({
open,
defaultOpen,
onOpenChange,
...props
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
return (
<DialogPrimitive.Root {...props} {...useSoundedOpen({ open, defaultOpen, onOpenChange })} />
);
}
const DialogTrigger = DialogPrimitive.Trigger;
const DialogPortal = DialogPrimitive.Portal;
const DialogClose = DialogPrimitive.Close;
/**
* The scrim: the world behind goes dark and out of focus, as a game pauses into a menu. The
* FiveM NUI turns backdrop-filter off, and the ink alone still separates the menu from the game.
*/
function DialogOverlay({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
return (
<DialogPrimitive.Overlay
data-slot="dialog-overlay"
className={cn(
'fixed inset-0 z-overlay bg-ink/70 backdrop-blur-md data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0',
className,
)}
{...props}
/>
);
}
export type DialogContentProps = React.ComponentProps<typeof DialogPrimitive.Content> & {
/** The ESC ✕ in the corner. On by default; turn it off when the footer has its own way out. */
showClose?: boolean;
};
/**
* A menu window: a game panel landing in from slightly small and out of focus, an accent line
* along its top edge. Centred by auto margins rather than a translate, which the entrance's own
* transform would overwrite mid-animation.
*/
function DialogContent({ className, children, showClose = true, ...props }: DialogContentProps) {
return (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
data-slot="dialog-content"
className={cn(
'panel fixed inset-0 z-modal m-auto grid h-fit max-h-[calc(100vh-2rem)] w-[calc(100%-2rem)] max-w-lg gap-5 overflow-y-auto overflow-x-hidden p-6 focus:outline-none data-[state=closed]:animate-menu-out data-[state=open]:animate-menu-in',
className,
)}
{...props}
>
<span
aria-hidden
className="pointer-events-none absolute inset-x-0 top-0 h-px bg-gradient-to-r from-transparent via-primary to-transparent"
/>
{children}
{showClose && (
<DialogPrimitive.Close
data-slot="dialog-close"
className="absolute right-3 top-3 flex items-center gap-1.5 rounded-md px-1.5 py-1 text-muted-foreground transition-colors duration-fast hover:bg-foreground/[0.08] hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-selection [&_svg]:size-4"
>
<Kbd size="sm" aria-hidden>
ESC
</Kbd>
<X />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
)}
</DialogPrimitive.Content>
</DialogPortal>
);
}
function DialogHeader({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="dialog-header"
className={cn('flex flex-col gap-2 pr-16 text-left', className)}
{...props}
/>
);
}
function DialogFooter({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="dialog-footer"
className={cn(
'-mx-6 -mb-6 mt-1 flex flex-col-reverse gap-2 border-t border-foreground/10 bg-black/15 px-6 py-4 sm:flex-row sm:items-center sm:justify-end',
className,
)}
{...props}
/>
);
}
function DialogTitle({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Title>) {
return (
<DialogPrimitive.Title
data-slot="dialog-title"
className={cn('font-display text-3xl leading-none text-foreground', className)}
{...props}
/>
);
}
function DialogDescription({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
return (
<DialogPrimitive.Description
data-slot="dialog-description"
className={cn('text-sm leading-relaxed text-muted-foreground', className)}
{...props}
/>
);
}
export {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogOverlay,
DialogPortal,
DialogTitle,
DialogTrigger,
};