mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
195 lines
4.8 KiB
TypeScript
195 lines
4.8 KiB
TypeScript
import { useEffect } from 'react';
|
|
import { useDropzone, type Accept } from 'react-dropzone';
|
|
|
|
import { Icon } from '@iconify/react';
|
|
import Button from '@/components/Button';
|
|
|
|
import { cn } from '@/lib/helper';
|
|
|
|
interface DropFileInputProps {
|
|
name: string;
|
|
label?: string;
|
|
bottomLabel?: string;
|
|
caption?: string;
|
|
values?: File[];
|
|
accept?: Accept;
|
|
required?: boolean;
|
|
maxFiles?: number; // defaults to 1
|
|
maxSize?: number; // defaults to 2097152 (2 MB)
|
|
isError?: boolean;
|
|
errorMessage?: string;
|
|
disabled?: boolean;
|
|
onChange?: (files: File[]) => void;
|
|
onDelete?: (index: number) => void;
|
|
className?: {
|
|
wrapper?: string;
|
|
inputContainer?: string;
|
|
label?: string;
|
|
inputWrapper?: string;
|
|
caption?: string;
|
|
bottomLabel?: string;
|
|
errorMessage?: string;
|
|
fileItemContainer?: string;
|
|
};
|
|
}
|
|
|
|
const DropFileInput: React.FC<DropFileInputProps> = ({
|
|
name,
|
|
label,
|
|
bottomLabel,
|
|
caption = 'Seret atau Pilih Dokumen',
|
|
values,
|
|
accept,
|
|
required,
|
|
maxFiles = Infinity,
|
|
maxSize,
|
|
isError,
|
|
errorMessage,
|
|
disabled,
|
|
onChange,
|
|
onDelete,
|
|
className,
|
|
}) => {
|
|
const isDisabled =
|
|
Boolean(values && maxFiles && values.length >= maxFiles) || disabled;
|
|
|
|
const {
|
|
acceptedFiles,
|
|
getRootProps,
|
|
getInputProps,
|
|
isFocused,
|
|
isDragAccept,
|
|
isDragReject,
|
|
} = useDropzone({
|
|
maxSize,
|
|
maxFiles,
|
|
accept: accept,
|
|
disabled: isDisabled,
|
|
});
|
|
|
|
useEffect(() => {
|
|
if (values && maxFiles && values.length <= maxFiles) {
|
|
onChange?.([...values, ...acceptedFiles]);
|
|
}
|
|
}, [acceptedFiles]);
|
|
|
|
return (
|
|
<div className={cn('w-full', className?.wrapper)}>
|
|
<div
|
|
className={cn(
|
|
'w-full flex flex-col gap-2 text-start',
|
|
className?.inputContainer
|
|
)}
|
|
>
|
|
{label && (
|
|
<label
|
|
htmlFor={name}
|
|
className={cn(
|
|
'w-full text-sm font-normal leading-5',
|
|
className?.label
|
|
)}
|
|
>
|
|
{label}
|
|
{required && (
|
|
<>
|
|
{' '}
|
|
<span className='tooltip tooltip-error' data-tip='required'>
|
|
<span className='text-error'>*</span>
|
|
</span>
|
|
</>
|
|
)}
|
|
</label>
|
|
)}
|
|
|
|
<div
|
|
{...getRootProps({
|
|
'aria-disabled': isDisabled,
|
|
className: cn(
|
|
'dropzone w-full px-4 py-2 border border-dashed border-gray-300 rounded cursor-pointer transition-all',
|
|
'hover:border-primary hover:bg-primary/10',
|
|
{
|
|
'border-success bg-success/10': isDragAccept,
|
|
'border-error bg-error/10': isDragReject || isError,
|
|
'border-primary bg-primary/10': isFocused,
|
|
'bg-gray-200/20 cursor-not-allowed': isDisabled,
|
|
},
|
|
className?.inputWrapper
|
|
),
|
|
})}
|
|
>
|
|
<input
|
|
{...getInputProps({
|
|
id: name,
|
|
name,
|
|
disabled: isDisabled,
|
|
'aria-disabled': isDisabled,
|
|
})}
|
|
/>
|
|
{caption && (
|
|
<p className={cn('text-gray-500 text-sm', className?.caption)}>
|
|
{caption}
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
{!isError && bottomLabel && (
|
|
<p
|
|
className={cn('w-full text-sm opacity-60', className?.bottomLabel)}
|
|
>
|
|
{bottomLabel}
|
|
</p>
|
|
)}
|
|
{isError && (
|
|
<p
|
|
className={cn('w-full text-sm text-error', className?.errorMessage)}
|
|
>
|
|
{errorMessage}
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
{values && values.length > 0 && (
|
|
<div
|
|
className={cn(
|
|
'w-full mt-1.5 flex flex-col gap-1.5',
|
|
className?.fileItemContainer
|
|
)}
|
|
>
|
|
{values.map((file, idx) => (
|
|
<div
|
|
key={idx}
|
|
className={cn('w-full flex flex-row items-center gap-2')}
|
|
>
|
|
<div className='p-2 rounded-full bg-primary/10'>
|
|
<Icon
|
|
icon='basil:file-solid'
|
|
width={24}
|
|
height={24}
|
|
className='text-blue-500'
|
|
/>
|
|
</div>
|
|
|
|
<div className='w-full text-sm'>
|
|
<p>{file.name}</p>
|
|
</div>
|
|
|
|
<Button
|
|
variant='ghost'
|
|
color='error'
|
|
onClick={() => {
|
|
onDelete?.(idx);
|
|
}}
|
|
className='rounded-full text-error focus-visible:text-error-content hover:text-error-content'
|
|
>
|
|
<Icon icon='fluent:delete-12-regular' width={24} height={24} />
|
|
</Button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default DropFileInput;
|