@okta/odyssey-react-mui 1.2.0 → 1.4.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 (56) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +1 -1
  3. package/dist/Autocomplete.js +1 -0
  4. package/dist/Autocomplete.js.map +1 -1
  5. package/dist/Breadcrumbs.js +146 -0
  6. package/dist/Breadcrumbs.js.map +1 -0
  7. package/dist/Link.js +3 -1
  8. package/dist/Link.js.map +1 -1
  9. package/dist/MenuButton.js.map +1 -1
  10. package/dist/Tabs.js +8 -6
  11. package/dist/Tabs.js.map +1 -1
  12. package/dist/index.js +1 -0
  13. package/dist/index.js.map +1 -1
  14. package/dist/labs/GroupPicker.js +190 -0
  15. package/dist/labs/GroupPicker.js.map +1 -0
  16. package/dist/labs/index.js +1 -0
  17. package/dist/labs/index.js.map +1 -1
  18. package/dist/properties/ts/odyssey-react-mui.js +2 -0
  19. package/dist/properties/ts/odyssey-react-mui.js.map +1 -1
  20. package/dist/src/Autocomplete.d.ts.map +1 -1
  21. package/dist/src/Breadcrumbs.d.ts +31 -0
  22. package/dist/src/Breadcrumbs.d.ts.map +1 -0
  23. package/dist/src/Link.d.ts +6 -1
  24. package/dist/src/Link.d.ts.map +1 -1
  25. package/dist/src/MenuButton.d.ts +1 -1
  26. package/dist/src/MenuButton.d.ts.map +1 -1
  27. package/dist/src/Tabs.d.ts +7 -2
  28. package/dist/src/Tabs.d.ts.map +1 -1
  29. package/dist/src/i18n.d.ts +2 -0
  30. package/dist/src/i18n.d.ts.map +1 -1
  31. package/dist/src/index.d.ts +1 -0
  32. package/dist/src/index.d.ts.map +1 -1
  33. package/dist/src/labs/GroupPicker.d.ts +25 -0
  34. package/dist/src/labs/GroupPicker.d.ts.map +1 -0
  35. package/dist/src/labs/index.d.ts +1 -0
  36. package/dist/src/labs/index.d.ts.map +1 -1
  37. package/dist/src/properties/ts/odyssey-react-mui.d.ts +2 -0
  38. package/dist/src/properties/ts/odyssey-react-mui.d.ts.map +1 -1
  39. package/dist/src/theme/components.d.ts.map +1 -1
  40. package/dist/theme/components.js +32 -4
  41. package/dist/theme/components.js.map +1 -1
  42. package/dist/tsconfig.production.tsbuildinfo +1 -1
  43. package/package.json +3 -3
  44. package/scripts/properties-to-ts.js +1 -1
  45. package/src/Autocomplete.tsx +3 -0
  46. package/src/Breadcrumbs.tsx +199 -0
  47. package/src/Link.tsx +7 -1
  48. package/src/MenuButton.tsx +2 -3
  49. package/src/Tabs.tsx +24 -12
  50. package/src/index.ts +1 -0
  51. package/src/labs/GroupPicker.tsx +241 -0
  52. package/src/labs/README.md +1 -1
  53. package/src/labs/index.ts +1 -0
  54. package/src/properties/odyssey-react-mui.properties +2 -0
  55. package/src/properties/ts/odyssey-react-mui.ts +1 -1
  56. package/src/theme/components.tsx +35 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@okta/odyssey-react-mui",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "React MUI components for Odyssey, Okta's design system",
5
5
  "author": "Okta, Inc.",
6
6
  "license": "Apache-2.0",
@@ -51,7 +51,7 @@
51
51
  "@mui/system": "^5.14.9",
52
52
  "@mui/utils": "^5.11.2",
53
53
  "@mui/x-date-pickers": "^5.0.15",
54
- "@okta/odyssey-design-tokens": "1.2.0",
54
+ "@okta/odyssey-design-tokens": "1.4.0",
55
55
  "date-fns": "^2.30.0",
56
56
  "i18next": "^23.5.1",
57
57
  "material-react-table": "^1.14.0",
@@ -63,5 +63,5 @@
63
63
  "react": ">=17 <19",
64
64
  "react-dom": ">=17 <19"
65
65
  },
