@plusscommunities/pluss-feature-builder-web-a 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,331 @@
1
+ import React, { useState } from "react";
2
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
3
+ import { Text } from "../../components";
4
+ import { iconImports } from "../../components/iconImports.js";
5
+ import styles from "./GalleryDisplay.module.css";
6
+
7
+
8
+ export const GalleryDisplay = (props) => {
9
+ const {
10
+ images = [],
11
+ layout = "grid",
12
+ columns = 3,
13
+ allowReordering = false,
14
+ allowLightbox = false,
15
+ style = {},
16
+ onImageClick,
17
+ onImageRemove,
18
+ onImageReorder,
19
+ } = props;
20
+
21
+ const [lightboxImage, setLightboxImage] = useState(null);
22
+ const [draggedIndex, setDraggedIndex] = useState(null);
23
+
24
+ // Filter out empty/invalid images
25
+ const validImages = images.filter((img) => img && img !== "");
26
+
27
+ if (validImages.length === 0) {
28
+ return (
29
+ <div className={styles.emptyGallery} style={style}>
30
+ <Text type="body" className={styles.emptyGalleryText}>
31
+ No images in gallery
32
+ </Text>
33
+ </div>
34
+ );
35
+ }
36
+
37
+ const handleImageClick = (image, index) => {
38
+ if (allowLightbox) {
39
+ setLightboxImage({ image, index });
40
+ }
41
+ if (onImageClick) {
42
+ onImageClick(image, index);
43
+ }
44
+ };
45
+
46
+ const handleDragStart = (index) => {
47
+ if (allowReordering) {
48
+ setDraggedIndex(index);
49
+ }
50
+ };
51
+
52
+ const handleDragOver = (e) => {
53
+ if (allowReordering) {
54
+ e.preventDefault();
55
+ }
56
+ };
57
+
58
+ const handleDrop = (e, dropIndex) => {
59
+ if (allowReordering && draggedIndex !== null) {
60
+ e.preventDefault();
61
+ onImageReorder?.(draggedIndex, dropIndex);
62
+ setDraggedIndex(null);
63
+ }
64
+ };
65
+
66
+ const removeImage = (index, e) => {
67
+ e.stopPropagation();
68
+ onImageRemove?.(index);
69
+ };
70
+
71
+ // Grid Layout
72
+ if (layout === "grid") {
73
+ return (
74
+ <>
75
+ <div
76
+ className={styles.gridContainer}
77
+ style={{
78
+ "--columns": columns,
79
+ ...style,
80
+ }}
81
+ >
82
+ {validImages.map((image, index) => (
83
+ <div
84
+ key={index}
85
+ className={styles.galleryItem}
86
+ onClick={() => handleImageClick(image, index)}
87
+ draggable={allowReordering}
88
+ onDragStart={() => handleDragStart(index)}
89
+ onDragOver={handleDragOver}
90
+ onDrop={(e) => handleDrop(e, index)}
91
+ style={{
92
+ cursor: allowLightbox || onImageClick ? "pointer" : "default",
93
+ }}
94
+ >
95
+ <img
96
+ src={image}
97
+ alt={`Gallery image ${index + 1}`}
98
+ className={styles.galleryItemImage}
99
+ />
100
+
101
+ {/* Remove button */}
102
+ {onImageRemove && (
103
+ <button
104
+ onClick={(e) => removeImage(index, e)}
105
+ className={styles.removeButton}
106
+ >
107
+ <FontAwesomeIcon icon={iconImports.minusCircle} />
108
+ </button>
109
+ )}
110
+
111
+ {/* Drag indicator */}
112
+ {allowReordering && (
113
+ <div className={styles.dragHandle}>
114
+ <FontAwesomeIcon icon={iconImports.repeat} />
115
+ </div>
116
+ )}
117
+ </div>
118
+ ))}
119
+ </div>
120
+
121
+ {/* Lightbox */}
122
+ {lightboxImage && (
123
+ <div
124
+ className={styles.lightbox}
125
+ onClick={() => setLightboxImage(null)}
126
+ >
127
+ <img
128
+ src={lightboxImage.image}
129
+ alt={`Gallery image ${lightboxImage.index + 1}`}
130
+ className={styles.lightboxImage}
131
+ onClick={(e) => e.stopPropagation()}
132
+ />
133
+
134
+ {/* Lightbox close button */}
135
+ <button
136
+ onClick={() => setLightboxImage(null)}
137
+ className={styles.lightboxClose}
138
+ >
139
+ ×
140
+ </button>
141
+
142
+ {/* Lightbox navigation */}
143
+ {validImages.length > 1 && (
144
+ <>
145
+ {lightboxImage.index > 0 && (
146
+ <button
147
+ onClick={(e) => {
148
+ e.stopPropagation();
149
+ setLightboxImage({
150
+ image: validImages[lightboxImage.index - 1],
151
+ index: lightboxImage.index - 1,
152
+ });
153
+ }}
154
+ className={`${styles.carouselNav} ${styles.carouselNavPrev}`}
155
+ >
156
+
157
+ </button>
158
+ )}
159
+ {lightboxImage.index < validImages.length - 1 && (
160
+ <button
161
+ onClick={(e) => {
162
+ e.stopPropagation();
163
+ setLightboxImage({
164
+ image: validImages[lightboxImage.index + 1],
165
+ index: lightboxImage.index + 1,
166
+ });
167
+ }}
168
+ className={`${styles.carouselNav} ${styles.carouselNavNext}`}
169
+ >
170
+
171
+ </button>
172
+ )}
173
+ </>
174
+ )}
175
+ </div>
176
+ )}
177
+ </>
178
+ );
179
+ }
180
+
181
+ // List Layout
182
+ if (layout === "list") {
183
+ return (
184
+ <div className={styles.listContainer} style={style}>
185
+ {validImages.map((image, index) => (
186
+ <div key={index} className={styles.listItemContent}>
187
+ <img
188
+ src={image}
189
+ alt={`Gallery image ${index + 1}`}
190
+ className={styles.listItemImage}
191
+ onClick={() => handleImageClick(image, index)}
192
+ style={{
193
+ cursor: allowLightbox || onImageClick ? "pointer" : "default",
194
+ }}
195
+ />
196
+
197
+ <div className={styles.listItemText}>
198
+ <Text type="bodyLarge" className={styles.listItemTitle}>
199
+ Image {index + 1}
200
+ </Text>
201
+ </div>
202
+
203
+ {/* Actions */}
204
+ <div
205
+ className={`${styles.listItemActions} ${styles.flex} ${styles.gap8}`}
206
+ >
207
+ {onImageRemove && (
208
+ <button
209
+ onClick={() => onImageRemove(index)}
210
+ className={styles.removeButton}
211
+ >
212
+ <FontAwesomeIcon icon={iconImports.minusCircle} />
213
+ </button>
214
+ )}
215
+ </div>
216
+ </div>
217
+ ))}
218
+ </div>
219
+ );
220
+ }
221
+
222
+ // Carousel Layout (simplified version)
223
+ if (layout === "carousel") {
224
+ const [currentIndex, setCurrentIndex] = useState(0);
225
+
226
+ return (
227
+ <div className={styles.carouselContainer} style={style}>
228
+ <div className={styles.carouselItem}>
229
+ {/* Current image */}
230
+ <img
231
+ src={validImages[currentIndex]}
232
+ alt={`Gallery image ${currentIndex + 1}`}
233
+ className={styles.galleryItemImage}
234
+ onClick={() =>
235
+ handleImageClick(validImages[currentIndex], currentIndex)
236
+ }
237
+ style={{
238
+ cursor: allowLightbox || onImageClick ? "pointer" : "default",
239
+ height: "400px",
240
+ }}
241
+ />
242
+
243
+ {/* Navigation */}
244
+ {validImages.length > 1 && (
245
+ <>
246
+ {currentIndex > 0 && (
247
+ <button
248
+ onClick={() => setCurrentIndex(currentIndex - 1)}
249
+ className={`${styles.carouselNav} ${styles.carouselNavPrev}`}
250
+ >
251
+
252
+ </button>
253
+ )}
254
+ {currentIndex < validImages.length - 1 && (
255
+ <button
256
+ onClick={() => setCurrentIndex(currentIndex + 1)}
257
+ className={`${styles.carouselNav} ${styles.carouselNavNext}`}
258
+ >
259
+
260
+ </button>
261
+ )}
262
+ </>
263
+ )}
264
+ </div>
265
+
266
+ {/* Thumbnail strip */}
267
+ {validImages.length > 1 && (
268
+ <div
269
+ style={{
270
+ display: "flex",
271
+ gap: "8px",
272
+ marginTop: "12px",
273
+ overflowX: "auto",
274
+ padding: "8px 0",
275
+ }}
276
+ >
277
+ {validImages.map((image, index) => (
278
+ <img
279
+ key={index}
280
+ src={image}
281
+ alt={`Thumbnail ${index + 1}`}
282
+ style={{
283
+ width: "60px",
284
+ height: "60px",
285
+ objectFit: "cover",
286
+ borderRadius: "4px",
287
+ border:
288
+ index === currentIndex
289
+ ? "2px solid var(--colour-branding-primary)"
290
+ : "1px solid var(--border-line-grey)",
291
+ cursor: "pointer",
292
+ opacity: index === currentIndex ? 1 : 0.6,
293
+ }}
294
+ onClick={() => setCurrentIndex(index)}
295
+ />
296
+ ))}
297
+ </div>
298
+ )}
299
+ </div>
300
+ );
301
+ }
302
+
303
+ // Fallback to grid
304
+ return (
305
+ <div
306
+ style={{
307
+ display: "grid",
308
+ gridTemplateColumns: `repeat(${columns}, 1fr)`,
309
+ gap: "12px",
310
+ ...style,
311
+ }}
312
+ >
313
+ {validImages.map((image, index) => (
314
+ <img
315
+ key={index}
316
+ src={image}
317
+ alt={`Gallery image ${index + 1}`}
318
+ style={{
319
+ width: "100%",
320
+ aspectRatio: "1",
321
+ objectFit: "cover",
322
+ borderRadius: "8px",
323
+ border: "1px solid var(--border-line-grey)",
324
+ }}
325
+ />
326
+ ))}
327
+ </div>
328
+ );
329
+ };
330
+
331
+ export default GalleryDisplay;
@@ -0,0 +1,309 @@
1
+ /* Empty gallery container */
2
+ .emptyGallery {
3
+ border: 2px dashed var(--border-line-grey);
4
+ border-radius: var(--border-radius-lg);
5
+ padding: 2.5rem 1.25rem;
6
+ text-align: center;
7
+ }
8
+
9
+ /* Empty gallery text */
10
+ .emptyGalleryText {
11
+ color: var(--text-bluegrey);
12
+ }
13
+
14
+ /* Grid layout container */
15
+ .gridContainer {
16
+ display: grid;
17
+ grid-template-columns: repeat(var(--columns, 3), 1fr);
18
+ gap: 1rem;
19
+ }
20
+
21
+ /* List layout container */
22
+ .listContainer {
23
+ display: flex;
24
+ flex-direction: column;
25
+ gap: 1rem;
26
+ }
27
+
28
+ /* Carousel container */
29
+ .carouselContainer {
30
+ position: relative;
31
+ overflow: hidden;
32
+ border-radius: var(--border-radius-lg);
33
+ }
34
+
35
+ /* Carousel track */
36
+ .carouselTrack {
37
+ display: flex;
38
+ transition: transform var(--transition-base) ease;
39
+ }
40
+
41
+ /* Carousel item */
42
+ .carouselItem {
43
+ flex: 0 0 100%;
44
+ width: 100%;
45
+ }
46
+
47
+ /* Carousel navigation */
48
+ .carouselNav {
49
+ position: absolute;
50
+ top: 50%;
51
+ transform: translateY(-50%);
52
+ background: rgba(0, 0, 0, 0.5);
53
+ color: white;
54
+ border: none;
55
+ border-radius: var(--border-radius-full);
56
+ width: 2.5rem;
57
+ height: 2.5rem;
58
+ display: flex;
59
+ align-items: center;
60
+ justify-content: center;
61
+ cursor: pointer;
62
+ transition: background var(--transition-base) ease;
63
+ }
64
+
65
+ .carouselNav:hover {
66
+ background: rgba(0, 0, 0, 0.7);
67
+ }
68
+
69
+ .carouselNavPrev {
70
+ left: 0.75rem;
71
+ }
72
+
73
+ .carouselNavNext {
74
+ right: 0.75rem;
75
+ }
76
+
77
+ /* Gallery item */
78
+ .galleryItem {
79
+ position: relative;
80
+ border-radius: var(--border-radius-lg);
81
+ overflow: hidden;
82
+ background-color: var(--bg-bluegrey);
83
+ cursor: pointer;
84
+ transition:
85
+ transform var(--transition-base) ease,
86
+ box-shadow var(--transition-base) ease;
87
+ }
88
+
89
+ .galleryItem:hover {
90
+ transform: translateY(-2px);
91
+ box-shadow: var(--shadow-lg);
92
+ }
93
+
94
+ /* Gallery item image */
95
+ .galleryItemImage {
96
+ width: 100%;
97
+ height: 12.5rem;
98
+ object-fit: cover;
99
+ display: block;
100
+ }
101
+
102
+ /* List item image */
103
+ .listItemImage {
104
+ width: 120px;
105
+ height: 120px;
106
+ object-fit: cover;
107
+ border-radius: var(--border-radius-lg);
108
+ flex-shrink: 0;
109
+ }
110
+
111
+ /* Remove button */
112
+ .removeButton {
113
+ position: absolute;
114
+ top: 0.5rem;
115
+ right: 0.5rem;
116
+ background: transparent;
117
+ color: var(--colour-branding-primary);
118
+ border: none;
119
+ border-radius: var(--border-radius-full);
120
+ width: 1.75rem;
121
+ height: 1.75rem;
122
+ display: flex;
123
+ align-items: center;
124
+ justify-content: center;
125
+ cursor: pointer;
126
+ opacity: 0;
127
+ transition: opacity var(--transition-base) ease;
128
+ }
129
+
130
+ .galleryItem:hover .removeButton {
131
+ opacity: 1;
132
+ }
133
+
134
+ /* Dragging state */
135
+ .dragging {
136
+ opacity: 0.5;
137
+ }
138
+
139
+ .dragOver {
140
+ border: 2px dashed var(--colour-branding-action);
141
+ }
142
+
143
+ /* Lightbox */
144
+ .lightbox {
145
+ position: fixed;
146
+ top: 0;
147
+ left: 0;
148
+ right: 0;
149
+ bottom: 0;
150
+ background: rgba(0, 0, 0, 0.9);
151
+ z-index: 1000;
152
+ display: flex;
153
+ align-items: center;
154
+ justify-content: center;
155
+ }
156
+
157
+ .lightboxImage {
158
+ max-width: 90%;
159
+ max-height: 90%;
160
+ object-fit: contain;
161
+ border-radius: var(--border-radius-lg);
162
+ }
163
+
164
+ .lightboxClose {
165
+ position: absolute;
166
+ top: 1.25rem;
167
+ right: 1.25rem;
168
+ background: rgba(255, 255, 255, 0.2);
169
+ color: white;
170
+ border: none;
171
+ border-radius: var(--border-radius-full);
172
+ width: 2.5rem;
173
+ height: 2.5rem;
174
+ display: flex;
175
+ align-items: center;
176
+ justify-content: center;
177
+ cursor: pointer;
178
+ transition: background var(--transition-base) ease;
179
+ }
180
+
181
+ .lightboxClose:hover {
182
+ background: rgba(255, 255, 255, 0.3);
183
+ }
184
+
185
+ /* List item content */
186
+ .listItemContent {
187
+ display: flex;
188
+ gap: 1rem;
189
+ align-items: center;
190
+ padding: 1rem;
191
+ background-color: var(--bg-white);
192
+ border-radius: var(--border-radius-lg);
193
+ border: 1px solid var(--border-line-grey);
194
+ }
195
+
196
+ .listItemText {
197
+ flex: 1;
198
+ }
199
+
200
+ .listItemTitle {
201
+ margin-bottom: 0.1rem;
202
+ }
203
+
204
+ .listItemActions {
205
+ display: flex;
206
+ gap: 8px;
207
+ }
208
+
209
+ /* List layout remove button (always visible) */
210
+ .listItemContent .removeButton {
211
+ opacity: 1;
212
+ position: static;
213
+ background: transparent;
214
+ color: var(--colour-branding-primary);
215
+ }
216
+
217
+ /* Drag handle */
218
+ .dragHandle {
219
+ position: absolute;
220
+ top: 50%;
221
+ left: 50%;
222
+ transform: translate(-50%, -50%);
223
+ background: rgba(0, 0, 0, 0.5);
224
+ color: white;
225
+ border: none;
226
+ border-radius: var(--border-radius-base);
227
+ padding: 0.1rem 0.2rem;
228
+ font-size: var(--font-size-xs);
229
+ cursor: move;
230
+ opacity: 0;
231
+ transition: opacity var(--transition-base) ease;
232
+ }
233
+
234
+ .galleryItem:hover .dragHandle {
235
+ opacity: 1;
236
+ }
237
+
238
+ /* Carousel dots */
239
+ .carouselDots {
240
+ display: flex;
241
+ justify-content: center;
242
+ gap: 8px;
243
+ margin-top: 12px;
244
+ }
245
+
246
+ .carouselDot {
247
+ width: 8px;
248
+ height: 8px;
249
+ border-radius: 50%;
250
+ background: var(--border-line-grey);
251
+ cursor: pointer;
252
+ transition: background var(--transition-base) ease;
253
+ }
254
+
255
+ .carouselDot.active {
256
+ background: var(--colour-branding-action);
257
+ }
258
+
259
+ /* Reorder button */
260
+ .reorderButton {
261
+ background: var(--colour-branding-action);
262
+ color: white;
263
+ border: none;
264
+ border-radius: var(--border-radius-base);
265
+ padding: 0.1rem 0.2rem;
266
+ font-size: var(--font-size-xs);
267
+ cursor: pointer;
268
+ transition: background var(--transition-base) ease;
269
+ }
270
+
271
+ .reorderButton:hover {
272
+ background: var(--colour-branding-action-hover);
273
+ }
274
+
275
+ /* Text styling */
276
+ .textBlueGrey {
277
+ color: var(--text-bluegrey);
278
+ }
279
+
280
+ /* Flex utilities */
281
+ .flex1 {
282
+ flex: 1;
283
+ }
284
+
285
+ .flex {
286
+ display: flex;
287
+ }
288
+
289
+ .gap8 {
290
+ gap: 0.2rem;
291
+ }
292
+
293
+ .listItemActions {
294
+ display: flex;
295
+ gap: 0.2rem;
296
+ }
297
+
298
+ /* Carousel navigation specific styling for carousel layout */
299
+ .carouselNav:not(.carouselNavPrev):not(.carouselNavNext) {
300
+ left: 0.3rem;
301
+ }
302
+
303
+ .carouselNav.carouselNavPrev {
304
+ left: 0.3rem;
305
+ }
306
+
307
+ .carouselNav.carouselNavNext {
308
+ right: 12px;
309
+ }
@@ -0,0 +1,82 @@
1
+ import React from "react";
2
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
3
+ import { GenericInput, Text } from "../index.js";
4
+ import { iconImports } from "../iconImports.js";
5
+ import styles from "./ListingField.module.css";
6
+
7
+ const ListingCTAInput = ({
8
+ field,
9
+ value,
10
+ onChange,
11
+ showError,
12
+ errorMessage,
13
+ }) => {
14
+ const { label, placeholder, isRequired } = field.values;
15
+
16
+ // CTA field value should contain both label and url
17
+ const fieldValue = value || {};
18
+ const buttonLabel = fieldValue.label || "";
19
+ const buttonUrl = fieldValue.url || "";
20
+
21
+ const handleLabelChange = (e) => {
22
+ const newValue = { ...fieldValue, label: e.target.value };
23
+ onChange(field.id, newValue);
24
+ };
25
+
26
+ const handleUrlChange = (e) => {
27
+ const newValue = { ...fieldValue, url: e.target.value };
28
+ onChange(field.id, newValue);
29
+ };
30
+
31
+ const createLabel = (text, required) => (
32
+ <>
33
+ {text}
34
+ {required && <span className={styles.requiredAsterisk}>*</span>}
35
+ </>
36
+ );
37
+
38
+ return (
39
+ <>
40
+ {/* Field header with icon and description */}
41
+ <div className={styles.listingField__header}>
42
+ <div className={styles.listingField__icon}>
43
+ <FontAwesomeIcon icon={iconImports.link} />
44
+ </div>
45
+ <div className={styles.listingField__content}>
46
+ <h3 className={styles.listingField__title}>Action Button</h3>
47
+ <p className={styles.listingField__description}>
48
+ Create clickable buttons with custom text and links
49
+ </p>
50
+ </div>
51
+ </div>
52
+
53
+ <GenericInput
54
+ type="text"
55
+ label={createLabel(`${label} - Button Text`, isRequired)}
56
+ placeholder="Enter button text"
57
+ value={buttonLabel}
58
+ onChange={handleLabelChange}
59
+ isRequired={isRequired}
60
+ isValid={!isRequired || buttonLabel.trim().length > 0}
61
+ showError={showError && isRequired && buttonLabel.trim().length === 0}
62
+ errorMessage="Button text is required"
63
+ alwaysShowLabel
64
+ />
65
+ <GenericInput
66
+ type="text"
67
+ label={createLabel(`${label} - Button URL`, isRequired)}
68
+ placeholder="Enter button URL (e.g., https://example.com)"
69
+ value={buttonUrl}
70
+ onChange={handleUrlChange}
71
+ isRequired={isRequired}
72
+ isValid={!isRequired || buttonUrl.trim().length > 0}
73
+ showError={showError && isRequired && buttonUrl.trim().length === 0}
74
+ errorMessage="Button URL is required"
75
+ help="Enter the full URL including https:// or http://"
76
+ alwaysShowLabel
77
+ />
78
+ </>
79
+ );
80
+ };
81
+
82
+ export default ListingCTAInput;