Skip to content

Commit 836ecd2

Browse files
authored
Merge pull request #4443 from pallava-joshi/fix-focus-trapping
frontend: DetailsDrawer: Activity: Fix focus trapping when resource drawer is open
2 parents d62a898 + 54065cc commit 836ecd2

File tree

14 files changed

+75
-3
lines changed

14 files changed

+75
-3
lines changed

frontend/src/components/activity/Activity.tsx

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,39 @@ export function SingleActivityRenderer({
291291
};
292292
}, [location]);
293293

294+
// Handle click outside to close temporary activities
295+
const selectedResource = useTypedSelector(state => state.drawerMode.selectedResource);
296+
const isDetailDrawerEnabled = useTypedSelector(state => state.drawerMode.isDetailDrawerEnabled);
297+
const isDetailsDrawerSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
298+
const isDetailsDrawerOpen =
299+
!!selectedResource && isDetailDrawerEnabled && !isDetailsDrawerSmallScreen;
300+
301+
useEffect(() => {
302+
function handleClickOutside(event: MouseEvent) {
303+
const target = event.target as Node;
304+
const isClickInside = activityElementRef.current?.contains(target);
305+
306+
if (
307+
activity.temporary &&
308+
!minimized &&
309+
!isOverview &&
310+
activityElementRef.current &&
311+
!isClickInside &&
312+
!isDetailsDrawerOpen
313+
) {
314+
Activity.close(id);
315+
}
316+
}
317+
318+
if (activity.temporary && !minimized && !isOverview) {
319+
document.addEventListener('mousedown', handleClickOutside);
320+
321+
return () => {
322+
document.removeEventListener('mousedown', handleClickOutside);
323+
};
324+
}
325+
}, [activity.temporary, minimized, isOverview, id, location, isDetailsDrawerOpen]);
326+
294327
return (
295328
<ActivityContext.Provider value={activity}>
296329
<Box

frontend/src/components/common/Resource/DetailsDrawer.tsx

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import Box from '@mui/material/Box';
1818
import { useTheme } from '@mui/material/styles';
1919
import useMediaQuery from '@mui/material/useMediaQuery';
20+
import { useCallback, useEffect, useRef } from 'react';
2021
import { useTranslation } from 'react-i18next';
2122
import { useDispatch } from 'react-redux';
2223
import { setSelectedResource } from '../../../redux/drawerModeSlice';
@@ -32,16 +33,38 @@ export default function DetailsDrawer() {
3233
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
3334
const isDetailDrawerEnabled = useTypedSelector(state => state?.drawerMode?.isDetailDrawerEnabled);
3435

35-
function closeDrawer() {
36+
const drawerRef = useRef<HTMLDivElement>(null);
37+
38+
const closeDrawer = useCallback(() => {
3639
dispatch(setSelectedResource(undefined));
37-
}
40+
}, [dispatch]);
41+
42+
useEffect(() => {
43+
if (selectedResource && !isSmallScreen && isDetailDrawerEnabled && drawerRef.current) {
44+
drawerRef.current.focus();
45+
}
46+
}, [selectedResource, isSmallScreen, isDetailDrawerEnabled]);
47+
48+
useEffect(() => {
49+
const mainElement = document.getElementById('main');
50+
if (!mainElement) return;
51+
52+
if (selectedResource && !isSmallScreen && isDetailDrawerEnabled) {
53+
mainElement.setAttribute('inert', '');
54+
return () => {
55+
mainElement.removeAttribute('inert');
56+
};
57+
}
58+
}, [selectedResource, isSmallScreen, isDetailDrawerEnabled]);
3859

3960
if (!selectedResource || isSmallScreen || !isDetailDrawerEnabled) {
4061
return null;
4162
}
4263

4364
return (
4465
<Box
66+
ref={drawerRef}
67+
tabIndex={-1}
4568
sx={{
4669
position: 'absolute',
4770
backgroundColor: 'background.paper',
@@ -54,9 +77,13 @@ export default function DetailsDrawer() {
5477
zIndex: 1,
5578
border: '1px solid',
5679
borderColor: theme.palette.divider,
80+
outline: 'none',
5781
}}
58-
role="complementary"
82+
role="dialog"
83+
aria-label={t('Resource details')}
5984
aria-describedby="resource-details-content"
85+
aria-modal="true"
86+
data-details-drawer="true"
6087
>
6188
<Box
6289
sx={{

frontend/src/i18n/locales/de/translation.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@
264264
"Deleted {{ itemsLength }} items.": "{{ itemsLength }} Elemente gelöscht.",
265265
"Error deleting {{ itemsLength }} items.": "Fehler beim Löschen von {{ itemsLength }} Elementen.",
266266
"Delete items": "Elemente löschen",
267+
"Resource details": "",
267268
"Loading documentation": "Dokumentation laden",
268269
"No documentation available.": "",
269270
"No documentation for type {{ docsType }}.": "Keine Dokumentation für Typ {{ docsType }}.",

frontend/src/i18n/locales/en/translation.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@
264264
"Deleted {{ itemsLength }} items.": "Deleted {{ itemsLength }} items.",
265265
"Error deleting {{ itemsLength }} items.": "Error deleting {{ itemsLength }} items.",
266266
"Delete items": "Delete items",
267+
"Resource details": "Resource details",
267268
"Loading documentation": "Loading documentation",
268269
"No documentation available.": "No documentation available.",
269270
"No documentation for type {{ docsType }}.": "No documentation for type {{ docsType }}.",

frontend/src/i18n/locales/es/translation.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@
265265
"Deleted {{ itemsLength }} items.": "Se ha eliminado {{ itemsLength }} items.",
266266
"Error deleting {{ itemsLength }} items.": "Error al eliminar {{ itemsLength }} items.",
267267
"Delete items": "Eliminar items",
268+
"Resource details": "",
268269
"Loading documentation": "Cargando documentación",
269270
"No documentation available.": "No hay documentación disponible.",
270271
"No documentation for type {{ docsType }}.": "Sin documentación para el tipo {{ docsType }}.",

frontend/src/i18n/locales/fr/translation.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@
265265
"Deleted {{ itemsLength }} items.": "",
266266
"Error deleting {{ itemsLength }} items.": "",
267267
"Delete items": "",
268+
"Resource details": "",
268269
"Loading documentation": "Chargement de la documentation",
269270
"No documentation available.": "",
270271
"No documentation for type {{ docsType }}.": "Pas de documentation pour le type {{ docsType }}.",

frontend/src/i18n/locales/hi/translation.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@
264264
"Deleted {{ itemsLength }} items.": "{{ itemsLength }} आइटम हटा दिए गए।",
265265
"Error deleting {{ itemsLength }} items.": "{{ itemsLength }} आइटम हटाने में त्रुटि।",
266266
"Delete items": "आइटम हटाएँ",
267+
"Resource details": "",
267268
"Loading documentation": "दस्तावेज़ीकरण लोड हो रहा है",
268269
"No documentation available.": "कोई दस्तावेज़ीकरण उपलब्ध नहीं है।",
269270
"No documentation for type {{ docsType }}.": "प्रकार {{ docsType }} के लिए कोई दस्तावेज़ीकरण नहीं है।",

frontend/src/i18n/locales/it/translation.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@
265265
"Deleted {{ itemsLength }} items.": "{{ itemsLength }} elementi eliminati.",
266266
"Error deleting {{ itemsLength }} items.": "Errore durante l'eliminazione di {{ itemsLength }} elementi.",
267267
"Delete items": "Elimina elementi",
268+
"Resource details": "",
268269
"Loading documentation": "Caricamento della documentazione",
269270
"No documentation available.": "Nessuna documentazione disponibile.",
270271
"No documentation for type {{ docsType }}.": "Nessuna documentazione per il tipo {{ docsType }}.",

frontend/src/i18n/locales/ja/translation.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,7 @@
263263
"Deleted {{ itemsLength }} items.": "{{ itemsLength }} 個のアイテムを削除しました。",
264264
"Error deleting {{ itemsLength }} items.": "{{ itemsLength }} 個のアイテムの削除中にエラーが発生しました。",
265265
"Delete items": "アイテムの削除",
266+
"Resource details": "",
266267
"Loading documentation": "ドキュメントの読み込み中",
267268
"No documentation available.": "利用可能なドキュメントがありません。",
268269
"No documentation for type {{ docsType }}.": "タイプ {{ docsType }} のドキュメントはありません。",

frontend/src/i18n/locales/ko/translation.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,7 @@
263263
"Deleted {{ itemsLength }} items.": "{{ itemsLength }}개 항목 삭제됨.",
264264
"Error deleting {{ itemsLength }} items.": "{{ itemsLength }}개 항목 삭제 중 오류 발생.",
265265
"Delete items": "항목 삭제",
266+
"Resource details": "",
266267
"Loading documentation": "문서 로딩 중",
267268
"No documentation available.": "사용 가능한 문서가 없습니다.",
268269
"No documentation for type {{ docsType }}.": "{{ docsType }} 유형에 대한 문서가 없습니다.",

0 commit comments

Comments
 (0)