@rabbitio/ui-kit 1.0.0-beta.42 → 1.0.0-beta.45

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 (67) hide show
  1. package/.gitlab-ci.yml +29 -0
  2. package/.husky/commit-msg +8 -0
  3. package/.husky/pre-push +1 -0
  4. package/README.md +13 -4
  5. package/dist/index.cjs +1545 -148
  6. package/dist/index.cjs.map +1 -1
  7. package/dist/index.css +23630 -0
  8. package/dist/index.css.map +1 -1
  9. package/dist/index.modern.js +1318 -103
  10. package/dist/index.modern.js.map +1 -1
  11. package/dist/index.module.js +1534 -149
  12. package/dist/index.module.js.map +1 -1
  13. package/dist/index.umd.js +1544 -152
  14. package/dist/index.umd.js.map +1 -1
  15. package/package.json +16 -3
  16. package/src/assets/image/icons/arrow-tosca.svg +3 -0
  17. package/src/assets/image/icons/arrow-white.svg +14 -0
  18. package/src/assets/image/icons/failed-validation-icon.svg +15 -0
  19. package/src/assets/image/icons/successful-validation-icon.svg +10 -0
  20. package/src/common/amountUtils.js +4 -2
  21. package/src/common/tests/integration/external-apis/ipAddressProviders/getClientIpAddress.test.js +14 -0
  22. package/src/common/utils/cache.js +4 -4
  23. package/src/components/atoms/BackgroundTitle/BackgroundTitle.jsx +44 -0
  24. package/src/components/atoms/BackgroundTitle/background-title.module.scss +52 -0
  25. package/src/components/atoms/Validation/Validation.jsx +130 -0
  26. package/src/components/atoms/Validation/validation.module.scss +15 -0
  27. package/src/components/atoms/buttons/Close/Close.jsx +64 -0
  28. package/src/components/atoms/buttons/Close/close.module.scss +75 -0
  29. package/src/components/atoms/buttons/LinkButton/LinkButton.jsx +121 -0
  30. package/src/components/atoms/buttons/LinkButton/link-button.module.scss +45 -0
  31. package/src/components/organisms/Dialog/Dialog.jsx +515 -0
  32. package/src/components/organisms/Dialog/DialogButtons/DialogButtons.jsx +122 -0
  33. package/src/components/organisms/Dialog/DialogButtons/dialog-buttons.module.scss +25 -0
  34. package/src/components/organisms/Dialog/DialogStep/DialogStep.jsx +664 -0
  35. package/src/components/organisms/Dialog/DialogStep/dialog-step.module.scss +362 -0
  36. package/src/components/organisms/Dialog/dialog.module.scss +223 -0
  37. package/src/components/tests/utils/inputValueProviders/provideFormatOfFloatValueByInputString.test.js +139 -0
  38. package/src/components/tests/utils/urlQueryUtils/getQueryParameterValues.test.js +71 -0
  39. package/src/components/tests/utils/urlQueryUtils/saveQueryParameterAndValues.test.js +144 -0
  40. package/src/components/utils/inputValueProviders.js +58 -0
  41. package/src/constants/organisms/dialog/DialogStep/dialogStep.js +1 -0
  42. package/src/constants/organisms/dialog/dialog.js +29 -0
  43. package/src/index.js +11 -0
  44. package/src/robustExteranlApiCallerService/robustExternalAPICallerService.js +3 -1
  45. package/src/robustExteranlApiCallerService/tests/robustExternalAPICallerService/robustExternalAPICallerService/callExternalAPI/_performCallAttempt.test.js +787 -0
  46. package/src/robustExteranlApiCallerService/tests/robustExternalAPICallerService/robustExternalAPICallerService/callExternalAPI/callExternalAPI.test.js +745 -0
  47. package/src/robustExteranlApiCallerService/tests/robustExternalAPICallerService/robustExternalAPICallerService/constructor.test.js +31 -0
  48. package/src/swaps-lib/external-apis/swapProvider.js +17 -4
  49. package/src/swaps-lib/external-apis/swapspaceSwapProvider.js +91 -30
  50. package/src/swaps-lib/models/baseSwapCreationInfo.js +4 -1
  51. package/src/swaps-lib/models/existingSwap.js +3 -0
  52. package/src/swaps-lib/models/existingSwapWithFiatData.js +4 -0
  53. package/src/swaps-lib/services/publicSwapService.js +32 -4
  54. package/src/swaps-lib/test/external-apis/swapspaceSwapProvider/_fetchSupportedCurrenciesIfNeeded.test.js +506 -0
  55. package/src/swaps-lib/test/external-apis/swapspaceSwapProvider/createSwap.test.js +1311 -0
  56. package/src/swaps-lib/test/external-apis/swapspaceSwapProvider/getAllSupportedCurrencies.test.js +76 -0
  57. package/src/swaps-lib/test/external-apis/swapspaceSwapProvider/getDepositCurrencies.test.js +82 -0
  58. package/src/swaps-lib/test/external-apis/swapspaceSwapProvider/getSwapInfo.test.js +1892 -0
  59. package/src/swaps-lib/test/external-apis/swapspaceSwapProvider/getWithdrawalCurrencies.test.js +111 -0
  60. package/src/swaps-lib/test/utils/swapUtils/safeHandleRequestsLimitExceeding.test.js +88 -0
  61. package/stories/stubs/exampleContent.jsx +20 -0
  62. package/styles/fonts/NunitoSans-Bold.ttf +0 -0
  63. package/styles/fonts/NunitoSans-ExtraBold.ttf +0 -0
  64. package/styles/fonts/NunitoSans-Light.ttf +0 -0
  65. package/styles/fonts/NunitoSans-Regular.ttf +0 -0
  66. package/styles/fonts/NunitoSans-SemiBold.ttf +0 -0
  67. package/styles/index.scss +14 -13
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rabbitio/ui-kit",
3
- "version": "1.0.0-beta.42",
3
+ "version": "1.0.0-beta.45",
4
4
  "description": "Rabbit.io react.js components kit",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -15,10 +15,13 @@
