1.0.0 • Published 4 months ago

@darksnow-ui/dialog v1.0.0

Weekly downloads
-
License
MIT
Repository
github
Last release
4 months ago

@darksnow-ui/dialog

A modal dialog component built on Radix UI with smooth animations and accessibility features.

Installation

npm install @darksnow-ui/dialog @radix-ui/react-dialog
# or
yarn add @darksnow-ui/dialog @radix-ui/react-dialog
# or
pnpm add @darksnow-ui/dialog @radix-ui/react-dialog

Usage

import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@darksnow-ui/dialog"
import { Button } from "@darksnow-ui/button"

export function Example() {
  return (
    <Dialog>
      <DialogTrigger asChild>
        <Button variant="outline">Edit Profile</Button>
      </DialogTrigger>
      <DialogContent className="sm:max-w-[425px]">
        <DialogHeader>
          <DialogTitle>Edit profile</DialogTitle>
          <DialogDescription>
            Make changes to your profile here. Click save when you're done.
          </DialogDescription>
        </DialogHeader>
        <div className="grid gap-4 py-4">
          <div className="grid grid-cols-4 items-center gap-4">
            <Label htmlFor="name" className="text-right">
              Name
            </Label>
            <Input id="name" value="Pedro Duarte" className="col-span-3" />
          </div>
          <div className="grid grid-cols-4 items-center gap-4">
            <Label htmlFor="username" className="text-right">
              Username
            </Label>
            <Input id="username" value="@peduarte" className="col-span-3" />
          </div>
        </div>
        <DialogFooter>
          <Button type="submit">Save changes</Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  )
}

Components

Dialog

The root container component.

PropTypeDefaultDescription
openboolean-Controlled open state
defaultOpenbooleanfalseDefault open state
onOpenChange(open: boolean) => void-Callback when open state changes
modalbooleantrueWhether the dialog is modal

DialogTrigger

Button that opens the dialog.

PropTypeDefaultDescription
asChildbooleanfalseRender as child element
childrenReactNode-Trigger content

DialogContent

The modal content container.

PropTypeDefaultDescription
classNamestring-Additional CSS classes
childrenReactNode-Dialog content

DialogHeader

Container for title and description.

PropTypeDefaultDescription
classNamestring-Additional CSS classes
childrenReactNode-Header content

DialogTitle

Dialog title with proper semantics.

PropTypeDefaultDescription
classNamestring-Additional CSS classes
childrenReactNode-Title content

DialogDescription

Optional description text.

PropTypeDefaultDescription
classNamestring-Additional CSS classes
childrenReactNode-Description content

DialogFooter

Container for action buttons.

PropTypeDefaultDescription
classNamestring-Additional CSS classes
childrenReactNode-Footer content

DialogClose

Button that closes the dialog.

PropTypeDefaultDescription
asChildbooleanfalseRender as child element
childrenReactNode-Close button content

Examples

Basic Dialog

import { Dialog, DialogContent, DialogTrigger } from "@darksnow-ui/dialog"
import { Button } from "@darksnow-ui/button"

<Dialog>
  <DialogTrigger asChild>
    <Button>Open Dialog</Button>
  </DialogTrigger>
  <DialogContent>
    <p>This is a basic dialog.</p>
  </DialogContent>
</Dialog>

Confirmation Dialog

import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@darksnow-ui/dialog"
import { Button } from "@darksnow-ui/button"

<Dialog>
  <DialogTrigger asChild>
    <Button variant="destructive">Delete Account</Button>
  </DialogTrigger>
  <DialogContent>
    <DialogHeader>
      <DialogTitle>Are you absolutely sure?</DialogTitle>
      <DialogDescription>
        This action cannot be undone. This will permanently delete your
        account and remove your data from our servers.
      </DialogDescription>
    </DialogHeader>
    <DialogFooter>
      <Button variant="outline">Cancel</Button>
      <Button variant="destructive">Delete</Button>
    </DialogFooter>
  </DialogContent>
