#Recipes
Common patterns and recipes for using use-disclosable in your applications.
#Confirmation Dialog
A classic yes/no confirmation dialog that returns the user's choice:
import type { DisclosableInjectedProps } from "use-disclosable";
import { Button } from "./ui/button";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter,
} from "./ui/dialog";
type ConfirmationDialogProps = {
title: string;
message: string;
confirmLabel?: string;
cancelLabel?: string;
} & DisclosableInjectedProps;
export const ConfirmationDialog: React.FC<ConfirmationDialogProps> = ({
title,
message,
confirmLabel = "Confirm",
cancelLabel = "Cancel",
isDisclosableOpen,
closeDisclosable,
}) => {
return (
<Dialog open={isDisclosableOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>{title}</DialogTitle>
</DialogHeader>
<p>{message}</p>
<DialogFooter>
<Button
variant="outline"
onClick={() => closeDisclosable({ closeReason: "cancel" })}
>
{cancelLabel}
</Button>
<Button onClick={() => closeDisclosable({ closeReason: "confirm" })}>
{confirmLabel}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
};Usage:
const { open } = useDisclosable();
const handleDelete = async () => {
const result = await open(ConfirmationDialog, {
props: {
title: "Delete Item",
message: "Are you sure you want to delete this item?",
},
});
if (result === "confirm") {
// User confirmed - delete the item
await deleteItem(id);
}
};#Form Dialog
A dialog with form inputs that returns the submitted data:
import { useState } from "react";
import type { DisclosableInjectedProps } from "use-disclosable";
import { Button } from "./ui/button";
import { Input } from "./ui/input";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter,
} from "./ui/dialog";
type FormDialogProps = {
initialValues?: { name: string; email: string };
onSave?: (data: { name: string; email: string }) => Promise<void>;
} & DisclosableInjectedProps;
export const FormDialog: React.FC<FormDialogProps> = ({
initialValues = { name: "", email: "" },
onSave,
isDisclosableOpen,
closeDisclosable,
}) => {
const [formData, setFormData] = useState(initialValues);
const handleSubmit = async () => {
if (onSave) {
await onSave(formData);
}
closeDisclosable({ closeReason: "confirm" });
};
return (
<Dialog open={isDisclosableOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>Enter Details</DialogTitle>
</DialogHeader>
<div className="space-y-4">
<div>
<label>Name</label>
<Input
value={formData.name}
onChange={(e) =>
setFormData({ ...formData, name: e.target.value })
}
/>
</div>
<div>
<label>Email</label>
<Input
type="email"
value={formData.email}
onChange={(e) =>
setFormData({ ...formData, email: e.target.value })
}
/>
</div>
</div>
<DialogFooter>
<Button
variant="outline"
onClick={() => closeDisclosable({ closeReason: "cancel" })}
>
Cancel
</Button>
<Button
onClick={handleSubmit}
disabled={!formData.name || !formData.email}
>
Save
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
};Usage:
const { open } = useDisclosable();
const handleAddUser = async () => {
const result = await open(FormDialog, {
props: { initialValues: { name: "", email: "" } },
});
if (result) {
const formData = JSON.parse(result);
// Submit form data
await createUser(formData);
}
};#Loading State with Async Operation
Show a loading state while an async operation completes:
import type { DisclosableInjectedProps } from "use-disclosable";
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "./ui/dialog";
type LoadingDialogProps = {
message?: string;
} & DisclosableInjectedProps;
export const LoadingDialog: React.FC<LoadingDialogProps> = ({
message = "Loading...",
isDisclosableOpen,
}) => {
return (
<Dialog open={isDisclosableOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>{message}</DialogTitle>
</DialogHeader>
<div className="flex justify-center py-4">
<Spinner /> {/* Your loading spinner component */}
</div>
</DialogContent>
</Dialog>
);
};Usage:
const { open, close } = useDisclosable();
const handleProcess = async () => {
// Open loading dialog
await open(LoadingDialog, { props: { message: "Processing..." } });
try {
await performHeavyOperation();
close(LoadingDialog);
} catch (error) {
// Handle error
}
};#Multiple Dialog Instances
Use custom identifiers to open multiple instances of the same dialog type:
const { open } = useDisclosable();
const handleOpenMultiple = () => {
// Open first instance
open(MyDialog, {
identifier: "dialog-1",
props: { title: "First Dialog" },
});
// Open second instance (different identifier)
open(MyDialog, {
identifier: "dialog-2",
props: { title: "Second Dialog" },
});
};#Dynamic Props Update
Update dialog props after it's already open:
const { open, setProps } = useDisclosable();
const handleShowDetails = async () => {
await open(ItemDetailsDialog, {
identifier: "item-details",
props: { itemId: null },
});
// Later, update the props
setProps("item-details", { itemId: 123 });
};#Alert Dialog
A simple alert that shows a message and has a single "OK" button:
import type { DisclosableInjectedProps } from "use-disclosable";
import { Button } from "./ui/button";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
DialogFooter,
} from "./ui/dialog";
type AlertDialogProps = {
title: string;
message: string;
} & DisclosableInjectedProps;
export const AlertDialog: React.FC<AlertDialogProps> = ({
title,
message,
isDisclosableOpen,
closeDisclosable,
}) => {
return (
<Dialog open={isDisclosableOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>{title}</DialogTitle>
<DialogDescription>{message}</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button onClick={() => closeDisclosable({ closeReason: "ok" })}>
OK
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
};Usage:
const { open } = useDisclosable();
const handleShowError = async () => {
await open(AlertDialog, {
props: {
title: "Error",
message: "Something went wrong. Please try again.",
},
});
// Continue after user clicks OK
};#Toast/Notification Pattern
Use closeDisclosable with a destroy delay for animated exits:
import type { DisclosableInjectedProps } from "use-disclosable";
import { Dialog, DialogContent } from "./ui/dialog";
type NotificationDialogProps = {
message: string;
type: "success" | "error" | "info";
} & DisclosableInjectedProps;
export const NotificationDialog: React.FC<NotificationDialogProps> = ({
message,
type,
isDisclosableOpen,
closeDisclosable,
}) => {
const handleClose = () => {
// Destroy after animation completes
closeDisclosable({ destroyAfter: 300 });
};
return (
<Dialog
open={isDisclosableOpen}
onOpenChange={(open) => !open && handleClose()}
>
<DialogContent>
<div className={`notification ${type}`}>{message}</div>
</DialogContent>
</Dialog>
);
};