15
15
  "./index.css": "./dist/index.css"
16
16
  },
17
17
  "scripts": {
18
+ "prepare": "husky",
18
19
  "build": "rm -rf dist && microbundle --no-compress --jsx React.createElement --jsxFragment React.Fragment --jsxImportSource react --globals react/jsx-runtime=jsx --visualize",
19
20
  "prepublish": "npm run build",
20
21
  "storybook": "storybook dev -p 6006",
21
- "build-storybook": "storybook build"
22
+ "build-storybook": "storybook build",
23
+ "test-watching": "vitest",
24
+ "test": "vitest run --coverage"
22
25
  },
23
26
  "keywords": [
24
27
  "rabbit.io",
@@ -38,12 +41,17 @@
38
41
  "react-dom": ">=18.2.0"
39
42
  },
40
43
  "dependencies": {
44
+ "animated-scroll-to": "2.3.0",
41
45
  "axios": "1.6.7",
42
46
  "bignumber.js": "9.1.2",
47
+ "body-scroll-lock": "3.1.5",
43
48
  "eventbusjs": "0.2.0",
44
49
  "jshashes": "1.0.8",
45
50
  "react": ">=18.2.0",
51
+ "react-animate-height": "3.2.3",
46
52
  "react-dom": ">=18.2.0",
53
+ "react-transition-group": "4.4.5",
54
+ "resize-observer-polyfill": "1.5.1",
47
55
  "uuid": "9.0.0"
48
56
  },
49
57
  "devDependencies": {
@@ -59,14 +67,19 @@
59
67
  "@storybook/react": "^7.6.6",
60
68
  "@storybook/react-webpack5": "^7.6.6",
61
69
  "@storybook/test": "^7.6.6",
70
+ "@vitest/coverage-v8": "^1.5.0",
71
+ "husky": "^9.0.11",
62
72
  "microbundle": "^0.15.1",
63
73
  "prettier": "^3.1.1",
64
74
  "prop-types": "^15.8.1",
65
75
  "resolve-url-loader": "^5.0.0",
66
76
  "sass": "^1.70.0",
67
77
  "sass-loader": "^14.1.0",
78
+ "should": "13.2.3",
79
+ "sinon": "17.0.1",
68
80
  "storybook": "^7.6.6",
69
- "style-loader": "^3.3.3"
81
+ "style-loader": "^3.3.3",
82
+ "vitest": "1.5.0"
70
83
  },
