@okta/odyssey-react-mui 1.1.1 → 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 (142) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +1 -1
  3. package/dist/Autocomplete.js +13 -1
  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/OdysseyCacheProvider.js +4 -1
  11. package/dist/OdysseyCacheProvider.js.map +1 -1
  12. package/dist/OdysseyProvider.js +5 -1
  13. package/dist/OdysseyProvider.js.map +1 -1
  14. package/dist/OdysseyThemeProvider.js +5 -1
  15. package/dist/OdysseyThemeProvider.js.map +1 -1
  16. package/dist/OdysseyTranslationProvider.js +1 -1
  17. package/dist/OdysseyTranslationProvider.js.map +1 -1
  18. package/dist/PasswordField.js +11 -3
  19. package/dist/PasswordField.js.map +1 -1
  20. package/dist/Select.js +34 -33
  21. package/dist/Select.js.map +1 -1
  22. package/dist/Tabs.js +8 -6
  23. package/dist/Tabs.js.map +1 -1
  24. package/dist/Typography.js +0 -22
  25. package/dist/Typography.js.map +1 -1
  26. package/dist/createShadowDom.js +26 -0
  27. package/dist/createShadowDom.js.map +1 -0
  28. package/dist/{OdysseyI18n.js → i18n.js} +3 -2
  29. package/dist/i18n.js.map +1 -0
  30. package/dist/index.js +2 -0
  31. package/dist/index.js.map +1 -1
  32. package/dist/labs/GroupPicker.js +190 -0
  33. package/dist/labs/GroupPicker.js.map +1 -0
  34. package/dist/labs/datePickerTheme.js +4 -2
  35. package/dist/labs/datePickerTheme.js.map +1 -1
  36. package/dist/labs/index.js +1 -0
  37. package/dist/labs/index.js.map +1 -1
  38. package/dist/properties/ts/odyssey-react-mui.js +4 -0
  39. package/dist/properties/ts/odyssey-react-mui.js.map +1 -1
  40. package/dist/src/Autocomplete.d.ts +23 -3
  41. package/dist/src/Autocomplete.d.ts.map +1 -1
  42. package/dist/src/Breadcrumbs.d.ts +31 -0
  43. package/dist/src/Breadcrumbs.d.ts.map +1 -0
  44. package/dist/src/Link.d.ts +6 -1
  45. package/dist/src/Link.d.ts.map +1 -1
  46. package/dist/src/MenuButton.d.ts +1 -1
  47. package/dist/src/MenuButton.d.ts.map +1 -1
  48. package/dist/src/OdysseyCacheProvider.d.ts +6 -1
  49. package/dist/src/OdysseyCacheProvider.d.ts.map +1 -1
  50. package/dist/src/OdysseyProvider.d.ts +1 -1
  51. package/dist/src/OdysseyProvider.d.ts.map +1 -1
  52. package/dist/src/OdysseyThemeProvider.d.ts +2 -1
  53. package/dist/src/OdysseyThemeProvider.d.ts.map +1 -1
  54. package/dist/src/OdysseyTranslationProvider.d.ts +1 -1
  55. package/dist/src/OdysseyTranslationProvider.d.ts.map +1 -1
  56. package/dist/src/PasswordField.d.ts +8 -0
  57. package/dist/src/PasswordField.d.ts.map +1 -1
  58. package/dist/src/Select.d.ts +1 -54
  59. package/dist/src/Select.d.ts.map +1 -1
  60. package/dist/src/Tabs.d.ts +7 -2
  61. package/dist/src/Tabs.d.ts.map +1 -1
  62. package/dist/src/Typography.d.ts +11 -15
  63. package/dist/src/Typography.d.ts.map +1 -1
  64. package/dist/src/createShadowDom.d.ts +16 -0
  65. package/dist/src/createShadowDom.d.ts.map +1 -0
  66. package/dist/src/{OdysseyI18n.d.ts → i18n.d.ts} +7 -2
  67. package/dist/src/i18n.d.ts.map +1 -0
  68. package/dist/src/index.d.ts +2 -0
  69. package/dist/src/index.d.ts.map +1 -1
  70. package/dist/src/labs/GroupPicker.d.ts +25 -0
  71. package/dist/src/labs/GroupPicker.d.ts.map +1 -0
  72. package/dist/src/labs/index.d.ts +1 -0
  73. package/dist/src/labs/index.d.ts.map +1 -1
  74. package/dist/src/properties/ts/odyssey-react-mui.d.ts +4 -0
  75. package/dist/src/properties/ts/odyssey-react-mui.d.ts.map +1 -1
  76. package/dist/src/theme/components.d.ts +4 -1
  77. package/dist/src/theme/components.d.ts.map +1 -1
  78. package/dist/src/theme/createOdysseyMuiTheme.d.ts +23 -0
  79. package/dist/src/theme/createOdysseyMuiTheme.d.ts.map +1 -0
  80. package/dist/src/theme/mixins.d.ts +3 -1
  81. package/dist/src/theme/mixins.d.ts.map +1 -1
  82. package/dist/src/theme/palette.d.ts +3 -1
  83. package/dist/src/theme/palette.d.ts.map +1 -1
  84. package/dist/src/theme/shape.d.ts +3 -1
  85. package/dist/src/theme/shape.d.ts.map +1 -1
  86. package/dist/src/theme/spacing.d.ts +3 -1
  87. package/dist/src/theme/spacing.d.ts.map +1 -1
  88. package/dist/src/theme/theme.d.ts +1 -8
  89. package/dist/src/theme/theme.d.ts.map +1 -1
  90. package/dist/src/theme/typography.d.ts +3 -1
  91. package/dist/src/theme/typography.d.ts.map +1 -1
  92. package/dist/theme/components.js +112 -67
  93. package/dist/theme/components.js.map +1 -1
  94. package/dist/theme/createOdysseyMuiTheme.js +51 -0
  95. package/dist/theme/createOdysseyMuiTheme.js.map +1 -0
  96. package/dist/theme/mixins.js +4 -1
  97. package/dist/theme/mixins.js.map +1 -1
  98. package/dist/theme/palette.js +4 -1
  99. package/dist/theme/palette.js.map +1 -1
  100. package/dist/theme/shape.js +4 -1
  101. package/dist/theme/shape.js.map +1 -1
  102. package/dist/theme/spacing.js +4 -1
  103. package/dist/theme/spacing.js.map +1 -1
  104. package/dist/theme/theme.js +1 -20
  105. package/dist/theme/theme.js.map +1 -1
  106. package/dist/theme/typography.js +4 -1
  107. package/dist/theme/typography.js.map +1 -1
  108. package/dist/tsconfig.production.tsbuildinfo +1 -1
  109. package/package.json +4 -4
  110. package/scripts/properties-to-ts.js +1 -1
  111. package/src/Autocomplete.tsx +47 -3
  112. package/src/Breadcrumbs.tsx +199 -0
  113. package/src/Link.tsx +7 -1
  114. package/src/MenuButton.tsx +2 -3
  115. package/src/OdysseyCacheProvider.tsx +9 -1
  116. package/src/OdysseyProvider.tsx +9 -2
  117. package/src/OdysseyThemeProvider.tsx +8 -2
  118. package/src/OdysseyTranslationProvider.test.tsx +2 -2
  119. package/src/OdysseyTranslationProvider.tsx +1 -1
  120. package/src/PasswordField.tsx +24 -8
  121. package/src/Select.tsx +147 -152
  122. package/src/Tabs.tsx +24 -12
  123. package/src/Typography.tsx +0 -26
  124. package/src/createShadowDom.ts +46 -0
  125. package/src/{OdysseyI18n.ts → i18n.ts} +2 -2
  126. package/src/index.ts +2 -0
  127. package/src/labs/GroupPicker.tsx +241 -0
  128. package/src/labs/README.md +1 -1
  129. package/src/labs/datePickerTheme.tsx +2 -2
  130. package/src/labs/index.ts +1 -0
  131. package/src/properties/odyssey-react-mui.properties +4 -0
  132. package/src/properties/ts/odyssey-react-mui.ts +1 -1
  133. package/src/theme/components.tsx +61 -13
  134. package/src/theme/createOdysseyMuiTheme.ts +47 -0
  135. package/src/theme/mixins.ts +5 -1
  136. package/src/theme/palette.ts +5 -3
  137. package/src/theme/shape.ts +5 -1
  138. package/src/theme/spacing.ts +5 -3
  139. package/src/theme/theme.ts +1 -26
  140. package/src/theme/typography.ts +5 -3
  141. package/dist/OdysseyI18n.js.map +0 -1
  142. package/dist/src/OdysseyI18n.d.ts.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@okta/odyssey-react-mui",
