UNPKG

@aegov/design-system-react

Version:

A design system for the Government of the United Arab Emirates. Extending the original design system released as @aegov/design-system, this is the package specefically created for the ReactJs enviornment.

311 lines (300 loc) 11.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.FileUpload = FileUpload; exports.default = void 0; var React = _interopRequireWildcard(require("react")); var _react2 = require("@phosphor-icons/react"); var _zod = require("zod"); var _reactDropzone = require("react-dropzone"); var _Button = _interopRequireDefault(require("../Button/Button")); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } // Constants const DEFAULT_ACCEPTED_FILES = { 'image/jpeg': [], 'image/png': [], 'image/gif': [] }; const MAX_FILE_SIZE = 10 * 1024 * 1024; // 50MB // Variant Components const SimpleVariant = ({ label, fileInputRef, disabled, acceptedFileTypes, maxFiles, handleFileSelect, selectedFiles }) => /*#__PURE__*/React.createElement("div", { className: "w-full" }, /*#__PURE__*/React.createElement("div", { className: `flex border rounded-lg overflow-hidden ${disabled ? 'opacity-50' : ''}` }, /*#__PURE__*/React.createElement("label", null, /*#__PURE__*/React.createElement(_Button.default, { disabled: disabled, style: "primary", variant: "link", className: `h-full px-4 py-2 rounded-none border-r ${disabled ? 'opacity-50 cursor-not-allowed' : ''}`, asChild: true }, /*#__PURE__*/React.createElement("span", { className: `cursor-pointer ${disabled ? 'cursor-not-allowed opacity-50' : ''}` }, label, /*#__PURE__*/React.createElement("input", { ref: fileInputRef, type: "file", accept: Object.keys(acceptedFileTypes).join(','), multiple: maxFiles > 1, onChange: handleFileSelect, disabled: disabled, className: "hidden" })))), /*#__PURE__*/React.createElement("div", { className: `flex-1 px-4 py-2 text-gray-500 ${disabled ? 'opacity-50' : ''}` }, selectedFiles.length > 0 ? selectedFiles[0].name : 'No file chosen'))); const PreviewVariant = ({ label, fileInputRef, disabled, handleFileSelect, previewUrl }) => /*#__PURE__*/React.createElement("div", { className: "flex items-center gap-3" }, /*#__PURE__*/React.createElement("div", { className: `h-12 w-12 flex items-center justify-center bg-gray-100 rounded-full overflow-hidden ${disabled ? 'opacity-50' : ''}` }, previewUrl ? /*#__PURE__*/React.createElement("img", { src: previewUrl, alt: "Preview", className: "h-full w-full object-cover" }) : /*#__PURE__*/React.createElement(_react2.Image, { size: 24, className: `text-gray-400 ${disabled ? 'opacity-50' : ''}` })), /*#__PURE__*/React.createElement("label", null, /*#__PURE__*/React.createElement(_Button.default, { disabled: disabled, style: "primary", variant: "solid", className: disabled ? 'opacity-50 cursor-not-allowed' : '', asChild: true }, /*#__PURE__*/React.createElement("span", { className: `flex items-center gap-2 cursor-pointer ${disabled ? 'cursor-not-allowed opacity-50' : ''}` }, label, /*#__PURE__*/React.createElement("input", { ref: fileInputRef, type: "file", accept: "image/*", multiple: false, onChange: handleFileSelect, disabled: disabled, className: "hidden" }))))); const DragDropVariant = ({ label, getRootProps, getInputProps, isDragActive, disabled, fileInputRef, acceptedFileTypes, maxFiles }) => /*#__PURE__*/React.createElement("div", _extends({}, getRootProps(), { className: `border-2 border-dashed rounded-lg p-8 text-center transition-colors ${isDragActive ? 'border-primary-500 bg-primary-50' : 'border-gray-300'} ${disabled ? 'opacity-50 cursor-not-allowed bg-gray-50' : 'cursor-pointer hover:border-primary-500'}` }), /*#__PURE__*/React.createElement("label", { className: "flex flex-col items-center" }, /*#__PURE__*/React.createElement(_react2.UploadSimple, { size: 32, className: `mb-2 ${isDragActive ? 'text-primary-500' : 'text-gray-400'}` }), /*#__PURE__*/React.createElement("span", { className: `font-semibold ${isDragActive ? 'text-primary-600' : 'text-gray-600'}` }, label), /*#__PURE__*/React.createElement("input", _extends({}, getInputProps(), { ref: fileInputRef })), /*#__PURE__*/React.createElement("span", { className: `text-gray-500 mt-1 ${disabled ? 'opacity-50' : ''}` }, maxFiles > 1 ? `Up to ${maxFiles} files` : 'Single file'), /*#__PURE__*/React.createElement("p", { className: `text-xs text-gray-400 mt-2 ${disabled ? 'opacity-50' : ''}` }, "Accepts ", Object.keys(acceptedFileTypes).map(type => type.split('/')[1].toUpperCase()).join(', '), " up to 50MB"))); const DefaultVariant = ({ label, fileInputRef, disabled, acceptedFileTypes, maxFiles, handleFileSelect }) => /*#__PURE__*/React.createElement("label", null, /*#__PURE__*/React.createElement(_Button.default, { disabled: disabled, style: "primary", variant: "solid", className: disabled ? 'opacity-50 cursor-not-allowed' : '', asChild: true }, /*#__PURE__*/React.createElement("span", { className: `flex items-center gap-2 cursor-pointer ${disabled ? 'cursor-not-allowed opacity-50' : ''}` }, label, /*#__PURE__*/React.createElement("input", { ref: fileInputRef, type: "file", accept: Object.keys(acceptedFileTypes).join(','), multiple: maxFiles > 1, onChange: handleFileSelect, disabled: disabled, className: "hidden" })))); function FileUpload({ variant = 'default', onFilesSelected, maxFiles = 1, disabled = false, className = '', acceptedFileTypes = DEFAULT_ACCEPTED_FILES, label = 'Upload file' }) { // State and refs const [selectedFiles, setSelectedFiles] = React.useState([]); const [error, setError] = React.useState(''); const [previewUrl, setPreviewUrl] = React.useState(null); const fileInputRef = React.useRef(null); // Dropzone configuration const onDrop = React.useCallback((acceptedFiles, rejectedFiles) => { if (rejectedFiles?.length > 0) { // Handle rejected files const error = rejectedFiles[0].errors[0]; if (error.code === 'file-too-large') { setError('File is too large (max 50MB)'); } else if (error.code === 'file-invalid-type') { setError(`Only ${Object.keys(acceptedFileTypes).map(type => type.split('/')[1].toUpperCase()).join(', ')} files are allowed`); } else { setError(error.message); } return; } handleFiles(acceptedFiles); }, [acceptedFileTypes, handleFiles]); const { getRootProps, getInputProps, isDragActive } = (0, _reactDropzone.useDropzone)({ accept: acceptedFileTypes, maxSize: MAX_FILE_SIZE, disabled, maxFiles: maxFiles - selectedFiles.length, onDrop, noClick: variant !== 'dragDrop', multiple: maxFiles > 1 }); // Variant validation React.useEffect(() => { if (variant === 'withPreview') { if (maxFiles !== 1) { console.warn('withPreview variant only supports single file upload'); } if (!Object.keys(acceptedFileTypes).every(type => type.startsWith('image/'))) { console.warn('withPreview variant only supports image files'); } } }, [variant, maxFiles, acceptedFileTypes]); // Cleanup preview URL React.useEffect(() => { return () => { if (previewUrl) { URL.revokeObjectURL(previewUrl); } }; }, [previewUrl]); // Handlers const handleFiles = React.useCallback(files => { try { if (!files || files.length === 0) { return; } // Check if adding new files would exceed maxFiles limit if (selectedFiles.length + files.length > maxFiles) { setError(`You can only upload up to ${maxFiles} files`); return; } const fileArray = Array.from(files); // Validate file types const invalidTypeFile = fileArray.find(file => !Object.keys(acceptedFileTypes).includes(file.type)); if (invalidTypeFile) { setError(`Only ${Object.keys(acceptedFileTypes).map(type => type.split('/')[1].toUpperCase()).join(', ')} files are allowed`); return; } // Validate file sizes const oversizedFile = fileArray.find(file => file.size > MAX_FILE_SIZE); if (oversizedFile) { setError('Files must be less than 50MB'); return; } setSelectedFiles(prev => [...prev, ...fileArray]); setError(''); onFilesSelected?.(fileArray); if (variant === 'withPreview' && fileArray[0]) { const url = URL.createObjectURL(fileArray[0]); setPreviewUrl(url); } } catch (err) { setError(err.message); } }, [maxFiles, selectedFiles.length, acceptedFileTypes, variant, onFilesSelected]); const handleFileSelect = event => { handleFiles(event.target.files); }; const removeFile = index => { setSelectedFiles(prev => prev.filter((_, i) => i !== index)); if (fileInputRef.current) { fileInputRef.current.value = ''; } if (variant === 'withPreview') { if (previewUrl) { URL.revokeObjectURL(previewUrl); } setPreviewUrl(null); } }; // File list renderer const renderFileList = () => { return selectedFiles.map((file, index) => /*#__PURE__*/React.createElement("div", { key: index, className: "flex items-center gap-2 mt-2 text-sm text-gray-600" }, /*#__PURE__*/React.createElement("button", { onClick: () => removeFile(index), className: "p-1 text-red-500 hover:text-red-700 rounded-full", "aria-label": "Remove file" }, /*#__PURE__*/React.createElement(_react2.X, { size: 16, weight: "bold" })), /*#__PURE__*/React.createElement("span", { className: "truncate" }, file.name))); }; // Variant renderer const renderVariant = () => { const commonProps = { label, fileInputRef, disabled, acceptedFileTypes, maxFiles, handleFileSelect, selectedFiles, previewUrl, getRootProps, getInputProps, isDragActive }; switch (variant) { case 'simple': return /*#__PURE__*/React.createElement(SimpleVariant, commonProps); case 'withPreview': return /*#__PURE__*/React.createElement(PreviewVariant, commonProps); case 'dragDrop': return /*#__PURE__*/React.createElement(DragDropVariant, commonProps); default: return /*#__PURE__*/React.createElement(DefaultVariant, commonProps); } }; return /*#__PURE__*/React.createElement("div", { className: `w-full ${className}` }, renderVariant(), renderFileList(), error && /*#__PURE__*/React.createElement("p", { className: "text-sm text-red-500 mt-2" }, error)); } var _default = exports.default = FileUpload;