@okta/odyssey-react-mui 1.14.7 → 1.15.8

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 (93) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/dist/Autocomplete.js +84 -2
  3. package/dist/Autocomplete.js.map +1 -1
  4. package/dist/Breadcrumbs.js +13 -3
  5. package/dist/Breadcrumbs.js.map +1 -1
  6. package/dist/Button.js +2 -2
  7. package/dist/Button.js.map +1 -1
  8. package/dist/Callout.js.map +1 -1
  9. package/dist/Checkbox.js +8 -4
  10. package/dist/Checkbox.js.map +1 -1
  11. package/dist/CheckboxGroup.js.map +1 -1
  12. package/dist/DataTable/DataTable.js +37 -9
  13. package/dist/DataTable/DataTable.js.map +1 -1
  14. package/dist/DataTable/DataTableEmptyState.js +1 -0
  15. package/dist/DataTable/DataTableEmptyState.js.map +1 -1
  16. package/dist/DataTable/DataTablePagination.js +1 -0
  17. package/dist/DataTable/DataTablePagination.js.map +1 -1
  18. package/dist/DataTable/DataTableRowActions.js +1 -0
  19. package/dist/DataTable/DataTableRowActions.js.map +1 -1
  20. package/dist/DataTable/DataTableSettings.js +1 -0
  21. package/dist/DataTable/DataTableSettings.js.map +1 -1
  22. package/dist/MenuButton.js.map +1 -1
  23. package/dist/RadioGroup.js.map +1 -1
  24. package/dist/Select.js +3 -4
  25. package/dist/Select.js.map +1 -1
  26. package/dist/Tabs.js +21 -0
  27. package/dist/Tabs.js.map +1 -1
  28. package/dist/Tile.js +14 -18
  29. package/dist/Tile.js.map +1 -1
  30. package/dist/labs/DataFilters.js +98 -97
  31. package/dist/labs/DataFilters.js.map +1 -1
  32. package/dist/labs/StaticTable.js +1 -0
  33. package/dist/labs/StaticTable.js.map +1 -1
  34. package/dist/labs/index.js +0 -1
  35. package/dist/labs/index.js.map +1 -1
  36. package/dist/src/Autocomplete.d.ts +7 -1
  37. package/dist/src/Autocomplete.d.ts.map +1 -1
  38. package/dist/src/Breadcrumbs.d.ts +1 -1
  39. package/dist/src/Breadcrumbs.d.ts.map +1 -1
  40. package/dist/src/Button.d.ts +31 -15
  41. package/dist/src/Button.d.ts.map +1 -1
  42. package/dist/src/Callout.d.ts +1 -1
  43. package/dist/src/Checkbox.d.ts.map +1 -1
  44. package/dist/src/CheckboxGroup.d.ts +2 -3
  45. package/dist/src/CheckboxGroup.d.ts.map +1 -1
  46. package/dist/src/DataTable/DataTable.d.ts +6 -2
  47. package/dist/src/DataTable/DataTable.d.ts.map +1 -1
  48. package/dist/src/DataTable/DataTableEmptyState.d.ts.map +1 -1
  49. package/dist/src/DataTable/DataTablePagination.d.ts.map +1 -1
  50. package/dist/src/DataTable/DataTableRowActions.d.ts.map +1 -1
  51. package/dist/src/DataTable/DataTableSettings.d.ts.map +1 -1
  52. package/dist/src/MenuButton.d.ts +3 -4
  53. package/dist/src/MenuButton.d.ts.map +1 -1
  54. package/dist/src/RadioGroup.d.ts +3 -3
  55. package/dist/src/RadioGroup.d.ts.map +1 -1
  56. package/dist/src/Select.d.ts.map +1 -1
  57. package/dist/src/Tabs.d.ts.map +1 -1
  58. package/dist/src/Tile.d.ts.map +1 -1
  59. package/dist/src/labs/DataFilters.d.ts +9 -1
  60. package/dist/src/labs/DataFilters.d.ts.map +1 -1
  61. package/dist/src/labs/StaticTable.d.ts.map +1 -1
  62. package/dist/src/labs/index.d.ts +0 -1
  63. package/dist/src/labs/index.d.ts.map +1 -1
  64. package/dist/src/theme/components.d.ts.map +1 -1
  65. package/dist/theme/components.js +15 -7
  66. package/dist/theme/components.js.map +1 -1
  67. package/dist/tsconfig.production.tsbuildinfo +1 -1
  68. package/package.json +9 -7
  69. package/src/Autocomplete.tsx +124 -1
  70. package/src/Breadcrumbs.tsx +16 -3
  71. package/src/Button.tsx +32 -16
  72. package/src/Callout.tsx +1 -1
  73. package/src/Checkbox.tsx +4 -3
  74. package/src/CheckboxGroup.tsx +2 -5
  75. package/src/DataTable/DataTable.tsx +75 -13
  76. package/src/DataTable/DataTableEmptyState.tsx +2 -0
  77. package/src/DataTable/DataTablePagination.tsx +2 -0
  78. package/src/DataTable/DataTableRowActions.tsx +2 -0
  79. package/src/DataTable/DataTableSettings.tsx +2 -0
  80. package/src/MenuButton.tsx +11 -21
  81. package/src/RadioGroup.tsx +3 -3
  82. package/src/Select.tsx +5 -2
  83. package/src/Tabs.tsx +40 -1
  84. package/src/Tile.tsx +12 -14
  85. package/src/labs/DataFilters.tsx +211 -177
  86. package/src/labs/StaticTable.tsx +4 -0
  87. package/src/labs/index.ts +0 -1
  88. package/src/theme/components.tsx +16 -7
  89. package/dist/labs/VirtualizedAutocomplete.js +0 -134
  90. package/dist/labs/VirtualizedAutocomplete.js.map +0 -1
  91. package/dist/src/labs/VirtualizedAutocomplete.d.ts +0 -90
  92. package/dist/src/labs/VirtualizedAutocomplete.d.ts.map +0 -1
  93. package/src/labs/VirtualizedAutocomplete.tsx +0 -365
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@okta/odyssey-react-mui",
3
- "version": "1.14.7",
3
+ "version": "1.15.8",
4
4
  "description": "React MUI components for Odyssey, Okta's design system",
