@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.
Files changed (101) hide show
  1. package/README.md +4 -4
  2. package/dist/Button/Button.d.ts +0 -1
  3. package/dist/DataGrid/datagrid.interfaces.d.ts +1 -0
  4. package/dist/Form/Checkbox/Checkbox.d.ts +1 -1
  5. package/dist/Form/FileUpload/FileItem/FileItem.d.ts +17 -0
  6. package/dist/Form/FileUpload/FileUpload.d.ts +26 -0
  7. package/dist/Form/FormHelperText/FormHelperText.d.ts +1 -1
  8. package/dist/Form/FormSelectorWrapper/FormSelectorWrapper.d.ts +1 -1
  9. package/dist/Form/Input/Input.d.ts +2 -2
  10. package/dist/Form/Radio/Radio.d.ts +1 -1
  11. package/dist/Form/Select/Select.d.ts +1 -1
  12. package/dist/Form/Textarea/Textarea.d.ts +1 -6
  13. package/dist/Form/Toggle/Toggle.d.ts +1 -1
  14. package/dist/Form/Wrapper/CheckboxWrapper/CheckboxWrapper.d.ts +1 -1
  15. package/dist/Form/Wrapper/InputWrapper/InputWrapper.d.ts +1 -1
  16. package/dist/Form/Wrapper/RadioWrapper/RadioWrapper.d.ts +1 -1
  17. package/dist/Form/Wrapper/SelectWrapper/SelectWrapper.d.ts +1 -1
  18. package/dist/Form/Wrapper/TextareaWrapper/TextareaWrapper.d.ts +1 -1
  19. package/dist/Form/form.interfaces.d.ts +1 -0
  20. package/dist/Icon/Icon.d.ts +4 -1
  21. package/dist/Link/Link.d.ts +1 -2
  22. package/dist/Notifications/Banner/Banner.d.ts +11 -0
  23. package/dist/ProgressBar/ProgressBar.d.ts +2 -1
  24. package/dist/Tabs/TabButton.d.ts +0 -1
  25. package/dist/_BaseStyling_/BaseStyling.d.ts +5 -0
  26. package/dist/hooks/useDetermineStatusIcon.d.ts +3 -0
  27. package/dist/hooks/useUploadFile.d.ts +22 -0
  28. package/dist/index.d.ts +1 -0
  29. package/dist/react-lib-components.cjs.development.js +431 -326
  30. package/dist/react-lib-components.cjs.development.js.map +1 -1
  31. package/dist/react-lib-components.cjs.production.min.js +1 -1
  32. package/dist/react-lib-components.cjs.production.min.js.map +1 -1
  33. package/dist/react-lib-components.esm.js +431 -327
  34. package/dist/react-lib-components.esm.js.map +1 -1
  35. package/dist/util/helper.d.ts +5 -0
  36. package/package.json +28 -25
  37. package/src/Button/BaseButton.module.scss +2 -2
  38. package/src/Button/Button.module.scss +4 -5
  39. package/src/Button/Button.tsx +0 -1
  40. package/src/Button/IconButton.module.scss +4 -5
  41. package/src/DataGrid/DataGrid.tsx +3 -2
  42. package/src/DataGrid/DataGridActions/DataGridActions.tsx +16 -9
  43. package/src/DataGrid/DataGridBody/DataGridCell.module.scss +2 -2
  44. package/src/DataGrid/DataGridHeader/DataGridHeader.test.tsx +8 -3
  45. package/src/DataGrid/DataGridHeader/DataGridHeader.tsx +3 -1
  46. package/src/DataGrid/datagrid.interfaces.ts +1 -0
  47. package/src/Form/FileUpload/FileItem/FileItem.modules.scss +75 -0
  48. package/src/Form/FileUpload/FileItem/FileItem.test.tsx +103 -0
  49. package/src/Form/FileUpload/FileItem/FileItem.tsx +141 -0
  50. package/src/Form/FileUpload/FileUpload.module.scss +106 -0
  51. package/src/Form/FileUpload/FileUpload.test.tsx +374 -0
  52. package/src/Form/FileUpload/FileUpload.tsx +251 -0
  53. package/src/Form/Input/Input.module.scss +36 -26
  54. package/src/Form/Input/Input.test.tsx +10 -0
  55. package/src/Form/Input/Input.tsx +7 -5
  56. package/src/Form/Select/Select.module.scss +9 -6
  57. package/src/Form/Select/Select.test.tsx +11 -0
  58. package/src/Form/Select/Select.tsx +5 -9
  59. package/src/Form/Select/SelectService.ts +2 -2
  60. package/src/Form/Textarea/Textarea.module.scss +21 -13
  61. package/src/Form/Textarea/Textarea.test.tsx +8 -0
  62. package/src/Form/Textarea/Textarea.tsx +6 -12
  63. package/src/Form/Toggle/Toggle.module.scss +3 -3
  64. package/src/Form/Wrapper/InputWrapper/InputWrapper.module.scss +7 -3
  65. package/src/Form/Wrapper/InputWrapper/InputWrapper.tsx +2 -0
  66. package/src/Form/Wrapper/SelectWrapper/SelectWrapper.tsx +12 -1
  67. package/src/Form/Wrapper/TextareaWrapper/TextareaWrapper.module.scss +15 -14
  68. package/src/Form/Wrapper/TextareaWrapper/TextareaWrapper.tsx +2 -1
  69. package/src/Form/Wrapper/Wrapper/Wrapper.module.scss +2 -2
  70. package/src/Form/form.interfaces.ts +1 -0
  71. package/src/Icon/Icon.module.scss +12 -0
  72. package/src/Icon/Icon.tsx +4 -1
  73. package/src/Link/Link.module.scss +5 -5
  74. package/src/Link/Link.tsx +14 -13
  75. package/src/Notifications/Banner/Banner.module.scss +76 -0
  76. package/src/Notifications/Banner/Banner.test.tsx +84 -0
  77. package/src/Notifications/Banner/Banner.tsx +78 -0
  78. package/src/Notifications/BaseModal/BaseModal.module.scss +2 -2
  79. package/src/Notifications/Snackbar/SnackbarContainer/SnackbarContainer.module.scss +2 -2
  80. package/src/Notifications/Snackbar/SnackbarItem/SnackbarItem.module.scss +4 -4
  81. package/src/Notifications/Snackbar/SnackbarItem/SnackbarItem.tsx +3 -2
  82. package/src/Popover/Popover.module.scss +2 -2
  83. package/src/ProgressBar/ProgressBar.module.scss +11 -9
  84. package/src/ProgressBar/ProgressBar.test.tsx +21 -0
  85. package/src/ProgressBar/ProgressBar.tsx +7 -2
  86. package/src/Skeleton/Skeleton.module.scss +2 -2
  87. package/src/Tabs/TabButton.tsx +1 -2
  88. package/src/Tabs/Tabs.module.scss +2 -2
  89. package/src/Tabs/Tabs.tsx +13 -10
  90. package/src/Tiles/Tile.module.scss +4 -4
  91. package/src/Tooltip/Tooltip.module.scss +3 -3
  92. package/src/Typography/Typography.module.scss +2 -2
  93. package/src/_BaseStyling_/BaseStyling.tsx +13 -3
  94. package/src/hooks/useDetermineStatusIcon.test.ts +28 -0
  95. package/src/hooks/useDetermineStatusIcon.tsx +35 -0
  96. package/src/hooks/useUploadFile.test.ts +211 -0
  97. package/src/hooks/useUploadFile.tsx +136 -0
  98. package/src/index.ts +1 -0
  99. package/src/mixins.module.scss +24 -5
  100. package/src/util/helper.test.tsx +156 -1
  101. package/src/util/helper.tsx +33 -0