66
- "gitHead": "6dc7afd2fce73ec25b0566b0c59aeedf0738b925"
66
+ "gitHead": "612e08d4ea3bd4c8acb732df7036bce16f4eda0b"
67
67
  }
@@ -11,7 +11,7 @@
11
11
  */
12
12
 
13
13
  // Part of this has been copied over from @okta/ui-build-tools' own internal node script
14
- // https://github.com/okta/ui-build-tools/blob/master/packages/clis/i18n/properties-to-json.js
14
+ // https://github.com/okta/ui-build-tools/blob/main/packages/clis/i18n/properties-to-json.js
15
15
 
16
16
  const { resolve, join, basename, extname } = require("node:path");
17
17
  const {
@@ -223,6 +223,9 @@ const Autocomplete = <
223
223
  );
224
224
  };
225
225
 
226
+ // Need the `typeof Autocomplete` because generics don't get passed through
226
227
  const MemoizedAutocomplete = memo(Autocomplete) as typeof Autocomplete;
228
+ // @ts-expect-error displayName is expected to not be on `typeof Autocomplete`
229
+ MemoizedAutocomplete.displayName = "Autocomplete";
227
230
 
228
231
  export { MemoizedAutocomplete as Autocomplete };
@@ -0,0 +1,199 @@
1
+ /*!
2
+ * Copyright (c) 2023-present, Okta, Inc. and/or its affiliates. All rights reserved.
3
+ * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.")
4
+ *
5
+ * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.
6
+ * Unless required by applicable law or agreed to in writing, software
7
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
8
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9
+ *
10
+ * See the License for the specific language governing permissions and limitations under the License.
11
+ */
12
+
13
+ import {
14
+ ButtonBase,
15
+ Menu,
16
+ MenuItem,
17
+ Breadcrumbs as MuiBreadcrumbs,
18
+ } from "@mui/material";
19
+ import {
20
+ MouseEventHandler,
21
+ ReactElement,
22
+ createContext,
23
+ memo,
24
+ useCallback,
25
+ useContext,
26
+ useMemo,
27
+ useState,
28
+ } from "react";
29
+ import { GroupIcon, HomeIcon, UserIcon } from "./icons.generated";
30
+ import { Typography } from "./Typography";
31
+ import { useTranslation } from "react-i18next";
32
+
33
+ export type BreadcrumbType = "listItem" | "menuItem" | "currentPage";
34
+
35
+ export type BreadcrumbProps = {
36
+ children?: string;
37
+ href: string;
38
+ iconName?: "user" | "group";
39
+ };
40
+
41
+ export type BreadcrumbsProps = {
42
+ children: ReactElement<typeof Breadcrumb>[];
43
+ homeHref?: string;
44
+ maxVisibleItems?: number;
45
+ };
46
+
47
+ export type BreadcrumbContextType = {
48
+ breadcrumbType: BreadcrumbType;
49
+ };
50
+
51
+ export const BreadcrumbContext = createContext<BreadcrumbContextType>({
52
+ breadcrumbType: "listItem",
53
+ });
54
+
55
+ export const Breadcrumb = ({ children, href, iconName }: BreadcrumbProps) => {
56
+ const { breadcrumbType } = useContext(BreadcrumbContext);
57
+
58
+ const breadcrumbContent = (
59
+ <>
60
+ {iconName === "group" ? (
61
+ <GroupIcon />
62
+ ) : iconName === "user" ? (
63
+ <UserIcon />
64
+ ) : null}
65
+ {children}
66
+ </>
67
+ );
68
+
69
+ if (breadcrumbType === "menuItem") {
70
+ return <MenuItem href={href}>{breadcrumbContent}</MenuItem>;
71
+ }
72
+
73
+ if (breadcrumbType === "currentPage") {
74
+ return <Typography>{breadcrumbContent}</Typography>;
75
+ }
76
+
77
+ // breadcrumbType === "listItem" is the default
78
+ // Provided here without a conditional to get TS to be quiet
79
+ // about potential undefined returns
80
+ return <ButtonBase href={href}>{breadcrumbContent}</ButtonBase>;
81
+ };
82
+
83
+ const breadcrumbProviderValue: Record<
84
+ BreadcrumbType,
85
+ { breadcrumbType: BreadcrumbType }
86
+ > = {
87
+ currentPage: {
88
+ breadcrumbType: "currentPage",
89
+ },
90
+ listItem: {
91
+ breadcrumbType: "listItem",
92
+ },
93
+ menuItem: {
94
+ breadcrumbType: "menuItem",
95
+ },
96
+ };
97
+
98
+ const BreadcrumbList = ({
99
+ children,
100
+ homeHref,
101
+ maxVisibleItems: maxVisibleItemsOverride,
102
+ }: BreadcrumbsProps) => {
103
+ const maxVisibleItems = maxVisibleItemsOverride ?? children.length;
104
+
105
+ const { t } = useTranslation();
106
+
107
+ const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
108
+
109
+ const breadcrumbSections = useMemo(() => {
110
+ if (children.length <= maxVisibleItems) {
111
+ return {
112
+ beforeMenu: [],
113
+ insideMenu: [],
114
+ remainingBreadcrumbs: children,
115
+ };
116
+ } else {
117
+ const menuStart = Math.floor(maxVisibleItems / 2);
118
+ const menuLength = children.length - maxVisibleItems;
119
+
120
+ return {
121
+ beforeMenu: children.slice(0, menuStart),
122
+ insideMenu: children.slice(menuStart, menuStart + menuLength),
123
+ remainingBreadcrumbs: children.slice(menuStart + menuLength),
124
+ };
125
+ }
126
+ }, [children, maxVisibleItems]);
127
+
128
+ const onMenuButtonClick = useCallback<MouseEventHandler<HTMLButtonElement>>(
129
+ (event) => setAnchorEl(event.currentTarget),
130
+ []
131
+ );
132
+ const onCloseMenu = useCallback(() => {
133
+ setAnchorEl(null);
134
+ }, []);
135
+
136
+ return (
137
+ <MuiBreadcrumbs
138
+ maxItems={children.length + 1}
139
+ aria-label={t("breadcrumbs.label.text")}
140
+ >
141
+ {homeHref && (
142
+ <ButtonBase href={homeHref} aria-label={t("breadcrumbs.home.text")}>
143
+ <HomeIcon />
144
+ </ButtonBase>
145
+ )}
146
+
147
+ {breadcrumbSections.beforeMenu.map((breadcrumb) => (
148
+ <BreadcrumbContext.Provider value={breadcrumbProviderValue.listItem}>
149
+ {breadcrumb}
150
+ </BreadcrumbContext.Provider>
151
+ ))}
152
+
153
+ {breadcrumbSections.insideMenu && (
154
+ <>
155
+ <ButtonBase onClick={onMenuButtonClick}>…</ButtonBase>
156
+ <Menu
157
+ open={Boolean(anchorEl)}
158
+ onClose={onCloseMenu}
159
+ anchorEl={anchorEl}
160
+ anchorOrigin={{ horizontal: "left", vertical: "bottom" }}
161
+ MenuListProps={{
162
+ sx: {
163
+ minWidth: 180,
164
+ },
165
+ }}
166
+ >
167
+ <BreadcrumbContext.Provider
168
+ value={breadcrumbProviderValue.menuItem}
169
+ >
170
+ {breadcrumbSections.insideMenu}
171
+ </BreadcrumbContext.Provider>
172
+ </Menu>
173
+ </>
174
+ )}
175
+
176
+ {breadcrumbSections.remainingBreadcrumbs.map((breadcrumb, i) => {
177
+ if (i === breadcrumbSections.remainingBreadcrumbs.length - 1) {
178
+ return (
179
+ <BreadcrumbContext.Provider
180
+ value={breadcrumbProviderValue.currentPage}
181
+ >
182
+ {breadcrumb}
183
+ </BreadcrumbContext.Provider>
184
+ );
185
+ }
186
+ return (
187
+ <BreadcrumbContext.Provider value={breadcrumbProviderValue.listItem}>
188
+ {breadcrumb}
189
+ </BreadcrumbContext.Provider>
190
+ );
191
+ })}
192
+ </MuiBreadcrumbs>
193
+ );
194
+ };
195
+
196
+ const MemoizedBreadcrumbList = memo(BreadcrumbList);
197
+ MemoizedBreadcrumbList.displayName = "BreadcrumbList";
198
+
199
+ export { MemoizedBreadcrumbList as BreadcrumbList };
package/src/Link.tsx CHANGED
@@ -14,7 +14,7 @@ import { memo, ReactElement } from "react";
14
14
  import { ExternalLinkIcon } from "./icons.generated";
