148 lines
3.2 KiB
TypeScript
148 lines
3.2 KiB
TypeScript
import clsx from 'clsx';
|
|
import { ChangeEvent, ComponentProps, createRef } from 'react';
|
|
import { CheckIcon, Loader2Icon, UploadIcon, XCircleIcon } from 'lucide-react';
|
|
|
|
import { AutomationTestingProps } from '@/types';
|
|
|
|
import { Button } from '@@/buttons';
|
|
import { Icon } from '@@/Icon';
|
|
import { Tooltip } from '@@/Tip/Tooltip';
|
|
|
|
export interface Props extends AutomationTestingProps {
|
|
onChange(value: File): void;
|
|
value?: File | null;
|
|
accept?: string;
|
|
title?: string;
|
|
required?: boolean;
|
|
inputId: string;
|
|
className?: string;
|
|
color?: ComponentProps<typeof Button>['color'];
|
|
name?: string;
|
|
hideFilename?: boolean;
|
|
tooltip?: string;
|
|
state?: 'uploading' | 'success';
|
|
disabled?: boolean;
|
|
}
|
|
|
|
export function FileUploadField({
|
|
onChange,
|
|
value,
|
|
accept,
|
|
title = 'Select a file',
|
|
required = false,
|
|
inputId,
|
|
className,
|
|
color = 'primary',
|
|
name,
|
|
hideFilename,
|
|
tooltip,
|
|
disabled,
|
|
state,
|
|
'data-cy': dataCy,
|
|
}: Props) {
|
|
const fileRef = createRef<HTMLInputElement>();
|
|
|
|
return (
|
|
<div className="flex items-center gap-2">
|
|
<input
|
|
id={inputId}
|
|
ref={fileRef}
|
|
type="file"
|
|
accept={accept}
|
|
required={required}
|
|
disabled={disabled}
|
|
className="!hidden"
|
|
onChange={changeHandler}
|
|
name={name}
|
|
data-cy={`${dataCy}-input`}
|
|
/>
|
|
<Button
|
|
size="small"
|
|
color={color}
|
|
onClick={handleButtonClick}
|
|
className={clsx('!ml-0', className)}
|
|
data-cy={dataCy}
|
|
icon={UploadIcon}
|
|
disabled={disabled}
|
|
>
|
|
{title}
|
|
</Button>
|
|
{tooltip && <Tooltip message={tooltip} />}
|
|
|
|
<FileStatus
|
|
state={state}
|
|
value={value}
|
|
hideFilename={hideFilename}
|
|
required={required}
|
|
dataCy={dataCy}
|
|
/>
|
|
</div>
|
|
);
|
|
|
|
function handleButtonClick() {
|
|
if (fileRef && fileRef.current) {
|
|
fileRef.current.click();
|
|
}
|
|
}
|
|
|
|
function changeHandler(event: ChangeEvent<HTMLInputElement>) {
|
|
if (event.target && event.target.files && event.target.files.length > 0) {
|
|
onChange(event.target.files[0]);
|
|
}
|
|
}
|
|
}
|
|
|
|
function FileStatus({
|
|
state,
|
|
value,
|
|
hideFilename,
|
|
required,
|
|
dataCy,
|
|
}: {
|
|
state?: 'uploading' | 'success';
|
|
value?: File | null;
|
|
hideFilename?: boolean;
|
|
required?: boolean;
|
|
dataCy?: string;
|
|
}) {
|
|
const stateIcon = getStateIcon(state, dataCy);
|
|
const filename = !hideFilename && value ? value.name : null;
|
|
|
|
if (stateIcon || filename) {
|
|
return (
|
|
<span className="flex items-center gap-2">
|
|
{stateIcon}
|
|
{filename}
|
|
</span>
|
|
);
|
|
}
|
|
|
|
if (required) {
|
|
return (
|
|
<span data-cy={dataCy ? `${dataCy}-required-icon` : undefined}>
|
|
<Icon icon={XCircleIcon} mode="danger" />
|
|
</span>
|
|
);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function getStateIcon(state?: 'uploading' | 'success', dataCy?: string) {
|
|
if (state === 'uploading') {
|
|
return (
|
|
<span data-cy={dataCy ? `${dataCy}-uploading-icon` : undefined}>
|
|
<Icon icon={Loader2Icon} className="animate-spin-slow" />
|
|
</span>
|
|
);
|
|
}
|
|
if (state === 'success') {
|
|
return (
|
|
<span data-cy={dataCy ? `${dataCy}-success-icon` : undefined}>
|
|
<Icon icon={CheckIcon} mode="success" />
|
|
</span>
|
|
);
|
|
}
|
|
return null;
|
|
}
|