mirror of
https://github.com/hyperdxio/hyperdx
synced 2026-04-21 13:37:15 +00:00
Update Drawers to do a proper focus trap (#1290)
Improves user reported issues that clicking outside the modal would click elements unexpectedly, also improves accessibility (keyboard focus trap & esc to exit) Fixes HDX-2642
This commit is contained in:
parent
ff86d40006
commit
bb3539dd5d
16 changed files with 136 additions and 118 deletions
5
.changeset/happy-spies-tickle.md
Normal file
5
.changeset/happy-spies-tickle.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"@hyperdx/app": patch
|
||||
---
|
||||
|
||||
improve drawer a11y
|
||||
|
|
@ -84,7 +84,6 @@
|
|||
"react-hotkeys-hook": "^4.3.7",
|
||||
"react-json-tree": "^0.17.0",
|
||||
"react-markdown": "^8.0.4",
|
||||
"react-modern-drawer": "^1.2.0",
|
||||
"react-papaparse": "^4.4.0",
|
||||
"react-query": "^3.39.3",
|
||||
"react-select": "^5.7.0",
|
||||
|
|
|
|||
|
|
@ -51,8 +51,6 @@ import { parseTimeQuery, useTimeQuery } from './timeQuery';
|
|||
import { KubePhase } from './types';
|
||||
import { formatNumber, formatUptime } from './utils';
|
||||
|
||||
import 'react-modern-drawer/dist/index.css';
|
||||
|
||||
const makeId = () => Math.floor(100000000 * Math.random()).toString(36);
|
||||
|
||||
const getKubePhaseNumber = (phase: string) => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import * as React from 'react';
|
||||
import Link from 'next/link';
|
||||
import Drawer from 'react-modern-drawer';
|
||||
import { StringParam, useQueryParam, withDefault } from 'use-query-params';
|
||||
import { tcFromSource } from '@hyperdx/common-utils/dist/metadata';
|
||||
import { TSource } from '@hyperdx/common-utils/dist/types';
|
||||
|
|
@ -9,6 +8,7 @@ import {
|
|||
Badge,
|
||||
Box,
|
||||
Card,
|
||||
Drawer,
|
||||
Flex,
|
||||
Grid,
|
||||
ScrollArea,
|
||||
|
|
@ -334,14 +334,17 @@ export default function NamespaceDetailsSidePanel({
|
|||
|
||||
return (
|
||||
<Drawer
|
||||
enableOverlay
|
||||
overlayOpacity={0.1}
|
||||
duration={0}
|
||||
open={!!namespaceName}
|
||||
opened={!!namespaceName}
|
||||
onClose={handleClose}
|
||||
direction="right"
|
||||
size={'80vw'}
|
||||
position="right"
|
||||
size="80vw"
|
||||
withCloseButton={false}
|
||||
zIndex={drawerZIndex}
|
||||
styles={{
|
||||
body: {
|
||||
padding: 0,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ZIndexContext.Provider value={drawerZIndex}>
|
||||
<div className={styles.panel}>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import * as React from 'react';
|
||||
import Link from 'next/link';
|
||||
import Drawer from 'react-modern-drawer';
|
||||
import { StringParam, useQueryParam, withDefault } from 'use-query-params';
|
||||
import { tcFromSource } from '@hyperdx/common-utils/dist/metadata';
|
||||
import {
|
||||
|
|
@ -12,6 +11,7 @@ import {
|
|||
Badge,
|
||||
Box,
|
||||
Card,
|
||||
Drawer,
|
||||
Flex,
|
||||
Grid,
|
||||
ScrollArea,
|
||||
|
|
@ -350,14 +350,17 @@ export default function NodeDetailsSidePanel({
|
|||
|
||||
return (
|
||||
<Drawer
|
||||
enableOverlay
|
||||
overlayOpacity={0.1}
|
||||
duration={0}
|
||||
open={!!nodeName}
|
||||
opened={!!nodeName}
|
||||
onClose={handleClose}
|
||||
direction="right"
|
||||
size={'80vw'}
|
||||
position="right"
|
||||
size="80vw"
|
||||
withCloseButton={false}
|
||||
zIndex={drawerZIndex}
|
||||
styles={{
|
||||
body: {
|
||||
padding: 0,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ZIndexContext.Provider value={drawerZIndex}>
|
||||
<div className={styles.panel} data-testid="k8s-node-details-panel">
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import * as React from 'react';
|
||||
import Link from 'next/link';
|
||||
import Drawer from 'react-modern-drawer';
|
||||
import { StringParam, useQueryParam, withDefault } from 'use-query-params';
|
||||
import { tcFromSource } from '@hyperdx/common-utils/dist/metadata';
|
||||
import { TSource } from '@hyperdx/common-utils/dist/types';
|
||||
|
|
@ -8,6 +7,7 @@ import {
|
|||
Anchor,
|
||||
Box,
|
||||
Card,
|
||||
Drawer,
|
||||
Flex,
|
||||
Grid,
|
||||
ScrollArea,
|
||||
|
|
@ -342,14 +342,18 @@ export default function PodDetailsSidePanel({
|
|||
|
||||
return (
|
||||
<Drawer
|
||||
enableOverlay={rowId == null}
|
||||
overlayOpacity={0.1}
|
||||
duration={0}
|
||||
open={!!podName}
|
||||
opened={!!podName}
|
||||
onClose={handleClose}
|
||||
direction="right"
|
||||
position="right"
|
||||
size={isNested ? '70vw' : '80vw'}
|
||||
withCloseButton={false}
|
||||
zIndex={drawerZIndex}
|
||||
styles={{
|
||||
body: {
|
||||
padding: 0,
|
||||
height: '100vh',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ZIndexContext.Provider value={drawerZIndex}>
|
||||
<div className={styles.panel} data-testid="k8s-pod-details-panel">
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@ import { useState } from 'react';
|
|||
import { Button } from 'react-bootstrap';
|
||||
import CopyToClipboard from 'react-copy-to-clipboard';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import Drawer from 'react-modern-drawer';
|
||||
import {
|
||||
DateRange,
|
||||
SearchCondition,
|
||||
SearchConditionLanguage,
|
||||
TSource,
|
||||
} from '@hyperdx/common-utils/dist/types';
|
||||
import { Drawer } from '@mantine/core';
|
||||
import { notifications } from '@mantine/notifications';
|
||||
|
||||
import { Session } from './sessions';
|
||||
|
|
@ -16,8 +16,6 @@ import SessionSubpanel from './SessionSubpanel';
|
|||
import { formatDistanceToNowStrictShort } from './utils';
|
||||
import { ZIndexContext } from './zIndex';
|
||||
|
||||
import 'react-modern-drawer/dist/index.css';
|
||||
|
||||
export default function SessionSidePanel({
|
||||
traceSource,
|
||||
sessionSource,
|
||||
|
|
@ -74,20 +72,24 @@ export default function SessionSidePanel({
|
|||
|
||||
return (
|
||||
<Drawer
|
||||
customIdSuffix={`session-side-panel-${sessionId}`}
|
||||
duration={0}
|
||||
overlayOpacity={0.5}
|
||||
open={sessionId != null}
|
||||
opened={sessionId != null}
|
||||
onClose={() => {
|
||||
if (!subDrawerOpen) {
|
||||
onClose();
|
||||
}
|
||||
}}
|
||||
direction="right"
|
||||
size={'82vw'}
|
||||
style={{ background: '#0F1216' }}
|
||||
className="border-start border-dark"
|
||||
position="right"
|
||||
size="82vw"
|
||||
withCloseButton={false}
|
||||
zIndex={zIndex}
|
||||
styles={{
|
||||
body: {
|
||||
padding: 0,
|
||||
background: '#0F1216',
|
||||
height: '100vh',
|
||||
},
|
||||
}}
|
||||
className="border-start border-dark"
|
||||
>
|
||||
<ZIndexContext.Provider value={zIndex}>
|
||||
<div className="d-flex flex-column h-100">
|
||||
|
|
|
|||
|
|
@ -12,10 +12,9 @@ import { isString } from 'lodash';
|
|||
import { parseAsStringEnum, useQueryState } from 'nuqs';
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import Drawer from 'react-modern-drawer';
|
||||
import { TSource } from '@hyperdx/common-utils/dist/types';
|
||||
import { ChartConfigWithDateRange } from '@hyperdx/common-utils/dist/types';
|
||||
import { Box, OptionalPortal, Stack } from '@mantine/core';
|
||||
import { Box, Drawer, Stack } from '@mantine/core';
|
||||
import { useClickOutside } from '@mantine/hooks';
|
||||
|
||||
import DBRowSidePanelHeader, {
|
||||
|
|
@ -36,7 +35,6 @@ import { RowOverviewPanel } from './DBRowOverviewPanel';
|
|||
import { DBSessionPanel, useSessionId } from './DBSessionPanel';
|
||||
import DBTracePanel from './DBTracePanel';
|
||||
|
||||
import 'react-modern-drawer/dist/index.css';
|
||||
import styles from '@/../styles/LogSidePanel.module.scss';
|
||||
|
||||
export type RowSidePanelContextProps = {
|
||||
|
|
@ -504,52 +502,57 @@ export default function DBRowSidePanelErrorBoundary({
|
|||
}, ['mouseup', 'touchend']);
|
||||
|
||||
return (
|
||||
<OptionalPortal withinPortal={!isNestedPanel}>
|
||||
<Drawer
|
||||
data-testid="row-side-panel"
|
||||
customIdSuffix={`log-side-panel-${rowId}`}
|
||||
duration={300}
|
||||
open={rowId != null}
|
||||
onClose={() => {
|
||||
if (!subDrawerOpen) {
|
||||
_onClose();
|
||||
}
|
||||
}}
|
||||
direction="right"
|
||||
size={`${width}vw`}
|
||||
zIndex={drawerZIndex}
|
||||
enableOverlay={subDrawerOpen}
|
||||
>
|
||||
<ZIndexContext.Provider value={drawerZIndex}>
|
||||
<div className={styles.panel} ref={drawerRef}>
|
||||
<Box className={styles.panelDragBar} onMouseDown={startResize} />
|
||||
<Drawer
|
||||
opened={rowId != null}
|
||||
withCloseButton={false}
|
||||
withinPortal={!isNestedPanel}
|
||||
onClose={() => {
|
||||
if (!subDrawerOpen) {
|
||||
_onClose();
|
||||
}
|
||||
}}
|
||||
position="right"
|
||||
size={`${width}vw`}
|
||||
styles={{
|
||||
body: {
|
||||
padding: '0',
|
||||
height: '100vh',
|
||||
},
|
||||
}}
|
||||
zIndex={drawerZIndex}
|
||||
>
|
||||
<ZIndexContext.Provider value={drawerZIndex}>
|
||||
<div
|
||||
className={styles.panel}
|
||||
ref={drawerRef}
|
||||
data-testid="row-side-panel"
|
||||
>
|
||||
<Box className={styles.panelDragBar} onMouseDown={startResize} />
|
||||
<ErrorBoundary
|
||||
fallbackRender={error => (
|
||||
<Stack>
|
||||
<div className="text-danger px-2 py-1 m-2 fs-7 font-monospace bg-danger-transparent p-4">
|
||||
An error occurred while rendering this event.
|
||||
</div>
|
||||
|
||||
<ErrorBoundary
|
||||
fallbackRender={error => (
|
||||
<Stack>
|
||||
<div className="text-danger px-2 py-1 m-2 fs-7 font-monospace bg-danger-transparent p-4">
|
||||
An error occurred while rendering this event.
|
||||
</div>
|
||||
|
||||
<div className="px-2 py-1 m-2 fs-7 font-monospace bg-dark-grey p-4">
|
||||
{error?.error?.message}
|
||||
</div>
|
||||
</Stack>
|
||||
)}
|
||||
>
|
||||
<DBRowSidePanel
|
||||
source={source}
|
||||
rowId={rowId}
|
||||
onClose={_onClose}
|
||||
isNestedPanel={isNestedPanel}
|
||||
breadcrumbPath={breadcrumbPath}
|
||||
setSubDrawerOpen={setSubDrawerOpen}
|
||||
onBreadcrumbClick={onBreadcrumbClick}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
</ZIndexContext.Provider>
|
||||
</Drawer>
|
||||
</OptionalPortal>
|
||||
<div className="px-2 py-1 m-2 fs-7 font-monospace bg-dark-grey p-4">
|
||||
{error?.error?.message}
|
||||
</div>
|
||||
</Stack>
|
||||
)}
|
||||
>
|
||||
<DBRowSidePanel
|
||||
source={source}
|
||||
rowId={rowId}
|
||||
onClose={_onClose}
|
||||
isNestedPanel={isNestedPanel}
|
||||
breadcrumbPath={breadcrumbPath}
|
||||
setSubDrawerOpen={setSubDrawerOpen}
|
||||
onBreadcrumbClick={onBreadcrumbClick}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
</ZIndexContext.Provider>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -897,6 +897,7 @@ export const RawLogTable = memo(
|
|||
<DBRowTableFieldWithPopover
|
||||
cellValue={cellValue}
|
||||
wrapLinesEnabled={wrapLinesEnabled}
|
||||
tableContainerRef={tableContainerRef}
|
||||
columnName={
|
||||
(cell.column.columnDef.meta as any)
|
||||
?.column
|
||||
|
|
|
|||
|
|
@ -15,12 +15,14 @@ export interface DBRowTableFieldWithPopoverProps {
|
|||
cellValue: unknown;
|
||||
wrapLinesEnabled: boolean;
|
||||
columnName?: string;
|
||||
tableContainerRef?: React.RefObject<HTMLDivElement>;
|
||||
isChart?: boolean;
|
||||
}
|
||||
|
||||
export const DBRowTableFieldWithPopover = ({
|
||||
children,
|
||||
cellValue,
|
||||
tableContainerRef,
|
||||
wrapLinesEnabled,
|
||||
columnName,
|
||||
isChart = false,
|
||||
|
|
@ -151,7 +153,7 @@ export const DBRowTableFieldWithPopover = ({
|
|||
position="top-start"
|
||||
offset={5}
|
||||
opened={opened}
|
||||
zIndex={1}
|
||||
portalProps={{ target: tableContainerRef?.current ?? undefined }}
|
||||
>
|
||||
<Popover.Target>
|
||||
<span
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import * as React from 'react';
|
||||
import Drawer from 'react-modern-drawer';
|
||||
import { JSDataType } from '@hyperdx/common-utils/dist/clickhouse';
|
||||
import { TSource } from '@hyperdx/common-utils/dist/types';
|
||||
import { Card, Stack, Text } from '@mantine/core';
|
||||
import { Card, Drawer, Stack, Text } from '@mantine/core';
|
||||
|
||||
import DBRowSidePanel from '@/components/DBRowSidePanel';
|
||||
import { RawLogTable } from '@/components/DBRowTable';
|
||||
|
|
@ -96,14 +95,17 @@ export default function PatternSidePanel({
|
|||
|
||||
return (
|
||||
<Drawer
|
||||
open={isOpen}
|
||||
opened={isOpen}
|
||||
onClose={selectedRowWhere ? handleCloseRowSidePanel : onClose}
|
||||
direction="right"
|
||||
position="right"
|
||||
size="70vw"
|
||||
withCloseButton={false}
|
||||
zIndex={drawerZIndex}
|
||||
enableOverlay={selectedRowWhere == null}
|
||||
overlayOpacity={0.1}
|
||||
duration={0}
|
||||
styles={{
|
||||
body: {
|
||||
padding: 0,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ZIndexContext.Provider value={drawerZIndex}>
|
||||
<div className={styles.panel}>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import { useCallback, useMemo } from 'react';
|
||||
import { parseAsString, useQueryState } from 'nuqs';
|
||||
import Drawer from 'react-modern-drawer';
|
||||
import type { Filter } from '@hyperdx/common-utils/dist/types';
|
||||
import { Grid, Group, Text } from '@mantine/core';
|
||||
import { Drawer, Grid, Group, Text } from '@mantine/core';
|
||||
|
||||
import { INTEGER_NUMBER_FORMAT, MS_NUMBER_FORMAT } from '@/ChartUtils';
|
||||
import { ChartBox } from '@/components/ChartBox';
|
||||
|
|
@ -14,7 +13,6 @@ import { getExpressions } from '@/serviceDashboard';
|
|||
import { useSource } from '@/source';
|
||||
import { useZIndex, ZIndexContext } from '@/zIndex';
|
||||
|
||||
import 'react-modern-drawer/dist/index.css';
|
||||
import styles from '@/../styles/LogSidePanel.module.scss';
|
||||
|
||||
export default function ServiceDashboardDbQuerySidePanel({
|
||||
|
|
@ -64,12 +62,17 @@ export default function ServiceDashboardDbQuerySidePanel({
|
|||
|
||||
return (
|
||||
<Drawer
|
||||
duration={0}
|
||||
open
|
||||
opened
|
||||
onClose={onClose}
|
||||
direction="right"
|
||||
size={'80vw'}
|
||||
position="right"
|
||||
size="80vw"
|
||||
withCloseButton={false}
|
||||
zIndex={drawerZIndex}
|
||||
styles={{
|
||||
body: {
|
||||
padding: 0,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ZIndexContext.Provider value={drawerZIndex}>
|
||||
<div className={styles.panel}>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import { useCallback, useMemo } from 'react';
|
||||
import { parseAsString, useQueryState } from 'nuqs';
|
||||
import Drawer from 'react-modern-drawer';
|
||||
import type { Filter } from '@hyperdx/common-utils/dist/types';
|
||||
import { Grid, Group, Text } from '@mantine/core';
|
||||
import { Drawer, Grid, Group, Text } from '@mantine/core';
|
||||
|
||||
import {
|
||||
ERROR_RATE_PERCENTAGE_NUMBER_FORMAT,
|
||||
|
|
@ -19,7 +18,6 @@ import { EndpointLatencyChart } from '@/ServicesDashboardPage';
|
|||
import { useSource } from '@/source';
|
||||
import { useZIndex, ZIndexContext } from '@/zIndex';
|
||||
|
||||
import 'react-modern-drawer/dist/index.css';
|
||||
import styles from '@/../styles/LogSidePanel.module.scss';
|
||||
|
||||
export default function ServiceDashboardEndpointSidePanel({
|
||||
|
|
@ -69,12 +67,17 @@ export default function ServiceDashboardEndpointSidePanel({
|
|||
|
||||
return (
|
||||
<Drawer
|
||||
duration={0}
|
||||
open
|
||||
opened
|
||||
onClose={onClose}
|
||||
direction="right"
|
||||
size={'80vw'}
|
||||
position="right"
|
||||
size="80vw"
|
||||
withCloseButton={false}
|
||||
zIndex={drawerZIndex}
|
||||
styles={{
|
||||
body: {
|
||||
padding: 0,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ZIndexContext.Provider value={drawerZIndex}>
|
||||
<div className={styles.panel}>
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ test.describe('Search', { tag: '@search' }, () => {
|
|||
);
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
const sidePanel = page.locator('nav[class*="EZDrawer__container"]');
|
||||
const sidePanel = page.locator('[data-testid="row-side-panel"]');
|
||||
await expect(sidePanel).toBeVisible();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -54,8 +54,8 @@ test.describe('Advanced Search Workflow - Traces', { tag: '@traces' }, () => {
|
|||
await firstRow.click();
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Use the main side panel container (nav element with EZDrawer__container class)
|
||||
const sidePanel = page.locator('nav[class*="EZDrawer__container"]');
|
||||
// Use the main side panel container to verify it is visible
|
||||
const sidePanel = page.locator('[data-testid="row-side-panel"]');
|
||||
await expect(sidePanel).toBeVisible();
|
||||
});
|
||||
|
||||
|
|
|
|||
10
yarn.lock
10
yarn.lock
|
|
@ -4480,7 +4480,6 @@ __metadata:
|
|||
react-hotkeys-hook: "npm:^4.3.7"
|
||||
react-json-tree: "npm:^0.17.0"
|
||||
react-markdown: "npm:^8.0.4"
|
||||
react-modern-drawer: "npm:^1.2.0"
|
||||
react-papaparse: "npm:^4.4.0"
|
||||
react-query: "npm:^3.39.3"
|
||||
react-select: "npm:^5.7.0"
|
||||
|
|
@ -24660,15 +24659,6 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-modern-drawer@npm:^1.2.0":
|
||||
version: 1.2.0
|
||||
resolution: "react-modern-drawer@npm:1.2.0"
|
||||
peerDependencies:
|
||||
react: ">16.0.0"
|
||||
checksum: 10c0/41301a6da8daa899c1f36d0cb3b0258bd89b5109d7052912cd0027dfb626e4b38109b3c1c5d68aa1872f62feac791efe56737e7a220cad894186d3017b7acd76
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-number-format@npm:^5.3.1":
|
||||
version: 5.3.1
|
||||
resolution: "react-number-format@npm:5.3.1"
|
||||
|
|
|
|||
Loading…
Reference in a new issue