5
5
  "author": "Okta, Inc.",
6
6
  "license": "Apache-2.0",
@@ -43,25 +43,27 @@
43
43
  },
44
44
  "dependencies": {
45
45
  "@emotion/cache": "^11.11.0",
46
- "@emotion/react": "^11.11.3",
46
+ "@emotion/react": "^11.11.4",
47
47
  "@emotion/styled": "^11.11.0",
48
48
  "@mui/icons-material": "^5.15.10",
49
49
  "@mui/lab": "^5.0.0-alpha.165",
50
- "@mui/material": "^5.15.10",
50
+ "@mui/material": "^5.15.12",
51
51
  "@mui/system": "^5.15.9",
52
52
  "@mui/utils": "^5.15.9",
53
53
  "@mui/x-date-pickers": "^5.0.15",
54
- "@okta/odyssey-design-tokens": "^1.14.7",
54
+ "@okta/odyssey-design-tokens": "^1.15.8",
55
55
  "date-fns": "^2.30.0",
56
56
  "i18next": "^23.8.2",
57
57
  "material-react-table": "^2.11.3",
58
58
  "react-i18next": "^14.0.5",
59
+ "react-virtualized-auto-sizer": "^1.0.22",
60
+ "react-window": "^1.8.10",
59
61
  "tsx": "^4.7.1",
60
62
  "word-wrap": "^1.2.5"
61
63
  },
62
64
  "peerDependencies": {
63
- "react": ">=17 <19",
64
- "react-dom": ">=17 <19"
65
+ "react": "^18.2.0",
66
+ "react-dom": "^18.2.0"
65
67
  },
66
- "gitHead": "f4d64bf568d491147afa57aaba6e218c1d496bab"
68
+ "gitHead": "7ce4fb11fd0b464ca6f1476e9cfaa0d320bc7d2e"
67
69
  }
@@ -18,7 +18,29 @@ import {
18
18
  AutocompleteValue,
19
19
  AutocompleteRenderInputParams,
20
20
  } from "@mui/material";
