Dialog Manager

PreviousNext

A powerful dialog manager for handling confirm, input, and custom dialogs with global state management.

About

The Dialog Manager is a comprehensive dialog management system that provides a simple API for creating confirm dialogs, input dialogs, and custom dialogs with global state management using Zustand.

Features

  • Simple API: Easy-to-use functions for common dialog patterns
  • Global State: Dialogs managed with Zustand for consistent state
  • Async Support: Built-in loading states for async actions
  • Queue Support: Multiple dialogs handled automatically
  • Customizable: Full control over styling and behavior
  • TypeScript: Fully typed for better developer experience

Preview

Installation

1. Install the component

pnpm dlx shadcn@latest add https://ui.nowts.app/r/dialog-manager.json

2. Add to your layout

Add the DialogManagerRenderer to your root layout:

app/layout.tsx
import { DialogManagerRenderer } from "@/lib/dialog-manager/dialog-manager-renderer"
 
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>
        {children}
        <DialogManagerRenderer />
      </body>
    </html>
  )
}

Usage

Basic confirm dialog

import { dialogManager } from "@/lib/dialog-manager/dialog-manager"
 
export function DeleteButton() {
  const handleDelete = () => {
    dialogManager.confirm({
      title: "Delete Item",
      description: "Are you sure you want to delete this item?",
      action: {
        label: "Delete",
        variant: "destructive",
        onClick: async () => {
          await deleteItem()
        },
      },
    })
  }
 
  return (
    <Button onClick={handleDelete} variant="destructive">
      Delete
    </Button>
  )
}

Confirm with text verification

dialogManager.confirm({
  title: "Delete Account",
  description:
    "This action is irreversible. All your data will be permanently deleted.",
  confirmText: "DELETE",
  action: {
    label: "Delete Account",
    variant: "destructive",
    onClick: async () => {
      await deleteAccount()
    },
  },
})

Input dialog

dialogManager.input({
  title: "Create Project",
  description: "Enter a name for your new project.",
  input: {
    label: "Project Name",
    placeholder: "My awesome project",
  },
  action: {
    label: "Create",
    onClick: async (projectName) => {
      if (!projectName?.trim()) return
      await createProject(projectName)
    },
  },
})

Input dialog with validation

dialogManager.input({
  title: "Rename File",
  description: "Enter a new name for this file.",
  input: {
    label: "Filename",
    placeholder: "document.pdf",
    defaultValue: currentFilename,
  },
  action: {
    label: "Rename",
    onClick: async (filename) => {
      if (!filename?.trim()) {
        throw new Error("Filename cannot be empty")
      }
 
      if (!/^[a-zA-Z0-9._-]+$/.test(filename)) {
        throw new Error("Invalid filename format")
      }
 
      await renameFile(filename)
    },
  },
})

Custom dialog with form

function CreateUserForm({ onClose }: { onClose: () => void }) {
  const [formData, setFormData] = useState({ name: "", email: "" })
 
  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault()
    await createUser(formData)
    onClose()
  }
 
  return (
    <form onSubmit={handleSubmit} className="space-y-4 p-6">
      <h2 className="text-lg font-semibold">Create New User</h2>
      <div>
        <Label htmlFor="name">Name</Label>
        <Input
          id="name"
          value={formData.name}
          onChange={(e) =>
            setFormData((prev) => ({ ...prev, name: e.target.value }))
          }
        />
      </div>
      <div>
        <Label htmlFor="email">Email</Label>
        <Input
          id="email"
          type="email"
          value={formData.email}
          onChange={(e) =>
            setFormData((prev) => ({ ...prev, email: e.target.value }))
          }
        />
      </div>
      <div className="flex justify-end gap-2">
        <Button type="button" variant="outline" onClick={onClose}>
          Cancel
        </Button>
        <Button type="submit">Create User</Button>
      </div>
    </form>
  )
}
 
// Usage
dialogManager.custom({
  children: <CreateUserForm onClose={() => dialogManager.closeAll()} />,
})

Loading State

If you return a promise from the action, the dialog will show a loading state.

If the promise is rejected, the dialog will not be closed.

dialogManager.confirm({
  title: "Delete Item",
  description: "Are you sure you want to delete this item?",
  action: {
    label: "Delete",
    variant: "destructive",
    onClick: async () => {
      await new Promise((resolve) => setTimeout(resolve, 1000))
      throw new Error("Failed to delete item")
    },
  },
})