@onewelcome/react-lib-components 1.8.3 → 1.9.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.
@@ -12,4 +12,5 @@ export declare const getValueByPath: (obj: {
12
12
  }, path: string) => any;
13
13
  /** Source: https://stackoverflow.com/a/42769683/5084110 */
14
14
  export declare const remToPx: (rem: number) => number;
15
+ export declare const isJsonString: (str: any) => boolean;
15
16
  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.8.3",
4
+ "version": "1.9.0",
5
5
  "license": "Apache-2.0",
6
6
  "author": "OneWelcome B.V.",
7
7
  "main": "dist/index.js",
@@ -53,7 +53,7 @@
53
53
  }
54
54
  ],
55
55
  "devDependencies": {
56
- "@babel/core": "^7.21.0",
56
+ "@babel/core": "^7.21.3",
57
57
  "@mdx-js/react": "^1.6.22",
58
58
  "@onewelcome/eslint-config-shared-codestyle": "^9.0.3",
59
59
  "@size-limit/preset-small-lib": "^8.2.4",
@@ -80,13 +80,13 @@
80
80
  "@types/react-dom": "^17.0.18",
81
81
  "@types/react-router": "^5.1.20",
82
82
  "@types/react-router-dom": "^5.3.3",
83
- "@typescript-eslint/eslint-plugin": "^5.53.0",
84
- "@typescript-eslint/parser": "^5.53.0",
83
+ "@typescript-eslint/eslint-plugin": "^5.56.0",
84
+ "@typescript-eslint/parser": "^5.56.0",
85
85
  "babel-loader": "^9.1.2",
86
- "chromatic": "^6.17.1",
86
+ "chromatic": "^6.17.2",
87
87
  "dts-cli": "^1.6.3",
88
- "eslint": "^8.34.0",
89
- "eslint-config-prettier": "^8.6.0",
88
+ "eslint": "^8.36.0",
89
+ "eslint-config-prettier": "^8.8.0",
90
90
  "eslint-plugin-cypress": "^2.12.1",
91
91
  "eslint-plugin-jest": "^27.2.1",
92
92
  "eslint-plugin-license-header": "^0.6.0",
@@ -95,19 +95,19 @@
95
95
  "husky": "^8.0.3",
96
96
  "identity-obj-proxy": "^3.0.0",
97
97
  "jest-junit": "^15.0.0",
98
- "lint-staged": "^13.1.2",
98
+ "lint-staged": "^13.2.0",
99
99
  "npm-run-all": "^4.1.5",
100
- "prettier": "^2.8.4",
100
+ "prettier": "^2.8.6",
101
101
  "react": "^17.0.2",
102
102
  "react-dom": "^17.0.2",
103
103
  "react-is": "^18.2.0",
104
- "react-router": "^6.6.2",
105
- "react-router-dom": "^6.8.2",
104
+ "react-router": "^6.9.0",
105
+ "react-router-dom": "^6.9.0",
106
106
  "rollup-plugin-cleanup": "^3.2.1",
107
107
  "rollup-plugin-styles": "^4.0.0",
108
- "sass": "^1.58.3",
109
- "size-limit": "^8.1.1",
110
- "style-loader": "^3.3.1",
108
+ "sass": "^1.60.0",
109
+ "size-limit": "^8.2.4",
110
+ "style-loader": "^3.3.2",
111
111
  "tslib": "^2.5.0",
112
112
  "typescript": "^4.9.5"
113
113
  }
@@ -18,4 +18,28 @@
18
18
 
19
19
  .button {
20
20
  @include mixins.buttonBase();
21
+ position: relative;
22
+
23
+ .content-hidden {
24
+ visibility: hidden;
25
+ display: flex;
26
+ }
27
+ }
28
+
29
+ .spinner {
30
+ position: absolute;
31
+ top: calc(50% - 0.75rem);
32
+ left: calc(50% - 0.75rem);
33
+ transform: translate(-50%, -50%);
34
+ animation: spin 1s infinite linear;
35
+
36
+ @keyframes spin {
37
+ 0% {
38
+ transform: rotate(0deg);
39
+ }
40
+
41
+ 100% {
42
+ transform: rotate(360deg);
43
+ }
44
+ }
21
45
  }
@@ -75,6 +75,18 @@ describe("Properties of the button", () => {
75
75
  expect(onClickHandler).toHaveBeenCalledTimes(0);
76
76
  });
77
77
 
