@splunk/react-ui 5.7.0 → 5.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/Accordion.js +6 -6
  2. package/Box.js +83 -34
  3. package/CHANGELOG.md +34 -0
  4. package/CollapsiblePanel.js +11 -11
  5. package/ComboBox.js +31 -27
  6. package/ControlGroup.js +92 -91
  7. package/DefinitionList.js +9 -9
  8. package/Drawer.d.ts +2 -0
  9. package/Drawer.js +679 -0
  10. package/DualListbox.js +1 -1
  11. package/JSONTree.js +73 -72
  12. package/Link.js +2 -2
  13. package/MIGRATION.md +10 -0
  14. package/Menu.js +338 -240
  15. package/Modal.js +127 -109
  16. package/Multiselect.js +437 -351
  17. package/Paginator.js +14 -12
  18. package/Popover.js +4 -1
  19. package/README.md +11 -0
  20. package/RadioBar.js +1 -1
  21. package/Search.js +103 -88
  22. package/Select.js +42 -40
  23. package/SelectBase.js +374 -328
  24. package/SidePanel.js +346 -167
  25. package/SlidingPanels.js +11 -11
  26. package/StepBar.js +7 -7
  27. package/Switch.js +5 -5
  28. package/Text.js +24 -24
  29. package/TextArea.js +7 -7
  30. package/TransitionOpen.js +204 -185
  31. package/docs-llm/Accordion.md +267 -0
  32. package/docs-llm/Anchor Menu.md +115 -0
  33. package/docs-llm/Anchor.md +54 -0
  34. package/docs-llm/AnimationToggle.md +254 -0
  35. package/docs-llm/Avatar.md +298 -0
  36. package/docs-llm/Badge.md +212 -0
  37. package/docs-llm/Breadcrumbs.md +306 -0
  38. package/docs-llm/Button Group.md +53 -0
  39. package/docs-llm/Button.md +361 -0
  40. package/docs-llm/Card Layout.md +286 -0
  41. package/docs-llm/Card.md +619 -0
  42. package/docs-llm/Checkbox.md +218 -0
  43. package/docs-llm/Chip.md +291 -0
  44. package/docs-llm/Clickable.md +160 -0
  45. package/docs-llm/Code.md +292 -0
  46. package/docs-llm/Collapsible Panel.md +744 -0
  47. package/docs-llm/Color.md +253 -0
  48. package/docs-llm/Column Layout.md +391 -0
  49. package/docs-llm/Combo Box.md +540 -0
  50. package/docs-llm/Control Group.md +594 -0
  51. package/docs-llm/Date.md +270 -0
  52. package/docs-llm/Definition List.md +278 -0
  53. package/docs-llm/Divider.md +216 -0
  54. package/docs-llm/Drawer.md +414 -0
  55. package/docs-llm/Dropdown.md +472 -0
  56. package/docs-llm/Dual Listbox.md +325 -0
  57. package/docs-llm/File.md +653 -0
  58. package/docs-llm/Form Rows.md +374 -0
  59. package/docs-llm/Heading.md +179 -0
  60. package/docs-llm/Image.md +109 -0
  61. package/docs-llm/JSON Tree.md +260 -0
  62. package/docs-llm/Layer.md +74 -0
  63. package/docs-llm/Layout.md +50 -0
  64. package/docs-llm/Link.md +318 -0
  65. package/docs-llm/List.md +189 -0
  66. package/docs-llm/Markdown.md +179 -0
  67. package/docs-llm/Menu.md +735 -0
  68. package/docs-llm/Message Bar.md +236 -0
  69. package/docs-llm/Message.md +248 -0
  70. package/docs-llm/Modal.md +443 -0
  71. package/docs-llm/Monogram.md +159 -0
  72. package/docs-llm/Multiselect.md +937 -0
  73. package/docs-llm/Number.md +298 -0
  74. package/docs-llm/Paginator.md +395 -0
  75. package/docs-llm/Paragraph.md +148 -0
  76. package/docs-llm/Phone Number.md +254 -0
  77. package/docs-llm/Popover.md +166 -0
  78. package/docs-llm/Progress.md +141 -0
  79. package/docs-llm/Radio Bar.md +303 -0
  80. package/docs-llm/Radio List.md +350 -0
  81. package/docs-llm/Resize.md +362 -0
  82. package/docs-llm/Screen Reader Content.md +73 -0
  83. package/docs-llm/Scroll Container Context.md +155 -0
  84. package/docs-llm/Scroll.md +152 -0
  85. package/docs-llm/Search.md +381 -0
  86. package/docs-llm/Select.md +985 -0
  87. package/docs-llm/Side Panel.md +777 -0
  88. package/docs-llm/Slider.md +339 -0
  89. package/docs-llm/Sliding Panels.md +340 -0
  90. package/docs-llm/Split Button.md +295 -0
  91. package/docs-llm/Static Content.md +90 -0
  92. package/docs-llm/Step Bar.md +292 -0
  93. package/docs-llm/Switch.md +268 -0
  94. package/docs-llm/Tab Bar.md +439 -0
  95. package/docs-llm/Tab Layout.md +398 -0
  96. package/docs-llm/Table.md +2642 -0
  97. package/docs-llm/Text Area.md +253 -0
  98. package/docs-llm/Text.md +339 -0
  99. package/docs-llm/Tooltip.md +325 -0
  100. package/docs-llm/Transition Open.md +406 -0
  101. package/docs-llm/Tree.md +586 -0
  102. package/docs-llm/Typography.md +125 -0
  103. package/docs-llm/Wait Spinner.md +121 -0
  104. package/docs-llm/llms.txt +97 -0
  105. package/package.json +6 -5
  106. package/types/src/Box/Box.d.ts +2 -10
  107. package/types/src/Drawer/Body.d.ts +17 -0
  108. package/types/src/Drawer/Drawer.d.ts +114 -0
  109. package/types/src/Drawer/DrawerContext.d.ts +11 -0
  110. package/types/src/Drawer/Footer.d.ts +25 -0
  111. package/types/src/Drawer/Header.d.ts +41 -0
  112. package/types/src/Drawer/docs/examples/Basic.d.ts +6 -0
  113. package/types/src/Drawer/docs/examples/ContainerPosition.d.ts +7 -0
  114. package/types/src/Drawer/docs/examples/InitialFocus.d.ts +9 -0
  115. package/types/src/Drawer/docs/examples/InlinePosition.d.ts +7 -0
  116. package/types/src/Drawer/docs/examples/PagePosition.d.ts +7 -0
  117. package/types/src/Drawer/index.d.ts +2 -0
  118. package/types/src/JSONTree/JSONTree.d.ts +12 -5
  119. package/types/src/JSONTree/renderTreeItems.d.ts +2 -1
  120. package/types/src/Menu/Item.d.ts +2 -1
  121. package/types/src/Menu/docs/examples/SelectableCheckbox.d.ts +7 -0
  122. package/types/src/Modal/Modal.d.ts +1 -2
  123. package/types/src/Select/Option.d.ts +6 -3
  124. package/types/src/Select/Select.d.ts +8 -5
  125. package/types/src/Select/docs/examples/Dimmed.d.ts +7 -0
  126. package/types/src/SelectBase/OptionBase.d.ts +6 -3
  127. package/types/src/SelectBase/SelectBase.d.ts +8 -3
  128. package/types/src/SidePanel/SidePanel.d.ts +43 -2
  129. package/types/src/SidePanel/docs/examples/DockLayout.d.ts +17 -0
  130. package/types/src/SidePanel/docs/examples/InitialFocus.d.ts +9 -0
  131. package/types/src/TransitionOpen/TransitionOpen.d.ts +29 -4
  132. package/types/src/useKeyPress/index.d.ts +9 -2
  133. package/types/src/useOnClickOutside/index.d.ts +2 -0
  134. package/types/src/useOnClickOutside/useOnClickOutside.d.ts +4 -0
  135. package/useKeyPress.js +23 -18
  136. package/useOnClickOutside.d.ts +2 -0
  137. package/useOnClickOutside.js +79 -0
  138. package/types/src/RadioList/docs/examples/Row.d.ts +0 -6