3
- "version": "1.1.1",
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,9 +51,9 @@
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.1.1",
54
+ "@okta/odyssey-design-tokens": "1.4.0",
55
55
  "date-fns": "^2.30.0",
56
- "i18next": "^22.4.15",
56
+ "i18next": "^23.5.1",
57
57
  "material-react-table": "^1.14.0",
58
58
  "react-i18next": "^12.2.2",
59
59
  "ts-node": "^10.9.1",
@@ -63,5 +63,5 @@
63
63
  "react": ">=17 <19",
64
64
  "react-dom": ">=17 <19"
65
65
  },
66
- "gitHead": "3cf42aaed1fa433ee7a0e4b10112f98544f28f56"
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 {
@@ -25,6 +25,10 @@ export type AutocompleteProps<
25
25
  HasMultipleChoices extends boolean | undefined,
26
26
  IsCustomValueAllowed extends boolean | undefined
27
27
  > = {
28
+ /**
29
+ * The error message for the Select
30
+ */
31
+ errorMessage?: string;
28
32
  /**
29
33
  * Enables multiple choice selection
30
34
  */
@@ -38,6 +42,10 @@ export type AutocompleteProps<
38
42
  * The hint text for the Autocomplete input
39
43
  */
40
44
  hint?: string;
45
+ /**
46
+ * The id attribute of the Select
47
+ */
48
+ id?: string;
41
49
  /**
42
50
  * Allows the input of custom values
43
51
  */
@@ -83,7 +91,20 @@ export type AutocompleteProps<
83
91
  */
84
92
  label: string;
85
93
  /**
86
- * Callback fired when the value of the autocomplete input changes
94
+ * The name of the `input` element. Defaults to the `id` if not set.
95
+ */
96
+ name?: string;
97
+ /**
98
+ * Callback fired when the autocomplete loses focus.
99
+ */
100
+ onBlur?: MuiAutocompleteProps<
101
+ OptionType,
102
+ HasMultipleChoices,
103
+ undefined,
104
+ IsCustomValueAllowed
105
+ >["onBlur"];
106
+ /**
107
+ * Callback fired when a selection is made.
87
108
  */
88
109
  onChange?: MuiAutocompleteProps<
89
110
  OptionType,
@@ -92,7 +113,7 @@ export type AutocompleteProps<
92
113
  IsCustomValueAllowed
93
114
  >["onChange"];
94
115
  /**
95
- * Callback fired when the input value of the autocomplete input changes
116
+ * Callback fired when the textbox receives typed characters.
96
117
  */
97
118
  onInputChange?: MuiAutocompleteProps<
98
119
  OptionType,
@@ -100,6 +121,15 @@ export type AutocompleteProps<
100
121
  undefined,
101
122
  IsCustomValueAllowed
102
123
  >["onInputChange"];
124
+ /**
125
+ * Callback fired when the autocomplete gains focus.
126
+ */
127
+ onFocus?: MuiAutocompleteProps<
128
+ OptionType,
129
+ HasMultipleChoices,
130
+ undefined,
131
+ IsCustomValueAllowed
132
+ >["onFocus"];
103
133
  /**
104
134
  * The options for the Autocomplete input
105
135
  */
@@ -125,7 +155,9 @@ const Autocomplete = <
125
155
  HasMultipleChoices extends boolean | undefined,
126
156
  IsCustomValueAllowed extends boolean | undefined
127
157
  >({
158
+ errorMessage,
128
159
  hasMultipleChoices,
160
+ id: idOverride,
129
161
  isCustomValueAllowed,
130
162
  isDisabled,
131
163
  isLoading,
@@ -133,8 +165,11 @@ const Autocomplete = <
133
165
  isReadOnly,
134
166
  hint,
135
167
  label,
168
+ name: nameOverride,
169
+ onBlur,
136
170
  onChange,
137
171
  onInputChange,
172
+ onFocus,
138
173
  options,
139
174
  value,
140
175
  testId,
@@ -142,6 +177,7 @@ const Autocomplete = <
142
177
  const renderInput = useCallback(
143
178
  ({ InputLabelProps, InputProps, ...params }) => (
144
179
  <Field
180
+ errorMessage={errorMessage}
145
181
  fieldType="single"
146
182
  hasVisibleLabel
147
183
  id={InputLabelProps.htmlFor}
@@ -154,12 +190,13 @@ const Autocomplete = <
154
190
  {...InputProps}
155
191
  aria-describedby={ariaDescribedBy}
156
192
  id={id}
193
+ name={nameOverride ?? id}
157
194
  required={!isOptional}
158
195
  />
159
196
  )}
160
197
  />
161
198
  ),
162
- [hint, isOptional, label]
199
+ [errorMessage, hint, isOptional, label, nameOverride]
163
200
  );
164
201
 
165
202
  return (
@@ -170,10 +207,14 @@ const Autocomplete = <
170
207
  disableCloseOnSelect={hasMultipleChoices}
171
208
  disabled={isDisabled}
172
209
  freeSolo={isCustomValueAllowed}
210
+ filterSelectedOptions={true}
211
+ id={idOverride}
173
212
  loading={isLoading}
174
213
  multiple={hasMultipleChoices}
214
+ onBlur={onBlur}
175
215
  onChange={onChange}
176
216
  onInputChange={onInputChange}
217
+ onFocus={onFocus}
177
218
  options={options}
178
219
  readOnly={isReadOnly}
179
220
  renderInput={renderInput}
@@ -182,6 +223,9 @@ const Autocomplete = <
182
223
  );
183
224
  };
184
225
 
226
+ // Need the `typeof Autocomplete` because generics don't get passed through
185
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";
186
230
 
187
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
@@ -24,12 +24,18 @@ import { useUniqueAlphabeticalId } from "./useUniqueAlphabeticalId";
24
24
 
25
25
  export type OdysseyCacheProviderProps = {
26
26
  children: ReactNode;
27
+ /**
28
+ * Emotion renders into this HTML element.
29
+ * When enabling this prop, Emotion renders at the top of this component rather than the bottom like it does in the HTML `<head>`.
30
+ */
31
+ emotionRootElement?: HTMLStyleElement;
27
32
  nonce?: string;
28
33
  stylisPlugins?: StylisPlugin[];
29
34
  };
30
35
 
31
36
  const OdysseyCacheProvider = ({
32
37
  children,
38
+ emotionRootElement,
33
39
  nonce,
34
40
  stylisPlugins,
35
41
  }: OdysseyCacheProviderProps) => {
@@ -38,11 +44,13 @@ const OdysseyCacheProvider = ({
38
44
  const emotionCache = useMemo(
39
45
  () =>
40
46
  createCache({
47
+ container: emotionRootElement,
41
48
  key: uniqueAlphabeticalId,
42
49
  nonce: nonce || window.cspNonce,
50
+ prepend: Boolean(emotionRootElement),
43
51
  stylisPlugins,
44
52
  }),
45
- [nonce, stylisPlugins, uniqueAlphabeticalId]
53
+ [emotionRootElement, nonce, stylisPlugins, uniqueAlphabeticalId]
46
54
  );
47
55
 
48
56
  return <CacheProvider value={emotionCache}>{children}</CacheProvider>;
@@ -35,16 +35,23 @@ export type OdysseyProviderProps = OdysseyCacheProviderProps &
35
35
  const OdysseyProvider = ({
36
36
  children,
37
37
  designTokensOverride,
38
+ emotionRootElement,
39
+ shadowRootElement,
38
40
  languageCode,
39
41
  nonce,
40
42
  stylisPlugins,
41
43
  themeOverride,
42
44
  translationOverrides,
43
45
  }: OdysseyProviderProps) => (
44
- <OdysseyCacheProvider nonce={nonce} stylisPlugins={stylisPlugins}>
46
+ <OdysseyCacheProvider
47
+ emotionRootElement={emotionRootElement}
48
+ nonce={nonce}
49
+ stylisPlugins={stylisPlugins}
50
+ >
45
51
  <OdysseyThemeProvider
46
- themeOverride={themeOverride}
47
52
  designTokensOverride={designTokensOverride}
53
+ shadowRootElement={shadowRootElement}
54
+ themeOverride={themeOverride}
48
55
  >
49
56
  <ScopedCssBaseline>
50
57
  <OdysseyTranslationProvider
@@ -25,12 +25,14 @@ import { OdysseyDesignTokensContext } from "./OdysseyDesignTokensContext";
25
25
  export type OdysseyThemeProviderProps = {
26
26
  children: ReactNode;
27
27
  designTokensOverride?: DesignTokensOverride;
28
+ shadowRootElement?: HTMLDivElement;
28
29
  themeOverride?: ThemeOptions;
29
30
  };
30
31
 
31
32
  const OdysseyThemeProvider = ({
32
33
  children,
33
34
  designTokensOverride,
35
+ shadowRootElement,
34
36
  themeOverride,
35
37
  }: OdysseyThemeProviderProps) => {
36
38
  const odysseyTokens = useMemo(
@@ -38,8 +40,12 @@ const OdysseyThemeProvider = ({
38
40
  [designTokensOverride]
39
41
  );
40
42
  const odysseyTheme = useMemo(
41
- () => createOdysseyMuiTheme(odysseyTokens),
42
- [odysseyTokens]
43
+ () =>
44
+ createOdysseyMuiTheme({
45
+ odysseyTokens,
46
+ shadowRootElement,
47
+ }),
48
+ [odysseyTokens, shadowRootElement]
43
49
  );
44
50
 
45
51
  const customOdysseyTheme = useMemo(
@@ -12,14 +12,14 @@
12
12
 
13
13
  import { render, screen } from "@testing-library/react";
14
14
  import { OdysseyTranslationProvider } from "./OdysseyTranslationProvider";
15
- import i18n from "./OdysseyI18n";
15
+ import { odysseyTranslate } from "./i18n";
16
16
  import { TextField } from "./TextField";
17
17
 
18
18
  describe("OdysseyTranslationProvider", () => {
19
19
  it("defaults to 'en' translation bundle", () => {
20
20
  render(
21
21
  <OdysseyTranslationProvider>
22
- <span>{i18n.t("fieldlabel.optional.text")}</span>
22
+ <span>{odysseyTranslate("fieldlabel.optional.text")}</span>
23
23
  </OdysseyTranslationProvider>
24
24
  );
25
25
 
@@ -14,7 +14,7 @@ import { ReactNode, useEffect } from "react";
14
14
 
15
15
  import { SupportedLanguages } from "./OdysseyTranslationProvider.types";
16
16
 
17
- import i18n, { defaultNS, resources } from "./OdysseyI18n";
17
+ import { i18n, defaultNS, resources } from "./i18n";
18
18
  import { I18nextProvider } from "react-i18next";
19
19
 
20
20
  export type TranslationOverrides = {
@@ -23,6 +23,7 @@ import {
23
23
  import { ShowIcon, HideIcon } from "./icons.generated";
24
24
  import { Field } from "./Field";
25
25
  import type { SeleniumProps } from "./SeleniumProps";
26
+ import { useTranslation } from "react-i18next";
26
27
 
27
28
  export type PasswordFieldProps = {
28
29
  /**
@@ -39,6 +40,10 @@ export type PasswordFieldProps = {
39
40
  * If `true`, the component will receive focus automatically.
40
41
  */
41
42
  hasInitialFocus?: boolean;
43
+ /**
44
+ * If `true`, the show/hide icon is not shown to the user
45
+ */
46
+ hasShowPassword?: boolean;
42
47
  /**
43
48
  * The helper text content.
44
49
  */
@@ -99,6 +104,7 @@ const PasswordField = forwardRef<HTMLInputElement, PasswordFieldProps>(
99
104
  id: idOverride,
100
105
  isDisabled = false,
101
106
  isOptional = false,
107
+ hasShowPassword = true,
102
108
  isReadOnly,
103
109
  label,
104
110
  name: nameOverride,
@@ -111,6 +117,7 @@ const PasswordField = forwardRef<HTMLInputElement, PasswordFieldProps>(
111
117
  },
112
118
  ref
113
119
  ) => {
120
+ const { t } = useTranslation();
114
121
  const [inputType, setInputType] = useState("password");
115
122
 
116
123
  const togglePasswordVisibility = useCallback(() => {
@@ -128,16 +135,23 @@ const PasswordField = forwardRef<HTMLInputElement, PasswordFieldProps>(
128
135
  autoFocus={hasInitialFocus}
129
136
  data-se={testId}
130
137
  endAdornment={
131
- <InputAdornment position="end">
132
- <IconButton
133
- aria-label="toggle password visibility"
134
- onClick={togglePasswordVisibility}
135
- >
136
- {inputType === "password" ? <ShowIcon /> : <HideIcon />}
137
- </IconButton>
138
- </InputAdornment>
138
+ hasShowPassword && (
139
+ <InputAdornment position="end">
140
+ <IconButton
141
+ aria-label={
142
+ inputType === "password"
143
+ ? t("passwordfield.icon.label.show")
144
+ : t("passwordfield.icon.label.hide")
145
+ }
146
+ onClick={togglePasswordVisibility}
147
+ >
148
+ {inputType === "password" ? <ShowIcon /> : <HideIcon />}
149
+ </IconButton>
150
+ </InputAdornment>
151
+ )
139
152
  }
140
153
  id={id}
154
+ inputProps={{ role: "textbox" }}
141
155
  name={nameOverride ?? id}
142
156
  onChange={onChange}
143
157
  onFocus={onFocus}
@@ -153,6 +167,7 @@ const PasswordField = forwardRef<HTMLInputElement, PasswordFieldProps>(
153
167
  [
154
168
  autoCompleteType,
155
169
  hasInitialFocus,
170
+ t,
156
171
  togglePasswordVisibility,
157
172
  inputType,
158
173
  nameOverride,
@@ -162,6 +177,7 @@ const PasswordField = forwardRef<HTMLInputElement, PasswordFieldProps>(
162
177
  placeholder,
163
178
  isOptional,
164
179
  isReadOnly,
180
+ hasShowPassword,
165
181
  ref,
166
182
  testId,
167
183
  value,