impl/typescript.tsx
1,753 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.
import { useId, type JSX, type ReactNode } from "react";
import { fieldIds } from "./react_form_form_field.ts"; ← from react.form.form-field ^1.0.0 · built alongside by fune
import type { FieldsetProps } from "./react_form_fieldset_types.ts";
function present(node: ReactNode): boolean {
return node !== null && node !== undefined && node !== false && node !== "";
}
/**
* The GOV.UK fieldset: a legend that asks the question, then the hint and the
* error message, then the inputs. The hint and error describe the fieldset
* itself, so a screen reader announces them once on entering the group
* rather than on every input. With isPageHeading the legend holds the page's
* h1, the pattern for a page that asks one question.
*/
export function Fieldset(props: FieldsetProps): JSX.Element {
const auto = useId();
const { legend, hint, error, isPageHeading, children, className } = props;
const id = props.id ?? auto;
const ids = fieldIds(id, present(hint), present(error));
const classes = ["fune-field", ids.error ? "fune-field--error" : null, className || null].filter(Boolean).join(" ");
return (
<div className={classes}>
<fieldset className="fune-fieldset" aria-describedby={ids.describedBy ?? undefined}>
<legend className={isPageHeading ? "fune-legend fune-legend--heading" : "fune-legend"}>
{isPageHeading ? <h1 className="fune-legend-heading">{legend}</h1> : legend}
</legend>
{ids.hint ? (
<div className="fune-hint" id={ids.hint}>
{hint}
</div>
) : null}
{ids.error ? (
<p className="fune-error-message" id={ids.error}>
<span className="fune-visually-hidden">Error:</span> {error}
</p>
) : null}
{children}
</fieldset>
</div>
);
}