21
- import { memo, useCallback, useMemo, useRef } from "react";
21
+ import {
22
+ createContext,
23
+ FC,
24
+ forwardRef,
25
+ HTMLAttributes,
26
+ memo,
27
+ ReactElement,
28
+ useCallback,
29
+ useContext,
30
+ useEffect,
31
+ useMemo,
32
+ useRef,
33
+ } from "react";
34
+ import styled from "@emotion/styled";
35
+ import { VariableSizeList, ListChildComponentProps } from "react-window";
36
+ import _AutoSizer, {
37
+ Props as AutoSizerProps,
38
+ Size as AutoSizerSize,
39
+ } from "react-virtualized-auto-sizer";
40
+
41
+ // This is required to get around a react-types issue for "AutoSizer is not a valid JSX element."
42
+ // @see https://github.com/bvaughn/react-virtualized/issues/1739#issuecomment-1291444246
43
+ const AutoSizer = _AutoSizer as unknown as FC<AutoSizerProps>;
22
44
 
23
45
  import { Field } from "./Field";
24
46
  import { FieldComponentProps } from "./FieldComponentProps";
@@ -160,6 +182,13 @@ export type AutocompleteProps<
160
182
  * You will need to implement this function if your `option` items are objects.
161
183
  */
162
184
  getIsOptionEqualToValue?: (option: OptionType, value: OptionType) => boolean;
185
+
186
+ /**
187
+ * If this component is required to display a virtualized list of options,
188
+ * then this value needs to be set to true.
189
+ * It is recommended if you're options are on the order of 10's of hundreds or more.
190
+ */
191
+ isVirtualized?: boolean;
163
192
  } & Pick<
164
193
  FieldComponentProps,
165
194
  | "errorMessage"
@@ -173,6 +202,11 @@ export type AutocompleteProps<
173
202
  > &
174
203
  Pick<HtmlProps, "ariaDescribedBy" | "testId" | "translate">;
175
204
 
205
+ const ListboxContainer = styled.div`
206
+ width: 100%;
207
+ height: 100%;
208
+ `;
209
+
176
210
  const Autocomplete = <
177
211
  OptionType,
178
212
  HasMultipleChoices extends boolean | undefined,
@@ -191,6 +225,7 @@ const Autocomplete = <
191
225
  isLoading,
192
226
  isOptional = false,
193
227
  isReadOnly,
228
+ isVirtualized: isVirtualizedProp = false,
194
229
  hint,
195
230
  HintLinkComponent,
196
231
  label,
@@ -211,6 +246,8 @@ const Autocomplete = <
211
246
  uncontrolledValue: defaultValue,
212
247
  }),
213
248
  );
249
+
250
+ const isVirtualized = useRef(Boolean(isVirtualizedProp));
214
251
  const defaultValueProp = useMemo<
215
252
  | AutocompleteValue<
216
253
  OptionType,
@@ -261,6 +298,7 @@ const Autocomplete = <
261
298
  hasVisibleLabel
262
299
  //@ts-expect-error htmlFor does not exist ont he InputLabelProps for autocomplete
263
300
  id={InputLabelProps.htmlFor}
301
+ isFullWidth={isFullWidth}
264
302
  hint={hint}
265
303
  HintLinkComponent={HintLinkComponent}
266
304
  label={label}
@@ -294,12 +332,95 @@ const Autocomplete = <
294
332
  errorMessageList,
295
333
  hint,
296
334
  HintLinkComponent,
335
+ isFullWidth,
297
336
  isOptional,
298
337
  label,
299
338
  nameOverride,
300
339
  testId,
301
340
  ],
302
341
  );
