Skip to content

Commit

Permalink
[pickers] Fix aria-labelledby assignment to dialog (#7608)
Browse files Browse the repository at this point in the history
Signed-off-by: Lukas <[email protected]>
Co-authored-by: Alexandre Fauquette <[email protected]>
  • Loading branch information
LukasTy and alexfauquette authored Jan 26, 2023
1 parent e7d92df commit bcbb19c
Show file tree
Hide file tree
Showing 21 changed files with 231 additions and 31 deletions.
13 changes: 13 additions & 0 deletions docs/data/date-pickers/base-concepts/base-concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,19 @@ There are many components available, each fitting specific use cases. Use the fo

{{"demo": "ComponentExplorerNoSnap.js", "hideToolbar": true}}

## Accessibility

Both `Desktop` and `Mobile` Date and Time Pickers are using `role="dialog"` to display their interactive view parts and thus they should follow [Modal accessibility guidelines](/material-ui/react-modal/#accessibility).
This behavior is automated as much as possible, ensuring that the Date and Time Pickers are accessible in most cases.
A correct `aria-labelledby` value is assigned to the dialog component based on the following rules:

- Use `toolbar` id if the toolbar is visible;
- Use the id of the input label if the toolbar is hidden;

:::info
Make sure to provide an `aria-labelledby` prop to `popper` and/or `mobilePaper` `slotProps` in case you are using Date and Time Pickers component with **hidden toolbar** and **without a label**.
:::

## TypeScript

In order to benefit from the [CSS overrides](/material-ui/customization/theme-components/#global-style-overrides) and [default prop customization](/material-ui/customization/theme-components/#default-props) with the theme, TypeScript users need to import the following types.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ DateRangePickerToolbar.propTypes = {
onRangePositionChange: PropTypes.func.isRequired,
rangePosition: PropTypes.oneOf(['end', 'start']).isRequired,
readOnly: PropTypes.bool,
titleId: PropTypes.string,
/**
* Toolbar date format.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ import {
PickersModalDialog,
InferError,
ExportedBaseToolbarProps,
useLocaleText,
} from '@mui/x-date-pickers/internals';
import useId from '@mui/utils/useId';
import {
MobileRangePickerAdditionalViewProps,
UseMobileRangePickerParams,
Expand All @@ -39,7 +41,7 @@ export const useMobileRangePicker = <

const {
slots,
slotProps,
slotProps: innerSlotProps,
className,
sx,
format,
Expand All @@ -50,6 +52,8 @@ export const useMobileRangePicker = <
} = props;

const [rangePosition, setRangePosition] = React.useState<RangePosition>('start');
const labelId = useId();
const contextLocaleText = useLocaleText();

const {
open,
Expand Down Expand Up @@ -93,7 +97,7 @@ export const useMobileRangePicker = <
InferError<TExternalProps>
> = useSlotProps({
elementType: Field,
externalSlotProps: slotProps?.field,
externalSlotProps: innerSlotProps?.field,
additionalProps: {
...pickerFieldProps,
readOnly: readOnly ?? true,
Expand All @@ -110,10 +114,12 @@ export const useMobileRangePicker = <
...fieldProps.slots,
};

const isToolbarHidden = innerSlotProps?.toolbar?.hidden ?? false;

const slotPropsForField: BaseMultiInputFieldProps<DateRange<TDate>, unknown>['slotProps'] = {
...fieldProps.slotProps,
textField: (ownerState) => {
const externalInputProps = resolveComponentProps(slotProps?.textField, ownerState);
const externalInputProps = resolveComponentProps(innerSlotProps?.textField, ownerState);
const inputPropsPassedByField = resolveComponentProps(
fieldProps.slotProps?.textField,
ownerState,
Expand All @@ -122,6 +128,7 @@ export const useMobileRangePicker = <
ownerState.position === 'start' ? fieldSlotProps.startInput : fieldSlotProps.endInput;

return {
...(isToolbarHidden && { id: `${labelId}-${ownerState.position}` }),
...externalInputProps,
...inputPropsPassedByField,
...inputPropsPassedByPicker,
Expand All @@ -132,7 +139,7 @@ export const useMobileRangePicker = <
};
},
root: (ownerState) => {
const externalRootProps = resolveComponentProps(slotProps?.fieldRoot, ownerState);
const externalRootProps = resolveComponentProps(innerSlotProps?.fieldRoot, ownerState);
const rootPropsPassedByField = resolveComponentProps(fieldProps.slotProps?.root, ownerState);
return {
...externalRootProps,
Expand All @@ -141,7 +148,10 @@ export const useMobileRangePicker = <
};
},
separator: (ownerState) => {
const externalSeparatorProps = resolveComponentProps(slotProps?.fieldSeparator, ownerState);
const externalSeparatorProps = resolveComponentProps(
innerSlotProps?.fieldSeparator,
ownerState,
);
const separatorPropsPassedByField = resolveComponentProps(
fieldProps.slotProps?.separator,
ownerState,
Expand All @@ -155,16 +165,40 @@ export const useMobileRangePicker = <
};

const slotPropsForLayout: PickersLayoutSlotsComponentsProps<DateRange<TDate>, TDate, TView> = {
...slotProps,
...innerSlotProps,
toolbar: {
...slotProps?.toolbar,
...innerSlotProps?.toolbar,
titleId: labelId,
rangePosition,
onRangePositionChange: setRangePosition,
} as ExportedBaseToolbarProps,
};

const Layout = slots?.layout ?? PickersLayout;

const finalLocaleText = {
...contextLocaleText,
...localeText,
};
let labelledById = labelId;
if (isToolbarHidden) {
const labels: string[] = [];
if (finalLocaleText.start) {
labels.push(`${labelId}-start-label`);
}
if (finalLocaleText.end) {
labels.push(`${labelId}-end-label`);
}
labelledById = labels.length > 0 ? labels.join(' ') : undefined;
}
const slotProps = {
...innerSlotProps,
mobilePaper: {
'aria-labelledby': labelledById,
...innerSlotProps?.mobilePaper,
},
};

const renderPicker = () => (
<LocalizationProvider localeText={localeText}>
<WrapperVariantContext.Provider value="mobile">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ DatePickerToolbar.propTypes = {
PropTypes.func,
PropTypes.object,
]),
titleId: PropTypes.string,
/**
* Toolbar date format.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ DateTimePickerToolbar.propTypes = {
*/
onViewChange: PropTypes.func.isRequired,
readOnly: PropTypes.bool,
titleId: PropTypes.string,
/**
* Toolbar date format.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {
UseDesktopPickerSlotsComponent,
UseDesktopPickerSlotsComponentsProps,
ExportedUseDesktopPickerSlotsComponentsProps,
DesktopOnlyPickerProps,
} from '../internals/hooks/useDesktopPicker';
import {
Expand All @@ -18,7 +18,7 @@ export interface DesktopDatePickerSlotsComponent<TDate>

export interface DesktopDatePickerSlotsComponentsProps<TDate>
extends BaseDatePickerSlotsComponentsProps<TDate>,
UseDesktopPickerSlotsComponentsProps<TDate, DateView> {}
ExportedUseDesktopPickerSlotsComponentsProps<TDate, DateView> {}

export interface DesktopDatePickerProps<TDate>
extends BaseDatePickerProps<TDate>,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {
UseDesktopPickerSlotsComponent,
UseDesktopPickerSlotsComponentsProps,
ExportedUseDesktopPickerSlotsComponentsProps,
DesktopOnlyPickerProps,
} from '../internals/hooks/useDesktopPicker';
import {
Expand All @@ -21,7 +21,7 @@ export interface DesktopDateTimePickerSlotsComponent<TDate>

export interface DesktopDateTimePickerSlotsComponentsProps<TDate>
extends BaseDateTimePickerSlotsComponentsProps<TDate>,
UseDesktopPickerSlotsComponentsProps<TDate, DateOrTimeView> {}
ExportedUseDesktopPickerSlotsComponentsProps<TDate, DateOrTimeView> {}

export interface DesktopDateTimePickerProps<TDate>
extends BaseDateTimePickerProps<TDate>,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {
UseDesktopPickerSlotsComponent,
UseDesktopPickerSlotsComponentsProps,
ExportedUseDesktopPickerSlotsComponentsProps,
DesktopOnlyPickerProps,
} from '../internals/hooks/useDesktopPicker';
import {
Expand All @@ -18,7 +18,7 @@ export interface DesktopTimePickerSlotsComponent<TDate>

export interface DesktopTimePickerSlotsComponentsProps<TDate>
extends BaseTimePickerSlotsComponentsProps,
UseDesktopPickerSlotsComponentsProps<TDate, TimeView> {}
ExportedUseDesktopPickerSlotsComponentsProps<TDate, TimeView> {}

export interface DesktopTimePickerProps<TDate>
extends BaseTimePickerProps<TDate>,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
UseMobilePickerSlotsComponent,
UseMobilePickerSlotsComponentsProps,
MobileOnlyPickerProps,
ExportedUseMobilePickerSlotsComponentsProps,
} from '../internals/hooks/useMobilePicker';
import {
BaseDatePickerProps,
Expand All @@ -18,7 +18,7 @@ export interface MobileDatePickerSlotsComponent<TDate>

export interface MobileDatePickerSlotsComponentsProps<TDate>
extends BaseDatePickerSlotsComponentsProps<TDate>,
UseMobilePickerSlotsComponentsProps<TDate, DateView> {}
ExportedUseMobilePickerSlotsComponentsProps<TDate, DateView> {}

export interface MobileDatePickerProps<TDate>
extends BaseDatePickerProps<TDate>,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {
UseMobilePickerSlotsComponent,
UseMobilePickerSlotsComponentsProps,
ExportedUseMobilePickerSlotsComponentsProps,
MobileOnlyPickerProps,
} from '../internals/hooks/useMobilePicker';
import {
Expand All @@ -18,7 +18,7 @@ export interface MobileDateTimePickerSlotsComponent<TDate>

export interface MobileDateTimePickerSlotsComponentsProps<TDate>
extends BaseDateTimePickerSlotsComponentsProps<TDate>,
UseMobilePickerSlotsComponentsProps<TDate, DateOrTimeView> {}
ExportedUseMobilePickerSlotsComponentsProps<TDate, DateOrTimeView> {}

export interface MobileDateTimePickerProps<TDate>
extends BaseDateTimePickerProps<TDate>,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {
UseMobilePickerSlotsComponent,
UseMobilePickerSlotsComponentsProps,
ExportedUseMobilePickerSlotsComponentsProps,
MobileOnlyPickerProps,
} from '../internals/hooks/useMobilePicker';
import {
Expand All @@ -18,7 +18,7 @@ export interface MobileTimePickerSlotsComponent<TDate>

export interface MobileTimePickerSlotsComponentsProps<TDate>
extends BaseTimePickerSlotsComponentsProps,
UseMobilePickerSlotsComponentsProps<TDate, TimeView> {}
ExportedUseMobilePickerSlotsComponentsProps<TDate, TimeView> {}

export interface MobileTimePickerProps<TDate>
extends BaseTimePickerProps<TDate>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ TimePickerToolbar.propTypes = {
*/
onViewChange: PropTypes.func.isRequired,
readOnly: PropTypes.bool,
titleId: PropTypes.string,
/**
* Toolbar date format.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { getPickersToolbarUtilityClass, PickersToolbarClasses } from './pickersT
import { DateOrTimeView } from '../models/views';

export interface PickersToolbarProps<TValue, TView extends DateOrTimeView>
extends Pick<BaseToolbarProps<TValue, TView>, 'isLandscape' | 'hidden'> {
extends Pick<BaseToolbarProps<TValue, TView>, 'isLandscape' | 'hidden' | 'titleId'> {
className?: string;
landscapeDirection?: 'row' | 'column';
toolbarTitle: React.ReactNode;
Expand Down Expand Up @@ -81,6 +81,7 @@ export const PickersToolbar = React.forwardRef(function PickersToolbar<
landscapeDirection = 'column',
toolbarTitle,
hidden,
titleId,
} = props;

const ownerState = props;
Expand All @@ -97,7 +98,12 @@ export const PickersToolbar = React.forwardRef(function PickersToolbar<
className={clsx(classes.root, className)}
ownerState={ownerState}
>
<Typography data-mui-test="picker-toolbar-title" color="text.secondary" variant="overline">
<Typography
data-mui-test="picker-toolbar-title"
color="text.secondary"
variant="overline"
id={titleId}
>
{toolbarTitle}
</Typography>
<PickersToolbarContent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ export { useDesktopPicker } from './useDesktopPicker';
export type {
UseDesktopPickerSlotsComponent,
UseDesktopPickerSlotsComponentsProps,
ExportedUseDesktopPickerSlotsComponentsProps,
DesktopOnlyPickerProps,
} from './useDesktopPicker.types';
Loading

0 comments on commit bcbb19c

Please sign in to comment.