This commit is contained in:
giancarlo
2024-03-24 02:23:22 +08:00
parent 648d77b430
commit bce3479368
589 changed files with 37067 additions and 9596 deletions

View File

@@ -0,0 +1,202 @@
'use client';
import type { FormEvent, MouseEventHandler } from 'react';
import { forwardRef, useCallback, useEffect, useRef, useState } from 'react';
import Image from 'next/image';
import cn from 'clsx';
import { UploadCloud, XIcon } from 'lucide-react';
import { Button } from '@kit/ui/button';
import { Label } from '@kit/ui/label';
import { If } from './if';
type Props = Omit<React.InputHTMLAttributes<unknown>, 'value'> & {
image?: string | null;
onClear?: () => void;
onValueChange?: (props: { image: string; file: File }) => void;
visible?: boolean;
};
const IMAGE_SIZE = 22;
const ImageUploadInput = forwardRef<React.ElementRef<'input'>, Props>(
function ImageUploadInputComponent(
{
children,
image,
onClear,
onInput,
onValueChange,
visible = true,
...props
},
forwardedRef,
) {
const localRef = useRef<HTMLInputElement>();
const [state, setState] = useState({
image,
fileName: '',
});
const onInputChange = useCallback(
(e: FormEvent<HTMLInputElement>) => {
e.preventDefault();
const files = e.currentTarget.files;
if (files?.length) {
const file = files[0];
const data = URL.createObjectURL(file);
setState({
image: data,
fileName: file.name,
});
if (onValueChange) {
onValueChange({
image: data,
file,
});
}
}
if (onInput) {
onInput(e);
}
},
[onInput, onValueChange],
);
const onRemove = useCallback(() => {
setState({
image: '',
fileName: '',
});
if (localRef.current) {
localRef.current.value = '';
}
if (onClear) {
onClear();
}
}, [onClear]);
const imageRemoved: MouseEventHandler = useCallback(
(e) => {
e.preventDefault();
onRemove();
},
[onRemove],
);
const setRef = useCallback(
(input: HTMLInputElement) => {
localRef.current = input;
if (typeof forwardedRef === 'function') {
forwardedRef(localRef.current);
}
},
[forwardedRef],
);
useEffect(() => {
setState((state) => ({ ...state, image }));
}, [image]);
useEffect(() => {
if (!image) {
onRemove();
}
}, [image, onRemove]);
const Input = () => (
<input
{...props}
className={cn('hidden', props.className)}
ref={setRef}
type={'file'}
onInput={onInputChange}
accept="image/*"
aria-labelledby={'image-upload-input'}
/>
);
if (!visible) {
return <Input />;
}
return (
// eslint-disable-next-line jsx-a11y/label-has-associated-control
<label
id={'image-upload-input'}
className={`relative flex h-10 w-full cursor-pointer rounded-md border border-dashed border-input
bg-background px-3 py-2 text-sm outline-none ring-primary ring-offset-2 ring-offset-background transition-all file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus:ring-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50`}
>
<Input />
<div className={'flex items-center space-x-4'}>
<div className={'flex'}>
<If condition={!state.image}>
<UploadCloud className={'dark:text-dark-500 h-5 text-gray-500'} />
</If>
<If condition={state.image}>
<Image
loading={'lazy'}
style={{
width: IMAGE_SIZE,
height: IMAGE_SIZE,
}}
className={'object-contain'}
width={IMAGE_SIZE}
height={IMAGE_SIZE}
src={state.image!}
alt={props.alt ?? ''}
/>
</If>
</div>
<If condition={!state.image}>
<div className={'flex flex-auto'}>
<Label className={'cursor-pointer text-xs'}>{children}</Label>
</div>
</If>
<If condition={state.image}>
<div className={'flex flex-auto'}>
<If
condition={state.fileName}
fallback={
<Label className={'cursor-pointer truncate text-xs'}>
{children}
</Label>
}
>
<Label className={'truncate text-xs'}>{state.fileName}</Label>
</If>
</div>
</If>
<If condition={state.image}>
<Button
size={'icon'}
className={'!h-5 !w-5'}
onClick={imageRemoved}
>
<XIcon className="h-4" />
</Button>
</If>
</div>
</label>
);
},
);
export default ImageUploadInput;