@okta/odyssey-react-mui 1.0.2 → 1.2.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 (274) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/README.md +1 -1
  3. package/dist/Autocomplete.js +20 -3
  4. package/dist/Autocomplete.js.map +1 -1
  5. package/dist/Banner.js +3 -1
  6. package/dist/Banner.js.map +1 -1
  7. package/dist/Box.js +8 -4
  8. package/dist/Box.js.map +1 -1
  9. package/dist/Button.js +3 -1
  10. package/dist/Button.js.map +1 -1
  11. package/dist/Callout.js +2 -0
  12. package/dist/Callout.js.map +1 -1
  13. package/dist/Checkbox.js +6 -2
  14. package/dist/Checkbox.js.map +1 -1
  15. package/dist/CheckboxGroup.js +5 -7
  16. package/dist/CheckboxGroup.js.map +1 -1
  17. package/dist/CircularProgress.js +2 -0
  18. package/dist/CircularProgress.js.map +1 -1
  19. package/dist/Dialog.js +2 -0
  20. package/dist/Dialog.js.map +1 -1
  21. package/dist/Field.js.map +1 -1
  22. package/dist/FieldError.js +2 -0
  23. package/dist/FieldError.js.map +1 -1
  24. package/dist/FieldHint.js +2 -0
  25. package/dist/FieldHint.js.map +1 -1
  26. package/dist/FieldLabel.js +3 -1
  27. package/dist/FieldLabel.js.map +1 -1
  28. package/dist/Fieldset.js +3 -1
  29. package/dist/Fieldset.js.map +1 -1
  30. package/dist/Form.js +5 -3
  31. package/dist/Form.js.map +1 -1
  32. package/dist/Link.js +3 -1
  33. package/dist/Link.js.map +1 -1
  34. package/dist/MenuButton.js +8 -2
  35. package/dist/MenuButton.js.map +1 -1
  36. package/dist/MenuItem.js +6 -2
  37. package/dist/MenuItem.js.map +1 -1
  38. package/dist/NativeSelect.js +3 -1
  39. package/dist/NativeSelect.js.map +1 -1
  40. package/dist/OdysseyCacheProvider.js +4 -1
  41. package/dist/OdysseyCacheProvider.js.map +1 -1
  42. package/dist/OdysseyProvider.js +12 -5
  43. package/dist/OdysseyProvider.js.map +1 -1
  44. package/dist/OdysseyThemeProvider.js +8 -7
  45. package/dist/OdysseyThemeProvider.js.map +1 -1
  46. package/dist/OdysseyTranslationProvider.js +1 -1
  47. package/dist/OdysseyTranslationProvider.js.map +1 -1
  48. package/dist/OdysseyTranslationProvider.types.js +1 -1
  49. package/dist/OdysseyTranslationProvider.types.js.map +1 -1
  50. package/dist/PasswordField.js +18 -9
  51. package/dist/PasswordField.js.map +1 -1
  52. package/dist/Radio.js +2 -0
  53. package/dist/Radio.js.map +1 -1
  54. package/dist/RadioGroup.js +5 -2
  55. package/dist/RadioGroup.js.map +1 -1
  56. package/dist/SearchField.js +11 -10
  57. package/dist/SearchField.js.map +1 -1
  58. package/dist/Select.js +39 -35
  59. package/dist/Select.js.map +1 -1
  60. package/dist/SeleniumProps.js +2 -0
  61. package/dist/SeleniumProps.js.map +1 -0
  62. package/dist/Status.js +4 -2
  63. package/dist/Status.js.map +1 -1
  64. package/dist/Tabs.js +11 -4
  65. package/dist/Tabs.js.map +1 -1
  66. package/dist/Tag.js +4 -2
  67. package/dist/Tag.js.map +1 -1
  68. package/dist/TagList.js +3 -1
  69. package/dist/TagList.js.map +1 -1
  70. package/dist/TextField.js +6 -2
  71. package/dist/TextField.js.map +1 -1
  72. package/dist/Toast.js +2 -0
  73. package/dist/Toast.js.map +1 -1
  74. package/dist/Tooltip.js +2 -0
  75. package/dist/Tooltip.js.map +1 -1
  76. package/dist/Typography.js +71 -59
  77. package/dist/Typography.js.map +1 -1
  78. package/dist/createShadowDom.js +26 -0
  79. package/dist/createShadowDom.js.map +1 -0
  80. package/dist/{OdysseyI18n.js → i18n.js} +5 -2
  81. package/dist/i18n.js.map +1 -0
  82. package/dist/index.js +1 -0
  83. package/dist/index.js.map +1 -1
  84. package/dist/labs/DatePicker.js +4 -2
  85. package/dist/labs/DatePicker.js.map +1 -1
  86. package/dist/labs/PaginatedTable.js +6 -4
  87. package/dist/labs/PaginatedTable.js.map +1 -1
  88. package/dist/labs/StaticTable.js +9 -4
  89. package/dist/labs/StaticTable.js.map +1 -1
  90. package/dist/labs/datePickerTheme.js +4 -2
  91. package/dist/labs/datePickerTheme.js.map +1 -1
  92. package/dist/properties/ts/odyssey-react-mui.js +2 -0
  93. package/dist/properties/ts/odyssey-react-mui.js.map +1 -1
  94. package/dist/src/Autocomplete.d.ts +29 -4
  95. package/dist/src/Autocomplete.d.ts.map +1 -1
  96. package/dist/src/Banner.d.ts +3 -2
  97. package/dist/src/Banner.d.ts.map +1 -1
  98. package/dist/src/Box.d.ts +9 -2
  99. package/dist/src/Box.d.ts.map +1 -1
  100. package/dist/src/Button.d.ts +3 -2
  101. package/dist/src/Button.d.ts.map +1 -1
  102. package/dist/src/Callout.d.ts +3 -2
  103. package/dist/src/Callout.d.ts.map +1 -1
  104. package/dist/src/Checkbox.d.ts +8 -3
  105. package/dist/src/Checkbox.d.ts.map +1 -1
  106. package/dist/src/CheckboxGroup.d.ts +3 -6
  107. package/dist/src/CheckboxGroup.d.ts.map +1 -1
  108. package/dist/src/CircularProgress.d.ts +3 -2
  109. package/dist/src/CircularProgress.d.ts.map +1 -1
  110. package/dist/src/Dialog.d.ts +3 -2
  111. package/dist/src/Dialog.d.ts.map +1 -1
  112. package/dist/src/Field.d.ts +2 -1
  113. package/dist/src/Field.d.ts.map +1 -1
  114. package/dist/src/FieldError.d.ts +3 -2
  115. package/dist/src/FieldError.d.ts.map +1 -1
  116. package/dist/src/FieldHint.d.ts +3 -2
  117. package/dist/src/FieldHint.d.ts.map +1 -1
  118. package/dist/src/FieldLabel.d.ts +3 -2
  119. package/dist/src/FieldLabel.d.ts.map +1 -1
  120. package/dist/src/Fieldset.d.ts +3 -2
  121. package/dist/src/Fieldset.d.ts.map +1 -1
  122. package/dist/src/Form.d.ts +3 -2
  123. package/dist/src/Form.d.ts.map +1 -1
  124. package/dist/src/Link.d.ts +3 -2
  125. package/dist/src/Link.d.ts.map +1 -1
  126. package/dist/src/MenuButton.d.ts +12 -3
  127. package/dist/src/MenuButton.d.ts.map +1 -1
  128. package/dist/src/MenuItem.d.ts +5 -4
  129. package/dist/src/MenuItem.d.ts.map +1 -1
  130. package/dist/src/NativeSelect.d.ts +56 -2
  131. package/dist/src/NativeSelect.d.ts.map +1 -1
  132. package/dist/src/OdysseyCacheProvider.d.ts +6 -1
  133. package/dist/src/OdysseyCacheProvider.d.ts.map +1 -1
  134. package/dist/src/OdysseyProvider.d.ts +1 -1
  135. package/dist/src/OdysseyProvider.d.ts.map +1 -1
  136. package/dist/src/OdysseyThemeProvider.d.ts +2 -1
  137. package/dist/src/OdysseyThemeProvider.d.ts.map +1 -1
  138. package/dist/src/OdysseyTranslationProvider.d.ts +1 -1
  139. package/dist/src/OdysseyTranslationProvider.d.ts.map +1 -1
  140. package/dist/src/OdysseyTranslationProvider.types.d.ts +1 -1
  141. package/dist/src/OdysseyTranslationProvider.types.d.ts.map +1 -1
  142. package/dist/src/PasswordField.d.ts +78 -2
  143. package/dist/src/PasswordField.d.ts.map +1 -1
  144. package/dist/src/Radio.d.ts +3 -2
  145. package/dist/src/Radio.d.ts.map +1 -1
  146. package/dist/src/RadioGroup.d.ts +8 -3
  147. package/dist/src/RadioGroup.d.ts.map +1 -1
  148. package/dist/src/SearchField.d.ts +58 -2
  149. package/dist/src/SearchField.d.ts.map +1 -1
  150. package/dist/src/Select.d.ts +7 -2
  151. package/dist/src/Select.d.ts.map +1 -1
  152. package/dist/src/SeleniumProps.d.ts +20 -0
  153. package/dist/src/SeleniumProps.d.ts.map +1 -0
  154. package/dist/src/Status.d.ts +3 -2
  155. package/dist/src/Status.d.ts.map +1 -1
  156. package/dist/src/Tabs.d.ts +9 -3
  157. package/dist/src/Tabs.d.ts.map +1 -1
  158. package/dist/src/Tag.d.ts +3 -2
  159. package/dist/src/Tag.d.ts.map +1 -1
  160. package/dist/src/TagList.d.ts +3 -2
  161. package/dist/src/TagList.d.ts.map +1 -1
  162. package/dist/src/TextField.d.ts +86 -2
  163. package/dist/src/TextField.d.ts.map +1 -1
  164. package/dist/src/Toast.d.ts +3 -2
  165. package/dist/src/Toast.d.ts.map +1 -1
  166. package/dist/src/Tooltip.d.ts +3 -2
  167. package/dist/src/Tooltip.d.ts.map +1 -1
  168. package/dist/src/Typography.d.ts +14 -49
  169. package/dist/src/Typography.d.ts.map +1 -1
  170. package/dist/src/createShadowDom.d.ts +16 -0
  171. package/dist/src/createShadowDom.d.ts.map +1 -0
  172. package/dist/src/{OdysseyI18n.d.ts → i18n.d.ts} +20 -2
  173. package/dist/src/i18n.d.ts.map +1 -0
  174. package/dist/src/index.d.ts +1 -0
  175. package/dist/src/index.d.ts.map +1 -1
  176. package/dist/src/labs/DatePicker.d.ts +5 -1
  177. package/dist/src/labs/DatePicker.d.ts.map +1 -1
  178. package/dist/src/labs/PaginatedTable.d.ts.map +1 -1
  179. package/dist/src/labs/StaticTable.d.ts.map +1 -1
  180. package/dist/src/properties/ts/odyssey-react-mui.d.ts +2 -0
  181. package/dist/src/properties/ts/odyssey-react-mui.d.ts.map +1 -1
  182. package/dist/src/theme/components.d.ts +4 -1
  183. package/dist/src/theme/components.d.ts.map +1 -1
  184. package/dist/src/theme/createOdysseyMuiTheme.d.ts +23 -0
  185. package/dist/src/theme/createOdysseyMuiTheme.d.ts.map +1 -0
  186. package/dist/src/theme/mixins.d.ts +3 -1
  187. package/dist/src/theme/mixins.d.ts.map +1 -1
  188. package/dist/src/theme/palette.d.ts +3 -1
  189. package/dist/src/theme/palette.d.ts.map +1 -1
  190. package/dist/src/theme/shape.d.ts +3 -1
  191. package/dist/src/theme/shape.d.ts.map +1 -1
  192. package/dist/src/theme/spacing.d.ts +3 -1
  193. package/dist/src/theme/spacing.d.ts.map +1 -1
  194. package/dist/src/theme/theme.d.ts +1 -8
  195. package/dist/src/theme/theme.d.ts.map +1 -1
  196. package/dist/src/theme/typography.d.ts +3 -1
  197. package/dist/src/theme/typography.d.ts.map +1 -1
  198. package/dist/theme/components.js +118 -73
  199. package/dist/theme/components.js.map +1 -1
  200. package/dist/theme/createOdysseyMuiTheme.js +51 -0
  201. package/dist/theme/createOdysseyMuiTheme.js.map +1 -0
  202. package/dist/theme/mixins.js +4 -1
  203. package/dist/theme/mixins.js.map +1 -1
  204. package/dist/theme/palette.js +4 -1
  205. package/dist/theme/palette.js.map +1 -1
  206. package/dist/theme/shape.js +4 -1
  207. package/dist/theme/shape.js.map +1 -1
  208. package/dist/theme/spacing.js +4 -1
  209. package/dist/theme/spacing.js.map +1 -1
  210. package/dist/theme/theme.js +1 -20
  211. package/dist/theme/theme.js.map +1 -1
  212. package/dist/theme/typography.js +4 -1
  213. package/dist/theme/typography.js.map +1 -1
  214. package/dist/tsconfig.production.tsbuildinfo +1 -1
  215. package/package.json +7 -6
  216. package/src/Autocomplete.tsx +56 -4
  217. package/src/Banner.tsx +11 -2
  218. package/src/Box.tsx +11 -5
  219. package/src/Button.tsx +6 -1
  220. package/src/Callout.tsx +5 -3
  221. package/src/Checkbox.tsx +14 -4
  222. package/src/CheckboxGroup.tsx +6 -10
  223. package/src/CircularProgress.tsx +5 -1
  224. package/src/Dialog.tsx +5 -2
  225. package/src/Field.tsx +2 -0
  226. package/src/FieldError.tsx +5 -3
  227. package/src/FieldHint.tsx +9 -3
  228. package/src/FieldLabel.tsx +5 -3
  229. package/src/Fieldset.tsx +4 -1
  230. package/src/Form.tsx +7 -4
  231. package/src/Link.tsx +18 -3
  232. package/src/MenuButton.tsx +33 -4
  233. package/src/MenuItem.tsx +11 -6
  234. package/src/NativeSelect.tsx +7 -2
  235. package/src/OdysseyCacheProvider.tsx +9 -1
  236. package/src/OdysseyProvider.tsx +18 -8
  237. package/src/OdysseyThemeProvider.tsx +12 -8
  238. package/src/OdysseyTranslationProvider.test.tsx +2 -2
  239. package/src/OdysseyTranslationProvider.tsx +1 -1
  240. package/src/OdysseyTranslationProvider.types.ts +1 -0
  241. package/src/PasswordField.tsx +37 -13
  242. package/src/Radio.tsx +5 -1
  243. package/src/RadioGroup.tsx +12 -4
  244. package/src/SearchField.tsx +23 -15
  245. package/src/Select.tsx +154 -149
  246. package/src/SeleniumProps.ts +20 -0
  247. package/src/Status.tsx +15 -3
  248. package/src/Tabs.tsx +18 -4
  249. package/src/Tag.tsx +12 -3
  250. package/src/TagList.tsx +4 -2
  251. package/src/TextField.tsx +14 -2
  252. package/src/Toast.tsx +4 -1
  253. package/src/Tooltip.tsx +4 -1
  254. package/src/Typography.tsx +76 -54
  255. package/src/createShadowDom.ts +46 -0
  256. package/src/{OdysseyI18n.ts → i18n.ts} +4 -2
  257. package/src/index.ts +1 -0
  258. package/src/labs/DatePicker.tsx +15 -7
  259. package/src/labs/PaginatedTable.tsx +12 -3
  260. package/src/labs/README.md +2 -2
  261. package/src/labs/StaticTable.tsx +13 -3
  262. package/src/labs/datePickerTheme.tsx +2 -2
  263. package/src/properties/odyssey-react-mui.properties +2 -0
  264. package/src/properties/ts/odyssey-react-mui.ts +1 -1
  265. package/src/theme/components.tsx +69 -18
  266. package/src/theme/createOdysseyMuiTheme.ts +47 -0
  267. package/src/theme/mixins.ts +5 -1
  268. package/src/theme/palette.ts +5 -3
  269. package/src/theme/shape.ts +5 -1
  270. package/src/theme/spacing.ts +5 -3
  271. package/src/theme/theme.ts +1 -26
  272. package/src/theme/typography.ts +5 -3
  273. package/dist/OdysseyI18n.js.map +0 -1
  274. package/dist/src/OdysseyI18n.d.ts.map +0 -1