78
+ it("when loading onClick function should not have been called", async () => {
79
+ const onClickHandler = jest.fn();
80
+ const { baseButton } = createBaseButton(defaultParams => ({
81
+ ...defaultParams,
82
+ loading: true,
83
+ onClick: onClickHandler
84
+ }));
85
+
86
+ await userEvent.click(baseButton);
87
+ expect(onClickHandler).toHaveBeenCalledTimes(0);
88
+ });
89
+
78
90
  it('should have the class "TESTING"', () => {
79
91
  const { baseButton } = createBaseButton(defaultParams => ({
80
92
  ...defaultParams,
@@ -14,21 +14,23 @@
14
14
  * limitations under the License.
15
15
  */
16
16
 
17
- import React, { ForwardRefRenderFunction, ComponentPropsWithRef } from "react";
17
+ import React, { ForwardRefRenderFunction, ComponentPropsWithRef, Fragment } from "react";
18
18
  import classes from "./BaseButton.module.scss";
19
+ import { Spinner } from "./Spinner";
19
20
 
20
21
  export interface Props extends ComponentPropsWithRef<"button"> {
21
22
  type?: "submit" | "button" | "reset";
22
23
  disabled?: boolean;
24
+ loading?: boolean;
23
25
  color?: "primary" | "secondary" | "tertiary" | "default";
24
26
  }
25
27
 
26
28
  const BaseButtonComponent: ForwardRefRenderFunction<HTMLButtonElement, Props> = (
27
- { children, type = "button", className, ...rest },
29
+ { children, type = "button", className, loading, disabled, ...rest },
28
30
  ref
29
31
  ) => {
30
32
  const validTypes = ["submit", "button", "reset"];
31
-
33
+ const isDisabled = disabled || loading;
32
34
  if (!validTypes.includes(type))
33
35
  throw new Error(
34
36
  `You have entered an invalid button type. Expected 'submit', 'button' or 'reset' got ${type}`
@@ -37,11 +39,21 @@ const BaseButtonComponent: ForwardRefRenderFunction<HTMLButtonElement, Props> =
37
39
  return (
38
40
  <button
39
41
  {...rest}
42
+ disabled={isDisabled}
40
43
  ref={ref}
41
44
  type={type}
42
- className={`${classes.button} ${className ? className : ""}`}
45
+ className={`${classes.button} ${loading ? classes.loading : ""} ${
46
+ className ? className : ""
47
+ }`}
43
48
  >
44
- {children}
49
+ {loading ? (
50
+ <Fragment>
51
+ <div className={classes["content-hidden"]}>{children}</div>
52
+ <Spinner className={classes["spinner"]} />
53
+ </Fragment>
54
+ ) : (
55
+ children
56
+ )}
45
57
  </button>
46
58
  );
47
59
  };
@@ -0,0 +1,33 @@
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 from "react";
18
+
19
+ export const Spinner: React.FC<React.SVGProps<SVGSVGElement>> = props => (
20
+ <svg
21
+ {...props}
22
+ width="24"
23
+ height="24"
24
+ viewBox="0 0 24 24"
25
+ fill="none"
26
+ xmlns="http://www.w3.org/2000/svg"
27
+ >
28
+ <path
29
+ d="M24 12C24 13.8937 23.5518 15.7606 22.6921 17.4479C21.8323 19.1352 20.5855 20.5951 19.0534 21.7082C17.5214 22.8213 15.7476 23.556 13.8772 23.8523C12.0068 24.1485 10.0928 23.9979 8.2918 23.4127C6.49076 22.8275 4.85378 21.8243 3.51472 20.4853C2.17565 19.1462 1.17251 17.5092 0.587322 15.7082C0.00212849 13.9072 -0.148504 11.9932 0.14774 10.1228C0.443984 8.25238 1.17869 6.47863 2.2918 4.94658L3.91307 6.1245C2.98585 7.4007 2.37384 8.87823 2.12707 10.4363C1.8803 11.9943 2.00577 13.5887 2.49324 15.0889C2.9807 16.5892 3.81632 17.9528 4.93176 19.0682C6.0472 20.1837 7.4108 21.0193 8.91107 21.5068C10.4113 21.9942 12.0057 22.1197 13.5637 21.8729C15.1218 21.6262 16.5993 21.0141 17.8755 20.0869C19.1517 19.1597 20.1903 17.9436 20.9065 16.5381C21.6227 15.1326 21.996 13.5775 21.996 12H24Z"
30
+ fill="#5D607E"
31
+ />
32
+ </svg>
33
+ );
@@ -45,7 +45,7 @@ Array [
45
45
  aria-controls="consent_menu_Paweł-menu"
46
46
  aria-expanded="false"
47
47
  aria-haspopup="true"
48
- class="button icon-button text default button-m"
48
+ class="button icon-button text default button-m"
49
49
  id="consent_menu_Paweł"
50
50
  tabindex="0"
51
51
  type="button"
@@ -108,7 +108,7 @@ Array [
108
108
  aria-controls="consent_menu_Michał-menu"
109
109
  aria-expanded="false"
110
110
  aria-haspopup="true"
111
- class="button icon-button text default button-m"
111
+ class="button icon-button text default button-m"
112
112
  id="consent_menu_Michał"
113
113
  tabindex="0"
114
114
  type="button"
@@ -171,7 +171,7 @@ Array [
171
171
  aria-controls="consent_menu_Daniel-menu"
172
172
  aria-expanded="false"
173
173
  aria-haspopup="true"
174
- class="button icon-button text default button-m"
174
+ class="button icon-button text default button-m"
175
175
  id="consent_menu_Daniel"
176
176
  tabindex="0"
177
177
  type="button"
@@ -234,7 +234,7 @@ Array [
234
234
  aria-controls="consent_menu_Jasha-menu"
235
235
  aria-expanded="false"
236
236
  aria-haspopup="true"
237
- class="button icon-button text default button-m"
237
+ class="button icon-button text default button-m"
238
238
  id="consent_menu_Jasha"
239
239
  tabindex="0"
240
240
  type="button"
@@ -39,8 +39,7 @@
39
39
  }
40
40
  &.error {
41
41
  span[data-icon-status],
42
- .file-upload-title,
43
- .file-selector-sub-text {
42
+ .file-upload-title {
44
43
  color: var(--error);
45
44
  }
46
45
  }
@@ -63,6 +62,14 @@
63
62
  }
64
63
  }
65
64
 
65
+ .file-selector-sub-text {
66
+ color: var(--greyed-out);
67
+ padding: 0 1.25rem;
68
+ &.error {
69
+ color: var(--error);
70
+ }
71
+ }
72
+
66
73
  .file-select {
67
74
  display: flex;
68
75
  align-items: center;
@@ -136,7 +136,7 @@ const FileUploadComponent: ForwardRefRenderFunction<HTMLInputElement, Props> = (
136
136
 
137
137
  let err = false;
138
138
  if (maxFileSize && file.size && file.size >= maxFileSize) {
139
- const mb = (file.size / (1024 * 1024)).toFixed(2);
139
+ const mb = (maxFileSize / (1024 * 1024)).toFixed(2);
140
140
  result.error =
141
141
  exceedingMaxSizeErrorText ||
142
142
  `The maximum allowed file size is ${mb}MB. Upload a smaller file.`;
@@ -188,40 +188,42 @@ const FileUploadComponent: ForwardRefRenderFunction<HTMLInputElement, Props> = (
188
188
 
189
189
  return (
190
190
  <div className={classes["file-upload-wrapper"]} {...wrapperProps}>
191
- <div
192
- className={dropzoneClassNames.join(" ")}
193
- onDragOver={e => !disabled && handleOnDragOver(e)}
194
- onDragLeave={e => !disabled && handleOnDragLeave(e)}
195
- onDrop={e => !disabled && handleOnDrop(e)}
196
- >
197
- <Typography variant="body-bold" className={classes["file-upload-title"]} ref={labelRef}>
198
- {title}
199
- </Typography>
200
- <div className={classes["file-select"]}>
201
- <Icon className={"drop-file-icon"} icon={Icons.FileUpload} />
202
- <Typography variant="body" className={"drag-and-drop-text"}>
203
- {dragAndDropText}
191
+ <div className={classes["dropzone-wrapper"]}>
192
+ <div
193
+ className={dropzoneClassNames.join(" ")}
194
+ onDragOver={e => !disabled && handleOnDragOver(e)}
195
+ onDragLeave={e => !disabled && handleOnDragLeave(e)}
196
+ onDrop={e => !disabled && handleOnDrop(e)}
197
+ >
198
+ <Typography variant="body-bold" className={classes["file-upload-title"]} ref={labelRef}>
199
+ {title}
204
200
  </Typography>
205
- <div className={classes["file-upload-btn"]}>
206
- <Button variant="outline" disabled={disabled}>
207
- {selectButtonText}
208
- <input
209
- className={classes["upload-input"]}
210
- {...rest}
211
- ref={ref}
212
- aria-labelledby={labeledBy}
213
- type="file"
214
- name={name}
215
- {...(multiple && { multiple: true })}
216
- disabled={disabled}
217
- accept={accept}
218
- onChange={onInputChange}
219
- spellCheck={rest.spellCheck || false}
220
- />
221
- </Button>
201
+ <div className={classes["file-select"]}>
202
+ <Icon className={"drop-file-icon"} icon={Icons.FileUpload} />
203
+ <Typography variant="body" className={"drag-and-drop-text"}>
204
+ {dragAndDropText}
205
+ </Typography>
206
+ <div className={classes["file-upload-btn"]}>
207
+ <Button variant="outline" disabled={disabled}>
208
+ {selectButtonText}
209
+ <input
210
+ className={classes["upload-input"]}
211
+ {...rest}
212
+ ref={ref}
213
+ aria-labelledby={labeledBy}
214
+ type="file"
215
+ name={name}
216
+ {...(multiple && { multiple: true })}
217
+ disabled={disabled}
218
+ accept={accept}
219
+ onChange={onInputChange}
220
+ spellCheck={rest.spellCheck || false}
221
+ />
222
+ </Button>
223
+ </div>
224
+ {!disabled && icon}
225
+ <span className={classes["outline"]}></span>
222
226
  </div>
223
- {!disabled && icon}
224
- <span className={classes["outline"]}></span>
225
227
  </div>
226
228
  {subText && (
227
229
  <Typography variant={"sub-text"} className={subTextClass.join(" ")}>
@@ -42,6 +42,7 @@ input {
42
42
  [data-prefix],
43
43
  [data-suffix] {
44
44
  transform: translateY(-0.125rem);
45
+ font-family: var(--font-family);
45
46
  }
46
47
 
47
48
  [data-prefix] + input {
@@ -19,7 +19,7 @@
19
19
  display: flex;
20
20
  justify-content: flex-end;
21
21
 
22
- & * + * {
22
+ & > * {
23
23
  margin-left: 2rem;
24
24
  }
25
25
  }
@@ -19,7 +19,7 @@
19
19
  * and make sure to add it to the shouldBeColorPicker array!
20
20
  */
21
21
 
22
- import React, { Fragment, HTMLAttributes, ReactChild, useEffect, useState } from "react";
22
+ import React, { HTMLAttributes, ReactChild, useEffect, useRef, useState } from "react";
23
23
 
24
24
  interface CSSProperties {
25
25
  colorFocus?: string;
@@ -193,23 +193,31 @@ export const BaseStyling = ({ children, properties = {} }: Props) => {
193
193
 
194
194
  /** We need a loading state, because otherwise you see the colors flash from the default to the possible overridden ones. */
195
195
  const [isLoading, setIsLoading] = useState(true);
196
+ const baseStylingWrapper = useRef(null);
196
197
 
197
198
  const setCSSProperties = (CSSPropertiesObject: CSSProperties) => {
198
199
  for (const [key, value] of Object.entries(CSSPropertiesObject)) {
199
200
  const formattedPropertyName = key.replace(/([A-Z])/g, val => `-${val.toLowerCase()}`);
200
- document.documentElement.style.setProperty(`--${formattedPropertyName}`, value);
201
+ (baseStylingWrapper.current! as HTMLElement).style.setProperty(
202
+ `--${formattedPropertyName}`,
203
+ value
204
+ );
201
205
  }
202
206
  };
203
207
 
204
208
  useEffect(() => {
205
- if (Object.keys(properties).length !== 0) {
209
+ if (Object.keys(properties).length !== 0 && baseStylingWrapper.current) {
206
210
  const mergedState = { ...defaultProperties, ...properties };
207
211
  setCSSProperties(mergedState);
208
- } else {
212
+ } else if (baseStylingWrapper.current) {
209
213
  setCSSProperties(defaultProperties);
210
214
  }
211
215
  setIsLoading(false);
212
- }, [properties]);
216
+ }, [properties, baseStylingWrapper.current]);
213
217
 
214
- return !isLoading ? <Fragment>{children}</Fragment> : null;
218
+ return (
219
+ <div className="basestyling-wrapper" ref={baseStylingWrapper}>
220
+ {!isLoading ? children : null}
221
+ </div>
222
+ );
215
223
  };
@@ -101,7 +101,7 @@ describe("it should perform upload", () => {
101
101
 
102
102
  it("should contain a file with status of error", async () => {
103
103
  const response = { code: 404, body: { message: "Error test" } };
104
- const mock = setupXhrEnvironment([404, DONE, response]);
104
+ const mock = setupXhrEnvironment([404, DONE, JSON.stringify(response)]);
105
105
  const files3 = [
106
106
  {
107
107
  name: "test3.txt",
@@ -115,7 +115,7 @@ describe("it should perform upload", () => {
115
115
 
116
116
  it("should contain a file with status of server error", async () => {
117
117
  const response = { code: 500, body: { message: "Error test" } };
118
- const mock = setupXhrEnvironment([500, DONE, response]);
118
+ const mock = setupXhrEnvironment([500, DONE, JSON.stringify(response)]);
119
119
  const files6 = [
120
120
  {
121
121
  name: "test6.txt",
@@ -16,7 +16,7 @@
16
16
 
17
17
  import { FileType } from "../Form/FileUpload/FileUpload";
18
18
  import { useEffect, useState } from "react";
19
- import { getValueByPath } from "../util/helper";
19
+ import { getValueByPath, isJsonString } from "../util/helper";
20
20
 
21
21
  export interface UploadResponseType {
22
22
  fileList: FileType[];
@@ -76,6 +76,7 @@ export const useUploadFile = (
76
76
  ) => {
77
77
  let fileStatus: FileType["status"] = undefined;
78
78
  let error = "";
79
+ const response = responseText && isJsonString(responseText) && JSON.parse(responseText);
79
80
  if (requestStatus >= 200 && requestStatus < 400) {
80
81
  fileStatus = "completed";
81
82
  } else if (requestStatus === 0) {
@@ -83,13 +84,15 @@ export const useUploadFile = (
83
84
  error =
84
85
  networkErrorText || "Network error. Check internet connection and retry uploading the file";
85
86
  } else if (requestStatus >= 400 && requestStatus < 500) {
86
- const response = responseText && JSON.parse(JSON.stringify(responseText));
87
87
  fileStatus = "error";
88
- error = responseErrorPath ? getValueByPath(response, responseErrorPath) : "Bad request";
88
+ error =
89
+ responseErrorPath && response ? getValueByPath(response, responseErrorPath) : "Bad request";
89
90
  } else if (requestStatus >= 500) {
90
- const response = responseText && JSON.parse(JSON.stringify(responseText));
91
91
  fileStatus = "error";
92
- error = responseErrorPath ? getValueByPath(response, responseErrorPath) : "Server Error";
92
+ error =
93
+ responseErrorPath && response
94
+ ? getValueByPath(response, responseErrorPath)
95
+ : "Server Error";
93
96
  }
94
97
  return { fileStatus, error };
95
98
  };
@@ -24,7 +24,8 @@ import {
24
24
  throttle,
25
25
  areArraysDifferent,
26
26
  getValueByPath,
27
- isEqual
27
+ isEqual,
28
+ isJsonString
28
29
  } from "./helper";
29
30
  import { render } from "@testing-library/react";
30
31
 
@@ -337,3 +338,17 @@ describe("pixel to rem function works", () => {
337
338
  expect(result).toBe(px);
338
339
  });
339
340
  });
341
+
342
+ describe("isJsonString should work", () => {
343
+ it("should return true when parameter is a valid JSON string", () => {
344
+ const param = JSON.stringify({ test: 1 });
345
+
346
+ expect(isJsonString(param)).toEqual(true);
347
+ });
348
+
349
+ it("should return false when parameter is a valid JSON string", () => {
350
+ const param = { test: 2 };
351
+
352
+ expect(isJsonString(param)).toEqual(false);
353
+ });
354
+ });
@@ -181,3 +181,12 @@ export const getValueByPath = (obj: { [key: string]: any }, path: string): any =
181
181
  export const remToPx = (rem: number): number => {
182
182
  return rem * parseFloat(getComputedStyle(document.documentElement).fontSize);
183
183
  };
184
+
185
+ export const isJsonString = (str: any) => {
186
+ try {
187
+ JSON.parse(str);
188
+ } catch (e) {
189
+ return false;
190
+ }
191
+ return true;
192
+ };