Skip to content

Commit

Permalink
2420 wcag layerpanel (#2426)
Browse files Browse the repository at this point in the history
* fix(layers): layer panel wcag issue closes2420

* fix(layers): layer panel wcag issue closes2420

* fix(layers): layer panel wcag issue closes2420

* fix(layers): fix data table export button  closes2420

* fix(layers): fix guide opening focus  closes2420

* fix(layers): fix unique id of the layer for focus  closes2420

* fix(layers): fix unique id of the layer for focus  closes2420
  • Loading branch information
kaminderpal authored Aug 7, 2024
1 parent 8ce41ba commit eed1456
Show file tree
Hide file tree
Showing 23 changed files with 212 additions and 110 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,7 @@ export function AppBar(props: AppBarProps): JSX.Element {
{appBarComponents.includes(CV_DEFAULT_APPBAR_CORE.EXPORT) && interaction === 'dynamic' && (
<List sx={sxClasses.appBarList}>
<ListItem>
<ExportButton className={` buttonFilled ${activeModalId ? 'active' : ''}`} />
<ExportButton className={` buttonFilled ${activeModalId === CV_DEFAULT_APPBAR_CORE.EXPORT ? 'active' : ''}`} />
</ListItem>
</List>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,12 @@ export function FocusTrapContainer({ children, open = false, id, containerType }
const { t } = useTranslation<string>();

// get values from the store
const { closeModal } = useUIStoreActions();
const { disableFocusTrap } = useUIStoreActions();
const activeTrapGeoView = useUIActiveTrapGeoView();
const focusItem = useUIActiveFocusItem();

const handleClose = (): void => {
closeModal();
document.getElementById(focusItem.callbackElementId as string)?.focus();
disableFocusTrap();
};

// #region REACT HOOKS
Expand All @@ -41,8 +40,8 @@ export function FocusTrapContainer({ children, open = false, id, containerType }
// Log
logger.logTraceUseEffect('FOCUS-TRAP-ELEMENT - activeTrapGeoView', activeTrapGeoView);

if (!activeTrapGeoView) closeModal();
}, [activeTrapGeoView, closeModal]);
if (!activeTrapGeoView) disableFocusTrap();
}, [activeTrapGeoView, disableFocusTrap]);

// if focus trap gets focus, send focus to the exit button
useEffect(() => {
Expand All @@ -56,7 +55,7 @@ export function FocusTrapContainer({ children, open = false, id, containerType }
// #endregion

return (
<FocusTrap open={id === focusItem.activeElementId || open}>
<FocusTrap open={id === focusItem.activeElementId || open} disableAutoFocus>
<Box tabIndex={id === focusItem.activeElementId || open ? 0 : -1} sx={{ height: '100%' }}>
{containerType === CONTAINER_TYPE.FOOTER_BAR && (
<Button
Expand Down
32 changes: 21 additions & 11 deletions packages/geoview-core/src/core/components/common/layer-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ReactNode, memo, useCallback } from 'react';
import { useTheme } from '@mui/material/styles';
import { useTranslation } from 'react-i18next';
import { animated, useSpring } from '@react-spring/web';
import { Box, List, ListItem, ListItemButton, ListItemIcon, Paper, Tooltip, Typography } from '@/ui';
import { Box, List, ListItem, ListItemButton, Paper, Tooltip, Typography } from '@/ui';
import { TypeFeatureInfoEntry, TypeQueryStatus, TypeLayerStatus } from '@/geo/map/map-schema-types';
import { getSxClasses } from './layer-list-style';
import { LayerIcon } from './layer-icon';
Expand All @@ -18,6 +18,7 @@ export interface LayerListEntry {
tooltip?: JSX.Element | string;
numOffeatures?: number;
features?: TypeFeatureInfoEntry[] | undefined | null;
layerUniqueId?: string;
}

interface LayerListProps {
Expand All @@ -27,13 +28,14 @@ interface LayerListProps {
}

interface LayerListItemProps {
id: string;
isSelected: boolean;
layer: LayerListEntry;
onListItemClick: (layer: LayerListEntry) => void;
layerIndex: number;
}

const LayerListItem = memo(function LayerListItem({ isSelected, layer, onListItemClick, layerIndex }: LayerListItemProps) {
const LayerListItem = memo(function LayerListItem({ id, isSelected, layer, onListItemClick, layerIndex }: LayerListItemProps) {
const theme = useTheme();
const sxClasses = getSxClasses(theme);
const { t } = useTranslation<string>();
Expand All @@ -54,11 +56,7 @@ const LayerListItem = memo(function LayerListItem({ isSelected, layer, onListIte
const renderLayerIcon = (): JSX.Element | null => {
// If there is content, this is a guide section with no icon
if (layer.layerPath && !layer.content) {
return (
<ListItemIcon aria-hidden="true">
<LayerIcon layer={layer} />
</ListItemIcon>
);
return <LayerIcon layer={layer} />;
}
return null;
};
Expand Down Expand Up @@ -118,9 +116,14 @@ const LayerListItem = memo(function LayerListItem({ isSelected, layer, onListIte
*/
const handleLayerKeyDown = useCallback(
(e: React.KeyboardEvent, selectedLayer: LayerListEntry): void => {
if (e.key === 'Enter') onListItemClick(selectedLayer);
if (e.key === 'Enter' && !isDisabled) {
onListItemClick(selectedLayer);
// NOTE: did this, bcz when enter is clicked, tab component `handleClick` function is fired,
// to avoid this we have do prevent default so that it doesn't probagate to the parent elements.
e.preventDefault();
}
},
[onListItemClick]
[isDisabled, onListItemClick]
);

const AnimatedPaper = animated(Paper);
Expand All @@ -129,13 +132,18 @@ const LayerListItem = memo(function LayerListItem({ isSelected, layer, onListIte
<AnimatedPaper sx={{ marginBottom: '1rem' }} style={listItemSpring} className={getContainerClass()}>
<Tooltip title={layer.tooltip} placement="top" arrow>
<Box>
<ListItem disablePadding onKeyDown={(e) => handleLayerKeyDown(e, layer)} tabIndex={0}>
<ListItem
disablePadding
onKeyDown={(e) => handleLayerKeyDown(e, layer)}
onClick={() => onListItemClick(layer)}
tabIndex={0}
id={id}
>
<ListItemButton
tabIndex={-1}
selected={isSelected}
// disable when layer features has null value.
disabled={isDisabled || isLoading}
onClick={() => onListItemClick(layer)}
aria-label={layer.layerName}
>
{renderLayerIcon()}
Expand Down Expand Up @@ -167,6 +175,7 @@ export function LayerList({ layerList, selectedLayerPath, onListItemClick }: Lay
{!!layerList.length &&
layerList.map((layer, ind) => (
<LayerListItem
id={`${layer?.layerUniqueId ?? ''}`}
key={layer.layerPath}
// Reason:- (layer?.numOffeatures ?? 1) > 0
// Some of layers will not have numOfFeatures, so to make layer look like selected, we need to set default value to 1.
Expand All @@ -179,6 +188,7 @@ export function LayerList({ layerList, selectedLayerPath, onListItemClick }: Lay
))}
{!layerList.length && (
<LayerListItem
id="dummyPath"
key="dummyPath"
isSelected={false}
layerIndex={0}
Expand Down
7 changes: 6 additions & 1 deletion packages/geoview-core/src/core/components/common/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { ResponsiveGridLayout, ResponsiveGridLayoutExposedMethods } from './resp
import { Tooltip, Typography } from '@/ui';
import { TypeContainerBox } from '@/core/types/global-types';
import { CONTAINER_TYPE } from '@/core/utils/constant';
import { useUIStoreActions } from '@/core/stores/store-interface-and-intial-values/ui-state';

interface LayoutProps {
children?: ReactNode;
Expand Down Expand Up @@ -33,6 +34,7 @@ export function Layout({
const responsiveLayoutRef = useRef<ResponsiveGridLayoutExposedMethods>(null);
const theme = useTheme();

const { setSelectedFooterLayerListItem } = useUIStoreActions();
/**
* Handles clicks to layers in left panel. Sets selected layer.
*
Expand All @@ -43,8 +45,11 @@ export function Layout({
onLayerListClicked?.(layer);
// Show the panel (hiding the layers list in the process if we're on mobile)
responsiveLayoutRef.current?.setIsRightPanelVisible(true);
responsiveLayoutRef.current?.setRightPanelFocus();
// set the focus item when layer item clicked.
setSelectedFooterLayerListItem(`${layer.layerUniqueId}`);
},
[onLayerListClicked]
[onLayerListClicked, setSelectedFooterLayerListItem]
);

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ export const getSxClasses = (theme: Theme): any => ({
border: `2px solid ${theme.palette.geoViewColor.primary.main}`,
borderRadius: '5px',
backgroundColor: theme.palette.geoViewColor.bgColor.light[300],
'&:focus-visible': {
border: '2px solid inherit',
},

'&.guide-container': {
backgroundColor: theme.palette.geoViewColor.white,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState, ReactNode, useCallback, forwardRef, useImperativeHandle, Ref, useEffect } from 'react';
import React, { useState, ReactNode, useCallback, forwardRef, useImperativeHandle, Ref, useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { useTheme } from '@mui/material/styles';
import Markdown from 'markdown-to-jsx';
Expand All @@ -10,6 +10,7 @@ import FullScreenDialog from './full-screen-dialog';
import { logger } from '@/core/utils/logger';
import { ArrowBackIcon, ArrowForwardIcon, CloseIcon, QuestionMarkIcon } from '@/ui/icons';
import { useAppGuide, useAppFullscreenActive } from '@/core/stores/store-interface-and-intial-values/app-state';
import { useUISelectedFooterLayerListItem } from '@/core/stores/store-interface-and-intial-values/ui-state';
import { TypeContainerBox } from '@/core/types/global-types';
import { CONTAINER_TYPE } from '@/core/utils/constant';

Expand All @@ -28,6 +29,7 @@ interface ResponsiveGridLayoutProps {

interface ResponsiveGridLayoutExposedMethods {
setIsRightPanelVisible: (isVisible: boolean) => void;
setRightPanelFocus: () => void;
}

const ResponsiveGridLayout = forwardRef(
Expand All @@ -51,6 +53,7 @@ const ResponsiveGridLayout = forwardRef(
const { t } = useTranslation<string>();
const guide = useAppGuide();
const isMapFullScreen = useAppFullscreenActive();
const selectedFooterLayerListItem = useUISelectedFooterLayerListItem();

const [isRightPanelVisible, setIsRightPanelVisible] = useState(false);
const [isGuideOpen, setIsGuideOpen] = useState(false);
Expand All @@ -59,11 +62,18 @@ const ResponsiveGridLayout = forwardRef(

// Custom hook for calculating the height of footer panel
const { leftPanelRef, rightPanelRef, panelTitleRef } = useFooterPanelHeight({ footerPanelTab: 'default' });
const rightMainRef = useRef<HTMLDivElement>();

// Expose imperative methods to parent component
useImperativeHandle(ref, function handleRef() {
return {
setIsRightPanelVisible: (isVisible: boolean) => setIsRightPanelVisible(isVisible),
setRightPanelFocus: () => {
if (rightMainRef.current) {
rightMainRef.current.tabIndex = 0;
rightMainRef.current?.focus();
}
},
};
});

Expand All @@ -88,6 +98,23 @@ const ResponsiveGridLayout = forwardRef(
}
}, [hideEnlargeBtn, isEnlarged]);

// return back the focus to layeritem for which right panel was opened.
useEffect(() => {
const handleEscapeKey = (event: KeyboardEvent): void => {
if (event.key === 'Escape' && selectedFooterLayerListItem.length && rightMainRef.current) {
rightMainRef.current.tabIndex = -1;
document.getElementById(selectedFooterLayerListItem)?.focus();
}
};

const rightPanel = rightMainRef.current;
rightPanel?.addEventListener('keydown', handleEscapeKey);

return () => {
rightPanel?.removeEventListener('keydown', handleEscapeKey);
};
}, [selectedFooterLayerListItem]);

/**
* Handles click on the Enlarge button.
*
Expand All @@ -110,6 +137,7 @@ const ResponsiveGridLayout = forwardRef(
const handleOpenGuide = useCallback((): void => {
if (guideContentIds) {
setIsGuideOpen(true);
rightMainRef.current?.focus();
}
}, [setIsGuideOpen, guideContentIds]);

Expand Down Expand Up @@ -256,7 +284,9 @@ const ResponsiveGridLayout = forwardRef(
</FullScreenDialog>

<Box
ref={rightMainRef}
sx={sxClasses.rightGridContent}
tabIndex={-1}
className={isGuideOpen ? 'responsive-layout-right-main-content guide-container' : 'responsive-layout-right-main-content'}
>
{content}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useTheme } from '@mui/material/styles';
import { delay } from 'lodash';
Expand All @@ -17,6 +17,7 @@ import {
useUIActiveFooterBarTabId,
useUIAppbarComponents,
} from '@/core/stores/store-interface-and-intial-values/ui-state';
import { useGeoViewMapId } from '@/core/stores/geoview-store';
import { LayerListEntry, Layout } from '@/core/components/common';
import { logger } from '@/core/utils/logger';
import { useFeatureFieldInfos } from './hooks';
Expand All @@ -39,10 +40,11 @@ export function Datapanel({ fullWidth = false, containerType = CONTAINER_TYPE.FO
const { t } = useTranslation();
const theme = useTheme();

const layerData = useDataTableAllFeaturesDataArray();

const dataTableRef = useRef<HTMLDivElement>();
const [isLoading, setIsLoading] = useState(false);

const mapId = useGeoViewMapId();
const layerData = useDataTableAllFeaturesDataArray();
const tableHeight = useDataTableTableHeight();
const selectedLayerPath = useDataTableSelectedLayerPath();
const datatableSettings = useDataTableLayerSettings();
Expand Down Expand Up @@ -225,11 +227,13 @@ export function Datapanel({ fullWidth = false, containerType = CONTAINER_TYPE.FO
if (!isLayerDisabled() && isSelectedLayerHasFeatures()) {
return (
<>
{orderedLayerData.map((data: MappedLayerDataType) => (
<Box key={data.layerPath}>
{data.layerPath === selectedLayerPath ? <DataTable data={data} layerPath={data.layerPath} tableHeight={tableHeight} /> : null}
</Box>
))}
{orderedLayerData
.filter((data) => data.layerPath === selectedLayerPath)
.map((data: MappedLayerDataType) => (
<Box key={data.layerPath} ref={dataTableRef}>
<DataTable data={data} layerPath={data.layerPath} tableHeight={tableHeight} />
</Box>
))}
</>
);
}
Expand All @@ -255,6 +259,7 @@ export function Datapanel({ fullWidth = false, containerType = CONTAINER_TYPE.FO

return orderedLayerData.map((layer) => ({
...layer,
layerUniqueId: `${mapId}-${TABS.DATA_TABLE}-${layer.layerPath}`,
layerFeatures: getFeaturesOfLayer(layer.layerPath),
tooltip: getLayerTooltip(layer.layerName ?? '', layer.layerPath),
mapFilteredIcon: isMapFilteredSelectedForLayer(layer.layerPath) && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export default function DataTableModal(): JSX.Element {
const [isLoading, setIsLoading] = useState(true);

// get store function
const { closeModal } = useUIStoreActions();
const { disableFocusTrap } = useUIStoreActions();
const activeModalId = useUIActiveFocusItem().activeElementId;
const selectedLayer = useLayerSelectedLayerPath();

Expand Down Expand Up @@ -148,7 +148,7 @@ export default function DataTableModal(): JSX.Element {
}, [layersData, selectedLayer]);

return (
<Dialog open={activeModalId === 'layerDataTable'} onClose={closeModal} maxWidth="xl">
<Dialog open={activeModalId === 'layerDataTable'} onClose={disableFocusTrap} maxWidth="xl">
<DialogTitle>{`${t('legend.tableDetails')} ${layer?.layerName ?? selectedLayer}`}</DialogTitle>
<DialogContent sx={{ overflow: 'hidden' }}>
{isLoading && (
Expand Down Expand Up @@ -187,7 +187,7 @@ export default function DataTableModal(): JSX.Element {
)}
</DialogContent>
<DialogActions>
<Button fullWidth variant="contained" className="buttonOutlineFilled" onClick={closeModal} type="text" autoFocus>
<Button fullWidth variant="contained" className="buttonOutlineFilled" onClick={disableFocusTrap} type="text" autoFocus>
{t('general.close')}
</Button>
</DialogActions>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ function DataTable({ data, layerPath, tableHeight = '500px' }: DataTableProps):
const { globalFilter, setGlobalFilter } = useGlobalFilter({ layerPath });
// #endregion

const { openModal } = useUIStoreActions();
const { enableFocusTrap } = useUIStoreActions();

/**
* Create table header cell
Expand Down Expand Up @@ -364,7 +364,7 @@ function DataTable({ data, layerPath, tableHeight = '500px' }: DataTableProps):
color="primary"
onClick={() => {
setSelectedFeature(feature);
openModal({ activeElementId: 'featureDetailDataTable', callbackElementId: 'table-details' });
enableFocusTrap({ activeElementId: 'featureDetailDataTable', callbackElementId: 'table-details' });
}}
>
<InfoOutlinedIcon />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ function ExportButton({ rows, columns, children }: ExportButtonProps): JSX.Eleme
});
const csvExporter = new ExportToCsv(getCsvOptions());
csvExporter.generateCsv(csvRows);
setAnchorEl(null);
}, [getCsvOptions, rows]);

return (
Expand Down
Loading

0 comments on commit eed1456

Please sign in to comment.