// ~/src/siteplane/components/fieldset/fieldset.tsx "use client"; import { Fieldset as FieldsetPrimitive } from "@base-ui/react/fieldset"; import { mergeProps } from "@base-ui/react/merge-props"; import { useRender } from "@base-ui/react/use-render"; import * as React from "react"; import { cn, type WithStringClassName } from "../../system/utils/cn"; import { slotClass } from "../../system/utils/slot-class"; /** Registers a description's id with the Fieldset it describes. */ type FieldsetDescriptionRegistry = (id: string) => () => void; const FieldsetDescriptionContext = React.createContext(null); /** * A group of related fields. Its FieldsetDescription parts describe it: their * ids join the fieldset's `aria-describedby`, after one the application gives. * Its rows stay packed at the top: a Fieldset that a grid or flex row * stretches to a taller neighbour's height leaves the extra height below its * last row instead of opening gaps between its legend, description and * fields (`content-start`). */ export function Fieldset({ "aria-describedby": describedBy, className, ...props }: WithStringClassName): React.ReactElement { const [descriptionIds, setDescriptionIds] = React.useState( [], ); const register = React.useCallback((id) => { setDescriptionIds((ids) => [...ids, id]); return () => setDescriptionIds((ids) => ids.filter((entry) => entry !== id)); }, []); const ariaDescribedBy = [describedBy, ...descriptionIds].filter(Boolean).join(" ") || undefined; return ( ); } export function FieldsetLegend({ className, ...props }: WithStringClassName): React.ReactElement { return ( ); } export type FieldsetDescriptionProps = useRender.ComponentProps<"p">; /** * Text that explains the group, for the hint a group of fields shares. It * reads like the descriptions of other groups, such as a Card's, and the * Fieldset names it in its `aria-describedby`, so a screen reader reads it * when focus enters the group. Renders a `

`. */ export function FieldsetDescription({ className, id, render, ...props }: FieldsetDescriptionProps): React.ReactElement { const generatedId = React.useId(); const descriptionId = id ?? generatedId; const register = React.useContext(FieldsetDescriptionContext); React.useLayoutEffect( () => register?.(descriptionId), [descriptionId, register], ); const defaultProps = { className: cn( slotClass("fieldset-description", props, render), "wrap-anywhere", className, ), "data-slot": "fieldset-description", id: descriptionId, }; return useRender({ defaultTagName: "p", props: mergeProps<"p">(defaultProps, props), render, }); } export { FieldsetPrimitive }; // ~/src/siteplane/components/fieldset/index.ts export * from "./fieldset";