@onewelcome/react-lib-components 1.5.0 → 1.7.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/README.md +4 -4
- package/dist/Button/Button.d.ts +0 -1
- package/dist/DataGrid/datagrid.interfaces.d.ts +1 -0
- package/dist/Form/Checkbox/Checkbox.d.ts +1 -1
- package/dist/Form/FileUpload/FileItem/FileItem.d.ts +17 -0
- package/dist/Form/FileUpload/FileUpload.d.ts +26 -0
- package/dist/Form/FormHelperText/FormHelperText.d.ts +1 -1
- package/dist/Form/FormSelectorWrapper/FormSelectorWrapper.d.ts +1 -1
- package/dist/Form/Input/Input.d.ts +2 -2
- package/dist/Form/Radio/Radio.d.ts +1 -1
- package/dist/Form/Select/Select.d.ts +1 -1
- package/dist/Form/Textarea/Textarea.d.ts +1 -6
- package/dist/Form/Toggle/Toggle.d.ts +1 -1
- package/dist/Form/Wrapper/CheckboxWrapper/CheckboxWrapper.d.ts +1 -1
- package/dist/Form/Wrapper/InputWrapper/InputWrapper.d.ts +1 -1
- package/dist/Form/Wrapper/RadioWrapper/RadioWrapper.d.ts +1 -1
- package/dist/Form/Wrapper/SelectWrapper/SelectWrapper.d.ts +1 -1
- package/dist/Form/Wrapper/TextareaWrapper/TextareaWrapper.d.ts +1 -1
- package/dist/Form/form.interfaces.d.ts +1 -0
- package/dist/Icon/Icon.d.ts +4 -1
- package/dist/Link/Link.d.ts +1 -2
- package/dist/Notifications/Banner/Banner.d.ts +11 -0
- package/dist/ProgressBar/ProgressBar.d.ts +2 -1
- package/dist/Tabs/TabButton.d.ts +0 -1
- package/dist/_BaseStyling_/BaseStyling.d.ts +5 -0
- package/dist/hooks/useDetermineStatusIcon.d.ts +3 -0
- package/dist/hooks/useUploadFile.d.ts +22 -0
- package/dist/index.d.ts +1 -0
- package/dist/react-lib-components.cjs.development.js +431 -326
- package/dist/react-lib-components.cjs.development.js.map +1 -1
- package/dist/react-lib-components.cjs.production.min.js +1 -1
- package/dist/react-lib-components.cjs.production.min.js.map +1 -1
- package/dist/react-lib-components.esm.js +431 -327
- package/dist/react-lib-components.esm.js.map +1 -1
- package/dist/util/helper.d.ts +5 -0
- package/package.json +28 -25
- package/src/Button/BaseButton.module.scss +2 -2
- package/src/Button/Button.module.scss +4 -5
- package/src/Button/Button.tsx +0 -1
- package/src/Button/IconButton.module.scss +4 -5
- package/src/DataGrid/DataGrid.tsx +3 -2
- package/src/DataGrid/DataGridActions/DataGridActions.tsx +16 -9
- package/src/DataGrid/DataGridBody/DataGridCell.module.scss +2 -2
- package/src/DataGrid/DataGridHeader/DataGridHeader.test.tsx +8 -3
- package/src/DataGrid/DataGridHeader/DataGridHeader.tsx +3 -1
- package/src/DataGrid/datagrid.interfaces.ts +1 -0
- package/src/Form/FileUpload/FileItem/FileItem.modules.scss +75 -0
- package/src/Form/FileUpload/FileItem/FileItem.test.tsx +103 -0
- package/src/Form/FileUpload/FileItem/FileItem.tsx +141 -0
- package/src/Form/FileUpload/FileUpload.module.scss +106 -0
- package/src/Form/FileUpload/FileUpload.test.tsx +374 -0
- package/src/Form/FileUpload/FileUpload.tsx +251 -0
- package/src/Form/Input/Input.module.scss +36 -26
- package/src/Form/Input/Input.test.tsx +10 -0
- package/src/Form/Input/Input.tsx +7 -5
- package/src/Form/Select/Select.module.scss +9 -6
- package/src/Form/Select/Select.test.tsx +11 -0
- package/src/Form/Select/Select.tsx +5 -9
- package/src/Form/Select/SelectService.ts +2 -2
- package/src/Form/Textarea/Textarea.module.scss +21 -13
- package/src/Form/Textarea/Textarea.test.tsx +8 -0
- package/src/Form/Textarea/Textarea.tsx +6 -12
- package/src/Form/Toggle/Toggle.module.scss +3 -3
- package/src/Form/Wrapper/InputWrapper/InputWrapper.module.scss +7 -3
- package/src/Form/Wrapper/InputWrapper/InputWrapper.tsx +2 -0
- package/src/Form/Wrapper/SelectWrapper/SelectWrapper.tsx +12 -1
- package/src/Form/Wrapper/TextareaWrapper/TextareaWrapper.module.scss +15 -14
- package/src/Form/Wrapper/TextareaWrapper/TextareaWrapper.tsx +2 -1
- package/src/Form/Wrapper/Wrapper/Wrapper.module.scss +2 -2
- package/src/Form/form.interfaces.ts +1 -0
- package/src/Icon/Icon.module.scss +12 -0
- package/src/Icon/Icon.tsx +4 -1
- package/src/Link/Link.module.scss +5 -5
- package/src/Link/Link.tsx +14 -13
- package/src/Notifications/Banner/Banner.module.scss +76 -0
- package/src/Notifications/Banner/Banner.test.tsx +84 -0
- package/src/Notifications/Banner/Banner.tsx +78 -0
- package/src/Notifications/BaseModal/BaseModal.module.scss +2 -2
- package/src/Notifications/Snackbar/SnackbarContainer/SnackbarContainer.module.scss +2 -2
- package/src/Notifications/Snackbar/SnackbarItem/SnackbarItem.module.scss +4 -4
- package/src/Notifications/Snackbar/SnackbarItem/SnackbarItem.tsx +3 -2
- package/src/Popover/Popover.module.scss +2 -2
- package/src/ProgressBar/ProgressBar.module.scss +11 -9
- package/src/ProgressBar/ProgressBar.test.tsx +21 -0
- package/src/ProgressBar/ProgressBar.tsx +7 -2
- package/src/Skeleton/Skeleton.module.scss +2 -2
- package/src/Tabs/TabButton.tsx +1 -2
- package/src/Tabs/Tabs.module.scss +2 -2
- package/src/Tabs/Tabs.tsx +13 -10
- package/src/Tiles/Tile.module.scss +4 -4
- package/src/Tooltip/Tooltip.module.scss +3 -3
- package/src/Typography/Typography.module.scss +2 -2
- package/src/_BaseStyling_/BaseStyling.tsx +13 -3
- package/src/hooks/useDetermineStatusIcon.test.ts +28 -0
- package/src/hooks/useDetermineStatusIcon.tsx +35 -0
- package/src/hooks/useUploadFile.test.ts +211 -0
- package/src/hooks/useUploadFile.tsx +136 -0
- package/src/index.ts +1 -0
- package/src/mixins.module.scss +24 -5
- package/src/util/helper.test.tsx +156 -1
- package/src/util/helper.tsx +33 -0
package/dist/util/helper.d.ts
CHANGED
|
@@ -5,4 +5,9 @@ export declare const generateID: (length?: number, stringToWeaveIn?: string) =>
|
|
|
5
5
|
export declare const filterProps: (props: any, regexPattern: RegExp, returnFiltered?: boolean) => KeyValuePair;
|
|
6
6
|
export declare const debounce: (fn: (...args: unknown[]) => unknown, delay: number) => (...args: unknown[]) => void;
|
|
7
7
|
export declare const throttle: (fn: (...args: unknown[]) => unknown, delay: number) => () => void;
|
|
8
|
+
export declare const isEqual: (x: any, y: any) => boolean;
|
|
9
|
+
export declare const areArraysDifferent: (arr1: Record<string, any>[], arr2: Record<string, any>[], key: string) => boolean;
|
|
10
|
+
export declare const getValueByPath: (obj: {
|
|
11
|
+
[key: string]: any;
|
|
12
|
+
}, path: string) => any;
|
|
8
13
|
export {};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"homepage": "http://onewelcome.github.io/react-lib-components",
|
|
3
3
|
"name": "@onewelcome/react-lib-components",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.7.0",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "OneWelcome B.V.",
|
|
7
7
|
"main": "dist/index.js",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"analyze": "size-limit --why",
|
|
23
23
|
"build": "dts build",
|
|
24
24
|
"build-storybook": "build-storybook",
|
|
25
|
+
"chromatic": "chromatic --exit-zero-on-changes",
|
|
25
26
|
"dev": "npm-run-all -p start test:watch storybook",
|
|
26
27
|
"lint": "eslint '**/*.{ts,tsx}'",
|
|
27
28
|
"prepare": "husky install && dts build",
|
|
@@ -52,38 +53,39 @@
|
|
|
52
53
|
}
|
|
53
54
|
],
|
|
54
55
|
"devDependencies": {
|
|
55
|
-
"@babel/core": "^7.
|
|
56
|
+
"@babel/core": "^7.21.0",
|
|
56
57
|
"@mdx-js/react": "^1.6.22",
|
|
57
58
|
"@onewelcome/eslint-config-shared-codestyle": "^9.0.3",
|
|
58
|
-
"@size-limit/preset-small-lib": "^
|
|
59
|
-
"@storybook/addon-a11y": "^6.5.
|
|
60
|
-
"@storybook/addon-docs": "^6.5.
|
|
61
|
-
"@storybook/addon-essentials": "^6.5.
|
|
62
|
-
"@storybook/addon-links": "^6.5.
|
|
63
|
-
"@storybook/addons": "^6.5.
|
|
64
|
-
"@storybook/builder-webpack5": "^6.5.
|
|
65
|
-
"@storybook/manager-webpack5": "^6.5.
|
|
59
|
+
"@size-limit/preset-small-lib": "^8.2.4",
|
|
60
|
+
"@storybook/addon-a11y": "^6.5.16",
|
|
61
|
+
"@storybook/addon-docs": "^6.5.16",
|
|
62
|
+
"@storybook/addon-essentials": "^6.5.16",
|
|
63
|
+
"@storybook/addon-links": "^6.5.16",
|
|
64
|
+
"@storybook/addons": "^6.5.16",
|
|
65
|
+
"@storybook/builder-webpack5": "^6.5.16",
|
|
66
|
+
"@storybook/manager-webpack5": "^6.5.16",
|
|
66
67
|
"@storybook/preset-scss": "^1.0.3",
|
|
67
|
-
"@storybook/react": "^6.5.
|
|
68
|
-
"@storybook/theming": "^6.5.
|
|
69
|
-
"@testing-library/dom": "^8.
|
|
68
|
+
"@storybook/react": "^6.5.16",
|
|
69
|
+
"@storybook/theming": "^6.5.16",
|
|
70
|
+
"@testing-library/dom": "^8.20.0",
|
|
70
71
|
"@testing-library/jest-dom": "^5.16.5",
|
|
71
72
|
"@testing-library/react": "^12.1.5",
|
|
72
73
|
"@testing-library/react-hooks": "^8.0.1",
|
|
73
74
|
"@testing-library/user-event": "^13.5.0",
|
|
74
75
|
"@tsconfig/create-react-app": "^1.0.3",
|
|
75
|
-
"@tsconfig/recommended": "^1.0.
|
|
76
|
+
"@tsconfig/recommended": "^1.0.2",
|
|
76
77
|
"@types/color-convert": "^2.0.0",
|
|
77
78
|
"@types/mdx": "^2.0.3",
|
|
78
79
|
"@types/react": "^17.0.52",
|
|
79
80
|
"@types/react-dom": "^17.0.18",
|
|
80
81
|
"@types/react-router": "^5.1.20",
|
|
81
82
|
"@types/react-router-dom": "^5.3.3",
|
|
82
|
-
"@typescript-eslint/eslint-plugin": "^5.
|
|
83
|
-
"@typescript-eslint/parser": "^5.
|
|
84
|
-
"babel-loader": "^9.1.
|
|
83
|
+
"@typescript-eslint/eslint-plugin": "^5.53.0",
|
|
84
|
+
"@typescript-eslint/parser": "^5.53.0",
|
|
85
|
+
"babel-loader": "^9.1.2",
|
|
86
|
+
"chromatic": "^6.17.0",
|
|
85
87
|
"dts-cli": "^1.6.3",
|
|
86
|
-
"eslint": "^8.
|
|
88
|
+
"eslint": "^8.34.0",
|
|
87
89
|
"eslint-config-prettier": "^8.6.0",
|
|
88
90
|
"eslint-plugin-cypress": "^2.12.1",
|
|
89
91
|
"eslint-plugin-jest": "^27.2.1",
|
|
@@ -93,19 +95,20 @@
|
|
|
93
95
|
"husky": "^8.0.3",
|
|
94
96
|
"identity-obj-proxy": "^3.0.0",
|
|
95
97
|
"jest-junit": "^15.0.0",
|
|
96
|
-
"lint-staged": "^13.1.
|
|
98
|
+
"lint-staged": "^13.1.2",
|
|
97
99
|
"npm-run-all": "^4.1.5",
|
|
98
|
-
"prettier": "^2.8.
|
|
100
|
+
"prettier": "^2.8.4",
|
|
99
101
|
"react": "^17.0.2",
|
|
100
102
|
"react-dom": "^17.0.2",
|
|
101
103
|
"react-is": "^18.2.0",
|
|
102
104
|
"react-router": "^6.6.2",
|
|
103
|
-
"react-router-dom": "^6.
|
|
105
|
+
"react-router-dom": "^6.8.1",
|
|
104
106
|
"rollup-plugin-cleanup": "^3.2.1",
|
|
105
107
|
"rollup-plugin-styles": "^4.0.0",
|
|
106
|
-
"sass": "^1.
|
|
107
|
-
"size-limit": "^
|
|
108
|
-
"
|
|
109
|
-
"
|
|
108
|
+
"sass": "^1.58.3",
|
|
109
|
+
"size-limit": "^8.1.1",
|
|
110
|
+
"style-loader": "^3.3.1",
|
|
111
|
+
"tslib": "^2.5.0",
|
|
112
|
+
"typescript": "^4.9.5"
|
|
110
113
|
}
|
|
111
114
|
}
|
|
@@ -14,19 +14,18 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
@
|
|
18
|
-
@import "../mixins.module.scss";
|
|
17
|
+
@use "../mixins.module.scss";
|
|
19
18
|
|
|
20
19
|
.fill {
|
|
21
|
-
@include button("fill");
|
|
20
|
+
@include mixins.button("fill");
|
|
22
21
|
}
|
|
23
22
|
|
|
24
23
|
.outline {
|
|
25
|
-
@include button("outline");
|
|
24
|
+
@include mixins.button("outline");
|
|
26
25
|
}
|
|
27
26
|
|
|
28
27
|
.text {
|
|
29
|
-
@include button("text");
|
|
28
|
+
@include mixins.button("text");
|
|
30
29
|
}
|
|
31
30
|
|
|
32
31
|
.has-icon {
|
package/src/Button/Button.tsx
CHANGED
|
@@ -14,8 +14,7 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
@
|
|
18
|
-
@import "../mixins.module.scss";
|
|
17
|
+
@use "../mixins.module.scss";
|
|
19
18
|
|
|
20
19
|
.icon-button {
|
|
21
20
|
border: var(--button-border-width) var(--button-border-style) transparent;
|
|
@@ -57,14 +56,14 @@
|
|
|
57
56
|
}
|
|
58
57
|
|
|
59
58
|
&.fill {
|
|
60
|
-
@include button("fill", "icon");
|
|
59
|
+
@include mixins.button("fill", "icon");
|
|
61
60
|
}
|
|
62
61
|
|
|
63
62
|
&.text {
|
|
64
|
-
@include button("text", "icon");
|
|
63
|
+
@include mixins.button("text", "icon");
|
|
65
64
|
}
|
|
66
65
|
|
|
67
66
|
&.outline {
|
|
68
|
-
@include button("outline", "icon");
|
|
67
|
+
@include mixins.button("outline", "icon");
|
|
69
68
|
}
|
|
70
69
|
}
|
|
@@ -144,14 +144,15 @@ const DataGridInner = <T extends {}>(
|
|
|
144
144
|
spacing={styleWithSpacing}
|
|
145
145
|
/>
|
|
146
146
|
<DataGridBody
|
|
147
|
-
children={children}
|
|
148
147
|
data={data}
|
|
149
148
|
headers={internalHeaders}
|
|
150
149
|
isLoading={isLoading}
|
|
151
150
|
disableContextMenuColumn={disableContextMenuColumn}
|
|
152
151
|
emptyLabel={emptyLabel}
|
|
153
152
|
spacing={styleWithSpacing}
|
|
154
|
-
|
|
153
|
+
>
|
|
154
|
+
{children}
|
|
155
|
+
</DataGridBody>
|
|
155
156
|
</table>
|
|
156
157
|
</div>
|
|
157
158
|
{paginationProps && !isLoading && (
|
|
@@ -52,6 +52,7 @@ const DataGridActionsComponent: ForwardRefRenderFunction<HTMLDivElement, Props>
|
|
|
52
52
|
searchIconBtnProps = {},
|
|
53
53
|
headers,
|
|
54
54
|
onColumnToggled,
|
|
55
|
+
children,
|
|
55
56
|
...rest
|
|
56
57
|
}: Props,
|
|
57
58
|
ref
|
|
@@ -59,6 +60,9 @@ const DataGridActionsComponent: ForwardRefRenderFunction<HTMLDivElement, Props>
|
|
|
59
60
|
const isHidden = !(enableAddBtn || enableColumnsBtn || enableSearchBtn);
|
|
60
61
|
const [showColsPopover, setShowColsPopover] = useState(false);
|
|
61
62
|
const showColumnBtn = useRef<HTMLButtonElement>(null);
|
|
63
|
+
const { children: addBtnChildren, ...restAddBtnProps } = addBtnProps;
|
|
64
|
+
const { children: columnsBtnChildren, ...restColumnsBtnProps } = columnsBtnProps;
|
|
65
|
+
const { children: searchBtnChildren, ...restSearchBtnProps } = searchBtnProps;
|
|
62
66
|
|
|
63
67
|
return isHidden ? null : (
|
|
64
68
|
<div {...rest} ref={ref} className={`${classes["actions"]} ${className ?? ""}`}>
|
|
@@ -70,9 +74,10 @@ const DataGridActionsComponent: ForwardRefRenderFunction<HTMLDivElement, Props>
|
|
|
70
74
|
title="Add item"
|
|
71
75
|
type="button"
|
|
72
76
|
variant="outline"
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
77
|
+
{...restAddBtnProps}
|
|
78
|
+
>
|
|
79
|
+
{addBtnChildren ?? "Add item"}
|
|
80
|
+
</Button>
|
|
76
81
|
)}
|
|
77
82
|
</div>
|
|
78
83
|
<div className={classes["right-actions"]}>
|
|
@@ -82,12 +87,13 @@ const DataGridActionsComponent: ForwardRefRenderFunction<HTMLDivElement, Props>
|
|
|
82
87
|
startIcon={<Icon icon={Icons.Change} />}
|
|
83
88
|
title="Show/hide columns"
|
|
84
89
|
variant="text"
|
|
85
|
-
|
|
86
|
-
{...columnsBtnProps}
|
|
90
|
+
{...restColumnsBtnProps}
|
|
87
91
|
className={`${classes["desktop"]} ${columnsBtnProps?.className ?? ""}`}
|
|
88
92
|
ref={showColumnBtn}
|
|
89
93
|
onClick={() => setShowColsPopover(true)}
|
|
90
|
-
|
|
94
|
+
>
|
|
95
|
+
{columnsBtnChildren ?? "Columns"}
|
|
96
|
+
</Button>
|
|
91
97
|
<IconButton
|
|
92
98
|
title="Show/hide columns"
|
|
93
99
|
{...columnsBtnProps}
|
|
@@ -112,10 +118,11 @@ const DataGridActionsComponent: ForwardRefRenderFunction<HTMLDivElement, Props>
|
|
|
112
118
|
startIcon={<Icon icon={Icons.TableSearch} />}
|
|
113
119
|
title="Search"
|
|
114
120
|
variant="text"
|
|
115
|
-
|
|
116
|
-
{...searchBtnProps}
|
|
121
|
+
{...restSearchBtnProps}
|
|
117
122
|
className={`${classes["desktop"]} ${searchBtnProps?.className ?? ""}`}
|
|
118
|
-
|
|
123
|
+
>
|
|
124
|
+
{searchBtnChildren ?? "Search"}
|
|
125
|
+
</Button>
|
|
119
126
|
<IconButton
|
|
120
127
|
title="Search"
|
|
121
128
|
{...searchIconBtnProps}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
@
|
|
17
|
+
@use "../../mixins.module.scss";
|
|
18
18
|
|
|
19
19
|
.cell {
|
|
20
20
|
min-height: 3.5rem;
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
.loading {
|
|
32
|
-
@include skeletonLoading();
|
|
32
|
+
@include mixins.skeletonLoading();
|
|
33
33
|
border-radius: 0.5rem;
|
|
34
34
|
height: 1.25rem;
|
|
35
35
|
margin: 0.625rem 0;
|
|
@@ -21,8 +21,8 @@ import userEvent from "@testing-library/user-event";
|
|
|
21
21
|
|
|
22
22
|
const defaultParams: Props = {
|
|
23
23
|
headers: [
|
|
24
|
-
{ name: "firstName", headline: "First name" },
|
|
25
|
-
{ name: "lastName", headline: "Last name" }
|
|
24
|
+
{ name: "firstName", headline: "First name", align: "center" },
|
|
25
|
+
{ name: "lastName", headline: "Last name", align: "right" }
|
|
26
26
|
]
|
|
27
27
|
};
|
|
28
28
|
|
|
@@ -36,7 +36,6 @@ const createDataGridHeader = (params?: (defaultParams: Props) => Props) => {
|
|
|
36
36
|
container
|
|
37
37
|
});
|
|
38
38
|
const dataGridHeader = queries.getByTestId("dataGridHeader");
|
|
39
|
-
|
|
40
39
|
return {
|
|
41
40
|
...queries,
|
|
42
41
|
dataGridHeader
|
|
@@ -51,6 +50,12 @@ describe("DataGridHeader should render", () => {
|
|
|
51
50
|
expect(getAllByRole("columnheader")).toHaveLength(2);
|
|
52
51
|
expect(getByRole("cell")).toBeDefined(); //context-menu empty cell
|
|
53
52
|
|
|
53
|
+
const headerCells = dataGridHeader.querySelectorAll("th");
|
|
54
|
+
|
|
55
|
+
expect(headerCells).toHaveLength(2);
|
|
56
|
+
expect(headerCells[0]).toHaveStyle({ textAlign: "center" });
|
|
57
|
+
expect(headerCells[1]).toHaveStyle({ textAlign: "right" });
|
|
58
|
+
|
|
54
59
|
expect(dataGridHeader.querySelectorAll("[data-icon]")).toHaveLength(0);
|
|
55
60
|
});
|
|
56
61
|
|
|
@@ -77,7 +77,9 @@ const DataGridHeaderComponent: ForwardRefRenderFunction<HTMLTableSectionElement,
|
|
|
77
77
|
return null;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
let headerStyle: React.CSSProperties = {
|
|
80
|
+
let headerStyle: React.CSSProperties = {
|
|
81
|
+
textAlign: header.align || "left"
|
|
82
|
+
};
|
|
81
83
|
|
|
82
84
|
if (index === 0) {
|
|
83
85
|
headerStyle.paddingLeft = spacing?.paddingLeft;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright 2022 OneWelcome B.V.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
@import "../../../mixins.module.scss";
|
|
18
|
+
|
|
19
|
+
.file-item-wrapper {
|
|
20
|
+
padding: 0.5rem 1.25rem;
|
|
21
|
+
border-radius: var(--input-border-radius);
|
|
22
|
+
background-color: var(--input-background-color);
|
|
23
|
+
@include transition(all, 0.2s, ease-in-out);
|
|
24
|
+
|
|
25
|
+
[class*="icon"] {
|
|
26
|
+
font-size: 1.25rem;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.progress-bar {
|
|
30
|
+
background-color: var(--light-pink);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
span[class*="bar--"] {
|
|
34
|
+
border-radius: var(--input-border-radius);
|
|
35
|
+
height: 1rem;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.file-name {
|
|
39
|
+
margin: 0;
|
|
40
|
+
display: flex;
|
|
41
|
+
align-items: center;
|
|
42
|
+
.friendly-name {
|
|
43
|
+
overflow: hidden;
|
|
44
|
+
text-overflow: ellipsis;
|
|
45
|
+
white-space: nowrap;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.action-icon {
|
|
50
|
+
margin-left: auto;
|
|
51
|
+
color: var(--color-primary);
|
|
52
|
+
cursor: pointer;
|
|
53
|
+
padding-left: 1.25rem;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.file-icon {
|
|
57
|
+
margin-right: 0.5rem;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.file-subtitle,
|
|
61
|
+
.progress-bar {
|
|
62
|
+
display: block;
|
|
63
|
+
margin: 0.3125rem 1.75rem;
|
|
64
|
+
padding: 0;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.error,
|
|
68
|
+
.retry {
|
|
69
|
+
color: var(--error);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.readonly {
|
|
73
|
+
color: var(--greyed-out);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { FILE_ACTION, FileItem, Props } from "./FileItem";
|
|
3
|
+
import { render } from "@testing-library/react";
|
|
4
|
+
import user from "@testing-library/user-event";
|
|
5
|
+
|
|
6
|
+
const defaultParams: Props = {
|
|
7
|
+
name: "Test.txt"
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const createFileItem = (params?: (defaultParams: Props) => Props) => {
|
|
11
|
+
let parameters: Props = defaultParams;
|
|
12
|
+
if (params) {
|
|
13
|
+
parameters = params(defaultParams);
|
|
14
|
+
}
|
|
15
|
+
const queries = render(<FileItem {...parameters} />);
|
|
16
|
+
const component = queries.getByLabelText(`${parameters.name}-wrapper`);
|
|
17
|
+
const title = component.querySelector(".file-name");
|
|
18
|
+
const fileIcon = component.querySelector(".file-icon");
|
|
19
|
+
const actionIcon = component.querySelector(".action-icon");
|
|
20
|
+
const errorSubtitle = component.querySelector(".file-subtitle");
|
|
21
|
+
const progressBar = component.querySelector(".progress-bar");
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
...queries,
|
|
25
|
+
component,
|
|
26
|
+
title,
|
|
27
|
+
fileIcon,
|
|
28
|
+
actionIcon,
|
|
29
|
+
errorSubtitle,
|
|
30
|
+
progressBar
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
describe("component should render", () => {
|
|
35
|
+
it("renders without crashing", () => {
|
|
36
|
+
const { component } = createFileItem();
|
|
37
|
+
|
|
38
|
+
expect(component).toBeDefined();
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe("component should change display the correct style and elements according to the status", () => {
|
|
43
|
+
it("should show the correct details for completed", () => {
|
|
44
|
+
const { actionIcon, title } = createFileItem(defaultParams => ({
|
|
45
|
+
...defaultParams,
|
|
46
|
+
status: "completed"
|
|
47
|
+
}));
|
|
48
|
+
expect(title).toHaveClass("completed");
|
|
49
|
+
expect(actionIcon).toHaveAttribute("title", FILE_ACTION.DELETE);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("should show the correct details for uploading", () => {
|
|
53
|
+
const { actionIcon, title, progressBar } = createFileItem(defaultParams => ({
|
|
54
|
+
...defaultParams,
|
|
55
|
+
status: "uploading"
|
|
56
|
+
}));
|
|
57
|
+
expect(title).toHaveClass("uploading");
|
|
58
|
+
expect(actionIcon).toHaveAttribute("title", FILE_ACTION.ABORT);
|
|
59
|
+
expect(progressBar).toBeDefined();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("should show the correct details for readonly", () => {
|
|
63
|
+
const { actionIcon, title } = createFileItem(defaultParams => ({
|
|
64
|
+
...defaultParams,
|
|
65
|
+
status: "readonly"
|
|
66
|
+
}));
|
|
67
|
+
expect(title).toHaveClass("readonly");
|
|
68
|
+
expect(actionIcon).toBeNull();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("should show the correct details for error", () => {
|
|
72
|
+
const { actionIcon, title, errorSubtitle } = createFileItem(defaultParams => ({
|
|
73
|
+
...defaultParams,
|
|
74
|
+
status: "error"
|
|
75
|
+
}));
|
|
76
|
+
expect(title).toHaveClass("error");
|
|
77
|
+
expect(actionIcon).toHaveAttribute("title", FILE_ACTION.REMOVE);
|
|
78
|
+
expect(errorSubtitle).toBeDefined();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("should show the correct details for retry", () => {
|
|
82
|
+
const { actionIcon, title } = createFileItem(defaultParams => ({
|
|
83
|
+
...defaultParams,
|
|
84
|
+
status: "retry"
|
|
85
|
+
}));
|
|
86
|
+
expect(title).toHaveClass("retry");
|
|
87
|
+
expect(actionIcon).toHaveAttribute("title", FILE_ACTION.RETRY);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe("component should transmit the correct message upwards when a file action icon is clicked", () => {
|
|
92
|
+
it("should call retry action", () => {
|
|
93
|
+
const onRequestedFileAction = jest.fn();
|
|
94
|
+
const { actionIcon } = createFileItem(defaultParams => ({
|
|
95
|
+
...defaultParams,
|
|
96
|
+
status: "retry",
|
|
97
|
+
onRequestedFileAction
|
|
98
|
+
}));
|
|
99
|
+
|
|
100
|
+
user.click(actionIcon as Element);
|
|
101
|
+
expect(onRequestedFileAction).toHaveBeenNthCalledWith(1, FILE_ACTION.RETRY, defaultParams.name);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2022 OneWelcome B.V.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import React, { ComponentPropsWithRef, ForwardRefRenderFunction } from "react";
|
|
18
|
+
import classes from "./FileItem.modules.scss";
|
|
19
|
+
import { Typography } from "../../../Typography/Typography";
|
|
20
|
+
import { Icon, Icons } from "../../../Icon/Icon";
|
|
21
|
+
import { ProgressBar } from "../../../ProgressBar/ProgressBar";
|
|
22
|
+
import { FileType } from "../FileUpload";
|
|
23
|
+
|
|
24
|
+
export type UploadProgress = "uploading" | "completed" | "error" | "readonly" | "retry";
|
|
25
|
+
|
|
26
|
+
export interface Props extends ComponentPropsWithRef<"div"> {
|
|
27
|
+
name: string;
|
|
28
|
+
status?: UploadProgress;
|
|
29
|
+
progress?: number;
|
|
30
|
+
error?: string;
|
|
31
|
+
onRequestedFileAction?: (action: FILE_ACTION, name: FileType["name"]) => void;
|
|
32
|
+
}
|
|
33
|
+
interface FileItemIcons {
|
|
34
|
+
fileIcon: Icons;
|
|
35
|
+
actionIcon?: { type: Icons; action: FILE_ACTION };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export enum FILE_ACTION {
|
|
39
|
+
DELETE = "delete",
|
|
40
|
+
REMOVE = "remove",
|
|
41
|
+
ABORT = "abort",
|
|
42
|
+
RETRY = "retry"
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const FileItemComponent: ForwardRefRenderFunction<HTMLDivElement, Props> = (
|
|
46
|
+
{ name, status, error, progress, onRequestedFileAction }: Props,
|
|
47
|
+
ref
|
|
48
|
+
) => {
|
|
49
|
+
const determineIcons = (status?: UploadProgress): FileItemIcons => {
|
|
50
|
+
switch (status) {
|
|
51
|
+
case "completed":
|
|
52
|
+
return {
|
|
53
|
+
fileIcon: Icons.FileOutline,
|
|
54
|
+
actionIcon: {
|
|
55
|
+
type: Icons.Trash,
|
|
56
|
+
action: FILE_ACTION.DELETE
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
case "error":
|
|
60
|
+
return {
|
|
61
|
+
fileIcon: Icons.InfoCircle,
|
|
62
|
+
actionIcon: {
|
|
63
|
+
type: Icons.Times,
|
|
64
|
+
action: FILE_ACTION.REMOVE
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
case "uploading":
|
|
68
|
+
return {
|
|
69
|
+
fileIcon: Icons.FileUpload,
|
|
70
|
+
actionIcon: {
|
|
71
|
+
type: Icons.Times,
|
|
72
|
+
action: FILE_ACTION.ABORT
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
case "retry":
|
|
76
|
+
return {
|
|
77
|
+
fileIcon: Icons.InfoCircle,
|
|
78
|
+
actionIcon: {
|
|
79
|
+
type: Icons.Refresh,
|
|
80
|
+
action: FILE_ACTION.RETRY
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
case "readonly":
|
|
84
|
+
default:
|
|
85
|
+
return { fileIcon: Icons.FileOutline };
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const icons = determineIcons(status);
|
|
90
|
+
|
|
91
|
+
const getFriendlyNameAndExtension = (name: string) => {
|
|
92
|
+
const index = name.indexOf(".");
|
|
93
|
+
return { friendlyName: name.slice(0, index), extension: name.slice(index + 1) };
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const { friendlyName, extension } = getFriendlyNameAndExtension(name);
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<div ref={ref} className={classes["file-item-wrapper"]} aria-label={`${name}-wrapper`}>
|
|
100
|
+
<Typography
|
|
101
|
+
variant={"body"}
|
|
102
|
+
title={name}
|
|
103
|
+
className={`${classes["file-name"]} ${status ? classes[status] : ""}`}
|
|
104
|
+
>
|
|
105
|
+
<Icon icon={icons.fileIcon} className={classes["file-icon"]} />
|
|
106
|
+
<span className={classes["friendly-name"]}>{friendlyName}</span>.<span>{extension}</span>
|
|
107
|
+
{icons.actionIcon && (
|
|
108
|
+
<Icon
|
|
109
|
+
title={icons.actionIcon.action}
|
|
110
|
+
icon={icons.actionIcon.type}
|
|
111
|
+
className={classes["action-icon"]}
|
|
112
|
+
onClick={() =>
|
|
113
|
+
icons.actionIcon &&
|
|
114
|
+
onRequestedFileAction &&
|
|
115
|
+
onRequestedFileAction(icons.actionIcon.action, name)
|
|
116
|
+
}
|
|
117
|
+
/>
|
|
118
|
+
)}
|
|
119
|
+
</Typography>
|
|
120
|
+
{error && (
|
|
121
|
+
<Typography
|
|
122
|
+
variant={"sub-text"}
|
|
123
|
+
className={`${classes["file-subtitle"]} ${status ? classes[status] : ""}`}
|
|
124
|
+
>
|
|
125
|
+
{error}
|
|
126
|
+
</Typography>
|
|
127
|
+
)}
|
|
128
|
+
{status === "uploading" ? (
|
|
129
|
+
<ProgressBar
|
|
130
|
+
className={classes["progress-bar"]}
|
|
131
|
+
placeholderText={""}
|
|
132
|
+
completed={progress}
|
|
133
|
+
/>
|
|
134
|
+
) : (
|
|
135
|
+
""
|
|
136
|
+
)}
|
|
137
|
+
</div>
|
|
138
|
+
);
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
export const FileItem = React.forwardRef(FileItemComponent);
|