Line data Source code
1 : import * as React from 'react';
2 : import * as styles from './result_table_overlay.module.css';
3 :
4 : import type { QueryExecutionState } from '../../query/query_execution_state.js';
5 : import { ButtonSize, ButtonVariant, IconButton } from '../../ui/foundations/button.js';
6 : import { useFocusTrap } from '../../ui/foundations/focus.js';
7 : import { Overlay, OverlaySize } from '../../ui/foundations/overlay.js';
8 : import { ScreenFullIcon, ScreenNormalIcon, SymbolIcon } from '../../ui/foundations/symbol_icon.js';
9 : import { QueryResultDetails } from './query_result_details.js';
10 : import { TableColumnHeader } from './data_table_cell.js';
11 :
12 : export interface ResultTableOverlayDependencies {
13 : QueryResultDetails: typeof QueryResultDetails;
14 : }
15 :
16 1 : const DEFAULT_DEPENDENCIES: ResultTableOverlayDependencies = {
17 1 : QueryResultDetails,
18 1 : };
19 :
20 : interface Props {
21 : scriptName: string;
22 : query: QueryExecutionState;
23 : onClose: () => void;
24 : dependencies?: ResultTableOverlayDependencies;
25 : }
26 :
27 : /// The height of the card before it is enlarged, the table grows up to it and scrolls then
28 : const MAX_CARD_HEIGHT = 600;
29 : const HEADER_HEIGHT = 44;
30 : // The table opens popups of its own (search, cell details), a click into them must not close the card
31 1 : const IGNORE_OUTSIDE_CLICK = () => {};
32 1 : const CloseIcon = SymbolIcon('x_16');
33 :
34 : /// The full result table of a feed entry or a dashboard card in a card over the page: the table with its column plots
35 : /// under a header with the name of the script, a button to enlarge the card to the window and a close button.
36 1 : export function ResultTableOverlay(props: Props): React.ReactElement {
37 1 : const dependencies = props.dependencies ?? DEFAULT_DEPENDENCIES;
38 1 : const dialogRef = React.useRef<HTMLElement | null>(null);
39 1 : const [expanded, setExpanded] = React.useState(false);
40 :
41 : // Focus the dialog, not its first button
42 1 : useFocusTrap({
43 1 : containerRef: dialogRef as React.RefObject<HTMLElement>,
44 1 : initialFocusRef: dialogRef as React.RefObject<HTMLElement>,
45 1 : restoreFocusOnCleanUp: true,
46 1 : });
47 :
48 1 : return (
49 1 : <Overlay
50 1 : centered
51 1 : width={OverlaySize.XXL}
52 1 : height={OverlaySize.AUTO}
53 1 : maxHeight={OverlaySize.XL}
54 1 : preventFocusOnOpen
55 1 : expanded={expanded}
56 1 : onEscape={props.onClose}
57 1 : onClickOutside={IGNORE_OUTSIDE_CLICK}
58 : >
59 1 : <section
60 1 : ref={dialogRef}
61 1 : className={styles.dialog}
62 1 : role="dialog"
63 1 : aria-modal="true"
64 1 : tabIndex={-1}
65 1 : aria-label={`Query results of ${props.scriptName}`}
66 1 : data-expanded={expanded || undefined}
67 1 : data-testid="result-table-overlay"
68 : >
69 1 : <header className={styles.header}>
70 1 : <h2 className={styles.title}>{props.scriptName}</h2>
71 1 : <div className={styles.spacer} />
72 1 : <IconButton
73 1 : variant={ButtonVariant.Invisible}
74 1 : size={ButtonSize.Small}
75 1 : aria-label={expanded ? 'Restore size' : 'Enlarge'}
76 1 : aria-pressed={expanded}
77 1 : data-testid="result-table-overlay-resize"
78 1 : onClick={() => setExpanded(e => !e)}
79 : >
80 1 : {expanded ? <ScreenNormalIcon size={16} /> : <ScreenFullIcon size={16} />}
81 1 : </IconButton>
82 1 : <IconButton
83 1 : variant={ButtonVariant.Invisible}
84 1 : size={ButtonSize.Small}
85 1 : aria-label="Close query results"
86 1 : data-testid="result-table-overlay-close"
87 1 : onClick={props.onClose}
88 : >
89 1 : <CloseIcon size={16} />
90 1 : </IconButton>
91 1 : </header>
92 1 : <div className={styles.body}>
93 1 : <dependencies.QueryResultDetails
94 1 : query={props.query}
95 1 : debugMode={false}
96 : // The enlarged card fills the window, the table fills the card
97 1 : fitHeight={!expanded}
98 1 : maxHeight={expanded ? undefined : MAX_CARD_HEIGHT - HEADER_HEIGHT}
99 1 : columnHeader={TableColumnHeader.WithColumnPlots}
100 1 : />
101 1 : </div>
102 1 : </section>
103 1 : </Overlay>
104 : );
105 1 : }
|