71
84
  "prettier": {
72
85
  "trailingComma": "es5",
@@ -0,0 +1,3 @@
1
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M12.2431 6L8.00045 10.2426L3.75781 6" stroke="#16CDD6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
3
+ </svg>
@@ -0,0 +1,14 @@
1
+ <svg
2
+ width="16"
3
+ height="16"
4
+ viewBox="0 0 16 16"
5
+ fill="none"
6
+ xmlns="http://www.w3.org/2000/svg"
7
+ >
8
+ <path
9
+ d="M12.2431 6L8.00045 10.2426L3.75781 6"
10
+ stroke="#fff" stroke-width="2"
11
+ stroke-linecap="round"
12
+ stroke-linejoin="round"
13
+ />
14
+ </svg>
@@ -0,0 +1,15 @@
1
+ <svg width="27" height="27" viewBox="0 0 27 27" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <rect opacity="0.8" x="1.5" y="1.5" width="24" height="24" rx="12" fill="#DAF1FE"/>
3
+ <path d="M12.3259 10.5855C12.2575 9.8896 12.8063 9.28745 13.5055 9.29121C14.2008 9.29495 14.7418 9.8964 14.6722 10.5881L14.1937 15.3431C14.1575 15.7026 13.855 15.9762 13.4937 15.9762C13.1319 15.9762 12.829 15.7017 12.7936 15.3415L12.3259 10.5855Z" fill="url(#paint0_linear)"/>
4
+ <path d="M13.514 17.1182C14.1325 17.1182 14.634 17.6196 14.634 18.2382C14.634 18.8567 14.1325 19.3582 13.514 19.3582H13.486C12.8674 19.3582 12.366 18.8567 12.366 18.2382C12.366 17.6196 12.8674 17.1182 13.486 17.1182H13.514Z" fill="url(#paint1_linear)"/>
5
+ <defs>
6
+ <linearGradient id="paint0_linear" x1="17.3456" y1="14.0483" x2="11.6522" y2="12.8896" gradientUnits="userSpaceOnUse">
7
+ <stop stop-color="#55E7D9"/>
8
+ <stop offset="1" stop-color="#54B4FF"/>
9
+ </linearGradient>
10
+ <linearGradient id="paint1_linear" x1="17.3456" y1="14.2383" x2="11.6522" y2="13.0796" gradientUnits="userSpaceOnUse">
11
+ <stop stop-color="#55E7D9"/>
12
+ <stop offset="1" stop-color="#54B4FF"/>
13
+ </linearGradient>
14
+ </defs>
15
+ </svg>
@@ -0,0 +1,10 @@
1
+ <svg width="27" height="27" viewBox="0 0 27 27" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <rect opacity="0.8" x="1.5" y="1.5" width="24" height="24" rx="12" fill="#DAF1FE"/>
3
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M11.8703 15.7837L9.64348 13.5569C9.30357 13.217 8.75246 13.217 8.41255 13.5569C8.07264 13.8968 8.07264 14.4479 8.41255 14.7878L11.2351 17.6104C11.5672 17.9425 12.103 17.9512 12.4457 17.6301L18.4492 12.0049C18.8 11.6762 18.8179 11.1254 18.4892 10.7746C18.1605 10.4238 17.6097 10.4059 17.2589 10.7346L11.8703 15.7837Z" fill="url(#paint0_linear)"/>
4
+ <defs>
5
+ <linearGradient id="paint0_linear" x1="19.8333" y1="18.9098" x2="15.586" y2="7.2736" gradientUnits="userSpaceOnUse">
6
+ <stop stop-color="#55E7D9"/>
7
+ <stop offset="1" stop-color="#54B4FF"/>
8
+ </linearGradient>
9
+ </defs>
10
+ </svg>
@@ -273,11 +273,13 @@ export class AmountUtils {
273
273
  }
274
274
 
275
275
  /**
276
+ * Returns integer part of number as a string.
277
+ *
276
278
  * @param amount {BigNumber|number|string|null|undefined} The number value to be trimmed.
277
279
  * HEX strings also allowed "0x..." and JS hex numbers
278
280
  * @return {string|null}
279
281
  */
280
- static intStr(amount) {
282
+ static toIntegerString(amount) {
281
283
  return this.trim(amount, 0);
282
284
  }
283
285
 
@@ -392,7 +394,7 @@ export class AmountUtils {
392
394
  leftNumber = leftNumber.times(multiplier);
393
395
  }
394
396
  }
395
- const leftAmountString = AmountUtils.intStr(leftNumber);
397
+ const leftAmountString = AmountUtils.toIntegerString(leftNumber);
396
398
  const rightAmountString =
397
399
  right != null
398
400
  ? right.toFixed(
@@ -0,0 +1,14 @@
1
+ import should from "should";
2
+
3
+ import { describe, it } from "vitest";
4
+ import { IpAddressProvider } from "../../../../external-apis/ipAddressProviders.js";
5
+
6
+ describe("ipAddressProviders", function () {
7
+ describe("#getClientIpAddress", function () {
8
+ it("Should successfully retrieve IP address", async function () {
9
+ (
10
+ await IpAddressProvider.getClientIpAddress()
11
+ ).should.not.be.empty();
12
+ });
13
+ });
14
+ });
@@ -9,10 +9,10 @@ import { Logger } from "./logging/logger.js";
9
9
  */
10
10
  export class Cache {
11
11
  /**
12
- * @param eventBus {EventBus} EventBus.js lib instance
13
- * @param [noSessionEvents=[]] {string[]} array of events that will be treated as "no session"
12
+ * @param [eventBus=null] {EventBus} EventBus.js lib instance if you plan to use Cache with events handling
13
+ * @param [noSessionEvents=[]] {string[]} array of events that will be treated as "no session", you should pass EventBus to make it work
14
14
  */
15
- constructor(eventBus, noSessionEvents = []) {
15
+ constructor(eventBus = null, noSessionEvents = []) {
16
16
  this._cache = new Map();
17
17
  this._eventDependentDataKeys = [];
18
18
  this._noSessionEvents = noSessionEvents;
@@ -102,7 +102,7 @@ export class Cache {
102
102
  );
103
103
  if (eventAndKeys) {
104
104
  eventAndKeys.push(key);
105
- } else {
105
+ } else if (this._eventBus) {
106
106
  this._eventDependentDataKeys.push([event, key]);
107
107
  this._eventBus.addEventListener(event, () => {
108
108
  try {
@@ -0,0 +1,44 @@
1
+ import React from "react";
2
+ import PropTypes from "prop-types";
3
+
4
+ import s from "./background-title.module.scss";
5
+
6
+ /**
7
+ * Component for displaying a title with a fancy background text, which
8
+ * is semi-visible. This component is designed to work with forms that
9
+ * have a white background, and it allows for a configuration of margins.
10
+ *
11
+ * @param {Object} props - Component props
12
+ * @param {string} props.text - Text to be displayed as the title
13
+ * @param {boolean} [props.smallMargins=false] - Whether to use smaller margins around the title
14
+ */
15
+ export const BackgroundTitle = ({ text, smallMargins = false }) => {
16
+ return (
17
+ <div className={s["background-title"]}>
18
+ <div
19
+ className={
20
+ s["background-title-wrapper"] +
21
+ (smallMargins ? " " + s["small-margins"] : "")
22
+ }
23
+ >
24
+ <div
25
+ className={
26
+ s["background-title-wrapper-text"] +
27
+ (smallMargins ? " " + s["small-margins"] : "")
28
+ }
29
+ >
30
+ {text}
31
+ </div>
32
+ </div>
33
+ </div>
34
+ );
35
+ };
36
+
37
+ BackgroundTitle.propTypes = {
38
+ text: PropTypes.string.isRequired,
39
+ smallMargins: PropTypes.bool,
40
+ };
41
+
42
+ BackgroundTitle.defaultProps = {
43
+ smallMargins: false,
44
+ };
@@ -0,0 +1,52 @@
1
+ @import "../../../../styles/index";
2
+
3
+ .background-title {
4
+ pointer-events: none;
5
+ user-select: none;
6
+ position: relative;
7
+ width: 200%;
8
+ font-family: NunitoSans;
9
+
10
+ &-wrapper {
11
+ position: absolute;
12
+ overflow-x: hidden;
13
+ width: 200%;
14
+ height: 90px;
15
+ top: 0;
16
+ left: 0;
17
+
18
+ @media (max-width: $phone-width) {
19
+ font-size: 30px;
20
+ line-height: 50px;
21
+ letter-spacing: 0.76875px;
22
+ }
23
+
24
+ &.small-margins {
25
+ top: 15px;
26
+ left: 19px;
27
+ }
28
+
29
+ &-text {
30
+ position: absolute;
31
+ top: 0;
32
+ left: 0;
33
+ font-size: 50px;
34
+ line-height: 50px;
35
+ font-weight: $extra-bold;
36
+ letter-spacing: 1.23px;
37
+ color: rgba(#12316b, 0.05);
38
+
39
+ &.small-margins {
40
+ font-size: 30px;
41
+ line-height: 50px;
42
+ letter-spacing: 0.76875px;
43
+ }
44
+
45
+ @media (max-width: $phone-width) {
46
+ font-size: 30px;
47
+ line-height: 40px;
48
+ letter-spacing: 0.76875px;
49
+ }
50
+ }
51
+ }
52
+ }
@@ -0,0 +1,130 @@
1
+ import React from "react";
2
+ import PropTypes from "prop-types";
3
+
4
+ import s from "./validation.module.scss";
5
+ // import successfulValidationIcon from "../../../assets/image/icons/successful-validation-icon.svg";
6
+ // import failedValidationIcon from "../../../assets/image/icons/failed-validation-icon.svg";
7
+
8
+ const SuccessfulValidationIcon = () => (
9
+ <svg
10
+ width="27"
11
+ height="27"
12
+ viewBox="0 0 27 27"
13
+ fill="none"
14
+ xmlns="http://www.w3.org/2000/svg"
15
+ >
16
+ <rect
17
+ opacity="0.8"
18
+ x="1.5"
19
+ y="1.5"
20
+ width="24"
21
+ height="24"
22
+ rx="12"
23
+ fill="#DAF1FE"
24
+ />
25
+ <path
26
+ fill-rule="evenodd"
27
+ clip-rule="evenodd"
28
+ d="M11.8703 15.7837L9.64348 13.5569C9.30357 13.217 8.75246 13.217 8.41255 13.5569C8.07264 13.8968 8.07264 14.4479 8.41255 14.7878L11.2351 17.6104C11.5672 17.9425 12.103 17.9512 12.4457 17.6301L18.4492 12.0049C18.8 11.6762 18.8179 11.1254 18.4892 10.7746C18.1605 10.4238 17.6097 10.4059 17.2589 10.7346L11.8703 15.7837Z"
29
+ fill="url(#paint0_linear)"
30
+ />
31
+ <defs>
32
+ <linearGradient
33
+ id="paint0_linear"
34
+ x1="19.8333"
35
+ y1="18.9098"
36
+ x2="15.586"
37
+ y2="7.2736"
38
+ gradientUnits="userSpaceOnUse"
39
+ >
40
+ <stop stop-color="#55E7D9" />
41
+ <stop offset="1" stop-color="#54B4FF" />
42
+ </linearGradient>
43
+ </defs>
44
+ </svg>
45
+ );
46
+
47
+ const FailedValidationIcon = () => (
48
+ <svg
49
+ width="27"
50
+ height="27"
51
+ viewBox="0 0 27 27"
52
+ fill="none"
53
+ xmlns="http://www.w3.org/2000/svg"
54
+ >
55
+ <rect
56
+ opacity="0.8"
57
+ x="1.5"
58
+ y="1.5"
59
+ width="24"
60
+ height="24"
61
+ rx="12"
62
+ fill="#DAF1FE"
63
+ />
64
+ <path
65
+ d="M12.3259 10.5855C12.2575 9.8896 12.8063 9.28745 13.5055 9.29121C14.2008 9.29495 14.7418 9.8964 14.6722 10.5881L14.1937 15.3431C14.1575 15.7026 13.855 15.9762 13.4937 15.9762C13.1319 15.9762 12.829 15.7017 12.7936 15.3415L12.3259 10.5855Z"
66
+ fill="url(#paint0_linear)"
67
+ />
68
+ <path
69
+ d="M13.514 17.1182C14.1325 17.1182 14.634 17.6196 14.634 18.2382C14.634 18.8567 14.1325 19.3582 13.514 19.3582H13.486C12.8674 19.3582 12.366 18.8567 12.366 18.2382C12.366 17.6196 12.8674 17.1182 13.486 17.1182H13.514Z"
70
+ fill="url(#paint1_linear)"
71
+ />
72
+ <defs>
73
+ <linearGradient
74
+ id="paint0_linear"
75
+ x1="17.3456"
76
+ y1="14.0483"
77
+ x2="11.6522"
78
+ y2="12.8896"
79
+ gradientUnits="userSpaceOnUse"
80
+ >
81
+ <stop stop-color="#55E7D9" />
82
+ <stop offset="1" stop-color="#54B4FF" />
83
+ </linearGradient>
84
+ <linearGradient
85
+ id="paint1_linear"
86
+ x1="17.3456"
87
+ y1="14.2383"
88
+ x2="11.6522"
89
+ y2="13.0796"
90
+ gradientUnits="userSpaceOnUse"
91
+ >
92
+ <stop stop-color="#55E7D9" />
93
+ <stop offset="1" stop-color="#54B4FF" />
94
+ </linearGradient>
95
+ </defs>
96
+ </svg>
97
+ );
98
+
99
+ /**
100
+ * Simple validation message with an icon.
101
+ * It shows either a success or failure icon based on the isSuccessAlert prop.
102
+ *
103
+ * @param {Object} props - The component props.
104
+ * @param {string} props.text - The validation message text to display.
105
+ * @param {boolean} props.isSuccessAlert - Determines if the displayed alert is for a success (true) or failure (false).
106
+ */
107
+ export const Validation = ({ text = "", isSuccessAlert }) => {
108
+ return (
109
+ <div className={s["validation"]}>
110
+ {isSuccessAlert ? (
111
+ // <img src={successfulValidationIcon} alt="validation icon" />
112
+ <SuccessfulValidationIcon />
113
+ ) : (
114
+ // <img src={failedValidationIcon} alt="validation icon" />
115
+ <FailedValidationIcon />
116
+ )}
117
+ <span className={s["validation-text"]}>{text}</span>
118
+ </div>
119
+ );
120
+ };
121
+
122
+ Validation.propTypes = {
123
+ text: PropTypes.string,
124
+ isSuccessAlert: PropTypes.bool.isRequired,
125
+ };
126
+
127
+ Validation.defaultProps = {
128
+ text: "",
129
+ isSuccessAlert: false,
130
+ };
@@ -0,0 +1,15 @@
1
+ @import "../../../../styles/index";
2
+
3
+ .validation {
4
+ display: flex;
5
+ align-items: center;
6
+ margin-top: Margin("3");
7
+
8
+ &-text {
9
+ text-align: left;
10
+ color: SolidColor("sky");
11
+ @extend .ml-2;
12
+ font-size: 14px;
13
+ @extend %text-semibold;
14
+ }
15
+ }
@@ -0,0 +1,64 @@
1
+ import React from "react";
2
+ import PropTypes from "prop-types";
3
+
4
+ import s from "./close.module.scss";
5
+
6
+ import { useCallHandlingErrors } from "../../../hooks/useCallHandlingErrors";
7
+
8
+ export const CLOSE_COLORS = {
9
+ WHITE: "white",
10
+ DARK: "dark",
11
+ DARK_INVERT: "dark-invert",
12
+ };
13
+
14
+ /**
15
+ * A component for rendering a close icon that handles errors on click.
16
+ * This component uses an SVG to render the icon and allows customization
17
+ * through props for color, size, and the click event handling.
18
+ *
19
+ * @param {Object} props - The properties passed to the component.
20
+ * @param {string} props.color - The color theme of the close icon.
21
+ * Should be one of the properties of CLOSE_COLORS.
22
+ * @param {Function} props.onClick - The function to call when the icon is clicked.
23
+ * @param {boolean} props.large - Whether the icon should be displayed in a larger size.
24
+ */
25
+ export const Close = ({
26
+ color = CLOSE_COLORS.WHITE,
27
+ onClick = () => {},
28
+ large = false,
29
+ }) => {
30
+ const callHandlingErrors = useCallHandlingErrors();
31
+
32
+ return (
33
+ <svg
34
+ className={
35
+ s["close"] + " " + s[color] + (large ? " " + s["large"] : "")
36
+ }
37
+ onClick={(e) => callHandlingErrors(onClick, e)}
38
+ style={{ cursor: "pointer" }}
39
+ xmlns="http://www.w3.org/2000/svg"
40
+ viewBox={`0 0 50 50`}
41
+ x="0px"
42
+ y="0px"
43
+ >
44
+ <path
45
+ d="M28.228,23.986L47.092,5.122c1.172-1.171,1.172-3.071,0-4.242c-1.172-1.172-3.07-1.172-4.242,0L23.986,19.744L5.121,0.88
46
+ c-1.172-1.172-3.07-1.172-4.242,0c-1.172,1.171-1.172,3.071,0,4.242l18.865,18.864L0.879,42.85c-1.172,1.171-1.172,3.071,0,4.242
47
+ C1.465,47.677,2.233,47.97,3,47.97s1.535-0.293,2.121-0.879l18.865-18.864L42.85,47.091c0.586,0.586,1.354,0.879,2.121,0.879
48
+ s1.535-0.293,2.121-0.879c1.172-1.171,1.172-3.071,0-4.242L28.228,23.986z"
49
+ />
50
+ </svg>
51
+ );
52
+ };
53
+
54
+ Close.propTypes = {
55
+ color: PropTypes.oneOf(Object.values(CLOSE_COLORS)),
56
+ onClick: PropTypes.func,
57
+ large: PropTypes.bool,
58
+ };
59
+
60
+ Close.defaultProps = {
61
+ color: CLOSE_COLORS.WHITE,
62
+ onClick: () => {},
63
+ large: false,
64
+ };
@@ -0,0 +1,75 @@
1
+ @import "../../../../../styles/index";
2
+
3
+ .close {
4
+ width: 24px;
5
+ height: 24px;
6
+ padding: 6px;
7
+ border-radius: 100%;
8
+ background-color: LightColor("grey-20");
9
+ opacity: 0.5;
10
+ transition: 0.2s all ease;
11
+ cursor: pointer;
12
+ box-sizing: border-box;
13
+
14
+ &.large {
15
+ width: 48px;
16
+ height: 48px;
17
+ padding: 16px;
18
+
19
+ @media (max-width: $tablet-width) {
20
+ width: 36px;
21
+ height: 36px;
22
+ padding: 12px;
23
+ }
24
+ }
25
+
26
+ &.white {
27
+ path {
28
+ fill: white;
29
+ background-color: none;
30
+ }
31
+ }
32
+
33
+ &.dark-invert {
34
+ opacity: 0.5;
35
+ background-color: SolidColor("grey");
36
+
37
+ @media (hover: hover) {
38
+ &:hover {
39
+ opacity: 1;
40
+ transition: opacity 0.03s ease;
41
+ }
42
+ }
43
+
44
+ &:active {
45
+ opacity: 1;
46
+ transition: opacity 0.03s ease;
47
+ }
48
+
49
+ path {
50
+ fill: SolidColor("lightsmoke");
51
+ }
52
+ }
53
+
54
+ &.dark {
55
+ box-shadow: $shadowOutlineStatic;
56
+
57
+ @media (hover: hover) {
58
+ &:hover {
59
+ opacity: 1;
60
+ transition: opacity 0.03s ease;
61
+ box-shadow: $shadowOutlineHover;
62
+ }
63
+ }
64
+
65
+ &:active {
66
+ opacity: 1;
67
+ transition: opacity 0.03s ease;
68
+ box-shadow: $shadowOutlineHover;
69
+ }
70
+
71
+ path {
72
+ fill: SolidColor("darkBlue");
73
+ }
74
+ }
75
+ }
@@ -0,0 +1,121 @@
1
+ import React, { useEffect, useState } from "react";
2
+ import PropTypes from "prop-types";
3
+
4
+ import { useCallHandlingErrors } from "../../../hooks/useCallHandlingErrors";
5
+
6
+ import s from "./link-button.module.scss";
7
+ import { LoadingDots } from "../../LoadingDots/LoadingDots";
8
+
9
+ export const iconRotateOptions = {
10
+ rotate0: "0deg",
11
+ rotate90: "90deg",
12
+ rotate180: "180deg",
13
+ rotate270: "270deg",
14
+ };
15
+
16
+ /**
17
+ * LinkButton renders a clickable button which can optionally display an icon and loading animation.
18
+ * It can also be styled to be colored or disabled. This button supports rotating the icon according to specified degrees.
19
+ *
20
+ * @component
21
+ * @param {Object} props - Properties passed down to the component.
22
+ * @param {Function} props.onClick - Callback function triggered on button click. It should handle any post-click logic like resetting loading state.
23
+ * @param {boolean} props.isDisabled - If true, the button will be disabled and non-interactable.
24
+ * @param {boolean} props.isLoadable - If true, the button will display a loading indicator upon being clicked.
25
+ * @param {string} props.content - Text content displayed on the button.
26
+ * @param {boolean} props.isColored - If true, applies additional styling to the button.
27
+ * @param {string} props.icon - URL of an icon image to be displayed on the button.
28
+ * @param {string} props.iconRotate - Rotation angle for the icon. Valid values are defined in `iconRotateOptions`.
29
+ * @param {Object} props.IconComponent - Icon component, if SVG is imported directly.
30
+ *
31
+ * @returns {JSX.Element} A React component that renders a clickable and optionally loadable and icon-displaying button.
32
+ */
33
+ export const LinkButton = ({
34
+ onClick = (resetButtonLoader) => {},
35
+ isDisabled = false,
36
+ isLoadable = false,
37
+ content = "",
38
+ isColored = true,
39
+ icon,
40
+ iconRotate,
41
+ IconComponent,
42
+ }) => {
43
+ const callHandlingErrors = useCallHandlingErrors();
44
+
45
+ const [isLoading, setIsLoading] = useState(false);
46
+ const [processedIconRotation, setProcessedIconRotation] = useState(null);
47
+
48
+ const handleClick = () => {
49
+ callHandlingErrors(() => {
50
+ if (!isDisabled) {
51
+ if (isLoadable) {
52
+ setIsLoading(true);
53
+ }
54
+ // TODO: [bug, moderate] Click can be called when there isLoading=true task_id=a15979a4ca2042b29dfd1d128a16c281
55
+ onClick(() => setIsLoading(false));
56
+ }
57
+ });
58
+ };
59
+
60
+ useEffect(() => {
61
+ if (
62
+ iconRotate &&
63
+ Object.keys(iconRotateOptions).find(
64
+ (key) => iconRotateOptions[key] === iconRotate
65
+ )
66
+ )
67
+ setProcessedIconRotation(iconRotate);
68
+ // eslint-disable-next-line react-hooks/exhaustive-deps
69
+ }, [iconRotate]);
70
+
71
+ return isLoading ? (
72
+ <LoadingDots isColored={true} />
73
+ ) : (
74
+ <div
75
+ className={
76
+ s["link-button"] +
77
+ ` ${
78
+ processedIconRotation &&
79
+ processedIconRotation !== iconRotateOptions.rotate0
80
+ ? " " + s["icon-rotate-" + processedIconRotation]
81
+ : ""
82
+ } ${isDisabled ? " " + s["disabled"] : ""}`
83
+ }
84
+ onClick={handleClick}
85
+ >
86
+ {icon ? <img src={icon} alt="link button alt" /> : null}
87
+ {IconComponent ? <IconComponent /> : null}
88
+ <p
89
+ className={
90
+ s["link-button-text"] +
91
+ ` ${isColored ? s["colored"] : ""} ${
92
+ isDisabled ? s["disabled"] : ""
93
+ }`
94
+ }
95
+ >
96
+ {content}
97
+ </p>
98
+ </div>
99
+ );
100
+ };
101
+
102
+ LinkButton.propTypes = {
103
+ onClick: PropTypes.func,
104
+ isDisabled: PropTypes.bool,
105
+ isLoadable: PropTypes.bool,
106
+ content: PropTypes.string,
107
+ icon: PropTypes.string,
108
+ isColored: PropTypes.bool,
109
+ iconRotate: PropTypes.oneOf(Object.values(iconRotateOptions)),
110
+ IconComponent: PropTypes.elementType,
111
+ };
112
+
113
+ LinkButton.defaultProps = {
114
+ onClick: (resetButtonLoader) => {},
115
+ isDisabled: false,
116
+ isLoadable: false,
117
+ content: "",
118
+ icon: undefined,
119
+ isColored: true,
120
+ iconRotate: iconRotateOptions.rotate0,
121
+ };