15
15
  import type { SeleniumProps } from "./SeleniumProps";
16
16
 
17
- import { Link as MuiLink } from "@mui/material";
17
+ import { Link as MuiLink, LinkProps as MuiLinkProps } from "@mui/material";
18
18
 
19
19
  export const linkVariantValues = ["default", "monochrome"] as const;
20
20
 
@@ -31,6 +31,10 @@ export type LinkProps = {
31
31
  * An optional Icon component at the start of the Link
32
32
  */
33
33
  icon?: ReactElement;
34
+ /**
35
+ * The click event handler for the Link
36
+ */
37
+ onClick?: MuiLinkProps["onClick"];
34
38
  /**
35
39
  * The HTML `rel` attribute for the Link
36
40
  */
@@ -58,6 +62,7 @@ const Link = ({
58
62
  target,
59
63
  testId,
60
64
  variant,
65
+ onClick,
61
66
  }: LinkProps) => (
62
67
  <MuiLink
63
68
  data-se={testId}
@@ -65,6 +70,7 @@ const Link = ({
65
70
  rel={rel}
66
71
  target={target}
67
72
  variant={variant}
73
+ onClick={onClick}
68
74
  >
69
75
  {icon && <span className="Link-icon">{icon}</span>}
70
76
 
@@ -50,9 +50,8 @@ export type MenuButtonProps = {
50
50
  * The <MenuItem> components within the Menu.
51
51
  */
52
52
  children: Array<
53
- ReactElement<
54
- typeof MenuItem | typeof Divider | typeof ListSubheader | NullElement
55
- >
53
+ | ReactElement<typeof MenuItem | typeof Divider | typeof ListSubheader>
54
+ | NullElement
56
55
  >;
57
56
  /**
58
57
  * The end Icon on the trigggering Button
package/src/Tabs.tsx CHANGED
@@ -10,7 +10,14 @@
10
10
  * See the License for the specific language governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- import React, {
13
+ import {
14
+ TabContext as MuiTabContext,
15
+ TabList as MuiTabList,
16
+ TabListProps as MuiTabListProps,
17
+ TabPanel as MuiTabPanel,
18
+ } from "@mui/lab";
19
+ import { Tab as MuiTab } from "@mui/material";
20
+ import {
14
21
  ReactElement,
15
22
  ReactNode,
16
23
  memo,
@@ -18,12 +25,6 @@ import React, {
18
25
  useEffect,
19
26
  useState,
20
27
  } from "react";
21
- import { Tab as MuiTab } from "@mui/material";
22
- import {
23
- TabList as MuiTabList,
24
- TabPanel as MuiTabPanel,
25
- TabContext as MuiTabContext,
26
- } from "@mui/lab";
27
28
  import { SeleniumProps } from "./SeleniumProps";
28
29
 
29
30
  export type TabItemProps = {
@@ -67,16 +68,27 @@ export type TabsProps = {
67
68
  * Identifier for the selected tab.
68
69
  */
69
70
  value?: string;
71
+ /**
72
+ * Callback fired when the active tab is changed.
73
+ */
74
+ onChange?: MuiTabListProps["onChange"];
70
75
  };
71
76
 
72
- const Tabs = ({ ariaLabel, initialValue, tabs, value }: TabsProps) => {
77
+ const Tabs = ({
78
+ ariaLabel,
79
+ initialValue,
80
+ tabs,
81
+ value,
82
+ onChange: onChangeProp,
83
+ }: TabsProps) => {
73
84
  const [tabState, setTabState] = useState(initialValue ?? value ?? "0");
74
85
 
75
- const onChange = useCallback(
76
- (_event: React.SyntheticEvent, newState: string) => {
77
- setTabState(newState);
86
+ const onChange = useCallback<NonNullable<MuiTabListProps["onChange"]>>(
87
+ (event, value: string) => {
88
+ setTabState(value);
89
+ onChangeProp?.(event, value);
78
90
  },
79
- []
91
+ [onChangeProp]
80
92
  );
81
93
 
82
94
  useEffect(() => {
package/src/index.ts CHANGED
@@ -59,6 +59,7 @@ export { useOdysseyDesignTokens } from "./OdysseyDesignTokensContext";
59
59
  export * from "./Autocomplete";
60
60
  export * from "./Banner";
61
61
  export * from "./Box";
62
+ export * from "./Breadcrumbs";
62
63
  export * from "./Button";
63
64
  export * from "./Callout";
64
65
  export * from "./Checkbox";
@@ -0,0 +1,241 @@
1
+ /*!
2
+ * Copyright (c) 2023-present, Okta, Inc. and/or its affiliates. All rights reserved.
3
+ * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.")
4
+ *
5
+ * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.
6
+ * Unless required by applicable law or agreed to in writing, software
7
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
8
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9
+ *
10
+ * See the License for the specific language governing permissions and limitations under the License.
11
+ */
12
+
13
+ import type { AutocompleteGetTagProps } from "@mui/material/useAutocomplete";
14
+
15
+ import {
16
+ Autocomplete as MuiAutocomplete,
17
+ Avatar as MuiAvatar,
18
+ Box,
19
+ InputBase,
20
+ } from "@mui/material";
21
+ import { avatarClasses } from "@mui/material/Avatar";
22
+ import { memo, useCallback } from "react";
23
+
24
+ import { AutocompleteProps } from "../Autocomplete";
25
+ import { Field } from "../Field";
26
+ import { Subordinate } from "../Typography";
27
+ import { Tag } from "../Tag";
28
+ import { useOdysseyDesignTokens } from "../OdysseyDesignTokensContext";
29
+ import { UserIcon, GridIcon, GroupIcon } from "../icons.generated";
30
+
31
+ export type GroupPickerOptionType = {
32
+ appsCount?: number;
33
+ description: string;
34
+ groupPushMappingsCount?: number;
35
+ id: string;
36
+ logo?: string;
37
+ name: string;
38
+ usersCount?: number;
39
+ };
40
+
41
+ export type GroupPickerProps<
42
+ GroupPickerOptionType,
43
+ HasMultipleChoices extends boolean | undefined,
44
+ IsCustomValueAllowed extends boolean | undefined
45
+ > = AutocompleteProps<
46
+ GroupPickerOptionType,
47
+ HasMultipleChoices,
48
+ IsCustomValueAllowed
49
+ >;
50
+
51
+ const avatarImageSizeSmall = 16;
52
+ const avatarImageSizeMedium = 24;
53
+
54
+ const GroupPicker = <
55
+ OptionType extends GroupPickerOptionType,
56
+ HasMultipleChoices extends boolean | undefined,
57
+ IsCustomValueAllowed extends boolean | undefined
58
+ >({
59
+ hasMultipleChoices,
60
+ isCustomValueAllowed,
61
+ isDisabled,
62
+ isLoading,
63
+ isOptional = false,
64
+ isReadOnly,
65
+ hint,
66
+ label,
67
+ onChange,
68
+ onInputChange,
69
+ options,
70
+ value,
71
+ testId,
72
+ }: GroupPickerProps<OptionType, HasMultipleChoices, IsCustomValueAllowed>) => {
73
+ const odysseyDesignTokens = useOdysseyDesignTokens();
74
+
75
+ const isOptionEqualToValue = useCallback((sourceValue, targetValue) => {
76
+ return sourceValue.id === targetValue.id;
77
+ }, []);
78
+
79
+ const getOptionLabel = useCallback((option) => {
80
+ return option.name;
81
+ }, []);
82
+
83
+ const renderOption = useCallback(
84
+ (props, option) => {
85
+ return (
86
+ <li {...props} key={option.id}>
87
+ <Box
88
+ sx={{
89
+ alignItems: "top",
90
+ display: "flex",
91
+ flexDirection: "row",
92
+ }}
93
+ >
94
+ <Box sx={{ paddingRight: odysseyDesignTokens.Spacing2 }}>
95
+ <MuiAvatar
96
+ alt={option.name}
97
+ src={option.logo}
98
+ sx={{
99
+ [`.${avatarClasses.fallback}`]: {
100
+ visibility: "hidden",
101
+ },
102
+ background: "transparent",
103
+ height: avatarImageSizeMedium,
104
+ width: avatarImageSizeMedium,
105
+ }}
106
+ />
107
+ </Box>
108
+ <Box>
109
+ {option.name}
110
+ <Subordinate>{option.description}</Subordinate>
111
+ <Box
112
+ sx={{
113
+ display: "flex",
114
+ flexDirection: "row",
115
+ paddingTop: odysseyDesignTokens.Spacing1,
116
+ }}
117
+ >
118
+ {typeof option.usersCount === "number" && (
119
+ <Box
120
+ sx={{
121
+ display: "flex",
122
+ flexDirection: "row",
123
+ paddingRight: odysseyDesignTokens.Spacing4,
124
+ }}
125
+ >
126
+ <UserIcon />
127
+ {option.usersCount}
128
+ </Box>
129
+ )}
130
+ {typeof option.appsCount === "number" && (
131
+ <Box
132
+ sx={{
133
+ display: "flex",
134
+ flexDirection: "row",
135
+ paddingRight: odysseyDesignTokens.Spacing4,
136
+ }}
137
+ >
138
+ <GridIcon />
139
+ {option.appsCount}
140
+ </Box>
141
+ )}
142
+ {typeof option.groupPushMappingsCount === "number" && (
143
+ <Box sx={{ display: "flex", flexDirection: "row" }}>
144
+ <GroupIcon />
145
+ {option.groupPushMappingsCount}
146
+ </Box>
147
+ )}
148
+ </Box>
149
+ </Box>
150
+ </Box>
151
+ </li>
152
+ );
153
+ },
154
+ [odysseyDesignTokens]
155
+ );
156
+
157
+ const renderTags = useCallback(
158
+ (values: OptionType[], getTagProps: AutocompleteGetTagProps) =>
159
+ values.map((option, index) => {
160
+ const { key, onDelete } = getTagProps({ index });
161
+ return (
162
+ <Box
163
+ key={key}
164
+ sx={{
165
+ margin: odysseyDesignTokens.Spacing1,
166
+ }}
167
+ >
168
+ <Tag
169
+ icon={
170
+ <MuiAvatar
171
+ alt={option.name}
172
+ src={option.logo}
173
+ sx={{
174
+ [`.${avatarClasses.fallback}`]: {
175
+ visibility: "hidden",
176
+ },
177
+ background: "transparent",
178
+ height: avatarImageSizeSmall,
179
+ width: avatarImageSizeSmall,
180
+ }}
181
+ />
182
+ }
183
+ label={option.name}
184
+ onRemove={onDelete}
185
+ />
186
+ </Box>
187
+ );
188
+ }),
189
+ [odysseyDesignTokens]
190
+ );
191
+
192
+ const renderInput = useCallback(
193
+ ({ InputLabelProps, InputProps, ...params }) => (
194
+ <Field
195
+ fieldType="single"
196
+ hasVisibleLabel
197
+ id={InputLabelProps.htmlFor}
198
+ hint={hint}
199
+ label={label}
200
+ isOptional={isOptional}
201
+ renderFieldComponent={({ ariaDescribedBy, id }) => (
202
+ <InputBase
203
+ {...params}
204
+ {...InputProps}
205
+ aria-describedby={ariaDescribedBy}
206
+ id={id}
207
+ required={!isOptional}
208
+ />
209
+ )}
210
+ />
211
+ ),
212
+ [hint, isOptional, label]
213
+ );
214
+
215
+ return (
216
+ <MuiAutocomplete
217
+ // AutoComplete is wrapped in a div within MUI which does not get the disabled attr. So this aria-disabled gets set in the div
218
+ aria-disabled={isDisabled}
219
+ data-se={testId}
220
+ disabled={isDisabled}
221
+ filterSelectedOptions={true}
222
+ freeSolo={isCustomValueAllowed}
223
+ getOptionLabel={getOptionLabel}
224
+ isOptionEqualToValue={isOptionEqualToValue}
225
+ loading={isLoading}
226
+ multiple={hasMultipleChoices}
227
+ onChange={onChange}
228
+ onInputChange={onInputChange}
229
+ options={options}
230
+ readOnly={isReadOnly}
231
+ renderInput={renderInput}
232
+ renderOption={renderOption}
233
+ renderTags={renderTags}
234
+ value={value}
235
+ />
236
+ );
237
+ };
238
+
239
+ const MemoizedGroupPicker = memo(GroupPicker) as typeof GroupPicker;
240
+
241
+ export { MemoizedGroupPicker as GroupPicker };
@@ -37,7 +37,7 @@ bundled, and polyfilled for advanced use cases and browser support targets.
37
37
 
38
38
  ## License
39
39
 
40
- [Apache Version 2.0](https://github.com/okta/odyssey/blob/master/LICENSE)
40
+ [Apache Version 2.0](https://github.com/okta/odyssey/blob/main/LICENSE)
41
41
 
42
42
  ## Support Disclaimer
43
43
 
package/src/labs/index.ts CHANGED
@@ -22,3 +22,4 @@ export * from "./datePickerTheme";
22
22
  export * from "./materialReactTableTypes";
23
23
  export * from "./PaginatedTable";
24
24
  export * from "./StaticTable";
25
+ export * from "./GroupPicker";
@@ -1,3 +1,5 @@
1
+ breadcrumbs.home.text = Home
2
+ breadcrumbs.label.text = Breadcrumbs
1
3
  fielderror.screenreader.text = Error
2
4
  fieldlabel.optional.text = Optional
3
5
  fieldlabel.required.text = Required
@@ -1 +1 @@
1
- export const translation = {"fielderror.screenreader.text":"Error","fieldlabel.optional.text":"Optional","fieldlabel.required.text":"Required","passwordfield.icon.label.show":"Show password","passwordfield.icon.label.hide":"Hide password","severity.error":"error","severity.info":"info","severity.success":"success","severity.warning":"warning","table.error":"Error loading data.","table.fetchedrows.text":"Fetched {{totalRows}} row","table.fetchedrows.text_plural":"Fetched {{totalRows}} total rows","table.rows.text":"{{totalRows}} row","table.rows.text_plural":"{{totalRows}} rows","toast.close.text":"close"};
1
+ export const translation = {"breadcrumbs.home.text":"Home","breadcrumbs.label.text":"Breadcrumbs","fielderror.screenreader.text":"Error","fieldlabel.optional.text":"Optional","fieldlabel.required.text":"Required","passwordfield.icon.label.show":"Show password","passwordfield.icon.label.hide":"Hide password","severity.error":"error","severity.info":"info","severity.success":"success","severity.warning":"warning","table.error":"Error loading data.","table.fetchedrows.text":"Fetched {{totalRows}} row","table.fetchedrows.text_plural":"Fetched {{totalRows}} total rows","table.rows.text":"{{totalRows}} row","table.rows.text_plural":"{{totalRows}} rows","toast.close.text":"close"};
@@ -333,6 +333,40 @@ export const components = ({
333
333
  }),
334
334
  },
335
335
  },
336
+ MuiBreadcrumbs: {
337
+ styleOverrides: {
338
+ li: {
339
+ fontSize: odysseyTokens.TypographySizeBody,
340
+ lineHeight: odysseyTokens.TypographyLineHeightUi,
341
+
342
+ "& > a, & > button": {
343
+ borderRadius: odysseyTokens.BorderRadiusTight,
344
+ color: odysseyTokens.TypographyColorSubordinate,
345
+ display: "flex",
346
+ gap: odysseyTokens.Spacing1,
347
+ padding: odysseyTokens.Spacing1,
348
+ transitionProperty: "color, background-color",
349
+ transitionDuration: "100ms",
350
+ transitionTimingFunction: "linear",
351
+
352
+ "&:hover": {
353
+ backgroundColor: odysseyTokens.HueNeutral200,
354
+ color: odysseyTokens.TypographyColorBody,
355
+ },
356
+
357
+ "&:focus-visible": {
358
+ boxShadow: `0 0 0 2px ${odysseyTokens.HueNeutralWhite}, 0 0 0 4px ${odysseyTokens.PalettePrimaryMain}`,
359
+ outline: "2px solid transparent",
360
+ outlineOffset: "1px",
361
+ },
362
+ },
363
+ },
364
+ separator: {
365
+ color: odysseyTokens.BorderColorDisplay,
366
+ marginInline: odysseyTokens.Spacing1,
367
+ },
368
+ },
369
+ },
336
370
  MuiButton: {
337
371
  defaultProps: {
338
372
  variant: "primary",
@@ -688,10 +722,7 @@ export const components = ({
688
722
  height: ".64em",
689
723
  marginInlineEnd: odysseyTokens.Spacing2,
690
724
  borderRadius: "100%",
691
- backgroundColor: "transparent",
692
- borderColor: odysseyTokens.TypographyColorBody,
693
- borderWidth: odysseyTokens.BorderWidthHeavy,
694
- borderStyle: odysseyTokens.BorderStyleMain,
725
+ backgroundColor: odysseyTokens.HueNeutral600,
695
726
  },
696
727
 
697
728
  [`&.${chipClasses.colorError}`]: {