</Dialog>

Form Dialog

import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@darksnow-ui/dialog"
import { Button } from "@darksnow-ui/button"
import { Input } from "@darksnow-ui/input"
import { Label } from "@darksnow-ui/label"

<Dialog>
  <DialogTrigger asChild>
    <Button>Edit Profile</Button>
  </DialogTrigger>
  <DialogContent className="sm:max-w-[425px]">
    <DialogHeader>
      <DialogTitle>Edit profile</DialogTitle>
      <DialogDescription>
        Make changes to your profile here. Click save when you're done.
      </DialogDescription>
    </DialogHeader>
    <form className="grid gap-4 py-4">
      <div className="grid grid-cols-4 items-center gap-4">
        <Label htmlFor="name" className="text-right">
          Name
        </Label>
        <Input 
          id="name" 
          defaultValue="Pedro Duarte" 
          className="col-span-3" 
        />
      </div>
      <div className="grid grid-cols-4 items-center gap-4">
        <Label htmlFor="username" className="text-right">
          Username
        </Label>
        <Input 
          id="username" 
          defaultValue="@peduarte" 
          className="col-span-3" 
        />
      </div>
      <div className="grid grid-cols-4 items-center gap-4">
        <Label htmlFor="email" className="text-right">
          Email
        </Label>
        <Input 
          id="email" 
          type="email"
          defaultValue="pedro@example.com" 
          className="col-span-3" 
        />
      </div>
    </form>
    <DialogFooter>
      <Button type="submit">Save changes</Button>
    </DialogFooter>
  </DialogContent>
</Dialog>

Controlled Dialog

const [open, setOpen] = useState(false)

<Dialog open={open} onOpenChange={setOpen}>
  <DialogTrigger asChild>
    <Button>Open Controlled Dialog</Button>
  </DialogTrigger>
  <DialogContent>
    <DialogHeader>
      <DialogTitle>Controlled Dialog</DialogTitle>
      <DialogDescription>
        This dialog's open state is controlled by React state.
      </DialogDescription>
    </DialogHeader>
    <DialogFooter>
      <Button onClick={() => setOpen(false)}>Close</Button>
    </DialogFooter>
  </DialogContent>
</Dialog>

Alert Dialog

import { AlertTriangle } from "lucide-react"

<Dialog>
  <DialogTrigger asChild>
    <Button variant="destructive">Delete</Button>
  </DialogTrigger>
  <DialogContent>
    <DialogHeader>
      <div className="flex items-center gap-2">
        <AlertTriangle className="h-5 w-5 text-theme-danger" />
        <DialogTitle>Delete confirmation</DialogTitle>
      </div>
      <DialogDescription>
        Are you sure you want to delete this item? This action cannot be undone.
      </DialogDescription>
    </DialogHeader>
    <DialogFooter>
      <Button variant="outline">Cancel</Button>
      <Button variant="destructive">Delete</Button>
    </DialogFooter>
  </DialogContent>
</Dialog>

Large Content Dialog

<Dialog>
  <DialogTrigger asChild>
    <Button>View Details</Button>
  </DialogTrigger>
  <DialogContent className="max-w-4xl max-h-[80vh] overflow-y-auto">
    <DialogHeader>
      <DialogTitle>Project Details</DialogTitle>
      <DialogDescription>
        Complete information about this project.
      </DialogDescription>
    </DialogHeader>
    <div className="space-y-6">
      <div>
        <h3 className="text-lg font-semibold mb-2">Overview</h3>
        <p className="text-theme-content-muted">
          Lorem ipsum dolor sit amet, consectetur adipiscing elit...
        </p>
      </div>
      
      <div>
        <h3 className="text-lg font-semibold mb-2">Team Members</h3>
        <div className="space-y-2">
          {/* Team member list */}
        </div>
      </div>
      
      <div>
        <h3 className="text-lg font-semibold mb-2">Timeline</h3>
        <div className="space-y-2">
          {/* Timeline items */}
        </div>
      </div>
    </div>
    <DialogFooter>
      <Button variant="outline">Close</Button>
      <Button>Edit Project</Button>
    </DialogFooter>
  </DialogContent>
