import { clsx } from 'clsx'; import { useId, useRef } from 'react'; import { Sentiment } from '../common'; import { InlinePrompt, type InlinePromptProps } from '../prompt'; import { FieldLabelContextProvider, InputDescribedByProvider, InputIdContextProvider, InputInvalidProvider, } from '../inputs/contexts'; import { Label } from '../label'; export type FieldProps = { /** `null` disables auto-generating the `id` attribute, falling back to nesting-based label association over setting `htmlFor` explicitly. */ id?: string | null; /** Should be specified unless the wrapped control has its own labeling mechanism, e.g. `Checkbox`. */ label?: React.ReactNode; /** @default true */ required?: boolean; /** @deprecated Use `description` prop instead. */ hint?: React.ReactNode; message?: React.ReactNode; /** * Override for the [InlinePrompt icon's default, accessible name](/?path=/docs/other-statusicon-accessibility--docs) * announced by the screen readers * */ messageIconLabel?: string; /** * If true, shows a loading spinner in place of the message icon of the InlinePrompt * @default false */ messageLoading?: boolean; description?: React.ReactNode; /** @deprecated Use `message` and `type={Sentiment.NEGATIVE}` prop instead. */ error?: React.ReactNode; /** @default Sentiment.NEUTRAL */ sentiment?: InlinePromptProps['sentiment']; className?: string; children?: React.ReactNode; }; export const Field = ({ id, label, required = true, message: propMessage, messageIconLabel, messageLoading, hint, description = hint, sentiment: propType = Sentiment.NEUTRAL, className, children, ...props }: FieldProps) => { const labelRef = useRef(null); const sentiment = props.error ? Sentiment.NEGATIVE : propType; const message = propMessage || props.error; const hasError = sentiment === Sentiment.NEGATIVE; const labelId = useId(); const fallbackInputId = useId(); const inputId = id !== null ? (id ?? fallbackInputId) : undefined; const messageId = useId(); const descriptionId = useId(); /** * form control can have multiple messages to describe it, * e.g the description underneath the label and inline alert */ function ariaDescribedbyByIds() { const messageIds = []; if (description) { messageIds.push(descriptionId); } if (message) { messageIds.push(messageId); } return messageIds.length > 0 ? messageIds.join(' ') : undefined; } return (
{label != null ? ( <> {description}
{children}
) : ( children )} {message && ( {message} )}
); };