Functional Weave
Code in TypeScript

react.form.select@1.0.1

impl/typescript.tsx

2,125 bytes · the TypeScript implementation · view raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

"use client";

import { useId, type JSX, type ReactNode } from "react";
import { FormField, fieldIds } from "./react_form_form_field.ts";  ← from react.form.form-field ^1.0.0 · built alongside by fune
import type { SelectProps } from "./react_form_select_types.ts";

function present(node: ReactNode): boolean {
  return node !== null && node !== undefined && node !== false && node !== "";
}

/**
 * The GOV.UK select: a label, an optional hint and error message, and the
 * select, described by both. A placeholder is a real first option with the
 * value "", so a required select refuses the form until something else is
 * chosen, and a post without a choice says so with an empty value.
 */
export function Select(props: SelectProps): JSX.Element {
  const auto = useId();
  const { label, options, placeholder, value, defaultValue, onChange, onBlur, autoComplete, hint, error, required, disabled, className } = props;
  const id = props.id ?? auto;
  const ids = fieldIds(id, present(hint), present(error));
  const seen = new Set<string>(placeholder !== undefined ? [""] : []);
  for (const o of options) {
    if (seen.has(o.value)) throw new Error(`select option values must be unique: "${o.value}" is there twice (the placeholder's is "")`);
    seen.add(o.value);
  }
  return (
    <FormField id={id} label={label} hint={hint} error={error} className={className}>
      <select
        className={ids.error ? "fune-select fune-select--error" : "fune-select"}
        id={ids.control}
        name={props.name ?? id}
        value={value}
        defaultValue={defaultValue}
        autoComplete={autoComplete}
        required={required}
        disabled={disabled}
        aria-describedby={ids.describedBy ?? undefined}
        aria-invalid={ids.error ? true : undefined}
        onBlur={onBlur ? () => onBlur() : undefined}
        onChange={(event) => onChange?.(event.target.value)}
      >
        {placeholder !== undefined ? <option value="">{placeholder}</option> : null}
        {options.map((o) => (
          <option key={o.value} value={o.value} disabled={o.disabled}>
            {o.label}
          </option>
        ))}
      </select>
    </FormField>
  );
}