@okta/odyssey-react-mui 1.14.4 → 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.
- package/CHANGELOG.md +8 -0
- package/dist/Badge.js +1 -1
- package/dist/Badge.js.map +1 -1
- package/dist/DataTable/DataTable.js +178 -58
- package/dist/DataTable/DataTable.js.map +1 -1
- package/dist/DataTable/DataTableEmptyState.js +55 -0
- package/dist/DataTable/DataTableEmptyState.js.map +1 -0
- package/dist/DataTable/DataTablePagination.js +221 -0
- package/dist/DataTable/DataTablePagination.js.map +1 -0
- package/dist/DataTable/DataTableRowActions.js +34 -24
- package/dist/DataTable/DataTableRowActions.js.map +1 -1
- package/dist/DataTable/DataTableSettings.js +22 -10
- package/dist/DataTable/DataTableSettings.js.map +1 -1
- package/dist/DataTable/constants.js +1 -0
- package/dist/DataTable/constants.js.map +1 -1
- package/dist/DataTable/index.js +1 -0
- package/dist/DataTable/index.js.map +1 -1
- package/dist/DataTable/useRowReordering.js +3 -3
- package/dist/DataTable/useRowReordering.js.map +1 -1
- package/dist/DataTable/useScrollIndication.js +70 -0
- package/dist/DataTable/useScrollIndication.js.map +1 -0
- package/dist/Field.js.map +1 -1
- package/dist/Fieldset.js +17 -14
- package/dist/Fieldset.js.map +1 -1
- package/dist/Form.js +33 -23
- package/dist/Form.js.map +1 -1
- package/dist/MenuButton.js +1 -1
- package/dist/MenuButton.js.map +1 -1
- package/dist/SearchField.js +2 -2
- package/dist/SearchField.js.map +1 -1
- package/dist/labs/DataFilters.js +6 -2
- package/dist/labs/DataFilters.js.map +1 -1
- package/dist/labs/DataTable.js +3 -3
- package/dist/labs/DataTable.js.map +1 -1
- package/dist/labs/FileUpload.js +195 -0
- package/dist/labs/FileUpload.js.map +1 -0
- package/dist/labs/FileUploadIllustration.js +54 -0
- package/dist/labs/FileUploadIllustration.js.map +1 -0
- package/dist/labs/FileUploadPreview.js +109 -0
- package/dist/labs/FileUploadPreview.js.map +1 -0
- package/dist/labs/index.js +1 -0
- package/dist/labs/index.js.map +1 -1
- package/dist/properties/ts/odyssey-react-mui.js +12 -0
- package/dist/properties/ts/odyssey-react-mui.js.map +1 -1
- package/dist/src/DataTable/DataTable.d.ts +36 -18
- package/dist/src/DataTable/DataTable.d.ts.map +1 -1
- package/dist/src/DataTable/DataTableEmptyState.d.ts +21 -0
- package/dist/src/DataTable/DataTableEmptyState.d.ts.map +1 -0
- package/dist/src/DataTable/DataTablePagination.d.ts +33 -0
- package/dist/src/DataTable/DataTablePagination.d.ts.map +1 -0
- package/dist/src/DataTable/DataTableRowActions.d.ts.map +1 -1
- package/dist/src/DataTable/DataTableSettings.d.ts.map +1 -1
- package/dist/src/DataTable/constants.d.ts +1 -0
- package/dist/src/DataTable/constants.d.ts.map +1 -1
- package/dist/src/DataTable/index.d.ts +2 -1
- package/dist/src/DataTable/index.d.ts.map +1 -1
- package/dist/src/DataTable/useRowReordering.d.ts.map +1 -1
- package/dist/src/DataTable/useScrollIndication.d.ts +22 -0
- package/dist/src/DataTable/useScrollIndication.d.ts.map +1 -0
- package/dist/src/Field.d.ts +8 -7
- package/dist/src/Field.d.ts.map +1 -1
- package/dist/src/Fieldset.d.ts.map +1 -1
- package/dist/src/Form.d.ts.map +1 -1
- package/dist/src/OdysseyTranslationProvider.d.ts +1 -1
- package/dist/src/OdysseyTranslationProvider.d.ts.map +1 -1
- package/dist/src/SearchField.d.ts.map +1 -1
- package/dist/src/labs/DataFilters.d.ts +5 -1
- package/dist/src/labs/DataFilters.d.ts.map +1 -1
- package/dist/src/labs/DataTable.d.ts.map +1 -1
- package/dist/src/labs/FileUpload.d.ts +40 -0
- package/dist/src/labs/FileUpload.d.ts.map +1 -0
- package/dist/src/labs/FileUploadIllustration.d.ts +15 -0
- package/dist/src/labs/FileUploadIllustration.d.ts.map +1 -0
- package/dist/src/labs/FileUploadPreview.d.ts +21 -0
- package/dist/src/labs/FileUploadPreview.d.ts.map +1 -0
- package/dist/src/labs/index.d.ts +4 -0
- package/dist/src/labs/index.d.ts.map +1 -1
- package/dist/src/properties/ts/odyssey-react-mui.d.ts +12 -0
- package/dist/src/properties/ts/odyssey-react-mui.d.ts.map +1 -1
- package/dist/src/theme/components.d.ts.map +1 -1
- package/dist/theme/components.js +10 -1
- package/dist/theme/components.js.map +1 -1
- package/dist/tsconfig.production.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/src/Badge.tsx +1 -1
- package/src/DataTable/DataTable.tsx +293 -85
- package/src/DataTable/DataTableEmptyState.tsx +62 -0
- package/src/DataTable/DataTablePagination.tsx +289 -0
- package/src/DataTable/DataTableRowActions.tsx +35 -37
- package/src/DataTable/DataTableSettings.tsx +43 -17
- package/src/DataTable/constants.ts +1 -0
- package/src/DataTable/index.tsx +7 -1
- package/src/DataTable/useRowReordering.tsx +5 -3
- package/src/DataTable/useScrollIndication.tsx +118 -0
- package/src/Field.tsx +9 -7
- package/src/Fieldset.tsx +24 -18
- package/src/Form.tsx +43 -27
- package/src/MenuButton.tsx +1 -1
- package/src/SearchField.tsx +1 -2
- package/src/labs/DataFilters.tsx +9 -0
- package/src/labs/DataTable.tsx +5 -9
- package/src/labs/FileUpload.tsx +301 -0
- package/src/labs/FileUploadIllustration.tsx +66 -0
- package/src/labs/FileUploadPreview.tsx +150 -0
- package/src/labs/index.ts +4 -2
- package/src/properties/odyssey-react-mui.properties +18 -0
- package/src/properties/ts/odyssey-react-mui.ts +1 -1
- package/src/theme/components.tsx +9 -0
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 {
|
|
20
|
+
import {
|
|
21
|
+
useOdysseyDesignTokens,
|
|
22
|
+
DesignTokens,
|
|
23
|
+
} from "./OdysseyDesignTokensContext";
|
|
20
24
|
import { useUniqueId } from "./useUniqueId";
|
|
21
|
-
|
|
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
|
-
<
|
|
77
|
-
component="fieldset"
|
|
93
|
+
<StyledFieldset
|
|
78
94
|
data-se={testId}
|
|
79
95
|
disabled={isDisabled}
|
|
80
|
-
name={name}
|
|
81
96
|
id={id}
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
</
|
|
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
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
</
|
|
148
|
-
<
|
|
171
|
+
</TitleContainer>
|
|
172
|
+
<div>{children}</div>
|
|
149
173
|
{formActions && (
|
|
150
|
-
<
|
|
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
|
-
</
|
|
176
|
+
</FormActionContainer>
|
|
161
177
|
)}
|
|
162
|
-
</
|
|
178
|
+
</StyledForm>
|
|
163
179
|
);
|
|
164
180
|
};
|
|
165
181
|
|
package/src/MenuButton.tsx
CHANGED
package/src/SearchField.tsx
CHANGED
|
@@ -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,
|
package/src/labs/DataFilters.tsx
CHANGED
|
@@ -118,6 +118,10 @@ export type DataFiltersProps = {
|
|
|
118
118
|
* the filter menu won't be shown.
|
|
119
119
|
*/
|
|
120
120
|
filters?: Array<DataFilter>;
|
|
121
|
+
/**
|
|
122
|
+
* If true, the filter and search will be disabled
|
|
123
|
+
*/
|
|
124
|
+
isDisabled?: boolean;
|
|
121
125
|
};
|
|
122
126
|
|
|
123
127
|
const DataFilters = ({
|
|
@@ -128,6 +132,7 @@ const DataFilters = ({
|
|
|
128
132
|
defaultSearchTerm = "",
|
|
129
133
|
additionalActions,
|
|
130
134
|
filters: filtersProp = [],
|
|
135
|
+
isDisabled,
|
|
131
136
|
}: DataFiltersProps) => {
|
|
132
137
|
const [filters, setFilters] = useState<DataFilter[]>(filtersProp);
|
|
133
138
|
const { t } = useTranslation();
|
|
@@ -311,6 +316,7 @@ const DataFilters = ({
|
|
|
311
316
|
aria-expanded={isFiltersMenuOpen ? "true" : undefined}
|
|
312
317
|
aria-haspopup="true"
|
|
313
318
|
ariaLabel={t("filters.filters.arialabel")}
|
|
319
|
+
isDisabled={isDisabled}
|
|
314
320
|
endIcon={<FilterIcon />}
|
|
315
321
|
onClick={(event) => {
|
|
316
322
|
setFiltersMenuAnchorElement(event.currentTarget);
|
|
@@ -406,6 +412,7 @@ const DataFilters = ({
|
|
|
406
412
|
),
|
|
407
413
|
[
|
|
408
414
|
isFiltersMenuOpen,
|
|
415
|
+
isDisabled,
|
|
409
416
|
filterPopoverCurrentFilter,
|
|
410
417
|
isFilterPopoverOpen,
|
|
411
418
|
filtersMenuAnchorElement,
|
|
@@ -659,6 +666,8 @@ const DataFilters = ({
|
|
|
659
666
|
<SearchField
|
|
660
667
|
value={searchValue}
|
|
661
668
|
label={t("filters.search.label")}
|
|
669
|
+
placeholder={t("filters.search.label")}
|
|
670
|
+
isDisabled={isDisabled}
|
|
662
671
|
onClear={() => {
|
|
663
672
|
setSearchValue("");
|
|
664
673
|
onChangeSearch("");
|
package/src/labs/DataTable.tsx
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import {
|
|
14
14
|
MRT_Updater,
|
|
15
|
-
|
|
15
|
+
MRT_RowVirtualizer,
|
|
16
16
|
MRT_RowData,
|
|
17
17
|
MRT_TableOptions,
|
|
18
18
|
useMaterialReactTable,
|
|
@@ -426,12 +426,9 @@ const DataTable = ({
|
|
|
426
426
|
);
|
|
427
427
|
|
|
428
428
|
const rowVirtualizerInstanceRef =
|
|
429
|
-
useRef<
|
|
429
|
+
useRef<MRT_RowVirtualizer<HTMLDivElement, HTMLTableRowElement>>(null);
|
|
430
430
|
|
|
431
|
-
const
|
|
432
|
-
table: TableType,
|
|
433
|
-
id: MRT_RowData["id"],
|
|
434
|
-
) => {
|
|
431
|
+
const setHoveredRow = (table: TableType, id: MRT_RowData["id"]) => {
|
|
435
432
|
if (id) {
|
|
436
433
|
const nextRow: MRT_RowData = table.getRow(id);
|
|
437
434
|
|
|
@@ -501,12 +498,12 @@ const DataTable = ({
|
|
|
501
498
|
|
|
502
499
|
if (isArrowDown || isArrowUp) {
|
|
503
500
|
const nextIndex = isArrowDown ? index + 1 : index - 1;
|
|
504
|
-
|
|
501
|
+
setHoveredRow(table, data[nextIndex]?.id);
|
|
505
502
|
}
|
|
506
503
|
} else {
|
|
507
504
|
if (isArrowDown || isArrowUp) {
|
|
508
505
|
const nextIndex = isArrowDown ? row.index + 1 : row.index - 1;
|
|
509
|
-
|
|
506
|
+
setHoveredRow(table, data[nextIndex]?.id);
|
|
510
507
|
}
|
|
511
508
|
}
|
|
512
509
|
} else {
|
|
@@ -605,7 +602,6 @@ const DataTable = ({
|
|
|
605
602
|
align: "right",
|
|
606
603
|
sx: {
|
|
607
604
|
width: "unset",
|
|
608
|
-
// TODO: Make the right padding here 16px (and possibly adapt it to the density padding)
|
|
609
605
|
},
|
|
610
606
|
},
|
|
611
607
|
},
|
|
@@ -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 };
|