handy online tools for AT Protocol
boat.kelinci.net
atproto
bluesky
atcute
typescript
solidjs
1import { createEffect, type JSX } from 'solid-js';
2
3import { createId } from '~/lib/hooks/id';
4
5import type { BoundInputEvent } from './_types';
6
7interface SelectInputProps<T extends string> {
8 label: JSX.Element;
9 blurb?: string;
10 name?: string;
11 required?: boolean;
12 value?: T;
13 autofocus?: boolean;
14 options: { value: NoInfer<T>; label: string; disabled?: boolean }[];
15 onChange?: (next: NoInfer<T>, event: BoundInputEvent<HTMLSelectElement>) => void;
16}
17
18const SelectInput = <T extends string>(props: SelectInputProps<T>) => {
19 const fieldId = createId();
20
21 const onChange = props.onChange;
22
23 return (
24 <div class="flex flex-col gap-2">
25 <label for={fieldId} class="font-semibold text-gray-600">
26 {props.label}
27 </label>
28
29 <select
30 ref={(node) => {
31 if ('autofocus' in props) {
32 createEffect(() => {
33 if (props.autofocus) {
34 node.focus();
35 }
36 });
37 }
38 }}
39 id={fieldId}
40 name={props.name}
41 required={props.required}
42 value={props.value ?? ''}
43 class="rounded border border-gray-400 py-2 pl-3 pr-8 text-sm focus:border-purple-800 focus:ring-1 focus:ring-purple-800 focus:ring-offset-0"
44 onInput={(event) => onChange?.(event.target.value as T, event)}
45 >
46 {props.options.map((props) => {
47 return (
48 <option value={/* @once */ props.value} disabled={props.disabled}>
49 {props.label}
50 </option>
51 );
52 })}
53 </select>
54
55 <p class="text-pretty text-[0.8125rem] leading-5 text-gray-500 empty:hidden">{props.blurb}</p>
56 </div>
57 );
58};
59
60export default SelectInput;