Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: add area-describedby to components that use helperText #13371

Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
394f3ed
fix(MultiSelect): add area-describedby to button
francinelucca Mar 17, 2023
4d1d6f5
fix(MultiSelect): format
francinelucca Mar 20, 2023
2a9cc07
fix(dropdown): add aria-describedby mapped to helperText
francinelucca Mar 20, 2023
c0a09df
fix(combobox): add aria-describedby mapped to helperText
francinelucca Mar 20, 2023
fd8eaa7
fix: format
francinelucca Mar 20, 2023
ecc07e8
fix(datepicker-input): add aria-describedby mapped to helperText
francinelucca Mar 20, 2023
bd5869f
fix(number-input): add aria-describedby mapped to helperText
francinelucca Mar 20, 2023
3fd200d
fix(select): add aria-describedby mapped to helperText
francinelucca Mar 20, 2023
7c28a3c
fix(text-area): add aria-describedby mapped to helperText
francinelucca Mar 21, 2023
3537b5e
fix(progress-bar): add aria-describedby mapped to helperText
francinelucca Mar 21, 2023
2b82838
fix(controlled-password-input): add aria-describedby mappd to helperText
francinelucca Mar 21, 2023
e66d4c7
fix(password-input): add aria-describedby mappd to helperText
francinelucca Mar 21, 2023
324f5ac
fix(datepicker): add helperText prop to playground story
francinelucca Mar 22, 2023
34fab10
Merge branch 'main' into 12722-a11y-dropdown-does-not-use-aria-descri…
francinelucca Mar 22, 2023
98bb46d
Merge branch 'main' of github.com:carbon-design-system/carbon into 12…
francinelucca Mar 23, 2023
2dfbaa0
fix(radio-button-group): add helperText prop to playground story
francinelucca Mar 23, 2023
e225c3c
test(radi-button): always display helpertext
francinelucca Mar 23, 2023
63b8832
Merge branch 'main' into 12722-a11y-dropdown-does-not-use-aria-descri…
francinelucca Mar 27, 2023
9669006
Merge branch 'main' of github.com:carbon-design-system/carbon into 12…
francinelucca Mar 28, 2023
0fc53f9
Merge branch '12722-a11y-dropdown-does-not-use-aria-describedby-to-li…
francinelucca Mar 28, 2023
9005b73
fix: format
francinelucca Mar 28, 2023
27caad6
Merge branch 'main' of github.com:carbon-design-system/carbon into 12…
francinelucca Mar 30, 2023
81d53a6
Merge branch 'main' into 12722-a11y-dropdown-does-not-use-aria-descri…
francinelucca Mar 31, 2023
1b8a733
fix(RadioButton): revert show helperText fix
francinelucca Mar 31, 2023
4e92044
Merge branch '12722-a11y-dropdown-does-not-use-aria-describedby-to-li…
francinelucca Mar 31, 2023
08a3e93
Merge branch 'main' into 12722-a11y-dropdown-does-not-use-aria-descri…
francinelucca Mar 31, 2023
a08f8f9
Merge branch 'main' of github.com:carbon-design-system/carbon into 12…
francinelucca Mar 31, 2023
4ed0bf8
fix(PasswordInput): cast hasHelper to a boolean type
francinelucca Mar 31, 2023
5923808
Merge branch '12722-a11y-dropdown-does-not-use-aria-describedby-to-li…
francinelucca Mar 31, 2023
51ec77d
Merge branch 'main' of github.com:carbon-design-system/carbon into 12…
francinelucca Apr 4, 2023
c418a1a
fix: format
francinelucca Apr 4, 2023
641495c
Merge branch 'main' into 12722-a11y-dropdown-does-not-use-aria-descri…
kodiakhq[bot] Apr 7, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/react/src/components/ComboBox/ComboBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,11 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
{...readOnlyEventHandlers}
readOnly={readOnly}
ref={mergeRefs(textInput, ref)}
aria-describedby={
helperText && !invalid && !warn && !isFluid
? comboBoxHelperId
: undefined
}
/>
{invalid && (
<WarningFilled
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -318,4 +318,10 @@ Playground.argTypes = {
category: 'DatePickerInput',
},
},
helperText: {
control: { type: 'text' },
table: {
category: 'DatePickerInput',
},
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import PropTypes from 'prop-types';
import React, { useContext } from 'react';
import { usePrefix } from '../../internal/usePrefix';
import { FormContext } from '../FluidForm';
import setupGetInstanceId from '../../tools/setupGetInstanceId';

const getInstanceId = setupGetInstanceId();

const DatePickerInput = React.forwardRef(function DatePickerInput(props, ref) {
const {
Expand All @@ -34,6 +37,7 @@ const DatePickerInput = React.forwardRef(function DatePickerInput(props, ref) {
} = props;
const prefix = usePrefix();
const { isFluid } = useContext(FormContext);
const datePickerInputInstanceId = getInstanceId();
const datePickerInputProps = {
id,
onChange: (event) => {
Expand Down Expand Up @@ -73,12 +77,17 @@ const DatePickerInput = React.forwardRef(function DatePickerInput(props, ref) {
[`${prefix}--date-picker--fluid--warn`]: isFluid && warn,
});

const datePickerInputHelperId = !helperText
? undefined
: `detepicker-input-helper-text-${datePickerInputInstanceId}`;

const inputProps = {
...rest,
...datePickerInputProps,
className: inputClasses,
disabled,
ref,
['aria-describedby']: helperText ? datePickerInputHelperId : undefined,
};
if (invalid) {
inputProps['data-invalid'] = true;
Expand Down Expand Up @@ -113,7 +122,11 @@ const DatePickerInput = React.forwardRef(function DatePickerInput(props, ref) {
<div className={`${prefix}--form-requirement`}>{warnText}</div>
</>
)}
{helperText && <div className={helperTextClasses}>{helperText}</div>}
{helperText && (
<div id={datePickerInputHelperId} className={helperTextClasses}>
{helperText}
</div>
)}
</div>
);
});
Expand Down
15 changes: 14 additions & 1 deletion packages/react/src/components/Dropdown/Dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ import { useFeatureFlag } from '../FeatureFlags';
import { usePrefix } from '../../internal/usePrefix';
import { FormContext } from '../FluidForm';
import { ReactAttr } from '../../types/common';
import setupGetInstanceId from '../../tools/setupGetInstanceId';

const getInstanceId = setupGetInstanceId();

const defaultItemToString = <ItemType,>(item?: ItemType): string => {
if (typeof item === 'string') {
Expand Down Expand Up @@ -248,6 +251,7 @@ const Dropdown = React.forwardRef(
initialSelectedItem,
onSelectedItemChange,
};
const { current: dropdownInstanceId } = useRef(getInstanceId());

// only set selectedItem if the prop is defined. Setting if it is undefined
// will overwrite default selected items from useSelect
Expand Down Expand Up @@ -311,12 +315,18 @@ const Dropdown = React.forwardRef(
}
);

const helperId = !helperText
? undefined
: `dropdown-helper-text-${dropdownInstanceId}`;

// needs to be Capitalized for react to render it correctly
const ItemToElement = itemToElement;
const toggleButtonProps = getToggleButtonProps();
const helper =
helperText && !isFluid ? (
<div className={helperClasses}>{helperText}</div>
<div id={helperId} className={helperClasses}>
{helperText}
</div>
) : null;

function onSelectedItemChange({
Expand Down Expand Up @@ -389,6 +399,9 @@ const Dropdown = React.forwardRef(
className={`${prefix}--list-box__field`}
disabled={disabled}
aria-disabled={readOnly ? true : undefined} // aria-disabled to remain focusable
aria-describedby={
!inline && !invalid && !warn && helper ? helperId : undefined
}
title={
selectedItem && itemToString !== undefined
? itemToString(selectedItem)
Expand Down
3 changes: 3 additions & 0 deletions packages/react/src/components/MultiSelect/MultiSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,9 @@ const MultiSelect = React.forwardRef(function MultiSelect<ItemType>(
className={`${prefix}--list-box__field`}
disabled={disabled}
aria-disabled={disabled || readOnly}
aria-describedby={
!inline && !invalid && !warn && helperText ? helperId : undefined
}
{...toggleButtonProps}
ref={mergedRef}
onKeyDown={onKeyDown}
Expand Down
19 changes: 16 additions & 3 deletions packages/react/src/components/NumberInput/NumberInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,9 @@ const NumberInput = React.forwardRef(function NumberInput(
if (normalizedProps.warn) {
ariaDescribedBy = normalizedProps.warnId;
}
if (!normalizedProps.validation) {
ariaDescribedBy = helperText ? normalizedProps.helperId : undefined;
}

function handleOnChange(event) {
if (disabled) {
Expand Down Expand Up @@ -441,7 +444,11 @@ const NumberInput = React.forwardRef(function NumberInput(
{normalizedProps.validation ? (
normalizedProps.validation
) : (
<HelperText disabled={disabled} description={helperText} />
<HelperText
id={normalizedProps.helperId}
disabled={disabled}
description={helperText}
/>
)}
</div>
</div>
Expand Down Expand Up @@ -619,24 +626,30 @@ Label.propTypes = {
};

interface HelperTextProps {
id?: string;
description?: ReactNode;
disabled?: boolean;
}
function HelperText({ disabled, description }: HelperTextProps) {
function HelperText({ disabled, description, id }: HelperTextProps) {
const prefix = usePrefix();
const className = cx(`${prefix}--form__helper-text`, {
[`${prefix}--form__helper-text--disabled`]: disabled,
});

if (description) {
return <div className={className}>{description}</div>;
return (
<div id={id} className={className}>
{description}
</div>
);
}
return null;
}

HelperText.propTypes = {
description: PropTypes.node,
disabled: PropTypes.bool,
id: PropTypes.string,
};

/**
Expand Down
6 changes: 5 additions & 1 deletion packages/react/src/components/ProgressBar/ProgressBar.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ function ProgressBar({
}) {
const labelId = useId('progress-bar');
const helperId = useId('progress-bar-helper');
const helperTextId = useId('progress-bar-helper-text');
const prefix = usePrefix();

const isFinished = status === 'finished';
Expand Down Expand Up @@ -101,13 +102,16 @@ function ProgressBar({
aria-busy={!isFinished}
aria-invalid={isError}
aria-labelledby={labelId}
aria-describedby={helperText ? helperTextId : undefined}
aria-valuemin={!indeterminate ? 0 : null}
aria-valuemax={!indeterminate ? max : null}
aria-valuenow={!indeterminate ? cappedValue : null}>
<div className={`${prefix}--progress-bar__bar`} ref={ref} />
</div>
{helperText && (
<div className={`${prefix}--progress-bar__helper-text`}>
<div
id={helperTextId}
className={`${prefix}--progress-bar__helper-text`}>
{helperText}
<div
className={`${prefix}--visually-hidden`}
Expand Down
16 changes: 15 additions & 1 deletion packages/react/src/components/Select/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import React, {
ForwardedRef,
ReactNode,
useContext,
useRef,
useState,
} from 'react';
import classNames from 'classnames';
Expand All @@ -24,6 +25,9 @@ import deprecate from '../../prop-types/deprecate';
import { useFeatureFlag } from '../FeatureFlags';
import { usePrefix } from '../../internal/usePrefix';
import { FormContext } from '../FluidForm';
import setupGetInstanceId from '../../tools/setupGetInstanceId';

const getInstanceId = setupGetInstanceId();

type ExcludedAttributes = 'size';

Expand Down Expand Up @@ -153,6 +157,7 @@ const Select = React.forwardRef(function Select(
const enabled = useFeatureFlag('enable-v11-release');
const { isFluid } = useContext(FormContext);
const [isFocused, setIsFocused] = useState(false);
const { current: selectInstanceId } = useRef(getInstanceId());

const selectClasses = classNames(
{
Expand Down Expand Up @@ -194,12 +199,21 @@ const Select = React.forwardRef(function Select(
const helperTextClasses = classNames(`${prefix}--form__helper-text`, {
[`${prefix}--form__helper-text--disabled`]: disabled,
});

const helperId = !helperText
? undefined
: `select-helper-text-${selectInstanceId}`;

const helper = helperText ? (
<div className={helperTextClasses}>{helperText}</div>
<div id={helperId} className={helperTextClasses}>
{helperText}
</div>
) : null;
const ariaProps = {};
if (invalid) {
ariaProps['aria-describedby'] = errorId;
} else if (!inline && !isFluid) {
ariaProps['aria-describedby'] = helper ? helperId : undefined;
}

const handleFocus = (evt) => {
Expand Down
22 changes: 20 additions & 2 deletions packages/react/src/components/TextArea/TextArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import { FormContext } from '../FluidForm';
import { useAnnouncer } from '../../internal/useAnnouncer';
import useIsomorphicEffect from '../../internal/useIsomorphicEffect';
import { useMergedRefs } from '../../internal/useMergedRefs';
import setupGetInstanceId from '../../tools/setupGetInstanceId';

const getInstanceId = setupGetInstanceId();

export interface TextAreaProps
extends React.InputHTMLAttributes<HTMLTextAreaElement> {
Expand Down Expand Up @@ -157,6 +160,7 @@ const TextArea = React.forwardRef((props: TextAreaProps, forwardRef) => {
const [textCount, setTextCount] = useState(
defaultValue?.toString().length || value?.toString().length || 0
);
const { current: textAreaInstanceId } = useRef(getInstanceId());

const textareaProps: {
id: TextAreaProps['id'];
Expand Down Expand Up @@ -207,8 +211,14 @@ const TextArea = React.forwardRef((props: TextAreaProps, forwardRef) => {
[`${prefix}--form__helper-text--disabled`]: other.disabled,
});

const helperId = !helperText
? undefined
: `text-area-helper-text-${textAreaInstanceId}`;

const helper = helperText ? (
<div className={helperTextClasses}>{helperText}</div>
<div id={helperId} className={helperTextClasses}>
{helperText}
</div>
) : null;

const errorId = id + '-error-msg';
Expand Down Expand Up @@ -257,14 +267,22 @@ const TextArea = React.forwardRef((props: TextAreaProps, forwardRef) => {
}
}, [other.cols]);

let ariaDescribedBy;

if (invalid) {
ariaDescribedBy = errorId;
} else if (!invalid && !warn && !isFluid && helperText) {
ariaDescribedBy = helperId;
}

const input = (
<textarea
{...other}
{...textareaProps}
placeholder={placeholder}
className={textareaClasses}
aria-invalid={invalid}
aria-describedby={invalid ? errorId : undefined}
aria-describedby={ariaDescribedBy}
disabled={other.disabled}
readOnly={other.readOnly}
ref={ref}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import React from 'react';
import React, { useRef } from 'react';
import classNames from 'classnames';
import PropTypes from 'prop-types';
import { View, ViewOff, WarningFilled } from '@carbon/icons-react';
import { textInputProps } from './util';
import { warning } from '../../internal/warning';
import deprecate from '../../prop-types/deprecate';
import { usePrefix } from '../../internal/usePrefix';
import setupGetInstanceId from '../../tools/setupGetInstanceId';

const getInstanceId = setupGetInstanceId();

let didWarnAboutDeprecation = false;

Expand Down Expand Up @@ -37,6 +40,7 @@ const ControlledPasswordInput = React.forwardRef(
ref
) {
const prefix = usePrefix();
const { current: controlledPasswordInstanceId } = useRef(getInstanceId());

if (__DEV__) {
warning(
Expand Down Expand Up @@ -109,13 +113,20 @@ const ControlledPasswordInput = React.forwardRef(
[`${prefix}--tooltip--align-${tooltipAlignment}`]: tooltipAlignment,
}
);

const helperId = !helperText
? undefined
: `controlled-password-helper-text-${controlledPasswordInstanceId}`;

const input = (
<>
<input
{...textInputProps({
invalid,
sharedTextInputProps,
invalidId: errorId,
hasHelper: !error && helperText,
helperId,
})}
data-toggle-password-visibility={type === 'password'}
/>
Expand All @@ -130,8 +141,11 @@ const ControlledPasswordInput = React.forwardRef(
</button>
</>
);

const helper = helperText ? (
<div className={helperTextClasses}>{helperText}</div>
<div id={helperId} className={helperTextClasses}>
{helperText}
</div>
) : null;

return (
Expand Down
9 changes: 8 additions & 1 deletion packages/react/src/components/TextInput/PasswordInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,9 @@ const PasswordInput = React.forwardRef(function PasswordInput(
</label>
) : null;
const helper = helperText ? (
<div className={helperTextClasses}>{helperText}</div>
<div id={normalizedProps.helperId} className={helperTextClasses}>
{helperText}
</div>
) : null;

const passwordIsVisible = inputType === 'text';
Expand Down Expand Up @@ -156,6 +158,11 @@ const PasswordInput = React.forwardRef(function PasswordInput(
invalidId: normalizedProps.invalidId,
warn: normalizedProps.warn,
warnId: normalizedProps.warnId,
hasHelper:
helperText &&
!isFluid &&
(inline || (!inline && !normalizedProps.validation)),
helperId: normalizedProps.helperId,
})}
disabled={disabled}
data-toggle-password-visibility={inputType === 'password'}
Expand Down
Loading