Skip to content

Commit 224c446

Browse files
committed
♿️(frontend) restore presenter focus trapping after share links
Restore FocusScope and aria-disabled for accessible presenter nav after share links
1 parent 9c3bc8a commit 224c446

3 files changed

Lines changed: 21 additions & 8 deletions

File tree

src/frontend/apps/impress/src/features/docs/doc-presenter/components/PresenterFloatingBar.tsx

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ const barCss = css`
4343
border: 1px solid var(--c--contextuals--border--surface--primary);
4444
background: var(--c--contextuals--background--surface--primary);
4545
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.05);
46+
47+
button[aria-disabled='true'] {
48+
opacity: 0.4;
49+
cursor: default;
50+
}
4651
`;
4752

4853
const separatorCss = css`
@@ -71,6 +76,12 @@ const PresenterDropdownLayerStyle = createGlobalStyle`
7176
box-sizing: border-box;
7277
height: 32px;
7378
}
79+
80+
.react-aria-Popover .c__dropdown-menu--tiny .c__dropdown-menu-item:focus-visible {
81+
outline: 2px solid var(--c--theme--colors--primary-400, #3b82f6);
82+
outline-offset: -2px;
83+
border-radius: 4px;
84+
}
7485
`;
7586

7687
export const PresenterFloatingBar = ({
@@ -97,7 +108,7 @@ export const PresenterFloatingBar = ({
97108
useEffect(() => {
98109
const id = requestAnimationFrame(() => {
99110
barRef.current
100-
?.querySelector<HTMLButtonElement>('button:not([disabled])')
111+
?.querySelector<HTMLButtonElement>('button:not([aria-disabled="true"])')
101112
?.focus();
102113
});
103114
return () => cancelAnimationFrame(id);
@@ -137,8 +148,8 @@ export const PresenterFloatingBar = ({
137148
size="nano"
138149
color="neutral"
139150
variant="tertiary"
140-
disabled={isFirst}
141-
onClick={onPrev}
151+
aria-disabled={isFirst}
152+
onClick={isFirst ? undefined : onPrev}
142153
aria-label={t('Previous slide')}
143154
icon={<ChevronLeft size="small" />}
144155
/>
@@ -149,8 +160,8 @@ export const PresenterFloatingBar = ({
149160
size="nano"
150161
color="neutral"
151162
variant="tertiary"
152-
disabled={isLast}
153-
onClick={onNext}
163+
aria-disabled={isLast}
164+
onClick={isLast ? undefined : onNext}
154165
aria-label={t('Next slide')}
155166
icon={<ChevronRight size="small" />}
156167
/>

src/frontend/apps/impress/src/features/docs/doc-presenter/components/PresenterOverlay.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ export const PresenterOverlay = ({
173173
}
174174

175175
return createPortal(
176-
<FocusScope autoFocus restoreFocus>
176+
<FocusScope contain autoFocus restoreFocus>
177177
<Box
178178
$css={overlayCss}
179179
role="dialog"

src/frontend/apps/impress/src/features/docs/doc-presenter/hooks/usePresenterShortcuts.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,10 @@ export const usePresenterShortcuts = ({
7474
case 'Escape':
7575
// While fullscreen, the browser handles Esc natively (exits
7676
// fullscreen) and we deliberately stay open. Once out of
77-
// fullscreen, Esc closes the presenter.
78-
if (!isFullscreen) {
77+
// fullscreen, Esc closes the presenter — unless a popover (e.g.
78+
// the share dropdown) is open, in which case Esc should only
79+
// dismiss the popover.
80+
if (!isFullscreen && !document.querySelector('.react-aria-Popover')) {
7981
event.preventDefault();
8082
onClose();
8183
}

0 commit comments

Comments
 (0)