@plusscommunities/pluss-feature-builder-web-d 1.0.2-beta.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 (117) hide show
  1. package/.babelrc +4 -0
  2. package/dist/index.cjs.js +7792 -0
  3. package/package.json +54 -0
  4. package/rollup.config.js +68 -0
  5. package/src/actions/featureBuilderStringsActions.js +88 -0
  6. package/src/actions/featureDefinitionsIndex.js +258 -0
  7. package/src/actions/formActions.js +311 -0
  8. package/src/actions/index.js +12 -0
  9. package/src/actions/listingActions.js +350 -0
  10. package/src/actions/wizardActions.js +240 -0
  11. package/src/components/ActivityCardExample.jsx +86 -0
  12. package/src/components/ActivityCardExample.module.css +130 -0
  13. package/src/components/BackgroundLoader.jsx +33 -0
  14. package/src/components/BackgroundLoader.module.css +46 -0
  15. package/src/components/BaseFieldConfig.jsx +305 -0
  16. package/src/components/BaseFieldConfig.module.css +42 -0
  17. package/src/components/CenteredContainer.jsx +29 -0
  18. package/src/components/CenteredContainer.module.css +171 -0
  19. package/src/components/DeleteConfirmationPopup.jsx +95 -0
  20. package/src/components/DeleteConfirmationPopup.module.css +12 -0
  21. package/src/components/ErrorBoundary.jsx +134 -0
  22. package/src/components/ErrorBoundary.module.css +77 -0
  23. package/src/components/ErrorMessage.jsx +85 -0
  24. package/src/components/ErrorMessage.module.css +116 -0
  25. package/src/components/ExampleDisplay.jsx +26 -0
  26. package/src/components/ExampleDisplay.module.css +3 -0
  27. package/src/components/FeatureBuilderSidebar.jsx +84 -0
  28. package/src/components/FeatureBuilderSuccessPopup.jsx +55 -0
  29. package/src/components/FeatureBuilderSuccessPopup.module.css +43 -0
  30. package/src/components/FeatureBuilderWelcomePopup.jsx +51 -0
  31. package/src/components/FeatureBuilderWelcomePopup.module.css +21 -0
  32. package/src/components/FeatureListingCard.jsx +104 -0
  33. package/src/components/FeatureListingCard.module.css +62 -0
  34. package/src/components/Fields.jsx +460 -0
  35. package/src/components/Fields.module.css +159 -0
  36. package/src/components/IconLoader.jsx +153 -0
  37. package/src/components/IconLoader.module.css +92 -0
  38. package/src/components/IconSelector.jsx +112 -0
  39. package/src/components/IconSelector.module.css +197 -0
  40. package/src/components/ListingEditor.jsx +406 -0
  41. package/src/components/ListingEditor.module.css +14 -0
  42. package/src/components/ListingSuccessPopup.jsx +52 -0
  43. package/src/components/LoadingScreen.jsx +54 -0
  44. package/src/components/LoadingScreen.module.css +103 -0
  45. package/src/components/LoadingState.jsx +40 -0
  46. package/src/components/LoadingState.module.css +18 -0
  47. package/src/components/PreviewFull.js +24 -0
  48. package/src/components/PreviewFull.module.css +11 -0
  49. package/src/components/PreviewGrid.js +14 -0
  50. package/src/components/PreviewWidget.js +27 -0
  51. package/src/components/PreviewWidget.module.css +15 -0
  52. package/src/components/SidebarLayout.jsx +292 -0
  53. package/src/components/SidebarLayout.module.css +145 -0
  54. package/src/components/SkeletonLoader.jsx +128 -0
  55. package/src/components/SkeletonLoader.module.css +295 -0
  56. package/src/components/SortButtonGroup.jsx +34 -0
  57. package/src/components/SortButtonGroup.module.css +51 -0
  58. package/src/components/ToastContainer.jsx +98 -0
  59. package/src/components/ToastContainer.module.css +156 -0
  60. package/src/components/ToggleSwitch.js +40 -0
  61. package/src/components/ToggleSwitch.module.css +48 -0
  62. package/src/components/TwoColumnInput.jsx +29 -0
  63. package/src/components/TwoColumnInput.module.css +32 -0
  64. package/src/components/ViewFull.js +139 -0
  65. package/src/components/ViewFull.module.css +71 -0
  66. package/src/components/ViewWidget.js +62 -0
  67. package/src/components/ViewWidget.module.css +28 -0
  68. package/src/components/iconCategories.js +135 -0
  69. package/src/components/iconImports.js +409 -0
  70. package/src/components/index.js +61 -0
  71. package/src/components/listing/FileListItem.jsx +86 -0
  72. package/src/components/listing/GalleryDisplay.jsx +331 -0
  73. package/src/components/listing/GalleryDisplay.module.css +309 -0
  74. package/src/components/listing/ListingCTAInput.jsx +82 -0
  75. package/src/components/listing/ListingDescriptionInput.jsx +73 -0
  76. package/src/components/listing/ListingField.jsx +101 -0
  77. package/src/components/listing/ListingField.module.css +106 -0
  78. package/src/components/listing/ListingFileInput.jsx +255 -0
  79. package/src/components/listing/ListingFileInput.module.css +192 -0
  80. package/src/components/listing/ListingForm.jsx +90 -0
  81. package/src/components/listing/ListingForm.module.css +38 -0
  82. package/src/components/listing/ListingGalleryInput.jsx +236 -0
  83. package/src/components/listing/ListingGalleryInput.module.css +131 -0
  84. package/src/components/listing/ListingImageInput.jsx +153 -0
  85. package/src/components/listing/ListingTextInput.jsx +72 -0
  86. package/src/feature.config.js +130 -0
  87. package/src/helper/index.js +135 -0
  88. package/src/hooks/useFeatureDefinitionLoader.js +62 -0
  89. package/src/images/full.png +0 -0
  90. package/src/images/fullNoTitle.png +0 -0
  91. package/src/images/previewWidget.png +0 -0
  92. package/src/images/widget.png +0 -0
  93. package/src/index.js +38 -0
  94. package/src/pages/CreateListingPage.jsx +49 -0
  95. package/src/pages/EditListingPage.jsx +58 -0
  96. package/src/reducers/featureBuilderReducer.js +744 -0
  97. package/src/screens/CreateListing.module.css +45 -0
  98. package/src/screens/Form.module.css +734 -0
  99. package/src/screens/FormFieldsStep.jsx +689 -0
  100. package/src/screens/FormLayoutStep.jsx +445 -0
  101. package/src/screens/FormOverviewStep.jsx +396 -0
  102. package/src/screens/ListingScreen.jsx +478 -0
  103. package/src/screens/ListingScreen.module.css +333 -0
  104. package/src/selectors/featureBuilderSelectors.js +529 -0
  105. package/src/types/index.js +91 -0
  106. package/src/utils/textUtils.js +89 -0
  107. package/src/validators/galleryValidators.js +345 -0
  108. package/src/values.config.a.js +49 -0
  109. package/src/values.config.b.js +49 -0
  110. package/src/values.config.c.js +49 -0
  111. package/src/values.config.d.js +49 -0
  112. package/src/values.config.js +49 -0
  113. package/src/webapi/featureDefinitionActions.js +0 -0
  114. package/src/webapi/featuresActions.js +90 -0
  115. package/src/webapi/helper.js +4 -0
  116. package/src/webapi/index.js +12 -0
  117. package/src/webapi/listingActions.js +176 -0
