{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "delete-button", "title": "Delete Button", "description": "S3 object delete button with confirmation dialog and toast support.", "dependencies": [ "@better-s3/core", "@better-s3/react", "lucide-react", "sonner" ], "registryDependencies": [ "button", "alert-dialog", "tooltip" ], "files": [ { "path": "registry/better-s3-ui/components/delete/delete-button.tsx", "content": "\"use client\";\n\nimport type { ComponentProps, ReactNode } from \"react\";\nimport {\n Trash2Icon,\n LoaderIcon,\n AlertCircleIcon,\n CheckCircle2Icon,\n} from \"lucide-react\";\nimport { cn } from \"@/registry/better-s3-ui/lib/utils\";\nimport { formatFileSize, truncateFileName } from \"@better-s3/core\";\nimport type { DeleteHooks } from \"@better-s3/react\";\nimport type { S3Api } from \"@better-s3/core\";\nimport { useDelete, useS3Translations } from \"@better-s3/react\";\nimport { Button } from \"@/registry/better-s3-ui/components/ui/button\";\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogMedia,\n AlertDialogTitle,\n AlertDialogTrigger,\n} from \"@/registry/better-s3-ui/components/ui/alert-dialog\";\nimport {\n Tooltip,\n TooltipContent,\n TooltipTrigger,\n} from \"@/registry/better-s3-ui/components/ui/tooltip\";\nimport { useDeleteToast } from \"@/registry/better-s3-ui/hooks/use-delete-toast\";\n\n/** Keep LTR file names and sizes readable inside RTL confirmation copy. */\nfunction isolateLtr(value: string): string {\n return `\\u2066${value}\\u2069`;\n}\n\n/** Props for {@link DeleteButton}. */\nexport type DeleteButtonProps = DeleteHooks & {\n /** S3Api. Optional when an `` is present in the tree. */\n api?: S3Api;\n /** S3 object key to delete. */\n objectKey: string;\n /** Display file name in the confirmation dialog. */\n fileName?: string;\n /** Display file size in the confirmation dialog. */\n fileSize?: number;\n /** Target bucket (overrides server default). */\n bucket?: string;\n /** Button label. */\n label?: string;\n /** Custom button content. Replaces default icon + label. */\n children?: ReactNode;\n className?: string;\n disabled?: boolean;\n tooltipText?: string;\n /** Show a sonner toast during delete. @default true */\n toast?: boolean;\n /** Show inline error below the button. @default true */\n showStatus?: boolean;\n confirmTitle?: string;\n confirmDescription?: string;\n /** Button variant. @default \"destructive\" */\n variant?: ComponentProps[\"variant\"];\n /** Button size. @default \"default\" */\n size?: ComponentProps[\"size\"];\n /** Extra classes on the trigger button element. */\n buttonClassName?: string;\n};\n\nexport function DeleteButton({\n api,\n objectKey,\n fileName,\n fileSize,\n bucket,\n label,\n children,\n className,\n disabled,\n tooltipText,\n toast: enableToast = true,\n showStatus = true,\n confirmTitle,\n confirmDescription,\n variant = \"destructive\",\n size = \"default\",\n buttonClassName,\n beforeDelete,\n onDeleteStart,\n onSuccess,\n onError,\n}: DeleteButtonProps) {\n const t = useS3Translations();\n const displayName = fileName ?? objectKey.split(\"/\").pop() ?? objectKey;\n const toastHandlers = useDeleteToast({\n enabled: enableToast,\n displayName,\n });\n\n const del = useDelete({\n api,\n bucket,\n beforeDelete,\n onDeleteStart,\n onSuccess: (key) => {\n toastHandlers.onSuccess(key);\n onSuccess?.(key);\n },\n onError: (key, error, phase) => {\n toastHandlers.onError(key, error);\n onError?.(key, error, phase);\n },\n });\n\n const isDeleting = del.phase === \"deleting\";\n const isDisabled = disabled || isDeleting;\n\n const buttonContent = children ?? (\n <>\n {isDeleting ? (\n \n ) : (\n \n )}\n {label ?? t(\"delete.label\")}\n \n );\n\n const fileLabel =\n fileSize != null\n ? `${truncateFileName(displayName)} (${formatFileSize(fileSize)})`\n : truncateFileName(displayName);\n\n const description =\n confirmDescription ??\n t(\"delete.confirmDescription\", {\n name: isolateLtr(fileLabel),\n });\n\n return (\n
\n
\n {\n if (!open) del.cancelDelete();\n }}\n >\n \n del.requestDelete(objectKey)}\n render={\n \n }\n />\n }\n >\n {buttonContent}\n \n {tooltipText ?? t(\"delete.file\")}\n \n\n \n \n \n \n \n \n {confirmTitle ?? t(\"delete.confirmTitle\")}\n \n \n {description}\n \n \n \n {t(\"common.cancel\")}\n del.confirmDelete()}\n >\n {t(\"delete.label\")}\n \n \n \n \n
\n\n {showStatus && del.phase === \"success\" && (\n
\n \n

\n {t(\"delete.inlineDeleted\", { name: truncateFileName(displayName) })}\n

\n
\n )}\n\n {showStatus && del.phase === \"error\" && (\n
\n \n

\n {del.error ?? t(\"delete.failed\")}\n

\n
\n )}\n
\n );\n}\n", "type": "registry:component", "target": "@components/delete/delete-button.tsx" }, { "path": "registry/better-s3-ui/hooks/use-delete-toast.tsx", "content": "\"use client\";\n\nimport { toast } from \"sonner\";\nimport { truncateFileName } from \"@better-s3/core\";\nimport { useS3Translations } from \"@better-s3/react\";\n\nexport type DeleteToastOptions = {\n enabled?: boolean;\n displayName: string;\n};\n\nexport function useDeleteToast({\n enabled = true,\n displayName,\n}: DeleteToastOptions) {\n const t = useS3Translations();\n\n const onSuccess = (_key: string) => {\n if (!enabled) return;\n toast.success(t(\"delete.successToast\"), {\n description: {truncateFileName(displayName)},\n });\n };\n\n const onError = (_key: string, error: unknown) => {\n if (!enabled) return;\n toast.error(t(\"delete.failed\"), {\n description: (\n \n {error instanceof Error ? error.message : t(\"common.unknownError\")}\n \n ),\n });\n };\n\n return { onSuccess, onError };\n}\n", "type": "registry:hook", "target": "@hooks/use-delete-toast.tsx" } ], "type": "registry:component" }