@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
JavaScript
;
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;