342
+
343
+ const renderVirtualizedRow = ({
344
+ data,
345
+ index,
346
+ style,
347
+ }: ListChildComponentProps) => {
348
+ const baseOption = data[index];
349
+ /**
350
+ * react-window calculates the absolute positions of the list items, via an inline style, so
351
+ * we need to add it to each list item that is being rendered in the viewable list window.
352
+ * See here if you need to know more: https://github.com/bvaughn/react-window?tab=readme-ov-file#why-is-my-list-blank-when-i-scroll
353
+ */
354
+ const optionItem = { ...baseOption, props: { ...baseOption.props, style } };
355
+ return optionItem;
356
+ };
357
+
358
+ const OuterListboxContext = createContext({});
359
+
360
+ const OuterListboxElementType = forwardRef<HTMLDivElement>((props, ref) => {
361
+ const outerProps = useContext(OuterListboxContext);
362
+ return <div ref={ref} {...props} {...outerProps} />;
363
+ });
364
+
365
+ function useResetCache(length: number) {
366
+ const ref = useRef<VariableSizeList>(null);
367
+ useEffect(() => {
368
+ if (ref.current) {
369
+ ref.current.resetAfterIndex(0, true);
370
+ }
371
+ }, [length]);
372
+ return ref;
373
+ }
374
+
375
+ const ListboxComponent = forwardRef<
376
+ HTMLDivElement,
377
+ HTMLAttributes<HTMLElement>
378
+ >(function (props, ref) {
379
+ const { children, ...other } = props;
380
+ const itemData: ReactElement[] = (children as ReactElement[]).flatMap(
381
+ (item: ReactElement & { children?: ReactElement[] }) =>
382
+ [item].concat(item.children || []),
383
+ );
384
+
385
+ // the height of an Odyssey autocomplete option item that is used to calculate height of window
386
+ const optionHeight = 45; //px
387
+
388
+ // The number of items (rows or columns) to render outside of the visible area for performance and scrolling reasons
389
+ const overscanRowCount = 8;
390
+
391
+ const itemSize = useCallback(() => optionHeight, []);
392
+
393
+ const gridRef = useResetCache(itemData.length);
394
+
395
+ const renderWindow = useCallback(
396
+ ({ height, width }: AutoSizerSize) => (
397
+ <VariableSizeList
398
+ innerElementType="ul"
399
+ itemData={itemData}
400
+ itemCount={itemData.length}
401
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
402
+ itemSize={itemSize}
403
+ height={height}
404
+ width={width}
405
+ ref={gridRef}
406
+ outerElementType={OuterListboxElementType}
407
+ overscanCount={overscanRowCount}
408
+ >
409
+ {renderVirtualizedRow}
410
+ </VariableSizeList>
411
+ ),
412
+ [itemData, gridRef, itemSize],
413
+ );
414
+
415
+ return (
416
+ <ListboxContainer ref={ref}>
417
+ <OuterListboxContext.Provider value={other}>
418
+ <AutoSizer>{renderWindow}</AutoSizer>
419
+ </OuterListboxContext.Provider>
420
+ </ListboxContainer>
421
+ );
422
+ });
423
+
303
424
  const onChange = useCallback<
304
425
  NonNullable<
305
426
  UseAutocompleteProps<
@@ -336,6 +457,8 @@ const Autocomplete = <
336
457
  <MuiAutocomplete
337
458
  {...valueProps}
338
459
  {...inputValueProp}
460
+ // conditionally provide the ListboxComponent if this needs to be virtualized
461
+ {...(isVirtualized.current && { ListboxComponent })}
339
462
  // AutoComplete is wrapped in a div within MUI which does not get the disabled attr. So this aria-disabled gets set in the div
340
463
  aria-disabled={isDisabled}
341
464
  disableCloseOnSelect={hasMultipleChoices}
@@ -30,6 +30,7 @@ import { GroupIcon, HomeIcon, UserIcon } from "./icons.generated";
30
30
  import { Subordinate } from "./Typography";
31
31
  import { useTranslation } from "react-i18next";
32
32
  import { HtmlProps } from "./HtmlProps";
33
+ import styled from "@emotion/styled";
33
34
 
34
35
  export type BreadcrumbType = "listItem" | "menuItem" | "currentPage";
35
36
 
@@ -53,6 +54,13 @@ export const BreadcrumbContext = createContext<BreadcrumbContextType>({
53
54
  breadcrumbType: "listItem",
54
55
  });
55
56
 
