86 lines
2.4 KiB
TypeScript
86 lines
2.4 KiB
TypeScript
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
|
|
|
|
import type { ReactNode, FC } from 'react';
|
|
import { createContext, useId } from 'react';
|
|
|
|
import { A11yLiveRegion } from 'mastodon/components/a11y_live_region';
|
|
import type { FieldStatus } from 'mastodon/components/callout_inline';
|
|
import { CalloutInline } from 'mastodon/components/callout_inline';
|
|
|
|
import classes from './fieldset.module.scss';
|
|
import { getFieldStatus } from './form_field_wrapper';
|
|
import formFieldWrapperClasses from './form_field_wrapper.module.scss';
|
|
|
|
interface FieldsetProps {
|
|
legend: ReactNode;
|
|
hint?: ReactNode;
|
|
name?: string;
|
|
status?: FieldStatus | FieldStatus['variant'];
|
|
layout?: 'vertical' | 'horizontal';
|
|
children: ReactNode;
|
|
}
|
|
|
|
export const FieldsetNameContext = createContext<string | undefined>(undefined);
|
|
|
|
/**
|
|
* A fieldset suitable for wrapping a group of checkboxes,
|
|
* radio buttons, or other grouped form controls.
|
|
*/
|
|
|
|
export const Fieldset: FC<FieldsetProps> = ({
|
|
legend,
|
|
hint,
|
|
name,
|
|
status,
|
|
layout,
|
|
children,
|
|
}) => {
|
|
const uniqueId = useId();
|
|
const labelId = `${uniqueId}-label`;
|
|
const hintId = `${uniqueId}-hint`;
|
|
const statusId = `${uniqueId}-status`;
|
|
const fieldsetName = name || `${uniqueId}-fieldset-name`;
|
|
const hasHint = !!hint;
|
|
|
|
const fieldStatus = getFieldStatus(status);
|
|
const hasStatusMessage = !!fieldStatus?.message;
|
|
|
|
const descriptionIds = [
|
|
hasHint ? hintId : '',
|
|
hasStatusMessage ? statusId : '',
|
|
]
|
|
.filter((id) => !!id)
|
|
.join(' ');
|
|
|
|
return (
|
|
<fieldset
|
|
className={classes.fieldset}
|
|
data-has-error={status === 'error'}
|
|
aria-labelledby={labelId}
|
|
aria-describedby={descriptionIds}
|
|
>
|
|
<div className={formFieldWrapperClasses.labelWrapper}>
|
|
<div id={labelId} className={formFieldWrapperClasses.label}>
|
|
{legend}
|
|
</div>
|
|
{hasHint && (
|
|
<p id={hintId} className={formFieldWrapperClasses.hint}>
|
|
{hint}
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
<div className={classes.fieldsWrapper} data-layout={layout}>
|
|
<FieldsetNameContext.Provider value={fieldsetName}>
|
|
{children}
|
|
</FieldsetNameContext.Provider>
|
|
</div>
|
|
|
|
{/* Live region must be rendered even when empty */}
|
|
<A11yLiveRegion className={classes.status} id={statusId}>
|
|
{hasStatusMessage && <CalloutInline {...fieldStatus} />}
|
|
</A11yLiveRegion>
|
|
</fieldset>
|
|
);
|
|
};
|