diff --git a/cypress/component/DateTimeInput.cy.tsx b/cypress/component/DateTimeInput.cy.tsx index 53207f9a21..8df6f8f531 100644 --- a/cypress/component/DateTimeInput.cy.tsx +++ b/cypress/component/DateTimeInput.cy.tsx @@ -42,7 +42,9 @@ describe('', () => { screenReaderLabels={{ calendarIcon: 'Choose date', prevMonthButton: 'Previous month', - nextMonthButton: 'Next month' + nextMonthButton: 'Next month', + datePickerDialog: 'Date picker', + selectedLabel: 'selected' }} dateRenderLabel="date-input label" timeRenderLabel="time-input label" @@ -106,7 +108,9 @@ describe('', () => { screenReaderLabels={{ calendarIcon: 'Open calendar', prevMonthButton: 'Previous month', - nextMonthButton: 'Next month' + nextMonthButton: 'Next month', + datePickerDialog: 'Date picker', + selectedLabel: 'selected' }} timeRenderLabel="time-input" invalidDateTimeMessage="whoops" @@ -134,7 +138,9 @@ describe('', () => { screenReaderLabels={{ calendarIcon: 'Open calendar', prevMonthButton: 'Previous month', - nextMonthButton: 'Next month' + nextMonthButton: 'Next month', + datePickerDialog: 'Date picker', + selectedLabel: 'selected' }} dateRenderLabel="date-input" timeRenderLabel="time-input" @@ -169,7 +175,9 @@ describe('', () => { screenReaderLabels={{ calendarIcon: 'Open calendar', prevMonthButton: 'Previous month', - nextMonthButton: 'Next month' + nextMonthButton: 'Next month', + datePickerDialog: 'Date picker', + selectedLabel: 'selected' }} dateRenderLabel="date-input" timeRenderLabel="time-input" @@ -199,7 +207,9 @@ describe('', () => { screenReaderLabels: { calendarIcon: 'Open calendar', prevMonthButton: 'Previous month', - nextMonthButton: 'Next month' + nextMonthButton: 'Next month', + datePickerDialog: 'Date picker', + selectedLabel: 'selected' }, timeRenderLabel: 'time-input', invalidDateTimeMessage: 'whoops', @@ -249,7 +259,9 @@ describe('', () => { screenReaderLabels={{ calendarIcon: 'Open calendar', prevMonthButton: 'Previous month', - nextMonthButton: 'Next month' + nextMonthButton: 'Next month', + datePickerDialog: 'Date picker', + selectedLabel: 'selected' }} timeRenderLabel="time-input" invalidDateTimeMessage="whoops" @@ -286,7 +298,9 @@ describe('', () => { screenReaderLabels={{ calendarIcon: 'Open calendar', prevMonthButton: 'Previous month', - nextMonthButton: 'Next month' + nextMonthButton: 'Next month', + datePickerDialog: 'Date picker', + selectedLabel: 'selected' }} timeRenderLabel="time-input" invalidDateTimeMessage="whoops" diff --git a/packages/__docs__/src/Nav/index.tsx b/packages/__docs__/src/Nav/index.tsx index ab171dbf33..6fd41f4744 100644 --- a/packages/__docs__/src/Nav/index.tsx +++ b/packages/__docs__/src/Nav/index.tsx @@ -451,9 +451,8 @@ class Nav extends Component { themeSelected = themeSelected || isSelected const renderThemeName = (rawName: string) => { - if (rawName !== 'canvas' && rawName !== 'canvas-high-contrast') { - return `${rawName} (beta)` - } + if (rawName === 'canvas') return 'legacy-canvas' + if (rawName === 'canvas-high-contrast') return 'legacy-canvas-high-contrast' return rawName } diff --git a/packages/ui-calendar/src/Calendar/__tests__/Calendar.test.tsx b/packages/ui-calendar/src/Calendar/__tests__/Calendar.test.tsx index cd9dea08da..d36365b7ee 100644 --- a/packages/ui-calendar/src/Calendar/__tests__/Calendar.test.tsx +++ b/packages/ui-calendar/src/Calendar/__tests__/Calendar.test.tsx @@ -294,8 +294,12 @@ describe('', () => { {generateDays()} ) - const defaultPrevButton = screen.getByText('Previous month') - const defaultNextButton = screen.getByText('Next month') + const defaultPrevButton = screen.getByRole('button', { + name: /^Previous month/ + }) + const defaultNextButton = screen.getByRole('button', { + name: /^Next month/ + }) expect(defaultPrevButton).toBeInTheDocument() expect(defaultNextButton).toBeInTheDocument() @@ -536,4 +540,52 @@ describe('', () => { expect(calendar.tagName).toBe('UL') expect(calendar).toHaveTextContent(weekdayLabels.join('')) }) + + describe('navigation button targetMonthSrLabel', () => { + it('should include the target month in the default button screen reader labels', () => { + render( + + {generateDays()} + + ) + + expect( + screen.getByRole('button', { name: 'Previous month, November 2023' }) + ).toBeInTheDocument() + expect( + screen.getByRole('button', { name: 'Next month, January 2024' }) + ).toBeInTheDocument() + }) + + it('should pass targetMonthSrLabel to function render props', () => { + render( + ( + + )} + renderNextMonthButton={({ targetMonthSrLabel }) => ( + + )} + > + {generateDays()} + + ) + + expect( + screen.getByRole('button', { name: 'Go to November 2023' }) + ).toBeInTheDocument() + expect( + screen.getByRole('button', { name: 'Go to January 2024' }) + ).toBeInTheDocument() + }) + }) }) diff --git a/packages/ui-calendar/src/Calendar/v1/index.tsx b/packages/ui-calendar/src/Calendar/v1/index.tsx index cecf8dffb5..e57c0f120d 100644 --- a/packages/ui-calendar/src/Calendar/v1/index.tsx +++ b/packages/ui-calendar/src/Calendar/v1/index.tsx @@ -175,28 +175,41 @@ class Calendar extends Component { renderMonthNavigationButtons = () => { const { renderNextMonthButton, renderPrevMonthButton } = this.props + const { visibleMonth } = this.state + const prevMonthName = visibleMonth + .clone() + .subtract({ months: 1 }) + .format('MMMM YYYY') + const nextMonthName = visibleMonth + .clone() + .add({ months: 1 }) + .format('MMMM YYYY') return { prevButton: renderPrevMonthButton ? ( - callRenderProp(renderPrevMonthButton) + callRenderProp(renderPrevMonthButton, { + targetMonthSrLabel: prevMonthName + }) ) : ( } - screenReaderLabel="Previous month" + screenReaderLabel={`Previous month, ${prevMonthName}`} /> ), nextButton: renderNextMonthButton ? ( - callRenderProp(renderNextMonthButton) + callRenderProp(renderNextMonthButton, { + targetMonthSrLabel: nextMonthName + }) ) : ( } - screenReaderLabel="Next month" + screenReaderLabel={`Next month, ${nextMonthName}`} /> ) } @@ -299,12 +312,12 @@ class Calendar extends Component { {renderNavigationLabel ? ( callRenderProp(renderNavigationLabel) ) : ( - +
{visibleMonth.format('MMMM')}
{!withYearPicker ? (
{visibleMonth.format('YYYY')}
) : null} - +
)} {nextButton && cloneButton(nextButton, this.handleMonthChange('next'))} diff --git a/packages/ui-calendar/src/Calendar/v1/props.ts b/packages/ui-calendar/src/Calendar/v1/props.ts index 6820e16771..3d22b5a08e 100644 --- a/packages/ui-calendar/src/Calendar/v1/props.ts +++ b/packages/ui-calendar/src/Calendar/v1/props.ts @@ -98,19 +98,23 @@ type CalendarOwnProps = { */ renderNavigationLabel?: Renderable /** - * A button to render in the navigation header. The recommendation is to - * compose it with the [IconButton](IconButton) component by setting the `size` - * prop to `small`, `withBorder` and `withBackground` to `false`, and setting - * `renderIcon` to [IconArrowOpenEnd](icons). - */ - renderNextMonthButton?: Renderable - /** - * A button to render in the navigation header. The recommendation is to - * compose it with the [IconButton](Button) component by setting the `size` - * prop to `small`, `withBorder` and `withBackground` to `false`, and setting - * `renderIcon` to [IconArrowOpenStart](icons). - */ - renderPrevMonthButton?: Renderable + * A button to render in the navigation header. + * When passed as a function, receives `{ targetMonthSrLabel }` — + * a pre-formatted screen reader label for the target month (e.g. "November 2023"). + * The recommendation is to compose it with the [IconButton](Button) component + * by setting the `size` prop to `small`, `withBorder` and `withBackground` to `false`, + * and setting `renderIcon` to [IconArrowOpenStart](icons). + */ + renderNextMonthButton?: Renderable<{ targetMonthSrLabel: string }> + /** + * A button to render in the navigation header. + * When passed as a function, receives `{ targetMonthSrLabel }` — + * a pre-formatted screen reader label for the target month (e.g. "November 2023"). + * The recommendation is to compose it with the [IconButton](Button) component + * by setting the `size` prop to `small`, `withBorder` and `withBackground` to `false`, + * and setting `renderIcon` to [IconArrowOpenStart](icons). + */ + renderPrevMonthButton?: Renderable<{ targetMonthSrLabel: string }> /** * An array of labels containing the name of each day of the week. The visible * portion of the label should be abbreviated (no longer than three characters). diff --git a/packages/ui-calendar/src/Calendar/v2/README.md b/packages/ui-calendar/src/Calendar/v2/README.md index e1d2f6f7a2..5d83e93111 100644 --- a/packages/ui-calendar/src/Calendar/v2/README.md +++ b/packages/ui-calendar/src/Calendar/v2/README.md @@ -174,23 +174,26 @@ type: example const date = parseDate(renderedDate) - const buttonProps = (type = 'prev') => ({ - size: 'small', - withBackground: false, - withBorder: false, - renderIcon: - type === 'prev' ? ( - - ) : ( - - ), - screenReaderLabel: type === 'prev' ? 'Previous month' : 'Next month' - }) + const renderMonthButton = (type = 'prev') => ({ targetMonthSrLabel }) => ( + + ) : ( + + ) + } + screenReaderLabel={`${type === 'prev' ? 'Previous month' : 'Next month'}, ${targetMonthSrLabel}`} + /> + ) return ( } - renderNextMonthButton={} + renderPrevMonthButton={renderMonthButton('prev')} + renderNextMonthButton={renderMonthButton('next')} renderNavigationLabel={
{date.format('MMMM')}
@@ -251,7 +254,4 @@ the abbreviation. ex. `[Sun, #### Rendering next and previous month buttons -The `renderNextMonthButton` and `renderPrevMonthButton` can be supplied using the -[IconButton](IconButton) component with the `size` prop set to -`small`, the `withBackground` and `withBorder` props both set to `false`, and the `renderIcon` prop set to `ChevronLeftInstUIIcon` or -`ChevronRightInstUIIcon`. +The `renderNextMonthButton` and `renderPrevMonthButton` can be supplied as a function that receives `{ targetMonthSrLabel }` — a pre-formatted screen reader label for the target month (e.g. "November 2023"). Use this to compose an [IconButton](IconButton) component with the `size` prop set to `small`, the `withBackground` and `withBorder` props both set to `false`, and the `renderIcon` prop set to `ChevronLeftInstUIIcon` or `ChevronRightInstUIIcon`. The `screenReaderLabel` should include the target month for better accessibility: e.g. "Previous month, November 2023". diff --git a/packages/ui-calendar/src/Calendar/v2/index.tsx b/packages/ui-calendar/src/Calendar/v2/index.tsx index fd6502e340..498f755e93 100644 --- a/packages/ui-calendar/src/Calendar/v2/index.tsx +++ b/packages/ui-calendar/src/Calendar/v2/index.tsx @@ -174,28 +174,41 @@ class Calendar extends Component { renderMonthNavigationButtons = () => { const { renderNextMonthButton, renderPrevMonthButton } = this.props + const { visibleMonth } = this.state + const prevMonthName = visibleMonth + .clone() + .subtract({ months: 1 }) + .format('MMMM YYYY') + const nextMonthName = visibleMonth + .clone() + .add({ months: 1 }) + .format('MMMM YYYY') return { prevButton: renderPrevMonthButton ? ( - callRenderProp(renderPrevMonthButton) + callRenderProp(renderPrevMonthButton, { + targetMonthSrLabel: prevMonthName + }) ) : ( } - screenReaderLabel="Previous month" + screenReaderLabel={`Previous month, ${prevMonthName}`} /> ), nextButton: renderNextMonthButton ? ( - callRenderProp(renderNextMonthButton) + callRenderProp(renderNextMonthButton, { + targetMonthSrLabel: nextMonthName + }) ) : ( } - screenReaderLabel="Next month" + screenReaderLabel={`Next month, ${nextMonthName}`} /> ) } @@ -298,12 +311,16 @@ class Calendar extends Component { {renderNavigationLabel ? ( callRenderProp(renderNavigationLabel) ) : ( - -
{visibleMonth.format('MMMM')}
+

+ {visibleMonth.format('MMMM')} {!withYearPicker ? ( -
{visibleMonth.format('YYYY')}
+ {visibleMonth.format('YYYY')} ) : null} - +

)} {nextButton && cloneButton(nextButton, this.handleMonthChange('next'))} @@ -312,7 +329,7 @@ class Calendar extends Component { {withYearPicker ? (
/** - * A button to render in the navigation header. The recommendation is to - * compose it with the [IconButton](Button) component by setting the `size` - * prop to `small`, `withBorder` and `withBackground` to `false`, and setting - * `renderIcon` to [IconArrowOpenStart](icons). + * A button to render in the navigation header. + * When passed as a function, receives `{ targetMonthSrLabel }` — + * a pre-formatted screen reader label for the target month (e.g. "November 2023"). + * The recommendation is to compose it with the [IconButton](Button) component + * by setting the `size` prop to `small`, `withBorder` and `withBackground` to `false`, + * and setting `renderIcon` to [IconArrowOpenStart](icons). */ - renderPrevMonthButton?: Renderable + renderPrevMonthButton?: Renderable<{ targetMonthSrLabel: string }> /** * An array of labels containing the name of each day of the week. The visible * portion of the label should be abbreviated (no longer than three characters). @@ -182,7 +186,11 @@ type CalendarProps = CalendarOwnProps & WithDeterministicIdProps type CalendarStyle = ComponentStyle< - 'navigation' | 'navigationWithButtons' | 'weekdayHeader' | 'yearPicker' + | 'navigation' + | 'navigationWithButtons' + | 'navigationLabel' + | 'weekdayHeader' + | 'yearPicker' > const allowedProps: AllowedPropKeys = [ 'as', diff --git a/packages/ui-calendar/src/Calendar/v2/styles.ts b/packages/ui-calendar/src/Calendar/v2/styles.ts index eba9f9f3a6..6d3335c85e 100644 --- a/packages/ui-calendar/src/Calendar/v2/styles.ts +++ b/packages/ui-calendar/src/Calendar/v2/styles.ts @@ -63,6 +63,15 @@ const generateStyle = ( maxWidth: componentTheme.maxHeaderWidth, lineHeight: componentTheme.lineHeight }, + navigationLabel: { + label: 'calendar__navigation-label', + margin: 0, + fontSize: 'inherit', + fontWeight: 'inherit', + '& span': { + display: 'block' + } + }, yearPicker: { display: 'flex', justifyContent: 'center', diff --git a/packages/ui-date-input/src/DateInput/__tests__/DateInput.test.tsx b/packages/ui-date-input/src/DateInput/__tests__/DateInput.test.tsx index 3005ea69a4..94c65a3cde 100644 --- a/packages/ui-date-input/src/DateInput/__tests__/DateInput.test.tsx +++ b/packages/ui-date-input/src/DateInput/__tests__/DateInput.test.tsx @@ -220,21 +220,15 @@ describe('', () => { await waitFor(() => { const prevMonthButton = screen.getByRole('button', { - name: prevMonthLabel + name: new RegExp(`^${prevMonthLabel}`) }) const nextMonthButton = screen.getByRole('button', { - name: nextMonthLabel + name: new RegExp(`^${nextMonthLabel}`) }) expect(prevMonthButton).toBeInTheDocument() expect(nextMonthButton).toBeInTheDocument() - const prevButtonLabel = screen.getByText(prevMonthLabel) - const nextButtonLabel = screen.getByText(nextMonthLabel) - - expect(prevButtonLabel).toBeInTheDocument() - expect(nextButtonLabel).toBeInTheDocument() - const prevMonthIcon = prevMonthButton.querySelector( 'svg[name="ChevronLeft"]' ) diff --git a/packages/ui-date-input/src/DateInput/v2/index.tsx b/packages/ui-date-input/src/DateInput/v2/index.tsx index 792f6df884..ee2bef3181 100644 --- a/packages/ui-date-input/src/DateInput/v2/index.tsx +++ b/packages/ui-date-input/src/DateInput/v2/index.tsx @@ -311,24 +311,24 @@ const DateInput = forwardRef( visibleMonth={selectedDate} locale={userLocale} timezone={userTimezone} - renderNextMonthButton={ + renderNextMonthButton={({ targetMonthSrLabel }) => ( } - screenReaderLabel={screenReaderLabels.nextMonthButton} + screenReaderLabel={`${screenReaderLabels.nextMonthButton}, ${targetMonthSrLabel}`} /> - } - renderPrevMonthButton={ + )} + renderPrevMonthButton={({ targetMonthSrLabel }) => ( } - screenReaderLabel={screenReaderLabels.prevMonthButton} + screenReaderLabel={`${screenReaderLabels.prevMonthButton}, ${targetMonthSrLabel}`} /> - } + )} /> } diff --git a/packages/ui-dialog/src/Dialog/index.tsx b/packages/ui-dialog/src/Dialog/index.tsx index 09e0243766..8fdc15db9f 100644 --- a/packages/ui-dialog/src/Dialog/index.tsx +++ b/packages/ui-dialog/src/Dialog/index.tsx @@ -168,6 +168,9 @@ class Dialog extends Component { {...omitProps(this.props, Dialog.allowedProps)} role={role} aria-label={this.props.label} + aria-modal={ + role === 'dialog' && this.props.shouldContainFocus ? true : undefined + } className={this.props.className} // TODO in V2 remove className, there is no usage of it. style={{ borderRadius: 'inherit' }} // ensure the dialog inherits border radius from View ref={this.getRef}