{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "action-with-loading-dropdown", "type": "registry:component", "title": "Action with Loading Dropdown", "description": "A dropdown menu for actions with loading state", "dependencies": [ "lucide-react", "@tanstack/react-query" ], "registryDependencies": [ "dropdown-menu", "button" ], "files": [ { "path": "registry/default/components/dropdown-menu/action-with-loading-dropdown.tsx", "content": "'use client'\r\n\r\nimport { Button } from '@/registry/default/ui/button'\r\nimport {\r\n DropdownMenu,\r\n DropdownMenuContent,\r\n DropdownMenuGroup,\r\n DropdownMenuItem,\r\n DropdownMenuLabel,\r\n DropdownMenuSeparator,\r\n DropdownMenuShortcut,\r\n DropdownMenuTrigger,\r\n} from '@/registry/default/ui/dropdown-menu'\r\nimport { useMutation } from '@tanstack/react-query'\r\nimport {\r\n EllipsisIcon,\r\n Loader2Icon,\r\n LockIcon,\r\n ScanSearchIcon,\r\n SquarePenIcon,\r\n Trash2Icon,\r\n} from 'lucide-react'\r\nimport React, { Dispatch, JSX, SetStateAction } from 'react'\r\n\r\ntype ActionDropdownProps = {\r\n align?: 'start' | 'end' | 'center'\r\n side?: 'top' | 'right' | 'bottom' | 'left'\r\n}\r\n\r\ntype ComponentProps = {\r\n open: boolean\r\n setOpen: Dispatch>\r\n}\r\n\r\ntype Item = {\r\n id: string\r\n subItems: {\r\n id: string\r\n icon: React.ReactNode\r\n variant?: 'destructive' | 'default'\r\n Component: ({ open, setOpen }: ComponentProps) => JSX.Element\r\n }[]\r\n}\r\n\r\nconst items: Item[] = [\r\n {\r\n id: 'general',\r\n subItems: [\r\n {\r\n id: 'edit',\r\n icon: ,\r\n Component: EditItem,\r\n },\r\n {\r\n id: 'view',\r\n icon: ,\r\n Component: ViewItem,\r\n },\r\n {\r\n id: 'block',\r\n icon: ,\r\n Component: BlockItem,\r\n },\r\n ],\r\n },\r\n {\r\n id: 'destructive',\r\n subItems: [\r\n {\r\n id: 'delete',\r\n icon: ,\r\n Component: DeleteItem,\r\n variant: 'destructive',\r\n },\r\n ],\r\n },\r\n]\r\n\r\nexport default function ActionWithLoadingDropdown({\r\n align = 'end',\r\n side = 'bottom',\r\n}: ActionDropdownProps) {\r\n const [open, setOpen] = React.useState(false)\r\n\r\n return (\r\n \r\n \r\n \r\n \r\n \r\n Actions\r\n \r\n {items.map((item, index) => (\r\n \r\n {item.subItems.map(({ Component, id }) => (\r\n \r\n ))}\r\n {index < items.length - 1 && }\r\n \r\n ))}\r\n \r\n \r\n )\r\n}\r\n\r\nfunction EditItem({ setOpen }: ComponentProps) {\r\n const { isPending, mutate } = useMutation({\r\n mutationFn: async () => {\r\n return new Promise((resolve) => {\r\n setTimeout(() => {\r\n resolve('Edit')\r\n }, 1000)\r\n })\r\n },\r\n onSuccess: () => {\r\n console.log(`Mutation successful: Edit`)\r\n },\r\n onError: () => {\r\n console.log(`Mutation failed: Edit`)\r\n },\r\n onSettled: () => {\r\n setOpen(false)\r\n },\r\n })\r\n\r\n return (\r\n {\r\n e.preventDefault()\r\n console.log('Edit clicked')\r\n mutate()\r\n }}\r\n disabled={isPending}\r\n >\r\n {isPending ? (\r\n \r\n ) : (\r\n \r\n )}\r\n Edit\r\n E\r\n \r\n )\r\n}\r\n\r\nfunction ViewItem({ setOpen }: ComponentProps) {\r\n const { isPending, mutate } = useMutation({\r\n mutationFn: async () => {\r\n return new Promise((resolve) => {\r\n setTimeout(() => {\r\n resolve('View')\r\n }, 1000)\r\n })\r\n },\r\n onSuccess: () => {\r\n console.log(`Mutation successful: View`)\r\n },\r\n onError: () => {\r\n console.log(`Mutation failed: View`)\r\n },\r\n onSettled: () => {\r\n setOpen(false)\r\n },\r\n })\r\n\r\n return (\r\n {\r\n e.preventDefault()\r\n console.log('View clicked')\r\n mutate()\r\n }}\r\n disabled={isPending}\r\n >\r\n {isPending ? (\r\n \r\n ) : (\r\n \r\n )}\r\n View\r\n V\r\n \r\n )\r\n}\r\n\r\nfunction BlockItem({ setOpen }: ComponentProps) {\r\n const { isPending, mutate } = useMutation({\r\n mutationFn: async () => {\r\n return new Promise((resolve) => {\r\n setTimeout(() => {\r\n resolve('Block')\r\n }, 1000)\r\n })\r\n },\r\n onSuccess: () => {\r\n console.log(`Mutation successful: Block`)\r\n },\r\n onError: () => {\r\n console.log(`Mutation failed: Block`)\r\n },\r\n onSettled: () => {\r\n setOpen(false)\r\n },\r\n })\r\n\r\n return (\r\n {\r\n e.preventDefault()\r\n console.log('Block clicked')\r\n mutate()\r\n }}\r\n disabled={isPending}\r\n >\r\n {isPending ? (\r\n \r\n ) : (\r\n \r\n )}\r\n Block\r\n B\r\n \r\n )\r\n}\r\n\r\nfunction DeleteItem({ setOpen }: ComponentProps) {\r\n const { isPending, mutate } = useMutation({\r\n mutationFn: async () => {\r\n return new Promise((resolve) => {\r\n setTimeout(() => {\r\n resolve('Delete')\r\n }, 1000)\r\n })\r\n },\r\n onSuccess: () => {\r\n console.log(`Mutation successful: Delete`)\r\n },\r\n onError: () => {\r\n console.log(`Mutation failed: Delete`)\r\n },\r\n onSettled: () => {\r\n setOpen(false)\r\n },\r\n })\r\n\r\n return (\r\n {\r\n e.preventDefault()\r\n console.log('Delete clicked')\r\n mutate()\r\n }}\r\n variant=\"destructive\"\r\n disabled={isPending}\r\n >\r\n {isPending ? (\r\n \r\n ) : (\r\n \r\n )}\r\n Delete\r\n \r\n D\r\n \r\n \r\n )\r\n}\r\n", "type": "registry:component", "target": "components/action-with-loading-dropdown.tsx" } ] }