-
Notifications
You must be signed in to change notification settings - Fork 300
feat: improved accessibility of notification tray #1817
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
base: master
Are you sure you want to change the base?
Changes from 1 commit
220d14e
c6fc88b
8eb8ab0
5199cee
217a46c
d23f8a2
ca3a43c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i added this custom hook |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,19 @@ | ||
| import { | ||
| useCallback, useContext, useEffect, useRef, | ||
| } from 'react'; | ||
|
|
||
| import { tryFocusAndPreventDefault } from '../../utils'; | ||
| import SidebarContext from '../../SidebarContext'; | ||
|
|
||
| /** | ||
| * Manages accessibility interactions for the SidebarBase component, including: | ||
| * 1. Setting initial focus when the sidebar opens. | ||
| * 2. Handling sidebar closing and returning focus to the trigger. | ||
| * 3. Managing keyboard navigation (Tab/Shift+Tab) for focus trapping/guidance. | ||
| * | ||
| * @param {string} sidebarId The unique ID of this sidebar. | ||
| * @param {string} [triggerButtonSelector] The CSS selector for the trigger button | ||
| */ | ||
| export const useSidebarFocusAndKeyboard = (sidebarId, triggerButtonSelector = '.sidebar-trigger-btn') => { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [question]: Can we add a short but meaningful JSDocs here to describe what this hook does?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. was added
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Resolved |
||
| const { | ||
| toggleSidebar, | ||
|
|
@@ -29,12 +39,10 @@ export const useSidebarFocusAndKeyboard = (sidebarId, triggerButtonSelector = '. | |
|
|
||
| const focusSidebarTriggerBtn = useCallback(() => { | ||
| requestAnimationFrame(() => { | ||
| requestAnimationFrame(() => { | ||
| const sidebarTriggerBtn = document.querySelector(triggerButtonSelector); | ||
| if (sidebarTriggerBtn) { | ||
| sidebarTriggerBtn.focus(); | ||
| } | ||
| }); | ||
| const sidebarTriggerBtn = document.querySelector(triggerButtonSelector); | ||
| if (sidebarTriggerBtn) { | ||
| sidebarTriggerBtn.focus(); | ||
| } | ||
| }); | ||
| }, [triggerButtonSelector]); | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,13 +1,8 @@ | ||||||||||||||||||||||||||
| import { renderHook, act } from '@testing-library/react'; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| import SidebarContext from '../../SidebarContext'; | ||||||||||||||||||||||||||
| import { useSidebarFocusAndKeyboard } from './useSidebarFocusAndKeyboard'; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| import { tryFocusAndPreventDefault } from '../../utils'; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| jest.mock('../../utils', () => ({ | ||||||||||||||||||||||||||
| tryFocusAndPreventDefault: jest.fn(), | ||||||||||||||||||||||||||
| })); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const SIDEBAR_ID = 'test-sidebar'; | ||||||||||||||||||||||||||
| const TRIGGER_SELECTOR = '.sidebar-trigger-btn'; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
@@ -131,13 +126,20 @@ describe('useSidebarFocusAndKeyboard', () => { | |||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| describe('handleKeyDown (Standard Close Button)', () => { | ||||||||||||||||||||||||||
| let mockEvent; | ||||||||||||||||||||||||||
| let mockOutlineTrigger; | ||||||||||||||||||||||||||
| let mockPrevButton; | ||||||||||||||||||||||||||
| let mockNextButton; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| beforeEach(() => { | ||||||||||||||||||||||||||
| mockEvent = { | ||||||||||||||||||||||||||
| key: 'Tab', | ||||||||||||||||||||||||||
| shiftKey: false, | ||||||||||||||||||||||||||
| preventDefault: jest.fn(), | ||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| mockOutlineTrigger = { focus: jest.fn(), disabled: false }; | ||||||||||||||||||||||||||
| mockPrevButton = { focus: jest.fn(), disabled: false }; | ||||||||||||||||||||||||||
| mockNextButton = { focus: jest.fn(), disabled: false }; | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| it('should do nothing if key is not Tab', () => { | ||||||||||||||||||||||||||
|
|
@@ -151,7 +153,6 @@ describe('useSidebarFocusAndKeyboard', () => { | |||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| expect(mockEvent.preventDefault).not.toHaveBeenCalled(); | ||||||||||||||||||||||||||
| expect(triggerButtonMock.focus).not.toHaveBeenCalled(); | ||||||||||||||||||||||||||
| expect(tryFocusAndPreventDefault).not.toHaveBeenCalled(); | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| it('should call focusSidebarTriggerBtn on Shift+Tab', () => { | ||||||||||||||||||||||||||
|
|
@@ -164,46 +165,44 @@ describe('useSidebarFocusAndKeyboard', () => { | |||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| expect(mockEvent.preventDefault).toHaveBeenCalledTimes(1); | ||||||||||||||||||||||||||
| act(() => { jest.runAllTimers(); }); | ||||||||||||||||||||||||||
| act(() => jest.runAllTimers()); | ||||||||||||||||||||||||||
| expect(triggerButtonMock.focus).toHaveBeenCalledTimes(1); | ||||||||||||||||||||||||||
| expect(tryFocusAndPreventDefault).not.toHaveBeenCalled(); | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| it('should attempt to focus elements sequentially on Tab', () => { | ||||||||||||||||||||||||||
| mockContextValue = getMockContext(SIDEBAR_ID); | ||||||||||||||||||||||||||
| const { result } = renderHookWithContext(mockContextValue); | ||||||||||||||||||||||||||
| mockEvent.shiftKey = false; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| (tryFocusAndPreventDefault).mockImplementation((event, selector) => { | ||||||||||||||||||||||||||
| if (selector === '.previous-button') { | ||||||||||||||||||||||||||
| event.preventDefault(); | ||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||
| mockQuerySelector.mockImplementation((selector) => { | ||||||||||||||||||||||||||
| if (selector === '#courseOutlineSidebarTrigger') { return mockOutlineTrigger; } | ||||||||||||||||||||||||||
| if (selector === '.previous-button') { return mockPrevButton; } | ||||||||||||||||||||||||||
| if (selector === '.next-button') { return mockNextButton; } | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
| if (selector === '#courseOutlineSidebarTrigger') { return mockOutlineTrigger; } | |
| if (selector === '.previous-button') { return mockPrevButton; } | |
| if (selector === '.next-button') { return mockNextButton; } | |
| if (selector === '#courseOutlineSidebarTrigger') { | |
| return mockOutlineTrigger; | |
| } | |
| if (selector === '.previous-button') { | |
| return mockPrevButton; | |
| } | |
| if (selector === '.next-button') { | |
| return mockNextButton; | |
| } |
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.
fixed
Outdated
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.
| if (selector === TRIGGER_SELECTOR) { return triggerButtonMock; } | |
| if (selector === TRIGGER_SELECTOR) { | |
| return triggerButtonMock; | |
| } |
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.
fixed
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,13 @@ | ||
| /** | ||
| * Attempts to find an interactive element by its selector and focus it. | ||
| * If the element is found and is not disabled, it prevents the default | ||
| * behavior of the event (e.g., standard Tab) and moves focus to the element. | ||
| * | ||
| * @param {Event} event - The keyboard event object (e.g., from a 'keydown' listener). | ||
| * @param {string} selector - The CSS selector for the target element to focus. | ||
| * @returns {boolean} - Returns `true` if the element was found, enabled, and focused. | ||
| * Returns `false` if the element was not found or was disabled. | ||
| */ | ||
| export const tryFocusAndPreventDefault = (event, selector) => { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [code style]: Please add some JSDocs for this function
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. was added
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Resolved |
||
| const element = document.querySelector(selector); | ||
| if (element && !element.disabled) { | ||
|
|
||
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.
[clarify]: Is there any reason why sidebarId was removed from deps? Also, do I need to remove // eslint-disable-next-line react-hooks/exhaustive-deps?
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.
no reason, removed
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.
My question is related to the fact that
sidebarIdwas previously included in the dependency array. Why was it removed?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.
because
sidebarIdit was an unnecessary dependency