@okta/odyssey-react-mui 1.14.5 → 1.14.6

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 (57) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/Badge.js +1 -1
  3. package/dist/Badge.js.map +1 -1
  4. package/dist/Field.js.map +1 -1
  5. package/dist/Fieldset.js +17 -14
  6. package/dist/Fieldset.js.map +1 -1
  7. package/dist/Form.js +33 -23
  8. package/dist/Form.js.map +1 -1
  9. package/dist/MenuButton.js +1 -1
  10. package/dist/MenuButton.js.map +1 -1
  11. package/dist/SearchField.js +2 -2
  12. package/dist/SearchField.js.map +1 -1
  13. package/dist/labs/FileUpload.js +195 -0
  14. package/dist/labs/FileUpload.js.map +1 -0
  15. package/dist/labs/FileUploadIllustration.js +54 -0
  16. package/dist/labs/FileUploadIllustration.js.map +1 -0
  17. package/dist/labs/FileUploadPreview.js +109 -0
  18. package/dist/labs/FileUploadPreview.js.map +1 -0
  19. package/dist/labs/index.js +1 -0
  20. package/dist/labs/index.js.map +1 -1
  21. package/dist/properties/ts/odyssey-react-mui.js +3 -0
  22. package/dist/properties/ts/odyssey-react-mui.js.map +1 -1
  23. package/dist/src/Field.d.ts +8 -7
  24. package/dist/src/Field.d.ts.map +1 -1
  25. package/dist/src/Fieldset.d.ts.map +1 -1
  26. package/dist/src/Form.d.ts.map +1 -1
  27. package/dist/src/OdysseyTranslationProvider.d.ts +1 -1
  28. package/dist/src/OdysseyTranslationProvider.d.ts.map +1 -1
  29. package/dist/src/SearchField.d.ts.map +1 -1
  30. package/dist/src/labs/FileUpload.d.ts +40 -0
  31. package/dist/src/labs/FileUpload.d.ts.map +1 -0
  32. package/dist/src/labs/FileUploadIllustration.d.ts +15 -0
  33. package/dist/src/labs/FileUploadIllustration.d.ts.map +1 -0
  34. package/dist/src/labs/FileUploadPreview.d.ts +21 -0
  35. package/dist/src/labs/FileUploadPreview.d.ts.map +1 -0
  36. package/dist/src/labs/index.d.ts +1 -0
  37. package/dist/src/labs/index.d.ts.map +1 -1
  38. package/dist/src/properties/ts/odyssey-react-mui.d.ts +3 -0
  39. package/dist/src/properties/ts/odyssey-react-mui.d.ts.map +1 -1
  40. package/dist/src/theme/components.d.ts.map +1 -1
  41. package/dist/theme/components.js +10 -1
  42. package/dist/theme/components.js.map +1 -1
  43. package/dist/tsconfig.production.tsbuildinfo +1 -1
  44. package/package.json +3 -3
  45. package/src/Badge.tsx +1 -1
  46. package/src/Field.tsx +9 -7
  47. package/src/Fieldset.tsx +24 -18
  48. package/src/Form.tsx +43 -27
  49. package/src/MenuButton.tsx +1 -1
  50. package/src/SearchField.tsx +1 -2
  51. package/src/labs/FileUpload.tsx +301 -0
  52. package/src/labs/FileUploadIllustration.tsx +66 -0
  53. package/src/labs/FileUploadPreview.tsx +150 -0
  54. package/src/labs/index.ts +1 -2
  55. package/src/properties/odyssey-react-mui.properties +3 -7
  56. package/src/properties/ts/odyssey-react-mui.ts +1 -1
  57. package/src/theme/components.tsx +9 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@okta/odyssey-react-mui",
3
- "version": "1.14.5",
3
+ "version": "1.14.6",
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.15.9",
52
52
  "@mui/utils": "^5.15.9",
53
53
  "@mui/x-date-pickers": "^5.0.15",
54
- "@okta/odyssey-design-tokens": "^1.14.5",
54
+ "@okta/odyssey-design-tokens": "^1.14.6",
55
55
  "date-fns": "^2.30.0",
56
56
  "i18next": "^23.8.2",
