@better-s3/ui 3.1045.0 → 3.1045.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,2 +1,1042 @@
1
- import {XIcon,CheckCircleIcon,AlertCircleIcon,UploadIcon,LoaderIcon,DownloadIcon,Trash2Icon,CheckCircle2Icon}from'lucide-react';import {formatFileSize,useUploadControls,useDownload,useFetchDownload,useDelete}from'@better-s3/react';import {clsx}from'clsx';import {twMerge}from'tailwind-merge';import {Button}from'@base-ui/react/button';import {cva}from'class-variance-authority';import {jsxs,jsx}from'react/jsx-runtime';import {Tooltip}from'@base-ui/react/tooltip';import {Progress}from'@base-ui/react/progress';import {useRef,useEffect}from'react';import {toast}from'sonner';import {AlertDialog}from'@base-ui/react/alert-dialog';function a(...e){return twMerge(clsx(e))}var Ue=cva("group/button cursor-pointer inline-flex shrink-0 items-center justify-center rounded-md border border-transparent bg-clip-padding text-xs/relaxed font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/30 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",{variants:{variant:{default:"bg-primary text-primary-foreground hover:bg-primary/80",outline:"border-border hover:bg-input/50 hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:bg-input/30",secondary:"bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",ghost:"hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",destructive:"bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",link:"text-primary underline-offset-4 hover:underline"},size:{default:"h-7 gap-1 px-2 text-xs/relaxed has-data-[icon=inline-end]:pe-1.5 has-data-[icon=inline-start]:ps-1.5 [&_svg:not([class*='size-'])]:size-3.5",xs:"h-5 gap-1 rounded-sm px-2 text-[0.625rem] has-data-[icon=inline-end]:pe-1.5 has-data-[icon=inline-start]:ps-1.5 [&_svg:not([class*='size-'])]:size-2.5",sm:"h-6 gap-1 px-2 text-xs/relaxed has-data-[icon=inline-end]:pe-1.5 has-data-[icon=inline-start]:ps-1.5 [&_svg:not([class*='size-'])]:size-3",lg:"h-8 gap-1 px-2.5 text-xs/relaxed has-data-[icon=inline-end]:pe-2 has-data-[icon=inline-start]:ps-2 [&_svg:not([class*='size-'])]:size-4",icon:"size-7 [&_svg:not([class*='size-'])]:size-3.5","icon-xs":"size-5 rounded-sm [&_svg:not([class*='size-'])]:size-2.5","icon-sm":"size-6 [&_svg:not([class*='size-'])]:size-3","icon-lg":"size-8 [&_svg:not([class*='size-'])]:size-4"}},defaultVariants:{variant:"default",size:"default"}});function c({className:e,variant:t="default",size:o="default",...r}){return jsx(Button,{"data-slot":"button",className:a(Ue({variant:t,size:o,className:e})),...r})}function B({delay:e=0,...t}){return jsx(Tooltip.Provider,{"data-slot":"tooltip-provider",delay:e,...t})}function S({...e}){return jsx(Tooltip.Root,{"data-slot":"tooltip",...e})}function I({...e}){return jsx(Tooltip.Trigger,{"data-slot":"tooltip-trigger",...e})}function $({className:e,side:t="top",sideOffset:o=4,align:r="center",alignOffset:l=0,children:n,...i}){return jsx(Tooltip.Portal,{children:jsx(Tooltip.Positioner,{align:r,alignOffset:l,side:t,sideOffset:o,className:"isolate z-50",children:jsxs(Tooltip.Popup,{"data-slot":"tooltip-content",className:a("z-50 inline-flex w-fit max-w-xs origin-(--transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pe-1.5 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-start-2 data-[side=inline-start]:slide-in-from-end-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",e),...i,children:[n,jsx(Tooltip.Arrow,{className:"z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground data-[side=bottom]:top-1 data-[side=inline-end]:top-1/2! data-[side=inline-end]:-start-1 data-[side=inline-end]:-translate-y-1/2 data-[side=inline-start]:top-1/2! data-[side=inline-start]:-end-1 data-[side=inline-start]:-translate-y-1/2 data-[side=left]:top-1/2! data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:top-1/2! data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5"})]})})})}function W({percent:e,size:t=20,strokeWidth:o=2.5}){let r=(t-o)/2,l=2*Math.PI*r,n=l-e/100*l;return jsxs("svg",{width:t,height:t,className:"shrink-0 -rotate-90",children:[jsx("circle",{cx:t/2,cy:t/2,r,fill:"none",stroke:"currentColor",strokeWidth:o,className:"text-muted-foreground/20"}),jsx("circle",{cx:t/2,cy:t/2,r,fill:"none",stroke:"currentColor",strokeWidth:o,strokeDasharray:l,strokeDashoffset:n,strokeLinecap:"round",className:"text-primary transition-[stroke-dashoffset] duration-200"})]})}function O({phase:e,progress:t,error:o,fileInfo:r,onCancel:l}){return e==="idle"?null:e==="uploading"&&r?jsxs("div",{className:"flex w-full items-center gap-1.5 text-xs",children:[jsx(W,{percent:t.percent,size:14,strokeWidth:2}),jsx("span",{className:"max-w-32 min-w-16 truncate sm:max-w-48",children:r.name}),jsxs("span",{className:"shrink-0 text-muted-foreground",children:[formatFileSize(t.loaded)," / ",formatFileSize(r.size)," (",t.percent,"%)"]}),jsx(c,{variant:"ghost",size:"icon",className:"ml-auto size-6 shrink-0",onClick:n=>{n.stopPropagation(),l?.();},children:jsx(XIcon,{className:"size-3.5"})})]}):e==="success"&&r?jsxs("div",{className:"flex items-center gap-1.5 text-xs",children:[jsx(CheckCircleIcon,{className:"size-3.5 shrink-0 text-green-600"}),jsx("span",{className:"[overflow-wrap:anywhere] max-w-32 min-w-16 truncate sm:max-w-48",children:r.name}),jsx("span",{className:"shrink-0 text-muted-foreground",children:formatFileSize(r.size)})]}):e==="error"?jsxs("div",{className:"flex min-w-0 items-start gap-1.5 text-xs",children:[jsx(AlertCircleIcon,{className:"mt-0.5 size-3.5 shrink-0 text-destructive"}),jsx("p",{className:"min-w-0 [overflow-wrap:anywhere] text-destructive",children:o??"Upload failed"})]}):e==="validating"||e==="presigning"?jsx("span",{className:"text-xs text-muted-foreground",children:"Preparing\u2026"}):null}function ne({className:e,children:t,value:o,...r}){return jsxs(Progress.Root,{value:o,"data-slot":"progress",className:a("flex flex-wrap gap-3",e),...r,children:[t,jsx(Me,{children:jsx(Fe,{})})]})}function Me({className:e,...t}){return jsx(Progress.Track,{className:a("relative flex h-1 w-full items-center overflow-x-hidden rounded-md bg-muted",e),"data-slot":"progress-track",...t})}function Fe({className:e,...t}){return jsx(Progress.Indicator,{"data-slot":"progress-indicator",className:a("h-full bg-primary transition-all",e),...t})}function le({className:e,...t}){return jsx(Progress.Label,{className:a("text-xs/relaxed font-medium",e),"data-slot":"progress-label",...t})}function de({className:e,...t}){return jsx(Progress.Value,{className:a("ms-auto text-xs/relaxed text-muted-foreground tabular-nums",e),"data-slot":"progress-value",...t})}function H({phase:e,files:t,totalProgress:o,error:r,onCancel:l}){return e==="idle"?null:e==="uploading"?jsxs("div",{className:"flex w-full flex-col gap-2",children:[jsxs("div",{className:"flex w-full items-center gap-1.5",children:[jsxs(ne,{value:o.percent,className:"flex-1",children:[jsxs(le,{children:[t.filter(n=>n.status==="success").length,"/",t.length," files"]}),jsx(de,{})]}),jsx(c,{variant:"ghost",size:"icon",className:"size-7 shrink-0",onClick:n=>{n.stopPropagation(),l?.();},children:jsx(XIcon,{className:"size-4"})})]}),jsx(ae,{files:t})]}):e==="success"?jsxs("div",{className:"flex w-full flex-col gap-1",children:[jsxs("span",{className:"text-xs text-green-600",children:["All ",t.length," file(s) uploaded"]}),jsx(ae,{files:t})]}):e==="error"?jsxs("div",{className:"flex w-full flex-col gap-1",children:[jsx("span",{className:"text-xs text-destructive",children:r??"Upload failed"}),t.length>0&&jsx(ae,{files:t})]}):e==="validating"?jsx("span",{className:"text-xs text-muted-foreground",children:"Validating\u2026"}):null}function ae({files:e}){return jsx("ul",{className:"flex flex-col gap-1",children:e.map(t=>jsxs("li",{className:"flex flex-col gap-0.5 text-xs",children:[jsxs("div",{className:"flex items-center gap-1.5",children:[t.status==="success"&&jsx(CheckCircleIcon,{className:"size-3.5 shrink-0 text-green-600"}),t.status==="error"&&jsx(AlertCircleIcon,{className:"size-3.5 shrink-0 text-destructive"}),(t.status==="pending"||t.status==="uploading")&&jsx(W,{percent:t.status==="uploading"?t.progress.percent:0,size:14,strokeWidth:2}),jsx("span",{className:"[overflow-wrap:anywhere] max-w-32 min-w-16 truncate sm:max-w-48",children:t.fileName}),t.status==="uploading"?jsxs("span",{className:"shrink-0 text-muted-foreground",children:[formatFileSize(t.progress.loaded)," /"," ",formatFileSize(t.fileSize)," (",t.progress.percent,"%)"]}):jsx("span",{className:"shrink-0 text-muted-foreground",children:formatFileSize(t.fileSize)})]}),t.status==="error"&&t.error&&jsx("span",{className:"truncate [overflow-wrap:anywhere] pl-5 text-destructive",children:t.error})]},t.id))})}var Ee=(e,t=100)=>e.length>t?e.slice(0,t)+"\u2026":e;function G(e,t){let o=useRef(null),r=e.mode==="multi",l=useRef(e.phase);if(l.current!==e.phase&&(l.current=e.phase,t&&(e.phase==="idle"&&o.current&&(toast.dismiss(o.current),o.current=null),e.phase==="success"&&(o.current&&toast.dismiss(o.current),r?toast.success(`${e.files.length} file(s) uploaded`,{description:formatFileSize(e.totalProgress.total)}):e.fileInfo&&toast.success("Upload complete",{description:formatFileSize(e.fileInfo.size)}),o.current=null),e.phase==="error"))){if(o.current&&toast.dismiss(o.current),r&&e.files.length>0){let n=e.files.filter(s=>s.status==="success").length,i=e.files.filter(s=>s.status==="error").length;toast.error("Upload finished with errors",{description:`${n} succeeded, ${i} failed`});}else toast.error("Upload failed",{description:Ee(e.error??"Unknown error")});o.current=null;}useEffect(()=>{if(!t||e.phase!=="uploading")return;let n=o.current??`upload-${Date.now()}`;if(o.current=n,r){let i=e.files.filter(s=>s.status==="success").length;toast.loading(`Uploading\u2026 ${i}/${e.files.length}`,{id:n,description:`${formatFileSize(e.totalProgress.loaded)} / ${formatFileSize(e.totalProgress.total)} (${e.totalProgress.percent}%)`,cancel:{label:"Cancel",onClick:()=>e.cancel()}});}else e.fileInfo&&toast.loading("Uploading\u2026",{id:n,description:`${formatFileSize(e.progress.loaded)} / ${formatFileSize(e.fileInfo.size)} (${e.progress.percent}%)`,cancel:{label:"Cancel",onClick:()=>e.cancel()}});},[t,e.phase,r,e.progress.loaded,e.progress.percent,e.totalProgress.loaded,e.totalProgress.total,e.totalProgress.percent,e.fileInfo,e.files,e.cancel]);}function Xe({className:e,label:t,disabled:o,tooltipText:r,toast:l=true,showStatus:n=true,...i}){let s=useUploadControls(i),d=s.mode==="multi",g=o||s.isUploading;G(s,l);let D=n?d?jsx(H,{phase:s.phase,files:s.files,totalProgress:s.totalProgress,error:s.error,onCancel:s.cancel}):jsx(O,{phase:s.phase,progress:s.progress,error:s.error,fileInfo:s.fileInfo,onCancel:s.cancel}):null;return jsxs("div",{className:a("inline-flex flex-col gap-2",e),children:[jsxs("div",{className:"inline-flex items-center gap-2",children:[jsx("input",{...s.inputProps}),jsx(B,{children:jsxs(S,{children:[jsxs(I,{render:jsx(c,{size:"default",disabled:g,onClick:s.openFilePicker}),children:[jsx(UploadIcon,{"data-icon":"inline-start"}),t??(d?"Upload files":"Upload file")]}),jsx($,{children:r??(d?"Upload files":"Upload file")})]})})]}),D]})}function Qe({className:e,label:t,disabled:o,toast:r=true,showStatus:l=true,...n}){let i=useUploadControls(n),s=i.mode==="multi",d=o||i.isUploading;G(i,r);let g=l?s?jsx(H,{phase:i.phase,files:i.files,totalProgress:i.totalProgress,error:i.error,onCancel:i.cancel}):jsx(O,{phase:i.phase,progress:i.progress,error:i.error,fileInfo:i.fileInfo,onCancel:i.cancel}):null;return jsxs("div",{className:a("flex flex-col items-center justify-center gap-3 rounded-lg border-2 border-dashed p-6 text-center transition-colors",d?"cursor-not-allowed border-muted-foreground/25":"cursor-pointer border-muted-foreground/25 hover:border-primary/50",e),onClick:d?void 0:i.openFilePicker,...d?{}:i.dropHandlers,children:[jsx("input",{...i.inputProps}),jsx(UploadIcon,{className:a("size-6 text-muted-foreground",d&&"opacity-50")}),jsx("p",{className:a("text-sm text-muted-foreground",d&&"opacity-50"),children:t??(s?"Click or drag & drop files to upload":"Click or drag & drop to upload")}),g&&jsx("div",{className:"w-full text-left",children:g})]})}var tt=(e,t=100)=>e.length>t?e.slice(0,t)+"\u2026":e;function ot({api:e,objectKey:t,fileName:o,label:r,className:l,disabled:n,toast:i=true,showStatus:s=true}){let d=useDownload({api:e,onInitiated:()=>{i&&toast.success("Download started");},onError:(D,U)=>{i&&toast.error("Download failed",{description:tt(U instanceof Error?U.message:"Unknown error")});}}),g=d.phase==="presigning";return jsxs("div",{className:a("inline-flex flex-col gap-1.5",l),children:[jsx(c,{size:"default",variant:"outline",disabled:n||g,onClick:()=>d.download(t,o),children:jsxs("span",{className:"inline-flex items-center gap-1",children:[g?jsx(LoaderIcon,{className:"animate-spin","data-icon":"inline-start"}):jsx(DownloadIcon,{"data-icon":"inline-start"}),r??"Download"]})}),s&&d.phase==="error"&&jsxs("div",{className:"flex min-w-0 items-start gap-1.5 text-xs",children:[jsx(AlertCircleIcon,{className:"mt-0.5 size-3.5 shrink-0 text-destructive"}),jsx("p",{className:"min-w-0 [overflow-wrap:anywhere] text-destructive",children:d.error??"Download failed"})]})]})}var st=(e,t=100)=>e.length>t?e.slice(0,t)+"\u2026":e;function nt({api:e,objectKey:t,fileName:o,fileSize:r,bucket:l,label:n,className:i,fillClassName:s,disabled:d,tooltipText:g="Download file",toast:D=true,showStatus:U=true,beforeDownload:Q,onDownloadStart:Y,onProgress:Z,onSuccess:K,onError:j,onCancel:V}){let x=o??t.split("/").pop()??t,f=useFetchDownload({api:e,bucket:l,beforeDownload:Q,onDownloadStart:Y,onProgress:Z,onSuccess:(v,P)=>{D&&(toast.dismiss(`dl-${t}`),toast.success("Download complete",{description:`${P}${r!=null?` \xB7 ${formatFileSize(r)}`:""}`})),K?.(v,P);},onError:(v,P,te)=>{D&&(toast.dismiss(`dl-${t}`),toast.error("Download failed",{description:st(P instanceof Error?P.message:"Unknown error")})),j?.(v,P,te);},onCancel:v=>{D&&(toast.dismiss(`dl-${t}`),toast.info("Download cancelled",{description:x})),V?.(v);}}),k=f.phase==="downloading"||f.phase==="presigning",ee=()=>{if(k){f.cancel();return}f.download(t,o);};return jsxs("div",{className:a("inline-flex flex-col gap-1.5",i),children:[jsx(B,{children:jsxs(S,{children:[jsxs(I,{render:jsx(c,{size:"default",variant:"outline",disabled:d,className:a("relative min-w-24 overflow-hidden"),onClick:ee}),children:[k&&jsx("span",{className:a("absolute inset-0 bg-primary/15 transition-[width] duration-200",s),style:{width:`${f.progress.percent}%`}}),jsxs("span",{className:"relative z-10 inline-flex items-center gap-1",children:[jsx(DownloadIcon,{"data-icon":"inline-start"}),k?formatFileSize(f.progress.loaded):n??"Download"]})]}),jsx($,{children:k?"Cancel download":g})]})}),U&&f.phase==="error"&&jsxs("div",{className:"flex min-w-0 items-start gap-1.5 text-xs",children:[jsx(AlertCircleIcon,{className:"mt-0.5 size-3.5 shrink-0 text-destructive"}),jsx("p",{className:"min-w-0 [overflow-wrap:anywhere] text-destructive",children:f.error??"Download failed"})]})]})}function me({...e}){return jsx(AlertDialog.Root,{"data-slot":"alert-dialog",...e})}function ge({...e}){return jsx(AlertDialog.Trigger,{"data-slot":"alert-dialog-trigger",...e})}function lt({...e}){return jsx(AlertDialog.Portal,{"data-slot":"alert-dialog-portal",...e})}function dt({className:e,...t}){return jsx(AlertDialog.Backdrop,{"data-slot":"alert-dialog-overlay",className:a("fixed inset-0 isolate z-50 bg-black/80 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",e),...t})}function fe({className:e,size:t="default",...o}){return jsxs(lt,{children:[jsx(dt,{}),jsx(AlertDialog.Popup,{"data-slot":"alert-dialog-content","data-size":t,className:a("group/alert-dialog-content fixed top-1/2 start-1/2 z-50 grid w-full -translate-x-1/2 rtl:translate-x-1/2 -translate-y-1/2 gap-3 rounded-xl bg-popover p-4 text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none data-[size=default]:max-w-xs data-[size=sm]:max-w-64 data-[size=default]:sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",e),...o})]})}function ve({className:e,...t}){return jsx("div",{"data-slot":"alert-dialog-header",className:a("grid grid-rows-[auto_1fr] place-items-center gap-1 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-4 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-start sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]",e),...t})}function xe({className:e,...t}){return jsx("div",{"data-slot":"alert-dialog-footer",className:a("flex flex-col-reverse gap-2 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end",e),...t})}function Pe({className:e,...t}){return jsx("div",{"data-slot":"alert-dialog-media",className:a("mb-2 inline-flex size-8 items-center justify-center rounded-md bg-muted sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-4",e),...t})}function he({className:e,...t}){return jsx(AlertDialog.Title,{"data-slot":"alert-dialog-title",className:a("font-heading text-sm font-medium sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2",e),...t})}function be({className:e,...t}){return jsx(AlertDialog.Description,{"data-slot":"alert-dialog-description",className:a("text-xs/relaxed text-balance text-muted-foreground md:text-pretty *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",e),...t})}function we({className:e,...t}){return jsx(c,{"data-slot":"alert-dialog-action",className:a(e),...t})}function De({className:e,variant:t="outline",size:o="default",...r}){return jsx(AlertDialog.Close,{"data-slot":"alert-dialog-cancel",className:a(e),render:jsx(c,{variant:t,size:o}),...r})}var vt=(e,t=100)=>e.length>t?e.slice(0,t)+"\u2026":e;function xt({api:e,objectKey:t,fileName:o,fileSize:r,bucket:l,label:n,className:i,disabled:s,tooltipText:d="Delete file",toast:g=true,showStatus:D=true,confirmTitle:U="Delete file?",confirmDescription:Q,beforeDelete:Y,onDeleteStart:Z,onSuccess:K,onError:j}){let V=o??t.split("/").pop()??t,x=useDelete({api:e,bucket:l,beforeDelete:Y,onDeleteStart:Z,onSuccess:v=>{g&&toast.success("File deleted",{description:V}),K?.(v);},onError:(v,P,te)=>{g&&toast.error("Delete failed",{description:vt(P instanceof Error?P.message:"Unknown error")}),j?.(v,P,te);}}),f=x.phase==="deleting",k=s||f,ee=Q??`Are you sure you want to delete "${V}"${r!=null?` (${formatFileSize(r)})`:""}? This action cannot be undone.`;return jsxs("div",{className:a("inline-flex flex-col gap-1.5",i),children:[jsx("div",{className:"inline-flex items-center gap-2",children:jsxs(me,{open:x.phase==="confirming",onOpenChange:v=>{v||x.cancelDelete();},children:[jsx(B,{children:jsxs(S,{children:[jsxs(I,{render:jsx(ge,{disabled:k,onClick:()=>x.requestDelete(t),render:jsx(c,{size:"default",variant:"destructive",disabled:k})}),children:[f?jsx(LoaderIcon,{className:"animate-spin","data-icon":"inline-start"}):jsx(Trash2Icon,{"data-icon":"inline-start"}),n??"Delete"]}),jsx($,{children:d})]})}),jsxs(fe,{children:[jsxs(ve,{children:[jsx(Pe,{children:jsx(Trash2Icon,{})}),jsx(he,{children:U}),jsx(be,{className:"[overflow-wrap:anywhere]",children:ee})]}),jsxs(xe,{children:[jsx(De,{children:"Cancel"}),jsx(we,{variant:"destructive",onClick:()=>x.confirmDelete(),children:"Delete"})]})]})]})}),D&&x.phase==="success"&&jsxs("div",{className:"flex min-w-0 items-center gap-1.5 text-xs",children:[jsx(CheckCircle2Icon,{className:"size-3.5 shrink-0 text-green-600"}),jsxs("p",{className:"min-w-0 [overflow-wrap:anywhere] text-green-600",children:["\u201C",jsx("span",{className:"inline-block max-w-[14ch] truncate align-bottom",children:V}),"\u201D deleted"]})]}),D&&x.phase==="error"&&jsxs("div",{className:"flex min-w-0 items-start gap-1.5 text-xs",children:[jsx(AlertCircleIcon,{className:"mt-0.5 size-3.5 shrink-0 text-destructive"}),jsx("p",{className:"min-w-0 [overflow-wrap:anywhere] text-destructive",children:x.error??"Delete failed"})]})]})}export{xt as DeleteButton,ot as DownloadButton,H as MultiUploadStatus,nt as ProgressDownloadButton,Xe as UploadButton,Qe as UploadDropzone,O as UploadStatus};//# sourceMappingURL=index.js.map
1
+ import { XIcon, CheckCircleIcon, AlertCircleIcon, UploadIcon, LoaderIcon, DownloadIcon, Trash2Icon, CheckCircle2Icon } from 'lucide-react';
2
+ import { formatFileSize, useUploadControls, useDownload, useFetchDownload, useDelete } from '@better-s3/react';
3
+ import { clsx } from 'clsx';
4
+ import { twMerge } from 'tailwind-merge';
5
+ import { Button as Button$1 } from '@base-ui/react/button';
6
+ import { cva } from 'class-variance-authority';
7
+ import { jsxs, jsx } from 'react/jsx-runtime';
8
+ import { Tooltip as Tooltip$1 } from '@base-ui/react/tooltip';
9
+ import { Progress as Progress$1 } from '@base-ui/react/progress';
10
+ import { useRef, useEffect } from 'react';
11
+ import { toast } from 'sonner';
12
+ import { AlertDialog as AlertDialog$1 } from '@base-ui/react/alert-dialog';
13
+
14
+ function cn(...inputs) {
15
+ return twMerge(clsx(inputs));
16
+ }
17
+ var buttonVariants = cva(
18
+ "group/button cursor-pointer inline-flex shrink-0 items-center justify-center rounded-md border border-transparent bg-clip-padding text-xs/relaxed font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/30 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
19
+ {
20
+ variants: {
21
+ variant: {
22
+ default: "bg-primary text-primary-foreground hover:bg-primary/80",
23
+ outline: "border-border hover:bg-input/50 hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:bg-input/30",
24
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
25
+ ghost: "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
26
+ destructive: "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
27
+ link: "text-primary underline-offset-4 hover:underline"
28
+ },
29
+ size: {
30
+ default: "h-7 gap-1 px-2 text-xs/relaxed has-data-[icon=inline-end]:pe-1.5 has-data-[icon=inline-start]:ps-1.5 [&_svg:not([class*='size-'])]:size-3.5",
31
+ xs: "h-5 gap-1 rounded-sm px-2 text-[0.625rem] has-data-[icon=inline-end]:pe-1.5 has-data-[icon=inline-start]:ps-1.5 [&_svg:not([class*='size-'])]:size-2.5",
32
+ sm: "h-6 gap-1 px-2 text-xs/relaxed has-data-[icon=inline-end]:pe-1.5 has-data-[icon=inline-start]:ps-1.5 [&_svg:not([class*='size-'])]:size-3",
33
+ lg: "h-8 gap-1 px-2.5 text-xs/relaxed has-data-[icon=inline-end]:pe-2 has-data-[icon=inline-start]:ps-2 [&_svg:not([class*='size-'])]:size-4",
34
+ icon: "size-7 [&_svg:not([class*='size-'])]:size-3.5",
35
+ "icon-xs": "size-5 rounded-sm [&_svg:not([class*='size-'])]:size-2.5",
36
+ "icon-sm": "size-6 [&_svg:not([class*='size-'])]:size-3",
37
+ "icon-lg": "size-8 [&_svg:not([class*='size-'])]:size-4"
38
+ }
39
+ },
40
+ defaultVariants: {
41
+ variant: "default",
42
+ size: "default"
43
+ }
44
+ }
45
+ );
46
+ function Button({
47
+ className,
48
+ variant = "default",
49
+ size = "default",
50
+ ...props
51
+ }) {
52
+ return /* @__PURE__ */ jsx(
53
+ Button$1,
54
+ {
55
+ "data-slot": "button",
56
+ className: cn(buttonVariants({ variant, size, className })),
57
+ ...props
58
+ }
59
+ );
60
+ }
61
+ function TooltipProvider({
62
+ delay = 0,
63
+ ...props
64
+ }) {
65
+ return /* @__PURE__ */ jsx(
66
+ Tooltip$1.Provider,
67
+ {
68
+ "data-slot": "tooltip-provider",
69
+ delay,
70
+ ...props
71
+ }
72
+ );
73
+ }
74
+ function Tooltip({ ...props }) {
75
+ return /* @__PURE__ */ jsx(Tooltip$1.Root, { "data-slot": "tooltip", ...props });
76
+ }
77
+ function TooltipTrigger({ ...props }) {
78
+ return /* @__PURE__ */ jsx(Tooltip$1.Trigger, { "data-slot": "tooltip-trigger", ...props });
79
+ }
80
+ function TooltipContent({
81
+ className,
82
+ side = "top",
83
+ sideOffset = 4,
84
+ align = "center",
85
+ alignOffset = 0,
86
+ children,
87
+ ...props
88
+ }) {
89
+ return /* @__PURE__ */ jsx(Tooltip$1.Portal, { children: /* @__PURE__ */ jsx(
90
+ Tooltip$1.Positioner,
91
+ {
92
+ align,
93
+ alignOffset,
94
+ side,
95
+ sideOffset,
96
+ className: "isolate z-50",
97
+ children: /* @__PURE__ */ jsxs(
98
+ Tooltip$1.Popup,
99
+ {
100
+ "data-slot": "tooltip-content",
101
+ className: cn(
102
+ "z-50 inline-flex w-fit max-w-xs origin-(--transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pe-1.5 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-start-2 data-[side=inline-start]:slide-in-from-end-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
103
+ className
104
+ ),
105
+ ...props,
106
+ children: [
107
+ children,
108
+ /* @__PURE__ */ jsx(Tooltip$1.Arrow, { className: "z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground data-[side=bottom]:top-1 data-[side=inline-end]:top-1/2! data-[side=inline-end]:-start-1 data-[side=inline-end]:-translate-y-1/2 data-[side=inline-start]:top-1/2! data-[side=inline-start]:-end-1 data-[side=inline-start]:-translate-y-1/2 data-[side=left]:top-1/2! data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:top-1/2! data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5" })
109
+ ]
110
+ }
111
+ )
112
+ }
113
+ ) });
114
+ }
115
+ function CircleProgress({
116
+ percent,
117
+ size = 20,
118
+ strokeWidth = 2.5
119
+ }) {
120
+ const r = (size - strokeWidth) / 2;
121
+ const c = 2 * Math.PI * r;
122
+ const offset = c - percent / 100 * c;
123
+ return /* @__PURE__ */ jsxs("svg", { width: size, height: size, className: "shrink-0 -rotate-90", children: [
124
+ /* @__PURE__ */ jsx(
125
+ "circle",
126
+ {
127
+ cx: size / 2,
128
+ cy: size / 2,
129
+ r,
130
+ fill: "none",
131
+ stroke: "currentColor",
132
+ strokeWidth,
133
+ className: "text-muted-foreground/20"
134
+ }
135
+ ),
136
+ /* @__PURE__ */ jsx(
137
+ "circle",
138
+ {
139
+ cx: size / 2,
140
+ cy: size / 2,
141
+ r,
142
+ fill: "none",
143
+ stroke: "currentColor",
144
+ strokeWidth,
145
+ strokeDasharray: c,
146
+ strokeDashoffset: offset,
147
+ strokeLinecap: "round",
148
+ className: "text-primary transition-[stroke-dashoffset] duration-200"
149
+ }
150
+ )
151
+ ] });
152
+ }
153
+ function UploadStatus({
154
+ phase,
155
+ progress,
156
+ error,
157
+ fileInfo,
158
+ onCancel
159
+ }) {
160
+ if (phase === "idle") return null;
161
+ if (phase === "uploading" && fileInfo) {
162
+ return /* @__PURE__ */ jsxs("div", { className: "flex w-full items-center gap-1.5 text-xs", children: [
163
+ /* @__PURE__ */ jsx(CircleProgress, { percent: progress.percent, size: 14, strokeWidth: 2 }),
164
+ /* @__PURE__ */ jsx("span", { className: "max-w-32 min-w-16 truncate sm:max-w-48", children: fileInfo.name }),
165
+ /* @__PURE__ */ jsxs("span", { className: "shrink-0 text-muted-foreground", children: [
166
+ formatFileSize(progress.loaded),
167
+ " / ",
168
+ formatFileSize(fileInfo.size),
169
+ " (",
170
+ progress.percent,
171
+ "%)"
172
+ ] }),
173
+ /* @__PURE__ */ jsx(
174
+ Button,
175
+ {
176
+ variant: "ghost",
177
+ size: "icon",
178
+ className: "ml-auto size-6 shrink-0",
179
+ onClick: (e) => {
180
+ e.stopPropagation();
181
+ onCancel?.();
182
+ },
183
+ children: /* @__PURE__ */ jsx(XIcon, { className: "size-3.5" })
184
+ }
185
+ )
186
+ ] });
187
+ }
188
+ if (phase === "success" && fileInfo) {
189
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs", children: [
190
+ /* @__PURE__ */ jsx(CheckCircleIcon, { className: "size-3.5 shrink-0 text-green-600" }),
191
+ /* @__PURE__ */ jsx("span", { className: "[overflow-wrap:anywhere] max-w-32 min-w-16 truncate sm:max-w-48", children: fileInfo.name }),
192
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 text-muted-foreground", children: formatFileSize(fileInfo.size) })
193
+ ] });
194
+ }
195
+ if (phase === "error") {
196
+ return /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-start gap-1.5 text-xs", children: [
197
+ /* @__PURE__ */ jsx(AlertCircleIcon, { className: "mt-0.5 size-3.5 shrink-0 text-destructive" }),
198
+ /* @__PURE__ */ jsx("p", { className: "min-w-0 [overflow-wrap:anywhere] text-destructive", children: error ?? "Upload failed" })
199
+ ] });
200
+ }
201
+ if (phase === "validating" || phase === "presigning") {
202
+ return /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Preparing\u2026" });
203
+ }
204
+ return null;
205
+ }
206
+ function Progress({
207
+ className,
208
+ children,
209
+ value,
210
+ ...props
211
+ }) {
212
+ return /* @__PURE__ */ jsxs(
213
+ Progress$1.Root,
214
+ {
215
+ value,
216
+ "data-slot": "progress",
217
+ className: cn("flex flex-wrap gap-3", className),
218
+ ...props,
219
+ children: [
220
+ children,
221
+ /* @__PURE__ */ jsx(ProgressTrack, { children: /* @__PURE__ */ jsx(ProgressIndicator, {}) })
222
+ ]
223
+ }
224
+ );
225
+ }
226
+ function ProgressTrack({ className, ...props }) {
227
+ return /* @__PURE__ */ jsx(
228
+ Progress$1.Track,
229
+ {
230
+ className: cn(
231
+ "relative flex h-1 w-full items-center overflow-x-hidden rounded-md bg-muted",
232
+ className
233
+ ),
234
+ "data-slot": "progress-track",
235
+ ...props
236
+ }
237
+ );
238
+ }
239
+ function ProgressIndicator({
240
+ className,
241
+ ...props
242
+ }) {
243
+ return /* @__PURE__ */ jsx(
244
+ Progress$1.Indicator,
245
+ {
246
+ "data-slot": "progress-indicator",
247
+ className: cn("h-full bg-primary transition-all", className),
248
+ ...props
249
+ }
250
+ );
251
+ }
252
+ function ProgressLabel({ className, ...props }) {
253
+ return /* @__PURE__ */ jsx(
254
+ Progress$1.Label,
255
+ {
256
+ className: cn("text-xs/relaxed font-medium", className),
257
+ "data-slot": "progress-label",
258
+ ...props
259
+ }
260
+ );
261
+ }
262
+ function ProgressValue({ className, ...props }) {
263
+ return /* @__PURE__ */ jsx(
264
+ Progress$1.Value,
265
+ {
266
+ className: cn(
267
+ "ms-auto text-xs/relaxed text-muted-foreground tabular-nums",
268
+ className
269
+ ),
270
+ "data-slot": "progress-value",
271
+ ...props
272
+ }
273
+ );
274
+ }
275
+ function MultiUploadStatus({
276
+ phase,
277
+ files,
278
+ totalProgress,
279
+ error,
280
+ onCancel
281
+ }) {
282
+ if (phase === "idle") return null;
283
+ if (phase === "uploading") {
284
+ return /* @__PURE__ */ jsxs("div", { className: "flex w-full flex-col gap-2", children: [
285
+ /* @__PURE__ */ jsxs("div", { className: "flex w-full items-center gap-1.5", children: [
286
+ /* @__PURE__ */ jsxs(Progress, { value: totalProgress.percent, className: "flex-1", children: [
287
+ /* @__PURE__ */ jsxs(ProgressLabel, { children: [
288
+ files.filter((f) => f.status === "success").length,
289
+ "/",
290
+ files.length,
291
+ " files"
292
+ ] }),
293
+ /* @__PURE__ */ jsx(ProgressValue, {})
294
+ ] }),
295
+ /* @__PURE__ */ jsx(
296
+ Button,
297
+ {
298
+ variant: "ghost",
299
+ size: "icon",
300
+ className: "size-7 shrink-0",
301
+ onClick: (e) => {
302
+ e.stopPropagation();
303
+ onCancel?.();
304
+ },
305
+ children: /* @__PURE__ */ jsx(XIcon, { className: "size-4" })
306
+ }
307
+ )
308
+ ] }),
309
+ /* @__PURE__ */ jsx(FileList, { files })
310
+ ] });
311
+ }
312
+ if (phase === "success") {
313
+ return /* @__PURE__ */ jsxs("div", { className: "flex w-full flex-col gap-1", children: [
314
+ /* @__PURE__ */ jsxs("span", { className: "text-xs text-green-600", children: [
315
+ "All ",
316
+ files.length,
317
+ " file(s) uploaded"
318
+ ] }),
319
+ /* @__PURE__ */ jsx(FileList, { files })
320
+ ] });
321
+ }
322
+ if (phase === "error") {
323
+ return /* @__PURE__ */ jsxs("div", { className: "flex w-full flex-col gap-1", children: [
324
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-destructive", children: error ?? "Upload failed" }),
325
+ files.length > 0 && /* @__PURE__ */ jsx(FileList, { files })
326
+ ] });
327
+ }
328
+ if (phase === "validating") {
329
+ return /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Validating\u2026" });
330
+ }
331
+ return null;
332
+ }
333
+ function FileList({ files }) {
334
+ return /* @__PURE__ */ jsx("ul", { className: "flex flex-col gap-1", children: files.map((f) => /* @__PURE__ */ jsxs("li", { className: "flex flex-col gap-0.5 text-xs", children: [
335
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
336
+ f.status === "success" && /* @__PURE__ */ jsx(CheckCircleIcon, { className: "size-3.5 shrink-0 text-green-600" }),
337
+ f.status === "error" && /* @__PURE__ */ jsx(AlertCircleIcon, { className: "size-3.5 shrink-0 text-destructive" }),
338
+ (f.status === "pending" || f.status === "uploading") && /* @__PURE__ */ jsx(
339
+ CircleProgress,
340
+ {
341
+ percent: f.status === "uploading" ? f.progress.percent : 0,
342
+ size: 14,
343
+ strokeWidth: 2
344
+ }
345
+ ),
346
+ /* @__PURE__ */ jsx("span", { className: "[overflow-wrap:anywhere] max-w-32 min-w-16 truncate sm:max-w-48", children: f.fileName }),
347
+ f.status === "uploading" ? /* @__PURE__ */ jsxs("span", { className: "shrink-0 text-muted-foreground", children: [
348
+ formatFileSize(f.progress.loaded),
349
+ " /",
350
+ " ",
351
+ formatFileSize(f.fileSize),
352
+ " (",
353
+ f.progress.percent,
354
+ "%)"
355
+ ] }) : /* @__PURE__ */ jsx("span", { className: "shrink-0 text-muted-foreground", children: formatFileSize(f.fileSize) })
356
+ ] }),
357
+ f.status === "error" && f.error && /* @__PURE__ */ jsx("span", { className: "truncate [overflow-wrap:anywhere] pl-5 text-destructive", children: f.error })
358
+ ] }, f.id)) });
359
+ }
360
+
361
+ // ../../registry/registry/better-s3-ui/lib/truncate-msg.ts
362
+ function truncateMsg(msg, max = 100) {
363
+ return msg.length > max ? msg.slice(0, max) + "..." : msg;
364
+ }
365
+
366
+ // ../../registry/registry/better-s3-ui/hooks/use-upload-toast.ts
367
+ function useUploadToast(ctrl, enabled) {
368
+ const toastIdRef = useRef(null);
369
+ const isMulti = ctrl.mode === "multi";
370
+ const prevPhaseRef = useRef(ctrl.phase);
371
+ if (prevPhaseRef.current !== ctrl.phase) {
372
+ prevPhaseRef.current = ctrl.phase;
373
+ if (enabled) {
374
+ if (ctrl.phase === "idle" && toastIdRef.current) {
375
+ toast.dismiss(toastIdRef.current);
376
+ toastIdRef.current = null;
377
+ }
378
+ if (ctrl.phase === "success") {
379
+ if (toastIdRef.current) toast.dismiss(toastIdRef.current);
380
+ if (isMulti) {
381
+ toast.success(`${ctrl.files.length} file(s) uploaded`, {
382
+ description: formatFileSize(ctrl.totalProgress.total)
383
+ });
384
+ } else if (ctrl.fileInfo) {
385
+ toast.success("Upload complete", {
386
+ description: formatFileSize(ctrl.fileInfo.size)
387
+ });
388
+ }
389
+ toastIdRef.current = null;
390
+ }
391
+ if (ctrl.phase === "error") {
392
+ if (toastIdRef.current) toast.dismiss(toastIdRef.current);
393
+ if (isMulti && ctrl.files.length > 0) {
394
+ const succeeded = ctrl.files.filter(
395
+ (f) => f.status === "success"
396
+ ).length;
397
+ const failed = ctrl.files.filter((f) => f.status === "error").length;
398
+ toast.error("Upload finished with errors", {
399
+ description: `${succeeded} succeeded, ${failed} failed`
400
+ });
401
+ } else {
402
+ toast.error("Upload failed", {
403
+ description: truncateMsg(ctrl.error ?? "Unknown error")
404
+ });
405
+ }
406
+ toastIdRef.current = null;
407
+ }
408
+ }
409
+ }
410
+ useEffect(() => {
411
+ if (!enabled || ctrl.phase !== "uploading") return;
412
+ const id = toastIdRef.current ?? `upload-${Date.now()}`;
413
+ toastIdRef.current = id;
414
+ if (isMulti) {
415
+ const done = ctrl.files.filter((f) => f.status === "success").length;
416
+ toast.loading(`Uploading... ${done}/${ctrl.files.length}`, {
417
+ id,
418
+ description: `${formatFileSize(ctrl.totalProgress.loaded)} / ${formatFileSize(ctrl.totalProgress.total)} (${ctrl.totalProgress.percent}%)`,
419
+ cancel: { label: "Cancel", onClick: () => ctrl.cancel() }
420
+ });
421
+ } else if (ctrl.fileInfo) {
422
+ toast.loading("Uploading...", {
423
+ id,
424
+ description: `${formatFileSize(ctrl.progress.loaded)} / ${formatFileSize(ctrl.fileInfo.size)} (${ctrl.progress.percent}%)`,
425
+ cancel: { label: "Cancel", onClick: () => ctrl.cancel() }
426
+ });
427
+ }
428
+ }, [
429
+ enabled,
430
+ ctrl.phase,
431
+ isMulti,
432
+ ctrl.progress.loaded,
433
+ ctrl.progress.percent,
434
+ ctrl.totalProgress.loaded,
435
+ ctrl.totalProgress.total,
436
+ ctrl.totalProgress.percent,
437
+ ctrl.fileInfo,
438
+ ctrl.files,
439
+ ctrl.cancel
440
+ ]);
441
+ }
442
+ function UploadButton({
443
+ className,
444
+ label,
445
+ disabled,
446
+ tooltipText,
447
+ toast: enableToast = true,
448
+ showStatus = true,
449
+ ...options
450
+ }) {
451
+ const ctrl = useUploadControls(options);
452
+ const isMulti = ctrl.mode === "multi";
453
+ const isDisabled = disabled || ctrl.isUploading;
454
+ useUploadToast(ctrl, enableToast);
455
+ const status = showStatus ? isMulti ? /* @__PURE__ */ jsx(
456
+ MultiUploadStatus,
457
+ {
458
+ phase: ctrl.phase,
459
+ files: ctrl.files,
460
+ totalProgress: ctrl.totalProgress,
461
+ error: ctrl.error,
462
+ onCancel: ctrl.cancel
463
+ }
464
+ ) : /* @__PURE__ */ jsx(
465
+ UploadStatus,
466
+ {
467
+ phase: ctrl.phase,
468
+ progress: ctrl.progress,
469
+ error: ctrl.error,
470
+ fileInfo: ctrl.fileInfo,
471
+ onCancel: ctrl.cancel
472
+ }
473
+ ) : null;
474
+ return /* @__PURE__ */ jsxs("div", { className: cn("inline-flex flex-col gap-2", className), children: [
475
+ /* @__PURE__ */ jsxs("div", { className: "inline-flex items-center gap-2", children: [
476
+ /* @__PURE__ */ jsx("input", { ...ctrl.inputProps }),
477
+ /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(Tooltip, { children: [
478
+ /* @__PURE__ */ jsxs(
479
+ TooltipTrigger,
480
+ {
481
+ render: /* @__PURE__ */ jsx(
482
+ Button,
483
+ {
484
+ size: "default",
485
+ disabled: isDisabled,
486
+ onClick: ctrl.openFilePicker
487
+ }
488
+ ),
489
+ children: [
490
+ /* @__PURE__ */ jsx(UploadIcon, { "data-icon": "inline-start" }),
491
+ label ?? (isMulti ? "Upload files" : "Upload file")
492
+ ]
493
+ }
494
+ ),
495
+ /* @__PURE__ */ jsx(TooltipContent, { children: tooltipText ?? (isMulti ? "Upload files" : "Upload file") })
496
+ ] }) })
497
+ ] }),
498
+ status
499
+ ] });
500
+ }
501
+ function UploadDropzone({
502
+ className,
503
+ label,
504
+ disabled,
505
+ toast: enableToast = true,
506
+ showStatus = true,
507
+ ...options
508
+ }) {
509
+ const ctrl = useUploadControls(options);
510
+ const isMulti = ctrl.mode === "multi";
511
+ const isDisabled = disabled || ctrl.isUploading;
512
+ useUploadToast(ctrl, enableToast);
513
+ const status = showStatus ? isMulti ? /* @__PURE__ */ jsx(
514
+ MultiUploadStatus,
515
+ {
516
+ phase: ctrl.phase,
517
+ files: ctrl.files,
518
+ totalProgress: ctrl.totalProgress,
519
+ error: ctrl.error,
520
+ onCancel: ctrl.cancel
521
+ }
522
+ ) : /* @__PURE__ */ jsx(
523
+ UploadStatus,
524
+ {
525
+ phase: ctrl.phase,
526
+ progress: ctrl.progress,
527
+ error: ctrl.error,
528
+ fileInfo: ctrl.fileInfo,
529
+ onCancel: ctrl.cancel
530
+ }
531
+ ) : null;
532
+ return /* @__PURE__ */ jsxs(
533
+ "div",
534
+ {
535
+ className: cn(
536
+ "flex flex-col items-center justify-center gap-3 rounded-lg border-2 border-dashed p-6 text-center transition-colors",
537
+ isDisabled ? "cursor-not-allowed border-muted-foreground/25" : "cursor-pointer border-muted-foreground/25 hover:border-primary/50",
538
+ className
539
+ ),
540
+ onClick: isDisabled ? void 0 : ctrl.openFilePicker,
541
+ ...isDisabled ? {} : ctrl.dropHandlers,
542
+ children: [
543
+ /* @__PURE__ */ jsx("input", { ...ctrl.inputProps }),
544
+ /* @__PURE__ */ jsx(
545
+ UploadIcon,
546
+ {
547
+ className: cn(
548
+ "size-6 text-muted-foreground",
549
+ isDisabled && "opacity-50"
550
+ )
551
+ }
552
+ ),
553
+ /* @__PURE__ */ jsx(
554
+ "p",
555
+ {
556
+ className: cn(
557
+ "text-sm text-muted-foreground",
558
+ isDisabled && "opacity-50"
559
+ ),
560
+ children: label ?? (isMulti ? "Click or drag & drop files to upload" : "Click or drag & drop to upload")
561
+ }
562
+ ),
563
+ status && /* @__PURE__ */ jsx("div", { className: "w-full text-left", children: status })
564
+ ]
565
+ }
566
+ );
567
+ }
568
+ function useDownloadToast({
569
+ enabled = true,
570
+ objectKey,
571
+ fileName,
572
+ fileSize
573
+ }) {
574
+ const buildErrorDescription = (error) => truncateMsg(error instanceof Error ? error.message : "Unknown error");
575
+ const displayName = truncateMsg(
576
+ fileName ?? objectKey.split("/").pop() ?? objectKey,
577
+ 60
578
+ );
579
+ const onInitiated = () => {
580
+ if (enabled) toast.success("Download started");
581
+ };
582
+ const onSuccess = (_key, actualFileName) => {
583
+ if (!enabled) return;
584
+ toast.dismiss(`dl-${objectKey}`);
585
+ const safeFileName = truncateMsg(actualFileName, 60);
586
+ toast.success("Download complete", {
587
+ description: `${safeFileName}${fileSize != null ? ` \xB7 ${formatFileSize(fileSize)}` : ""}`
588
+ });
589
+ };
590
+ const onError = (_key, error) => {
591
+ if (!enabled) return;
592
+ toast.dismiss(`dl-${objectKey}`);
593
+ toast.error("Download failed", {
594
+ description: buildErrorDescription(error)
595
+ });
596
+ };
597
+ const onErrorWithPhase = (_key, error, _phase) => {
598
+ if (!enabled) return;
599
+ toast.dismiss(`dl-${objectKey}`);
600
+ toast.error("Download failed", {
601
+ description: buildErrorDescription(error)
602
+ });
603
+ };
604
+ const onCancel = (_key) => {
605
+ if (!enabled) return;
606
+ toast.dismiss(`dl-${objectKey}`);
607
+ toast.info("Download cancelled", { description: displayName });
608
+ };
609
+ return { onInitiated, onSuccess, onError, onErrorWithPhase, onCancel };
610
+ }
611
+ function DownloadButton({
612
+ api,
613
+ objectKey,
614
+ fileName,
615
+ label,
616
+ className,
617
+ disabled,
618
+ toast: enableToast = true,
619
+ showStatus = true
620
+ }) {
621
+ const toastHandlers = useDownloadToast({
622
+ enabled: enableToast,
623
+ objectKey,
624
+ fileName
625
+ });
626
+ const dl = useDownload({
627
+ api,
628
+ onInitiated: () => {
629
+ toastHandlers.onInitiated();
630
+ },
631
+ onError: (key, error) => {
632
+ toastHandlers.onError(key, error);
633
+ }
634
+ });
635
+ const isPending = dl.phase === "presigning";
636
+ return /* @__PURE__ */ jsxs("div", { className: cn("inline-flex flex-col gap-1.5", className), children: [
637
+ /* @__PURE__ */ jsx(
638
+ Button,
639
+ {
640
+ size: "default",
641
+ variant: "outline",
642
+ disabled: disabled || isPending,
643
+ onClick: () => dl.download(objectKey, fileName),
644
+ children: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1", children: [
645
+ isPending ? /* @__PURE__ */ jsx(LoaderIcon, { className: "animate-spin", "data-icon": "inline-start" }) : /* @__PURE__ */ jsx(DownloadIcon, { "data-icon": "inline-start" }),
646
+ label ?? "Download"
647
+ ] })
648
+ }
649
+ ),
650
+ showStatus && dl.phase === "error" && /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-start gap-1.5 text-xs", children: [
651
+ /* @__PURE__ */ jsx(AlertCircleIcon, { className: "mt-0.5 size-3.5 shrink-0 text-destructive" }),
652
+ /* @__PURE__ */ jsx("p", { className: "min-w-0 [overflow-wrap:anywhere] text-destructive", children: dl.error ?? "Download failed" })
653
+ ] })
654
+ ] });
655
+ }
656
+ function ProgressDownloadButton({
657
+ api,
658
+ objectKey,
659
+ fileName,
660
+ fileSize,
661
+ bucket,
662
+ label,
663
+ className,
664
+ fillClassName,
665
+ disabled,
666
+ tooltipText = "Download file",
667
+ toast: enableToast = true,
668
+ showStatus = true,
669
+ beforeDownload,
670
+ onDownloadStart,
671
+ onProgress,
672
+ onSuccess,
673
+ onError,
674
+ onCancel
675
+ }) {
676
+ const toastHandlers = useDownloadToast({
677
+ enabled: enableToast,
678
+ objectKey,
679
+ fileName,
680
+ fileSize
681
+ });
682
+ const dl = useFetchDownload({
683
+ api,
684
+ bucket,
685
+ beforeDownload,
686
+ onDownloadStart,
687
+ onProgress,
688
+ onSuccess: (key, actualFileName) => {
689
+ toastHandlers.onSuccess(key, actualFileName);
690
+ onSuccess?.(key, actualFileName);
691
+ },
692
+ onError: (key, error, phase) => {
693
+ toastHandlers.onErrorWithPhase(key, error, phase);
694
+ onError?.(key, error, phase);
695
+ },
696
+ onCancel: (key) => {
697
+ toastHandlers.onCancel(key);
698
+ onCancel?.(key);
699
+ }
700
+ });
701
+ const isDownloading = dl.phase === "downloading" || dl.phase === "presigning";
702
+ const handleClick = () => {
703
+ if (isDownloading) {
704
+ dl.cancel();
705
+ return;
706
+ }
707
+ dl.download(objectKey, fileName);
708
+ };
709
+ return /* @__PURE__ */ jsxs("div", { className: cn("inline-flex flex-col gap-1.5", className), children: [
710
+ /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(Tooltip, { children: [
711
+ /* @__PURE__ */ jsxs(
712
+ TooltipTrigger,
713
+ {
714
+ render: /* @__PURE__ */ jsx(
715
+ Button,
716
+ {
717
+ size: "default",
718
+ variant: "outline",
719
+ disabled,
720
+ className: cn("relative min-w-24 overflow-hidden"),
721
+ onClick: handleClick
722
+ }
723
+ ),
724
+ children: [
725
+ isDownloading && /* @__PURE__ */ jsx(
726
+ "span",
727
+ {
728
+ className: cn(
729
+ "absolute inset-0 bg-primary/15 transition-[width] duration-200",
730
+ fillClassName
731
+ ),
732
+ style: { width: `${dl.progress.percent}%` }
733
+ }
734
+ ),
735
+ /* @__PURE__ */ jsxs("span", { className: "relative z-10 inline-flex items-center gap-1", children: [
736
+ /* @__PURE__ */ jsx(DownloadIcon, { "data-icon": "inline-start" }),
737
+ isDownloading ? formatFileSize(dl.progress.loaded) : label ?? "Download"
738
+ ] })
739
+ ]
740
+ }
741
+ ),
742
+ /* @__PURE__ */ jsx(TooltipContent, { children: isDownloading ? "Cancel download" : tooltipText })
743
+ ] }) }),
744
+ showStatus && dl.phase === "error" && /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-start gap-1.5 text-xs", children: [
745
+ /* @__PURE__ */ jsx(AlertCircleIcon, { className: "mt-0.5 size-3.5 shrink-0 text-destructive" }),
746
+ /* @__PURE__ */ jsx("p", { className: "min-w-0 [overflow-wrap:anywhere] text-destructive", children: dl.error ?? "Download failed" })
747
+ ] })
748
+ ] });
749
+ }
750
+ function AlertDialog({ ...props }) {
751
+ return /* @__PURE__ */ jsx(AlertDialog$1.Root, { "data-slot": "alert-dialog", ...props });
752
+ }
753
+ function AlertDialogTrigger({ ...props }) {
754
+ return /* @__PURE__ */ jsx(AlertDialog$1.Trigger, { "data-slot": "alert-dialog-trigger", ...props });
755
+ }
756
+ function AlertDialogPortal({ ...props }) {
757
+ return /* @__PURE__ */ jsx(AlertDialog$1.Portal, { "data-slot": "alert-dialog-portal", ...props });
758
+ }
759
+ function AlertDialogOverlay({
760
+ className,
761
+ ...props
762
+ }) {
763
+ return /* @__PURE__ */ jsx(
764
+ AlertDialog$1.Backdrop,
765
+ {
766
+ "data-slot": "alert-dialog-overlay",
767
+ className: cn(
768
+ "fixed inset-0 isolate z-50 bg-black/80 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
769
+ className
770
+ ),
771
+ ...props
772
+ }
773
+ );
774
+ }
775
+ function AlertDialogContent({
776
+ className,
777
+ size = "default",
778
+ ...props
779
+ }) {
780
+ return /* @__PURE__ */ jsxs(AlertDialogPortal, { children: [
781
+ /* @__PURE__ */ jsx(AlertDialogOverlay, {}),
782
+ /* @__PURE__ */ jsx(
783
+ AlertDialog$1.Popup,
784
+ {
785
+ "data-slot": "alert-dialog-content",
786
+ "data-size": size,
787
+ className: cn(
788
+ "group/alert-dialog-content fixed top-1/2 start-1/2 z-50 grid w-full -translate-x-1/2 rtl:translate-x-1/2 -translate-y-1/2 gap-3 rounded-xl bg-popover p-4 text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none data-[size=default]:max-w-xs data-[size=sm]:max-w-64 data-[size=default]:sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
789
+ className
790
+ ),
791
+ ...props
792
+ }
793
+ )
794
+ ] });
795
+ }
796
+ function AlertDialogHeader({
797
+ className,
798
+ ...props
799
+ }) {
800
+ return /* @__PURE__ */ jsx(
801
+ "div",
802
+ {
803
+ "data-slot": "alert-dialog-header",
804
+ className: cn(
805
+ "grid grid-rows-[auto_1fr] place-items-center gap-1 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-4 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-start sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]",
806
+ className
807
+ ),
808
+ ...props
809
+ }
810
+ );
811
+ }
812
+ function AlertDialogFooter({
813
+ className,
814
+ ...props
815
+ }) {
816
+ return /* @__PURE__ */ jsx(
817
+ "div",
818
+ {
819
+ "data-slot": "alert-dialog-footer",
820
+ className: cn(
821
+ "flex flex-col-reverse gap-2 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end",
822
+ className
823
+ ),
824
+ ...props
825
+ }
826
+ );
827
+ }
828
+ function AlertDialogMedia({
829
+ className,
830
+ ...props
831
+ }) {
832
+ return /* @__PURE__ */ jsx(
833
+ "div",
834
+ {
835
+ "data-slot": "alert-dialog-media",
836
+ className: cn(
837
+ "mb-2 inline-flex size-8 items-center justify-center rounded-md bg-muted sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-4",
838
+ className
839
+ ),
840
+ ...props
841
+ }
842
+ );
843
+ }
844
+ function AlertDialogTitle({
845
+ className,
846
+ ...props
847
+ }) {
848
+ return /* @__PURE__ */ jsx(
849
+ AlertDialog$1.Title,
850
+ {
851
+ "data-slot": "alert-dialog-title",
852
+ className: cn(
853
+ "font-heading text-sm font-medium sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2",
854
+ className
855
+ ),
856
+ ...props
857
+ }
858
+ );
859
+ }
860
+ function AlertDialogDescription({
861
+ className,
862
+ ...props
863
+ }) {
864
+ return /* @__PURE__ */ jsx(
865
+ AlertDialog$1.Description,
866
+ {
867
+ "data-slot": "alert-dialog-description",
868
+ className: cn(
869
+ "text-xs/relaxed text-balance text-muted-foreground md:text-pretty *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
870
+ className
871
+ ),
872
+ ...props
873
+ }
874
+ );
875
+ }
876
+ function AlertDialogAction({
877
+ className,
878
+ ...props
879
+ }) {
880
+ return /* @__PURE__ */ jsx(
881
+ Button,
882
+ {
883
+ "data-slot": "alert-dialog-action",
884
+ className: cn(className),
885
+ ...props
886
+ }
887
+ );
888
+ }
889
+ function AlertDialogCancel({
890
+ className,
891
+ variant = "outline",
892
+ size = "default",
893
+ ...props
894
+ }) {
895
+ return /* @__PURE__ */ jsx(
896
+ AlertDialog$1.Close,
897
+ {
898
+ "data-slot": "alert-dialog-cancel",
899
+ className: cn(className),
900
+ render: /* @__PURE__ */ jsx(Button, { variant, size }),
901
+ ...props
902
+ }
903
+ );
904
+ }
905
+ function useDeleteToast({ enabled = true, displayName }) {
906
+ const safeDisplayName = truncateMsg(displayName, 60);
907
+ const onSuccess = (_key) => {
908
+ if (!enabled) return;
909
+ toast.success("File deleted", { description: safeDisplayName });
910
+ };
911
+ const onError = (_key, error) => {
912
+ if (!enabled) return;
913
+ toast.error("Delete failed", {
914
+ description: truncateMsg(
915
+ error instanceof Error ? error.message : "Unknown error"
916
+ )
917
+ });
918
+ };
919
+ return { onSuccess, onError };
920
+ }
921
+ function DeleteButton({
922
+ api,
923
+ objectKey,
924
+ fileName,
925
+ fileSize,
926
+ bucket,
927
+ label,
928
+ className,
929
+ disabled,
930
+ tooltipText = "Delete file",
931
+ toast: enableToast = true,
932
+ showStatus = true,
933
+ confirmTitle = "Delete file?",
934
+ confirmDescription,
935
+ beforeDelete,
936
+ onDeleteStart,
937
+ onSuccess,
938
+ onError
939
+ }) {
940
+ const displayName = fileName ?? objectKey.split("/").pop() ?? objectKey;
941
+ const toastHandlers = useDeleteToast({
942
+ enabled: enableToast,
943
+ displayName
944
+ });
945
+ const del = useDelete({
946
+ api,
947
+ bucket,
948
+ beforeDelete,
949
+ onDeleteStart,
950
+ onSuccess: (key) => {
951
+ toastHandlers.onSuccess(key);
952
+ onSuccess?.(key);
953
+ },
954
+ onError: (key, error, phase) => {
955
+ toastHandlers.onError(key, error);
956
+ onError?.(key, error, phase);
957
+ }
958
+ });
959
+ const isDeleting = del.phase === "deleting";
960
+ const isDisabled = disabled || isDeleting;
961
+ const description = confirmDescription ?? `Are you sure you want to delete "${displayName}"${fileSize != null ? ` (${formatFileSize(fileSize)})` : ""}? This action cannot be undone.`;
962
+ return /* @__PURE__ */ jsxs("div", { className: cn("inline-flex flex-col gap-1.5", className), children: [
963
+ /* @__PURE__ */ jsx("div", { className: "inline-flex items-center gap-2", children: /* @__PURE__ */ jsxs(
964
+ AlertDialog,
965
+ {
966
+ open: del.phase === "confirming",
967
+ onOpenChange: (open) => {
968
+ if (!open) del.cancelDelete();
969
+ },
970
+ children: [
971
+ /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(Tooltip, { children: [
972
+ /* @__PURE__ */ jsxs(
973
+ TooltipTrigger,
974
+ {
975
+ render: /* @__PURE__ */ jsx(
976
+ AlertDialogTrigger,
977
+ {
978
+ disabled: isDisabled,
979
+ onClick: () => del.requestDelete(objectKey),
980
+ render: /* @__PURE__ */ jsx(
981
+ Button,
982
+ {
983
+ size: "default",
984
+ variant: "destructive",
985
+ disabled: isDisabled
986
+ }
987
+ )
988
+ }
989
+ ),
990
+ children: [
991
+ isDeleting ? /* @__PURE__ */ jsx(
992
+ LoaderIcon,
993
+ {
994
+ className: "animate-spin",
995
+ "data-icon": "inline-start"
996
+ }
997
+ ) : /* @__PURE__ */ jsx(Trash2Icon, { "data-icon": "inline-start" }),
998
+ label ?? "Delete"
999
+ ]
1000
+ }
1001
+ ),
1002
+ /* @__PURE__ */ jsx(TooltipContent, { children: tooltipText })
1003
+ ] }) }),
1004
+ /* @__PURE__ */ jsxs(AlertDialogContent, { children: [
1005
+ /* @__PURE__ */ jsxs(AlertDialogHeader, { children: [
1006
+ /* @__PURE__ */ jsx(AlertDialogMedia, { children: /* @__PURE__ */ jsx(Trash2Icon, {}) }),
1007
+ /* @__PURE__ */ jsx(AlertDialogTitle, { children: confirmTitle }),
1008
+ /* @__PURE__ */ jsx(AlertDialogDescription, { className: "[overflow-wrap:anywhere]", children: description })
1009
+ ] }),
1010
+ /* @__PURE__ */ jsxs(AlertDialogFooter, { children: [
1011
+ /* @__PURE__ */ jsx(AlertDialogCancel, { children: "Cancel" }),
1012
+ /* @__PURE__ */ jsx(
1013
+ AlertDialogAction,
1014
+ {
1015
+ variant: "destructive",
1016
+ onClick: () => del.confirmDelete(),
1017
+ children: "Delete"
1018
+ }
1019
+ )
1020
+ ] })
1021
+ ] })
1022
+ ]
1023
+ }
1024
+ ) }),
1025
+ showStatus && del.phase === "success" && /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center gap-1.5 text-xs", children: [
1026
+ /* @__PURE__ */ jsx(CheckCircle2Icon, { className: "size-3.5 shrink-0 text-green-600" }),
1027
+ /* @__PURE__ */ jsxs("p", { className: "min-w-0 [overflow-wrap:anywhere] text-green-600", children: [
1028
+ "\u201C",
1029
+ /* @__PURE__ */ jsx("span", { className: "inline-block max-w-[14ch] truncate align-bottom", children: displayName }),
1030
+ "\u201D deleted"
1031
+ ] })
1032
+ ] }),
1033
+ showStatus && del.phase === "error" && /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-start gap-1.5 text-xs", children: [
1034
+ /* @__PURE__ */ jsx(AlertCircleIcon, { className: "mt-0.5 size-3.5 shrink-0 text-destructive" }),
1035
+ /* @__PURE__ */ jsx("p", { className: "min-w-0 [overflow-wrap:anywhere] text-destructive", children: del.error ?? "Delete failed" })
1036
+ ] })
1037
+ ] });
1038
+ }
1039
+
1040
+ export { DeleteButton, DownloadButton, MultiUploadStatus, ProgressDownloadButton, UploadButton, UploadDropzone, UploadStatus };
1041
+ //# sourceMappingURL=index.js.map
2
1042
  //# sourceMappingURL=index.js.map