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

[IRN-5039] BottomSheet supports aria-label #3427

Merged
merged 6 commits into from
May 13, 2024
Merged

[IRN-5039] BottomSheet supports aria-label #3427

merged 6 commits into from
May 13, 2024

Conversation

steviehailey-skyscanner
Copy link
Contributor

@steviehailey-skyscanner steviehailey-skyscanner commented May 8, 2024

Description

Previously BottomSheet consumers had to provide an aria-labelledBy to be attached to the dialog element but it is rare that they know the relevant title element id to provide.

Now BottomSheet consumers can provide either:
a. aria-labelledBy - if they are not providing a string title and want to reference an element within the content as a label
b. aria-label - if they would like to provide an a11y string (e.g. same value as title or a more accessible equivalent)

Screenshot - BottomSheet consumer can now control aria-label on dialog element

image

Remember to include the following changes:

  • Ensure the PR title includes the name of the component you are changing so it's clear in the release notes for consumers of the changes in the version e.g [KOA-123][BpkButton] Updating the colour
  • README.md (If you have created a new component)
  • Component README.md
  • Tests
  • Storybook examples created/updated
  • For breaking changes or deprecating components/properties, migration guides added to the description of the PR. If the guide has large changes, consider creating a new Markdown page inside the component's docs folder and link it here

@@ -146,7 +146,7 @@ BottomSheetContainerProps,
Open bottom sheet
</BpkButtonV2>
<BpkBottomSheet
ariaLabelledby='test'
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make the default demo more of a valid example - there was no #test element. Now screen reader reads the a11y title.

@@ -31,9 +31,8 @@ import STYLES from './BpkBottomSheet.module.scss';

const getClassName = cssModules(STYLES);

export type Props = {
interface CommonProps {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extract out the non-a11y props so we can conditionally allow either label or labelledBy...

title = '',
wide = false
}: Props) => {
export type Props = CommonProps & ({ ariaLabelledby: string } | { ariaLabel: string; });
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure we get one aria prop or the other

@@ -92,7 +93,7 @@ const BpkBottomSheet = ({
return <BpkBreakpoint query={BREAKPOINTS.ABOVE_MOBILE}>
{(isAboveMobile: boolean) =>
<BpkDialogWrapper
ariaLabelledby={ariaLabelledby}
{...props}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pass down the a11y props (we don't know which one we'll have but TS assures we'll have one)

};

export type Props = CommonProps & ({ ariaLabelledby: string } | { ariaLabel: string; });;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same pattern as above

@@ -135,6 +135,11 @@ export const BpkDialogWrapper = (props: Props) => {
};
}, [id, isOpen, onClose, closeOnEscPressed, closeOnScrimClick]);

const ariaProps = {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extract whichever aria prop has been provided to add the the dialog element

Copy link

github-actions bot commented May 8, 2024

Warnings
⚠️

Package source files (e.g. packages/package-name/src/Component.js) were updated, but snapshots weren't. Have you checked that the tests still pass?

⚠️

Package source files (e.g. packages/package-name/src/Component.tsx) were updated, but type files weren't. Have you checked that no types have changed?

Browser support

If this is a visual change, make sure you've tested it in multiple browsers.

Generated by 🚫 dangerJS against aaba15a

Copy link

github-actions bot commented May 8, 2024

Visit https://backpack.github.io/storybook-prs/3427 to see this build running in a browser.

}: Props) => {
export type Props = CommonProps & ({ ariaLabelledby: string } | { ariaLabel: string; });

const BpkBottomSheet = (props: Props) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not keep it as it was previously and destructure it here? 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a limitation of the mutually exclusive aria props unfortunately. TS won't allow destructuring of those fields because it knows one will not be there so by keeping the props intact we can safely reference or pass them on (subject to performing checks for existence to keep TS happy).

In this component we just pass whichever we have by spreading the props

In BpkDialogWrapper we have an example of checking and using a specific one here: https://github.com/Skyscanner/backpack/pull/3427/files#diff-8ae3b880e3772a84f85517514640da0930f295cffbdfd531fcf063a29ba84606R139

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, but we could also destructure the props here and spread the aria props rather than explicitly destructure them? Something like

const BpkBottomSheet = ({actionText = '', children, etc, ...ariaProps}) => {...}

?
In which case we'd also pass whichever we have

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yeah we could use that syntax to keep the destructure - will push an update 🚀

</BpkDialogWrapper>
);

expect(container.querySelector('dialog[aria-labelledby="my-title-id"]')).toBeInTheDocument();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check aria correctly applied
(I noticed most other tests use snapshots but this feels more explicit on what we're looking for while still capturing a low level of DOM detail for the relevant parts)

expect(results).toHaveNoViolations();
});

