Skip to content

Commit

Permalink
♿ - feat: indicate required fields
Browse files Browse the repository at this point in the history
  • Loading branch information
svenvandescheur committed Jan 23, 2025
1 parent 2955041 commit 2ac1966
Show file tree
Hide file tree
Showing 17 changed files with 178 additions and 18 deletions.
1 change: 1 addition & 0 deletions src/components/data/attributetable/attributetable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export const AttributeTable = <T extends object = object>({
const renderTable = () => {
return editable ? (
<Form<Record<keyof T, SerializedFormData[string]>>
showRequiredExplanation={false}
fieldsetClassName="mykn-attributetable__body"
showActions={isFormOpenState}
secondaryActions={[
Expand Down
1 change: 1 addition & 0 deletions src/components/data/datagrid/datagridtoolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ export const DataGridToolbar = <
},
]}
labelSubmit={ucFirst(_labelSaveFieldSelection)}
showRequiredExplanation={false}
onSubmit={(e) => {
const form = e.target as HTMLFormElement;
const data = serializeForm(form, false);
Expand Down
1 change: 1 addition & 0 deletions src/components/data/kanban/kanban.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ export const WithToolbar: Story = {
direction: "horizontal",
label: "Sorteren",
required: true,
showRequiredIndicator: false,
options: [
{ label: "Nieuwste eerst", value: "-pk", selected: true },
{ label: "Oudste eerst", value: "pk", selected: true },
Expand Down
52 changes: 39 additions & 13 deletions src/components/form/form/form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ import {
import { forceArray } from "../../../lib";
import { ButtonProps } from "../../button";
import { Toolbar, ToolbarItem, ToolbarProps } from "../../toolbar";
import { P } from "../../typography";
import { ErrorMessage } from "../errormessage";
import { FormControl } from "../formcontrol";
import "./form.scss";
import { TRANSLATIONS } from "./translations";

export type FormProps<T extends SerializedFormData = SerializedFormData> = Omit<
React.ComponentProps<"form">,
Expand Down Expand Up @@ -63,6 +65,21 @@ export type FormProps<T extends SerializedFormData = SerializedFormData> = Omit<
/** Whether to show the form actions. */
showActions?: boolean;

/** Whether to show a required indicator (*) when a field is required. */
showRequiredIndicator?: boolean;

/** The required indicator (*). */
requiredIndicator?: string;

/** The required explanation text. */
requiredExplanation?: string;

/** The required (accessible) label. */
labelRequired?: string;

/** Whether to show a text describing the meaning of * when one or more fields are required. */
showRequiredExplanation?: boolean;

/** Props to pass to Toolbar. */
toolbarProps?: Partial<ToolbarProps>;

Expand Down Expand Up @@ -126,6 +143,11 @@ export const Form = <T extends SerializedFormData = SerializedFormData>({
onChange,
onSubmit,
showActions = true,
showRequiredIndicator = true,
requiredIndicator,
labelRequired,
showRequiredExplanation = true,
requiredExplanation,
toolbarProps,
useTypedResults = false,
validate = validateForm<T>,
Expand All @@ -151,13 +173,17 @@ export const Form = <T extends SerializedFormData = SerializedFormData>({

const intl = useIntl();

const _labelSubmit = labelSubmit
? labelSubmit
: intl.formatMessage({
id: "mykn.components.Form.labelSubmit",
description: "mykn.components.Form: The submit form label",
defaultMessage: "verzenden",
});
const _requiredIndicator =
requiredIndicator || intl.formatMessage(TRANSLATIONS.REQUIRED_INDICATOR);

const _requiredExplanation =
requiredExplanation ||
intl.formatMessage(TRANSLATIONS.REQUIRED_EXPLANATION, {
requiredIndicator: _requiredIndicator,
});

const _labelSubmit =
labelSubmit || intl.formatMessage(TRANSLATIONS.LABEL_SUBMIT);

/**
* Revalidate on state change.
Expand Down Expand Up @@ -237,6 +263,8 @@ export const Form = <T extends SerializedFormData = SerializedFormData>({
</div>
)}

{showRequiredExplanation && <P>{_requiredExplanation}</P>}

{Boolean(fields?.length) && (
<div className={fieldsetClassName}>
{fields.map((field, index) => {
Expand All @@ -249,12 +277,7 @@ export const Form = <T extends SerializedFormData = SerializedFormData>({
const _labelValidationErrorRequired = labelValidationErrorRequired
? labelValidationErrorRequired
: intl.formatMessage(
{
id: "mykn.components.Form.labelValidationErrorRequired",
description:
'mykn.components.Form: The "required" validation error',
defaultMessage: "Veld {label} is verplicht",
},
TRANSLATIONS.LABEL_VALIDATION_ERROR_REQUIRED,
{ ...field, label, value },
);

Expand All @@ -271,6 +294,9 @@ export const Form = <T extends SerializedFormData = SerializedFormData>({
error={message}
forceShowError={!validateOnChange}
justify={justify}
showRequiredIndicator={showRequiredIndicator}
requiredIndicator={requiredIndicator}
labelRequired={labelRequired}
value={value}
onChange={defaultOnChange}
{...field}
Expand Down
34 changes: 34 additions & 0 deletions src/components/form/form/translations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Define the structure of a single message descriptor
import { defineMessages } from "../../../lib";

export const TRANSLATIONS = defineMessages({
REQUIRED_EXPLANATION: {
id: "mykn.components.Form.requiredExplanation",
description: "mykn.components.Form: The required explanation text",
defaultMessage:
"Verplichte velden zijn gemarkeerd met een sterretje ({requiredIndicator})",
},
REQUIRED_INDICATOR: {
id: "mykn.components.Form.requiredIndicator",
description: "mykn.components.Form: The required indicator (*)",
defaultMessage: "*",
},

LABEL_REQUIRED: {
id: "mykn.components.Form.labelRequired",
description: "mykn.components.Form: The required (accessible) label",
defaultMessage: "Veld {label} is verplicht",
},

LABEL_VALIDATION_ERROR_REQUIRED: {
id: "mykn.components.Form.labelValidationErrorRequired",
description: 'mykn.components.Form: The "required" validation error',
defaultMessage: "Veld {label} is verplicht",
},

LABEL_SUBMIT: {
id: "mykn.components.Form.labelSubmit",
description: "mykn.components.Form: The submit form label",
defaultMessage: "verzenden",
},
});
4 changes: 4 additions & 0 deletions src/components/form/formcontrol/formcontrol.scss
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,8 @@
width: 100%;
}
}

&__required-inidicator {
text-decoration: none;
}
}
16 changes: 14 additions & 2 deletions src/components/form/formcontrol/formcontrol.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,18 @@ type Story = StoryObj<typeof FormControl>;

export const InputFormControl: Story = {
args: {
error: 'Field "school year" does not contain a valid e-mail address',
error: 'Field "e-mail" does not contain a valid e-mail address',
forceShowError: true, // Make sure the Story shows error.
label: "Enter your e-mail address",
name: "e-mail",
value: "johndoen#example.com",
value: "johndoe@example.com",
},
};

export const SelectFormControl: Story = {
args: {
error: 'Field "school year" is required',
forceShowError: true, // Make sure the Story shows error.
options: [
{ label: "Freshman" },
{ label: "Sophomore" },
Expand All @@ -34,3 +36,13 @@ export const SelectFormControl: Story = {
value: "Junior",
},
};

export const Required: Story = {
args: {
error: "Dit veld is verplicht",
forceShowError: true, // Make sure the Story shows error.
label: "Enter your e-mail address",
name: "e-mail",
required: true,
},
};
42 changes: 41 additions & 1 deletion src/components/form/formcontrol/formcontrol.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import clsx from "clsx";
import React, { useId, useState } from "react";

import { useIntl } from "../../../lib";
import {
FormField,
isCheckbox,
Expand All @@ -19,6 +20,7 @@ import { DateInput } from "../dateinput";
import { DatePicker } from "../datepicker";
import { DateRangeInput } from "../daterangeinput";
import { ErrorMessage } from "../errormessage";
import { TRANSLATIONS } from "../form/translations";
import { Input, InputProps } from "../input";
import { Label } from "../label";
import { Radio } from "../radio";
Expand All @@ -36,6 +38,15 @@ export type FormControlProps = FormField & {

/** 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;
};

/**
Expand All @@ -45,6 +56,9 @@ export type FormControlProps = FormField & {
* @param justify
* @param error
* @param forceShowError
* @param showRequiredLabel
* @param requiredIndicator
* @param labelRequired
* @param props
* @constructor
*/
Expand All @@ -53,6 +67,9 @@ export const FormControl: React.FC<FormControlProps> = ({
justify = "baseline",
error = "",
forceShowError,
showRequiredIndicator = true,
requiredIndicator,
labelRequired,
...props
}) => {
const [isDirty, setIsDirty] = useState(false);
Expand All @@ -63,6 +80,15 @@ export const FormControl: React.FC<FormControlProps> = ({
isCheckboxGroup(props) || isRadioGroup(props) ? `${_id}-choice-0` : _id;
const idError = `${id}_error`;

const intl = useIntl();

const _requiredIndicator =
requiredIndicator || intl.formatMessage(TRANSLATIONS.REQUIRED_INDICATOR);

const _labelRequired =
labelRequired ||
intl.formatMessage(TRANSLATIONS.LABEL_REQUIRED, { label: props.label });

return (
<div
className={clsx(
Expand All @@ -71,7 +97,20 @@ export const FormControl: React.FC<FormControlProps> = ({
`mykn-form-control--justify-${justify}`,
)}
>
{props.label && <Label htmlFor={htmlFor}>{props.label}</Label>}
{props.label && (
<Label htmlFor={htmlFor}>
{props.label}
{props.required && showRequiredIndicator && (
<abbr
className="mykn-form-control__required-inidicator"
title={_labelRequired}
>
{_requiredIndicator}
</abbr>
)}
</Label>
)}

<FormWidget
id={_id}
aria-invalid={!!error}
Expand All @@ -84,6 +123,7 @@ export const FormControl: React.FC<FormControlProps> = ({
props.onChange?.(e);
}}
/>

{(isDirty || forceShowError) && error && (
<ErrorMessage id={idError}>{error}</ErrorMessage>
)}
Expand Down
2 changes: 1 addition & 1 deletion src/components/form/select/select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,8 @@ export const Select: React.FC<SelectProps> = ({
)}
tabIndex={0}
ref={refs.setReference}
title={label || undefined}
aria-autocomplete="none"
aria-label={label || undefined}
aria-hidden={hidden}
onBlur={handleBlur}
{...getReferenceProps()}
Expand Down
1 change: 1 addition & 0 deletions src/components/modal/modal.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export const ModalComponent: Story = {
},
]}
showActions={false}
showRequiredExplanation={false}
/>
</Body>
),
Expand Down
4 changes: 3 additions & 1 deletion src/hooks/dialog/useprompt.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ export const usePrompt = () => {
},
onCancel,
modalProps,
undefined,
{
showRequiredExplanation: false,
},
autofocus,
);
};
Expand Down
3 changes: 3 additions & 0 deletions src/lib/i18n/compiled/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,11 @@
"mykn.components.DatePicker.labelWeekPrefix": "Week",
"mykn.components.DateRangeInput.labelEndDate": "end date",
"mykn.components.DateRangeInput.labelStartDate": "start date",
"mykn.components.Form.labelRequired": "Field {label} is required",
"mykn.components.Form.labelSubmit": "submit",
"mykn.components.Form.labelValidationErrorRequired": "Field {label} is required",
"mykn.components.Form.requiredExplanation": "Required fields are marked with an asterisk ({requiredIndicator})",
"mykn.components.Form.requiredIndicator": "*",
"mykn.components.Kanban.labelMoveObject": "change position of item",
"mykn.components.Kanban.labelSelectColumn": "move item to column",
"mykn.components.Logo.hrefLabel": "Go to \"{href}\"",
Expand Down
3 changes: 3 additions & 0 deletions src/lib/i18n/compiled/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,11 @@
"mykn.components.DatePicker.labelWeekPrefix": "week",
"mykn.components.DateRangeInput.labelEndDate": "einddatum",
"mykn.components.DateRangeInput.labelStartDate": "startdatum",
"mykn.components.Form.labelRequired": "Veld {label} is verplicht",
"mykn.components.Form.labelSubmit": "verzenden",
"mykn.components.Form.labelValidationErrorRequired": "Veld {label} is verplicht",
"mykn.components.Form.requiredExplanation": "Verplichte velden zijn gemarkeerd met een sterretje ({requiredIndicator})",
"mykn.components.Form.requiredIndicator": "*",
"mykn.components.Kanban.labelMoveObject": "wijzig positie van onderdeel",
"mykn.components.Kanban.labelSelectColumn": "verplaats onderdeel naar kolom",
"mykn.components.Logo.hrefLabel": "Navigeer naar \"{href}\"",
Expand Down
15 changes: 15 additions & 0 deletions src/lib/i18n/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,11 @@
"description": "mykn.components.DateRangeInput: The start date (accessible) label",
"originalDefault": "startdatum"
},
"mykn.components.Form.labelRequired": {
"defaultMessage": "Field {label} is required",
"description": "mykn.components.Form: The required (accessible) label",
"originalDefault": "Veld {label} is verplicht"
},
"mykn.components.Form.labelSubmit": {
"defaultMessage": "submit",
"description": "mykn.components.Form: The submit form label",
Expand All @@ -154,6 +159,16 @@
"description": "mykn.components.Form: The \"required\" validation error",
"originalDefault": "Veld {label} is verplicht"
},
"mykn.components.Form.requiredExplanation": {
"defaultMessage": "Required fields are marked with an asterisk ({requiredIndicator})",
"description": "mykn.components.Form: The required explanation text",
"originalDefault": "Verplichte velden zijn gemarkeerd met een sterretje ({requiredIndicator})"
},
"mykn.components.Form.requiredIndicator": {
"defaultMessage": "*",
"description": "mykn.components.Form: The required indicator (*)",
"originalDefault": "*"
},
"mykn.components.Kanban.labelMoveObject": {
"defaultMessage": "change position of item",
"description": "mykn.components.Kanban: The kanban \"move object position\" (accessible) label",
Expand Down
Loading

0 comments on commit 2ac1966

Please sign in to comment.