dudu-file-upload-components
Version:
A highly customizable, config-driven file upload component library built with React, TailwindCSS, and class-variance-authority
3 lines (2 loc) • 20.4 kB
JavaScript
import e,{useState as r,useRef as t,useCallback as i,forwardRef as s}from"react";import{jsxs as a,jsx as n}from"react/jsx-runtime";import{Upload as o,File as l,X as c,AlertCircle as d}from"lucide-react";import{clsx as u}from"clsx";import{cva as m}from"class-variance-authority";const p=(e={})=>{const{maxFiles:s=1/0,maxSize:a=1/0,accept:n="*",multiple:o=!1,initialFiles:l=[],onFilesChange:c,onFilesAdded:d}=e,[u,m]=r({files:l.map(e=>({file:e,id:e.id,preview:e.url})),isDragging:!1,errors:[]}),p=t(null),g=i(e=>{if(e instanceof File){if(e.size>a)return`File "${e.name}" exceeds the maximum size of ${f(a)}.`}else if(e.size>a)return`File "${e.name}" exceeds the maximum size of ${f(a)}.`;if("*"!==n){const r=n.split(",").map(e=>e.trim()),t=e instanceof File?e.type||"":e.type,i=`.${File,e.name.split(".").pop()}`;if(!r.some(e=>{if(e.startsWith("."))return i.toLowerCase()===e.toLowerCase();if(e.endsWith("/*")){const r=e.split("/")[0];return t.startsWith(`${r}/`)}return t===e}))return`File "${File,e.name}" is not an accepted file type.`}return null},[n,a]),v=i(e=>e instanceof File?URL.createObjectURL(e):e.url,[]),b=i(e=>e instanceof File?`${e.name}-${Date.now()}-${Math.random().toString(36).substring(2,9)}`:e.id,[]),h=i(()=>{m(e=>{e.files.forEach(e=>{e.preview&&e.file instanceof File&&e.file.type.startsWith("image/")&&URL.revokeObjectURL(e.preview)}),p.current&&(p.current.value="");const r=Object.assign(Object.assign({},e),{files:[],errors:[]});return setTimeout(()=>{null==c||c(r.files)},0),r})},[c]),x=i(e=>{if(!e||0===e.length)return;const r=Array.from(e),t=[];if(m(e=>Object.assign(Object.assign({},e),{errors:[]})),o||h(),o&&s!==1/0&&u.files.length+r.length>s)return t.push(`You can only upload a maximum of ${s} files.`),void m(e=>Object.assign(Object.assign({},e),{errors:t}));const i=[];r.forEach(e=>{if(o){if(u.files.some(r=>r.file.name===e.name&&r.file.size===e.size))return}if(e.size>a)return void t.push(o?`Some files exceed the maximum size of ${f(a)}.`:`File exceeds the maximum size of ${f(a)}.`);const r=g(e);r?t.push(r):i.push({file:e,id:b(e),preview:v(e)})}),i.length>0?(null==d||d(i),m(e=>{const r=o?[...e.files,...i]:i;return setTimeout(()=>{null==c||c(r)},0),Object.assign(Object.assign({},e),{files:r,errors:t})})):t.length>0&&m(e=>Object.assign(Object.assign({},e),{errors:t})),p.current&&(p.current.value="")},[u.files,s,o,a,g,v,b,h,c,d]),w=i(e=>{m(r=>{const t=r.files.find(r=>r.id===e);t&&t.preview&&t.file instanceof File&&t.file.type.startsWith("image/")&&URL.revokeObjectURL(t.preview);const i=r.files.filter(r=>r.id!==e);return setTimeout(()=>{null==c||c(i)},0),Object.assign(Object.assign({},r),{files:i,errors:[]})})},[c]),z=i(()=>{m(e=>Object.assign(Object.assign({},e),{errors:[]}))},[]),y=i(e=>{e.preventDefault(),e.stopPropagation(),m(e=>Object.assign(Object.assign({},e),{isDragging:!0}))},[]),F=i(e=>{e.preventDefault(),e.stopPropagation(),e.currentTarget.contains(e.relatedTarget)||m(e=>Object.assign(Object.assign({},e),{isDragging:!1}))},[]),N=i(e=>{e.preventDefault(),e.stopPropagation()},[]),O=i(e=>{var r;if(e.preventDefault(),e.stopPropagation(),m(e=>Object.assign(Object.assign({},e),{isDragging:!1})),!(null===(r=p.current)||void 0===r?void 0:r.disabled)&&e.dataTransfer.files&&e.dataTransfer.files.length>0)if(o)x(e.dataTransfer.files);else{const r=e.dataTransfer.files[0];x([r])}},[x,o]),j=i(e=>{e.target.files&&e.target.files.length>0&&x(e.target.files)},[x]),D=i(()=>{p.current&&p.current.click()},[]),C=i((e={})=>Object.assign(Object.assign({},e),{type:"file",onChange:j,accept:e.accept||n,multiple:void 0!==e.multiple?e.multiple:o,ref:p}),[n,o,j]);return[u,{addFiles:x,removeFile:w,clearFiles:h,clearErrors:z,handleDragEnter:y,handleDragLeave:F,handleDragOver:N,handleDrop:O,handleFileChange:j,openFileDialog:D,getInputProps:C}]},f=(e,r=2)=>{if(0===e)return"0 Bytes";const t=r<0?0:r,i=Math.floor(Math.log(e)/Math.log(1024));return Number.parseFloat((e/Math.pow(1024,i)).toFixed(t))+["Bytes","KB","MB","GB","TB","PB","EB","ZB","YB"][i]};function g(e,r){var t={};for(var i in e)Object.prototype.hasOwnProperty.call(e,i)&&r.indexOf(i)<0&&(t[i]=e[i]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols){var s=0;for(i=Object.getOwnPropertySymbols(e);s<i.length;s++)r.indexOf(i[s])<0&&Object.prototype.propertyIsEnumerable.call(e,i[s])&&(t[i[s]]=e[i[s]])}return t}function v(...e){return u(e)}function b(e,r,t=[]){const i=[];for(const s of r)switch(s.type){case"maxSize":"number"==typeof s.value&&e.size>s.value&&i.push(s.message.replace("{size}",N(s.value)));break;case"fileType":if("string"==typeof s.value){const r=s.value.split(",").map(e=>e.trim()),t=e.type||"",a=`.${e.name.split(".").pop()}`;r.some(e=>{if(e.startsWith("."))return a.toLowerCase()===e.toLowerCase();if(e.endsWith("/*")){const r=e.split("/")[0];return t.startsWith(`${r}/`)}return t===e})||i.push(s.message)}break;case"maxFiles":"number"==typeof s.value&&t.length>s.value&&i.push(s.message.replace("{count}",s.value.toString()));break;case"minFiles":"number"==typeof s.value&&t.length<s.value&&i.push(s.message.replace("{count}",s.value.toString()));break;case"custom":s.validator&&!s.validator(e,t)&&i.push(s.message)}return i}function h(e){return e.type.startsWith("image/")}function x(e){return["application/pdf","application/msword","application/vnd.openxmlformats-officedocument.wordprocessingml.document","application/vnd.ms-excel","application/vnd.openxmlformats-officedocument.spreadsheetml.sheet","application/vnd.ms-powerpoint","application/vnd.openxmlformats-officedocument.presentationml.presentation","text/plain","text/csv"].includes(e.type)}function w(e){var r;if(h(e))return"image";if(x(e))return"file-text";switch(null===(r=e.name.split(".").pop())||void 0===r?void 0:r.toLowerCase()){case"zip":case"rar":case"7z":return"archive";case"mp4":case"avi":case"mov":case"wmv":return"video";case"mp3":case"wav":case"flac":return"music";default:return"file"}}function z(e){if(h(e))return URL.createObjectURL(e)}function y(e){e.startsWith("blob:")&&URL.revokeObjectURL(e)}function F(e){return`${e.name}-${e.size}-${Date.now()}-${Math.random().toString(36).substring(2,9)}`}function N(e,r=2){if(0===e)return"0 Bytes";const t=r<0?0:r,i=Math.floor(Math.log(e)/Math.log(1024));return Number.parseFloat((e/Math.pow(1024,i)).toFixed(t))+" "+["Bytes","KB","MB","GB","TB","PB","EB","ZB","YB"][i]}function O(e,r){const t=Object.assign({},e);for(const i in r)if(void 0!==r[i]){const s=e[i],a=r[i];"object"!=typeof a||null===a||Array.isArray(a)||"object"!=typeof s||null===s||Array.isArray(s)?t[i]=a:t[i]=O(s,a)}return t}function j(e,r){let t=null;return(...i)=>{t&&clearTimeout(t),t=setTimeout(()=>e(...i),r)}}function D(e,r){let t;return(...i)=>{t||(e(...i),t=!0,setTimeout(()=>t=!1,r))}}"function"==typeof SuppressedError&&SuppressedError;const C=m("relative inline-flex items-center justify-center transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",{variants:{variant:{button:"rounded-md bg-primary text-primary-foreground hover:bg-primary/90",dropzone:"border-2 border-dashed border-border hover:border-primary/50 bg-background rounded-lg p-6 text-center cursor-pointer transition-colors",preview:"border border-border rounded-lg overflow-hidden bg-background",compact:"rounded-md border border-border bg-background hover:bg-accent hover:text-accent-foreground"},size:{sm:"h-8 px-3 text-xs",md:"h-10 px-4 py-2",lg:"h-11 px-8"},radius:{none:"rounded-none",sm:"rounded-sm",md:"rounded-md",lg:"rounded-lg",full:"rounded-full"}},compoundVariants:[{variant:"dropzone",size:"sm",className:"min-h-24 p-4"},{variant:"dropzone",size:"md",className:"min-h-32 p-6"},{variant:"dropzone",size:"lg",className:"min-h-40 p-8"},{variant:"preview",size:"sm",className:"w-16 h-16"},{variant:"preview",size:"md",className:"w-24 h-24"},{variant:"preview",size:"lg",className:"w-32 h-32"},{variant:"compact",size:"sm",className:"h-8 px-2 text-xs"},{variant:"compact",size:"md",className:"h-9 px-3 text-sm"},{variant:"compact",size:"lg",className:"h-10 px-4"}],defaultVariants:{variant:"dropzone",size:"md",radius:"md"}}),k=m("relative overflow-hidden transition-all",{variants:{variant:{grid:"rounded-lg border border-border bg-background p-2",list:"flex items-center gap-3 rounded-md border border-border bg-background p-3",compact:"flex items-center gap-2 rounded border border-border bg-background p-2"},size:{sm:"text-xs",md:"text-sm",lg:"text-base"}},compoundVariants:[{variant:"grid",size:"sm",className:"w-20 h-20"},{variant:"grid",size:"md",className:"w-24 h-24"},{variant:"grid",size:"lg",className:"w-32 h-32"}],defaultVariants:{variant:"grid",size:"md"}}),S=m("text-sm font-medium",{variants:{variant:{default:"text-destructive",subtle:"text-muted-foreground",prominent:"text-destructive bg-destructive/10 px-3 py-2 rounded-md"}},defaultVariants:{variant:"default"}}),P=m("w-full bg-secondary rounded-full overflow-hidden",{variants:{size:{sm:"h-1",md:"h-2",lg:"h-3"}},defaultVariants:{size:"md"}}),E=m("flex-shrink-0",{variants:{size:{sm:"w-4 h-4",md:"w-5 h-5",lg:"w-6 h-6",xl:"w-8 h-8"},variant:{default:"text-muted-foreground",primary:"text-primary",success:"text-green-500",error:"text-destructive",warning:"text-yellow-500"}},defaultVariants:{size:"md",variant:"default"}}),$=m("inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",{variants:{variant:{default:"bg-primary text-primary-foreground hover:bg-primary/90",destructive:"bg-destructive text-destructive-foreground hover:bg-destructive/90",outline:"border border-input bg-background hover:bg-accent hover:text-accent-foreground",secondary:"bg-secondary text-secondary-foreground hover:bg-secondary/80",ghost:"hover:bg-accent hover:text-accent-foreground",link:"text-primary underline-offset-4 hover:underline"},size:{default:"h-10 px-4 py-2",sm:"h-9 rounded-md px-3",lg:"h-11 rounded-md px-8",icon:"h-10 w-10"}},defaultVariants:{variant:"default",size:"default"}}),L=({value:e,size:r="md",className:t,showLabel:i=!1,status:s="uploading"})=>{const o=Math.min(100,Math.max(0,e));return a("div",{className:v("w-full",t),children:[n("div",{className:P({size:r}),children:n("div",{className:v("h-full transition-all duration-300 ease-out rounded-full",(()=>{switch(s){case"success":return"bg-green-500";case"error":return"bg-destructive";case"uploading":return"bg-primary";default:return"bg-muted-foreground"}})()),style:{width:`${o}%`}})}),i&&a("div",{className:"flex justify-between items-center mt-1 text-xs text-muted-foreground",children:[n("span",{className:"capitalize",children:s}),a("span",{children:[Math.round(o),"%"]})]})]})},A=s((r,t)=>{var{variant:i="dropzone",size:s="md",radius:u="md",multiple:m=!1,maxFiles:f=1,maxSize:b=10485760,accept:x="*",disabled:w=!1,showPreview:z=!0,showProgress:y=!1,allowRemove:F=!0,className:O,onFilesChange:j,onFilesAdded:D,onError:P,initialFiles:A=[],labels:B={}}=r,U=g(r,["variant","size","radius","multiple","maxFiles","maxSize","accept","disabled","showPreview","showProgress","allowRemove","className","onFilesChange","onFilesAdded","onError","initialFiles","labels"]);const M=Object.assign({dropzone:"Drop files here or click to browse",button:"Choose Files",dragActive:"Drop files here or click to browse",remove:"Remove file",clear:"Clear all files"},B),[R,T]=p({multiple:m,maxFiles:f,maxSize:b,accept:x,initialFiles:null==A?void 0:A.map(e=>({name:e.file.name,size:e.file.size,type:e.file.type,url:e.preview||"",id:e.id})),onFilesChange:j,onFilesAdded:D});e.useEffect(()=>{R.errors.length>0&&(null==P||P(R.errors))},[R.errors,P]);return a("div",Object.assign({ref:t},U,{children:[n("input",Object.assign({},T.getInputProps(),{className:"hidden"})),"button"===i&&a("button",{type:"button",onClick:T.openFileDialog,disabled:w,className:v($({variant:"default",size:"md"===s?"default":"sm"===s?"sm":"lg"}),O),children:[n(o,{className:E({size:"sm"})}),M.button]}),"dropzone"===i&&n("div",{onDragEnter:T.handleDragEnter,onDragLeave:T.handleDragLeave,onDragOver:T.handleDragOver,onDrop:T.handleDrop,onClick:T.openFileDialog,"data-dragging":R.isDragging||void 0,className:v(C({variant:i,size:s,radius:u}),"data-[dragging=true]:bg-accent/50 data-[dragging=true]:border-primary transition-colors",w&&"opacity-50 cursor-not-allowed",O),children:a("div",{className:"flex flex-col items-center gap-2",children:[n(o,{className:E({size:"sm"===s?"md":"lg"})}),n("div",{className:"text-sm text-muted-foreground",children:R.isDragging?M.dragActive:M.dropzone}),b<1/0&&a("div",{className:"text-xs text-muted-foreground",children:["Max size: ",N(b)]})]})}),"preview"===i&&n("div",{className:v(C({variant:i,size:s,radius:u}),O),children:R.files.length>0?a("div",{className:"relative w-full h-full",children:[R.files[0].preview&&h(R.files[0].file)?n("img",{src:R.files[0].preview,alt:"Preview",className:v("w-full h-full object-cover","full"===u&&"rounded-full")}):n("div",{className:"flex items-center justify-center w-full h-full bg-muted",children:n(l,{className:E({size:"lg"})})}),F&&n("button",{type:"button",onClick:()=>T.removeFile(R.files[0].id),className:"absolute top-1 right-1 p-1 bg-destructive text-destructive-foreground rounded-full hover:bg-destructive/90",children:n(c,{className:"w-3 h-3"})})]}):n("div",{onClick:T.openFileDialog,className:"flex items-center justify-center w-full h-full cursor-pointer hover:bg-muted/50 transition-colors",children:n(o,{className:E({size:"lg"})})})}),"compact"===i&&a("button",{type:"button",onClick:T.openFileDialog,disabled:w,className:v(C({variant:i,size:s,radius:u}),O),children:[n(o,{className:E({size:"sm"})}),n("span",{className:"ml-2",children:M.button})]}),"preview"!==i&&(z&&0!==R.files.length?n("div",{className:"mt-4 space-y-2",children:R.files.map(e=>a("div",{className:k({variant:"list",size:s}),children:[a("div",{className:"flex items-center gap-3 flex-1 min-w-0",children:[e.preview&&h(e.file)?n("img",{src:e.preview,alt:"Preview",className:"w-10 h-10 object-cover rounded"}):n(l,{className:E({size:"md"})}),a("div",{className:"flex-1 min-w-0",children:[n("div",{className:"text-sm font-medium truncate",children:e.file.name}),n("div",{className:"text-xs text-muted-foreground",children:N(e.file.size)}),y&&void 0!==e.progress&&n("div",{className:"mt-2",children:n(L,{value:e.progress,size:"sm",showLabel:!0,status:e.status})})]})]}),F&&n("button",{type:"button",onClick:()=>T.removeFile(e.id),className:"p-1 hover:bg-destructive/10 rounded",children:n(c,{className:"w-4 h-4 text-destructive"})})]},e.id))}):null),0===R.errors.length?null:n("div",{className:"mt-2 space-y-1",children:R.errors.map((e,r)=>a("div",{className:S({variant:"default"}),children:[n(d,{className:"w-4 h-4 inline mr-1"}),e]},r))})]}))});A.displayName="FileUpload";const B={variant:"dropzone",size:"md",radius:"md",multiple:!1,maxFiles:1,maxSize:10485760,accept:"*",disabled:!1,showPreview:!0,showProgress:!1,allowRemove:!0,labels:{dropzone:"Drop files here or click to browse",button:"Choose Files",dragActive:"Drop files here",maxFiles:"Maximum {count} files allowed",maxSize:"File size must be less than {size}",fileType:"File type not supported",remove:"Remove file",clear:"Clear all files"},icons:{upload:"upload",file:"file",image:"image",remove:"x",error:"alert-circle"},styles:{container:"",dropzone:"",button:"",preview:"",error:""}},U={default:{},"image-upload":{variant:"dropzone",accept:"image/*",maxSize:5242880,showPreview:!0,labels:{dropzone:"Drop images here or click to browse",button:"Choose Images",dragActive:"Drop images here",fileType:"Only image files are supported"}},"document-upload":{variant:"button",accept:".pdf,.doc,.docx,.txt,.csv,.xls,.xlsx,.ppt,.pptx",maxSize:26214400,multiple:!0,maxFiles:5,showPreview:!1,labels:{dropzone:"Drop documents here or click to browse",button:"Choose Documents",dragActive:"Drop documents here",fileType:"Only document files are supported"}},"multi-file":{variant:"dropzone",multiple:!0,maxFiles:10,maxSize:52428800,showPreview:!0,allowRemove:!0,labels:{dropzone:"Drop multiple files here or click to browse",button:"Choose Multiple Files",dragActive:"Drop files here",maxFiles:"Maximum 10 files allowed"}},compact:{variant:"compact",size:"sm",showPreview:!1,labels:{button:"Upload",dropzone:"Drop file"}}};function M(e="default",r={}){const t=U[e]||{};return Object.assign(Object.assign(Object.assign(Object.assign({},B),t),r),{labels:Object.assign(Object.assign(Object.assign({},B.labels),t.labels),r.labels),icons:Object.assign(Object.assign(Object.assign({},B.icons),t.icons),r.icons),styles:Object.assign(Object.assign(Object.assign({},B.styles),t.styles),r.styles)})}function R(e){const r=[];return void 0!==e.maxFiles&&e.maxFiles<1&&r.push("maxFiles must be at least 1"),void 0!==e.maxSize&&e.maxSize<1&&r.push("maxSize must be at least 1 byte"),!1===e.multiple&&void 0!==e.maxFiles&&e.maxFiles>1&&r.push("maxFiles cannot be greater than 1 when multiple is false"),r}const T={basicUpload:{variant:"button",size:"md",labels:{button:"Upload File"}},imageGallery:{variant:"dropzone",accept:"image/*",multiple:!0,maxFiles:20,showPreview:!0,labels:{dropzone:"Add photos to your gallery",dragActive:"Drop photos here"}},documentLibrary:{variant:"dropzone",accept:".pdf,.doc,.docx",multiple:!0,maxFiles:50,maxSize:10485760,showPreview:!1,labels:{dropzone:"Upload documents to your library",maxFiles:"Up to 50 documents"}}},W=e=>{var{config:r,className:t,onFilesChange:i,onFilesAdded:s,onError:a,initialFiles:o}=e,l=g(e,["config","className","onFilesChange","onFilesAdded","onError","initialFiles"]);const c=R(r);return c.length>0&&console.warn("FileUpload configuration errors:",c),n(A,Object.assign({variant:r.variant,size:r.size,radius:r.radius,multiple:r.multiple,maxFiles:r.maxFiles,maxSize:r.maxSize,accept:r.accept,disabled:r.disabled,showPreview:r.showPreview,allowRemove:r.allowRemove,labels:r.labels,className:t,onFilesChange:i,onFilesAdded:s,onError:a,initialFiles:o},l))},V=e=>{var{preset:r,overrides:t={}}=e,i=g(e,["preset","overrides"]);const s=M(r,t);return n(W,Object.assign({config:s},i))},J=r=>{var{configJSON:t,fallbackPreset:i="default",onConfigError:s}=r,o=g(r,["configJSON","fallbackPreset","onConfigError"]);const[l,c]=e.useState(null),[d,u]=e.useState(null);return e.useEffect(()=>{try{const e=JSON.parse(t),r=M("default",e),a=R(e);if(a.length>0){const e=`Configuration errors: ${a.join(", ")}`;u(e),null==s||s(e),c(M(i))}else c(r),u(null)}catch(e){const r=`Invalid JSON configuration: ${e instanceof Error?e.message:"Unknown error"}`;u(r),null==s||s(r),c(M(i))}},[t,i,s]),l?a("div",{children:[d&&a("div",{className:"mb-4 p-3 bg-destructive/10 border border-destructive/20 rounded-md",children:[n("div",{className:"text-sm text-destructive font-medium",children:"Configuration Error"}),n("div",{className:"text-xs text-destructive/80 mt-1",children:d}),a("div",{className:"text-xs text-muted-foreground mt-1",children:["Using fallback preset: ",i]})]}),n(W,Object.assign({config:l},o))]}):n("div",{children:"Loading..."})};function I(r){const[t,i]=e.useState(()=>M("default",r)),s=e.useCallback(e=>{i(r=>M("default",Object.assign(Object.assign({},r),e)))},[]),a=e.useCallback((e="default")=>{i(M(e))},[]),n=e.useCallback(()=>JSON.stringify(t,null,2),[t]),o=e.useCallback(e=>{try{const r=JSON.parse(e),t=R(r);if(t.length>0)throw new Error(`Configuration errors: ${t.join(", ")}`);return i(M("default",r)),{success:!0,error:null}}catch(e){return{success:!1,error:e instanceof Error?e.message:"Unknown error"}}},[]);return{config:t,updateConfig:s,resetConfig:a,exportConfig:n,importConfig:o}}export{B as DEFAULT_CONFIG,T as EXAMPLE_CONFIGS,A as FileUpload,J as FileUploadJSONRenderer,V as FileUploadPresetRenderer,W as FileUploadRenderer,U as PRESET_CONFIGS,L as ProgressBar,z as createFilePreview,j as debounce,S as errorVariants,k as filePreviewVariants,C as fileUploadVariants,N as formatBytes,F as generateFileId,M as getConfig,w as getFileIcon,E as iconVariants,x as isDocumentFile,h as isImageFile,O as mergeConfig,P as progressVariants,y as revokeFilePreview,D as throttle,$ as uploadButtonVariants,p as useFileUpload,I as useFileUploadConfig,R as validateConfig,b as validateFile};
//# sourceMappingURL=index.esm.js.map