@okta/odyssey-react-mui 1.5.0 → 1.6.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@okta/odyssey-react-mui",
3
- "version": "1.5.0",
3
+ "version": "1.6.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.5.0",
54
+ "@okta/odyssey-design-tokens": "1.6.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": "cf8dbb65cb3ca95e69bfe65b9825366146682368"
66
+ "gitHead": "03b8e69263df83250402101d7637a5ede3578099"
67
67
  }
@@ -130,7 +130,7 @@ const PasswordField = forwardRef<HTMLInputElement, PasswordFieldProps>(
130
130
  ({ ariaDescribedBy, errorMessageElementId, id, labelElementId }) => (
131
131
  <InputBase
132
132
  aria-describedby={ariaDescribedBy}
133
- autoComplete={autoCompleteType}
133
+ autoComplete={inputType === "password" ? autoCompleteType : "off"}
134
134
  /* eslint-disable-next-line jsx-a11y/no-autofocus */
135
135
  autoFocus={hasInitialFocus}
136
136
  data-se={testId}
package/src/Select.tsx CHANGED
@@ -10,16 +10,15 @@
10
10
  * See the License for the specific language governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- import { ReactNode, memo, useCallback, useMemo, useState } from "react";
13
+ import { memo, useCallback, useMemo, useState } from "react";
14
14
  import {
15
15
  Box,
16
- Chip,
17
16
  Checkbox as MuiCheckbox,
17
+ Chip,
18
+ ListItemSecondaryAction,
18
19
  ListSubheader,
19
20
  MenuItem,
20
21
  Select as MuiSelect,
21
- SelectChangeEvent,
22
- ListItemSecondaryAction,
23
22
  } from "@mui/material";
24
23
  import { SelectProps as MuiSelectProps } from "@mui/material";
25
24
  import { Field } from "./Field";
@@ -32,11 +31,21 @@ export type SelectOption = {
32
31
  value?: string;
33
32
  };
34
33
 
35
- export type SelectProps = {
34
+ export type SelectValueType<HasMultipleChoices> =
35
+ HasMultipleChoices extends true ? string[] : string;
36
+
37
+ export type SelectProps<
38
+ Value extends SelectValueType<HasMultipleChoices>,
39
+ HasMultipleChoices extends boolean
40
+ > = {
36
41
  /**
37
42
  * The error message for the Select
38
43
  */
39
44
  errorMessage?: string;
45
+ /**
46
+ * If `true`, the Select allows multiple selections
47
+ */
48
+ hasMultipleChoices?: HasMultipleChoices;
40
49
  /**
41
50
  * The hint text for the Select
42
51
  */
@@ -50,9 +59,10 @@ export type SelectProps = {
50
59
  */
51
60
  isDisabled?: boolean;
52
61
  /**
53
- * If `true`, the Select allows multiple selections
62
+ * @deprecated Use `hasMultipleChoices` instead.
54
63
  */
55
- isMultiSelect?: boolean;
64
+ /** **Deprecated:** use `hasMultipleChoices` */
65
+ isMultiSelect?: HasMultipleChoices;
56
66
  /**
57
67
  * If `true`, the Select is optional
58
68
  */
@@ -68,15 +78,15 @@ export type SelectProps = {
68
78
  /**
69
79
  * Callback fired when the Select loses focus
70
80
  */
71
- onBlur?: MuiSelectProps["onBlur"];
81
+ onBlur?: MuiSelectProps<Value>["onBlur"];
72
82
  /**
73
83
  * Callback fired when the value of the Select changes
74
84
  */
75
- onChange?: MuiSelectProps["onChange"];
85
+ onChange?: MuiSelectProps<Value>["onChange"];
76
86
  /**
77
87
  * Callback fired when the Select gains focus
78
88
  */
79
- onFocus?: MuiSelectProps["onFocus"];
89
+ onFocus?: MuiSelectProps<Value>["onFocus"];
80
90
  /**
81
91
  * The options for the Select
82
92
  */
@@ -84,7 +94,7 @@ export type SelectProps = {
84
94
  /**
85
95
  * The value or values selected in the Select
86
96
  */
87
- value?: string | string[];
97
+ value?: Value;
88
98
  } & SeleniumProps;
89
99
 
90
100
  /**
@@ -102,50 +112,59 @@ export type SelectProps = {
102
112
  * - { text: string, type: "heading" } — Used to display a group heading with the text
103
113
  */
104
114
 
105
- const Select = ({
115
+ const Select = <
116
+ Value extends SelectValueType<HasMultipleChoices>,
117
+ HasMultipleChoices extends boolean
118
+ >({
106
119
  errorMessage,
120
+ hasMultipleChoices: hasMultipleChoicesProp,
107
121
  hint,
108
122
  id: idOverride,
109
123
  isDisabled = false,
110
- isMultiSelect = false,
124
+ isMultiSelect,
111
125
  isOptional = false,
112
126
  label,
113
127
  name: nameOverride,
114
128
  onBlur,
115
129
  onChange: onChangeProp,
116
130
  onFocus,
117
- value,
118
- testId,
119
131
  options,
120
- }: SelectProps) => {
121
- // If there's no value set, we set it to a blank string (if it's a single-select)
122
- // or an empty array (if it's a multi-select)
123
- if (typeof value === "undefined") {
124
- value = isMultiSelect ? [] : "";
125
- }
132
+ testId,
133
+ value,
134
+ }: SelectProps<Value, HasMultipleChoices>) => {
135
+ const hasMultipleChoices = useMemo(
136
+ () =>
137
+ hasMultipleChoicesProp === undefined
138
+ ? isMultiSelect
139
+ : hasMultipleChoicesProp,
140
+ [hasMultipleChoicesProp, isMultiSelect]
141
+ );
126
142
 
127
- const [selectedValue, setSelectedValue] = useState<string | string[]>(value);
143
+ const formattedValueForMultiSelect = isMultiSelect
144
+ ? ([] as string[] as Value)
145
+ : ("" as string as Value);
128
146
 
129
- const onChange = useCallback(
130
- (event: SelectChangeEvent<string | string[]>, child: ReactNode) => {
131
- const {
132
- target: { value },
133
- } = event;
147
+ const [selectedValue, setSelectedValue] = useState(
148
+ value === undefined ? formattedValueForMultiSelect : value
149
+ );
150
+
151
+ const onChange = useCallback<NonNullable<MuiSelectProps<Value>["onChange"]>>(
152
+ (event, child) => {
153
+ const valueFromEvent = event.target.value;
134
154
 
135
- // Set the field value, with some additional logic to handle array values
136
- // for multi-selects
137
- if (isMultiSelect) {
138
- setSelectedValue(typeof value === "string" ? value.split(",") : value);
155
+ if (typeof valueFromEvent === "string") {
156
+ if (hasMultipleChoices) {
157
+ setSelectedValue(valueFromEvent.split(",") as Value);
158
+ } else {
159
+ setSelectedValue(valueFromEvent as Value);
160
+ }
139
161
  } else {
140
- setSelectedValue(value);
162
+ setSelectedValue(valueFromEvent);
141
163
  }
142
164
 
143
- // Trigger the onChange event, if one has been passed
144
- if (onChangeProp) {
145
- onChangeProp(event, child);
146
- }
165
+ onChangeProp?.(event, child);
147
166
  },
148
- [isMultiSelect, onChangeProp, setSelectedValue]
167
+ [hasMultipleChoices, onChangeProp, setSelectedValue]
149
168
  );
150
169
 
151
170
  // Normalize the options array to accommodate the various
@@ -165,7 +184,7 @@ const Select = ({
165
184
  );
166
185
 
167
186
  const renderValue = useCallback(
168
- (selected: string | string[]) => {
187
+ (selected: Value) => {
169
188
  // If the selected value isn't an array, then we don't need to display
170
189
  // chips and should fall back to the default render behavior
171
190
  if (typeof selected === "string") {
@@ -209,7 +228,7 @@ const Select = ({
209
228
 
210
229
  return (
211
230
  <MenuItem key={option.value} value={option.value}>
212
- {isMultiSelect && (
231
+ {hasMultipleChoices && (
213
232
  <MuiCheckbox checked={selectedValue.includes(option.value)} />
214
233
  )}
215
234
  {option.text}
@@ -221,30 +240,30 @@ const Select = ({
221
240
  </MenuItem>
222
241
  );
223
242
  }),
224
- [isMultiSelect, normalizedOptions, selectedValue]
243
+ [hasMultipleChoices, normalizedOptions, selectedValue]
225
244
  );
226
245
 
227
246
  const renderFieldComponent = useCallback(
228
247
  ({ ariaDescribedBy, errorMessageElementId, id, labelElementId }) => (
229
248
  <MuiSelect
249
+ aria-describedby={ariaDescribedBy}
250
+ aria-errormessage={errorMessageElementId}
230
251
  children={children}
231
252
  data-se={testId}
232
253
  id={id}
233
- aria-errormessage={errorMessageElementId}
234
- aria-describedby={ariaDescribedBy}
235
254
  labelId={labelElementId}
236
- multiple={isMultiSelect}
255
+ multiple={hasMultipleChoices}
237
256
  name={nameOverride ?? id}
238
257
  onBlur={onBlur}
239
258
  onChange={onChange}
240
259
  onFocus={onFocus}
241
- renderValue={isMultiSelect ? renderValue : undefined}
260
+ renderValue={hasMultipleChoices ? renderValue : undefined}
242
261
  value={selectedValue}
243
262
  />
244
263
  ),
245
264
  [
246
265
  children,
247
- isMultiSelect,
266
+ hasMultipleChoices,
248
267
  nameOverride,
249
268
  onBlur,
250
269
  onChange,
package/src/index.ts CHANGED
@@ -66,6 +66,7 @@ export * from "./Callout";
66
66
  export * from "./Checkbox";
67
67
  export * from "./CheckboxGroup";
68
68
  export * from "./CircularProgress";
69
+ export * from "./createShadowDom";
69
70
  export * from "./createUniqueId";
70
71
  export * from "./Dialog";
71
72
  export * from "./Fieldset";
@@ -1675,7 +1675,7 @@ export const components = ({
1675
1675
  styleOverrides: {
1676
1676
  root: {
1677
1677
  color: odysseyTokens.TypographyColorAction,
1678
- textDecoration: "underline",
1678
+ textDecoration: "none",
1679
1679
  cursor: "pointer",
1680
1680
 
1681
1681
  "&:visited": {
@@ -1703,11 +1703,11 @@ export const components = ({
1703
1703
  },
1704
1704
 
1705
1705
  ".Link-indicator": {
1706
- marginInlineStart: odysseyTokens.Spacing2,
1706
+ marginInlineStart: odysseyTokens.Spacing1,
1707
1707
  },
1708
1708
 
1709
1709
  ".Link-icon": {
1710
- marginInlineEnd: odysseyTokens.Spacing2,
1710
+ marginInlineEnd: odysseyTokens.Spacing1,
1711
1711
  },
1712
1712
  svg: {
1713
1713
  fontSize: "1em",