Functional Weave
Code in Rust

react.form.radio-group@1.0.1

impl/typescript.tsx

3,236 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 { Fragment, useId, type JSX, type ReactNode } from "react";
import { Fieldset } from "./react_form_fieldset.ts";  ← from react.form.fieldset ^1.0.0 · built alongside by fune
import type { RadioGroupProps } from "./react_form_radio_group_types.ts";

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

/**
 * GOV.UK radios: a fieldset whose legend asks the question, its hint and
 * error describing the whole group, then one radio per option, all under one
 * name. The first radio takes the id, so an error summary's link lands on
 * it, and the rest are <id>-2, <id>-3...; the group's hint and error are
 * <id>-hint and <id>-error. A radio has no state of its own to keep: the browser
 * knows which is chosen, so value makes it controlled and defaultValue does
 * not.
 */
export function RadioGroup(props: RadioGroupProps): JSX.Element {
  const auto = useId();
  const { legend, options, value, defaultValue, onChange, onBlur, hint, error, isPageHeading, inline, small, required, disabled, className } = props;
  const id = props.id ?? auto;
  const name = props.name ?? id;
  if (options.length === 0) throw new Error("a radio group needs at least one option");
  const seen = new Set<string>();
  for (const o of options) {
    if (seen.has(o.value)) throw new Error(`radio option values must be unique: "${o.value}" is there twice`);
    seen.add(o.value);
  }
  const classes = ["fune-radios", inline ? "fune-radios--inline" : null, small ? "fune-radios--small" : null].filter(Boolean).join(" ");
  return (
    <Fieldset id={id} legend={legend} hint={hint} error={error} isPageHeading={isPageHeading} className={className}>
      <div className={classes}>
        {options.map((o, i) => {
          const itemId = i === 0 ? id : `${id}-${i + 1}`;
          const hintId = present(o.hint) ? `${itemId}-item-hint` : undefined;
          return (
            <Fragment key={o.value}>
              {present(o.divider) ? <div className="fune-radios-divider">{o.divider}</div> : null}
              <div className="fune-radio">
                <input
                  className="fune-radio-input"
                  id={itemId}
                  name={name}
                  type="radio"
                  value={o.value}
                  checked={value === undefined ? undefined : value === o.value}
                  defaultChecked={value === undefined && defaultValue !== undefined ? defaultValue === o.value : undefined}
                  required={required}
                  disabled={disabled || o.disabled}
                  aria-describedby={hintId}
                  onBlur={onBlur ? () => onBlur() : undefined}
                  onChange={(event) => {
                    if (event.target.checked) onChange?.(o.value);
                  }}
                />
                <label className="fune-label fune-radio-label" htmlFor={itemId}>
                  {o.label}
                </label>
                {hintId ? (
                  <div className="fune-hint fune-radio-hint" id={hintId}>
                    {o.hint}
                  </div>
                ) : null}
              </div>
            </Fragment>
          );
        })}
      </div>
    </Fieldset>
  );
}