@@ -11,7 +11,7 @@
11
11
  */
12
12
 
13
13
  import { useState, useEffect } from "react";
14
- import { InputAdornment, InputBase } from "@mui/material";
14
+ import { InputAdornment, InputBase, IconButton } from "@mui/material";
15
15
  import {
16
16
  ChangeEventHandler,
17
17
  FocusEventHandler,
@@ -23,7 +23,7 @@ import {
23
23
 
24
24
  import { CloseCircleFilledIcon, SearchIcon } from "./icons.generated";
25
25
  import { Field } from "./Field";
26
- import { Button } from "./Button";
26
+ import type { SeleniumProps } from "./SeleniumProps";
27
27
 
28
28
  export type SearchFieldProps = {
29
29
  /**
@@ -48,6 +48,10 @@ export type SearchFieldProps = {
48
48
  * This label won't show up visually, but it's required for accessibility.
49
49
  */
50
50
  label: string;
51
+ /**
52
+ * The name of the `input` element. Defaults to the `id` if not set.
53
+ */
54
+ name?: string;
51
55
  /**
52
56
  * Callback fired when the `input` element loses focus.
53
57
  */
@@ -72,7 +76,7 @@ export type SearchFieldProps = {
72
76
  * The value of the `input` element, required for a controlled component.
73
77
  */
74
78
  value?: string;
75
- };
79
+ } & SeleniumProps;
76
80
 
77
81
  const SearchField = forwardRef<HTMLInputElement, SearchFieldProps>(
78
82
  (
@@ -82,11 +86,13 @@ const SearchField = forwardRef<HTMLInputElement, SearchFieldProps>(
82
86
  id: idOverride,
83
87
  isDisabled = false,
84
88
  label,
89
+ name: nameOverride,
85
90
  onChange: onChangeProp,
86
91
  onFocus,
87
92
  onBlur,
88
93
  onClear: onClearProp,
89
94
  placeholder,
95
+ testId,
90
96
  value: controlledValue,
91
97
  },
92
98
  ref
@@ -120,26 +126,26 @@ const SearchField = forwardRef<HTMLInputElement, SearchFieldProps>(
120
126
  autoComplete={autoCompleteType}
121
127
  /* eslint-disable-next-line jsx-a11y/no-autofocus */
122
128
  autoFocus={hasInitialFocus}
129
+ data-se={testId}
123
130
  endAdornment={
124
131
  uncontrolledValue && (
125
132
  <InputAdornment position="end">
126
- <Button
127
- ariaLabel="Clear"
128
- isDisabled={isDisabled}
129
- label=""
133
+ <IconButton
134
+ aria-label="Clear"
135
+ disabled={isDisabled}
130
136
  onClick={onClear}
131
137
  size="small"
132
- startIcon={<CloseCircleFilledIcon />}
133
- variant="floating"
134
- />
138
+ >
139
+ <CloseCircleFilledIcon />
140
+ </IconButton>
135
141
  </InputAdornment>
136
142
  )
137
143
  }
138
144
  id={id}
139
- name={id}
145
+ name={nameOverride ?? id}
146
+ onBlur={onBlur}
140
147
  onChange={onChange}
141
148
  onFocus={onFocus}
142
- onBlur={onBlur}
143
149
  placeholder={placeholder}
144
150
  ref={ref}
145
151
  startAdornment={
@@ -155,15 +161,17 @@ const SearchField = forwardRef<HTMLInputElement, SearchFieldProps>(
155
161
  ),
156
162
  [
157
163
  autoCompleteType,
164
+ controlledValue,
158
165
  hasInitialFocus,
159
166
  isDisabled,
160
- onClear,
167
+ nameOverride,
168
+ onBlur,
161
169
  onChange,
170
+ onClear,
162
171
  onFocus,
163
- onBlur,
164
172
  placeholder,
165
173
  ref,
166
- controlledValue,
174
+ testId,
167
175
  uncontrolledValue,
168
176
  ]
169
177
  );
package/src/Select.tsx CHANGED
@@ -10,18 +10,19 @@
10
10
  * See the License for the specific language governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- import { ReactNode, forwardRef, memo, useCallback, useState } from "react";
13
+ import { ReactNode, memo, useCallback, useMemo, useState } from "react";
14
14
  import {
15
15
  Box,
16
16
  Chip,
17
+ Checkbox as MuiCheckbox,
17
18
  ListSubheader,
18
19
  MenuItem,
19
20
  Select as MuiSelect,
20
21
  SelectChangeEvent,
21
22
  } from "@mui/material";
22
23
  import { SelectProps as MuiSelectProps } from "@mui/material";
23
- import { Checkbox } from "./Checkbox";
24
24
  import { Field } from "./Field";
25
+ import type { SeleniumProps } from "./SeleniumProps";
25
26
 
26
27
  export type SelectOption = {
27
28
  text: string;
@@ -58,6 +59,10 @@ export type SelectProps = {
58
59
  * The label text for the Select
59
60
  */
60
61
  label: string;
62
+ /**
63
+ * The name of the `input` element. Defaults to the `id` if not set.
64
+ */
65
+ name?: string;
61
66
  /**
62
67
  * Callback fired when the Select loses focus
63
68
  */
@@ -78,7 +83,7 @@ export type SelectProps = {
78
83
  * The value or values selected in the Select
79
84
  */
80
85
  value?: string | string[];
81
- };
86
+ } & SeleniumProps;
82
87
 
83
88
  /**
84
89
  * Options in Odyssey <Select> are passed as an array, which can contain any combination
@@ -95,168 +100,168 @@ export type SelectProps = {
95
100
  * - { text: string, type: "heading" } — Used to display a group heading with the text
96
101
  */
97
102
 
98
- const Select = forwardRef<HTMLSelectElement, SelectProps>(
99
- (
100
- {
101
- errorMessage,
102
- hint,
103
- id: idOverride,
104
- isDisabled = false,
105
- isMultiSelect = false,
106
- isOptional = false,
107
- label,
108
- onBlur,
109
- onChange: onChangeProp,
110
- onFocus,
111
- value,
112
- options,
113
- },
114
- ref
115
- ) => {
116
- // If there's no value set, we set it to a blank string (if it's a single-select)
117
- // or an empty array (if it's a multi-select)
118
- if (typeof value === "undefined") {
119
- value = isMultiSelect ? [] : "";
120
- }
121
-
122
- const [selectedValue, setSelectedValue] = useState<string | string[]>(
123
- value
124
- );
125
-
126
- const onChange = useCallback(
127
- (event: SelectChangeEvent<string | string[]>, child: ReactNode) => {
128
- const {
129
- target: { value },
130
- } = event;
103
+ const Select = ({
104
+ errorMessage,
105
+ hint,
106
+ id: idOverride,
107
+ isDisabled = false,
108
+ isMultiSelect = false,
109
+ isOptional = false,
110
+ label,
111
+ name: nameOverride,
112
+ onBlur,
113
+ onChange: onChangeProp,
114
+ onFocus,
115
+ value,
116
+ testId,
117
+ options,
118
+ }: SelectProps) => {
119
+ // If there's no value set, we set it to a blank string (if it's a single-select)
120
+ // or an empty array (if it's a multi-select)
121
+ if (typeof value === "undefined") {
122
+ value = isMultiSelect ? [] : "";
123
+ }
131
124
 
132
- // Set the field value, with some additional logic to handle array values
133
- // for multi-selects
134
- if (isMultiSelect) {
135
- setSelectedValue(
136
- typeof value === "string" ? value.split(",") : value
137
- );
138
- } else {
139
- setSelectedValue(value);
140
- }
125
+ const [selectedValue, setSelectedValue] = useState<string | string[]>(value);
141
126
 
142
- // Trigger the onChange event, if one has been passed
143
- if (onChangeProp) {
144
- onChangeProp(event, child);
145
- }
146
- },
147
- [isMultiSelect, onChangeProp, setSelectedValue]
148
- );
127
+ const onChange = useCallback(
128
+ (event: SelectChangeEvent<string | string[]>, child: ReactNode) => {
129
+ const {
130
+ target: { value },
131
+ } = event;
149
132
 
150
- // Normalize the options array to accommodate the various
151
- // data types that might be passed
152
- const normalizedOptions = options.map((option) => {
153
- if (typeof option === "object") {
154
- return {
155
- text: option.text,
156
- value: option.value || option.text,
157
- type: option.type === "heading" ? "heading" : "option",
158
- };
133
+ // Set the field value, with some additional logic to handle array values
134
+ // for multi-selects
135
+ if (isMultiSelect) {
136
+ setSelectedValue(typeof value === "string" ? value.split(",") : value);
137
+ } else {
138
+ setSelectedValue(value);
159
139
  }
160
140
 
161
- return { text: option, value: option, type: "option" };
162
- });
163
-
164
- const renderValue = useCallback(
165
- (selected: string | string[]) => {
166
- // If the selected value isn't an array, then we don't need to display
167
- // chips and should fall back to the default render behavior
168
- if (typeof selected === "string") {
169
- return undefined;
170
- }
171
-
172
- // Convert the selected options array into <Chip>s
173
- const renderedChips = selected
174
- .map((item: string) => {
175
- const selectedOption = normalizedOptions.find(
176
- (option) => option.value === item
177
- );
141
+ // Trigger the onChange event, if one has been passed
142
+ if (onChangeProp) {
143
+ onChangeProp(event, child);
144
+ }
145
+ },
146
+ [isMultiSelect, onChangeProp, setSelectedValue]
147
+ );
178
148
 
179
- if (!selectedOption) {
180
- return null;
149
+ // Normalize the options array to accommodate the various
150
+ // data types that might be passed
151
+ const normalizedOptions = useMemo(
152
+ () =>
153
+ options.map((option) =>
154
+ typeof option === "object"
155
+ ? {
156
+ text: option.text,
157
+ value: option.value || option.text,
158
+ type: option.type === "heading" ? "heading" : "option",
181
159
  }
160
+ : { text: option, value: option, type: "option" }
161
+ ),
162
+ [options]
163
+ );
182
164
 
183
- return <Chip key={item} label={selectedOption.text} />;
184
- })
185
- .filter(Boolean);
165
+ const renderValue = useCallback(
166
+ (selected: string | string[]) => {
167
+ // If the selected value isn't an array, then we don't need to display
168
+ // chips and should fall back to the default render behavior
169
+ if (typeof selected === "string") {
170
+ return undefined;
171
+ }
186
172
 
187
- if (renderedChips.length === 0) {
188
- return null;
189
- }
173
+ // Convert the selected options array into <Chip>s
174
+ const renderedChips = selected
175
+ .map((item: string) => {
176
+ const selectedOption = normalizedOptions.find(
177
+ (option) => option.value === item
178
+ );
179
+
180
+ if (!selectedOption) {
181
+ return null;
182
+ }
190
183
 
191
- // We need the <Box> to surround the <Chip>s for
192
- // proper styling
193
- return <Box>{renderedChips}</Box>;
194
- },
195
- [normalizedOptions]
196
- );
184
+ return <Chip key={item} label={selectedOption.text} />;
185
+ })
186
+ .filter(Boolean);
197
187
 
198
- // Convert the options into the ReactNode children
199
- // that will populate the <Select>
200
- const children = normalizedOptions.map((option) => {
201
- if (option.type === "heading") {
202
- return <ListSubheader key={option.text}>{option.text}</ListSubheader>;
188
+ if (renderedChips.length === 0) {
189
+ return null;
203
190
  }
204
191
 
205
- return (
206
- <MenuItem key={option.value} value={option.value}>
207
- {isMultiSelect && (
208
- <Checkbox isDefaultChecked={selectedValue.includes(option.value)} />
209
- )}
210
- {option.text}
211
- </MenuItem>
212
- );
213
- });
192
+ // We need the <Box> to surround the <Chip>s for
193
+ // proper styling
194
+ return <Box>{renderedChips}</Box>;
195
+ },
196
+ [normalizedOptions]
197
+ );
214
198
 
215
- const renderFieldComponent = useCallback(
216
- () => (
217
- <MuiSelect
218
- children={children}
219
- id={idOverride}
220
- multiple={isMultiSelect}
221
- name={idOverride}
222
- onBlur={onBlur}
223
- onChange={onChange}
224
- onFocus={onFocus}
225
- ref={ref}
226
- renderValue={isMultiSelect ? renderValue : undefined}
227
- value={selectedValue}
228
- labelId={label}
229
- />
230
- ),
231
- [
232
- idOverride,
233
- isMultiSelect,
234
- onBlur,
235
- onChange,
236
- onFocus,
237
- ref,
238
- children,
239
- renderValue,
240
- selectedValue,
241
- label,
242
- ]
243
- );
199
+ // Convert the options into the ReactNode children
200
+ // that will populate the <Select>
201
+ const children = useMemo(
202
+ () =>
203
+ normalizedOptions.map((option) => {
204
+ if (option.type === "heading") {
205
+ return <ListSubheader key={option.text}>{option.text}</ListSubheader>;
206
+ }
207
+
208
+ return (
209
+ <MenuItem key={option.value} value={option.value}>
210
+ {isMultiSelect && (
211
+ <MuiCheckbox checked={selectedValue.includes(option.value)} />
212
+ )}
213
+ {option.text}
214
+ </MenuItem>
215
+ );
216
+ }),
217
+ [isMultiSelect, normalizedOptions, selectedValue]
218
+ );
244
219
 
245
- return (
246
- <Field
247
- errorMessage={errorMessage}
248
- fieldType="single"
249
- hasVisibleLabel
250
- hint={hint}
251
- id={idOverride}
252
- isDisabled={isDisabled}
253
- isOptional={isOptional}
254
- label={label}
255
- renderFieldComponent={renderFieldComponent}
220
+ const renderFieldComponent = useCallback(
221
+ ({ ariaDescribedBy, id }) => (
222
+ <MuiSelect
223
+ aria-describedby={ariaDescribedBy}
224
+ children={children}
225
+ data-se={testId}
226
+ id={id}
227
+ labelId={label}
228
+ multiple={isMultiSelect}
229
+ name={nameOverride ?? id}
230
+ onBlur={onBlur}
231
+ onChange={onChange}
232
+ onFocus={onFocus}
233
+ renderValue={isMultiSelect ? renderValue : undefined}
234
+ value={selectedValue}
256
235
  />
257
- );
258
- }
259
- );
236
+ ),
237
+ [
238
+ children,
239
+ isMultiSelect,
240
+ label,
241
+ nameOverride,
242
+ onBlur,
243
+ onChange,
244
+ onFocus,
245
+ renderValue,
246
+ selectedValue,
247
+ testId,
248
+ ]
249
+ );
250
+
251
+ return (
252
+ <Field
253
+ errorMessage={errorMessage}
254
+ fieldType="single"
255
+ hasVisibleLabel
256
+ hint={hint}
257
+ id={idOverride}
258
+ isDisabled={isDisabled}
259
+ isOptional={isOptional}
260
+ label={label}
261
+ renderFieldComponent={renderFieldComponent}
262
+ />
263
+ );
264
+ };
260
265
 
261
266
  const MemoizedSelect = memo(Select);
262
267
  MemoizedSelect.displayName = "Select";
@@ -0,0 +1,20 @@
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
+ export type SeleniumProps = {
14
+ /**
15
+ * This prop puts a `data` attribute on an HTML element in this component with the value provided.
16
+ *
17
+ * @deprecated **WARNING:** You should be using Semantic Selectors instead of this property. This is a temporary measure for backwards compatibility with existing Selenium tests.
18
+ */
19
+ testId?: string;
20
+ };
package/src/Status.tsx CHANGED
@@ -13,6 +13,7 @@
13
13
  import { Chip } from "@mui/material";
14
14
 
15
15
  import { useMuiProps } from "./MuiPropsContext";
16
+ import type { SeleniumProps } from "./SeleniumProps";
16
17
 
17
18
  export const statusSeverityValues = [
18
19
  "default",
@@ -35,12 +36,23 @@ export type StatusProps = {
35
36
  * The style of the Status indicator
36
37
  */
37
38
  variant?: (typeof statusVariantValues)[number];
38
- };
39
+ } & SeleniumProps;
39
40
 
40
- export const Status = ({ severity, label, variant = "lamp" }: StatusProps) => {
41
+ export const Status = ({
42
+ label,
43
+ severity,
44
+ testId,
45
+ variant = "lamp",
46
+ }: StatusProps) => {
41
47
  const muiProps = useMuiProps();
42
48
 
43
49
  return (
44
- <Chip {...muiProps} label={label} color={severity} variant={variant} />
50
+ <Chip
51
+ {...muiProps}
52
+ color={severity}
53
+ data-se={testId}
54
+ label={label}
55
+ variant={variant}
56
+ />
45
57
  );
46
58
  };
package/src/Tabs.tsx CHANGED
@@ -15,6 +15,7 @@ import React, {
15
15
  ReactNode,
16
16
  memo,
17
17
  useCallback,
18
+ useEffect,
18
19
  useState,
19
20
  } from "react";
20
21
  import { Tab as MuiTab } from "@mui/material";
@@ -23,6 +24,7 @@ import {
23
24
  TabPanel as MuiTabPanel,
24
25
  TabContext as MuiTabContext,
25
26
  } from "@mui/lab";
27
+ import { SeleniumProps } from "./SeleniumProps";
26
28
 
27
29
  export type TabItemProps = {
28
30
  /**
@@ -45,7 +47,7 @@ export type TabItemProps = {
45
47
  * The value associated with the TabItem
46
48
  */
47
49
  value?: string;
48
- };
50
+ } & SeleniumProps;
49
51
 
50
52
  export type TabsProps = {
51
53
  /**
@@ -53,17 +55,22 @@ export type TabsProps = {
53
55
  */
54
56
  ariaLabel?: string;
55
57
  /**
56
- * The value of the Tab that should be selected by default
58
+ * @deprecated please use the `value` prop instead
59
+ * When `value` is provided, `initialValue` isn't used.
57
60
  */
58
61
  initialValue?: string;
59
62
  /**
60
63
  * The TabItems to be included in the Tabs group
61
64
  */
62
65
  tabs: TabItemProps[];
66
+ /**
67
+ * Identifier for the selected tab.
68
+ */
69
+ value?: string;
63
70
  };
64
71
 
65
- const Tabs = ({ ariaLabel, initialValue = "0", tabs }: TabsProps) => {
66
- const [tabState, setTabState] = useState(initialValue);
72
+ const Tabs = ({ ariaLabel, initialValue, tabs, value }: TabsProps) => {
73
+ const [tabState, setTabState] = useState(initialValue ?? value ?? "0");
67
74
 
68
75
  const onChange = useCallback(
69
76
  (_event: React.SyntheticEvent, newState: string) => {
@@ -72,11 +79,18 @@ const Tabs = ({ ariaLabel, initialValue = "0", tabs }: TabsProps) => {
72
79
  []
73
80
  );
74
81
 
82
+ useEffect(() => {
83
+ if (value !== undefined) {
84
+ setTabState(value);
85
+ }
86
+ }, [value]);
87
+
75
88
  return (
76
89
  <MuiTabContext value={tabState}>
77
90
  <MuiTabList onChange={onChange} aria-label={ariaLabel}>
78
91
  {tabs.map((tab, index) => (
79
92
  <MuiTab
93
+ data-se={tab.testId}
80
94
  disabled={tab.isDisabled}
81
95
  icon={tab.startIcon}
82
96
  label={tab.label}
package/src/Tag.tsx CHANGED
@@ -14,6 +14,7 @@ import { Chip as MuiChip, ChipProps as MuiChipProps } from "@mui/material";
14
14
  import { memo, ReactElement, useCallback, useContext } from "react";
15
15
  import { TagListContext } from "./TagListContext";
16
16
  import { MuiPropsContext } from "./MuiPropsContext";
17
+ import { SeleniumProps } from "./SeleniumProps";
17
18
 
18
19
  export type TagProps = {
19
20
  icon?: ReactElement;
@@ -30,9 +31,16 @@ export type TagProps = {
30
31
  * Callback fired when the remove button of the Tag is clicked
31
32
  */
32
33
  onRemove?: MuiChipProps["onDelete"];
33
- };
34
+ } & SeleniumProps;
34
35
 
35
- const Tag = ({ icon, isDisabled, label, onClick, onRemove }: TagProps) => {
36
+ const Tag = ({
37
+ icon,
38
+ isDisabled,
39
+ label,
40
+ onClick,
41
+ onRemove,
42
+ testId,
43
+ }: TagProps) => {
36
44
  const { chipElementType } = useContext(TagListContext);
37
45
 
38
46
  const renderTag = useCallback(
@@ -42,6 +50,7 @@ const Tag = ({ icon, isDisabled, label, onClick, onRemove }: TagProps) => {
42
50
  aria-disabled={isDisabled}
43
51
  clickable={onClick ? true : false}
44
52
  component={chipElementType}
53
+ data-se={testId}
45
54
  disabled={isDisabled}
46
55
  icon={icon}
47
56
  label={label}
@@ -49,7 +58,7 @@ const Tag = ({ icon, isDisabled, label, onClick, onRemove }: TagProps) => {
49
58
  onDelete={onRemove}
50
59
  />
51
60
  ),
52
- [chipElementType, icon, isDisabled, label, onClick, onRemove]
61
+ [chipElementType, icon, isDisabled, label, onClick, onRemove, testId]
53
62
  );
54
63
 
55
64
  return <MuiPropsContext.Consumer>{renderTag}</MuiPropsContext.Consumer>;
package/src/TagList.tsx CHANGED
@@ -14,15 +14,16 @@ import { Tag } from "./Tag";
14
14
  import { Stack } from "@mui/material";
15
15
  import { memo, ReactElement, useMemo } from "react";
16
16
  import { ChipElementType, TagListContext } from "./TagListContext";
17
+ import { SeleniumProps } from "./SeleniumProps";
17
18
 
18
19
  export type TagListProps = {
19
20
  /**
20
21
  * The Tag or array of Tags within the TagList
21
22
  */
22
23
  children: ReactElement<typeof Tag> | Array<ReactElement<typeof Tag>>;
23
- };
24
+ } & SeleniumProps;
24
25
 
25
- const TagList = ({ children }: TagListProps) => {
26
+ const TagList = ({ children, testId }: TagListProps) => {
26
27
  const providerValue = useMemo<{
27
28
  chipElementType: ChipElementType;
28
29
  }>(
@@ -35,6 +36,7 @@ const TagList = ({ children }: TagListProps) => {
35
36
  return (
36
37
  <Stack
37
38
  component="ul"
39
+ data-se={testId}
38
40
  direction="row"
39
41
  spacing={2}
40
42
  useFlexGap