57
57
  "material-react-table": "^2.11.3",
@@ -63,5 +63,5 @@
63
63
  "react": ">=17 <19",
64
64
  "react-dom": ">=17 <19"
65
65
  },
66
- "gitHead": "36a135899e3a8f69a3c6ab89efa1252090d6e632"
66
+ "gitHead": "e9b92857eb24eff1f87ff3ad415e67fb6508e0fc"
67
67
  }
package/src/Badge.tsx CHANGED
@@ -89,7 +89,7 @@ const Badge = ({
89
89
  const hasNotificationCount = badgeContent && badgeContent > 0;
90
90
 
91
91
  return hasNotificationCount ? (
92
- <Box sx={badgeStyles} data-se={testId} translate={translate}>
92
+ <Box sx={badgeStyles} testId={testId} translate={translate}>
93
93
  {formattedContent}
94
94
  </Box>
95
95
  ) : null;
package/src/Field.tsx CHANGED
@@ -29,6 +29,14 @@ import { useUniqueId } from "./useUniqueId";
29
29
 
30
30
  export const fieldTypeValues = ["single", "group"] as const;
31
31
 
32
+ export type RenderFieldComponentProps = {
33
+ ariaDescribedBy?: string;
34
+ dataSe?: string;
35
+ errorMessageElementId?: string;
36
+ id: string;
37
+ labelElementId: string;
38
+ };
39
+
32
40
  export type FieldProps = {
33
41
  /**
34
42
  * If `error` is not undefined, the `input` will indicate an error.
@@ -67,13 +75,7 @@ export type FieldProps = {
67
75
  errorMessageElementId,
68
76
  id,
69
77
  labelElementId,
70
- }: {
71
- ariaDescribedBy?: string;
72
- dataSe?: string;
73
- errorMessageElementId?: string;
74
- id: string;
75
- labelElementId: string;
76
- }) => ReactElement;
78
+ }: RenderFieldComponentProps) => ReactElement;
77
79
  };
78
80
 
79
81
  const Field = ({
package/src/Fieldset.tsx CHANGED
@@ -10,15 +10,32 @@
10
10
  * See the License for the specific language governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- import { Box } from "@mui/material";
14
13
  import { memo, ReactElement, useMemo } from "react";
14
+ import styled from "@emotion/styled";
15
15
 
16
16
  import { Callout } from "./Callout";
17
17
  import { FieldsetContext } from "./FieldsetContext";
18
+ import type { HtmlProps } from "./HtmlProps";
18
19
  import { Legend, Support } from "./Typography";
19
- import { useOdysseyDesignTokens } from "./OdysseyDesignTokensContext";
20
+ import {
21
+ useOdysseyDesignTokens,
22
+ DesignTokens,
23
+ } from "./OdysseyDesignTokensContext";
20
24
  import { useUniqueId } from "./useUniqueId";
21
- import type { HtmlProps } from "./HtmlProps";
25
+
26
+ const StyledFieldset = styled.fieldset<{
27
+ odysseyDesignTokens: DesignTokens;
28
+ }>(({ odysseyDesignTokens }) => ({
29
+ border: "0",
30
+ margin: odysseyDesignTokens.Spacing0,
31
+ marginBlockEnd: odysseyDesignTokens.Spacing6,
32
+ maxWidth: odysseyDesignTokens.TypographyLineLengthMax,
33
+ padding: odysseyDesignTokens.Spacing0,
34
+
35
+ "&:last-child": {
36
+ marginBlockEnd: odysseyDesignTokens.Spacing0,
37
+ },
38
+ }));
22
39
 
23
40
  export type FieldsetProps = {
24
41
  /**
@@ -73,23 +90,12 @@ const Fieldset = ({
73
90
  );
74
91
 
75
92
  return (
76
- <Box
77
- component="fieldset"
93
+ <StyledFieldset
78
94
  data-se={testId}
79
95
  disabled={isDisabled}
80
- name={name}
81
96
  id={id}
82
- sx={{
83
- border: "0",
84
- margin: odysseyDesignTokens.Spacing0,
85
- marginBlockEnd: odysseyDesignTokens.Spacing6,
86
- maxWidth: odysseyDesignTokens.TypographyLineLengthMax,
87
- padding: odysseyDesignTokens.Spacing0,
88
-
89
- "&:last-child": {
90
- marginBlockEnd: odysseyDesignTokens.Spacing0,
91
- },
92
- }}
97
+ name={name}
98
+ odysseyDesignTokens={odysseyDesignTokens}
93
99
  >
94
100
  <Legend translate={translate}>{legend}</Legend>
95
101
 
@@ -100,7 +106,7 @@ const Fieldset = ({
100
106
  <FieldsetContext.Provider value={fieldsetContextValue}>
101
107
  {children}
102
108
  </FieldsetContext.Provider>
103
- </Box>
109
+ </StyledFieldset>
104
110
  );
105
111
  };
106
112
 
package/src/Form.tsx CHANGED
@@ -11,7 +11,7 @@
11
11
  */
12
12
 
13
13
  import { FormEventHandler, memo, ReactElement } from "react";
14
- import { Box } from "@mui/material";
14
+ import styled from "@emotion/styled";
15
15
 
16
16
  import { Button } from "./Button";
17
17
  import { Callout } from "./Callout";
@@ -19,6 +19,10 @@ import { FieldComponentProps } from "./FieldComponentProps";
19
19
  import type { HtmlProps } from "./HtmlProps";
20
20
  import { Heading4, Support } from "./Typography";
21
21
  import { useUniqueId } from "./useUniqueId";
22
+ import {
23
+ useOdysseyDesignTokens,
24
+ DesignTokens,
25
+ } from "./OdysseyDesignTokensContext";
22
26
 
23
27
  export const formEncodingTypeValues = [
24
28
  "application/x-www-form-urlencoded",
@@ -29,6 +33,34 @@ export const formEncodingTypeValues = [
29
33
  export const formAutoCompleteTypeValues = ["on", "off"] as const;
30
34
  export const formMethodValues = ["post", "get", "dialog"] as const;
31
35
 
36
+ const StyledForm = styled.form<{
37
+ isFullWidth?: boolean;
38
+ odysseyDesignTokens: DesignTokens;
39
+ }>(({ isFullWidth, odysseyDesignTokens }) => ({
40
+ maxWidth: isFullWidth ? "100%" : odysseyDesignTokens.TypographyLineLengthMax,
41
+ margin: 0,
42
+ padding: 0,
43
+ }));
44
+
45
+ const TitleContainer = styled.div<{
46
+ odysseyDesignTokens: DesignTokens;
47
+ }>(({ odysseyDesignTokens }) => ({
48
+ marginBlockEnd: odysseyDesignTokens.Spacing4,
49
+ }));
50
+
51
+ const FormActionContainer = styled.div<{
52
+ odysseyDesignTokens: DesignTokens;
53
+ }>(
54
+ {
55
+ display: "flex",
56
+ justifyContent: "flex-end",
57
+ },
58
+ ({ odysseyDesignTokens }) => ({
59
+ gap: odysseyDesignTokens.Spacing1,
60
+ marginBlockStart: odysseyDesignTokens.Spacing7,
61
+ }),
62
+ );
63
+
32
64
  export type FormProps = {
33
65
  /**
34
66
  * A Callout indicating a Form-wide error or status update.
@@ -112,31 +144,23 @@ const Form = ({
112
144
  translate,
113
145
  }: FormProps) => {
114
146
  const id = useUniqueId(idOverride);
147
+ const odysseyDesignTokens = useOdysseyDesignTokens();
115
148
 
116
149
  return (
117
- <Box
150
+ <StyledForm
118
151
  autoComplete={autoCompleteType}
119
- component="form"
120
152
  data-se={testId}
121
153
  encType={encodingType}
122
154
  id={id}
155
+ isFullWidth={isFullWidth}
123
156
  method={method}
124
157
  name={name}
125
158
  noValidate={noValidate}
159
+ odysseyDesignTokens={odysseyDesignTokens}
126
160
  onSubmit={onSubmit}
127
161
  target={target}
128
- sx={{
129
- maxWidth: (theme) => (isFullWidth ? "100%" : theme.mixins.maxWidth),
130
- margin: (theme) => theme.spacing(0),
131
- padding: (theme) => theme.spacing(0),
132
- }}
133
162
  >
134
- <Box
135
- component="div"
136
- sx={{
137
- marginBlockEnd: (theme) => theme.spacing(4),
138
- }}
139
- >
163
+ <TitleContainer odysseyDesignTokens={odysseyDesignTokens}>
140
164
  {title && (
141
165
  <Heading4 component="h1" translate={translate}>
142
166
  {title}
@@ -144,22 +168,14 @@ const Form = ({
144
168
  )}
145
169
  {description && <Support translate={translate}>{description}</Support>}
146
170
  {alert}
147
- </Box>
148
- <Box component="div">{children}</Box>
171
+ </TitleContainer>
172
+ <div>{children}</div>
149
173
  {formActions && (
150
- <Box
151
- component="div"
152
- sx={{
153
- display: "flex",
154
- justifyContent: "flex-end",
155
- gap: (theme) => theme.spacing(1),
156
- marginBlockStart: (theme) => theme.spacing(7),
157
- }}
158
- >
174
+ <FormActionContainer odysseyDesignTokens={odysseyDesignTokens}>
159
175
  {formActions}
160
- </Box>
176
+ </FormActionContainer>
161
177
  )}
162
- </Box>
178
+ </StyledForm>
163
179
  );
164
180
  };
165
181
 
@@ -179,7 +179,7 @@ const MenuButton = ({
179
179
  ariaDescribedBy={ariaDescribedBy}
180
180
  ariaLabel={ariaLabel}
181
181
  ariaLabelledBy={ariaLabelledBy}
182
- data-se={testId}
182
+ testId={testId}
183
183
  endIcon={endIcon}
184
184
  id={`${uniqueId}-button`}
185
185
  isDisabled={isDisabled}
@@ -158,7 +158,7 @@ const SearchField = forwardRef<HTMLInputElement, SearchFieldProps>(
158
158
  autoFocus={hasInitialFocus}
159
159
  data-se={testId}
160
160
  endAdornment={
161
- defaultValue && (
161
+ (inputValues?.defaultValue || inputValues?.value) && (
162
162
  <InputAdornment position="end">
163
163
  <IconButton
164
164
  aria-label="Clear"
@@ -191,7 +191,6 @@ const SearchField = forwardRef<HTMLInputElement, SearchFieldProps>(
191
191
  ),
192
192
  [
193
193
  autoCompleteType,
194
- defaultValue,
195
194
  hasInitialFocus,
196
195
  inputValues,
197
196
  isDisabled,
@@ -0,0 +1,301 @@
1
+ /*!
2
+ * Copyright (c) 2022-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
+ memo,
15
+ ChangeEvent,
16
+ useCallback,
17
+ useEffect,
18
+ useRef,
19
+ useState,
20
+ } from "react";
21
+ import styled from "@emotion/styled";
22
+ import { useTranslation } from "react-i18next";
23
+
24
+ import { Button } from "../Button";
25
+ import { UploadIcon } from "../icons.generated";
26
+ import { Field, RenderFieldComponentProps } from "../Field";
27
+ import { FieldComponentProps } from "../FieldComponentProps";
28
+ import { FileUploadPreview } from "./FileUploadPreview";
29
+ import { FileUploadIllustration } from "./FileUploadIllustration";
30
+ import {
31
+ useOdysseyDesignTokens,
32
+ DesignTokens,
33
+ } from "../OdysseyDesignTokensContext";
34
+ import { Support } from "../Typography";
35
+
36
+ export const fileUploadTypes = ["single", "multiple"] as const;
37
+ export const fileUploadVariants = [
38
+ "button",
39
+ "dragAndDrop",
40
+ "dragAndDropWithIcon",
41
+ ] as const;
42
+
43
+ const BaseInputWrapper = styled.div({
44
+ position: "relative",
45
+ alignSelf: "flex-start",
46
+
47
+ input: {
48
+ position: "absolute",
49
+ width: "100%",
50
+ height: "100%",
51
+ opacity: 0,
52
+ },
53
+ });
54
+
55
+ const InputContainer = styled(BaseInputWrapper)<{
56
+ odysseyDesignTokens: DesignTokens;
57
+ }>(
58
+ {
59
+ display: "flex",
60
+ alignSelf: "unset",
61
+ alignItems: "center",
62
+ justifyContent: "center",
63
+
64
+ "&:has(input:focus)": {
65
+ borderStyle: "solid",
66
+ },
67
+ },
68
+ ({ odysseyDesignTokens }) => ({
69
+ padding: `${odysseyDesignTokens.Spacing6} ${odysseyDesignTokens.Spacing3}`,
70
+ border: `1px dashed ${odysseyDesignTokens.HueNeutral300}`,
71
+ borderRadius: odysseyDesignTokens.BorderRadiusMain,
72
+ transition: `border-color ${odysseyDesignTokens.TransitionTimingMain}, box-shadow ${odysseyDesignTokens.TransitionTimingMain}`,
73
+
74
+ "&:hover": {
75
+ borderColor: odysseyDesignTokens.HueNeutral700,
76
+ },
77
+
78
+ "&:has(input:focus)": {
79
+ borderColor: odysseyDesignTokens.FocusOutlineColorPrimary,
80
+ boxShadow: `0 0 0 1px ${odysseyDesignTokens.FocusOutlineColorPrimary}`,
81
+ outline: `${odysseyDesignTokens.FocusOutlineWidthMain} ${odysseyDesignTokens.FocusOutlineStyle} transparent`,
82
+ outlineOffset: odysseyDesignTokens.FocusOutlineOffsetTight,
83
+ },
84
+
85
+ "&:has(input:disabled)": {
86
+ backgroundColor: odysseyDesignTokens.HueNeutral50,
87
+ border: `1px solid ${odysseyDesignTokens.BorderColorDisabled}`,
88
+ color: odysseyDesignTokens.TypographyColorDisabled,
89
+
90
+ "&:hover": {
91
+ borderColor: odysseyDesignTokens.BorderColorDisabled,
92
+ },
93
+ },
94
+ }),
95
+ );
96
+
97
+ const ButtonAndInfoContainer = styled.div({
98
+ display: "flex",
99
+ flexDirection: "column",
100
+ alignItems: "center",
101
+ justifyContent: "center",
102
+ });
103
+
104
+ export type FileUploadProps = {
105
+ /**
106
+ * an array of file types the user is able to upload. @see https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/accept#unique_file_type_specifiers for examples
107
+ */
108
+ acceptedFileTypes?: string[];
109
+ /**
110
+ * The label for the `input` element.
111
+ */
112
+ label: string;
113
+ /**
114
+ * Function that is called when the list of ifles to upload is changed
115
+ */
116
+ onChange: (files: File[]) => void;
117
+ /**
118
+ * Either `single` or `multiple`. If `multiple`, multiple files can be uploaded
119
+ */
120
+ type?: (typeof fileUploadTypes)[number];
121
+ /**
122
+ * Either `button`, `dragAndDrop` or `dragAndDropWithIcon`. Will determine how component appears visually
123
+ */
124
+ variant: (typeof fileUploadVariants)[number];
125
+ } & Pick<
126
+ FieldComponentProps,
127
+ | "errorMessage"
128
+ | "hint"
129
+ | "HintLinkComponent"
130
+ | "id"
131
+ | "isDisabled"
132
+ | "isOptional"
133
+ >;
134
+
135
+ const FileUpload = ({
136
+ acceptedFileTypes,
137
+ errorMessage,
138
+ id,
139
+ isDisabled = false,
140
+ isOptional,
141
+ hint,
142
+ HintLinkComponent,
143
+ label,
144
+ onChange,
145
+ type,
146
+ variant,
147
+ }: FileUploadProps) => {
148
+ const odysseyDesignTokens = useOdysseyDesignTokens();
149
+ const { t } = useTranslation();
150
+ const inputRef = useRef<HTMLInputElement>(null);
151
+ const [filesToUpload, setFilesToUpload] = useState<File[]>([]);
152
+
153
+ useEffect(() => {
154
+ onChange(filesToUpload);
155
+ }, [filesToUpload, onChange]);
156
+
157
+ const updateFilesToUpload = useCallback(
158
+ (event: ChangeEvent<HTMLInputElement>) => {
159
+ const { files } = event.target;
160
+
161
+ if (files && files.length > 0) {
162
+ const mergedFiles =
163
+ type === "multiple"
164
+ ? [...filesToUpload, ...files]
165
+ : ([...files] satisfies File[] as File[]);
166
+
167
+ setFilesToUpload(mergedFiles);
168
+ }
169
+
170
+ // reset input value to allow re-upload of a file with the same name
171
+ event.target.value = "";
172
+ },
173
+ [type, filesToUpload],
174
+ );
175
+
176
+ const triggerFileInputClick = useCallback(() => {
177
+ inputRef.current?.focus();
178
+ }, [inputRef]);
179
+
180
+ const removeFileFromFilesToUploadList = useCallback<(name: string) => void>(
181
+ (name) => {
182
+ const deletedFileFilteredOut = filesToUpload.filter(
183
+ (file) => file.name !== name,
184
+ );
185
+ setFilesToUpload(deletedFileFilteredOut);
186
+ },
187
+ [filesToUpload],
188
+ );
189
+
190
+ const renderFileInput = useCallback(
191
+ ({
192
+ ariaDescribedBy,
193
+ errorMessageElementId,
194
+ id,
195
+ labelElementId,
196
+ }: RenderFieldComponentProps) => {
197
+ const fileNames = filesToUpload.map((file) => file.name);
198
+ const acceptedFileTypesAsString = acceptedFileTypes?.join(",");
199
+
200
+ const Input = () => (
201
+ <input
202
+ accept={acceptedFileTypesAsString}
203
+ aria-describedby={ariaDescribedBy}
204
+ aria-errormessage={errorMessageElementId}
205
+ aria-labelledby={labelElementId}
206
+ disabled={isDisabled}
207
+ id={id}
208
+ multiple={type === "multiple"}
209
+ onChange={updateFilesToUpload}
210
+ ref={inputRef}
211
+ title=""
212
+ type="file"
213
+ />
214
+ );
215
+
216
+ if (variant === "button") {
217
+ return (
218
+ <>
219
+ <BaseInputWrapper>
220
+ <Input />
221
+ <Button
222
+ isDisabled={isDisabled}
223
+ label={t("fileupload.button.text")}
224
+ onClick={triggerFileInputClick}
225
+ startIcon={<UploadIcon />}
226
+ variant="secondary"
227
+ />
228
+ </BaseInputWrapper>
229
+ <FileUploadPreview
230
+ fileNames={fileNames}
231
+ onFileRemove={removeFileFromFilesToUploadList}
232
+ isDisabled={isDisabled}
233
+ />
234
+ </>
235
+ );
236
+ }
237
+
238
+ return (
239
+ <>
240
+ <InputContainer odysseyDesignTokens={odysseyDesignTokens}>
241
+ <Input />
242
+ <ButtonAndInfoContainer>
243
+ {variant === "dragAndDropWithIcon" && <FileUploadIllustration />}
244
+ <Support color="textSecondary">
245
+ {t("fileupload.prompt.text")}
246
+ </Support>
247
+ <Button
248
+ isDisabled={isDisabled}
249
+ label={t("fileupload.button.text")}
250
+ onClick={triggerFileInputClick}
251
+ startIcon={<UploadIcon />}
252
+ variant="secondary"
253
+ />
254
+ </ButtonAndInfoContainer>
255
+ </InputContainer>
256
+ <FileUploadPreview
257
+ fileNames={fileNames}
258
+ onFileRemove={removeFileFromFilesToUploadList}
259
+ isDisabled={isDisabled}
260
+ />
261
+ </>
262
+ );
263
+ },
264
+ [
265
+ acceptedFileTypes,
266
+ filesToUpload,
267
+ isDisabled,
268
+ inputRef,
269
+ odysseyDesignTokens,
270
+ removeFileFromFilesToUploadList,
271
+ triggerFileInputClick,
272
+ t,
273
+ type,
274
+ updateFilesToUpload,
275
+ variant,
276
+ ],
277
+ );
278
+
279
+ return (
280
+ <>
281
+ <Field
282
+ errorMessage={errorMessage}
283
+ fieldType="single"
284
+ hasVisibleLabel
285
+ hint={hint}
286
+ HintLinkComponent={HintLinkComponent}
287
+ id={id}
288
+ isDisabled={isDisabled}
289
+ isFullWidth
290
+ isOptional={isOptional}
291
+ label={label}
292
+ renderFieldComponent={renderFileInput}
293
+ />
294
+ </>
295
+ );
296
+ };
297
+
298
+ const MemoizedFileUpload = memo(FileUpload);
299
+ MemoizedFileUpload.displayName = "FileUpload";
300
+
301
+ export { MemoizedFileUpload as FileUpload };
@@ -0,0 +1,66 @@
1
+ /*!
2
+ * Copyright (c) 2022-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 { memo } from "react";
14
+ import styled from "@emotion/styled";
15
+
16
+ import {
17
+ useOdysseyDesignTokens,
18
+ DesignTokens,
19
+ } from "../OdysseyDesignTokensContext";
20
+
21
+ const UploadIllustrationContainer = styled.div<{
22
+ odysseyDesignTokens: DesignTokens;
23
+ }>(({ odysseyDesignTokens }) => ({
24
+ marginBlockEnd: odysseyDesignTokens.Spacing2,
25
+ padding: odysseyDesignTokens.Spacing3,
26
+ backgroundColor: odysseyDesignTokens.HueNeutral50,
27
+ borderRadius: "50%",
28
+
29
+ svg: {
30
+ display: "flex",
31
+ width: odysseyDesignTokens.Spacing8,
32
+ height: odysseyDesignTokens.Spacing8,
33
+ },
34
+ }));
35
+
36
+ const FileUploadIllustration = () => {
37
+ const odysseyDesignTokens = useOdysseyDesignTokens();
38
+
39
+ return (
40
+ <UploadIllustrationContainer
41
+ aria-hidden="true"
42
+ odysseyDesignTokens={odysseyDesignTokens}
43
+ >
44
+ <svg
45
+ aria-hidden="true"
46
+ viewBox="0 0 44 45"
47
+ fill="none"
48
+ xmlns="http://www.w3.org/2000/svg"
49
+ >
50
+ <path
51
+ d="M32.0763 11.001C29.3564 3.7855 21.6595 -0.565827 13.7765 0.726748C5.35441 2.10773 -0.676662 9.50714 0.0603005 17.8612C0.441865 22.1865 2.56458 25.9787 5.71703 28.614L8.28246 25.545C5.90122 23.5544 4.32811 20.7209 4.04483 17.5097C3.50262 11.3633 7.94433 5.73648 14.4238 4.67404C20.9164 3.60944 27.0806 7.52016 28.6895 13.5191C28.9239 14.3932 29.7162 15.001 30.6212 15.001H32.9114C36.8985 15.001 39.9997 18.0938 39.9997 21.7505C39.9997 24.3423 38.4576 26.6352 36.1259 27.7678L37.8736 31.3658C41.4737 29.6171 43.9997 25.9917 43.9997 21.7505C43.9997 15.7428 38.963 11.001 32.9114 11.001H32.0763Z"
52
+ fill={odysseyDesignTokens.HueNeutral200}
53
+ />
54
+ <path
55
+ d="M23.9994 29.3277V44.5H19.9994V29.3289L14.4142 34.9141L11.5858 32.0857L19.7373 23.9342C20.9869 22.6845 23.0131 22.6845 24.2627 23.9342L32.4142 32.0857L29.5858 34.9141L23.9994 29.3277Z"
56
+ fill={odysseyDesignTokens.HueNeutral200}
57
+ />
58
+ </svg>
59
+ </UploadIllustrationContainer>
60
+ );
61
+ };
62
+
63
+ const MemoizedFileUploadIllustration = memo(FileUploadIllustration);
64
+ MemoizedFileUploadIllustration.displayName = "FileUploadIllustration";
65
+
66
+ export { MemoizedFileUploadIllustration as FileUploadIllustration };