@@ -0,0 +1,2642 @@
1
+ # Table
2
+
3
+ ## Overview
4
+
5
+
6
+ > Image: Illustration of a table component.
7
+
8
+
9
+ ## When to use this component
10
+ - There’s a need to arrange data in a precise way that’s easy to understand and interact with.
11
+ - In contexts where data points are compared side-by-side.
12
+ - For schedules, pricing plans, product features, or other structured data.
13
+
14
+ ## When to use another component
15
+ - For simpler or single data points, use a List component instead.
16
+ - For data collection, create a form with Control Group.
17
+ - Consider a graph or chart if there are trends, patterns or exceptions to highlight in the data.
18
+
19
+ ```mermaid
20
+ graph TD
21
+ accDescr: Decision tree that guides on when to use the Table component or something else
22
+ A(Do you need to collect data?) -- Yes --- B(Control Group)
23
+ A -- No --- C(Would your data benefit from graphical representation?)
24
+ C -- Yes --- D(Charts or graphs)
25
+ C -- No --- E(Do you need to display structured and comparable data)
26
+ E -- Yes --- F(Table)
27
+ E -- No --- G(List)
28
+ ```
29
+
30
+ ### Check out
31
+ - [List] [1]
32
+ - [Control Group] [2]
33
+ - [Data Visualization] [3]
34
+
35
+ ## Behaviors
36
+
37
+ ### Row selection
38
+ > Image: A table displays several rows, each with a checkbox in the first column. Three rows are selected, indicated by check marks in their respective checkboxes. The table contains columns for
39
+
40
+
41
+ ### Column resizing
42
+ > Image: A table shows several rows of data with columns for
43
+
44
+
45
+ ### Reordering
46
+
47
+ #### Columns
48
+ > Image: A table shows multiple rows with columns for
49
+
50
+
51
+ #### Rows
52
+ > Image: table displays several rows with columns for
53
+
54
+
55
+ ### Striped rows
56
+ Striped rows provide an additional affordance to distinguish between alternating rows, especially in data-heavy tables.
57
+ > Image: A table with four columns: Name, Status, Path, and Max Size. The background color alternates between rows to help distinguish between them. The table lists database entries with names such as Simple, Audit, Test, and Splunk. The Status column displays either
58
+
59
+
60
+
61
+ ## Usage
62
+
63
+ ### Minimize the number of columns
64
+ It's easier to scan many rows than it is to scan many columns.
65
+ > Image: Two examples of Table illustrating the impact of column quantity on scanability. The first example, with a heart-eyes emoji, has fewer columns, indicating it is easier to scan. The second example, with a grimacing face emoji, has many more columns, making it harder to read and scan due to the increased number of columns.
66
+
67
+
68
+ ### Limit column data
69
+ Each column should only contain a single piece of data to maintain readability, accessibility, and usability. If a column contains multiple pieces of data, it can become confusing and difficult for users to parse and understand the information.
70
+
71
+ > Image: Two examples of Table data, tables have several rows and columns for
72
+
73
+
74
+ ### Filtering and sorting
75
+ Start with filtering and sorting on the individual table columns before using other components. For more complex interactions, [dropdowns in column headers](#dropdown) and the [Table toolbar area](#table-toolbar-area) in Table layout.
76
+
77
+ #### Filter
78
+ A column can be filtered as indicated by a funnel icon next to the column header title. You can identify how many filters are applied to a column by the count '2/4' displayed after the column header title.
79
+
80
+ > Image: Table displays rows of data with columns labeled
81
+
82
+
83
+ #### Sort
84
+ A column can be sorted as indicated by the arrows after the column title. The direction of the arrow indicates whether the column is sorted in ascending or descending order. A column with the ArrowUpDown icon indicates that it can be sorted but is currently unsorted.
85
+
86
+ > Image: Table displays rows of data with columns labeled
87
+
88
+
89
+ #### Dropdown
90
+ Use a dropdown in the column header when you need to provide filter and sort on the same column or you have multiple actions.
91
+ > Image: Table displays rows of data with columns labeled
92
+
93
+
94
+ ### Pagination
95
+ - Use pagination to avoid overloading the Table with too much data and improve performance.
96
+ - Consider including the option for users to increase or decrease the number of items in a table.
97
+ - Recommended to place a Paginator at the top and bottom of longer tables table for improved keyboard navigation.
98
+
99
+ > Image: A table displays multiple rows of data with columns labeled
100
+
101
+
102
+ ### Editing
103
+ Editing table data should be initiated via an action in the actions column, which either opens a modal or toggles a side panel.
104
+
105
+ #### Modal
106
+ Use a modal to edit or update a table data if you have only a few inputs.
107
+
108
+ > Image: A modal window is displayed over a dimmed table in the background, indicating that the user is in the process of editing or updating table data. The modal contains two input fields and action buttons at the bottom, with a
109
+
110
+
111
+ #### Side panel
112
+ Use a side panel when you have several inputs. The side panel allows users to edit information without fully obstructing the view of the underlying table, providing context while making changes. The side panel can either overlay, push content, or occupy reserved space and remain static.
113
+
114
+ > Image: Table is partially visible on the left side of the screen, with a side panel open on the right side for editing or updating table data. The side panel contains input fields and action buttons similar to a modal, with a
115
+
116
+
117
+ ## Table layout
118
+
119
+ Tables fill their container's width, but if they become too wide, readability can suffer as the content spreads too far apart. To improve legibility, ensure that the layout and alignment are properly adjusted. Additionally, organize the table content based on the importance of the information according to your users' needs.
120
+
121
+ ### Title and description
122
+ All tables should include a title and, if possible, a description to provide context and clarity.
123
+
124
+ > Image: A table is displayed with a clear title,
125
+
126
+
127
+ ### Table toolbar area
128
+ The toolbar area is reserved for elements like filter, find, tabs, pagination, buttons, and other interactive elements that manipulate the table data.
129
+
130
+ Organize elements into:
131
+ 1. Find (Search) and Filter (Select, Multiselect)
132
+ 2. Additional actions or inputs
133
+ 3. Select (items per page) and Paginator (page control)
134
+
135
+ > Image: A table displays a highlighted area above the table which is reserved for elements like filter, find, tabs, pagination, buttons, and other interactive elements that manipulate the table data. The toolbar starts with a search bar and radio bar options labeled
136
+
137
+
138
+ ### Actions
139
+ A maximum of three buttons should be used in the actions column. The first two actions should be the most relevant, with the third button reserved for overflow.
140
+
141
+ If you have button labels with 2 words, want to reduce the space of the actions column, or using an icon alone is too vague, it's recommended to use a menu button for all actions.
142
+
143
+ > Image: A table displays rows of data with columns labeled
144
+
145
+
146
+ ### Show and hide columns
147
+ Use the actions table header to control the visibility of table columns.
148
+
149
+ > Image: A table displays rows of data with columns labeled
150
+
151
+
152
+ ### Table data
153
+
154
+ #### Loading Table data
155
+ When loading data, it's best to render the table title, description and column headers on page load, and then load all rows at once.
156
+
157
+ > Image: A table is displayed with a title at the top,
158
+
159
+
160
+ #### Empty or no data
161
+ Empty state displays both when no rows exist and when no rows exist due to filtering.
162
+
163
+ > Image: table is displayed with a title at the top,
164
+
165
+
166
+ ## Content
167
+
168
+ ### Column header truncation and text wrapping
169
+ Stick with one or two words, use sentence-case capitalization, and avoid truncating or text wrapping if possible.
170
+
171
+ #### Truncation
172
+ > Image: Two examples illustrating the approach to handling truncation in column headers. The first example with heart eyes emoji, features clear and concise column headers like
173
+
174
+
175
+ #### Text wrapping
176
+ > Image: Two examples illustrating the approach to handling text wrapping in column headers. The first example with heart eyes emoji shows clear, unwrapped column headers
177
+
178
+
179
+ ### Row truncation and text wrapping
180
+ Ensure enough of the data is displayed for legibility. Text wrapping should avoided if possible. Otherwise, limit text wrapping to a maximum of two lines to maintain row height consistency.
181
+
182
+ #### Truncation
183
+
184
+ > Image: Two examples demonstrating the approach handling long text within table rows. The first example with heart eyes emoji, shows the full text in the
185
+
186
+
187
+ #### Text wrapping
188
+ Text wrapping will make table rows taller and potentially harder to scan quickly, so it should be avoided to preserve readability and maintain the layout.
189
+
190
+ > Image: Two examples demonstrating the approach handling long text within table rows. The first example with heart eyes emoji, maintains a single line of text per row, ensuring that the full path is visible without wrapping, which keeps the table clean and easy to read. The second example with a grimacing face emoji, wraps the text in the
191
+
192
+
193
+ ### Content alignment
194
+ Align numerical data to the right and text data to the left for easier comparison.
195
+
196
+ > Image: Two examples that demonstrate aligning text and numerical data. The first example with heart eyes emoji, correctly aligns text data, such as
197
+
198
+
199
+ ### Missing values
200
+ To indicate null or not applicable (N/A) values in the data, use an em dash (—) where there are gaps.
201
+
202
+ Leaving gaps blank can create confusion and make it harder for users to discern whether data is missing or simply omitted. It’s important to use a clear indicator for missing values to improve readability and understanding.
203
+
204
+ > Image: Two examples demonstrating the approach to handling null or not applicable (N/A) values. The first example with heart eyes emoji, uses an em dash (—) to clearly indicate gaps or missing data in the
205
+
206
+
207
+ ### Color
208
+ Do not rely solely on color to convey information. Ensure there are text or icons for better accessibility.
209
+
210
+ > Image: Two examples demonstrating the approach to color usage in tables. The first example with heart eyes emoji, uses both color and text/icons in the
211
+
212
+
213
+ [1]: ./List
214
+ [2]: ./ControlGroup
215
+ [3]: https://splunkui.splunk.com/Packages/visualizations/?path=/Overview
216
+
217
+ ## Examples
218
+
219
+
220
+ ### data
221
+
222
+ ```typescript
223
+ import React from 'react';
224
+
225
+ import Table from '@splunk/react-ui/Table';
226
+
227
+ const data = [
228
+ { name: 'Rylan', age: 42, email: 'Angelita_Weimann42@gmail.com' },
229
+ { name: 'Amelia', age: 24, email: 'Dexter.Trantow57@hotmail.com' },
230
+ { name: 'Estevan', age: 56, email: 'Aimee7@hotmail.com' },
231
+ { name: 'Florence', age: 71, email: 'Jarrod.Bernier13@yahoo.com' },
232
+ { name: 'Tressa', age: 38, email: 'Yadira1@hotmail.com' },
233
+ ];
234
+
235
+
236
+ function Basic() {
237
+ return (
238
+ <Table>
239
+ <Table.Head>
240
+ <Table.HeadCell>Name</Table.HeadCell>
241
+ <Table.HeadCell align="right">Age</Table.HeadCell>
242
+ <Table.HeadCell>Email</Table.HeadCell>
243
+ </Table.Head>
244
+ <Table.Body>
245
+ {data.map((row) => (
246
+ <Table.Row key={row.email}>
247
+ <Table.Cell>{row.name}</Table.Cell>
248
+ <Table.Cell align="right">{row.age}</Table.Cell>
249
+ <Table.Cell>{row.email}</Table.Cell>
250
+ </Table.Row>
251
+ ))}
252
+ </Table.Body>
253
+ </Table>
254
+ );
255
+ }
256
+
257
+ export default Basic;
258
+ ```
259
+
260
+
261
+
262
+ ### Sortable Columns
263
+
264
+ ```typescript
265
+ import React, { useState, useCallback } from 'react';
266
+
267
+ import Table, { HeadCellSortHandler } from '@splunk/react-ui/Table';
268
+
269
+ interface Row {
270
+ email: string;
271
+ name: string;
272
+ }
273
+
274
+ const data: Row[] = [
275
+ { name: 'Rylan', email: 'Angelita_Weimann42@gmail.com' },
276
+ { name: 'Amelia', email: 'Dexter.Trantow57@hotmail.com' },
277
+ { name: 'Estevan', email: 'Aimee7@hotmail.com' },
278
+ { name: 'Florence', email: 'Jarrod.Bernier13@yahoo.com' },
279
+ { name: 'Tressa', email: 'Yadira1@hotmail.com' },
280
+ ];
281
+
282
+ interface Column {
283
+ label: string;
284
+ sortKey: 'email' | 'name';
285
+ }
286
+
287
+ const columns: Column[] = [
288
+ { sortKey: 'name', label: 'Name' },
289
+ { sortKey: 'email', label: 'Email' },
290
+ ];
291
+
292
+
293
+ function SortableColumns() {
294
+ const [sortKey, setSortKey] = useState<'email' | 'name'>('name');
295
+ const [sortDir, setSortDir] = useState<'asc' | 'desc'>('asc');
296
+
297
+ const handleSort: HeadCellSortHandler = useCallback(
298
+ (e, { sortKey: newSortKey }) => {
299
+ setSortKey((prevSortKey) => {
300
+ const prevSortDir = prevSortKey === newSortKey ? sortDir : 'none';
301
+ const nextSortDir = prevSortDir === 'asc' ? 'desc' : 'asc';
302
+ setSortDir(nextSortDir);
303
+
304
+ return newSortKey as 'email' | 'name';
305
+ });
306
+ },
307
+ [sortDir]
308
+ );
309
+
310
+ return (
311
+ <Table>
312
+ <Table.Head>
313
+ {columns.map((headData) => (
314
+ <Table.HeadCell
315
+ key={headData.sortKey}
316
+ onSort={handleSort}
317
+ sortKey={headData.sortKey}
318
+ sortDir={headData.sortKey === sortKey ? sortDir : 'none'}
319
+ >
320
+ {headData.label}
321
+ </Table.HeadCell>
322
+ ))}
323
+ </Table.Head>
324
+ <Table.Body>
325
+ {data
326
+ .sort((rowA, rowB) => {
327
+ if (sortDir === 'asc') {
328
+ return rowA[sortKey] > rowB[sortKey] ? 1 : -1;
329
+ }
330
+ return rowB[sortKey] > rowA[sortKey] ? 1 : -1;
331
+ })
332
+ .map((row) => (
333
+ <Table.Row key={row.email}>
334
+ <Table.Cell>{row.name}</Table.Cell>
335
+ <Table.Cell>{row.email}</Table.Cell>
336
+ </Table.Row>
337
+ ))}
338
+ </Table.Body>
339
+ </Table>
340
+ );
341
+ }
342
+
343
+ export default SortableColumns;
344
+ ```
345
+
346
+
347
+
348
+ ### Clickable Cells
349
+
350
+ ```typescript
351
+ import React, { useState } from 'react';
352
+
353
+ import DL, { Term as DT, Description as DD } from '@splunk/react-ui/DefinitionList';
354
+ import Table, { CellClickHandler } from '@splunk/react-ui/Table';
355
+ import Typography from '@splunk/react-ui/Typography';
356
+
357
+ interface Row {
358
+ email: string;
359
+ name: string;
360
+ }
361
+
362
+ const initialData: Row[] = [
363
+ { name: 'Rylan', email: 'Angelita_Weimann42@gmail.com' },
364
+ { name: 'Amelia', email: 'Dexter.Trantow57@hotmail.com' },
365
+ { name: 'Estevan', email: 'Aimee7@hotmail.com' },
366
+ { name: 'Florence', email: 'Jarrod.Bernier13@yahoo.com' },
367
+ { name: 'Tressa', email: 'Yadira1@hotmail.com' },
368
+ ];
369
+
370
+
371
+ function Click() {
372
+ const [data] = useState<Row[]>(initialData);
373
+ const [clickedRow, setClickedRow] = useState<string | undefined>(undefined);
374
+
375
+ const handleClick: CellClickHandler = (e, rowData) => {
376
+ setClickedRow(JSON.stringify(rowData));
377
+ };
378
+
379
+ return (
380
+ <div>
381
+ <Table>
382
+ <Table.Head>
383
+ <Table.HeadCell>Name</Table.HeadCell>
384
+ <Table.HeadCell>Email</Table.HeadCell>
385
+ </Table.Head>
386
+ <Table.Body>
387
+ {data.map((row) => (
388
+ <Table.Row key={row.email}>
389
+ <Table.Cell onClick={handleClick} data={row}>
390
+ {row.name}
391
+ </Table.Cell>
392
+ <Table.Cell>{row.email}</Table.Cell>
393
+ </Table.Row>
394
+ ))}
395
+ </Table.Body>
396
+ </Table>
397
+ <aside style={{ marginTop: 20 }} aria-live="polite" aria-relevant="text">
398
+ <Typography as="p">Click a name to see the returned data</Typography>
399
+ {clickedRow && (
400
+ <div style={{ overflow: 'scroll', marginBottom: 10 }}>
401
+ <DL>
402
+ <DT>Data:</DT>
403
+ <DD>
404
+ <code>{clickedRow}</code>
405
+ </DD>
406
+ </DL>
407
+ </div>
408
+ )}
409
+ </aside>
410
+ </div>
411
+ );
412
+ }
413
+
414
+ export default Click;
415
+ ```
416
+
417
+
418
+
419
+ ### Clickable Rows
420
+
421
+ ```typescript
422
+ import React, { useState } from 'react';
423
+
424
+ import DL, { Term as DT, Description as DD } from '@splunk/react-ui/DefinitionList';
425
+ import Table, { RowClickHandler } from '@splunk/react-ui/Table';
426
+ import Typography from '@splunk/react-ui/Typography';
427
+
428
+ interface Row {
429
+ email: string;
430
+ name: string;
431
+ }
432
+
433
+ const initialData: Row[] = [
434
+ { name: 'Rylan', email: 'Angelita_Weimann42@gmail.com' },
435
+ { name: 'Amelia', email: 'Dexter.Trantow57@hotmail.com' },
436
+ { name: 'Estevan', email: 'Aimee7@hotmail.com' },
437
+ { name: 'Florence', email: 'Jarrod.Bernier13@yahoo.com' },
438
+ { name: 'Tressa', email: 'Yadira1@hotmail.com' },
439
+ ];
440
+
441
+
442
+ function ClickRows() {
443
+ const [data] = useState<Row[]>(initialData);
444
+ const [clickedRow, setClickedRow] = useState<string | undefined>(undefined);
445
+
446
+ const handleClick: RowClickHandler = (e, rowData) => {
447
+ setClickedRow(JSON.stringify(rowData));
448
+ };
449
+
450
+ return (
451
+ <div>
452
+ <Table>
453
+ <Table.Head>
454
+ <Table.HeadCell>Name</Table.HeadCell>
455
+ <Table.HeadCell>Email</Table.HeadCell>
456
+ </Table.Head>
457
+ <Table.Body>
458
+ {data.map((row) => (
459
+ <Table.Row key={row.email} onClick={handleClick} data={row}>
460
+ <Table.Cell>{row.name}</Table.Cell>
461
+ <Table.Cell>{row.email}</Table.Cell>
462
+ </Table.Row>
463
+ ))}
464
+ </Table.Body>
465
+ </Table>
466
+ <aside style={{ marginTop: 20 }} aria-live="polite" aria-relevant="text">
467
+ <Typography as="p">Click a name to see the returned data</Typography>
468
+ {clickedRow && (
469
+ <div style={{ overflow: 'scroll' }}>
470
+ <DL>
471
+ <DT>Data:</DT>
472
+ <DD>
473
+ <code>{clickedRow}</code>
474
+ </DD>
475
+ </DL>
476
+ </div>
477
+ )}
478
+ </aside>
479
+ </div>
480
+ );
481
+ }
482
+
483
+ export default ClickRows;
484
+ ```
485
+
486
+
487
+
488
+ ### Selectable Rows
489
+
490
+ ```typescript
491
+ import React, { useState, useCallback } from 'react';
492
+
493
+ import { cloneDeep, find } from 'lodash';
494
+
495
+ import Table, { RowRequestToggleHandler } from '@splunk/react-ui/Table';
496
+
497
+ interface Row {
498
+ email: string;
499
+ name: string;
500
+ selected: boolean;
501
+ }
502
+
503
+ const initialData: Row[] = [
504
+ { name: 'Rylan', email: 'Angelita_Weimann42@gmail.com', selected: false },
505
+ { name: 'Amelia', email: 'Dexter.Trantow57@hotmail.com', selected: false },
506
+ { name: 'Estevan', email: 'Aimee7@hotmail.com', selected: false },
507
+ { name: 'Florence', email: 'Jarrod.Bernier13@yahoo.com', selected: false },
508
+ { name: 'Tressa', email: 'Yadira1@hotmail.com', selected: false },
509
+ ];
510
+
511
+
512
+ function Selectable() {
513
+ const [data, setData] = useState<Row[]>(initialData);
514
+
515
+ const handleToggle: RowRequestToggleHandler = useCallback((event, { email }) => {
516
+ setData((prevData) => {
517
+ const updatedData = cloneDeep(prevData);
518
+
519
+ const selectedRow = find(updatedData, { email });
520
+ if (selectedRow) {
521
+ selectedRow.selected = !selectedRow.selected;
522
+
523
+ return updatedData;
524
+ }
525
+ return [];
526
+ });
527
+ }, []);
528
+
529
+ const getRowSelectionState = useCallback((rows: Row[]): 'none' | 'all' | 'some' => {
530
+ const selectedCount = rows.filter((row) => row.selected).length;
531
+ if (selectedCount === 0) return 'none';
532
+ if (selectedCount === rows.length) return 'all';
533
+ return 'some';
534
+ }, []);
535
+
536
+ const handleToggleAll = useCallback(() => {
537
+ setData((prevData) => {
538
+ const selected = getRowSelectionState(prevData) !== 'all';
539
+ return prevData.map((row) => ({ ...row, selected }));
540
+ });
541
+ }, [getRowSelectionState]);
542
+
543
+ return (
544
+ <div>
545
+ <Table
546
+ onRequestToggleAllRows={handleToggleAll}
547
+ rowSelection={getRowSelectionState(data)}
548
+ >
549
+ <Table.Head>
550
+ <Table.HeadCell>Name</Table.HeadCell>
551
+ <Table.HeadCell>Email</Table.HeadCell>
552
+ </Table.Head>
553
+ <Table.Body>
554
+ {data.map((row) => (
555
+ <Table.Row
556
+ key={row.email}
557
+ onRequestToggle={handleToggle}
558
+ data={row}
559
+ selected={row.selected}
560
+ >
561
+ <Table.Cell>{row.name}</Table.Cell>
562
+ <Table.Cell>{row.email}</Table.Cell>
563
+ </Table.Row>
564
+ ))}
565
+ </Table.Body>
566
+ </Table>
567
+ </div>
568
+ );
569
+ }
570
+
571
+ export default Selectable;
572
+ ```
573
+
574
+
575
+
576
+ ### Dropdowns in Header
577
+
578
+ ```typescript
579
+ import React, { useState, useCallback } from 'react';
580
+
581
+ import Menu from '@splunk/react-ui/Menu';
582
+ import Table from '@splunk/react-ui/Table';
583
+
584
+ interface Row {
585
+ email: string;
586
+ name: string;
587
+ }
588
+
589
+ const data: Row[] = [
590
+ { name: 'Rylan', email: 'Angelita_Weimann42@gmail.com' },
591
+ { name: 'Amelia', email: 'Dexter.Trantow57@hotmail.com' },
592
+ { name: 'Estevan', email: 'Aimee7@hotmail.com' },
593
+ { name: 'Florence', email: 'Jarrod.Bernier13@yahoo.com' },
594
+ { name: 'Tressa', email: 'Yadira1@hotmail.com' },
595
+ ];
596
+
597
+ interface Column {
598
+ align: 'left' | 'right';
599
+ label: string;
600
+ sortKey: 'email' | 'name';
601
+ }
602
+
603
+ const initialColumns: Column[] = [
604
+ { sortKey: 'name', label: 'Name', align: 'left' },
605
+ { sortKey: 'email', label: 'Email', align: 'left' },
606
+ ];
607
+
608
+
609
+ function HeadDropdownCell() {
610
+ const [columns, setColumns] = useState<Column[]>(initialColumns);
611
+ const [sortKey, setSortKey] = useState<'email' | 'name'>('name');
612
+ const [sortDir, setSortDir] = useState<'asc' | 'desc'>('asc');
613
+
614
+ const handleSort = useCallback(
615
+ (
616
+ e: React.MouseEvent,
617
+ {
618
+ sortKey: newSortKey,
619
+ sortDir: newSortDir,
620
+ }: { sortKey: 'email' | 'name'; sortDir: 'asc' | 'desc' }
621
+ ) => {
622
+ setSortKey(newSortKey);
623
+ setSortDir(newSortDir);
624
+ },
625
+ []
626
+ );
627
+
628
+ const handleAlign = useCallback(
629
+ (
630
+ e: React.MouseEvent,
631
+ {
632
+ sortKey: targetSortKey,
633
+ align,
634
+ }: { sortKey: 'email' | 'name'; align: 'left' | 'right' }
635
+ ) => {
636
+ setColumns((prevColumns) =>
637
+ prevColumns.map((column) =>
638
+ column.sortKey !== targetSortKey ? column : { ...column, align }
639
+ )
640
+ );
641
+ },
642
+ []
643
+ );
644
+
645
+ return (
646
+ <Table>
647
+ <Table.Head>
648
+ {columns.map((headData) => (
649
+ <Table.HeadDropdownCell
650
+ label={headData.label}
651
+ key={headData.sortKey}
652
+ align={headData.align}
653
+ >
654
+ <Menu>
655
+ <Menu.Item
656
+ selectable
657
+ selected={headData.sortKey === sortKey && sortDir === 'asc'}
658
+ onClick={(e: React.MouseEvent) =>
659
+ handleSort(e, { sortKey: headData.sortKey, sortDir: 'asc' })
660
+ }
661
+ >
662
+ Sort Ascending
663
+ </Menu.Item>
664
+ <Menu.Item
665
+ selectable
666
+ selected={headData.sortKey === sortKey && sortDir === 'desc'}
667
+ onClick={(e: React.MouseEvent) =>
668
+ handleSort(e, { sortKey: headData.sortKey, sortDir: 'desc' })
669
+ }
670
+ >
671
+ Sort Descending
672
+ </Menu.Item>
673
+ <Menu.Divider />
674
+ <Menu.Item
675
+ selectable
676
+ selected={headData.align === 'left'}
677
+ onClick={(e: React.MouseEvent) =>
678
+ handleAlign(e, { sortKey: headData.sortKey, align: 'left' })
679
+ }
680
+ >
681
+ Align Left
682
+ </Menu.Item>
683
+ <Menu.Item
684
+ selectable
685
+ selected={headData.align === 'right'}
686
+ onClick={(e: React.MouseEvent) =>
687
+ handleAlign(e, { sortKey: headData.sortKey, align: 'right' })
688
+ }
689
+ >
690
+ Align Right
691
+ </Menu.Item>
692
+ </Menu>
693
+ </Table.HeadDropdownCell>
694
+ ))}
695
+ </Table.Head>
696
+ <Table.Body>
697
+ {data
698
+ .sort((rowA, rowB) => {
699
+ if (sortDir === 'asc') {
700
+ return rowA[sortKey] > rowB[sortKey] ? 1 : -1;
701
+ }
702
+ return rowB[sortKey] > rowA[sortKey] ? 1 : -1;
703
+ })
704
+ .map((row) => (
705
+ <Table.Row key={row.email}>
706
+ <Table.Cell align={columns[0].align}>{row.name}</Table.Cell>
707
+ <Table.Cell align={columns[1].align}>{row.email}</Table.Cell>
708
+ </Table.Row>
709
+ ))}
710
+ </Table.Body>
711
+ </Table>
712
+ );
713
+ }
714
+
715
+ export default HeadDropdownCell;
716
+ ```
717
+
718
+
719
+
720
+ ### Filter Column Values
721
+
722
+ ```typescript
723
+ import React, { useState } from 'react';
724
+
725
+ import Filter from '@splunk/react-icons/Filter';
726
+ import Menu from '@splunk/react-ui/Menu';
727
+ import Table from '@splunk/react-ui/Table';
728
+
729
+ const kindValues = ['Amazon S3', 'Index', 'Indexer', 'Lookup', 'View'] as const;
730
+
731
+ type Kind = (typeof kindValues)[number];
732
+
733
+ interface Row {
734
+ name: string;
735
+ kind: Kind;
736
+ }
737
+
738
+ const data: Row[] = [
739
+ { name: 'S3_bucket_1', kind: 'Amazon S3' },
740
+ { name: 'S3_bucket_2', kind: 'Amazon S3' },
741
+ { name: 'Splunk_CMP_1', kind: 'Index' },
742
+ { name: 'EC_1', kind: 'View' },
743
+ { name: 'EC_2', kind: 'View' },
744
+ ];
745
+
746
+
747
+ function FilterableTable() {
748
+ const [filter, setFilter] = useState<Kind[]>([]);
749
+
750
+ const toggleFilterValue = (filterValue: Kind) => {
751
+ setFilter((prevFilter) =>
752
+ prevFilter.includes(filterValue)
753
+ ? prevFilter.filter((f) => f !== filterValue)
754
+ : [...prevFilter, filterValue]
755
+ );
756
+ };
757
+
758
+ return (
759
+ <Table>
760
+ <Table.Head>
761
+ <Table.HeadCell>Name</Table.HeadCell>
762
+ <Table.HeadDropdownCell
763
+ label={
764
+ <>
765
+ <Filter variant={filter.length > 0 ? 'filled' : 'outlined'} />
766
+ Kind
767
+ {filter.length > 0 ? ` ${filter.length}/${kindValues.length}` : ''}
768
+ </>
769
+ }
770
+ >
771
+ <Menu>
772
+ {kindValues.map((kind) => (
773
+ <Menu.Item
774
+ key={kind}
775
+ selectableAppearance="checkbox"
776
+ selectable
777
+ selected={filter.includes(kind)}
778
+ onClick={() => toggleFilterValue(kind)}
779
+ >
780
+ {kind}
781
+ </Menu.Item>
782
+ ))}
783
+ </Menu>
784
+ </Table.HeadDropdownCell>
785
+ </Table.Head>
786
+ <Table.Body>
787
+ {data
788
+ .filter((row) => filter.length === 0 || filter.includes(row.kind))
789
+ .map((row) => (
790
+ <Table.Row key={row.name}>
791
+ <Table.Cell>{row.name}</Table.Cell>
792
+ <Table.Cell>{row.kind}</Table.Cell>
793
+ </Table.Row>
794
+ ))}
795
+ </Table.Body>
796
+ </Table>
797
+ );
798
+ }
799
+
800
+ export default FilterableTable;
801
+ ```
802
+
803
+
804
+
805
+ ### Fixed Header
806
+
807
+ ```typescript
808
+ import React from 'react';
809
+
810
+ import Table from '@splunk/react-ui/Table';
811
+
812
+ interface Row {
813
+ email: string;
814
+ name: string;
815
+ }
816
+
817
+ const data: Row[] = [
818
+ { name: 'Rylan', email: 'Angelita_Weimann42@gmail.com' },
819
+ { name: 'Amelia', email: 'Dexter.Trantow57@hotmail.com' },
820
+ { name: 'Estevan', email: 'Aimee7@hotmail.com' },
821
+ { name: 'Florence', email: 'Jarrod.Bernier13@yahoo.com' },
822
+ { name: 'Tressa', email: 'Yadira1@hotmail.com' },
823
+ { name: 'Bernice', email: 'bernice.Gilbert@gmail.com' },
824
+ { name: 'Adrian', email: 'adrian7456@gmail.com' },
825
+ { name: 'Ester', email: 'esternyc@gmail.com' },
826
+ { name: 'Andrew', email: 'andrew.fillmore2@gmail.com' },
827
+ { name: 'Felix', email: 'felixfelix@hotmail.com' },
828
+ ];
829
+
830
+
831
+ function FixedHeader() {
832
+ return (
833
+ <Table headType="fixed" innerStyle={{ maxHeight: 160 }}>
834
+ <Table.Head>
835
+ <Table.HeadCell>Name</Table.HeadCell>
836
+ <Table.HeadCell>Email</Table.HeadCell>
837
+ </Table.Head>
838
+ <Table.Body>
839
+ {data.map((row) => (
840
+ <Table.Row key={row.email}>
841
+ <Table.Cell>{row.name}</Table.Cell>
842
+ <Table.Cell>{row.email}</Table.Cell>
843
+ </Table.Row>
844
+ ))}
845
+ </Table.Body>
846
+ </Table>
847
+ );
848
+ }
849
+
850
+ export default FixedHeader;
851
+ ```
852
+
853
+
854
+
855
+ ### Expandable Rows
856
+
857
+ ```typescript
858
+ import React from 'react';
859
+
860
+ import DL from '@splunk/react-ui/DefinitionList';
861
+ import Table from '@splunk/react-ui/Table';
862
+
863
+ interface Row {
864
+ email: string;
865
+ name: string;
866
+ }
867
+
868
+ const data: Row[] = [
869
+ { name: 'Rylan', email: 'Angelita_Weimann42@gmail.com' },
870
+ { name: 'Amelia', email: 'Dexter.Trantow57@hotmail.com' },
871
+ { name: 'Estevan', email: 'Aimee7@hotmail.com' },
872
+ { name: 'Florence', email: 'Jarrod.Bernier13@yahoo.com' },
873
+ { name: 'Tressa', email: 'Yadira1@hotmail.com' },
874
+ ];
875
+
876
+
877
+ function getExpansionRow(row: Row) {
878
+ return (
879
+ <Table.Row key={`${row.email}-expansion`}>
880
+ <Table.Cell style={{ borderTop: 'none' }} colSpan={2}>
881
+ <DL>
882
+ <DL.Term>Name</DL.Term>
883
+ <DL.Description>{row.name}</DL.Description>
884
+ <DL.Term>Email</DL.Term>
885
+ <DL.Description>{row.email}</DL.Description>
886
+ </DL>
887
+ </Table.Cell>
888
+ </Table.Row>
889
+ );
890
+ }
891
+
892
+ function ExpandableRows() {
893
+ return (
894
+ <Table rowExpansion="single">
895
+ <Table.Head>
896
+ <Table.HeadCell>Name</Table.HeadCell>
897
+ <Table.HeadCell>Email</Table.HeadCell>
898
+ </Table.Head>
899
+ <Table.Body>
900
+ {data.map((row) => (
901
+ <Table.Row key={row.email} expansionRow={getExpansionRow(row)}>
902
+ <Table.Cell>{row.name}</Table.Cell>
903
+ <Table.Cell>{row.email}</Table.Cell>
904
+ </Table.Row>
905
+ ))}
906
+ </Table.Body>
907
+ </Table>
908
+ );
909
+ }
910
+
911
+ export default ExpandableRows;
912
+ ```
913
+
914
+
915
+
916
+ ### Expandable Rows - Controlled
917
+
918
+ ```typescript
919
+ import React, { useState } from 'react';
920
+
921
+ import DL from '@splunk/react-ui/DefinitionList';
922
+ import Table from '@splunk/react-ui/Table';
923
+
924
+ interface Row {
925
+ id: string;
926
+ email: string;
927
+ name: string;
928
+ }
929
+
930
+ const data: Row[] = [
931
+ { id: 'unique-0', name: 'Rylan', email: 'Angelita_Weimann42@gmail.com' },
932
+ { id: 'unique-1', name: 'Amelia', email: 'Dexter.Trantow57@hotmail.com' },
933
+ { id: 'unique-2', name: 'Estevan', email: 'Aimee7@hotmail.com' },
934
+ { id: 'unique-3', name: 'Florence', email: 'Jarrod.Bernier13@yahoo.com' },
935
+ { id: 'unique-4', name: 'Tressa', email: 'Yadira1@hotmail.com' },
936
+ ];
937
+
938
+
939
+ function getExpansionRow(row: Row) {
940
+ return (
941
+ <Table.Row key={`${row.email}-expansion`}>
942
+ <Table.Cell style={{ borderTop: 'none' }} colSpan={2}>
943
+ <DL>
944
+ <DL.Term>Name</DL.Term>
945
+ <DL.Description>{row.name}</DL.Description>
946
+ <DL.Term>Email</DL.Term>
947
+ <DL.Description>{row.email}</DL.Description>
948
+ </DL>
949
+ </Table.Cell>
950
+ </Table.Row>
951
+ );
952
+ }
953
+
954
+ function ExpandableRowsControlled() {
955
+ const [expandedRowId, setExpandedRowId] = useState(null as string | null);
956
+
957
+ const handleRowExpansion = (rowId: string) => {
958
+ if (expandedRowId === rowId) {
959
+ setExpandedRowId(null);
960
+ } else {
961
+ setExpandedRowId(rowId);
962
+ }
963
+ };
964
+
965
+ return (
966
+ <Table rowExpansion="controlled">
967
+ <Table.Head>
968
+ <Table.HeadCell>Name</Table.HeadCell>
969
+ <Table.HeadCell>Email</Table.HeadCell>
970
+ </Table.Head>
971
+ <Table.Body>
972
+ {data.map((row) => (
973
+ <Table.Row
974
+ key={row.id}
975
+ expansionRow={getExpansionRow(row)}
976
+ onExpansion={() => handleRowExpansion(row.id)}
977
+ expanded={row.id === expandedRowId}
978
+ >
979
+ <Table.Cell>{row.name}</Table.Cell>
980
+ <Table.Cell>{row.email}</Table.Cell>
981
+ </Table.Row>
982
+ ))}
983
+ </Table.Body>
984
+ </Table>
985
+ );
986
+ }
987
+
988
+ export default ExpandableRowsControlled;
989
+ ```
990
+
991
+
992
+
993
+ ### Row actions
994
+
995
+ ```typescript
996
+ import React, { useState, useCallback } from 'react';
997
+
998
+ import Cog from '@splunk/react-icons/Cog';
999
+ import Pencil from '@splunk/react-icons/Pencil';
1000
+ import Button from '@splunk/react-ui/Button';
1001
+ import DL, { Term as DT, Description as DD } from '@splunk/react-ui/DefinitionList';
1002
+ import Dropdown from '@splunk/react-ui/Dropdown';
1003
+ import Menu from '@splunk/react-ui/Menu';
1004
+ import Table, {
1005
+ RowActionPrimaryClickHandler,
1006
+ RowActionSecondaryClickHandler,
1007
+ } from '@splunk/react-ui/Table';
1008
+ import Tooltip from '@splunk/react-ui/Tooltip';
1009
+ import Typography from '@splunk/react-ui/Typography';
1010
+ import { _ } from '@splunk/ui-utils/i18n';
1011
+
1012
+ interface Row {
1013
+ age: number;
1014
+ country: string;
1015
+ email: string;
1016
+ favoriteColor: string;
1017
+ favoriteDay: string;
1018
+ industry: string;
1019
+ name: string;
1020
+ occupation: string;
1021
+ state: string;
1022
+ }
1023
+
1024
+ interface Column {
1025
+ label: string;
1026
+ name: keyof Row;
1027
+ visible: boolean;
1028
+ }
1029
+
1030
+ const initialData: Row[] = [
1031
+ {
1032
+ name: 'Rylan',
1033
+ age: 42,
1034
+ email: 'Angelita_Weimann42@gmail.com',
1035
+ state: 'CA',
1036
+ country: 'US',
1037
+ favoriteColor: 'Orange',
1038
+ favoriteDay: 'Monday',
1039
+ occupation: 'Engineer',
1040
+ industry: 'Software Development',
1041
+ },
1042
+ {
1043
+ name: 'Amelia',
1044
+ age: 24,
1045
+ email: 'Dexter.Trantow57@hotmail.com',
1046
+ state: 'NY',
1047
+ country: 'US',
1048
+ favoriteColor: 'White',
1049
+ favoriteDay: 'Friday',
1050
+ occupation: 'Engineer',
1051
+ industry: 'Software',
1052
+ },
1053
+ {
1054
+ name: 'Estevan',
1055
+ age: 56,
1056
+ email: 'Aimee7@hotmail.com',
1057
+ state: 'IL',
1058
+ country: 'US',
1059
+ favoriteColor: 'Green',
1060
+ favoriteDay: 'Saturday',
1061
+ occupation: 'Engineer',
1062
+ industry: 'Software',
1063
+ },
1064
+ {
1065
+ name: 'Florence',
1066
+ age: 71,
1067
+ email: 'Jarrod.Bernier13@yahoo.com',
1068
+ state: 'CA',
1069
+ country: 'US',
1070
+ favoriteColor: 'Red',
1071
+ favoriteDay: 'Tuesday',
1072
+ occupation: 'Engineer',
1073
+ industry: 'Software',
1074
+ },
1075
+ {
1076
+ name: 'Teresa',
1077
+ age: 38,
1078
+ email: 'Yadira1@hotmail.com',
1079
+ state: 'NJ',
1080
+ country: 'US',
1081
+ favoriteColor: 'Blue',
1082
+ favoriteDay: 'Wednesday',
1083
+ occupation: 'Engineer',
1084
+ industry: 'Software',
1085
+ },
1086
+ ];
1087
+
1088
+ const initialColumns: Column[] = [
1089
+ { name: 'name', label: 'Name', visible: true },
1090
+ { name: 'age', label: 'Age', visible: true },
1091
+ { name: 'email', label: 'Email', visible: true },
1092
+ { name: 'state', label: 'State', visible: true },
1093
+ { name: 'country', label: 'Country', visible: true },
1094
+ { name: 'favoriteColor', label: 'Favorite Color', visible: true },
1095
+ { name: 'favoriteDay', label: 'Favorite Day', visible: true },
1096
+ { name: 'occupation', label: 'Occupation', visible: true },
1097
+ { name: 'industry', label: 'Industry', visible: true },
1098
+ ];
1099
+
1100
+
1101
+ function RowAction() {
1102
+ const [data] = useState<Row[]>(initialData);
1103
+ const [columns, setColumns] = useState<Column[]>(initialColumns);
1104
+ const [primaryAction, setPrimaryAction] = useState<string | undefined>();
1105
+ const [primaryActionRowData, setPrimaryActionRowData] = useState<string | undefined>();
1106
+ const [secondaryAction, setSecondaryAction] = useState<string | undefined>();
1107
+ const [secondaryActionRowData, setSecondaryActionRowData] = useState<string | undefined>();
1108
+
1109
+ const handleShowHide = useCallback((e: React.MouseEvent, { name }: { name: string }) => {
1110
+ setColumns((prevColumns) =>
1111
+ prevColumns.map((col) => (col.name === name ? { ...col, visible: !col.visible } : col))
1112
+ );
1113
+ }, []);
1114
+
1115
+ const handleEditActionClick: RowActionPrimaryClickHandler = useCallback((e, rowData) => {
1116
+ setPrimaryAction('Edit');
1117
+ setPrimaryActionRowData(JSON.stringify(rowData));
1118
+ }, []);
1119
+
1120
+ const handleSaveActionClick: RowActionSecondaryClickHandler = useCallback((e, rowData) => {
1121
+ setSecondaryAction('Save');
1122
+ setSecondaryActionRowData(JSON.stringify(rowData));
1123
+ }, []);
1124
+
1125
+ const handleAddActionClick: RowActionSecondaryClickHandler = useCallback((e, rowData) => {
1126
+ setSecondaryAction('Add');
1127
+ setSecondaryActionRowData(JSON.stringify(rowData));
1128
+ }, []);
1129
+
1130
+ const handleDeleteActionClick: RowActionSecondaryClickHandler = useCallback((e, rowData) => {
1131
+ setSecondaryAction('Delete');
1132
+ setSecondaryActionRowData(JSON.stringify(rowData));
1133
+ }, []);
1134
+
1135
+ const toggle = (
1136
+ <Button appearance="subtle" data-test="actions-toggle" icon={<Cog variant="filled" />} />
1137
+ );
1138
+
1139
+ const actions = [
1140
+ <Dropdown toggle={toggle} key="settings">
1141
+ <Menu>
1142
+ <Menu.Heading>Show/Hide Columns</Menu.Heading>
1143
+ {columns.map((col) => (
1144
+ <Menu.Item
1145
+ key={col.name}
1146
+ selectable
1147
+ selected={col.visible}
1148
+ onClick={(e: React.MouseEvent) => handleShowHide(e, { name: col.name })}
1149
+ >
1150
+ {col.label}
1151
+ </Menu.Item>
1152
+ ))}
1153
+ <Menu.Divider />
1154
+ <Menu.Heading>More actions</Menu.Heading>
1155
+ <Menu.Item>Add new item</Menu.Item>
1156
+ </Menu>
1157
+ </Dropdown>,
1158
+ ];
1159
+
1160
+ const rowActionPrimaryButton = (
1161
+ <Tooltip
1162
+ content={_('Edit')}
1163
+ contentRelationship="label"
1164
+ onClick={handleEditActionClick}
1165
+ style={{ marginRight: 8 }}
1166
+ >
1167
+ <Button appearance="subtle" icon={<Pencil variant="filled" />} />
1168
+ </Tooltip>
1169
+ );
1170
+
1171
+ const rowActionsSecondaryMenu = (
1172
+ <Menu>
1173
+ <Menu.Item onClick={handleSaveActionClick}>Save</Menu.Item>
1174
+ <Menu.Item onClick={handleAddActionClick}>Add</Menu.Item>
1175
+ <Menu.Item onClick={handleDeleteActionClick}>Delete</Menu.Item>
1176
+ </Menu>
1177
+ );
1178
+
1179
+ return (
1180
+ <div>
1181
+ <Table actions={actions} actionsColumnWidth={104}>
1182
+ <Table.Head>
1183
+ {columns.map(
1184
+ (c) => c.visible && <Table.HeadCell key={c.name}>{c.label}</Table.HeadCell>
1185
+ )}
1186
+ </Table.Head>
1187
+ <Table.Body>
1188
+ {data.map((row) => (
1189
+ <Table.Row
1190
+ data={row}
1191
+ key={row.email}
1192
+ onClick={() => {}}
1193
+ actionPrimary={rowActionPrimaryButton}
1194
+ actionsSecondary={rowActionsSecondaryMenu}
1195
+ >
1196
+ {columns.map(
1197
+ (c) =>
1198
+ c.visible && <Table.Cell key={c.name}>{row[c.name]}</Table.Cell>
1199
+ )}
1200
+ </Table.Row>
1201
+ ))}
1202
+ </Table.Body>
1203
+ </Table>
1204
+ <aside style={{ marginTop: 20 }} aria-live="polite" aria-relevant="text">
1205
+ <Typography as="p">
1206
+ Click a primary action or secondary action to see the returned data
1207
+ </Typography>
1208
+ {primaryActionRowData && (
1209
+ <div style={{ overflow: 'scroll', marginBottom: 10 }}>
1210
+ <DL>
1211
+ <DT>Primary action:</DT>
1212
+ <DD>&apos;{primaryAction}&apos;</DD>
1213
+ <DT>Data:</DT>
1214
+ <DD>
1215
+ <code>{primaryActionRowData}</code>
1216
+ </DD>
1217
+ </DL>
1218
+ </div>
1219
+ )}
1220
+ {secondaryActionRowData && (
1221
+ <div style={{ overflow: 'scroll' }}>
1222
+ <DL>
1223
+ <DT>Secondary action:</DT>
1224
+ <DD>&apos;{secondaryAction}&apos;</DD>
1225
+ <DT>Data:</DT>
1226
+ <DD>
1227
+ <code>{secondaryActionRowData}</code>
1228
+ </DD>
1229
+ </DL>
1230
+ </div>
1231
+ )}
1232
+ </aside>
1233
+ </div>
1234
+ );
1235
+ }
1236
+
1237
+ export default RowAction;
1238
+ ```
1239
+
1240
+
1241
+
1242
+ ### Pin action column
1243
+
1244
+ ```typescript
1245
+ import React, { useState } from 'react';
1246
+
1247
+ import { noop } from 'lodash';
1248
+
1249
+ import Pencil from '@splunk/react-icons/Pencil';
1250
+ import Button from '@splunk/react-ui/Button';
1251
+ import Table from '@splunk/react-ui/Table';
1252
+ import Tooltip from '@splunk/react-ui/Tooltip';
1253
+ import { _ } from '@splunk/ui-utils/i18n';
1254
+
1255
+ interface Row {
1256
+ age: number;
1257
+ country: string;
1258
+ email: string;
1259
+ favoriteColor: string;
1260
+ favoriteDay: string;
1261
+ industry: string;
1262
+ name: string;
1263
+ occupation: string;
1264
+ state: string;
1265
+ }
1266
+
1267
+ interface Column {
1268
+ label: string;
1269
+ name: keyof Row;
1270
+ }
1271
+
1272
+ const initialData: Row[] = [
1273
+ {
1274
+ name: 'Rylan',
1275
+ age: 42,
1276
+ email: 'Angelita_Weimann42@gmail.com',
1277
+ state: 'CA',
1278
+ country: 'US',
1279
+ favoriteColor: 'Orange',
1280
+ favoriteDay: 'Monday',
1281
+ occupation: 'Engineer',
1282
+ industry: 'Software Development',
1283
+ },
1284
+ {
1285
+ name: 'Amelia',
1286
+ age: 24,
1287
+ email: 'Dexter.Trantow57@hotmail.com',
1288
+ state: 'NY',
1289
+ country: 'US',
1290
+ favoriteColor: 'White',
1291
+ favoriteDay: 'Friday',
1292
+ occupation: 'Engineer',
1293
+ industry: 'Software',
1294
+ },
1295
+ {
1296
+ name: 'Estevan',
1297
+ age: 56,
1298
+ email: 'Aimee7@hotmail.com',
1299
+ state: 'IL',
1300
+ country: 'US',
1301
+ favoriteColor: 'Green',
1302
+ favoriteDay: 'Saturday',
1303
+ occupation: 'Engineer',
1304
+ industry: 'Software',
1305
+ },
1306
+ {
1307
+ name: 'Florence',
1308
+ age: 71,
1309
+ email: 'Jarrod.Bernier13@yahoo.com',
1310
+ state: 'CA',
1311
+ country: 'US',
1312
+ favoriteColor: 'Red',
1313
+ favoriteDay: 'Tuesday',
1314
+ occupation: 'Engineer',
1315
+ industry: 'Software',
1316
+ },
1317
+ {
1318
+ name: 'Teresa',
1319
+ age: 38,
1320
+ email: 'Yadira1@hotmail.com',
1321
+ state: 'NJ',
1322
+ country: 'US',
1323
+ favoriteColor: 'Blue',
1324
+ favoriteDay: 'Wednesday',
1325
+ occupation: 'Engineer',
1326
+ industry: 'Software',
1327
+ },
1328
+ ];
1329
+
1330
+ const initialColumns: Column[] = [
1331
+ { name: 'name', label: 'Name' },
1332
+ { name: 'age', label: 'Age' },
1333
+ { name: 'email', label: 'Email' },
1334
+ { name: 'state', label: 'State' },
1335
+ { name: 'country', label: 'Country' },
1336
+ { name: 'favoriteColor', label: 'Favorite Color' },
1337
+ { name: 'favoriteDay', label: 'Favorite Day' },
1338
+ { name: 'occupation', label: 'Occupation' },
1339
+ { name: 'industry', label: 'Industry' },
1340
+ ];
1341
+
1342
+
1343
+ function PinActionColumn() {
1344
+ const [data] = useState<Row[]>(initialData);
1345
+ const [columns] = useState<Column[]>(initialColumns);
1346
+
1347
+ const rowActionPrimaryButton = (
1348
+ <Tooltip content={_('Edit')} contentRelationship="label" onClick={noop}>
1349
+ <Button appearance="subtle" icon={<Pencil variant="filled" />} />
1350
+ </Tooltip>
1351
+ );
1352
+
1353
+ return (
1354
+ <div>
1355
+ <Table
1356
+ outerStyle={{ width: 800 }}
1357
+ horizontalOverflow="scroll"
1358
+ pinnedColumns={{ actions: true }}
1359
+ actionsColumnWidth={45}
1360
+ >
1361
+ <Table.Head>
1362
+ {columns.map((c) => (
1363
+ <Table.HeadCell key={c.name}>{c.label}</Table.HeadCell>
1364
+ ))}
1365
+ </Table.Head>
1366
+ <Table.Body>
1367
+ {data.map((row) => (
1368
+ <Table.Row
1369
+ data={row}
1370
+ key={row.email}
1371
+ actionPrimary={rowActionPrimaryButton}
1372
+ >
1373
+ {columns.map((c) => (
1374
+ <Table.Cell key={c.name}>{row[c.name]}</Table.Cell>
1375
+ ))}
1376
+ </Table.Row>
1377
+ ))}
1378
+ </Table.Body>
1379
+ </Table>
1380
+ </div>
1381
+ );
1382
+ }
1383
+
1384
+ export default PinActionColumn;
1385
+ ```
1386
+
1387
+
1388
+
1389
+ ### Reorder Rows
1390
+
1391
+ ```typescript
1392
+ import React, { useState, useCallback } from 'react';
1393
+
1394
+ import { cloneDeep } from 'lodash';
1395
+
1396
+ import Table, { TableRequestMoveRowHandler } from '@splunk/react-ui/Table';
1397
+
1398
+ interface Row {
1399
+ age: number;
1400
+ email: string;
1401
+ name: string;
1402
+ }
1403
+
1404
+ interface Header {
1405
+ key: 'age' | 'email' | 'name';
1406
+ label: string;
1407
+ }
1408
+
1409
+ const initialHeaders: Header[] = [
1410
+ { label: 'Name', key: 'name' },
1411
+ { label: 'Age', key: 'age' },
1412
+ { label: 'Email', key: 'email' },
1413
+ ];
1414
+
1415
+ const initialData: Row[] = [
1416
+ { name: 'Rylan', age: 12, email: 'Angelita_Weimann42@gmail.com' },
1417
+ { name: 'Amelia', age: 23, email: 'Dexter.Trantow57@hotmail.com' },
1418
+ { name: 'Estevan', age: 19, email: 'Aimee7@hotmail.com' },
1419
+ { name: 'Florence', age: 20, email: 'Jarrod.Bernier13@yahoo.com' },
1420
+ { name: 'Tressa', age: 22, email: 'Yadira1@hotmail.com' },
1421
+ ];
1422
+
1423
+
1424
+ function ReorderRows() {
1425
+ const [headers] = useState<Header[]>(initialHeaders);
1426
+ const [data, setData] = useState<Row[]>(initialData);
1427
+
1428
+ const handleRequestMoveRow: TableRequestMoveRowHandler = useCallback(
1429
+ ({ fromIndex, toIndex }) => {
1430
+ setData((prevData) => {
1431
+ const updatedData = cloneDeep(prevData);
1432
+ const rowToMove = data[fromIndex];
1433
+ const insertionIndex = toIndex < fromIndex ? toIndex : toIndex + 1;
1434
+ updatedData.splice(insertionIndex, 0, rowToMove);
1435
+
1436
+ const removalIndex = toIndex < fromIndex ? fromIndex + 1 : fromIndex;
1437
+ updatedData.splice(removalIndex, 1);
1438
+
1439
+ return updatedData;
1440
+ });
1441
+ },
1442
+ [data]
1443
+ );
1444
+
1445
+ return (
1446
+ <Table onRequestMoveRow={handleRequestMoveRow}>
1447
+ <Table.Head>
1448
+ {headers.map((header) => (
1449
+ <Table.HeadCell key={header.key}>{header.label}</Table.HeadCell>
1450
+ ))}
1451
+ </Table.Head>
1452
+ <Table.Body>
1453
+ {data.map((row) => (
1454
+ <Table.Row key={row.email}>
1455
+ {headers.map((header) => (
1456
+ <Table.Cell key={row[header.key]}>{row[header.key]}</Table.Cell>
1457
+ ))}
1458
+ </Table.Row>
1459
+ ))}
1460
+ </Table.Body>
1461
+ </Table>
1462
+ );
1463
+ }
1464
+
1465
+ export default ReorderRows;
1466
+ ```
1467
+
1468
+
1469
+
1470
+ ### Reorder Columns
1471
+
1472
+ ```typescript
1473
+ import React, { useState, useCallback } from 'react';
1474
+
1475
+ import { cloneDeep } from 'lodash';
1476
+
1477
+ import Table, { TableRequestMoveColumnHandler } from '@splunk/react-ui/Table';
1478
+
1479
+ interface Row {
1480
+ age: number;
1481
+ email: string;
1482
+ name: string;
1483
+ }
1484
+
1485
+ interface Header {
1486
+ key: 'age' | 'email' | 'name';
1487
+ label: string;
1488
+ }
1489
+
1490
+ const initialHeaders: Header[] = [
1491
+ { label: 'Name', key: 'name' },
1492
+ { label: 'Age', key: 'age' },
1493
+ { label: 'Email', key: 'email' },
1494
+ ];
1495
+
1496
+ const initialData: Row[] = [
1497
+ { name: 'Rylan', age: 12, email: 'Angelita_Weimann42@gmail.com' },
1498
+ { name: 'Amelia', age: 23, email: 'Dexter.Trantow57@hotmail.com' },
1499
+ { name: 'Estevan', age: 19, email: 'Aimee7@hotmail.com' },
1500
+ { name: 'Florence', age: 20, email: 'Jarrod.Bernier13@yahoo.com' },
1501
+ { name: 'Tressa', age: 22, email: 'Yadira1@hotmail.com' },
1502
+ ];
1503
+
1504
+
1505
+ function ReorderColumns() {
1506
+ const [headers, setHeaders] = useState<Header[]>(initialHeaders);
1507
+ const [data] = useState<Row[]>(initialData);
1508
+
1509
+ const handleRequestMoveColumn: TableRequestMoveColumnHandler = useCallback(
1510
+ ({ fromIndex, toIndex }) => {
1511
+ setHeaders((prevHeaders) => {
1512
+ const updatedHeaders = cloneDeep(prevHeaders);
1513
+ const headerToMove = updatedHeaders[fromIndex];
1514
+
1515
+ const insertionIndex = toIndex < fromIndex ? toIndex : toIndex + 1;
1516
+ updatedHeaders.splice(insertionIndex, 0, headerToMove);
1517
+
1518
+ const removalIndex = toIndex < fromIndex ? fromIndex + 1 : fromIndex;
1519
+ updatedHeaders.splice(removalIndex, 1);
1520
+
1521
+ return updatedHeaders;
1522
+ });
1523
+ },
1524
+ []
1525
+ );
1526
+
1527
+ return (
1528
+ <Table onRequestMoveColumn={handleRequestMoveColumn}>
1529
+ <Table.Head>
1530
+ {headers.map((header) => (
1531
+ <Table.HeadCell key={header.key}>{header.label}</Table.HeadCell>
1532
+ ))}
1533
+ </Table.Head>
1534
+ <Table.Body>
1535
+ {data.map((row) => (
1536
+ <Table.Row key={row.email}>
1537
+ {headers.map((header) => (
1538
+ <Table.Cell key={`${row.email}-${header.key}`}>
1539
+ {row[header.key]}
1540
+ </Table.Cell>
1541
+ ))}
1542
+ </Table.Row>
1543
+ ))}
1544
+ </Table.Body>
1545
+ </Table>
1546
+ );
1547
+ }
1548
+
1549
+ export default ReorderColumns;
1550
+ ```
1551
+
1552
+
1553
+
1554
+ ### Resizable Columns
1555
+
1556
+ ```typescript
1557
+ import React, { useState, useCallback } from 'react';
1558
+
1559
+ import { cloneDeep } from 'lodash';
1560
+
1561
+ import Table, { TableRequestResizeColumnHandler } from '@splunk/react-ui/Table';
1562
+
1563
+ interface Row {
1564
+ age: number;
1565
+ email: string;
1566
+ name: string;
1567
+ }
1568
+
1569
+ interface Header {
1570
+ key: 'age' | 'email' | 'name';
1571
+ label: string;
1572
+ minWidth: number;
1573
+ width: number;
1574
+ }
1575
+
1576
+ const initialHeaders: Header[] = [
1577
+ { label: 'Name', key: 'name', width: 200, minWidth: 80 },
1578
+ { label: 'Age', key: 'age', width: 60, minWidth: 40 },
1579
+ { label: 'Email Address', key: 'email', width: 400, minWidth: 120 },
1580
+ ];
1581
+
1582
+ const initialData: Row[] = [
1583
+ { name: 'Rylan', age: 12, email: 'Angelita_Weimann42@gmail.com' },
1584
+ { name: 'Amelia', age: 23, email: 'Dexter.Trantow57@hotmail.com' },
1585
+ { name: 'Estevan', age: 19, email: 'Aimee7@hotmail.com' },
1586
+ { name: 'Florence', age: 20, email: 'Jarrod.Bernier13@yahoo.com' },
1587
+ { name: 'Tressa', age: 22, email: 'Yadira1@hotmail.com' },
1588
+ ];
1589
+
1590
+
1591
+ function Resizable() {
1592
+ const [headers, setHeaders] = useState<Header[]>(initialHeaders);
1593
+ const [data] = useState<Row[]>(initialData);
1594
+
1595
+ const handleResizeColumn: TableRequestResizeColumnHandler = useCallback(
1596
+ (event, { columnId, index, width }) => {
1597
+ setHeaders((prevHeaders) => {
1598
+ const updatedHeaders = cloneDeep(prevHeaders);
1599
+
1600
+ // min and max widths can be controlled in the callback.
1601
+ const selectedColumn = updatedHeaders.find(({ key }) => key === columnId);
1602
+ if (selectedColumn) {
1603
+ const widthAboveMinimum = Math.max(width, selectedColumn.minWidth);
1604
+ updatedHeaders[index].width = widthAboveMinimum;
1605
+
1606
+ return updatedHeaders;
1607
+ }
1608
+ return [];
1609
+ });
1610
+ },
1611
+ []
1612
+ );
1613
+
1614
+ return (
1615
+ <Table onRequestResizeColumn={handleResizeColumn}>
1616
+ <Table.Head>
1617
+ {headers.map((header) => (
1618
+ <Table.HeadCell key={header.key} columnId={header.key} width={header.width}>
1619
+ {header.label}
1620
+ </Table.HeadCell>
1621
+ ))}
1622
+ </Table.Head>
1623
+ <Table.Body>
1624
+ {data.map((row) => (
1625
+ <Table.Row key={row.email}>
1626
+ {headers.map((header) => (
1627
+ <Table.Cell key={`${row.email}-${header.key}`}>
1628
+ {row[header.key]}
1629
+ </Table.Cell>
1630
+ ))}
1631
+ </Table.Row>
1632
+ ))}
1633
+ </Table.Body>
1634
+ </Table>
1635
+ );
1636
+ }
1637
+
1638
+ export default Resizable;
1639
+ ```
1640
+
1641
+
1642
+
1643
+ ### Fill Layout with Resizable Columns
1644
+
1645
+ ```typescript
1646
+ import React, { useState, useCallback } from 'react';
1647
+
1648
+ import { cloneDeep } from 'lodash';
1649
+
1650
+ import Table, { TableRequestResizeColumnHandler } from '@splunk/react-ui/Table';
1651
+
1652
+ interface Row {
1653
+ age: number;
1654
+ email: string;
1655
+ name: string;
1656
+ }
1657
+
1658
+ interface Header {
1659
+ key: 'age' | 'email' | 'name';
1660
+ label: string;
1661
+ minWidth: number;
1662
+ width: number | 'auto';
1663
+ }
1664
+
1665
+ const initialHeaders: Header[] = [
1666
+ { label: 'Name', key: 'name', width: 100, minWidth: 80 },
1667
+ { label: 'Age', key: 'age', width: 'auto', minWidth: 60 },
1668
+ { label: 'Email Address', key: 'email', width: 'auto', minWidth: 120 },
1669
+ ];
1670
+
1671
+ const initialData: Row[] = [
1672
+ { name: 'Rylan', age: 12, email: 'Angelita_Weimann42@gmail.com' },
1673
+ { name: 'Amelia', age: 23, email: 'Dexter.Trantow57@hotmail.com' },
1674
+ { name: 'Estevan', age: 19, email: 'Aimee7@hotmail.com' },
1675
+ { name: 'Florence', age: 20, email: 'Jarrod.Bernier13@yahoo.com' },
1676
+ { name: 'Tressa', age: 22, email: 'Yadira1@hotmail.com' },
1677
+ ];
1678
+
1679
+
1680
+ function ResizableFill() {
1681
+ const [headers, setHeaders] = useState<Header[]>(initialHeaders);
1682
+ const [data] = useState<Row[]>(initialData);
1683
+
1684
+ const handleResizeColumn: TableRequestResizeColumnHandler = useCallback(
1685
+ (event, { columnId, index, width }) => {
1686
+ setHeaders((prevHeaders) => {
1687
+ const updatedHeaders = cloneDeep(prevHeaders);
1688
+
1689
+ // min and max widths can be controlled in the callback.
1690
+ const selectedColumn = updatedHeaders.find(({ key }) => key === columnId);
1691
+
1692
+ if (selectedColumn) {
1693
+ const widthAboveMinimum = Math.max(width, selectedColumn.minWidth);
1694
+ updatedHeaders[index].width = widthAboveMinimum;
1695
+
1696
+ return updatedHeaders;
1697
+ }
1698
+ return [];
1699
+ });
1700
+ },
1701
+ []
1702
+ );
1703
+
1704
+ return (
1705
+ <section style={{ display: 'block', margin: '0 auto', width: '500px' }}>
1706
+ <Table onRequestResizeColumn={handleResizeColumn} resizableFillLayout>
1707
+ <Table.Head>
1708
+ {headers.map((header) => (
1709
+ <Table.HeadCell key={header.key} columnId={header.key} width={header.width}>
1710
+ {header.label}
1711
+ </Table.HeadCell>
1712
+ ))}
1713
+ </Table.Head>
1714
+ <Table.Body>
1715
+ {data.map((row) => (
1716
+ <Table.Row key={row.email}>
1717
+ {headers.map((header) => (
1718
+ <Table.Cell key={row[header.key]}>{row[header.key]}</Table.Cell>
1719
+ ))}
1720
+ </Table.Row>
1721
+ ))}
1722
+ </Table.Body>
1723
+ </Table>
1724
+ </section>
1725
+ );
1726
+ }
1727
+
1728
+ export default ResizableFill;
1729
+ ```
1730
+
1731
+
1732
+
1733
+ ### Stripe rows
1734
+
1735
+ ```typescript
1736
+ import React from 'react';
1737
+
1738
+ import Table from '@splunk/react-ui/Table';
1739
+
1740
+ const data = [
1741
+ { name: 'Rylan', age: 42, email: 'Angelita_Weimann42@gmail.com' },
1742
+ { name: 'Amelia', age: 24, email: 'Dexter.Trantow57@hotmail.com' },
1743
+ { name: 'Estevan', age: 56, email: 'Aimee7@hotmail.com' },
1744
+ { name: 'Florence', age: 71, email: 'Jarrod.Bernier13@yahoo.com' },
1745
+ { name: 'Tressa', age: 38, email: 'Yadira1@hotmail.com' },
1746
+ ];
1747
+
1748
+
1749
+ function StripeRows() {
1750
+ return (
1751
+ <Table stripeRows>
1752
+ <Table.Head>
1753
+ <Table.HeadCell>Name</Table.HeadCell>
1754
+ <Table.HeadCell align="right">Age</Table.HeadCell>
1755
+ <Table.HeadCell>Email</Table.HeadCell>
1756
+ </Table.Head>
1757
+ <Table.Body>
1758
+ {data.map((row) => (
1759
+ <Table.Row key={row.email}>
1760
+ <Table.Cell>{row.name}</Table.Cell>
1761
+ <Table.Cell align="right">{row.age}</Table.Cell>
1762
+ <Table.Cell>{row.email}</Table.Cell>
1763
+ </Table.Row>
1764
+ ))}
1765
+ </Table.Body>
1766
+ </Table>
1767
+ );
1768
+ }
1769
+
1770
+ export default StripeRows;
1771
+ ```
1772
+
1773
+
1774
+
1775
+ ### Horizontal overflow scroll
1776
+
1777
+ ```typescript
1778
+ import React, { useState } from 'react';
1779
+
1780
+ import Table from '@splunk/react-ui/Table';
1781
+
1782
+ interface Column {
1783
+ label: string;
1784
+ name: keyof Row;
1785
+ width?: number;
1786
+ }
1787
+
1788
+ interface Row {
1789
+ name: string;
1790
+ age: number;
1791
+ email: string;
1792
+ state: string;
1793
+ country: string;
1794
+ favoriteColor: string;
1795
+ favoriteDay: string;
1796
+ occupation: string;
1797
+ industry: string;
1798
+ }
1799
+
1800
+ const initialData: Row[] = [
1801
+ {
1802
+ name: 'Amelia Trantow',
1803
+ age: 24,
1804
+ email: 'Dexter.Trantow57@hotmail.com',
1805
+ state: 'NY',
1806
+ country: 'US',
1807
+ favoriteColor: 'White',
1808
+ favoriteDay: 'Friday',
1809
+ occupation: 'Engineer',
1810
+ industry: 'Software',
1811
+ },
1812
+ {
1813
+ name: 'Estevan',
1814
+ age: 56,
1815
+ email: 'Aimee7@hotmail.com',
1816
+ state: 'IL',
1817
+ country: 'US',
1818
+ favoriteColor: 'Green',
1819
+ favoriteDay: 'Saturday',
1820
+ occupation: 'Engineer',
1821
+ industry: 'Software',
1822
+ },
1823
+ {
1824
+ name: 'Florence',
1825
+ age: 71,
1826
+ email: 'Jarrod.Bernier13@yahoo.com',
1827
+ state: 'CA',
1828
+ country: 'US',
1829
+ favoriteColor: 'Red',
1830
+ favoriteDay: 'Tuesday',
1831
+ occupation: 'Engineer',
1832
+ industry: 'Software',
1833
+ },
1834
+ {
1835
+ name: 'Teresa',
1836
+ age: 38,
1837
+ email: 'Yadira1@hotmail.com',
1838
+ state: 'NJ',
1839
+ country: 'US',
1840
+ favoriteColor: 'Blue',
1841
+ favoriteDay: 'Wednesday',
1842
+ occupation: 'Engineer',
1843
+ industry: 'Software',
1844
+ },
1845
+ ];
1846
+
1847
+ const initialColumns: Column[] = [
1848
+ { name: 'name', label: 'Name' },
1849
+ { name: 'age', label: 'Age' },
1850
+ { name: 'email', label: 'Email' },
1851
+ { name: 'state', label: 'State' },
1852
+ { name: 'country', label: 'Country' },
1853
+ { name: 'favoriteColor', label: 'Favorite Color' },
1854
+ { name: 'favoriteDay', label: 'Favorite Day' },
1855
+ { name: 'occupation', label: 'Occupation' },
1856
+ { name: 'industry', label: 'Industry' },
1857
+ ];
1858
+
1859
+
1860
+ function HorizontalOverflowScroll() {
1861
+ const [data] = useState<Row[]>(initialData);
1862
+
1863
+ return (
1864
+ <Table outerStyle={{ width: 800 }} horizontalOverflow="scroll">
1865
+ <Table.Head>
1866
+ {initialColumns.map((c) => (
1867
+ <Table.HeadCell key={c.name} width={c.width}>
1868
+ {c.label}
1869
+ </Table.HeadCell>
1870
+ ))}
1871
+ </Table.Head>
1872
+ <Table.Body>
1873
+ {data.map((row) => (
1874
+ <Table.Row data={row} key={row.email} onClick={() => {}}>
1875
+ {initialColumns.map((c) => (
1876
+ <Table.Cell key={c.name}>{row[c.name]}</Table.Cell>
1877
+ ))}
1878
+ </Table.Row>
1879
+ ))}
1880
+ </Table.Body>
1881
+ </Table>
1882
+ );
1883
+ }
1884
+
1885
+ export default HorizontalOverflowScroll;
1886
+ ```
1887
+
1888
+
1889
+
1890
+ ### initialHeaders
1891
+
1892
+ ```typescript
1893
+ import React, { useState, useCallback } from 'react';
1894
+
1895
+ import { cloneDeep } from 'lodash';
1896
+ import styled from 'styled-components';
1897
+
1898
+ import Gear from '@splunk/react-icons/enterprise/Gear';
1899
+ import Pencil from '@splunk/react-icons/enterprise/Pencil';
1900
+ import FilterArrowDown from '@splunk/react-icons/FilterArrowDown';
1901
+ import FilterArrowUp from '@splunk/react-icons/FilterArrowUp';
1902
+ import FilterArrowUpDown from '@splunk/react-icons/FilterArrowUpDown';
1903
+ import Button from '@splunk/react-ui/Button';
1904
+ import DL, { Term as DT, Description as DD } from '@splunk/react-ui/DefinitionList';
1905
+ import Dropdown from '@splunk/react-ui/Dropdown';
1906
+ import Heading from '@splunk/react-ui/Heading';
1907
+ import Menu from '@splunk/react-ui/Menu';
1908
+ import Table, {
1909
+ HeadCellSortHandler,
1910
+ RowActionPrimaryClickHandler,
1911
+ RowActionSecondaryClickHandler,
1912
+ RowRequestToggleHandler,
1913
+ TableRequestMoveColumnHandler,
1914
+ TableRequestResizeColumnHandler,
1915
+ RowClickHandler,
1916
+ } from '@splunk/react-ui/Table';
1917
+ import Text from '@splunk/react-ui/Text';
1918
+ import Tooltip from '@splunk/react-ui/Tooltip';
1919
+ import Typography from '@splunk/react-ui/Typography';
1920
+ import { variables } from '@splunk/themes';
1921
+ import { _ } from '@splunk/ui-utils/i18n';
1922
+
1923
+ interface Row {
1924
+ age: number;
1925
+ birthState: string;
1926
+ disabled: boolean;
1927
+ email: string;
1928
+ name: string;
1929
+ selected: boolean;
1930
+ status: string;
1931
+ }
1932
+
1933
+ interface Header {
1934
+ align: 'left' | 'center' | 'right';
1935
+ key: keyof Row;
1936
+ label: string;
1937
+ minWidth: number;
1938
+ visible: boolean;
1939
+ width: number;
1940
+ tooltip?: string;
1941
+ filterAndSort?: boolean;
1942
+ }
1943
+
1944
+ const initialHeaders: Header[] = [
1945
+ {
1946
+ label: 'Name',
1947
+ key: 'name',
1948
+ align: 'left',
1949
+ width: 180,
1950
+ minWidth: 80,
1951
+ visible: true,
1952
+ tooltip: 'Legal first name',
1953
+ },
1954
+ { label: 'Status', key: 'status', align: 'left', width: 100, minWidth: 40, visible: true },
1955
+ {
1956
+ label: 'Birth State',
1957
+ key: 'birthState',
1958
+ align: 'left',
1959
+ width: 120,
1960
+ minWidth: 40,
1961
+ visible: true,
1962
+ },
1963
+ { label: 'Age', key: 'age', align: 'left', width: 100, minWidth: 40, visible: true },
1964
+ {
1965
+ label: 'Email Address',
1966
+ key: 'email',
1967
+ align: 'left',
1968
+ width: 400,
1969
+ minWidth: 120,
1970
+ visible: true,
1971
+ filterAndSort: true,
1972
+ },
1973
+ ];
1974
+
1975
+ const initialData: Row[] = [
1976
+ {
1977
+ name: 'Rylan',
1978
+ status: 'single',
1979
+ birthState: 'HI',
1980
+ age: 12,
1981
+ email: 'Angelita_Weimann42@gmail.com',
1982
+ selected: false,
1983
+ disabled: true,
1984
+ },
1985
+ {
1986
+ name: 'Amelia',
1987
+ status: 'married',
1988
+ birthState: 'UT',
1989
+ age: 23,
1990
+ email: 'Dexter.Trantow57@hotmail.com',
1991
+ selected: false,
1992
+ disabled: false,
1993
+ },
1994
+ {
1995
+ name: 'Estevan',
1996
+ status: 'single',
1997
+ birthState: 'NY',
1998
+ age: 19,
1999
+ email: 'Aimee7@hotmail.com',
2000
+ selected: false,
2001
+ disabled: false,
2002
+ },
2003
+ {
2004
+ name: 'Florence',
2005
+ status: 'single',
2006
+ birthState: 'AZ',
2007
+ age: 20,
2008
+ email: 'Jarrod.Bernier13@yahoo.com',
2009
+ selected: false,
2010
+ disabled: false,
2011
+ },
2012
+ {
2013
+ name: 'Tressa',
2014
+ status: 'married',
2015
+ birthState: 'CA',
2016
+ age: 22,
2017
+ email: 'yadira1@hotmail.com',
2018
+ selected: false,
2019
+ disabled: false,
2020
+ },
2021
+ {
2022
+ name: 'Bernice',
2023
+ status: 'single',
2024
+ birthState: 'TX',
2025
+ age: 17,
2026
+ email: 'bernice.Gilbert@gmail.com',
2027
+ selected: false,
2028
+ disabled: false,
2029
+ },
2030
+ {
2031
+ name: 'Adrian',
2032
+ status: 'married',
2033
+ birthState: 'MA',
2034
+ age: 23,
2035
+ email: 'adrian7456@gmail.com',
2036
+ selected: false,
2037
+ disabled: false,
2038
+ },
2039
+ {
2040
+ name: 'Ester',
2041
+ status: 'single',
2042
+ birthState: 'NY',
2043
+ age: 88,
2044
+ email: 'esternyc@gmail.com',
2045
+ selected: false,
2046
+ disabled: false,
2047
+ },
2048
+ {
2049
+ name: 'Andrew',
2050
+ status: 'single',
2051
+ birthState: 'NM',
2052
+ age: 16,
2053
+ email: 'andrew.fillmore2@gmail.com',
2054
+ selected: false,
2055
+ disabled: false,
2056
+ },
2057
+ {
2058
+ name: 'Felix',
2059
+ status: 'married',
2060
+ birthState: 'CA',
2061
+ age: 36,
2062
+ email: 'felixfelix@hotmail.com',
2063
+ selected: false,
2064
+ disabled: false,
2065
+ },
2066
+ ];
2067
+
2068
+ const StyledFilterContainer = styled.div`
2069
+ display: grid;
2070
+ gap: ${variables.spacingSmall};
2071
+ padding: ${variables.spacingSmall} ${variables.spacingLarge};
2072
+ `;
2073
+
2074
+
2075
+ function Complex() {
2076
+ const [headers, setHeaders] = useState<Header[]>(initialHeaders);
2077
+ const [data, setData] = useState<Row[]>(initialData);
2078
+ const [sortKey, setSortKey] = useState<keyof Row>('name');
2079
+ const [sortDir, setSortDir] = useState<'asc' | 'desc'>('asc');
2080
+ const [columnFilter, setColumnFilter] = useState<{ [key: string]: string }>({});
2081
+ const [activeRow, setActiveRow] = useState<string | undefined>(undefined);
2082
+ const [activeRowData, setActiveRowData] = useState<string | undefined>(undefined);
2083
+
2084
+ const [primaryAction, setPrimaryAction] = useState<string | undefined>(undefined);
2085
+ const [primaryActionRowData, setPrimaryActionRowData] = useState<string | undefined>(undefined);
2086
+ const [secondaryAction, setSecondaryAction] = useState<string | undefined>(undefined);
2087
+ const [secondaryActionRowData, setSecondaryActionRowData] = useState<string | undefined>(
2088
+ undefined
2089
+ );
2090
+
2091
+ const handleRequestMoveColumn: TableRequestMoveColumnHandler = useCallback(
2092
+ ({ fromIndex, toIndex }) => {
2093
+ setHeaders((prevHeaders) => {
2094
+ const updatedHeaders = cloneDeep(prevHeaders);
2095
+ const headerToMove = updatedHeaders[fromIndex];
2096
+
2097
+ const insertionIndex = toIndex < fromIndex ? toIndex : toIndex + 1;
2098
+ updatedHeaders.splice(insertionIndex, 0, headerToMove);
2099
+
2100
+ const removalIndex = toIndex < fromIndex ? fromIndex + 1 : fromIndex;
2101
+ updatedHeaders.splice(removalIndex, 1);
2102
+ return updatedHeaders;
2103
+ });
2104
+ },
2105
+ []
2106
+ );
2107
+
2108
+ const handleSort: HeadCellSortHandler = useCallback(
2109
+ (e, { sortKey: newSortKey }) => {
2110
+ setSortKey((prevSortKey) => {
2111
+ const prevSortDir = prevSortKey === newSortKey ? sortDir : 'none';
2112
+ const nextSortDir = prevSortDir === 'asc' ? 'desc' : 'asc';
2113
+ setSortDir(nextSortDir);
2114
+ return newSortKey as keyof Row;
2115
+ });
2116
+ },
2117
+ [sortDir]
2118
+ );
2119
+
2120
+ const handleResizeColumn: TableRequestResizeColumnHandler = useCallback(
2121
+ (event, { columnId, index, width }) => {
2122
+ setHeaders((prevHeaders) => {
2123
+ const updatedHeaders = cloneDeep(prevHeaders);
2124
+
2125
+ // min and max widths can be controlled in the callback.
2126
+ const selectedColumn = updatedHeaders.find(({ key }) => key === columnId);
2127
+ if (selectedColumn) {
2128
+ const widthAboveMinimum = Math.max(width, selectedColumn.minWidth);
2129
+ updatedHeaders[index].width = widthAboveMinimum;
2130
+
2131
+ return updatedHeaders;
2132
+ }
2133
+ return [];
2134
+ });
2135
+ },
2136
+ [setHeaders]
2137
+ );
2138
+
2139
+ const handleToggle: RowRequestToggleHandler = useCallback((event, { email }) => {
2140
+ setData((prevData) => {
2141
+ const updatedData = cloneDeep(prevData);
2142
+
2143
+ const selectedRow = updatedData.find(({ email: rowEmail }) => rowEmail === email);
2144
+ if (selectedRow) {
2145
+ selectedRow.selected = !selectedRow.selected;
2146
+
2147
+ return updatedData;
2148
+ }
2149
+ return [];
2150
+ });
2151
+ }, []);
2152
+
2153
+ const getRowSelectionState = useCallback((rowData: Row[]): 'none' | 'all' | 'some' => {
2154
+ const selectedCount = rowData.filter((row) => row.selected).length;
2155
+ const disabledCount = rowData.filter((row) => row.disabled).length;
2156
+
2157
+ if (selectedCount === 0) return 'none';
2158
+ if (selectedCount + disabledCount === rowData.length) return 'all';
2159
+ return 'some';
2160
+ }, []);
2161
+
2162
+ const handleToggleAll = useCallback(() => {
2163
+ setData((prevData) => {
2164
+ const updatedData = cloneDeep(prevData);
2165
+ const selected = getRowSelectionState(updatedData) !== 'all';
2166
+ const finalData = updatedData.map((row) => ({
2167
+ ...row,
2168
+ selected: row.disabled ? false : selected,
2169
+ }));
2170
+
2171
+ return finalData;
2172
+ });
2173
+ }, [getRowSelectionState]);
2174
+
2175
+ const handleRowClick: RowClickHandler = useCallback((event, rowData) => {
2176
+ setActiveRow(rowData.name);
2177
+ setActiveRowData(JSON.stringify(rowData));
2178
+ }, []);
2179
+
2180
+ const handleShowHide = useCallback((e: React.MouseEvent, { label }: { label: string }) => {
2181
+ setHeaders((prevHeaders) =>
2182
+ prevHeaders.map((header) =>
2183
+ header.label === label ? { ...header, visible: !header.visible } : header
2184
+ )
2185
+ );
2186
+ }, []);
2187
+
2188
+ const handleEditActionClick: RowActionPrimaryClickHandler = useCallback((e, rowData) => {
2189
+ setPrimaryAction('Edit');
2190
+ setPrimaryActionRowData(JSON.stringify(rowData));
2191
+ }, []);
2192
+
2193
+ const handleSaveActionClick: RowActionSecondaryClickHandler = useCallback((e, rowData) => {
2194
+ setSecondaryAction('Save');
2195
+ setSecondaryActionRowData(JSON.stringify(rowData));
2196
+ }, []);
2197
+
2198
+ const handleAddActionClick: RowActionSecondaryClickHandler = useCallback((e, rowData) => {
2199
+ setSecondaryAction('Add');
2200
+ setSecondaryActionRowData(JSON.stringify(rowData));
2201
+ }, []);
2202
+
2203
+ const handleDeleteActionClick: RowActionSecondaryClickHandler = useCallback((e, rowData) => {
2204
+ setSecondaryAction('Delete');
2205
+ setSecondaryActionRowData(JSON.stringify(rowData));
2206
+ }, []);
2207
+
2208
+ const getFilterIcon = (header: Header) => {
2209
+ const hasFilter = columnFilter[header.key]?.length > 0;
2210
+ const iconProps = { variant: hasFilter ? 'filled' : 'outlined' } as const;
2211
+
2212
+ if (sortKey === header.key && sortDir === 'asc') {
2213
+ return <FilterArrowUp {...iconProps} />;
2214
+ }
2215
+ if (sortKey === header.key && sortDir === 'desc') {
2216
+ return <FilterArrowDown {...iconProps} />;
2217
+ }
2218
+ return <FilterArrowUpDown {...iconProps} />;
2219
+ };
2220
+
2221
+ const toggle = (
2222
+ <Button appearance="subtle" data-test="actions-toggle" icon={<Gear hideDefaultTooltip />} />
2223
+ );
2224
+
2225
+ const actions = [
2226
+ <Dropdown toggle={toggle} key="settings">
2227
+ <Menu>
2228
+ <Menu.Heading>Show/Hide Columns</Menu.Heading>
2229
+ {headers.map((header) => (
2230
+ <Menu.Item
2231
+ key={header.label}
2232
+ selectable
2233
+ selected={header.visible}
2234
+ onClick={(e: React.MouseEvent) =>
2235
+ handleShowHide(e, {
2236
+ label: header.label,
2237
+ })
2238
+ }
2239
+ >
2240
+ {header.label}
2241
+ </Menu.Item>
2242
+ ))}
2243
+ <Menu.Divider />
2244
+ <Menu.Heading>More actions</Menu.Heading>
2245
+ <Menu.Item>Add new item</Menu.Item>
2246
+ </Menu>
2247
+ </Dropdown>,
2248
+ ];
2249
+
2250
+ const rowActionPrimaryButton = (
2251
+ <Tooltip
2252
+ content={_('Edit')}
2253
+ contentRelationship="label"
2254
+ onClick={handleEditActionClick}
2255
+ style={{ marginRight: 8 }}
2256
+ >
2257
+ <Button
2258
+ appearance="subtle"
2259
+ icon={<Pencil hideDefaultTooltip screenReaderText={null} />}
2260
+ />
2261
+ </Tooltip>
2262
+ );
2263
+
2264
+ const rowActionsSecondaryMenu = (
2265
+ <Menu>
2266
+ <Menu.Item onClick={handleSaveActionClick}>Save</Menu.Item>
2267
+ <Menu.Item onClick={handleAddActionClick}>Add</Menu.Item>
2268
+ <Menu.Item onClick={handleDeleteActionClick}>Delete</Menu.Item>
2269
+ </Menu>
2270
+ );
2271
+
2272
+ return (
2273
+ <div>
2274
+ <Table
2275
+ onRequestMoveColumn={handleRequestMoveColumn}
2276
+ onRequestResizeColumn={handleResizeColumn}
2277
+ onRequestToggleAllRows={handleToggleAll}
2278
+ rowSelection={getRowSelectionState(data)}
2279
+ headType="fixed"
2280
+ innerStyle={{ maxHeight: 160 }}
2281
+ actions={actions}
2282
+ actionsColumnWidth={104}
2283
+ >
2284
+ <Table.Head>
2285
+ {headers.map((header) =>
2286
+ header.filterAndSort ? (
2287
+ <Table.HeadDropdownCell
2288
+ key={header.key}
2289
+ columnId={header.key}
2290
+ align={header.align}
2291
+ width={header.width}
2292
+ label={
2293
+ <>
2294
+ {getFilterIcon(header)} {header.label}
2295
+ </>
2296
+ }
2297
+ >
2298
+ <StyledFilterContainer>
2299
+ <Heading level={4}>Filter</Heading>
2300
+ <Text
2301
+ onChange={(e, { value }) =>
2302
+ setColumnFilter((prev) => ({
2303
+ ...prev,
2304
+ [header.key]: value,
2305
+ }))
2306
+ }
2307
+ value={columnFilter[header.key] || ''}
2308
+ />
2309
+ </StyledFilterContainer>
2310
+
2311
+ <Menu>
2312
+ <Menu.Heading title>Sort</Menu.Heading>
2313
+ <Menu.Divider />
2314
+ <Menu.Item
2315
+ selectable
2316
+ selected={sortKey === header.key && sortDir === 'asc'}
2317
+ onClick={() => {
2318
+ setSortKey(header.key);
2319
+ setSortDir('asc');
2320
+ }}
2321
+ >
2322
+ Ascending
2323
+ </Menu.Item>
2324
+ <Menu.Item
2325
+ selectable
2326
+ selected={sortKey === header.key && sortDir === 'desc'}
2327
+ onClick={() => {
2328
+ setSortKey(header.key);
2329
+ setSortDir('desc');
2330
+ }}
2331
+ >
2332
+ Descending
2333
+ </Menu.Item>
2334
+ </Menu>
2335
+ </Table.HeadDropdownCell>
2336
+ ) : (
2337
+ <Table.HeadCell
2338
+ key={header.key}
2339
+ columnId={header.key}
2340
+ align={header.align}
2341
+ width={header.width}
2342
+ onSort={handleSort}
2343
+ sortKey={header.key}
2344
+ sortDir={header.key === sortKey ? sortDir : 'none'}
2345
+ tooltip={header.tooltip}
2346
+ >
2347
+ {header.label}
2348
+ </Table.HeadCell>
2349
+ )
2350
+ )}
2351
+ </Table.Head>
2352
+ <Table.Body>
2353
+ {data
2354
+ .filter((row) => {
2355
+ return Object.entries(columnFilter).every(([key, filterValue]) => {
2356
+ if (!filterValue) return true;
2357
+ const cellValue = String(row[key as keyof Row]).toLowerCase();
2358
+ return cellValue.startsWith(filterValue.toLowerCase());
2359
+ });
2360
+ })
2361
+ .sort((rowA, rowB) => {
2362
+ if (sortDir === 'asc') {
2363
+ return rowA[sortKey] > rowB[sortKey] ? 1 : -1;
2364
+ }
2365
+ if (sortDir === 'desc') {
2366
+ return rowB[sortKey] > rowA[sortKey] ? 1 : -1;
2367
+ }
2368
+ return 0;
2369
+ })
2370
+ .map((row) => (
2371
+ <Table.Row
2372
+ key={row.email}
2373
+ actionPrimary={row.disabled ? undefined : rowActionPrimaryButton}
2374
+ actionsSecondary={
2375
+ row.disabled ? undefined : rowActionsSecondaryMenu
2376
+ }
2377
+ onRequestToggle={handleToggle}
2378
+ onClick={row.disabled ? undefined : handleRowClick}
2379
+ data={row}
2380
+ selected={row.selected}
2381
+ disabled={row.disabled}
2382
+ >
2383
+ {headers.map((header) => (
2384
+ <Table.Cell
2385
+ key={`${row.email}-${header.key}`}
2386
+ align={header.align}
2387
+ >
2388
+ {row[header.key]}
2389
+ </Table.Cell>
2390
+ ))}
2391
+ </Table.Row>
2392
+ ))}
2393
+ </Table.Body>
2394
+ </Table>
2395
+ <aside style={{ marginTop: 20 }} aria-live="polite" aria-relevant="text">
2396
+ <Typography as="p">
2397
+ Click a primary action, secondary action, or row to see the returned data
2398
+ </Typography>
2399
+ {activeRowData && (
2400
+ <div style={{ overflow: 'scroll' }}>
2401
+ <DL>
2402
+ <DT>Row:</DT>
2403
+ <DD>&apos;{activeRow}&apos;</DD>
2404
+ <DT>Data:</DT>
2405
+ <DD>
2406
+ <code>{activeRowData}</code>
2407
+ </DD>
2408
+ </DL>
2409
+ </div>
2410
+ )}
2411
+ {primaryActionRowData && (
2412
+ <div style={{ overflow: 'scroll', marginBottom: 10 }}>
2413
+ <DL>
2414
+ <DT>Primary action:</DT>
2415
+ <DD>&apos;{primaryAction}&apos;</DD>
2416
+ <DT>Data:</DT>
2417
+ <DD>
2418
+ <code>{primaryActionRowData}</code>
2419
+ </DD>
2420
+ </DL>
2421
+ </div>
2422
+ )}
2423
+ {secondaryActionRowData && (
2424
+ <div style={{ overflow: 'scroll', marginBottom: 10 }}>
2425
+ <DL>
2426
+ <DT>Secondary action:</DT>
2427
+ <DD>&apos;{secondaryAction}&apos;</DD>
2428
+ <DT>Data:</DT>
2429
+ <DD>
2430
+ <code>{secondaryActionRowData}</code>
2431
+ </DD>
2432
+ </DL>
2433
+ </div>
2434
+ )}
2435
+ </aside>
2436
+ </div>
2437
+ );
2438
+ }
2439
+
2440
+ export default Complex;
2441
+ ```
2442
+
2443
+
2444
+
2445
+
2446
+ ## API
2447
+
2448
+
2449
+ ### Table API
2450
+
2451
+ #### Props
2452
+
2453
+ | Name | Type | Required | Default | Description |
2454
+ |------|------|------|------|------|
2455
+ | actions | React.ReactElement[] | no | | Adds table-level actions. Not compatible with `onRequestResize`. |
2456
+ | actionsColumnWidth | number | no | | Specifies the width of the actions column. Adds an empty header for row actions if no table-level actions are present. |
2457
+ | children | React.ReactNode | no | | Must be `Table.Head`, `Table.Body`, or `Table.Caption`. |
2458
+ | dockOffset | number | no | | Sets the offset from the top of the window. Only applies when `headType` is 'docked'. |
2459
+ | dockScrollBar | boolean | no | | Docks the horizontal scroll bar at the bottom of the window when the bottom of the table is below the viewport. |
2460
+ | elementRef | React.Ref<HTMLDivElement> | no | | A React ref which is set to the DOM element when the component mounts and null when it unmounts. |
2461
+ | headType | 'docked' \| 'fixed' \| 'inline' | no | | Sets the table head type: * `docked`: The head is docked against the window * `fixed` : The head is fixed in the table. The table can scroll independently from the head. * `inline`: The head isn't fixed, but can scroll with the rest of the table. |
2462
+ | horizontalOverflow | 'auto' \| 'scroll' | no | | Controls how the Table handles horizontal content overflow: * `auto`: The default behavior for overflow. `HeadCell` content will truncate and `Cell` content will wrap. The Table will scroll horizontally when the container's width exceeds the Table's width. * `scroll`: The Table will scroll horizontally. `HeadCell` content will not truncate and `Cell` content will wrap only for word breaks. |
2463
+ | innerStyle | React.CSSProperties | no | | Style specification for the inner container, which is the scrolling container. |
2464
+ | onRequestMoveColumn | TableRequestMoveColumnHandler | no | | An event handler for handle the re-order action of Table. The function is passed an options object with `fromIndex` and `toIndex`. |
2465
+ | onRequestMoveRow | TableRequestMoveRowHandler | no | | An event handler to handle the reorder rows action of Table. The function is passed an options object with `fromIndex` and `toIndex`. |
2466
+ | onRequestResizeColumn | TableRequestResizeColumnHandler | no | | An event handler for resize of columns for the current column being resized. The function is passed an event and a data object with `columnId`, `id`, `index`, and `width`. Every Table.HeadCell must have a width prop when using onRequestResizeColumn. Table with resizableFillLayout supports width of "auto". |
2467
+ | onRequestToggleAllRows | () => void | no | | Callback invoked when a user clicks the row selection toggle in the header. |
2468
+ | onScroll | React.UIEventHandler<HTMLDivElement> | no | | Callback invoked when a scroll event occurs on the inner scrolling container. |
2469
+ | outerStyle | React.CSSProperties | no | | Style specification for the outer container. |
2470
+ | pinnedColumns | PinnedColumnsProp | no | | Optionally pin the actions column to the end of the table by passing `pinnedColumns={{ actions: true }}.` When using pinned columns `horizontalOverflow` should be set to `scroll`. |
2471
+ | primaryColumnIndex | number | no | | Indicates the column to use as the primary label for each row. |
2472
+ | resizableFillLayout | boolean | no | | Table will fill parent container. Resizable columns can have a `width` of `auto` only with this prop enabled. |
2473
+ | rowExpansion | 'single' \| 'multi' \| 'controlled' \| 'none' | no | | Adds a column to the table with an expansion button for each row that has expansion content. Supported values: * `single`: Only one row can be expanded at a time. If another expansion button is clicked, the currently expanded row closes and the new one opens. * `multi`: Allows multiple rows to be expanded at the same time. * `controlled`: Allows the expanded state to be externally managed by `expanded` prop of `Row`. * `none`: The default with no row expansion. |
2474
+ | rowSelection | 'all' \| 'some' \| 'none' | no | | When an `onRequestToggleAllRows` handler is defined, this prop determines the appearance of the toggle all rows button. |
2475
+ | stripeRows | boolean | no | | Alternate rows are given a darker background to improve readability. |
2476
+ | tableStyle | React.CSSProperties | no | | The style attribute for the table. This is primarily useful for setting the CSS table-layout property. |
2477
+
2478
+ #### Types
2479
+
2480
+ | Name | Type | Description |
2481
+ |------|------|------|
2482
+ | PinnedColumnsProp | { actions?: boolean; } | |
2483
+ | TableRequestMoveColumnHandler | (data: { fromIndex: number; toIndex: number }) => void | |
2484
+ | TableRequestMoveRowHandler | (data: { fromIndex: number; toIndex: number }) => void | |
2485
+ | TableRequestResizeColumnHandler | ( event: React.MouseEvent<HTMLHRElement> \| React.KeyboardEvent<HTMLHRElement> \| MouseEvent, data: { columnId?: string; id?: string; index: number; width: number; } ) => void | |
2486
+
2487
+
2488
+
2489
+ ### Table.Head API
2490
+
2491
+ #### Props
2492
+
2493
+ | Name | Type | Required | Default | Description |
2494
+ |------|------|------|------|------|
2495
+ | children | React.ReactNode | no | | Must be `Table.HeadCell`s or `Table.HeadDropdownCell`s. |
2496
+ | elementRef | React.Ref<HTMLTableSectionElement> | no | | A React ref which is set to the DOM element when the component mounts and null when it unmounts. |
2497
+
2498
+
2499
+
2500
+ ### Table.Body API
2501
+
2502
+ #### Props
2503
+
2504
+ | Name | Type | Required | Default | Description |
2505
+ |------|------|------|------|------|
2506
+ | children | React.ReactNode | no | | Must be `Table.Row`. |
2507
+ | elementRef | React.Ref<HTMLTableSectionElement> | no | | A React ref which is set to the DOM element when the component mounts and null when it unmounts. |
2508
+
2509
+
2510
+
2511
+ ### Table.Row API
2512
+
2513
+ #### Props
2514
+
2515
+ | Name | Type | Required | Default | Description |
2516
+ |------|------|------|------|------|
2517
+ | actionPrimary | React.ReactElement | no | | Adds primary actions. For best results, use an icon-only button style. The `onClick` handler of each action is passed the event and the `data` prop of this row. |
2518
+ | actionsSecondary | React.ReactElement | no | | Adds a secondary actions dropdown menu. This prop must be a `Menu`. The `onClick` handler of each action is passed the event and the `data` prop of this row. |
2519
+ | children | React.ReactNode | no | | Must be `Table.Cell`. |
2520
+ | data | any | no | | This data is returned with the onClick and toggle events as the second argument. |
2521
+ | disabled | boolean | no | | Indicates whether the row selection is disabled. |
2522
+ | elementRef | React.Ref<HTMLTableRowElement> | no | | A React ref which is set to the DOM element when the component mounts and null when it unmounts. |
2523
+ | expanded | boolean | no | | Allows row expansion to be controlled programmatically if the `rowExpansion` prop is set to `controlled` in `Table`. |
2524
+ | expansionRow | React.ReactElement \| React.ReactElement[] | no | | An optional row that is displayed when this row is expanded, or an array of rows. |
2525
+ | onClick | RowClickHandler | no | | Providing an `onClick` handler enables focus, hover, and related styles. |
2526
+ | onExpansion | ( event: React.MouseEvent<HTMLButtonElement> \| React.KeyboardEvent<HTMLButtonElement>, data?: any // eslint-disable-line @typescript-eslint/no-explicit-any ) => void | no | | An event handler that triggers when the row expansion element is selected. |
2527
+ | onRequestToggle | RowRequestToggleHandler | no | | An event handler for toggle of the row. resize of columns. The function is passed the event and the `data` prop for this row. |
2528
+ | rowScreenReaderText | string | no | | Indicates the row's label when selected or unselected. |
2529
+ | selected | boolean | no | | When an `onRequestToggle` handler is defined, this prop determines the appearance of the toggle. |
2530
+
2531
+ #### Types
2532
+
2533
+ | Name | Type | Description |
2534
+ |------|------|------|
2535
+ | RowActionPrimaryClickHandler | ( event: React.MouseEvent, data?: any // eslint-disable-line @typescript-eslint/no-explicit-any ) => void | |
2536
+ | RowActionSecondaryClickHandler | ( event: React.MouseEvent, data?: any // eslint-disable-line @typescript-eslint/no-explicit-any ) => void | |
2537
+ | RowClickHandler | ( event: React.MouseEvent<HTMLTableRowElement> \| React.KeyboardEvent<HTMLTableRowElement>, data?: any // eslint-disable-line @typescript-eslint/no-explicit-any ) => void | |
2538
+ | RowRequestExpansionHandler | ( event: React.MouseEvent<HTMLButtonElement> \| React.KeyboardEvent<HTMLButtonElement>, data?: any // eslint-disable-line @typescript-eslint/no-explicit-any ) => void | |
2539
+ | RowRequestToggleHandler | ( event: React.MouseEvent<HTMLButtonElement \| HTMLAnchorElement>, data?: any // eslint-disable-line @typescript-eslint/no-explicit-any ) => void | |
2540
+
2541
+
2542
+
2543
+ ### Table.Cell API
2544
+
2545
+ #### Props
2546
+
2547
+ | Name | Type | Required | Default | Description |
2548
+ |------|------|------|------|------|
2549
+ | align | 'left' \| 'center' \| 'right' | no | 'left' | Align the text in the cell. |
2550
+ | children | React.ReactNode | no | | |
2551
+ | data | any | no | | This data is returned with the onClick events as the second argument. |
2552
+ | elementRef | React.Ref<HTMLTableCellElement> | no | | A React ref which is set to the DOM element when the component mounts and null when it unmounts. |
2553
+ | onClick | CellClickHandler | no | | Providing an `onClick` handler enables focus, hover, and related styles. |
2554
+
2555
+ #### Types
2556
+
2557
+ | Name | Type | Description |
2558
+ |------|------|------|
2559
+ | CellClickHandler | ( event: React.MouseEvent<HTMLTableCellElement> \| React.KeyboardEvent<HTMLTableCellElement>, data?: any // eslint-disable-line @typescript-eslint/no-explicit-any ) => void | |
2560
+
2561
+
2562
+
2563
+ ### Table.HeadCell API
2564
+
2565
+ #### Props
2566
+
2567
+ | Name | Type | Required | Default | Description |
2568
+ |------|------|------|------|------|
2569
+ | align | 'left' \| 'center' \| 'right' | no | 'left' | Align the text in the label. |
2570
+ | children | React.ReactNode | no | | |
2571
+ | columnId | string | no | | An id that is passed to the `onSort` callback and as `Table`'s `onRequestResizeColumn` callback. |
2572
+ | elementRef | React.Ref<HTMLTableCellElement> | no | | A React ref which is set to the DOM element when the component mounts and null when it unmounts. |
2573
+ | headCellScreenReaderText | string | no | | A string used to generate the `aria-label` for the head cell during column reordering. When the `children` prop is not a string, providing `headCellScreenReaderText` is recommended to improve the screen reader announcements during the reordering interaction. |
2574
+ | onSort | HeadCellSortHandler | no | | A callback invoked when this head cell is clicked. If provided, this HeadCell is sortable and renders the appropriate user interface. |
2575
+ | resizable | boolean | no | true | Allows the user to resize the column when onRequestResize is passed to the `Table`. Set resizable to `false` to prevent some columns for resizing. |
2576
+ | sortDir | HeadCellSortDir | no | 'none' | The current sort direction of this column. |
2577
+ | sortKey | string | no | | The `sortKey` is passed in the data object to the `onSort` callback, if provided. |
2578
+ | tooltip | React.ReactNode | no | | Content to show in a tooltip. |
2579
+ | truncate | boolean | no | true | Truncate the text in the label. |
2580
+ | width | number \| 'auto' | no | | The width of the column in pixels. |
2581
+
2582
+ #### Types
2583
+
2584
+ | Name | Type | Description |
2585
+ |------|------|------|
2586
+ | HeadCellSortDir | 'asc' \| 'desc' \| 'none' | |
2587
+ | HeadCellSortHandler | ( event: \| React.MouseEvent<HTMLButtonElement \| HTMLDivElement> \| React.KeyboardEvent<HTMLButtonElement \| HTMLDivElement>, data: { columnId?: string; id?: string; index: number; sortDir: HeadCellSortDir; sortKey?: string; } ) => void | |
2588
+
2589
+
2590
+
2591
+ ### Table.HeadDropdownCell API
2592
+
2593
+ #### Props
2594
+
2595
+ | Name | Type | Required | Default | Description |
2596
+ |------|------|------|------|------|
2597
+ | align | 'left' \| 'center' \| 'right' | no | 'left' | Align the text in the label. |
2598
+ | buttonRef | React.Ref<HTMLButtonElement> | no | | A React ref which is set to the button element when the component mounts and null when it unmounts. |
2599
+ | canCoverHead | boolean | no | true | If there is not enough room to render the `Popover` in a direction, this option enables it to be rendered over the Head. |
2600
+ | children | React.ReactNode | yes | | |
2601
+ | closeReasons | HeadDropdownCellPossibleCloseReason[] | no | [ 'clickAway', 'contentClick', 'escapeKey', 'offScreen', 'tabKey', 'toggleClick', ] | An array of reasons for which this `Popover` should close. |
2602
+ | columnId | string | no | | An id that is passed to the `onRequestOpen`, `onRequestClose` callback and as `Table`'s `onRequestResizeColumn` callback. |
2603
+ | elementRef | React.Ref<HTMLTableCellElement> | no | | A React ref which is set to the DOM element when the component mounts and null when it unmounts. |
2604
+ | focusToggleReasons | HeadDropdownCellPossibleCloseReason[] | no | [ 'contentClick', 'escapeKey', 'toggleClick', ] | An array of reasons for which to set focus on the toggle. Only subset of `closeReasons` will be honored. When Menu.Items open a Modal or other dialog, it may be necessary to remove the 'contentClick' reason to allow focus to be passed to the dialog. |
2605
+ | headCellScreenReaderText | string | no | | A string used to generate the `aria-label` for the head cell during column reordering. When the `label` prop is not a string, providing `headCellScreenReaderText` is recommended to improve the screen reader announcements during the reordering interaction. |
2606
+ | label | React.ReactNode | no | | The label on the heading, which may simply be text or may contain an element with icons or other markup. |
2607
+ | onRequestClose | HeadDropdownCellRequestCloseHandler | no | | A callback function invoked with a data object containing the event (if applicable) and a reason for the close request. |
2608
+ | onRequestOpen | HeadDropdownCellRequestOpenHandler | no | | A callback function invoked with a data object containing the event. (The reason is always toggleClick). |
2609
+ | open | boolean | no | | If an open prop is provided, this component will behave as a [controlled component](https://reactjs.org/docs/forms.html#controlled-components). This means that the consumer is responsible for handling the open/close state. If no open prop is provided, the component will handle the open/close state internally. |
2610
+ | repositionMode | 'none' \| 'flip' \| 'any' | no | 'flip' | See `repositionMode` on `Popover` for details. |
2611
+ | resizable | boolean | no | true | Allow the user to resize the column when onRequestResize is passed to the `Table`. Set resizable to false to prevent some columns for resizing. |
2612
+ | retainFocus | boolean | no | | Keep focus within the Popover while open. Note, Menu handles it's own focus by default, so this is only necessary when the popover contains other types of content. |
2613
+ | takeFocus | boolean | no | true | When true, the Popover will automatically take focus when 'open' changes to true. Disable this for a Popover that has shows on hover, such as a tooltip. |
2614
+ | truncate | boolean | no | true | Truncate the text in the label. `truncate=false` is not compatible with `Table`'s `onRequestResize`. |
2615
+ | width | number | no | | The width of the column in pixels. |
2616
+
2617
+ #### Types
2618
+
2619
+ | Name | Type | Description |
2620
+ |------|------|------|
2621
+ | HeadDropdownCellPossibleCloseReason | \| 'clickAway' \| 'contentClick' \| 'escapeKey' \| 'offScreen' \| 'tabKey' \| 'toggleClick' | |
2622
+ | HeadDropdownCellRequestCloseHandler | ( event: \| React.MouseEvent<HTMLDivElement \| HTMLButtonElement> \| React.KeyboardEvent<HTMLDivElement \| HTMLButtonElement> \| MouseEvent \| KeyboardEvent \| TouchEvent \| undefined, data: { columnId?: string; index: number; reason: HeadDropdownCellPossibleCloseReason; } ) => void | |
2623
+ | HeadDropdownCellRequestOpenHandler | ( event: \| React.MouseEvent<HTMLButtonElement \| HTMLDivElement> \| React.KeyboardEvent<HTMLButtonElement \| HTMLDivElement>, data: { columnId?: string; index: number; reason: 'toggleClick'; } ) => void | |
2624
+
2625
+
2626
+
2627
+ ### Table.Caption API
2628
+
2629
+ Tables that use a docked header must place the caption on the bottom side.
2630
+ Tables that use a fixed header cannot use captions.
2631
+
2632
+ #### Props
2633
+
2634
+ | Name | Type | Required | Default | Description |
2635
+ |------|------|------|------|------|
2636
+ | children | React.ReactNode | yes | | |
2637
+ | side | 'top' \| 'bottom' | no | 'top' | The location of the caption relative to the table. |
2638
+
2639
+
2640
+
2641
+
2642
+