-
Notifications
You must be signed in to change notification settings - Fork 608
♿️(frontend) limit share modal opening announcement for screen readers #2452
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: main
Are you sure you want to change the base?
Changes from all commits
8f41648
6449fd5
d40b395
ba0f239
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -40,6 +40,9 @@ import { | |
| import { QuickSearchGroupMember } from './DocShareMember'; | ||
| import { DocShareModalFooter } from './DocShareModalFooter'; | ||
|
|
||
| const SEARCH_QUERY_DEBOUNCE_MS = 300; | ||
| const ACCESSIBILITY_REEXPOSURE_DELAY_MS = 300; | ||
|
|
||
| const ShareModalStyle = createGlobalStyle` | ||
| .--docs--doc-share-modal [cmdk-item] { | ||
| cursor: auto; | ||
|
|
@@ -84,6 +87,7 @@ export const DocShareModal = ({ doc, onClose, isRootDoc = true }: Props) => { | |
|
|
||
| const [listHeight, setListHeight] = useState<string>('400px'); | ||
| const canShare = doc.abilities.accesses_manage && isRootDoc; | ||
| const [isContentAccessible, setIsContentAccessible] = useState(canShare); | ||
| const canViewAccesses = doc.abilities.accesses_view; | ||
| const showMemberSection = inputValue === '' && selectedUsers.length === 0; | ||
| const showFooter = selectedUsers.length === 0 && !inputValue; | ||
|
|
@@ -119,7 +123,7 @@ export const DocShareModal = ({ doc, onClose, isRootDoc = true }: Props) => { | |
|
|
||
| const onFilter = useDebouncedCallback((str: string) => { | ||
| setUserQuery(str); | ||
| }, 300); | ||
| }, SEARCH_QUERY_DEBOUNCE_MS); | ||
|
|
||
| const onRemoveUser = (row: User) => { | ||
| setSelectedUsers((prevState) => { | ||
|
|
@@ -161,6 +165,23 @@ export const DocShareModal = ({ doc, onClose, isRootDoc = true }: Props) => { | |
| const showInheritedShareContent = | ||
| inheritedAccesses.length > 0 && showMemberSection && !isRootDoc; | ||
|
|
||
| // When the search input is hidden, keep the modal content out of the | ||
| // accessibility tree during the opening announcement, then restore it. | ||
| useEffect(() => { | ||
| if (canShare) { | ||
| setIsContentAccessible(true); | ||
| return; | ||
| } | ||
|
|
||
| setIsContentAccessible(false); | ||
|
|
||
| const id = window.setTimeout(() => { | ||
| setIsContentAccessible(true); | ||
| }, ACCESSIBILITY_REEXPOSURE_DELAY_MS); | ||
|
|
||
| return () => window.clearTimeout(id); | ||
| }, [canShare]); | ||
|
|
||
|
Ovgodd marked this conversation as resolved.
|
||
| // Invalidate relevant queries to ensure fresh data on modal open | ||
| useEffect(() => { | ||
| [ | ||
|
|
@@ -197,6 +218,7 @@ export const DocShareModal = ({ doc, onClose, isRootDoc = true }: Props) => { | |
| {t('Share the document')} | ||
| </Text> | ||
| <ButtonCloseModal | ||
| autoFocus={!canShare} | ||
| aria-label={t('Close the share modal')} | ||
| onClick={onClose} | ||
| /> | ||
|
|
@@ -205,7 +227,11 @@ export const DocShareModal = ({ doc, onClose, isRootDoc = true }: Props) => { | |
| hideCloseButton | ||
| > | ||
| <ShareModalStyle /> | ||
| {/* aria-hidden is temporary (300ms) to prevent NVDA from reading | ||
| the entire modal body on open when the search input is absent. | ||
| autoFocus alone on the close button is not enough. */} | ||
| <Box | ||
| aria-hidden={isContentAccessible ? undefined : true} | ||
|
Collaborator
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 am not sure to understand why do we do that ? Seems strange to hide a part of the modal a few milliseconds, without apparent reason.
Collaborator
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. Yes good point, I should have added a comment, just pushed one! I first tried with just |
||
| $height="auto" | ||
| $maxHeight={canViewAccesses ? modalContentHeight : 'none'} | ||
| $overflow="hidden" | ||
|
|
||
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.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: suitenumerique/docs
Length of output: 11433
🏁 Script executed:
Repository: suitenumerique/docs
Length of output: 197
🏁 Script executed:
Repository: suitenumerique/docs
Length of output: 6166
Move focus out of the share content before
canSharehides it.autoFocus={!canShare}only runs on mount, so atrue → falsetransition can leave focus inside anaria-hiddensubtree. Shift focus to the close button on that transition and make the hidden state update synchronously fromcanShareinstead of waiting for the effect.🤖 Prompt for AI Agents