-
Notifications
You must be signed in to change notification settings - Fork 183
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
Conversation
@@ -146,7 +146,7 @@ BottomSheetContainerProps, | |||
Open bottom sheet | |||
</BpkButtonV2> | |||
<BpkBottomSheet | |||
ariaLabelledby='test' |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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; }); |
There was a problem hiding this comment.
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} |
There was a problem hiding this comment.
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; });; |
There was a problem hiding this comment.
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 = { |
There was a problem hiding this comment.
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
Browser supportIf this is a visual change, make sure you've tested it in multiple browsers. |
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) => { |
There was a problem hiding this comment.
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? 🤔
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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 () => { |
There was a problem hiding this comment.
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
Visit https://backpack.github.io/storybook-prs/3427 to see this build running in a browser. |
|
||
import BpkBottomSheet from './BpkBottomSheet'; | ||
// mock breakpoint to always match | ||
jest.mock('../../bpk-component-breakpoint/src/useMediaQuery', () => jest.fn(() => true)); | ||
describe('BpkBottomSheet', () => { | ||
|
||
const props = { |
There was a problem hiding this comment.
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', () => { |
There was a problem hiding this comment.
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)
Visit https://backpack.github.io/storybook-prs/3427 to see this build running in a browser. |
}; | ||
|
||
export type Props = CommonProps & ({ ariaLabelledby: string } | { ariaLabel: string; });; |
There was a problem hiding this comment.
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 > |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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; }); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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).
Visit https://backpack.github.io/storybook-prs/3427 to see this build running in a browser. |
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' |
There was a problem hiding this comment.
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
? 🤔
There was a problem hiding this comment.
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' |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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' |
There was a problem hiding this comment.
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
It all looks great to me! |
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 |
Visit https://backpack.github.io/storybook-prs/3427 to see this build running in a browser. |
* [IRN-5039] BottomSheet supports aria-label * Add tests for modal wrapper * Add bottom sheet aria tests * PR feedback
* [IRN-5039] BottomSheet supports aria-label * Add tests for modal wrapper * Add bottom sheet aria tests * PR feedback
* [IRN-5039] BottomSheet supports aria-label * Add tests for modal wrapper * Add bottom sheet aria tests * PR feedback
Description
Previously BottomSheet consumers had to provide an
aria-labelledBy
to be attached to thedialog
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 labelb.
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
Remember to include the following changes:
[KOA-123][BpkButton] Updating the colour
README.md
(If you have created a new component)README.md