@@ -0,0 +1,295 @@
1
+ /* Base skeleton animation */
2
+ .skeletonItem {
3
+ background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
4
+ background-size: 200% 100%;
5
+ animation: skeletonLoading 1.5s ease-in-out infinite;
6
+ border-radius: 4px;
7
+ }
8
+
9
+ @keyframes skeletonLoading {
10
+ 0% {
11
+ background-position: 200% 0;
12
+ }
13
+ 100% {
14
+ background-position: -200% 0;
15
+ }
16
+ }
17
+
18
+ .skeletonLoader {
19
+ display: flex;
20
+ flex-direction: column;
21
+ gap: 1rem;
22
+ width: 100%;
23
+ }
24
+
25
+ .skeletonItemWrapper {
26
+ width: 100%;
27
+ }
28
+
29
+ /* Listing Card Skeleton */
30
+ .listingCard {
31
+ background: white;
32
+ border: 1px solid #e9ecef;
33
+ border-radius: 8px;
34
+ padding: 1.5rem;
35
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
36
+ }
37
+
38
+ .listingCardHeader {
39
+ display: flex;
40
+ align-items: center;
41
+ gap: 1rem;
42
+ margin-bottom: 1rem;
43
+ }
44
+
45
+ .listingCard .icon {
46
+ width: 48px;
47
+ height: 48px;
48
+ border-radius: 8px;
49
+ }
50
+
51
+ .listingCard .title {
52
+ flex: 1;
53
+ height: 24px;
54
+ width: 60%;
55
+ }
56
+
57
+ .listingCardBody {
58
+ margin-bottom: 1rem;
59
+ }
60
+
61
+ .listingCard .description {
62
+ height: 16px;
63
+ margin-bottom: 0.5rem;
64
+ }
65
+
66
+ .listingCard .description:first-child {
67
+ width: 80%;
68
+ }
69
+
70
+ .listingCard .description:last-child {
71
+ width: 60%;
72
+ }
73
+
74
+ .listingCardFooter {
75
+ display: flex;
76
+ justify-content: space-between;
77
+ align-items: center;
78
+ }
79
+
80
+ .listingCard .smallText {
81
+ width: 80px;
82
+ height: 14px;
83
+ }
84
+
85
+ .listingCard .button {
86
+ width: 80px;
87
+ height: 32px;
88
+ border-radius: 6px;
89
+ }
90
+
91
+ /* Form Field Skeleton */
92
+ .formField {
93
+ background: white;
94
+ border: 1px solid #e9ecef;
95
+ border-radius: 8px;
96
+ padding: 1.5rem;
97
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
98
+ }
99
+
100
+ .formFieldHeader {
101
+ display: flex;
102
+ justify-content: space-between;
103
+ align-items: center;
104
+ margin-bottom: 1rem;
105
+ }
106
+
107
+ .formField .fieldType {
108
+ width: 80px;
109
+ height: 20px;
110
+ border-radius: 4px;
111
+ }
112
+
113
+ .formField .fieldActions {
114
+ width: 60px;
115
+ height: 20px;
116
+ border-radius: 4px;
117
+ }
118
+
119
+ .formFieldBody {
120
+ display: flex;
121
+ flex-direction: column;
122
+ gap: 0.5rem;
123
+ }
124
+
125
+ .formField .label {
126
+ width: 120px;
127
+ height: 16px;
128
+ border-radius: 4px;
129
+ }
130
+
131
+ .formField .input {
132
+ width: 100%;
133
+ height: 40px;
134
+ border-radius: 6px;
135
+ }
136
+
137
+ .formField .helpText {
138
+ width: 180px;
139
+ height: 14px;
140
+ border-radius: 4px;
141
+ }
142
+
143
+ /* Layout Option Skeleton */
144
+ .layoutOption {
145
+ display: flex;
146
+ align-items: flex-start;
147
+ gap: 1rem;
148
+ background: white;
149
+ border: 1px solid #e9ecef;
150
+ border-radius: 8px;
151
+ padding: 1.5rem;
152
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
153
+ }
154
+
155
+ .layoutOption .layoutPreview {
156
+ width: 80px;
157
+ height: 80px;
158
+ border-radius: 8px;
159
+ }
160
+
161
+ .layoutContent {
162
+ flex: 1;
163
+ display: flex;
164
+ flex-direction: column;
165
+ gap: 0.5rem;
166
+ }
167
+
168
+ .layoutOption .layoutTitle {
169
+ width: 60%;
170
+ height: 20px;
171
+ border-radius: 4px;
172
+ }
173
+
174
+ .layoutOption .layoutDescription {
175
+ width: 90%;
176
+ height: 16px;
177
+ border-radius: 4px;
178
+ }
179
+
180
+ /* Form Input Skeleton */
181
+ .formInput {
182
+ margin-bottom: 1rem;
183
+ }
184
+
185
+ .formInput .label {
186
+ width: 120px;
187
+ height: 16px;
188
+ margin-bottom: 0.5rem;
189
+ border-radius: 4px;
190
+ }
191
+
192
+ .formInput .input {
193
+ width: 100%;
194
+ height: 40px;
195
+ border-radius: 6px;
196
+ }
197
+
198
+ /* Generic Skeleton Items */
199
+ .card {
200
+ background: white;
201
+ border: 1px solid #e9ecef;
202
+ border-radius: 8px;
203
+ padding: 1.5rem;
204
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
205
+ }
206
+
207
+ .header {
208
+ width: 60%;
209
+ height: 24px;
210
+ margin-bottom: 1rem;
211
+ border-radius: 4px;
212
+ }
213
+
214
+ .content {
215
+ width: 100%;
216
+ height: 100px;
217
+ margin-bottom: 1rem;
218
+ border-radius: 4px;
219
+ }
220
+
221
+ .footer {
222
+ width: 40%;
223
+ height: 16px;
224
+ border-radius: 4px;
225
+ }
226
+
227
+ .button {
228
+ width: 100px;
229
+ height: 36px;
230
+ border-radius: 6px;
231
+ }
232
+
233
+ .text {
234
+ width: 100%;
235
+ height: 16px;
236
+ margin-bottom: 0.5rem;
237
+ border-radius: 4px;
238
+ }
239
+
240
+ .text:last-child {
241
+ width: 70%;
242
+ margin-bottom: 0;
243
+ }
244
+
245
+ /* Table Row Skeleton */
246
+ .tableRow {
247
+ width: 100%;
248
+ }
249
+
250
+ .tableRow .tableDate {
251
+ width: 120px;
252
+ height: 16px;
253
+ border-radius: 4px;
254
+ }
255
+
256
+ .tableRow .tableTitle {
257
+ width: 100%;
258
+ height: 16px;
259
+ border-radius: 4px;
260
+ }
261
+
262
+ .tableRow .tableActions {
263
+ width: 160px;
264
+ height: 16px;
265
+ border-radius: 4px;
266
+ }
267
+
268
+ /* Responsive adjustments */
269
+ @media (max-width: 768px) {
270
+ .listingCard,
271
+ .formField,
272
+ .layoutOption {
273
+ padding: 1rem;
274
+ }
275
+
276
+ .listingCardHeader {
277
+ flex-direction: column;
278
+ align-items: flex-start;
279
+ }
280
+
281
+ .listingCard .title {
282
+ width: 80%;
283
+ }
284
+
285
+ .layoutOption {
286
+ flex-direction: column;
287
+ align-items: center;
288
+ text-align: center;
289
+ }
290
+
291
+ .layoutOption .layoutTitle,
292
+ .layoutOption .layoutDescription {
293
+ width: 100%;
294
+ }
295
+ }
@@ -0,0 +1,34 @@
1
+ import React from "react";
2
+ import { PlussCore } from "../feature.config";
3
+ import styles from "./SortButtonGroup.module.css";
4
+ const { SVGIcon, Text, Tag } = PlussCore.Components;
5
+
6
+ const SortButtonGroup = ({ currentSort, onSortChange }) => {
7
+ const handleSortClick = (sortType) => {
8
+ if (onSortChange && sortType !== currentSort) {
9
+ onSortChange(sortType);
10
+ }
11
+ };
12
+
13
+ const sortOptions = [
14
+ { type: "newest", label: "Newest first" },
15
+ { type: "oldest", label: "Oldest first" },
16
+ { type: "az", label: "A-Z" },
17
+ { type: "za", label: "Z-A" },
18
+ ];
19
+
20
+ return (
21
+ <div className={styles.sortButtonGroup}>
22
+ {sortOptions.map((option) => (
23
+ <Tag
24
+ key={option.type}
25
+ leftIcon={option.type === currentSort ? "check" : null}
26
+ onClick={() => handleSortClick(option.type)}
27
+ text={option.label}
28
+ />
29
+ ))}
30
+ </div>
31
+ );
32
+ };
33
+
34
+ export default SortButtonGroup;
@@ -0,0 +1,51 @@
1
+ /* SortButtonGroup BEM CSS Module */
2
+
3
+ /* Block */
4
+ .sortButtonGroup {
5
+ display: flex;
6
+ align-items: center;
7
+ gap: 8px;
8
+ }
9
+
10
+ /* Element */
11
+ .sortButtonGroup__button {
12
+ padding: 8px 12px;
13
+ border: 1px solid var(--linegrey);
14
+ background-color: var(--bg-white);
15
+ color: var(--active-text);
16
+ border-radius: 4px;
17
+ cursor: pointer;
18
+ display: flex;
19
+ align-items: center;
20
+ gap: 6px;
21
+ font-size: 13px;
22
+ margin-right: 8px;
23
+ transition: all 0.2s ease;
24
+ }
25
+
26
+ /* Modifier */
27
+ .sortButtonGroup__button--selected {
28
+ border-color: var(--colour-blue);
29
+ background-color: var(--colour-blue);
30
+ color: var(--bg-white);
31
+ }
32
+
33
+ .sortButtonGroup__button--selected:hover {
34
+ border-color: var(--colour-darkblue);
35
+ background-color: var(--colour-darkblue);
36
+ }
37
+
38
+ .sortButtonGroup__button:hover:not(.sortButtonGroup__button--selected) {
39
+ border-color: var(--colour-blue);
40
+ background-color: var(--bg-bluegrey);
41
+ }
42
+
43
+ /* Element */
44
+ .sortButtonGroup__icon {
45
+ width: 12px;
46
+ height: 12px;
47
+ }
48
+
49
+ .sortButtonGroup__text {
50
+ margin: 0;
51
+ }
@@ -0,0 +1,98 @@
1
+ import React, { useEffect } from "react";
2
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
3
+ import { iconImports } from "./iconImports.js";
4
+ import styles from "./ToastContainer.module.css";
5
+
6
+ const ToastContainer = ({ toasts, onDismiss }) => {
7
+ if (!toasts || toasts.length === 0) {
8
+ return null;
9
+ }
10
+
11
+ const getDismissDelay = (type) => {
12
+ switch (type) {
13
+ case "success":
14
+ return 3000; // 3 seconds
15
+ case "warning":
16
+ return 4000; // 4 seconds
17
+ case "error":
18
+ return 5000; // 5 seconds for error messages
19
+ default:
20
+ return 3000;
21
+ }
22
+ };
23
+
24
+ // Auto-dismiss toasts based on type
25
+ useEffect(() => {
26
+ const timeouts = toasts.map((toast) => {
27
+ if (toast.autoDismiss !== false && toast.isVisible) {
28
+ return setTimeout(() => {
29
+ onDismiss(toast.id);
30
+ }, getDismissDelay(toast.type));
31
+ }
32
+ return null;
33
+ });
34
+
35
+ return () => {
36
+ timeouts.forEach((timeout) => clearTimeout(timeout));
37
+ };
38
+ }, [toasts, onDismiss]);
39
+
40
+ return (
41
+ <div className={styles.toastContainer} role="status" aria-live="polite">
42
+ {toasts.map((toast, index) => (
43
+ <div
44
+ key={toast.id}
45
+ className={`${styles.toast} ${styles[toast.type]} ${toast.isVisible ? styles.toastEnter : styles.toastExit}`}
46
+ onMouseEnter={() => {
47
+ // Pause auto-dismiss on hover
48
+ if (toast.timeoutId) {
49
+ clearTimeout(toast.timeoutId);
50
+ }
51
+ }}
52
+ onMouseLeave={() => {
53
+ // Resume auto-dismiss when hover ends
54
+ if (toast.autoDismiss !== false) {
55
+ toast.timeoutId = setTimeout(() => {
56
+ onDismiss(toast.id);
57
+ }, getDismissDelay(toast.type));
58
+ }
59
+ }}
60
+ role={toast.type === "error" ? "alert" : "status"}
61
+ aria-label={`${toast.type} message: ${toast.message}`}
62
+ aria-atomic="true"
63
+ >
64
+ <span className={styles.toastIcon}>
65
+ {toast.type === "success" && (
66
+ <FontAwesomeIcon
67
+ icon={iconImports["check-circle"]}
68
+ className={styles.successIcon}
69
+ />
70
+ )}
71
+ {toast.type === "error" && (
72
+ <FontAwesomeIcon
73
+ icon={iconImports["times-circle"]}
74
+ className={styles.errorIcon}
75
+ />
76
+ )}
77
+ {toast.type === "warning" && (
78
+ <FontAwesomeIcon
79
+ icon={iconImports["exclamation-triangle"]}
80
+ className={styles.warningIcon}
81
+ />
82
+ )}
83
+ </span>
84
+ <span className={styles.toastMessage}>{toast.message}</span>
85
+ <button
86
+ className={styles.toastDismiss}
87
+ onClick={() => onDismiss(toast.id)}
88
+ aria-label="Dismiss notification"
89
+ >
90
+ <FontAwesomeIcon icon={iconImports.times} />
91
+ </button>
92
+ </div>
93
+ ))}
94
+ </div>
95
+ );
96
+ };
97
+
98
+ export default ToastContainer;
@@ -0,0 +1,156 @@
1
+ .toastContainer {
2
+ position: fixed;
3
+ bottom: 24px;
4
+ left: 50%;
5
+ transform: translateX(-50%);
6
+ z-index: 9999;
7
+ pointer-events: none;
8
+ display: flex;
9
+ flex-direction: column;
10
+ align-items: center;
11
+ gap: 12px;
12
+ }
13
+
14
+ .toast {
15
+ position: relative;
16
+ min-width: 300px;
17
+ max-width: 450px;
18
+ padding: 12px 16px;
19
+ border-radius: 8px;
20
+ background-color: var(--bg-white);
21
+ box-shadow: 0 8px 24px rgba(19, 19, 26, 0.3);
22
+ border: 1px solid var(--colour-branding-inactive);
23
+ font-size: 14px;
24
+ font-family: var(--font-family);
25
+ color: var(--text-dark);
26
+ transform: translateY(100%);
27
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
28
+ pointer-events: auto;
29
+ white-space: nowrap;
30
+ display: flex;
31
+ align-items: center;
32
+ justify-content: space-between;
33
+ }
34
+
35
+ .toast.toastEnter {
36
+ transform: translateY(0);
37
+ opacity: 1;
38
+ }
39
+
40
+ .toast.toastExit {
41
+ transform: translateY(100%);
42
+ opacity: 0;
43
+ }
44
+
45
+ /* Success toast */
46
+ .toast.success {
47
+ border-left: 4px solid var(--colour-success);
48
+ }
49
+
50
+ .toast.success .toastMessage {
51
+ font-weight: 500;
52
+ }
53
+
54
+ /* Error toast */
55
+ .toast.error {
56
+ border-left: 4px solid var(--colour-error);
57
+ }
58
+
59
+ .toast.error .toastMessage {
60
+ color: var(--colour-error);
61
+ font-weight: 500;
62
+ }
63
+
64
+ /* Warning toast */
65
+ .toast.warning {
66
+ border-left: 4px solid var(--colour-warning);
67
+ }
68
+
69
+ .toast.warning .toastMessage {
70
+ color: var(--colour-warning);
71
+ font-weight: 500;
72
+ }
73
+
74
+ /* Icon styles */
75
+ .toastIcon {
76
+ font-size: 16px;
77
+ line-height: 20px;
78
+ display: flex;
79
+ align-items: center;
80
+ margin-right: 8px;
81
+ min-width: 20px;
82
+ justify-content: center;
83
+ }
84
+
85
+ /* Message styles */
86
+ .toastMessage {
87
+ display: flex;
88
+ align-items: center;
89
+ line-height: 20px;
90
+ margin-right: 8px;
91
+ flex: 1;
92
+ }
93
+
94
+ /* Dismiss button */
95
+ .toastDismiss {
96
+ background: none;
97
+ border: none;
98
+ padding: 0;
99
+ color: var(--text-muted);
100
+ cursor: pointer;
101
+ font-size: 14px;
102
+ display: flex;
103
+ align-items: center;
104
+ justify-content: center;
105
+ width: 20px;
106
+ height: 20px;
107
+ border-radius: 50%;
108
+ transition: all 0.2s ease;
109
+ flex-shrink: 0;
110
+ }
111
+
112
+ .toastDismiss:hover {
113
+ color: var(--text-dark);
114
+ background-color: rgba(0, 0, 0, 0.05);
115
+ }
116
+
117
+ /* Animation */
118
+ @keyframes slideUp {
119
+ from {
120
+ transform: translateX(-50%) translateY(100%);
121
+ opacity: 0;
122
+ }
123
+ to {
124
+ transform: translateX(-50%) translateY(0);
125
+ opacity: 1;
126
+ }
127
+ }
128
+
129
+ /* Reduced motion support */
130
+ @media (prefers-reduced-motion: reduce) {
131
+ .toast {
132
+ transition: opacity 0.2s ease-in-out;
133
+ transform: translateY(0) !important;
134
+ }
135
+
136
+ .toast.toastEnter {
137
+ opacity: 1;
138
+ }
139
+
140
+ .toast.toastExit {
141
+ opacity: 0;
142
+ }
143
+ }
144
+
145
+ /* Icon colors */
146
+ .successIcon {
147
+ color: var(--colour-green);
148
+ }
149
+
150
+ .errorIcon {
151
+ color: var(--colour-red);
152
+ }
153
+
154
+ .warningIcon {
155
+ color: var(--colour-warning);
156
+ }
@@ -0,0 +1,40 @@
1
+ import React from "react";
2
+ import styles from "./ToggleSwitch.module.css";
3
+
4
+ const ToggleSwitch = ({ disabled, onChange, isActive, className, style }) => {
5
+ const handleClick = () => {
6
+ if (disabled) {
7
+ return;
8
+ }
9
+ if (onChange) {
10
+ onChange(!isActive);
11
+ }
12
+ };
13
+
14
+ const getSwitchClassName = () => {
15
+ const classes = [
16
+ styles.root,
17
+ isActive ? styles["root--active"] : "",
18
+ disabled ? styles["root--disabled"] : "",
19
+ ]
20
+ .filter(Boolean)
21
+ .join(" ");
22
+ return classes;
23
+ };
24
+
25
+ const knobClassName = styles.knob;
26
+
27
+ return (
28
+ <div
29
+ className={`${className} ${styles.wrapper}`}
30
+ style={{ ...style }}
31
+ onClick={handleClick}
32
+ >
33
+ <div className={getSwitchClassName()}>
34
+ <div className={knobClassName} />
35
+ </div>
36
+ </div>
37
+ );
38
+ };
39
+
40
+ export { ToggleSwitch };
@@ -0,0 +1,48 @@
1
+ /* ToggleSwitch CSS Module */
2
+
3
+ /* Block */
4
+ .wrapper {
5
+ display: inline-block;
6
+ }
7
+
8
+ .root {
9
+ display: flex;
10
+ align-items: center;
11
+ justify-content: flex-start;
12
+ width: 44px;
13
+ height: 24px;
14
+ background-color: var(--linegrey);
15
+ border-radius: 12px;
16
+ padding: 2px;
17
+ cursor: pointer;
18
+ opacity: 1;
19
+ transition: all 0.2s ease;
20
+ }
21
+
22
+ /* Modifiers */
23
+ .root--active {
24
+ background-color: var(--colour-green);
25
+ }
26
+
27
+ .root--active .knob {
28
+ transform: translateX(20px);
29
+ }
30
+
31
+ .root--disabled {
32
+ cursor: not-allowed;
33
+ opacity: 0.6;
34
+ }
35
+
36
+ /* Elements */
37
+ .knob {
38
+ width: 20px;
39
+ height: 20px;
40
+ background-color: var(--bg-white);
41
+ border-radius: 50%;
42
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
43
+ transition: transform 0.25s cubic-bezier(0.25, 0.46, 0.45, 0.94);
44
+ transform: translateX(0);
45
+ position: relative;
46
+ z-index: 2;
47
+ flex-shrink: 0;
48
+ }