personal web client for Bluesky
typescript
solidjs
bluesky
atcute
1import type { JSX } from 'solid-js';
2
3import { createId } from '~/lib/hooks/id';
4
5import { useFieldset } from './fieldset';
6
7export interface TextInputProps {
8 ref?: (node: HTMLInputElement) => void;
9 label?: string;
10 type?: 'text' | 'email' | 'password' | 'search' | 'tel' | 'url';
11 name?: string;
12 autocomplete?:
13 | 'off'
14 | 'on'
15 | 'name'
16 | 'email'
17 | 'username'
18 | 'current-password'
19 | 'new-password'
20 | 'one-time-code';
21 pattern?: string;
22 required?: boolean;
23 disabled?: boolean;
24 placeholder?: string;
25 error?: string | null | undefined | false;
26 description?: string;
27 value?: string;
28 headerAccessory?: JSX.Element;
29 onInput?: (ev: InputEvent) => void;
30}
31
32const TextInput = (props: TextInputProps) => {
33 const fieldset = useFieldset();
34 const id = createId();
35
36 const hasValue = 'value' in props;
37 const isDisabled = () => fieldset.disabled || !!props.disabled;
38
39 const hasLabel = 'label' in props;
40 const hasHeaderAccessory = 'headerAccessory' in props;
41
42 return (
43 <div class={`flex flex-col gap-2` + (isDisabled() ? ` opacity-50` : ``)}>
44 {(hasLabel || hasHeaderAccessory) && (
45 <div class="flex justify-between gap-2">
46 <label for={id} class="overflow-hidden break-words text-sm font-medium text-contrast">
47 {props.label}
48 </label>
49
50 {props.headerAccessory}
51 </div>
52 )}
53
54 <input
55 ref={props.ref}
56 id={id}
57 name={props.name}
58 type={props.type || 'text'}
59 autocomplete={props.autocomplete}
60 pattern={props.pattern}
61 required={props.required}
62 disabled={isDisabled()}
63 value={hasValue ? props.value : ''}
64 onInput={props.onInput}
65 placeholder={props.placeholder}
66 class="rounded border border-outline-md bg-background px-3 py-2 text-sm leading-6 text-contrast outline-2 -outline-offset-2 outline-accent placeholder:text-contrast-muted focus:outline"
67 />
68
69 {props.error && <p class="text-de text-error">{props.error}</p>}
70
71 <p class="text-pretty text-de text-contrast-muted empty:hidden">{props.description}</p>
72 </div>
73 );
74};
75
76export default TextInput;