import React, { forwardRef, useId } from 'react'; import { useTranslations } from '@shoptet/ui-core-web'; import type { FormRowProps } from '../../FormRow/FormRow'; import { FormRow } from '../../FormRow/FormRow'; import { FileInput } from '../../Inputs/FileInput/FileInput'; import type { FileItemProps } from '../../Inputs/FileInput/FileItem'; import type { FileInputItem, FileInputOptions, StoredFile } from '../../Inputs/FileInput/hooks'; import { getFileName, getImagePreviewSource } from '../../Inputs/FileInput/utils'; import { dictionary } from './dictionary'; export type { FileInputItem as FileFieldItem, StoredFile } from '../../Inputs/FileInput/hooks'; /** @inheritdoc */ export interface FileFieldProps extends Omit, FileInputOptions, Pick { /** * Id of the input button * @default undefined * */ id?: string; /** * The error message to display. * If an array is provided, the error message will be displayed per file. * @default undefined */ error?: string | string[]; /** * The warning message to display. * If an array is provided, the warning message will be displayed per file. * @default undefined */ warning?: string | string[]; /** * The hint message to display. * If an array is provided, the hint message will be displayed per file. * @default undefined */ hint?: string | string[]; /** * The label after an uploaded file. * If a function is provided, it will be called for each uploaded file. * If a React node is provided, it will be displayed for each uploaded file. * @default undefined */ labelAfter?: React.ReactNode | ((file: FileInputItem) => React.ReactNode); /** * The label for the file input field. */ label: string; /** * Function to provide an image preview URL for a stored file or a URL string. * Called only for `StoredFile` and `string` items — local `File` objects * always use their own `createObjectURL` preview. * Defaults to `FileField.getPreviewSrc`, which delegates to `getImagePreviewSource`: * it returns the value as-is for `string` items that look like an image URL and * always returns `undefined` for `StoredFile`. Provide a custom implementation if you * need previews for `StoredFile` items. */ getPreviewSrc?: (file: StoredFile | string) => string | undefined; } export const FileField = forwardRef( ( { width = 'md', label, required, justify, labelHidden, as, error, warning, hint, id, accept, disabled, maxFiles, multiple, onChange, value, tooltip, labelAfter, getPreviewSrc = getImagePreviewSource, showImagePreview, ...restProps }, ref ) => { const fileInputId = useId(); const { files, add, replace, remove, inputProps, inputRef } = FileInput.useFileInput({ multiple, maxFiles, accept, onChange, value, disabled, }); const translations = useTranslations(dictionary); const allowMoreFiles = files.length === 0 || (multiple && (!maxFiles || files.length < maxFiles)); return (
{files.map((file, index) => (
remove(index)} onReplace={() => replace(index)} error={Array.isArray(error) ? !!error[index] : !!error} warning={Array.isArray(warning) ? !!warning[index] : !!warning} tooltip={!multiple && !allowMoreFiles && index === 0 && tooltip} labelAfter={typeof labelAfter === 'function' ? labelAfter(file) : labelAfter} />
))} {(allowMoreFiles || multiple) && ( {files.length > 0 ? translations.addMoreFiles : translations.addFile} )}
); } ); FileField.displayName = 'FileField'; (FileField as typeof FileField & { getPreviewSrc: typeof getImagePreviewSource }).getPreviewSrc = getImagePreviewSource;