</Dialog>

Custom Close Button

import { DialogClose } from "@darksnow-ui/dialog"
import { X } from "lucide-react"

<Dialog>
  <DialogTrigger asChild>
    <Button>Open Dialog</Button>
  </DialogTrigger>
  <DialogContent className="relative">
    <DialogClose asChild>
      <Button 
        variant="ghost" 
        size="icon"
        className="absolute right-4 top-4"
      >
        <X className="h-4 w-4" />
      </Button>
    </DialogClose>
    
    <DialogHeader>
      <DialogTitle>Custom Close Button</DialogTitle>
    </DialogHeader>
    
    <p>This dialog has a custom close button.</p>
  </DialogContent>
</Dialog>

Multi-step Dialog

const [step, setStep] = useState(1)

<Dialog>
  <DialogTrigger asChild>
    <Button>Setup Wizard</Button>
  </DialogTrigger>
  <DialogContent>
    <DialogHeader>
      <DialogTitle>Setup Wizard - Step {step} of 3</DialogTitle>
      <DialogDescription>
        {step === 1 && "Enter your basic information"}
        {step === 2 && "Configure your preferences"}
        {step === 3 && "Review and confirm"}
      </DialogDescription>
    </DialogHeader>
    
    <div className="py-4">
      {step === 1 && (
        <div className="space-y-4">
          <Input placeholder="Full name" />
          <Input placeholder="Email" type="email" />
        </div>
      )}
      {step === 2 && (
        <div className="space-y-4">
          <div className="flex items-center space-x-2">
            <input type="checkbox" id="notifications" />
            <label htmlFor="notifications">Email notifications</label>
          </div>
          <div className="flex items-center space-x-2">
            <input type="checkbox" id="newsletter" />
            <label htmlFor="newsletter">Newsletter subscription</label>
          </div>
        </div>
      )}
      {step === 3 && (
        <div>
          <p>Please review your information and click finish to complete setup.</p>
        </div>
      )}
    </div>
    
    <DialogFooter>
      {step > 1 && (
        <Button variant="outline" onClick={() => setStep(step - 1)}>
          Previous
        </Button>
      )}
      {step < 3 ? (
        <Button onClick={() => setStep(step + 1)}>
          Next
        </Button>
      ) : (
        <Button>
          Finish
        </Button>
      )}
    </DialogFooter>
  </DialogContent>
</Dialog>

Styling

The dialog components use these CSS classes:

  • DialogOverlay: fixed inset-0 z-50 bg-theme-shadow/80
  • DialogContent: fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-theme-mark-light bg-theme-surface p-6 shadow-theme-xl
  • DialogHeader: flex flex-col space-y-1.5 text-center sm:text-left
  • DialogTitle: text-lg font-semibold leading-none tracking-tight
  • DialogDescription: text-sm text-theme-content-muted
  • DialogFooter: flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2

Accessibility

  • Full keyboard navigation support
  • Focus trap within the dialog
  • Escape key closes the dialog
  • Proper ARIA attributes for screen readers
  • Focus returns to trigger element when closed
  • Supports both modal and non-modal dialogs

Theming

The dialog uses CSS custom properties for theming:

  • --theme-shadow - Overlay background color
  • --theme-mark-light - Border color
  • --theme-surface - Dialog background
  • --theme-content-muted - Description text color

Technical Details

  • Built on Radix UI Dialog primitive
  • Includes smooth enter/exit animations
  • Portal rendering for proper z-index management
  • TypeScript support with proper type inference
  • Supports both controlled and uncontrolled usage

License

MIT © DarkSnow UI