shadimage
lumiui
blockdialog-01source: ok · hit

Dialog 01

Complex user management UI with nested dialogs.

npx shadcn add "https://www.lumiui.dev/r/dialog-01.json"
free

Interaction

modal

Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.

Dependencies

Bundle weight · light
iconslucide-react

Registry dependencies

@lumi-ui/dialog@lumi-ui/alert-dialog@lumi-ui/dropdown-menu@lumi-ui/button@lumi-ui/select@lumi-ui/toast@lumi-ui/separator

Files3 · source cached 10/8/2026, 9:04:07 PM

registry/blocks/dialog-01/page.tsx
import { TeamManagement } from "./components/team-management";

export default function Page() {
  return (
    <div className="h-screen flex justify-center items-center w-screen">
      <TeamManagement />
    </div>
  );
}
registry/blocks/dialog-01/components/team-management.tsx
"use client";

import type { DialogRootChangeEventDetails } from "@base-ui/react";
import {
  ChevronsUpDownIcon,
  Eye,
  Loader2,
  MoreHorizontal,
  ShieldAlert,
  ShieldCheck,
  Trash2,
  User,
} from "lucide-react";
import * as React from "react";
import { cn } from "@/lib/utils";
import {
  AlertDialog,
  AlertDialogClose,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogTrigger,
  createAlertDialogHandle,
} from "@/registry/ui/alert-dialog";
import { Badge } from "@/registry/ui/badge";
import { Button } from "@/registry/ui/button";
import {
  createDialogHandle,
  Dialog,
  DialogClose,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from "@/registry/ui/dialog";
import {
  createDropdownMenuHandle,
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/registry/ui/dropdown-menu";
import { Label } from "@/registry/ui/label";
import {
  Select,
  SelectContent,
  SelectItemContent,
  SelectTriggerGroup,
} from "@/registry/ui/select";
import { Separator } from "@/registry/ui/separator";
import { toast } from "@/registry/ui/toast";
import { MemberCard } from "./member-card";

export type TeamMember = {
  id: string;
  name: string;
  email: string;
  role: "Admin" | "Member" | "Viewer";
  initials: string;
};

const menuHandle = createDropdownMenuHandle<TeamMember>();
const editMemberDialogHandle = createDialogHandle<TeamMember>();
const removeMemberDialogHandle = createDialogHandle<TeamMember>();
const confirmDeleteDialogHandle = createAlertDialogHandle<TeamMember>();
const closeConfirmDialogHandle = createAlertDialogHandle();

export function TeamManagement() {
  const [members, setMembers] =
    React.useState<TeamMember[]>(INITIAL_TEAM_MEMBERS);
  const [selectedRole, setSelectedRole] = React.useState<
    TeamMember["role"] | null
  >(null);
  const [editingMember, setEditingMember] = React.useState<TeamMember | null>(
    null,
  );
  const [editOpen, setEditOpen] = React.useState(false);
  const [editTriggerId, setEditTriggerId] = React.useState<string | null>(null);

  const [removeOpen, setRemoveOpen] = React.useState(false);
  const [removeTriggerId, setRemoveTriggerId] = React.useState<string | null>(
    null,
  );

  const [confirmDeleteOpen, setConfirmDeleteOpen] = React.useState(false);
  const [confirmDeleteTriggerId, setConfirmDeleteTriggerId] = React.useState<
    string | null
  >(null);

  const [isUpdating, setIsUpdating] = React.useState(false);
  const [isDeleting, setIsDeleting] = React.useState(false);

  const handleReset = () => {
    setMembers(INITIAL_TEAM_MEMBERS);
    setSelectedRole(null);
  };

  const handleDeleteMember = async (memberId: string) => {
    setIsDeleting(true);

    toast.promise(
      new Promise<void>((resolve, reject) => {
        setTimeout(() => {
          const shouldSucceed = Math.random() > 0.1;
          if (shouldSucceed) {
            setMembers((prev) => prev.filter((m) => m.id !== memberId));
            resolve();
          } else {
            reject(new Error("Failed to delete member."));
          }
        }, 1500);
      }),
      {
        error: (err: Error) => {
          setIsDeleting(false);
          return { description: err.message, title: "Error" };
        },
        loading: { title: "Deleting member..." },
        success: () => {
          setConfirmDeleteOpen(false);
          setRemoveOpen(false);
          setIsDeleting(false);
          return {
            description: "Member deleted successfully",
            title: "Success",
          };
        },
      },
    );
  };

  const handleUpdateRole = async (
    memberId: string,
    newRole: TeamMember["role"],
  ) => {
    setIsUpdating(true);

    toast.promise(
      new Promise<void>((resolve, reject) => {
        setTimeout(() => {
          const shouldSucceed = Math.random() > 0.1;
          if (shouldSucceed) {
            setMembers((prev) =>
              prev.map((m) =>
                m.id === memberId ? { ...m, role: newRole } : m,
              ),
            );
            resolve();
          } else {
            reject(new Error("Failed to update role."));
          }
        }, 1500);
      }),
      {
        error: (err: Error) => {
          setIsUpdating(false);
          return { description: err.message, title: "Error" };
        },
        loading: { title: "Updating role..." },
        success: () => {
          setEditOpen(false);
          setSelectedRole(null);
          setIsUpdating(false);
          return { description: "Role updated successfully", title: "Success" };
        },
      },
    );
  };

  const handleConfirmDiscard = () => {
    closeConfirmDialogHandle.close();
    setEditOpen(false);
    setSelectedRole(null);
  };

  const handleEditDialogChange = (
    isOpen: boolean,
    eventDetails: DialogRootChangeEventDetails,
  ) => {
    if (!isOpen && selectedRole !== null && editingMember) {
      if (selectedRole !== editingMember.role) {
        closeConfirmDialogHandle.open(null);
        return; // Prevents the dialog from closing
      }
    }

    setEditOpen(isOpen);

    if (isOpen) {
      setEditTriggerId(eventDetails.trigger?.id ?? null);
    } else {
      setSelectedRole(null);
      setEditingMember(null);
    }
  };

  return (
    <div className="w-full max-w-2xl mx-auto border rounded-md">
      <div className="flex justify-between p-6">
        <div className="space-y-2">
          <div className="text-lg font-semibold">Team Members</div>
          <div className="text-sm text-muted-foreground">
            Manage your team members and their account permissions.
          </div>
        </div>
        <Button onClick={handleReset} size="sm" variant="outline">
          Reset
        </Button>
      </div>
      <Separator />

      <div className="flex flex-col gap-6 p-6 min-h-140">
        {members.map((member) => (
          <div
            className="flex items-center justify-between space-x-4"
            key={member.id}
          >
            <MemberCard member={member} />
            <div className="flex items-center gap-3">
              <Badge
                variant={member.role === "Admin" ? "default" : "secondary"}
              >
                {member.role}
              </Badge>

              {/* Detached Trigger */}
              <DropdownMenuTrigger
                handle={menuHandle}
                payload={member}
                render={(props, state) => {
                  return (
                    <Button
                      className={cn(state.open && "bg-accent hover:bg-accent!")}
                      size="icon-xs"
                      variant={"ghost"}
                      {...props}
                    >
                      <MoreHorizontal className="size-4" />
                      <span className="sr-only">
                        Open menu for {member.name}
                      </span>
                    </Button>
                  );
                }}
              />
            </div>
          </div>
        ))}
      </div>

      <DropdownMenu handle={menuHandle}>
        {({ payload: member }) => (
          <DropdownMenuContent
            align="center"
            className="w-40"
            matchAnchorWidth={false}
          >
            <DropdownMenuItem
              onClick={() => {
                setEditingMember(member as TeamMember);
                editMemberDialogHandle.openWithPayload(member as TeamMember);
              }}
            >
              <Eye className="size-4" />
              <span>View member</span>
            </DropdownMenuItem>
            <DropdownMenuItem
              onClick={() =>
                removeMemberDialogHandle.openWithPayload(member as TeamMember)
              }
              variant="destructive"
            >
              <Trash2 className="size-4" />
              <span>Delete member</span>
            </DropdownMenuItem>
          </DropdownMenuContent>
        )}
      </DropdownMenu>

      {/* Edit Dialog */}
      <Dialog
        handle={editMemberDialogHandle}
        onOpenChange={handleEditDialogChange}
        open={editOpen}
        triggerId={editTriggerId}
      >
        {({ payload }) => {
          const currentRole = selectedRole || payload?.role;
          const isDirty = currentRole !== payload?.role;
          const isSaveDisabled = isUpdating || !isDirty;
          return (
            <DialogContent layout="stacked" showCloseButton>
              <DialogHeader>
                <DialogTitle>Edit Permissions</DialogTitle>
                {payload && (
                  <DialogDescription>
                    Change the access level for{" "}
                    <span className="font-medium text-foreground">
                      {payload.name}
                    </span>
                  </DialogDescription>
                )}
              </DialogHeader>
              {payload && (
                <div className="grid gap-6 py-4">
                  <MemberCard member={payload} />

                  <div className="space-y-2">
                    <Label>Role</Label>
                    <Select
                      disabled={isUpdating}
                      onValueChange={(value) =>
                        setSelectedRole(value as TeamMember["role"])
                      }
                      value={currentRole}
                    >
                      <SelectTriggerGroup
                        indicatorIcon={<ChevronsUpDownIcon />}
                      />
                      <SelectContent alignItemWithTrigger>
                        <SelectItemContent value="Admin">
                          <ShieldAlert className="size-4 opacity-50" />
                          <span>Admin</span>
                        </SelectItemContent>
                        <SelectItemContent value="Member">
                          <ShieldCheck className="size-4 opacity-50" />
                          <span>Member</span>
                        </SelectItemContent>
                        <SelectItemContent value="Viewer">
                          <User className="size-4 opacity-50" />
                          <span>Viewer</span>
                        </SelectItemContent>
                      </SelectContent>
                    </Select>
                  </div>
                </div>
              )}

              <DialogFooter>
                <DialogClose
                  disabled={isUpdating}
                  render={<Button variant="outline" />}
                >
                  Cancel
                </DialogClose>
                <Button
                  disabled={isSaveDisabled}
                  onClick={() => {
                    if (payload && currentRole) {
                      handleUpdateRole(payload.id, currentRole);
                    }
                  }}
                >
                  {isUpdating ? (
                    <>
                      <Loader2 className="mr-2 h-4 w-4 animate-spin" />
                      Saving...
                    </>
                  ) : (
                    "Save Changes"
                  )}
                </Button>
              </DialogFooter>
              {/* Discard Confirmation */}
              <AlertDialog handle={closeConfirmDialogHandle}>
                <AlertDialogContent layout="stacked">
                  <AlertDialogHeader>
                    <AlertDialogTitle>Discard changes?</AlertDialogTitle>
                    <AlertDialogDescription>
                      You have unsaved changes. Are you sure you want to close?
                      The changes you made will be lost.
                    </AlertDialogDescription>
                  </AlertDialogHeader>
                  <AlertDialogFooter>
                    <AlertDialogClose render={<Button variant="outline" />}>
                      No, Keep Editing
                    </AlertDialogClose>
                    <Button
                      onClick={handleConfirmDiscard}
                      variant="destructive"
                    >
                      Yes, Discard
                    </Button>
                  </AlertDialogFooter>
                </AlertDialogContent>
              </AlertDialog>
            </DialogContent>
          );
        }}
      </Dialog>

      {/* Remove Dialog */}
      <Dialog
        handle={removeMemberDialogHandle}
        onOpenChange={(open, event) => {
          setRemoveOpen(open);
          if (open) setRemoveTriggerId(event.trigger?.id ?? null);
        }}
        open={removeOpen}
        triggerId={removeTriggerId}
      >
        {({ payload }) => (
          <DialogContent layout="stacked">
            <DialogHeader>
              <DialogTitle>Remove Member</DialogTitle>
              <DialogDescription>
                Review the member details before removing.
              </DialogDescription>
            </DialogHeader>
            {payload && <MemberCard member={payload} />}
            <DialogFooter>
              <DialogClose
                render={
                  <Button disabled={isDeleting} variant="outline">
                    Cancel
                  </Button>
                }
              />
              <AlertDialogTrigger
                handle={confirmDeleteDialogHandle}
                id={`confirm-delete-trigger-${payload?.id}`}
                payload={payload}
                render={
                  <Button disabled={isDeleting} variant="destructive">
                    Delete Member
                  </Button>
                }
              />
            </DialogFooter>

            {/* Confirm Delete Dialog */}
            <AlertDialog
              handle={confirmDeleteDialogHandle}
              onOpenChange={(open, event) => {
                setConfirmDeleteOpen(open);
                if (open) setConfirmDeleteTriggerId(event.trigger?.id ?? null);
              }}
              open={confirmDeleteOpen}
              triggerId={confirmDeleteTriggerId}
            >
              {({ payload: confirmPayload }) => (
                <AlertDialogContent layout="stacked">
                  <AlertDialogHeader>
                    <AlertDialogTitle>
                      Are you absolutely sure?
                    </AlertDialogTitle>
                    <AlertDialogDescription>
                      This action cannot be undone. This will permanently remove{" "}
                      <span className="font-medium text-foreground">
                        {confirmPayload?.name}
                      </span>{" "}
                      from the team.
                    </AlertDialogDescription>
                  </AlertDialogHeader>
                  <AlertDialogFooter>
                    <AlertDialogClose
                      disabled={isDeleting}
                      onClick={() => {
                        setConfirmDeleteOpen(false);
                      }}
                      render={<Button variant="outline" />}
                    >
                      Cancel
                    </AlertDialogClose>
                    <Button
                      disabled={isDeleting}
                      onClick={() => {
                        if (confirmPayload) {
                          handleDeleteMember(confirmPayload.id);
                        }
                      }}
                      variant="destructive"
                    >
                      {isDeleting ? (
                        <>
                          <Loader2 className="animate-spin" />
                          Deleting...
                        </>
                      ) : (
                        "Confirm Deletion"
                      )}
                    </Button>
                  </AlertDialogFooter>
                </AlertDialogContent>
              )}
            </AlertDialog>
          </DialogContent>
        )}
      </Dialog>
    </div>
  );
}

const INITIAL_TEAM_MEMBERS: TeamMember[] = [
  {
    email: "sofia@example.com",
    id: "1",
    initials: "SD",
    name: "Sofia Davis",
    role: "Admin",
  },
  {
    email: "jackson@example.com",
    id: "2",
    initials: "JL",
    name: "Jackson Lee",
    role: "Member",
  },
  {
    email: "isabella@example.com",
    id: "3",
    initials: "IN",
    name: "Isabella Nguyen",
    role: "Viewer",
  },
  {
    email: "lucy@example.com",
    id: "4",
    initials: "LU",
    name: "Lucy Wang",
    role: "Member",
  },
  {
    email: "matt@example.com",
    id: "5",
    initials: "MA",
    name: "Matthew Brown",
    role: "Viewer",
  },
  {
    email: "alex@example.com",
    id: "6",
    initials: "AL",
    name: "Alexander Johnson",
    role: "Admin",
  },
  {
    email: "allen@example.com",
    id: "7",
    initials: "AL",
    name: "Allen Lee",
    role: "Member",
  },
];
registry/blocks/dialog-01/components/member-card.tsx
import { cn } from "@/lib/utils";
import { Avatar, AvatarFallback } from "@/registry/ui/avatar";
import type { TeamMember } from "./team-management";

export const MemberCard = ({
  member,
  className,
}: {
  member: TeamMember;
  className?: string;
}) => {
  return (
    <div
      className={cn(
        "flex items-center justify-between space-x-4 p-2",
        className,
      )}
    >
      <div className="flex items-center space-x-4">
        <Avatar>
          <AvatarFallback>{member.initials}</AvatarFallback>
        </Avatar>
        <div>
          <p className="text-sm font-medium leading-none">{member.name}</p>
          <p className="text-sm text-muted-foreground">{member.email}</p>
        </div>
      </div>
    </div>
  );
};

More from lumiui

Browse all components in this library →