Skip to content

Commit

Permalink
fix(forms): add message id to input aria-describedby attribute (#1389)
Browse files Browse the repository at this point in the history
  • Loading branch information
Francois-Esquire authored Aug 2, 2022
1 parent 84f326f commit 56b157e
Show file tree
Hide file tree
Showing 9 changed files with 85 additions and 41 deletions.
16 changes: 8 additions & 8 deletions packages/forms/.size-snapshot.json
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
{
"index.cjs.js": {
"bundled": 156277,
"minified": 107076,
"gzipped": 18758
"bundled": 156963,
"minified": 107350,
"gzipped": 18826
},
"index.esm.js": {
"bundled": 147057,
"minified": 98835,
"gzipped": 18335,
"bundled": 147731,
"minified": 99097,
"gzipped": 18400,
"treeshaked": {
"rollup": {
"code": 80737,
"code": 80971,
"import_statements": 745
},
"webpack": {
"code": 88465
"code": 88734
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/forms/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"sideEffects": false,
"types": "dist/typings/index.d.ts",
"dependencies": {
"@zendeskgarden/container-field": "^1.3.6",
"@zendeskgarden/container-field": "^2.1.0",
"@zendeskgarden/container-utilities": "^0.7.0",
"lodash.debounce": "^4.0.8",
"polished": "^4.0.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/forms/src/elements/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export const Input = React.forwardRef<HTMLInputElement, IInputProps>(
}

if (fieldContext) {
combinedProps = fieldContext.getInputProps(combinedProps, { isDescribed: true });
combinedProps = fieldContext.getInputProps(combinedProps);
}

return <StyledTextInput {...(combinedProps as any)} />;
Expand Down
22 changes: 21 additions & 1 deletion packages/forms/src/elements/common/Field.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import React from 'react';
import { render, act } from 'garden-test-utils';
import { Field, Label, Input, Hint } from '../..';
import { Field, Label, Input, Hint, Message } from '../..';

describe('Field', () => {
it('passes ref to underlying DOM element', () => {
Expand Down Expand Up @@ -69,4 +69,24 @@ describe('Field', () => {

expect(getByTestId('input')).not.toHaveAttribute('aria-describedby', expect.any(String));
});

it('renders correct aria attributes when Message component is present', () => {
const ExampleField = ({ showMessage = false }) => (
<Field>
<Label>Label</Label>
{showMessage ? <Message>Message</Message> : null}
<Input data-test-id="input" />
</Field>
);

const { getByTestId, rerender } = render(<ExampleField showMessage />);

expect(getByTestId('input')).toHaveAttribute('aria-describedby', expect.any(String));

act(() => {
rerender(<ExampleField showMessage={false} />);
});

expect(getByTestId('input')).not.toHaveAttribute('aria-describedby', expect.any(String));
});
});
28 changes: 19 additions & 9 deletions packages/forms/src/elements/common/Field.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,26 +24,36 @@ export interface IFieldProps extends HTMLAttributes<HTMLDivElement> {
export const Field = React.forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
(props, ref) => {
const [hasHint, setHasHint] = useState(false);
const [hasMessage, setHasMessage] = useState(false);
const [isLabelActive, setIsLabelActive] = useState(false);
const [isLabelHovered, setIsLabelHovered] = useState(false);
const multiThumbRangeRef = useRef<HTMLDivElement>(null);
const getMessageProps = (messageProps: any) => ({ role: 'alert', ...messageProps });
const { getInputProps, ...propGetters } = useField(props.id);
const { getInputProps, getMessageProps, ...propGetters } = useField(props.id);
const fieldProps = useMemo(
() => ({
...propGetters,
getMessageProps,
getInputProps: (options: any, describeOptions: any = {}) =>
getInputProps(options, { ...describeOptions, isDescribed: hasHint, hasMessage }),
getMessageProps: (options: any) => getMessageProps({ role: 'alert', ...options }),
isLabelActive,
setIsLabelActive,
isLabelHovered,
setIsLabelHovered,
multiThumbRangeRef,
getInputProps: (options: any, describeOptions: any) =>
getInputProps(options, { ...describeOptions, isDescribed: hasHint }),
setHint: (hintPresent: boolean) => setHasHint(hintPresent),
hasHint
hasHint,
setHasHint,
hasMessage,
setHasMessage,
multiThumbRangeRef
}),
[propGetters, isLabelActive, isLabelHovered, hasHint, getInputProps]
[
propGetters,
getInputProps,
getMessageProps,
isLabelActive,
isLabelHovered,
hasHint,
hasMessage
]
);

return (
Expand Down
16 changes: 8 additions & 8 deletions packages/forms/src/elements/common/Hint.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,20 @@ import { StyledHint, StyledCheckHint, StyledRadioHint, StyledToggleHint } from '
*/
export const Hint = React.forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
(props, ref) => {
const fieldContext = useFieldContext();
const { hasHint, setHasHint, getHintProps } = useFieldContext() || {};
const type = useInputContext();

useEffect(() => {
if (fieldContext && !fieldContext.hasHint) {
fieldContext.setHint(true);
if (!hasHint) {
setHasHint!(true);
}

return () => {
if (fieldContext && fieldContext.hasHint) {
fieldContext.setHint(false);
if (hasHint) {
setHasHint!(false);
}
};
}, [fieldContext]);
}, [hasHint, setHasHint]);

let HintComponent;

Expand All @@ -44,8 +44,8 @@ export const Hint = React.forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivEleme

let combinedProps = props;

if (fieldContext) {
combinedProps = fieldContext.getHintProps(combinedProps);
if (getHintProps) {
combinedProps = getHintProps(combinedProps);
}

return <HintComponent ref={ref} {...(combinedProps as any)} />;
Expand Down
20 changes: 16 additions & 4 deletions packages/forms/src/elements/common/Message.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* found at http://www.apache.org/licenses/LICENSE-2.0.
*/

import React from 'react';
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { useText } from '@zendeskgarden/react-theming';

Expand All @@ -25,9 +25,21 @@ import {
*/
export const Message = React.forwardRef<HTMLDivElement, IMessageProps>(
({ validation, validationLabel, children, ...props }, ref) => {
const fieldContext = useFieldContext();
const { hasMessage, setHasMessage, getMessageProps } = useFieldContext() || {};
const type = useInputContext();

useEffect(() => {
if (!hasMessage) {
setHasMessage!(true);
}

return () => {
if (hasMessage) {
setHasMessage!(false);
}
};
}, [hasMessage, setHasMessage]);

let MessageComponent;

if (type === 'checkbox') {
Expand All @@ -42,8 +54,8 @@ export const Message = React.forwardRef<HTMLDivElement, IMessageProps>(

let combinedProps = { validation, validationLabel, ...props };

if (fieldContext) {
combinedProps = fieldContext.getMessageProps(combinedProps);
if (getMessageProps) {
combinedProps = getMessageProps(combinedProps);
}

const ariaLabel = useText(Message, combinedProps, 'validationLabel', validation as string);
Expand Down
4 changes: 3 additions & 1 deletion packages/forms/src/utils/useFieldContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ interface IFieldContext extends IUseFieldPropGetters {
setIsLabelHovered: (isLabelHovered: boolean) => void;
setIsLabelActive: (isLabelActive: boolean) => void;
multiThumbRangeRef: MutableRefObject<HTMLDivElement | null>;
setHint: (hintPresent: boolean) => void;
setHasHint: (hintPresent: boolean) => void;
hasHint: boolean;
setHasMessage: (messagePresent: boolean) => void;
hasMessage: boolean;
}

export const FieldContext = createContext<IFieldContext | undefined>(undefined);
Expand Down
16 changes: 8 additions & 8 deletions packages/forms/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.172.tgz#aad774c28e7bfd7a67de25408e03ee5a8c3d028a"
integrity sha512-/BHF5HAx3em7/KkzVKm3LrsD6HZAXuXO1AJZQ3cRRBZj4oHZDviWPYu0aEplAqDFNHZPW6d3G7KN+ONcCCC7pw==

"@zendeskgarden/container-field@^1.3.6":
version "1.3.8"
resolved "https://registry.yarnpkg.com/@zendeskgarden/container-field/-/container-field-1.3.8.tgz#b92572527a45d3b9c58c8951f47996d0ea93376c"
integrity sha512-WWvyjNsCzbE1br3VEgQEiyTwrmsBP/yi+mq6QkUu8+ArtJ46tbX+2FcD4un39nnqrVfFyNIdOoHertt2rIHX5w==
"@zendeskgarden/container-field@^2.1.0":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@zendeskgarden/container-field/-/container-field-2.1.0.tgz#ed1e0fb8c320dfafb78e000eea5dfe197b980cf4"
integrity sha512-kqfOIsIUeWATva5DBo2nVpbtrPgRdnyOu1bOeZWH9B7tdhNIIStWnwrcj6T4E5L27saZtEtmyH3LEPKJjwDSxA==
dependencies:
"@babel/runtime" "^7.8.4"
react-uid "^2.2.0"
Expand All @@ -60,10 +60,10 @@
"@babel/runtime" "^7.8.4"
"@reach/auto-id" "^0.16.0"

"@zendeskgarden/react-theming@^8.52.0":
version "8.52.0"
resolved "https://registry.yarnpkg.com/@zendeskgarden/react-theming/-/react-theming-8.52.0.tgz#88d6f8c24515828ebfe313cae76deaa9407bb513"
integrity sha512-LMQr71DPL3hhNR6mRY61JMerXjqLpJp7TZHLe0f+FXeNeA2RI9IXQYUPkTHWDW0Pvc1dWCPd0P9L9AnvYRjyig==
"@zendeskgarden/react-theming@^8.54.0":
version "8.54.0"
resolved "https://registry.yarnpkg.com/@zendeskgarden/react-theming/-/react-theming-8.54.0.tgz#104f0b67f1cb3ce2132a7f065494eabab1ea0f07"
integrity sha512-fkS/dFb+GGJKX8k3m/TjuXPVbf2zhusY37ZN3dOKisCA9ToviCAF0Go+TG+Qzv+//cT1gkdKb76RIFwRxHRbfQ==
dependencies:
"@zendeskgarden/container-focusvisible" "^0.4.6"
"@zendeskgarden/container-utilities" "^0.7.0"
Expand Down

0 comments on commit 56b157e

Please sign in to comment.