it('should not have programmatically-detectable accessibility issues using airaLabel', async () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional axe check for alternative aria

Copy link

github-actions bot commented May 8, 2024

Visit https://backpack.github.io/storybook-prs/3427 to see this build running in a browser.

@steviehailey-skyscanner steviehailey-skyscanner added the minor Non breaking change label May 9, 2024

import BpkBottomSheet from './BpkBottomSheet';
// mock breakpoint to always match
jest.mock('../../bpk-component-breakpoint/src/useMediaQuery', () => jest.fn(() => true));
describe('BpkBottomSheet', () => {

const props = {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extract out some common props to reduce repetition and let the cases focus on the props of interest.

wide
>
Bottom Sheet content
</BpkBottomSheet>
);
expect(asFragment()).toMatchSnapshot();
});
it('renders correctly with ariaLabelledBy prop', () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New cases to explicitly check the propagation of the aria props (based on the DialogWrapper ones)

Copy link

github-actions bot commented May 9, 2024

Visit https://backpack.github.io/storybook-prs/3427 to see this build running in a browser.

@steviehailey-skyscanner steviehailey-skyscanner marked this pull request as ready for review May 9, 2024 09:31
};

export type Props = CommonProps & ({ ariaLabelledby: string } | { ariaLabel: string; });;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's an extra ;?

@@ -177,6 +182,6 @@ export const BpkDialogWrapper = (props: Props) => {
</div>
</dialog>
</CSSTransition>
</div>
</div >
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this an automatic linting change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what happened there - the linter is happy with or without the space so will remove 😆

title = '',
wide = false
}: Props) => {
export type Props = CommonProps & ({ ariaLabelledby: string } | { ariaLabel: string; });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, I thought interfaces could only be extended and & wouldn't work on them

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah there is some subtlety there, you can mention interfaces when using intersection to define a type (you just can define an interface using intersection).

Copy link

github-actions bot commented May 9, 2024

Visit https://backpack.github.io/storybook-prs/3427 to see this build running in a browser.

Copy link

github-actions bot commented May 9, 2024

Visit https://backpack.github.io/storybook-prs/3427 to see this build running in a browser.

const { container } = render(
<BpkBottomSheet
{...props}
ariaLabel='my a11y title'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing this will take precedence since it's added after ariaLabelledBy? 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have done some testing in Safari with VoiceOver and if you give it a valid ariaLabelledBy then it will use that. If the labelledBy was missing or invalid then it falls back to the aria-label instead.

const { container } = render(
<BpkBottomSheet
{...props}
ariaLabelledby='my-bottomsheet-title-id'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's already part of props so I don't think we need it here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I we could avoid it but I thought it was nice to be explicit when we are asserting based on that property?

const { container } = render(
<BpkDialogWrapper
{...props}
ariaLabelledby='my-title-id'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, it's already declared in props

@anambl
Copy link
Contributor

anambl commented May 9, 2024

It all looks great to me!
What happens if you don't pass neither or if you pass both aria props? For the first, I assume a TS error, but I don't think it's possible to unit test that 😅 And for the 2nd, the latter takes precedence? 🤔

@steviehailey-skyscanner
Copy link
Contributor Author

It all looks great to me! What happens if you don't pass neither or if you pass both aria props? For the first, I assume a TS error, but I don't think it's possible to unit test that 😅 And for the 2nd, the latter takes precedence? 🤔

Indeed TS will prevent the former but if someone ignored the TS error then things function but you get a degraded screenreader experience because it often describes the "Close button" before describing what the dialog is for. For the later case, it seems that labelledBy is used in favour of label if someone did provide both which seems reasonable.

Copy link

Visit https://backpack.github.io/storybook-prs/3427 to see this build running in a browser.

@anambl anambl merged commit b0093c8 into main May 13, 2024
9 checks passed
@anambl anambl deleted the IRN-5039 branch May 13, 2024 07:55
FireRedNinja pushed a commit that referenced this pull request May 22, 2024
* [IRN-5039] BottomSheet supports aria-label

* Add tests for modal wrapper

* Add bottom sheet aria tests

* PR feedback
KathyWang0208 pushed a commit that referenced this pull request May 27, 2024
* [IRN-5039] BottomSheet supports aria-label

* Add tests for modal wrapper

* Add bottom sheet aria tests

* PR feedback
KathyWang0208 pushed a commit that referenced this pull request May 27, 2024
* [IRN-5039] BottomSheet supports aria-label

* Add tests for modal wrapper

* Add bottom sheet aria tests

* PR feedback
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
minor Non breaking change
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants