From 947e890639ce6ac3ece68bfd7f1b60667541988b Mon Sep 17 00:00:00 2001 From: Nikita Orliak Date: Mon, 4 Nov 2024 16:14:06 +0100 Subject: [PATCH 1/7] fix(Input): Clarification that developers should use `ref` with `Input`. Add a new example for Storybook. --- .../a11y-input-error-message-not-read.md | 5 + .../src/components/Input/Input.stories.tsx | 126 ++++++++++++++++-- .../react-magma-docs/src/pages/api/input.mdx | 13 +- 3 files changed, 132 insertions(+), 12 deletions(-) create mode 100644 .changeset/a11y-input-error-message-not-read.md diff --git a/.changeset/a11y-input-error-message-not-read.md b/.changeset/a11y-input-error-message-not-read.md new file mode 100644 index 000000000..d996b451d --- /dev/null +++ b/.changeset/a11y-input-error-message-not-read.md @@ -0,0 +1,5 @@ +--- +'react-magma-docs': patch +--- + +fix(Input): Clarification that developers should use `ref` with `Input`. Add a new example for Storybook. diff --git a/packages/react-magma-dom/src/components/Input/Input.stories.tsx b/packages/react-magma-dom/src/components/Input/Input.stories.tsx index bbb5805b7..e4652fad1 100644 --- a/packages/react-magma-dom/src/components/Input/Input.stories.tsx +++ b/packages/react-magma-dom/src/components/Input/Input.stories.tsx @@ -2,12 +2,13 @@ import { Meta, Story } from '@storybook/react/types-6-0'; import React from 'react'; import { HelpIcon, NotificationsIcon, WorkIcon } from 'react-magma-icons'; import { Input, InputProps } from '.'; -import { ButtonSize, ButtonType, ButtonVariant } from '../Button'; +import { Button, ButtonSize, ButtonType, ButtonVariant } from '../Button'; import { Card, CardBody } from '../Card'; import { IconButton } from '../IconButton'; import { InputIconPosition, InputSize, InputType } from '../InputBase'; import { LabelPosition } from '../Label'; import { Tooltip } from '../Tooltip'; +import { ButtonGroup } from '../ButtonGroup'; const Template: Story = args => ( <> @@ -168,10 +169,7 @@ export const HelpLink = args => { }; return ( <> - + { /> - + { @@ -304,3 +306,105 @@ NumberInput.args = { NumberInput.parameters = { controls: { exclude: ['type', 'iconPosition', 'labelWidth'] }, }; + +export const SeveralErrors = () => { + const [inputValues, setInputValues] = React.useState({ + first: '', + second: '', + third: '', + }); + const [hasErrors, setHasErrors] = React.useState({ + first: true, + second: true, + third: true, + }); + + const firstInputRef = React.useRef(); + const secondInputRef = React.useRef(); + const thirdInputRef = React.useRef(); + + const submit = () => { + setHasErrors({ + first: false, + second: false, + third: false, + }); + + if (!inputValues.third) { + setHasErrors(prev => ({ ...prev, third: true })); + thirdInputRef.current.focus(); + } + + if (!inputValues.second) { + setHasErrors(prev => ({ ...prev, second: true })); + secondInputRef.current.focus(); + } + + if (!inputValues.first) { + setHasErrors(prev => ({ ...prev, first: true })); + firstInputRef.current.focus(); + } + }; + + const reset = () => { + setHasErrors({ + first: false, + second: false, + third: false, + }); + setInputValues({ + first: '', + second: '', + third: '', + }); + + firstInputRef.current.focus(); + }; + + return ( + <> + + setInputValues(prev => ({ ...prev, first: event.target.value })) + } + required + value={inputValues.first} + ref={firstInputRef} + /> +
+ + setInputValues(prev => ({ ...prev, second: event.target.value })) + } + required + value={inputValues.second} + ref={secondInputRef} + /> +
+ + setInputValues(prev => ({ ...prev, third: event.target.value })) + } + required + value={inputValues.third} + ref={thirdInputRef} + /> +
+ + + + + + ); +}; diff --git a/website/react-magma-docs/src/pages/api/input.mdx b/website/react-magma-docs/src/pages/api/input.mdx index efb512104..e88b5895b 100644 --- a/website/react-magma-docs/src/pages/api/input.mdx +++ b/website/react-magma-docs/src/pages/api/input.mdx @@ -170,6 +170,8 @@ export function Example() { If an input has an `errorMessage`, the input will be styled to highlight it's error state and the error message will appear below the input field. If an error message is present, it will replace the helper text. Can be a node or a string. +For best practices, use a `ref` on the input to enhance usability when handling an `errorMessage`. + The `required` prop can be used to indicate when a field is required. It is also important to indicate to the user whenever a field is required. While React Magma provides the error styling, it is up to the consumer app to handle the validation. @@ -180,10 +182,12 @@ import { Input, Button, ButtonGroup, Spacer } from 'react-magma-dom'; export function Example() { const [hasError, setHasError] = React.useState(false); const [nameValue, setNameValue] = React.useState(''); + const inputRef = React.useRef(); function submit() { if (nameValue === '') { setHasError(true); + inputRef.current.focus(); } else { setHasError(false); } @@ -192,6 +196,7 @@ export function Example() { function reset() { setHasError(false); setNameValue(''); + inputRef.current.focus(); } return ( @@ -203,6 +208,7 @@ export function Example() { onChange={event => setNameValue(event.target.value)} required value={nameValue} + ref={inputRef} /> @@ -396,6 +402,7 @@ export function Example() { Some inputs require more content next to the form field, such as a help link to provide a bit of explanation to the user. If you pass children to the input field, it will render immediately after the input. the placement of the content will vary based on the label's position: + - When the label is positioned at the top, the help link will appear at the top right of the input. - When the label is positioned on the left, the help link will appear to the right of the input. - When the label is visually hidden, the help link will appear to the right of the input. @@ -439,7 +446,11 @@ export function Example() { />
- + Date: Wed, 20 Nov 2024 11:26:10 +0100 Subject: [PATCH 2/7] fix(Input): update form example (basic usage) --- .../src/components/AlertBase/index.tsx | 6 +- .../react-magma-docs/src/pages/api/form.mdx | 115 +++++++++++++++++- 2 files changed, 115 insertions(+), 6 deletions(-) diff --git a/packages/react-magma-dom/src/components/AlertBase/index.tsx b/packages/react-magma-dom/src/components/AlertBase/index.tsx index dcae22b7c..295d8d57d 100644 --- a/packages/react-magma-dom/src/components/AlertBase/index.tsx +++ b/packages/react-magma-dom/src/components/AlertBase/index.tsx @@ -379,6 +379,10 @@ const DismissButton = styled(IconButton, { shouldForwardProp })<{ } `; +const AlertSpan = styled.span` + white-space: pre-line; +` + function renderIcon(variant = 'info', isToast?: boolean, theme?: any) { const Icon = VARIANT_ICON[variant]; @@ -479,7 +483,7 @@ export const AlertBase = React.forwardRef( isDismissible={isDismissible} theme={theme} > - {children} + {children} {additionalContent && ( {additionalContent} diff --git a/website/react-magma-docs/src/pages/api/form.mdx b/website/react-magma-docs/src/pages/api/form.mdx index 908b2c9ef..03fc5cded 100644 --- a/website/react-magma-docs/src/pages/api/form.mdx +++ b/website/react-magma-docs/src/pages/api/form.mdx @@ -29,23 +29,128 @@ import { } from 'react-magma-dom'; export function Example() { + const [state, setState] = React.useState({ + firstName: '', + lastName: '', + email: '', + }); + + const [errors, setErrors] = React.useState({ + firstName: true, + lastName: true, + email: true, + }); + + const resetErrors = () => { + setErrors({ + firstName: false, + lastName: false, + email: false, + }); + }; + + const onSubmit = event => { + event.preventDefault(); + resetErrors(); + + if (!state.firstName) { + setErrors(prevErrors => ({ ...prevErrors, firstName: true })); + } + + if (!state.lastName) { + setErrors(prevErrors => ({ ...prevErrors, lastName: true })); + } + + if (!state.email) { + setErrors(prevErrors => ({ ...prevErrors, email: true })); + } + }; + + const cancel = () => { + setState({ + firstName: '', + lastName: '', + email: '', + }); + setErrors({ + firstName: true, + lastName: true, + email: true, + }); + }; + + const errorMessage = React.useMemo(() => { + let message = ''; + + for (const error in errors) { + if (errors[error]) { + switch (error) { + case 'firstName': + message += '- First Name field may not be blank\n'; + break; + case 'lastName': + message += '- Last Name field may not be blank\n'; + break; + case 'email': + message += '- Email field may not be blank\n'; + break; + default: + return; + } + } + } + + return message; + }, [errors]); + return (
alert('form submitted')} + onSubmit={onSubmit} header="Form Heading" description="Some Form Description" - errorMessage="Some Form Error" + errorMessage={errorMessage} actions={ - + } > <> - + + setState(prevState => ({ + ...prevState, + firstName: event.target.value, + })) + } + /> + + + setState(prevState => ({ + ...prevState, + lastName: event.target.value, + })) + } + /> - + + setState(prevState => ({ + ...prevState, + email: event.target.value, + })) + } + /> From e1a2ec22323a6fc07674f986ff356733ce98f82c Mon Sep 17 00:00:00 2001 From: Nikita Orliak Date: Fri, 22 Nov 2024 00:46:38 +0100 Subject: [PATCH 3/7] fix(Input): resolve conflicts, fix comments --- .../Alert/__snapshots__/Alert.test.js.snap | 48 +++++++++- .../src/components/Input/Input.stories.tsx | 94 ++++++++++--------- .../react-magma-docs/src/pages/api/input.mdx | 5 +- 3 files changed, 97 insertions(+), 50 deletions(-) diff --git a/packages/react-magma-dom/src/components/Alert/__snapshots__/Alert.test.js.snap b/packages/react-magma-dom/src/components/Alert/__snapshots__/Alert.test.js.snap index 31bb651f6..c50f9e412 100644 --- a/packages/react-magma-dom/src/components/Alert/__snapshots__/Alert.test.js.snap +++ b/packages/react-magma-dom/src/components/Alert/__snapshots__/Alert.test.js.snap @@ -389,6 +389,10 @@ exports[`Alert Variants should render an alert with danger variant 1`] = ` } } +.emotion-8 { + white-space: pre-line; +} + .emotion-0 { -webkit-align-items: stretch; -webkit-box-align: stretch; @@ -492,6 +496,10 @@ exports[`Alert Variants should render an alert with danger variant 1`] = ` } } +.emotion-8 { + white-space: pre-line; +} +
- + Test Alert Text
@@ -634,6 +644,10 @@ exports[`Alert Variants should render an alert with info variant 1`] = ` } } +.emotion-8 { + white-space: pre-line; +} + .emotion-0 { -webkit-align-items: stretch; -webkit-box-align: stretch; @@ -737,6 +751,10 @@ exports[`Alert Variants should render an alert with info variant 1`] = ` } } +.emotion-8 { + white-space: pre-line; +} +
- + Test Alert Text
@@ -879,6 +899,10 @@ exports[`Alert Variants should render an alert with warning variant 1`] = ` } } +.emotion-8 { + white-space: pre-line; +} + .emotion-0 { -webkit-align-items: stretch; -webkit-box-align: stretch; @@ -982,6 +1006,10 @@ exports[`Alert Variants should render an alert with warning variant 1`] = ` } } +.emotion-8 { + white-space: pre-line; +} +
- + Test Alert Text
@@ -1124,6 +1154,10 @@ exports[`Alert should render an alert with default variant 1`] = ` } } +.emotion-8 { + white-space: pre-line; +} + .emotion-0 { -webkit-align-items: stretch; -webkit-box-align: stretch; @@ -1227,6 +1261,10 @@ exports[`Alert should render an alert with default variant 1`] = ` } } +.emotion-8 { + white-space: pre-line; +} +
- + Test Alert Text
diff --git a/packages/react-magma-dom/src/components/Input/Input.stories.tsx b/packages/react-magma-dom/src/components/Input/Input.stories.tsx index e4652fad1..694d489d2 100644 --- a/packages/react-magma-dom/src/components/Input/Input.stories.tsx +++ b/packages/react-magma-dom/src/components/Input/Input.stories.tsx @@ -8,6 +8,7 @@ import { IconButton } from '../IconButton'; import { InputIconPosition, InputSize, InputType } from '../InputBase'; import { LabelPosition } from '../Label'; import { Tooltip } from '../Tooltip'; +import { Spacer } from '../Spacer'; import { ButtonGroup } from '../ButtonGroup'; const Template: Story = args => ( @@ -198,7 +199,12 @@ export const HelpLink = args => { /> - + + { const [inputValues, setInputValues] = React.useState({ - first: '', - second: '', - third: '', + firstName: '', + lastName: '', + emailAddress: '', }); const [hasErrors, setHasErrors] = React.useState({ - first: true, - second: true, - third: true, + firstName: true, + lastName: true, + emailAddress: true, }); - const firstInputRef = React.useRef(); - const secondInputRef = React.useRef(); - const thirdInputRef = React.useRef(); + const firstNameInputRef = React.useRef(); + const lastNameInputRef = React.useRef(); + const emailAddressInputRef = React.useRef(); const submit = () => { setHasErrors({ - first: false, - second: false, - third: false, + firstName: false, + lastName: false, + emailAddress: false, }); - if (!inputValues.third) { - setHasErrors(prev => ({ ...prev, third: true })); - thirdInputRef.current.focus(); + if (!inputValues.emailAddress) { + setHasErrors(prev => ({ ...prev, emailAddress: true })); + emailAddressInputRef.current.focus(); } - if (!inputValues.second) { - setHasErrors(prev => ({ ...prev, second: true })); - secondInputRef.current.focus(); + if (!inputValues.lastName) { + setHasErrors(prev => ({ ...prev, lastName: true })); + lastNameInputRef.current.focus(); } - if (!inputValues.first) { - setHasErrors(prev => ({ ...prev, first: true })); - firstInputRef.current.focus(); + if (!inputValues.firstName) { + setHasErrors(prev => ({ ...prev, firstName: true })); + firstNameInputRef.current.focus(); } }; const reset = () => { setHasErrors({ - first: false, - second: false, - third: false, + firstName: false, + lastName: false, + emailAddress: false, }); setInputValues({ - first: '', - second: '', - third: '', + firstName: '', + lastName: '', + emailAddress: '', }); - firstInputRef.current.focus(); + firstNameInputRef.current.focus(); }; return ( <> - setInputValues(prev => ({ ...prev, first: event.target.value })) + setInputValues(prev => ({ ...prev, firstName: event.target.value })) } required - value={inputValues.first} - ref={firstInputRef} + value={inputValues.firstName} + ref={firstNameInputRef} />
- setInputValues(prev => ({ ...prev, second: event.target.value })) + setInputValues(prev => ({ ...prev, lastName: event.target.value })) } required - value={inputValues.second} - ref={secondInputRef} + value={inputValues.lastName} + ref={lastNameInputRef} />
- setInputValues(prev => ({ ...prev, third: event.target.value })) + setInputValues(prev => ({ ...prev, emailAddress: event.target.value })) } required - value={inputValues.third} - ref={thirdInputRef} + value={inputValues.emailAddress} + ref={emailAddressInputRef} />
diff --git a/website/react-magma-docs/src/pages/api/input.mdx b/website/react-magma-docs/src/pages/api/input.mdx index e88b5895b..6972b67bb 100644 --- a/website/react-magma-docs/src/pages/api/input.mdx +++ b/website/react-magma-docs/src/pages/api/input.mdx @@ -172,9 +172,9 @@ If an error message is present, it will replace the helper text. Can be a node o For best practices, use a `ref` on the input to enhance usability when handling an `errorMessage`. -The `required` prop can be used to indicate when a field is required. It is also important to indicate to the user whenever a field is required. +While React Magma provides the error styling, it is up to the consumer app to handle the validation. We recommend using a ref on the input for accessibility. -While React Magma provides the error styling, it is up to the consumer app to handle the validation. +For short forms with an error, clicking submit should bring the focus back to the input with an error. For long forms, we recommend using an alert to combine the errors and focus should be moved to the alert. See example in Form. ```tsx import React from 'react'; @@ -446,6 +446,7 @@ export function Example() { />
+ Date: Fri, 22 Nov 2024 01:28:25 +0100 Subject: [PATCH 4/7] fix(Input): fix md file --- website/react-magma-docs/src/pages/api/input.mdx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/website/react-magma-docs/src/pages/api/input.mdx b/website/react-magma-docs/src/pages/api/input.mdx index 6972b67bb..5e4dc9dd4 100644 --- a/website/react-magma-docs/src/pages/api/input.mdx +++ b/website/react-magma-docs/src/pages/api/input.mdx @@ -170,11 +170,9 @@ export function Example() { If an input has an `errorMessage`, the input will be styled to highlight it's error state and the error message will appear below the input field. If an error message is present, it will replace the helper text. Can be a node or a string. -For best practices, use a `ref` on the input to enhance usability when handling an `errorMessage`. +While React Magma provides the error styling, it is up to the consumer app to handle the validation. We recommend using a `ref` on the input for accessibility. -While React Magma provides the error styling, it is up to the consumer app to handle the validation. We recommend using a ref on the input for accessibility. - -For short forms with an error, clicking submit should bring the focus back to the input with an error. For long forms, we recommend using an alert to combine the errors and focus should be moved to the alert. See example in Form. +For short forms with an error, clicking submit should bring the focus back to the input with an error. For long forms, we recommend using an alert to combine the errors and focus should be moved to the alert. See example in [Form](https://docs-preview-dev--upbeat-sinoussi-f675aa.netlify.app/api/form/). ```tsx import React from 'react'; From b79cc81d9fe18c5fb40587d04846fd651fc1e5e6 Mon Sep 17 00:00:00 2001 From: Nikita Orliak Date: Mon, 25 Nov 2024 10:22:47 +0100 Subject: [PATCH 5/7] Small fixes --- .../src/components/Input/Input.stories.tsx | 6 +++--- website/react-magma-docs/src/pages/api/form.mdx | 12 ++++++------ website/react-magma-docs/src/pages/api/input.mdx | 2 ++ 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/react-magma-dom/src/components/Input/Input.stories.tsx b/packages/react-magma-dom/src/components/Input/Input.stories.tsx index 12526fb57..a9ac657dc 100644 --- a/packages/react-magma-dom/src/components/Input/Input.stories.tsx +++ b/packages/react-magma-dom/src/components/Input/Input.stories.tsx @@ -335,9 +335,9 @@ export const SeveralErrors = () => { emailAddress: '', }); const [hasErrors, setHasErrors] = React.useState({ - firstName: true, - lastName: true, - emailAddress: true, + firstName: false, + lastName: false, + emailAddress: false, }); const firstNameInputRef = React.useRef(); diff --git a/website/react-magma-docs/src/pages/api/form.mdx b/website/react-magma-docs/src/pages/api/form.mdx index 950ff832f..5c50a299c 100644 --- a/website/react-magma-docs/src/pages/api/form.mdx +++ b/website/react-magma-docs/src/pages/api/form.mdx @@ -36,9 +36,9 @@ export function Example() { }); const [errors, setErrors] = React.useState({ - firstName: true, - lastName: true, - email: true, + firstName: false, + lastName: false, + email: false, }); const resetErrors = () => { @@ -73,9 +73,9 @@ export function Example() { email: '', }); setErrors({ - firstName: true, - lastName: true, - email: true, + firstName: false, + lastName: false, + email: false, }); }; diff --git a/website/react-magma-docs/src/pages/api/input.mdx b/website/react-magma-docs/src/pages/api/input.mdx index 1bd30650e..bca56631b 100644 --- a/website/react-magma-docs/src/pages/api/input.mdx +++ b/website/react-magma-docs/src/pages/api/input.mdx @@ -170,6 +170,8 @@ export function Example() { If an input has an `errorMessage`, the input will be styled to highlight it's error state and the error message will appear below the input field. If an error message is present, it will replace the helper text. Can be a node or a string. +The `required` prop can be used to indicate when a field is required. It is also important to indicate to the user whenever a field is required. + While React Magma provides the error styling, it is up to the consumer app to handle the validation. We recommend using a `ref` on the input for accessibility. For short forms with an error, clicking submit should bring the focus back to the input with an error. For long forms, we recommend using an alert to combine the errors and focus should be moved to the alert. See example in [Form](https://docs-preview-dev--upbeat-sinoussi-f675aa.netlify.app/api/form/). From 36959814e44795c5ccb35d67d76925a9b75da16a Mon Sep 17 00:00:00 2001 From: Nikita Orliak Date: Tue, 26 Nov 2024 10:18:21 +0100 Subject: [PATCH 6/7] fix(Input): add announce to form error message --- packages/react-magma-dom/src/components/Form/index.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/react-magma-dom/src/components/Form/index.tsx b/packages/react-magma-dom/src/components/Form/index.tsx index 9544c18a4..c77c6e794 100644 --- a/packages/react-magma-dom/src/components/Form/index.tsx +++ b/packages/react-magma-dom/src/components/Form/index.tsx @@ -8,6 +8,7 @@ import { ThemeInterface } from '../../theme/magma'; import { InverseContext, useIsInverse } from '../../inverse'; import styled from '@emotion/styled'; import { TypographyContextVariant, TypographyVisualStyle } from '../Typography'; +import { Announce } from '../Announce'; /** * @children required @@ -108,7 +109,9 @@ export const Form = React.forwardRef( {description && {description}} {errorMessage && ( - {errorMessage} + + {errorMessage} + )}
{children}
{actions} From f8b5048d6b8d633167c6510fe6b2b8ab0a4a0f68 Mon Sep 17 00:00:00 2001 From: Nikita Orliak Date: Mon, 9 Dec 2024 15:36:14 +0100 Subject: [PATCH 7/7] fix(Input): change link to `Form` in the mdx file --- website/react-magma-docs/src/pages/api/input.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/react-magma-docs/src/pages/api/input.mdx b/website/react-magma-docs/src/pages/api/input.mdx index bca56631b..0d688acd5 100644 --- a/website/react-magma-docs/src/pages/api/input.mdx +++ b/website/react-magma-docs/src/pages/api/input.mdx @@ -174,7 +174,7 @@ The `required` prop can be used to indicate when a field is required. It is also While React Magma provides the error styling, it is up to the consumer app to handle the validation. We recommend using a `ref` on the input for accessibility. -For short forms with an error, clicking submit should bring the focus back to the input with an error. For long forms, we recommend using an alert to combine the errors and focus should be moved to the alert. See example in [Form](https://docs-preview-dev--upbeat-sinoussi-f675aa.netlify.app/api/form/). +For short forms with an error, clicking submit should bring the focus back to the input with an error. For long forms, we recommend using an alert to combine the errors and focus should be moved to the alert. See example in Form. ```tsx import React from 'react';