57
+ const BreadcrumbContent = styled.span`
58
+ white-space: nowrap;
59
+ overflow: hidden;
60
+ max-width: 10rem;
61
+ text-overflow: ellipsis;
62
+ `;
63
+
56
64
  export const Breadcrumb = ({ children, href, iconName }: BreadcrumbProps) => {
57
65
  const { breadcrumbType } = useContext(BreadcrumbContext);
58
66
 
@@ -63,7 +71,7 @@ export const Breadcrumb = ({ children, href, iconName }: BreadcrumbProps) => {
63
71
  ) : iconName === "user" ? (
64
72
  <UserIcon />
65
73
  ) : null}
66
- {children}
74
+ <BreadcrumbContent>{children}</BreadcrumbContent>
67
75
  </>
68
76
  );
69
77
 
@@ -101,7 +109,7 @@ const defaultTruncationValue = 5;
101
109
  const BreadcrumbList = ({
102
110
  children,
103
111
  homeHref,
104
- maxVisibleItems = defaultTruncationValue,
112
+ maxVisibleItems: maxVisibleItemsProp = defaultTruncationValue,
105
113
  testId,
106
114
  translate,
107
115
  }: BreadcrumbsProps) => {
@@ -109,6 +117,11 @@ const BreadcrumbList = ({
109
117
 
110
118
  const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
111
119
 
120
+ const maxVisibleItems = useMemo(
121
+ () => Math.min(Math.max(maxVisibleItemsProp, 0), children.length),
122
+ [maxVisibleItemsProp, children],
123
+ );
124
+
112
125
  const breadcrumbSections = useMemo(() => {
113
126
  if (children.length <= maxVisibleItems) {
114
127
  return {
@@ -155,7 +168,7 @@ const BreadcrumbList = ({
155
168
  </BreadcrumbContext.Provider>
156
169
  ))}
157
170
 
158
- {breadcrumbSections.insideMenu && (
171
+ {breadcrumbSections.insideMenu.length > 0 && (
159
172
  <>
160
173
  <ButtonBase onClick={onMenuButtonClick}>...</ButtonBase>
161
174
  <Menu
package/src/Button.tsx CHANGED
@@ -13,6 +13,7 @@
13
13
  import { Button as MuiButton } from "@mui/material";
14
14
  import type { ButtonProps as MuiButtonProps } from "@mui/material";
15
15
  import {
16
+ HTMLAttributes,
16
17
  memo,
17
18
  ReactElement,
18
19
  useCallback,
@@ -45,10 +46,6 @@ export type ButtonProps = {
45
46
  * The ref forwarded to the Button
46
47
  */
47
48
  buttonRef?: React.RefObject<FocusHandle>;
48
- /**
49
- * The icon element to display at the end of the Button
50
- */
51
- endIcon?: ReactElement;
52
49
  /**
53
50
  * The ID of the Button
54
51
  */
@@ -61,10 +58,6 @@ export type ButtonProps = {
61
58
  * Determines whether the Button should take up the full available width
62
59
  */
63
60
  isFullWidth?: boolean;
64
- /**
65
- * The text content of the Button
66
- */
67
- label?: string;
68
61
  /**
69
62
  * The click event handler for the Button
70
63
  */
@@ -73,10 +66,7 @@ export type ButtonProps = {
73
66
  * The size of the button
74
67
  */
75
68
  size?: (typeof buttonSizeValues)[number];
76
- /**
77
- * The icon element to display at the start of the Button
78
- */
79
- startIcon?: ReactElement;
69
+ tabIndex?: HTMLAttributes<HTMLElement>["tabIndex"];
80
70
  /**
81
71
  * The tooltip text for the Button if it's icon-only
82
72
  */
@@ -91,18 +81,45 @@ export type ButtonProps = {
91
81
  variant: (typeof buttonVariantValues)[number] | "tertiary";
92
82
  } & (
93
83
  | {
84
+ /**
85
+ * The icon element to display at the end of the Button
86
+ */
94
87
  endIcon?: ReactElement;
88
+ /**
89
+ * The text content of the Button
90
+ */
95
91
  label: string;
92
+ /**
93
+ * The icon element to display at the start of the Button
94
+ */
96
95
  startIcon?: ReactElement;
97
96
  }
98
97
  | {
98
+ /**
99
+ * The icon element to display at the end of the Button
100
+ */
99
101
  endIcon?: ReactElement;
100
- label?: "" | undefined;
102
+ /**
103
+ * The text content of the Button
104
+ */
105
+ label?: string | "" | undefined;
106
+ /**
107
+ * The icon element to display at the start of the Button
108
+ */
101
109
  startIcon: ReactElement;
102
110
  }
103
111
  | {
112
+ /**
113
+ * The icon element to display at the end of the Button
114
+ */
104
115
  endIcon: ReactElement;
105
- label?: "" | undefined;
116
+ /**
117
+ * The text content of the Button
118
+ */
119
+ label?: never;
120
+ /**
121
+ * The icon element to display at the start of the Button
122
+ */
106
123
  startIcon?: ReactElement;
107
124
  }
108
125
  ) &
@@ -184,7 +201,7 @@ const Button = ({
184
201
  data-se={testId}
185
202
  disabled={isDisabled}
186
203
  endIcon={endIcon}
187
- fullWidth={buttonContext.isFullWidth ?? isFullWidth}
204
+ fullWidth={isFullWidth}
188
205
  id={id}
189
206
  onClick={onClick}
190
207
  ref={localButtonRef}
@@ -210,7 +227,6 @@ const Button = ({
210
227
  id,
211
228
  isDisabled,
212
229
  isFullWidth,
213
- buttonContext.isFullWidth,
214
230
  label,
215
231
  onClick,
216
232
  size,
package/src/Callout.tsx CHANGED
@@ -34,7 +34,7 @@ export const calloutSeverityValues = [
34
34
 
35
35
  export type CalloutProps = {
36
36
  /**
37
- * @deprecated Callout content shuold be set via title, text, linkText, and linkUrl
37
+ * Used to optionally pass a text list to the component
38
38
  */
39
39
  children?: ReactNode;
40
40
  /**
package/src/Checkbox.tsx CHANGED
@@ -115,7 +115,7 @@ const Checkbox = ({
115
115
  const label = useMemo(() => {
116
116
  return (
117
117
  <>
118
- {labelProp}
118
+ <Typography component="span">{labelProp}</Typography>
119
119
  {isRequired && (
120
120
  <>
121
121
  {" "}
@@ -164,10 +164,11 @@ const Checkbox = ({
164
164
  inputProps={{
165
165
  "data-se": testId,
166
166
  }}
167
+ disabled={isDisabled}
167
168
  inputRef={localInputRef}
168
- sx={() => ({
169
+ sx={{
169
170
  marginBlockStart: "2px",
170
- })}
171
+ }}
171
172
  />
172
173
  }
173
174
  disabled={isDisabled}
@@ -11,9 +11,8 @@
11
11
  */
12
12
 
13
13
  import { FormGroup as MuiFormGroup } from "@mui/material";
14
- import { memo, ReactElement, useCallback } from "react";
14
+ import { memo, ReactNode, useCallback } from "react";
15
15
 
16
- import { Checkbox } from "./Checkbox";
17
16
  import { Field } from "./Field";
18
17
  import {
19
18
  FieldComponentProps,
@@ -25,9 +24,7 @@ export type CheckboxGroupProps = {
25
24
  /**
26
25
  * A single Checkbox element or an array of Checkbox elements
27
26
  */
28
- children:
29
- | ReactElement<typeof Checkbox>
30
- | Array<ReactElement<typeof Checkbox>>;
27
+ children: ReactNode;
31
28
  /**
32
29
  * If `true`, the CheckboxGroup is required
33
30
  */
@@ -49,7 +49,11 @@ import { useRowReordering } from "./useRowReordering";
49
49
  import { DataTableSettings } from "./DataTableSettings";
50
50
  import { MenuButton, MenuButtonProps } from "../MenuButton";
51
51
  import { Box } from "../Box";
52
- import { DataTableRowSelectionState } from ".";
52
+ import {
53
+ DataTableColumn,
54
+ DataTableRowData,
55
+ DataTableRowSelectionState,
56
+ } from ".";
53
57
  import {
54
58
  DesignTokens,
55
59
  useOdysseyDesignTokens,
@@ -201,6 +205,10 @@ export type DataTableProps = {
201
205
  * The component to display when the query returns no results
202
206
  */
203
207
  noResultsPlaceholder?: ReactNode;
208
+ /**
209
+ * An optional set of filters to render in the filters menu
210
+ */
211
+ filters?: Array<DataFilter | DataTableColumn<DataTableRowData> | string>;
204
212
  };
205
213
 
206
214
  const displayColumnDefOptions = {
@@ -344,6 +352,7 @@ const DataTable = ({
344
352
  errorMessage: errorMessageProp,
345
353
  emptyPlaceholder,
346
354
  noResultsPlaceholder,
355
+ filters: filtersProp,
347
356
  }: DataTableProps) => {
348
357
  const [data, setData] = useState<MRT_RowData[]>([]);
349
358
  const [pagination, setPagination] = useState({
@@ -445,21 +454,74 @@ const DataTable = ({
445
454
  ],
446
455
  );
447
456
 
448
- const dataTableFilters = useMemo(
449
- () =>
450
- columns
451
- .filter((column) => column.enableColumnFilter !== false)
452
- .map((column) => {
453
- return {
454
- id: column.accessorKey as string,
457
+ /**
458
+ * This hack is to provide compatibility with Material-React-Table's
459
+ * filterOptions format, which allows for strings and { label: string, value: string }
460
+ */
461
+ const convertFilterSelectOptions = useCallback(
462
+ (options: DataTableColumn<DataTableRowData>["filterSelectOptions"]) =>
463
+ options?.map((option) =>
464
+ typeof option === "string"
465
+ ? {
466
+ label: option,
467
+ value: option,
468
+ }
469
+ : {
470
+ // If the option isn't a string, it must have value and/or option defined
471
+ // If either is undefined, use the other
472
+ label: option.label ?? option.value,
473
+ value: option.value ?? option.label,
474
+ },
475
+ ),
476
+ [],
477
+ );
478
+
479
+ const convertColumnToFilter = useCallback(
480
+ (column: DataTableColumn<DataTableRowData>) =>
481
+ column.enableColumnFilter !== false && column.accessorKey
482
+ ? ({
483
+ id: column.accessorKey,
455
484
  label: column.header,
456
- variant: column.filterVariant ?? "text",
457
- options: column.filterSelectOptions,
458
- } as DataFilter;
459
- }),
460
- [columns],
485
+ variant: column.filterVariant,
486
+ options: convertFilterSelectOptions(column.filterSelectOptions),
487
+ } satisfies DataFilter as DataFilter)
488
+ : null,
489
+ [convertFilterSelectOptions],
461
490
  );
462
491
 
492
+ /**
493
+ * Filters default to the columns, but can be overridden
494
+ * with the `filters` prop. `filters` should be an array
495
+ * of column accessorKeys, column defs, or DataFilters.
496
+ */
497
+ const dataTableFilters = useMemo(() => {
498
+ const providedFilters = filtersProp || columns;
499
+ return providedFilters.reduce<DataFilter[]>((accumulator, item) => {
500
+ if (typeof item === "string") {
501
+ const foundColumn = columns.find(
502
+ (column) => column.accessorKey === item,
503
+ );
504
+ if (foundColumn) {
505
+ const filter = convertColumnToFilter(foundColumn);
506
+ if (filter) {
507
+ return accumulator.concat(filter);
508
+ }
509
+ }
510
+ } else if ("accessorKey" in item) {
511
+ // Checks if it's a column
512
+ const filter = convertColumnToFilter(item);
513
+ if (filter) {
514
+ return accumulator.concat(filter);
515
+ }
516
+ } else if ("label" in item) {
517
+ // Checks if it's a DataFilter
518
+ return accumulator.concat(item);
519
+ }
520
+ // If none of the conditions match, item is ignored (not mapping to undefined)
521
+ return accumulator;
522
+ }, []);
523
+ }, [columns, filtersProp, convertColumnToFilter]);
524
+
463
525
  const defaultCell = useCallback(
464
526
  ({ cell }: { cell: MRT_Cell<MRT_RowData> }) => {
465
527
  const value = cell.getValue<string>();
@@ -59,4 +59,6 @@ const DataTableEmptyState = ({
59
59
  };
60
60
 
61
61
  const MemoizedDataTableEmptyState = memo(DataTableEmptyState);
62
+ MemoizedDataTableEmptyState.displayName = "DataTableEmptyState";
63
+
62
64
  export { MemoizedDataTableEmptyState as DataTableEmptyState };
@@ -286,4 +286,6 @@ const DataTablePagination = ({
286
286
  };
287
287
 
288
288
  const MemoizedDataTablePagination = memo(DataTablePagination);
289
+ MemoizedDataTablePagination.displayName = "DataTablePagination";
290
+
289
291
  export { MemoizedDataTablePagination as DataTablePagination };
@@ -119,4 +119,6 @@ const DataTableRowActions = ({
119
119
  };
120
120
 
121
121
  const MemoizedDataTableRowActions = memo(DataTableRowActions);
122
+ MemoizedDataTableRowActions.displayName = "DataTableRowActions";
123
+
122
124
  export { MemoizedDataTableRowActions as DataTableRowActions };
@@ -132,4 +132,6 @@ const DataTableSettings = ({
132
132
  };
133
133
 
134
134
  const MemoizedDataTableSettings = memo(DataTableSettings);
135
+ MemoizedDataTableSettings.displayName = "DataTableSettings";
136
+
135
137
  export { MemoizedDataTableSettings as DataTableSettings };
@@ -10,21 +10,17 @@
10
10
  * See the License for the specific language governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- import { memo, type ReactElement, useCallback, useMemo, useState } from "react";
14
13
  import {
15
- Divider,
16
- ListSubheader,
17
- Menu as MuiMenu,
18
- PopoverOrigin,
19
- } from "@mui/material";
20
-
21
- import {
22
- Button,
23
- buttonSizeValues,
24
- buttonVariantValues,
25
- MenuItem,
26
- useUniqueId,
27
- } from "./";
14
+ memo,
15
+ type ReactElement,
16
+ useCallback,
17
+ useMemo,
18
+ useState,
19
+ ReactNode,
20
+ } from "react";
21
+ import { Menu as MuiMenu, PopoverOrigin } from "@mui/material";
22
+
23
+ import { Button, buttonSizeValues, buttonVariantValues, useUniqueId } from "./";
28
24
  import { ChevronDownIcon, MoreIcon } from "./icons.generated";
29
25
  import { FieldComponentProps } from "./FieldComponentProps";
30
26
  import { MenuContext, MenuContextType } from "./MenuContext";
@@ -45,13 +41,7 @@ export type MenuButtonProps = {
45
41
  /**
46
42
  * The <MenuItem> components within the Menu.
47
43
  */
48
- children:
49
- | ReactElement<typeof MenuItem | typeof Divider | typeof ListSubheader>
50
- | Array<
51
- | ReactElement<typeof MenuItem | typeof Divider | typeof ListSubheader>
52
- | NullElement
53
- >
54
- | NullElement;
44
+ children: ReactNode | NullElement;
55
45
  /**
56
46
  * The end Icon on the trigggering Button
57
47
  */
@@ -14,9 +14,9 @@ import {
14
14
  RadioGroup as MuiRadioGroup,
15
15
  type RadioGroupProps as MuiRadioGroupProps,
16
16
  } from "@mui/material";
17
- import { memo, ReactElement, useCallback, useRef } from "react";
17
+ import { memo, ReactNode, useCallback, useRef } from "react";
18
18
 
19
- import { Radio, RadioProps } from "./Radio";
19
+ import { RadioProps } from "./Radio";
20
20
  import { Field } from "./Field";
21
21
  import {
22
22
  FieldComponentProps,
@@ -29,7 +29,7 @@ export type RadioGroupProps = {
29
29
  /**
30
30
  * The Radio components within the group. Must include two or more.
31
31
  */
32
- children: Array<ReactElement<typeof Radio>>;
32
+ children: ReactNode;
33
33
  /**
34
34
  * The text value of the Radio that should be selected by default
35
35
  */