1import Check from "lucide-solid/icons/check";
2import Minus from "lucide-solid/icons/minus";
3import { createEffect, type JSX } from "solid-js";
4
5/**
6 * A checkbox drawn in the page's style around a real input, so Space, label clicks and forms behave natively. A string
7 * `disabled` is the reason, shown as the tooltip; `checked` without `onChange` is just the initial state in a form. The
8 * box flips at once, and once `onChange`'s promise settles it shows `checked` again, so a failed save flips it back.
9 */
10export function Checkbox(props: {
11 checked?: boolean;
12 indeterminate?: boolean;
13 disabled?: boolean | string;
14 name?: string;
15 value?: string;
16 onChange?: (checked: boolean) => unknown;
17 children: JSX.Element;
18}) {
19 const reason = () => (typeof props.disabled === "string" ? props.disabled : undefined);
20 let input!: HTMLInputElement;
21 createEffect(() => (input.indeterminate = !!props.indeterminate));
22 return (
23 <label class="checkbox" data-tip={reason()} tabIndex={reason() ? 0 : undefined}>
24 <input ref={input} type="checkbox" name={props.name} value={props.value} checked={props.checked} disabled={!!props.disabled}
25 onChange={async (event) => {
26 if (!props.onChange) return;
27 try {
28 await props.onChange(event.currentTarget.checked);
29 } finally {
30 input.checked = !!props.checked;
31 }
32 }} />
33 <span class="box" aria-hidden="true"><Check class="tick" /><Minus class="dash" /></span>
34 {props.children}
35 </label>
36 );
37}