import { deprecated } from "@maykin-ui/client-common"; import clsx from "clsx"; import React, { useId, useState } from "react"; import { FormField, gettextFirst, isCheckbox, isCheckboxGroup, isChoiceField, isDateInput, isDatePicker, isDateRangeInput, isDurationInput, isInput, isRadio, isRadioGroup, isTextarea, } from "../../../lib"; import { Checkbox } from "../checkbox"; import { ChoiceField } from "../choicefield"; import { DateInput } from "../dateinput"; import { DatePicker } from "../datepicker"; import { DateRangeInput } from "../daterangeinput"; import { DurationInput } from "../durationinput"; import { ErrorMessage } from "../errormessage"; import { TRANSLATIONS } from "../form/translations"; import { Input, InputProps } from "../input"; import { Label } from "../label"; import { Radio } from "../radio"; import { Textarea } from "../textarea"; import "./formcontrol.scss"; // FIXME: Clashes in types, add generic for FormField? export type FormControlProps = FormField & { /** The direction in which to render the form. */ direction?: "v" | "h" | "vertical" | "horizontal"; // TODO: deprecate horizontal and vertical /** Justification type. */ justify?: "baseline" | "stretch"; /** An error message to show. */ error?: string; /** Whether to forcefully show the error (if set), this skips the dirty check. */ forceShowError?: boolean; /** Whether to show a required indicator (*) when a field is required. */ showRequiredIndicator?: boolean; /** The required indicator (*). */ requiredIndicator?: string; /** The required (accessible) label. */ labelRequired?: string; }; /** * Renders the correct form widget (based on its props shape) along with its * label and `error` message. * @param direction * @param justify * @param error * @param forceShowError * @param showRequiredLabel * @param requiredIndicator * @param labelRequired * @param props * @constructor */ export const FormControl: React.FC = ({ direction = "v", justify = "baseline", error = "", forceShowError, showRequiredIndicator = true, requiredIndicator, labelRequired, ...props }) => { deprecated( direction === "horizontal", 'direction="horizontal"', `mykn.components.FormControl: direction="horizontal" is deprecated, use direction="h" instead`, ); deprecated( direction === "vertical", 'direction="vertical"', `mykn.components.FormControl: direction="vertical" is deprecated, use direction="v" instead`, ); const [isDirty, setIsDirty] = useState(false); const id = useId(); const _id = props.id || id; // Keep in sync with CheckboxGroup, possibly add constant? const htmlFor = isCheckboxGroup(props) || isRadioGroup(props) ? `${_id}-choice-0` : _id; const idError = `${id}_error`; const _requiredIndicator = gettextFirst( requiredIndicator, TRANSLATIONS.REQUIRED_INDICATOR, ); const _labelRequired = gettextFirst( labelRequired, TRANSLATIONS.LABEL_REQUIRED, { label: props.label }, ); const _props = isCheckboxGroup(props) || isRadioGroup(props) ? { ...props, direction: direction[0] } : props; return (
{props.label && ( )} { setIsDirty(true); // @ts-expect-error - EventTarget type not resolved. props.onChange?.(e); }} /> {(isDirty || forceShowError) && error && ( {error} )}
); }; /** * Renders the correct component for props based on its shape. * @param props * @constructor * @private */ export const FormWidget: React.FC = ({ ...props }) => { if (isCheckbox(props)) { return ; } if (isRadio(props)) { return ; } if (isDateInput(props)) { return ; } if (isDateRangeInput(props)) { return ; } if (isDatePicker(props)) { return ; } if (isChoiceField(props)) { return ; } if (isTextarea(props)) { return