@better-s3/ui 3.1045.0 → 3.1045.2

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,1043 @@
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, Fragment } 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
+ function useUploadToast(ctrl, enabled) {
361
+ const toastIdRef = useRef(null);
362
+ const isMulti = ctrl.mode === "multi";
363
+ const prevPhaseRef = useRef(ctrl.phase);
364
+ if (prevPhaseRef.current !== ctrl.phase) {
365
+ prevPhaseRef.current = ctrl.phase;
366
+ if (enabled) {
367
+ if (ctrl.phase === "idle" && toastIdRef.current) {
368
+ toast.dismiss(toastIdRef.current);
369
+ toastIdRef.current = null;
370
+ }
371
+ if (ctrl.phase === "success") {
372
+ if (toastIdRef.current) toast.dismiss(toastIdRef.current);
373
+ if (isMulti) {
374
+ toast.success(`${ctrl.files.length} file(s) uploaded`, {
375
+ description: formatFileSize(ctrl.totalProgress.total)
376
+ });
377
+ } else if (ctrl.fileInfo) {
378
+ toast.success("Upload complete", {
379
+ description: formatFileSize(ctrl.fileInfo.size)
380
+ });
381
+ }
382
+ toastIdRef.current = null;
383
+ }
384
+ if (ctrl.phase === "error") {
385
+ if (toastIdRef.current) toast.dismiss(toastIdRef.current);
386
+ if (isMulti && ctrl.files.length > 0) {
387
+ const succeeded = ctrl.files.filter(
388
+ (f) => f.status === "success"
389
+ ).length;
390
+ const failed = ctrl.files.filter((f) => f.status === "error").length;
391
+ toast.error("Upload finished with errors", {
392
+ description: `${succeeded} succeeded, ${failed} failed`
393
+ });
394
+ } else {
395
+ toast.error("Upload failed", {
396
+ description: /* @__PURE__ */ jsx("span", { className: "block [overflow-wrap:anywhere]", children: ctrl.error ?? "Unknown error" })
397
+ });
398
+ }
399
+ toastIdRef.current = null;
400
+ }
401
+ }
402
+ }
403
+ useEffect(() => {
404
+ if (!enabled || ctrl.phase !== "uploading") return;
405
+ const id = toastIdRef.current ?? `upload-${Date.now()}`;
406
+ toastIdRef.current = id;
407
+ if (isMulti) {
408
+ const done = ctrl.files.filter((f) => f.status === "success").length;
409
+ toast.loading(`Uploading... ${done}/${ctrl.files.length}`, {
410
+ id,
411
+ description: `${formatFileSize(ctrl.totalProgress.loaded)} / ${formatFileSize(ctrl.totalProgress.total)} (${ctrl.totalProgress.percent}%)`,
412
+ cancel: { label: "Cancel", onClick: () => ctrl.cancel() }
413
+ });
414
+ } else if (ctrl.fileInfo) {
415
+ toast.loading("Uploading...", {
416
+ id,
417
+ description: `${formatFileSize(ctrl.progress.loaded)} / ${formatFileSize(ctrl.fileInfo.size)} (${ctrl.progress.percent}%)`,
418
+ cancel: { label: "Cancel", onClick: () => ctrl.cancel() }
419
+ });
420
+ }
421
+ }, [
422
+ enabled,
423
+ ctrl.phase,
424
+ isMulti,
425
+ ctrl.progress.loaded,
426
+ ctrl.progress.percent,
427
+ ctrl.totalProgress.loaded,
428
+ ctrl.totalProgress.total,
429
+ ctrl.totalProgress.percent,
430
+ ctrl.fileInfo,
431
+ ctrl.files,
432
+ ctrl.cancel
433
+ ]);
434
+ }
435
+ function UploadButton({
436
+ className,
437
+ label,
438
+ disabled,
439
+ tooltipText,
440
+ toast: enableToast = true,
441
+ showStatus = true,
442
+ ...options
443
+ }) {
444
+ const ctrl = useUploadControls(options);
445
+ const isMulti = ctrl.mode === "multi";
446
+ const isDisabled = disabled || ctrl.isUploading;
447
+ useUploadToast(ctrl, enableToast);
448
+ const status = showStatus ? isMulti ? /* @__PURE__ */ jsx(
449
+ MultiUploadStatus,
450
+ {
451
+ phase: ctrl.phase,
452
+ files: ctrl.files,
453
+ totalProgress: ctrl.totalProgress,
454
+ error: ctrl.error,
455
+ onCancel: ctrl.cancel
456
+ }
457
+ ) : /* @__PURE__ */ jsx(
458
+ UploadStatus,
459
+ {
460
+ phase: ctrl.phase,
461
+ progress: ctrl.progress,
462
+ error: ctrl.error,
463
+ fileInfo: ctrl.fileInfo,
464
+ onCancel: ctrl.cancel
465
+ }
466
+ ) : null;
467
+ return /* @__PURE__ */ jsxs("div", { className: cn("inline-flex flex-col gap-2", className), children: [
468
+ /* @__PURE__ */ jsxs("div", { className: "inline-flex items-center gap-2", children: [
469
+ /* @__PURE__ */ jsx("input", { ...ctrl.inputProps }),
470
+ /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(Tooltip, { children: [
471
+ /* @__PURE__ */ jsxs(
472
+ TooltipTrigger,
473
+ {
474
+ render: /* @__PURE__ */ jsx(
475
+ Button,
476
+ {
477
+ size: "default",
478
+ disabled: isDisabled,
479
+ onClick: ctrl.openFilePicker
480
+ }
481
+ ),
482
+ children: [
483
+ /* @__PURE__ */ jsx(UploadIcon, { "data-icon": "inline-start" }),
484
+ label ?? (isMulti ? "Upload files" : "Upload file")
485
+ ]
486
+ }
487
+ ),
488
+ /* @__PURE__ */ jsx(TooltipContent, { children: tooltipText ?? (isMulti ? "Upload files" : "Upload file") })
489
+ ] }) })
490
+ ] }),
491
+ status
492
+ ] });
493
+ }
494
+ function UploadDropzone({
495
+ className,
496
+ label,
497
+ disabled,
498
+ toast: enableToast = true,
499
+ showStatus = true,
500
+ ...options
501
+ }) {
502
+ const ctrl = useUploadControls(options);
503
+ const isMulti = ctrl.mode === "multi";
504
+ const isDisabled = disabled || ctrl.isUploading;
505
+ useUploadToast(ctrl, enableToast);
506
+ const status = showStatus ? isMulti ? /* @__PURE__ */ jsx(
507
+ MultiUploadStatus,
508
+ {
509
+ phase: ctrl.phase,
510
+ files: ctrl.files,
511
+ totalProgress: ctrl.totalProgress,
512
+ error: ctrl.error,
513
+ onCancel: ctrl.cancel
514
+ }
515
+ ) : /* @__PURE__ */ jsx(
516
+ UploadStatus,
517
+ {
518
+ phase: ctrl.phase,
519
+ progress: ctrl.progress,
520
+ error: ctrl.error,
521
+ fileInfo: ctrl.fileInfo,
522
+ onCancel: ctrl.cancel
523
+ }
524
+ ) : null;
525
+ return /* @__PURE__ */ jsxs(
526
+ "div",
527
+ {
528
+ className: cn(
529
+ "flex flex-col items-center justify-center gap-3 rounded-lg border-2 border-dashed p-6 text-center transition-colors",
530
+ isDisabled ? "cursor-not-allowed border-muted-foreground/25" : "cursor-pointer border-muted-foreground/25 hover:border-primary/50",
531
+ className
532
+ ),
533
+ onClick: isDisabled ? void 0 : ctrl.openFilePicker,
534
+ ...isDisabled ? {} : ctrl.dropHandlers,
535
+ children: [
536
+ /* @__PURE__ */ jsx("input", { ...ctrl.inputProps }),
537
+ /* @__PURE__ */ jsx(
538
+ UploadIcon,
539
+ {
540
+ className: cn(
541
+ "size-6 text-muted-foreground",
542
+ isDisabled && "opacity-50"
543
+ )
544
+ }
545
+ ),
546
+ /* @__PURE__ */ jsx(
547
+ "p",
548
+ {
549
+ className: cn(
550
+ "text-sm text-muted-foreground",
551
+ isDisabled && "opacity-50"
552
+ ),
553
+ children: label ?? (isMulti ? "Click or drag & drop files to upload" : "Click or drag & drop to upload")
554
+ }
555
+ ),
556
+ status && /* @__PURE__ */ jsx("div", { className: "w-full text-left", children: status })
557
+ ]
558
+ }
559
+ );
560
+ }
561
+ function useDownloadToast({
562
+ enabled = true,
563
+ objectKey,
564
+ fileName,
565
+ fileSize
566
+ }) {
567
+ const displayName = fileName ?? objectKey.split("/").pop() ?? objectKey;
568
+ const errorNode = (error) => /* @__PURE__ */ jsx("span", { className: "block [overflow-wrap:anywhere]", children: error instanceof Error ? error.message : "Unknown error" });
569
+ const onInitiated = () => {
570
+ if (enabled) toast.success("Download started");
571
+ };
572
+ const onSuccess = (_key, actualFileName) => {
573
+ if (!enabled) return;
574
+ toast.dismiss(`dl-${objectKey}`);
575
+ toast.success("Download complete", {
576
+ description: /* @__PURE__ */ jsxs("span", { className: "block max-w-[50ch] truncate", children: [
577
+ actualFileName,
578
+ fileSize != null ? ` \xB7 ${formatFileSize(fileSize)}` : ""
579
+ ] })
580
+ });
581
+ };
582
+ const onError = (_key, error) => {
583
+ if (!enabled) return;
584
+ toast.dismiss(`dl-${objectKey}`);
585
+ toast.error("Download failed", { description: errorNode(error) });
586
+ };
587
+ const onErrorWithPhase = (_key, error, _phase) => {
588
+ if (!enabled) return;
589
+ toast.dismiss(`dl-${objectKey}`);
590
+ toast.error("Download failed", { description: errorNode(error) });
591
+ };
592
+ const onCancel = (_key) => {
593
+ if (!enabled) return;
594
+ toast.dismiss(`dl-${objectKey}`);
595
+ toast.info("Download cancelled", {
596
+ description: /* @__PURE__ */ jsx("span", { className: "block max-w-[50ch] truncate", children: displayName })
597
+ });
598
+ };
599
+ return { onInitiated, onSuccess, onError, onErrorWithPhase, onCancel };
600
+ }
601
+ function DownloadButton({
602
+ api,
603
+ objectKey,
604
+ fileName,
605
+ label,
606
+ className,
607
+ disabled,
608
+ toast: enableToast = true,
609
+ showStatus = true
610
+ }) {
611
+ const toastHandlers = useDownloadToast({
612
+ enabled: enableToast,
613
+ objectKey,
614
+ fileName
615
+ });
616
+ const dl = useDownload({
617
+ api,
618
+ onInitiated: () => {
619
+ toastHandlers.onInitiated();
620
+ },
621
+ onError: (key, error) => {
622
+ toastHandlers.onError(key, error);
623
+ }
624
+ });
625
+ const isPending = dl.phase === "presigning";
626
+ return /* @__PURE__ */ jsxs("div", { className: cn("inline-flex flex-col gap-1.5", className), children: [
627
+ /* @__PURE__ */ jsx(
628
+ Button,
629
+ {
630
+ size: "default",
631
+ variant: "outline",
632
+ disabled: disabled || isPending,
633
+ onClick: () => dl.download(objectKey, fileName),
634
+ children: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1", children: [
635
+ isPending ? /* @__PURE__ */ jsx(LoaderIcon, { className: "animate-spin", "data-icon": "inline-start" }) : /* @__PURE__ */ jsx(DownloadIcon, { "data-icon": "inline-start" }),
636
+ label ?? "Download"
637
+ ] })
638
+ }
639
+ ),
640
+ showStatus && dl.phase === "error" && /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-start gap-1.5 text-xs", children: [
641
+ /* @__PURE__ */ jsx(AlertCircleIcon, { className: "mt-0.5 size-3.5 shrink-0 text-destructive" }),
642
+ /* @__PURE__ */ jsx("p", { className: "min-w-0 [overflow-wrap:anywhere] text-destructive", children: dl.error ?? "Download failed" })
643
+ ] })
644
+ ] });
645
+ }
646
+ function ProgressDownloadButton({
647
+ api,
648
+ objectKey,
649
+ fileName,
650
+ fileSize,
651
+ bucket,
652
+ label,
653
+ className,
654
+ fillClassName,
655
+ disabled,
656
+ tooltipText = "Download file",
657
+ toast: enableToast = true,
658
+ showStatus = true,
659
+ beforeDownload,
660
+ onDownloadStart,
661
+ onProgress,
662
+ onSuccess,
663
+ onError,
664
+ onCancel
665
+ }) {
666
+ const toastHandlers = useDownloadToast({
667
+ enabled: enableToast,
668
+ objectKey,
669
+ fileName,
670
+ fileSize
671
+ });
672
+ const dl = useFetchDownload({
673
+ api,
674
+ bucket,
675
+ beforeDownload,
676
+ onDownloadStart,
677
+ onProgress,
678
+ onSuccess: (key, actualFileName) => {
679
+ toastHandlers.onSuccess(key, actualFileName);
680
+ onSuccess?.(key, actualFileName);
681
+ },
682
+ onError: (key, error, phase) => {
683
+ toastHandlers.onErrorWithPhase(key, error, phase);
684
+ onError?.(key, error, phase);
685
+ },
686
+ onCancel: (key) => {
687
+ toastHandlers.onCancel(key);
688
+ onCancel?.(key);
689
+ }
690
+ });
691
+ const isDownloading = dl.phase === "downloading" || dl.phase === "presigning";
692
+ const handleClick = () => {
693
+ if (isDownloading) {
694
+ dl.cancel();
695
+ return;
696
+ }
697
+ dl.download(objectKey, fileName);
698
+ };
699
+ return /* @__PURE__ */ jsxs("div", { className: cn("inline-flex flex-col gap-1.5", className), children: [
700
+ /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(Tooltip, { children: [
701
+ /* @__PURE__ */ jsxs(
702
+ TooltipTrigger,
703
+ {
704
+ render: /* @__PURE__ */ jsx(
705
+ Button,
706
+ {
707
+ size: "default",
708
+ variant: "outline",
709
+ disabled,
710
+ className: cn("relative min-w-24 overflow-hidden"),
711
+ onClick: handleClick
712
+ }
713
+ ),
714
+ children: [
715
+ isDownloading && /* @__PURE__ */ jsx(
716
+ "span",
717
+ {
718
+ className: cn(
719
+ "absolute inset-0 bg-primary/15 transition-[width] duration-200",
720
+ fillClassName
721
+ ),
722
+ style: { width: `${dl.progress.percent}%` }
723
+ }
724
+ ),
725
+ /* @__PURE__ */ jsxs("span", { className: "relative z-10 inline-flex items-center gap-1", children: [
726
+ /* @__PURE__ */ jsx(DownloadIcon, { "data-icon": "inline-start" }),
727
+ isDownloading ? formatFileSize(dl.progress.loaded) : label ?? "Download"
728
+ ] })
729
+ ]
730
+ }
731
+ ),
732
+ /* @__PURE__ */ jsx(TooltipContent, { children: isDownloading ? "Cancel download" : tooltipText })
733
+ ] }) }),
734
+ showStatus && dl.phase === "error" && /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-start gap-1.5 text-xs", children: [
735
+ /* @__PURE__ */ jsx(AlertCircleIcon, { className: "mt-0.5 size-3.5 shrink-0 text-destructive" }),
736
+ /* @__PURE__ */ jsx("p", { className: "min-w-0 [overflow-wrap:anywhere] text-destructive", children: dl.error ?? "Download failed" })
737
+ ] })
738
+ ] });
739
+ }
740
+ function AlertDialog({ ...props }) {
741
+ return /* @__PURE__ */ jsx(AlertDialog$1.Root, { "data-slot": "alert-dialog", ...props });
742
+ }
743
+ function AlertDialogTrigger({ ...props }) {
744
+ return /* @__PURE__ */ jsx(AlertDialog$1.Trigger, { "data-slot": "alert-dialog-trigger", ...props });
745
+ }
746
+ function AlertDialogPortal({ ...props }) {
747
+ return /* @__PURE__ */ jsx(AlertDialog$1.Portal, { "data-slot": "alert-dialog-portal", ...props });
748
+ }
749
+ function AlertDialogOverlay({
750
+ className,
751
+ ...props
752
+ }) {
753
+ return /* @__PURE__ */ jsx(
754
+ AlertDialog$1.Backdrop,
755
+ {
756
+ "data-slot": "alert-dialog-overlay",
757
+ className: cn(
758
+ "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",
759
+ className
760
+ ),
761
+ ...props
762
+ }
763
+ );
764
+ }
765
+ function AlertDialogContent({
766
+ className,
767
+ size = "default",
768
+ ...props
769
+ }) {
770
+ return /* @__PURE__ */ jsxs(AlertDialogPortal, { children: [
771
+ /* @__PURE__ */ jsx(AlertDialogOverlay, {}),
772
+ /* @__PURE__ */ jsx(
773
+ AlertDialog$1.Popup,
774
+ {
775
+ "data-slot": "alert-dialog-content",
776
+ "data-size": size,
777
+ className: cn(
778
+ "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",
779
+ className
780
+ ),
781
+ ...props
782
+ }
783
+ )
784
+ ] });
785
+ }
786
+ function AlertDialogHeader({
787
+ className,
788
+ ...props
789
+ }) {
790
+ return /* @__PURE__ */ jsx(
791
+ "div",
792
+ {
793
+ "data-slot": "alert-dialog-header",
794
+ className: cn(
795
+ "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]",
796
+ className
797
+ ),
798
+ ...props
799
+ }
800
+ );
801
+ }
802
+ function AlertDialogFooter({
803
+ className,
804
+ ...props
805
+ }) {
806
+ return /* @__PURE__ */ jsx(
807
+ "div",
808
+ {
809
+ "data-slot": "alert-dialog-footer",
810
+ className: cn(
811
+ "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",
812
+ className
813
+ ),
814
+ ...props
815
+ }
816
+ );
817
+ }
818
+ function AlertDialogMedia({
819
+ className,
820
+ ...props
821
+ }) {
822
+ return /* @__PURE__ */ jsx(
823
+ "div",
824
+ {
825
+ "data-slot": "alert-dialog-media",
826
+ className: cn(
827
+ "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",
828
+ className
829
+ ),
830
+ ...props
831
+ }
832
+ );
833
+ }
834
+ function AlertDialogTitle({
835
+ className,
836
+ ...props
837
+ }) {
838
+ return /* @__PURE__ */ jsx(
839
+ AlertDialog$1.Title,
840
+ {
841
+ "data-slot": "alert-dialog-title",
842
+ className: cn(
843
+ "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",
844
+ className
845
+ ),
846
+ ...props
847
+ }
848
+ );
849
+ }
850
+ function AlertDialogDescription({
851
+ className,
852
+ ...props
853
+ }) {
854
+ return /* @__PURE__ */ jsx(
855
+ AlertDialog$1.Description,
856
+ {
857
+ "data-slot": "alert-dialog-description",
858
+ className: cn(
859
+ "text-xs/relaxed text-balance text-muted-foreground md:text-pretty *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
860
+ className
861
+ ),
862
+ ...props
863
+ }
864
+ );
865
+ }
866
+ function AlertDialogAction({
867
+ className,
868
+ ...props
869
+ }) {
870
+ return /* @__PURE__ */ jsx(
871
+ Button,
872
+ {
873
+ "data-slot": "alert-dialog-action",
874
+ className: cn(className),
875
+ ...props
876
+ }
877
+ );
878
+ }
879
+ function AlertDialogCancel({
880
+ className,
881
+ variant = "outline",
882
+ size = "default",
883
+ ...props
884
+ }) {
885
+ return /* @__PURE__ */ jsx(
886
+ AlertDialog$1.Close,
887
+ {
888
+ "data-slot": "alert-dialog-cancel",
889
+ className: cn(className),
890
+ render: /* @__PURE__ */ jsx(Button, { variant, size }),
891
+ ...props
892
+ }
893
+ );
894
+ }
895
+ function useDeleteToast({
896
+ enabled = true,
897
+ displayName
898
+ }) {
899
+ const onSuccess = (_key) => {
900
+ if (!enabled) return;
901
+ toast.success("File deleted", {
902
+ description: /* @__PURE__ */ jsx("span", { className: "block max-w-[50ch] truncate", children: displayName })
903
+ });
904
+ };
905
+ const onError = (_key, error) => {
906
+ if (!enabled) return;
907
+ toast.error("Delete failed", {
908
+ description: /* @__PURE__ */ jsx("span", { className: "block [overflow-wrap:anywhere]", children: error instanceof Error ? error.message : "Unknown error" })
909
+ });
910
+ };
911
+ return { onSuccess, onError };
912
+ }
913
+ function DeleteButton({
914
+ api,
915
+ objectKey,
916
+ fileName,
917
+ fileSize,
918
+ bucket,
919
+ label,
920
+ className,
921
+ disabled,
922
+ tooltipText = "Delete file",
923
+ toast: enableToast = true,
924
+ showStatus = true,
925
+ confirmTitle = "Delete file?",
926
+ confirmDescription,
927
+ beforeDelete,
928
+ onDeleteStart,
929
+ onSuccess,
930
+ onError
931
+ }) {
932
+ const displayName = fileName ?? objectKey.split("/").pop() ?? objectKey;
933
+ const toastHandlers = useDeleteToast({
934
+ enabled: enableToast,
935
+ displayName
936
+ });
937
+ const del = useDelete({
938
+ api,
939
+ bucket,
940
+ beforeDelete,
941
+ onDeleteStart,
942
+ onSuccess: (key) => {
943
+ toastHandlers.onSuccess(key);
944
+ onSuccess?.(key);
945
+ },
946
+ onError: (key, error, phase) => {
947
+ toastHandlers.onError(key, error);
948
+ onError?.(key, error, phase);
949
+ }
950
+ });
951
+ const isDeleting = del.phase === "deleting";
952
+ const isDisabled = disabled || isDeleting;
953
+ const description = confirmDescription ?? /* @__PURE__ */ jsxs(Fragment, { children: [
954
+ "Are you sure you want to delete ",
955
+ /* @__PURE__ */ jsxs("span", { className: "inline-block max-w-[30ch] truncate align-bottom", children: [
956
+ "\u201C",
957
+ displayName,
958
+ "\u201D"
959
+ ] }),
960
+ fileSize != null ? ` (${formatFileSize(fileSize)})` : "",
961
+ "? This action cannot be undone."
962
+ ] });
963
+ return /* @__PURE__ */ jsxs("div", { className: cn("inline-flex flex-col gap-1.5", className), children: [
964
+ /* @__PURE__ */ jsx("div", { className: "inline-flex items-center gap-2", children: /* @__PURE__ */ jsxs(
965
+ AlertDialog,
966
+ {
967
+ open: del.phase === "confirming",
968
+ onOpenChange: (open) => {
969
+ if (!open) del.cancelDelete();
970
+ },
971
+ children: [
972
+ /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(Tooltip, { children: [
973
+ /* @__PURE__ */ jsxs(
974
+ TooltipTrigger,
975
+ {
976
+ render: /* @__PURE__ */ jsx(
977
+ AlertDialogTrigger,
978
+ {
979
+ disabled: isDisabled,
980
+ onClick: () => del.requestDelete(objectKey),
981
+ render: /* @__PURE__ */ jsx(
982
+ Button,
983
+ {
984
+ size: "default",
985
+ variant: "destructive",
986
+ disabled: isDisabled
987
+ }
988
+ )
989
+ }
990
+ ),
991
+ children: [
992
+ isDeleting ? /* @__PURE__ */ jsx(
993
+ LoaderIcon,
994
+ {
995
+ className: "animate-spin",
996
+ "data-icon": "inline-start"
997
+ }
998
+ ) : /* @__PURE__ */ jsx(Trash2Icon, { "data-icon": "inline-start" }),
999
+ label ?? "Delete"
1000
+ ]
1001
+ }
1002
+ ),
1003
+ /* @__PURE__ */ jsx(TooltipContent, { children: tooltipText })
1004
+ ] }) }),
1005
+ /* @__PURE__ */ jsxs(AlertDialogContent, { children: [
1006
+ /* @__PURE__ */ jsxs(AlertDialogHeader, { children: [
1007
+ /* @__PURE__ */ jsx(AlertDialogMedia, { children: /* @__PURE__ */ jsx(Trash2Icon, {}) }),
1008
+ /* @__PURE__ */ jsx(AlertDialogTitle, { children: confirmTitle }),
1009
+ /* @__PURE__ */ jsx(AlertDialogDescription, { className: "[overflow-wrap:anywhere]", children: description })
1010
+ ] }),
1011
+ /* @__PURE__ */ jsxs(AlertDialogFooter, { children: [
1012
+ /* @__PURE__ */ jsx(AlertDialogCancel, { children: "Cancel" }),
1013
+ /* @__PURE__ */ jsx(
1014
+ AlertDialogAction,
1015
+ {
1016
+ variant: "destructive",
1017
+ onClick: () => del.confirmDelete(),
1018
+ children: "Delete"
1019
+ }
1020
+ )
1021
+ ] })
1022
+ ] })
1023
+ ]
1024
+ }
1025
+ ) }),
1026
+ showStatus && del.phase === "success" && /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center gap-1.5 text-xs", children: [
1027
+ /* @__PURE__ */ jsx(CheckCircle2Icon, { className: "size-3.5 shrink-0 text-green-600" }),
1028
+ /* @__PURE__ */ jsxs("p", { className: "min-w-0 [overflow-wrap:anywhere] text-green-600", children: [
1029
+ "\u201C",
1030
+ /* @__PURE__ */ jsx("span", { className: "inline-block max-w-[30ch] truncate align-bottom", children: displayName }),
1031
+ "\u201D deleted"
1032
+ ] })
1033
+ ] }),
1034
+ showStatus && del.phase === "error" && /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-start gap-1.5 text-xs", children: [
1035
+ /* @__PURE__ */ jsx(AlertCircleIcon, { className: "mt-0.5 size-3.5 shrink-0 text-destructive" }),
1036
+ /* @__PURE__ */ jsx("p", { className: "min-w-0 [overflow-wrap:anywhere] text-destructive", children: del.error ?? "Delete failed" })
1037
+ ] })
1038
+ ] });
1039
+ }
1040
+
1041
+ export { DeleteButton, DownloadButton, MultiUploadStatus, ProgressDownloadButton, UploadButton, UploadDropzone, UploadStatus };
1042
+ //# sourceMappingURL=index.js.map
2
1043
  //# sourceMappingURL=index.js.map