@@ -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.5.0",
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.20.12",
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": "^7.0.8",
59
- "@storybook/addon-a11y": "^6.5.15",
60
- "@storybook/addon-docs": "^6.5.15",
61
- "@storybook/addon-essentials": "^6.5.15",
62
- "@storybook/addon-links": "^6.5.15",
63
- "@storybook/addons": "^6.5.15",
64
- "@storybook/builder-webpack5": "^6.5.15",
65
- "@storybook/manager-webpack5": "^6.5.15",
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.15",
68
- "@storybook/theming": "^6.5.15",
69
- "@testing-library/dom": "^8.19.1",
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.1",
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.48.1",
83
- "@typescript-eslint/parser": "^5.48.1",
84
- "babel-loader": "^9.1.0",
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.31.0",
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.0",
98
+ "lint-staged": "^13.1.2",
97
99
  "npm-run-all": "^4.1.5",
98
- "prettier": "^2.8.2",
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.6.2",
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.57.1",
107
- "size-limit": "^7.0.8",
108
- "tslib": "^2.4.1",
109
- "typescript": "^4.9.4"
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,8 +14,8 @@
14
14
  * limitations under the License.
15
15
  */
16
16
 
17
- @import "../mixins.module.scss";
17
+ @use "../mixins.module.scss";
18
18
 
19
19
  .button {
20
- @include buttonBase();
20
+ @include mixins.buttonBase();
21
21
  }
@@ -14,19 +14,18 @@
14
14
  * limitations under the License.
15
15
  */
16
16
 
17
- @import "../readyclasses.module.scss";
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 {
@@ -21,7 +21,6 @@ import classes from "./Button.module.scss";
21
21
  export interface Props extends BaseButtonProps {
22
22
  startIcon?: React.ReactNode | false;
23
23
  endIcon?: React.ReactNode | false;
24
- children?: React.ReactNode;
25
24
  variant?: "text" | "fill" | "outline";
26
25
  }
27
26
 
@@ -14,8 +14,7 @@
14
14
  * limitations under the License.
15
15
  */
16
16
 
17
- @import "../readyclasses.module.scss";
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
- children="Add item"
74
- {...addBtnProps}
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
- children="Columns"
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
- children="Search"
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
- @import "../../mixins.module.scss";
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;
@@ -27,4 +27,5 @@ export interface HeaderCell {
27
27
  headline: string;
28
28
  disableSorting?: boolean;
29
29
  hidden?: boolean;
30
+ align?: "left" | "right" | "center";
30
31
  }
@@ -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);