-
Notifications
You must be signed in to change notification settings - Fork 381
Expand file tree
/
Copy pathTable.tsx
More file actions
254 lines (237 loc) · 9.63 KB
/
Table.tsx
File metadata and controls
254 lines (237 loc) · 9.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
import { createContext, forwardRef, useEffect, useRef, useState } from 'react';
import styles from '@patternfly/react-styles/css/components/Table/table';
import stylesGrid from '@patternfly/react-styles/css/components/Table/table-grid';
import stylesTreeView from '@patternfly/react-styles/css/components/Table/table-tree-view';
import { css } from '@patternfly/react-styles';
import { toCamel } from './utils';
import { IVisibility } from './utils/decorators/classNames';
import { handleArrows, setTabIndex } from '@patternfly/react-core/dist/esm/helpers/KeyboardHandler';
import { KeyTypes } from '@patternfly/react-core/dist/esm/helpers/constants';
import { useOUIAProps, OUIAProps } from '@patternfly/react-core/dist/esm/helpers/OUIA/ouia';
import { useHasAnimations } from '@patternfly/react-core/dist/esm/helpers';
import { TableGridBreakpoint, TableVariant } from './TableTypes';
export interface BaseCellProps {
/** Content rendered inside the cell */
children?: React.ReactNode;
/** Additional classes added to the cell */
className?: string;
/** Element to render */
component?: React.ReactNode;
/** Modifies cell to center its contents. */
textCenter?: boolean;
/** Style modifier to apply */
modifier?: 'breakWord' | 'fitContent' | 'nowrap' | 'truncate' | 'wrap';
/** Width percentage modifier */
width?: 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 60 | 70 | 80 | 90 | 100;
/** Visibility breakpoint modifiers */
visibility?: (keyof IVisibility)[];
/** @hide Forwarded ref */
innerRef?: React.Ref<any>;
}
export interface TableProps extends React.HTMLProps<HTMLTableElement>, OUIAProps {
/** Adds an accessible name for the Table */
'aria-label'?: string;
/** Content rendered inside the Table */
children?: React.ReactNode;
/** Additional classes added to the Table */
className?: string;
/**
* Style variant for the Table
* compact: Reduces spacing and makes the table more compact
*/
variant?: TableVariant | 'compact';
/** Render borders */
borders?: boolean;
/** Specifies the grid breakpoints */
gridBreakPoint?: '' | 'grid' | 'grid-md' | 'grid-lg' | 'grid-xl' | 'grid-2xl';
/** A valid WAI-ARIA role to be applied to the table element */
role?: string;
/** @beta Flag indicating if the table should have plain styling with a transparent background */
isPlain?: boolean;
/** @beta Flag indicating if the table should not have plain styling when in the glass theme */
isNoPlainOnGlass?: boolean;
/** If set to true, the table header sticks to the top of its container */
isStickyHeader?: boolean;
/** @hide Forwarded ref */
innerRef?: React.RefObject<any>;
/** Flag indicating table is a tree table */
isTreeTable?: boolean;
/** Flag indicating this table is nested within another table */
isNested?: boolean;
/** Flag indicating this table should be striped. This property works best for a single <tbody> table. Striping may also be done manually by applying this property to Tbody and Tr components. */
isStriped?: boolean;
/** Flag indicating this table contains expandable rows. */
isExpandable?: boolean;
/** @beta Flag indicating whether expandable rows within the table have animations. Expandable rows cannot be dynamically rendered. This prop
* will be removed in the next breaking change, with the default behavior becoming animations always being enabled.
*/
hasAnimations?: boolean;
/** Flag indicating this table's rows will not have the inset typically reserved for expanding/collapsing rows in a tree table. Intended for use on tree tables with no visible rows with children. */
hasNoInset?: boolean;
/** Collection of column spans for nested headers. Deprecated: see https://github.com/patternfly/patternfly/issues/4584 */
nestedHeaderColumnSpans?: number[];
/** Visible text to add alongside the hidden a11y caption for tables with selectable rows. */
selectableRowCaptionText?: string;
/** Value to overwrite the randomly generated data-ouia-component-id.*/
ouiaId?: number | string;
/** Set the value of data-ouia-safe. Only set to true when the component is in a static state, i.e. no animations are occurring. At all other times, this value must be false. */
ouiaSafe?: boolean;
}
interface TableContextProps {
registerSelectableRow?: () => void;
hasAnimations?: boolean;
variant?: TableVariant | 'compact';
isStickyHeader?: boolean;
}
export const TableContext = createContext<TableContextProps>({
registerSelectableRow: () => {},
hasAnimations: false,
variant: undefined,
isStickyHeader: false
});
const TableBase: React.FunctionComponent<TableProps> = ({
children,
className,
variant,
borders = true,
isStickyHeader = false,
isPlain = false,
isNoPlainOnGlass = false,
gridBreakPoint = TableGridBreakpoint.gridMd,
'aria-label': ariaLabel,
role = 'grid',
innerRef,
ouiaId,
ouiaSafe = true,
isTreeTable = false,
isNested = false,
isStriped = false,
isExpandable = false,
hasAnimations: hasAnimationsProp,
hasNoInset = false,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
nestedHeaderColumnSpans,
selectableRowCaptionText,
...props
}: TableProps) => {
const hasAnimations = useHasAnimations(hasAnimationsProp);
const ref = useRef(null);
const tableRef = innerRef || ref;
const [hasSelectableRows, setHasSelectableRows] = useState(false);
const [tableCaption, setTableCaption] = useState<React.JSX.Element | undefined>();
useEffect(() => {
document.addEventListener('keydown', handleKeys);
// sets up roving tab-index to tree tables only
if (tableRef && tableRef.current && tableRef.current.classList.contains('pf-m-tree-view')) {
const tbody = tableRef.current.querySelector('tbody');
tbody && setTabIndex(Array.from(tbody.querySelectorAll('button, a, input')));
}
return function cleanup() {
document.removeEventListener('keydown', handleKeys);
};
}, [tableRef, tableRef.current]);
useEffect(() => {
if (selectableRowCaptionText) {
setTableCaption(
<caption>
{selectableRowCaptionText}
<div className="pf-v6-screen-reader">
This table has selectable rows. It can be navigated by row using tab, and each row can be selected using
space or enter.
</div>
</caption>
);
} else {
setTableCaption(
<caption className="pf-v6-screen-reader">
This table has selectable rows. It can be navigated by row using tab, and each row can be selected using space
or enter.
</caption>
);
}
}, [selectableRowCaptionText]);
const ouiaProps = useOUIAProps('Table', ouiaId, ouiaSafe);
const grid =
stylesGrid.modifiers?.[
toCamel(gridBreakPoint || '').replace(/-?2xl/, '_2xl') as 'grid' | 'gridMd' | 'gridLg' | 'gridXl' | 'grid_2xl'
];
const breakPointPrefix = `treeView${gridBreakPoint.charAt(0).toUpperCase() + gridBreakPoint.slice(1)}`;
const treeGrid =
stylesTreeView.modifiers?.[
toCamel(breakPointPrefix || '').replace(/-?2xl/, '_2xl') as
| 'treeViewGrid'
| 'treeViewGridMd'
| 'treeViewGridLg'
| 'treeViewGridXl'
| 'treeViewGrid_2xl'
];
const handleKeys = (event: KeyboardEvent) => {
if (
isNested ||
!(tableRef && tableRef.current && tableRef.current.classList.contains(stylesTreeView.modifiers.treeView)) || // implements roving tab-index to tree tables only
(tableRef && tableRef.current !== (event.target as HTMLElement).closest(`.${styles.table}:not(.pf-m-nested)`))
) {
return;
}
const activeElement = document.activeElement;
const key = event.key;
const rows = (Array.from(tableRef.current.querySelectorAll('tbody tr')) as Element[]).filter(
(el) => !el.classList.contains('pf-m-disabled') && !(el as HTMLElement).hidden
);
if (key === KeyTypes.Space || key === KeyTypes.Enter) {
(activeElement as HTMLElement).click();
event.preventDefault();
}
const getFocusableElement = (element: Element) =>
element.querySelectorAll('button:not(:disabled), input:not(:disabled), a:not(:disabled)')[0];
handleArrows(
event,
rows,
(element: Element) => element === activeElement.closest('tr'),
getFocusableElement,
['button', 'input', 'a'],
undefined,
false,
true,
false
);
};
const registerSelectableRow = () => {
!hasSelectableRows && setHasSelectableRows(true);
};
return (
<TableContext.Provider value={{ registerSelectableRow, hasAnimations, variant, isStickyHeader }}>
<table
aria-label={ariaLabel}
role={role}
className={css(
className,
styles.table,
isTreeTable ? treeGrid : grid,
styles.modifiers[variant],
!borders && styles.modifiers.noBorderRows,
isStickyHeader && styles.modifiers.stickyHeader,
isTreeTable && stylesTreeView.modifiers.treeView,
isStriped && styles.modifiers.striped,
isExpandable && styles.modifiers.expandable,
isPlain && styles.modifiers.plain,
isNoPlainOnGlass && styles.modifiers.noPlainOnGlass,
hasNoInset && stylesTreeView.modifiers.noInset,
isNested && 'pf-m-nested',
hasAnimations && styles.modifiers.animateExpand
)}
ref={tableRef}
{...(isTreeTable && { role: 'treegrid' })}
{...ouiaProps}
{...props}
>
{hasSelectableRows && tableCaption}
{children}
</table>
</TableContext.Provider>
);
};
export const Table = forwardRef((props: TableProps, ref: React.Ref<HTMLTableElement>) => (
<TableBase {...props} innerRef={ref as React.MutableRefObject<any>} />
));
Table.displayName = 'Table';