Skip to content

Commit ad19f55

Browse files
committed
feat: add separate file input component
1 parent 1efbf7c commit ad19f55

File tree

3 files changed

+55
-1
lines changed

3 files changed

+55
-1
lines changed

src/components/file-input.tsx

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { type ComponentProps, splitProps } from 'solid-js';
2+
3+
import { cn, type WithOverride } from '~/utils';
4+
import {
5+
makeFieldComponent,
6+
FORM_INPUT_PROP_NAMES,
7+
type FormInputProps,
8+
} from './forms';
9+
10+
export type FileFieldProps = ComponentProps<typeof FileField>;
11+
12+
export const FileField = makeFieldComponent({
13+
inputComponent: FileInput,
14+
inputPropNames: [...FORM_INPUT_PROP_NAMES, 'multiple', 'accept'],
15+
});
16+
17+
export interface FileInputOptions
18+
extends FormInputProps<HTMLInputElement, File[] | null> {
19+
multiple?: boolean;
20+
accept?: string;
21+
}
22+
23+
export type FileInputProps = WithOverride<
24+
ComponentProps<'input'>,
25+
FileInputOptions
26+
>;
27+
28+
/**
29+
* A file input component that allows users to select one or multiple
30+
* files.
31+
*/
32+
export function FileInput(props: FileInputProps) {
33+
// `value` cannot be set for file inputs and thus gets discarded.
34+
const [_, rest] = splitProps(props, ['class', 'onChange', 'value']);
35+
36+
function onChange(event: Event) {
37+
const input = event.currentTarget as HTMLInputElement;
38+
const files = input.files ? Array.from(input.files) : null;
39+
props.onChange?.(files);
40+
}
41+
42+
return (
43+
<input
44+
type="file"
45+
class={cn(
46+
'flex w-full rounded-md border border-input bg-transparent text-sm shadow-sm transition-shadow file:mr-4 file:py-2 file:px-4 file:border-0 file:bg-secondary file:text-sm file:font-medium hover:file:bg-secondary/80 focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
47+
props.class,
48+
)}
49+
onChange={onChange}
50+
{...rest}
51+
/>
52+
);
53+
}

src/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export * from './context-menu';
1414
export * from './dialog';
1515
export * from './drawer';
1616
export * from './dropdown-menu';
17+
export * from './file-input';
1718
export * from './forms';
1819
export * from './hover-card';
1920
export * from './image';

src/components/text-input.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export function TextInput(props: TextInputProps) {
3333
return (
3434
<input
3535
class={cn(
36-
'flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-shadow file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
36+
'flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-shadow placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
3737
props.class,
3838
)}
3939
onChange={e => props.onChange?.(e.target.value)}

0 commit comments

Comments
 (0)