@plusscommunities/pluss-feature-builder-web-d 1.0.7 → 1.0.9-beta.3

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 (119) hide show
  1. package/dist/{index.cjs.js → index.js} +3803 -3504
  2. package/dist/index.js.map +1 -0
  3. package/package.json +20 -27
  4. package/.babelrc +0 -4
  5. package/rollup.config.js +0 -69
  6. package/src/actions/featureBuilderStringsActions.js +0 -88
  7. package/src/actions/featureDefinitionsIndex.js +0 -258
  8. package/src/actions/formActions.js +0 -301
  9. package/src/actions/index.js +0 -12
  10. package/src/actions/listingActions.js +0 -352
  11. package/src/actions/wizardActions.js +0 -228
  12. package/src/components/ActivityCardExample.jsx +0 -86
  13. package/src/components/ActivityCardExample.module.css +0 -130
  14. package/src/components/BackgroundLoader.jsx +0 -33
  15. package/src/components/BackgroundLoader.module.css +0 -46
  16. package/src/components/BaseFieldConfig.jsx +0 -305
  17. package/src/components/BaseFieldConfig.module.css +0 -42
  18. package/src/components/CenteredContainer.jsx +0 -29
  19. package/src/components/CenteredContainer.module.css +0 -171
  20. package/src/components/DeleteConfirmationPopup.jsx +0 -95
  21. package/src/components/DeleteConfirmationPopup.module.css +0 -12
  22. package/src/components/ErrorBoundary.jsx +0 -134
  23. package/src/components/ErrorBoundary.module.css +0 -77
  24. package/src/components/ErrorMessage.jsx +0 -85
  25. package/src/components/ErrorMessage.module.css +0 -116
  26. package/src/components/ExampleDisplay.jsx +0 -26
  27. package/src/components/ExampleDisplay.module.css +0 -3
  28. package/src/components/FeatureBuilderSidebar.jsx +0 -84
  29. package/src/components/FeatureBuilderSuccessPopup.jsx +0 -49
  30. package/src/components/FeatureBuilderSuccessPopup.module.css +0 -41
  31. package/src/components/FeatureBuilderWelcomePopup.jsx +0 -51
  32. package/src/components/FeatureBuilderWelcomePopup.module.css +0 -21
  33. package/src/components/FeatureListingCard.jsx +0 -104
  34. package/src/components/FeatureListingCard.module.css +0 -62
  35. package/src/components/Fields.jsx +0 -423
  36. package/src/components/Fields.module.css +0 -159
  37. package/src/components/IconLoader.jsx +0 -153
  38. package/src/components/IconLoader.module.css +0 -92
  39. package/src/components/IconSelector.jsx +0 -111
  40. package/src/components/IconSelector.module.css +0 -197
  41. package/src/components/ListingEditor.jsx +0 -405
  42. package/src/components/ListingEditor.module.css +0 -14
  43. package/src/components/ListingSuccessPopup.jsx +0 -52
  44. package/src/components/LoadingScreen.jsx +0 -54
  45. package/src/components/LoadingScreen.module.css +0 -103
  46. package/src/components/LoadingState.jsx +0 -40
  47. package/src/components/LoadingState.module.css +0 -18
  48. package/src/components/PreviewFull.js +0 -24
  49. package/src/components/PreviewFull.module.css +0 -11
  50. package/src/components/PreviewGrid.js +0 -14
  51. package/src/components/PreviewWidget.js +0 -27
  52. package/src/components/PreviewWidget.module.css +0 -15
  53. package/src/components/SidebarLayout.jsx +0 -252
  54. package/src/components/SidebarLayout.module.css +0 -71
  55. package/src/components/SkeletonLoader.jsx +0 -128
  56. package/src/components/SkeletonLoader.module.css +0 -295
  57. package/src/components/SortButtonGroup.jsx +0 -34
  58. package/src/components/SortButtonGroup.module.css +0 -51
  59. package/src/components/ToastContainer.jsx +0 -98
  60. package/src/components/ToastContainer.module.css +0 -156
  61. package/src/components/ToggleSwitch.js +0 -40
  62. package/src/components/ToggleSwitch.module.css +0 -48
  63. package/src/components/TwoColumnInput.jsx +0 -29
  64. package/src/components/TwoColumnInput.module.css +0 -32
  65. package/src/components/ViewFull.js +0 -139
  66. package/src/components/ViewFull.module.css +0 -71
  67. package/src/components/ViewWidget.js +0 -62
  68. package/src/components/ViewWidget.module.css +0 -28
  69. package/src/components/iconCategories.js +0 -135
  70. package/src/components/iconImports.js +0 -409
  71. package/src/components/index.js +0 -59
  72. package/src/components/listing/FileListItem.jsx +0 -86
  73. package/src/components/listing/GalleryDisplay.jsx +0 -330
  74. package/src/components/listing/GalleryDisplay.module.css +0 -309
  75. package/src/components/listing/ListingCTAInput.jsx +0 -82
  76. package/src/components/listing/ListingDescriptionInput.jsx +0 -73
  77. package/src/components/listing/ListingField.jsx +0 -101
  78. package/src/components/listing/ListingField.module.css +0 -106
  79. package/src/components/listing/ListingFileInput.jsx +0 -273
  80. package/src/components/listing/ListingFileInput.module.css +0 -189
  81. package/src/components/listing/ListingForm.jsx +0 -90
  82. package/src/components/listing/ListingForm.module.css +0 -38
  83. package/src/components/listing/ListingGalleryInput.jsx +0 -239
  84. package/src/components/listing/ListingGalleryInput.module.css +0 -132
  85. package/src/components/listing/ListingImageInput.jsx +0 -153
  86. package/src/components/listing/ListingTextInput.jsx +0 -72
  87. package/src/feature.config.js +0 -130
  88. package/src/helper/index.js +0 -135
  89. package/src/hooks/useFeatureDefinitionLoader.js +0 -66
  90. package/src/images/full.png +0 -0
  91. package/src/images/fullNoTitle.png +0 -0
  92. package/src/images/previewWidget.png +0 -0
  93. package/src/images/widget.png +0 -0
  94. package/src/index.js +0 -38
  95. package/src/pages/CreateListingPage.jsx +0 -49
  96. package/src/pages/EditListingPage.jsx +0 -58
  97. package/src/reducers/featureBuilderReducer.js +0 -739
  98. package/src/screens/CreateListing.module.css +0 -45
  99. package/src/screens/Form.module.css +0 -744
  100. package/src/screens/FormFieldsStep.jsx +0 -626
  101. package/src/screens/FormLayoutStep.jsx +0 -405
  102. package/src/screens/FormOverviewStep.jsx +0 -389
  103. package/src/screens/ListingScreen.jsx +0 -477
  104. package/src/screens/ListingScreen.module.css +0 -333
  105. package/src/selectors/featureBuilderSelectors.js +0 -533
  106. package/src/types/index.js +0 -91
  107. package/src/utils/textUtils.js +0 -89
  108. package/src/validators/galleryValidators.js +0 -345
  109. package/src/values.config.a.js +0 -49
  110. package/src/values.config.b.js +0 -49
  111. package/src/values.config.c.js +0 -49
  112. package/src/values.config.d.js +0 -49
  113. package/src/values.config.default.js +0 -49
  114. package/src/values.config.js +0 -49
  115. package/src/webapi/featureDefinitionActions.js +0 -0
  116. package/src/webapi/featuresActions.js +0 -90
  117. package/src/webapi/helper.js +0 -4
  118. package/src/webapi/index.js +0 -12
  119. package/src/webapi/listingActions.js +0 -176
@@ -1,189 +0,0 @@
1
- /* Enhanced File Input Styles - Based on existing PlussCore FileInput styling */
2
-
3
- /* Main container for enhanced file input */
4
- .listingFileInput {
5
- width: 100%;
6
- }
7
-
8
- /* Drop zone area - reusing existing imageInput styling */
9
- .listingFileInput__dropZone {
10
- width: 100%;
11
- margin-bottom: 1rem;
12
- }
13
-
14
- /* Drop zone error state */
15
- .listingFileInput__dropZone--error {
16
- border: 1px solid var(--colour-error);
17
- border-radius: var(--border-radius-base);
18
- padding: 0.25rem;
19
- background-color: var(--colour-error-light, rgba(220, 53, 69, 0.05));
20
- }
21
-
22
- .listingFileInput__dropZone .imageInputOuter-single {
23
- width: 100%;
24
- }
25
-
26
- .listingFileInput__dropZone .imageInput {
27
- width: 100%;
28
- margin-right: 0;
29
- }
30
-
31
- /* File list container */
32
- .listingFileInput__fileList {
33
- margin-top: 1rem;
34
- }
35
-
36
- /* Individual file item */
37
- .fileListItem {
38
- display: flex;
39
- align-items: center;
40
- gap: 0.75rem;
41
- background-color: var(--bg-white);
42
- transition: all var(--transition-base) ease-in-out;
43
- }
44
-
45
- .fileListItem:hover {
46
- box-shadow: 0 0 4px var(--colour-branding-action-alpha20);
47
- }
48
-
49
- .fileListItem__icon {
50
- color: var(--text-bluegrey);
51
- font-size: 2rem;
52
- flex-shrink: 0;
53
- text-align: center;
54
- }
55
-
56
- .fileListItem__input {
57
- min-width: 0;
58
- flex-grow: 1;
59
- }
60
-
61
- .fileListItem__nameInput {
62
- }
63
-
64
- .fileListItem__originalName {
65
- flex: 0 1 200px;
66
- color: var(--text-bluegrey);
67
- font-size: 16px;
68
- white-space: nowrap;
69
- overflow: hidden;
70
- text-overflow: ellipsis;
71
- }
72
-
73
- .fileListItem__actions {
74
- flex-shrink: 0;
75
- }
76
-
77
- .fileListItem__removeButton {
78
- font-size: 16px;
79
- width: 1.75rem;
80
- height: 1.75rem;
81
- border-radius: var(--border-radius-full);
82
- background-color: transparent;
83
- border: none;
84
- display: flex;
85
- color: var(--colour-branding-main);
86
- align-items: center;
87
- justify-content: center;
88
- padding: 0;
89
- min-height: auto;
90
- transition: all var(--transition-base) ease-in-out;
91
- }
92
-
93
- .fileListItem__removeButton:hover:not(:disabled) {
94
- color: var(--colour-branding-light);
95
- }
96
-
97
- .fileListItem__removeButton:disabled {
98
- opacity: 0.5;
99
- cursor: not-allowed;
100
- }
101
-
102
- /* Add more files button */
103
- .listingFileInput__addMore {
104
- margin-top: 0.75rem;
105
- text-align: center;
106
- }
107
-
108
- .listingFileInput__addMoreButton {
109
- font-size: var(--font-size-sm);
110
- padding: 0.5rem 1rem;
111
- }
112
-
113
- /* Upload progress styling */
114
- .fileListItem--uploading {
115
- background-color: var(--colour-branding-secondary-light);
116
- border-color: var(--colour-branding-action);
117
- }
118
-
119
- .fileListItem__progress {
120
- display: flex;
121
- align-items: center;
122
- gap: 0.5rem;
123
- font-size: var(--font-size-xs);
124
- color: var(--colour-branding-action);
125
- }
126
-
127
- .fileListItem__spinner {
128
- color: var(--colour-branding-action);
129
- animation: spin 1s linear infinite;
130
- }
131
-
132
- @keyframes spin {
133
- 0% {
134
- transform: rotate(0deg);
135
- }
136
- 100% {
137
- transform: rotate(360deg);
138
- }
139
- }
140
-
141
- /* Responsive design */
142
- @media (max-width: 768px) {
143
- .fileListItem {
144
- flex-direction: column;
145
- align-items: stretch;
146
- gap: 0.5rem;
147
- }
148
-
149
- .fileListItem__icon {
150
- text-align: center;
151
- }
152
-
153
- .fileListItem__actions {
154
- text-align: center;
155
- }
156
- }
157
-
158
- /* Integration with existing field styling */
159
- .listingFileInput__dropZone .imageInput_upload {
160
- background-color: var(--bg-white);
161
- transition: all var(--transition-base) ease-in-out;
162
- }
163
-
164
- .listingFileInput__dropZone .imageInput_upload:hover {
165
- background-color: var(--bg-bluegrey);
166
- }
167
-
168
- .listingFileInput__dropZone .imageInput_dropZoneActive {
169
- background-color: var(--bg-bluegrey);
170
- }
171
-
172
- .listingFileInput__dropZone .imageInput_icon {
173
- width: 1.5rem;
174
- height: 2rem;
175
- margin: 0 auto;
176
- display: block;
177
- }
178
-
179
- .listingFileInput__dropZone .imageInput_helpText {
180
- font-size: var(--font-size-base);
181
- color: var(--colour-lightgrey);
182
- margin-top: 1.25rem;
183
- text-align: center;
184
- }
185
-
186
- .listingFileInput__dropZone .imageInput_button {
187
- text-align: center;
188
- margin-top: 1rem;
189
- }
@@ -1,90 +0,0 @@
1
- import React, { useState } from "react";
2
- import { PlussCore } from "../../feature.config";
3
- const { Components } = PlussCore;
4
- const { Text, Button } = Components;
5
- import ListingField from "./ListingField.jsx";
6
- import styles from "./ListingForm.module.css";
7
- import formStyles from "../../screens/Form.module.css";
8
-
9
- const ListingForm = ({
10
- featureDefinition,
11
- onSubmit,
12
- mode = "create",
13
- initialData,
14
- onChange,
15
- disabled = false,
16
- hideSubmitButton = false,
17
- formData: externalFormData,
18
- setFormData: externalSetFormData,
19
- formErrors = {},
20
- showErrors = false,
21
- formErrorMessage = null, // For form-level validation errors like "Form data is invalid"
22
- }) => {
23
- // Use external state if provided, otherwise use internal state (backward compatibility)
24
- const [internalFormData, setInternalFormData] = useState(
25
- initialData?.fields || {},
26
- );
27
- const formData =
28
- externalFormData !== undefined ? externalFormData : internalFormData;
29
- const setFormData = externalSetFormData || setInternalFormData;
30
-
31
- React.useEffect(() => {
32
- // Only update if using internal state
33
- if (!externalSetFormData) {
34
- setFormData(initialData?.fields || {});
35
- }
36
- }, [initialData, externalSetFormData]);
37
-
38
- const handleFieldChange = (fieldId, value) => {
39
- const newFormData = { ...formData, [fieldId]: value };
40
-
41
- setFormData(newFormData);
42
- if (onChange) {
43
- onChange();
44
- }
45
- };
46
-
47
- return (
48
- <div className={styles.root}>
49
- {/* Form-level error message */}
50
- {formErrorMessage && (
51
- <div className={formStyles.errorMessageContainer}>
52
- <Text className={formStyles.errorMessage}>{formErrorMessage}</Text>
53
- </div>
54
- )}
55
-
56
- {featureDefinition.fields
57
- .slice()
58
- .sort((a, b) => a.order - b.order)
59
- .map((field) => (
60
- <ListingField
61
- key={field.id}
62
- field={field}
63
- value={formData[field.id]}
64
- onChange={handleFieldChange}
65
- showError={showErrors && formErrors[field.id]}
66
- errorMessage={formErrors[field.id]}
67
- isActive={showErrors && formErrors[field.id]}
68
- disabled={disabled}
69
- />
70
- ))}
71
-
72
- {!hideSubmitButton && (
73
- <Button
74
- onClick={() => onSubmit({ fields: formData })}
75
- disabled={disabled}
76
- isActive={true}
77
- buttonType="primary"
78
- >
79
- {disabled
80
- ? "Saving..."
81
- : mode === "create"
82
- ? "Create Listing"
83
- : "Update Listing"}
84
- </Button>
85
- )}
86
- </div>
87
- );
88
- };
89
-
90
- export default ListingForm;
@@ -1,38 +0,0 @@
1
- /* ListingForm CSS Module */
2
-
3
- /* Block */
4
- .root {
5
- display: flex;
6
- flex-direction: column;
7
- background-color: var(--bg-white);
8
- gap: 1.25rem;
9
- }
10
-
11
- .button {
12
- background-color: var(--colour-blue);
13
- color: var(--bg-white);
14
- border: none;
15
- padding: 1rem 1.5rem;
16
- border-radius: var(--border-radius-md);
17
- cursor: pointer;
18
- font-size: var(--font-size-base);
19
- font-weight: var(--font-weight-semibold);
20
- transition:
21
- background-color var(--transition-base) ease,
22
- transform var(--transition-fast) ease;
23
- min-height: 3rem;
24
- box-shadow: 0 2px 4px var(--text-dark-alpha20);
25
- }
26
-
27
- .button:hover:not(.button--disabled) {
28
- background-color: var(--colour-darkblue);
29
- transform: translateY(-1px);
30
- box-shadow: 0 4px 8px var(--text-dark-alpha20);
31
- }
32
-
33
- .button--disabled {
34
- background-color: var(--linegrey);
35
- cursor: not-allowed;
36
- transform: none;
37
- box-shadow: none;
38
- }
@@ -1,239 +0,0 @@
1
- import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
2
- import React, { useEffect, useRef, useState } from "react";
3
- import { ImageInput, Text } from "../../components";
4
- import { iconImports } from "../../components/iconImports.js";
5
- import styles from "./ListingField.module.css";
6
- import galleryStyles from "./ListingGalleryInput.module.css";
7
-
8
- const ListingGalleryInput = ({
9
- field,
10
- value,
11
- onChange,
12
- showError,
13
- errorMessage,
14
- disabled = false,
15
- }) => {
16
- const imageInputRef = useRef(null);
17
- const [images, setImages] = useState([]);
18
- const [validationErrors, setValidationErrors] = useState([]);
19
-
20
- // Initialize images from value prop
21
- useEffect(() => {
22
- if (Array.isArray(value)) {
23
- setImages(value);
24
- } else if (value && typeof value === "string") {
25
- // Handle legacy single image string
26
- setImages([value]);
27
- } else {
28
- setImages([]);
29
- }
30
- }, [value]);
31
-
32
- const handleGalleryChange = (imageData) => {
33
- let newImages = [];
34
-
35
- // Handle different data structures from ImageInput
36
- if (Array.isArray(imageData)) {
37
- newImages = imageData
38
- .map((img) => {
39
- if (typeof img === "object" && img !== null) {
40
- return img.url || img.src || img.imageUrl || JSON.stringify(img);
41
- }
42
- return img;
43
- })
44
- .filter((img) => img && img !== ""); // Remove empty values
45
- } else if (typeof imageData === "object" && imageData !== null) {
46
- newImages = [
47
- imageData.url ||
48
- imageData.src ||
49
- imageData.imageUrl ||
50
- JSON.stringify(imageData),
51
- ];
52
- } else if (imageData && imageData !== "") {
53
- newImages = [imageData];
54
- }
55
-
56
- setImages(newImages);
57
-
58
- // Validate images
59
- const errors = validateGallery(newImages);
60
- setValidationErrors(errors);
61
-
62
- // Pass array to parent
63
- onChange(field.id, newImages);
64
- };
65
-
66
- const validateGallery = (imageArray) => {
67
- const errors = [];
68
- const { values } = field;
69
-
70
- // Required field validation
71
- if (values.isRequired && (!imageArray || imageArray.length === 0)) {
72
- errors.push(`${values.label} is required`);
73
- }
74
-
75
- // Minimum images validation
76
- if (values.minImages && imageArray.length < values.minImages) {
77
- errors.push(
78
- `Minimum ${values.minImages} image${values.minImages > 1 ? "s" : ""} required`,
79
- );
80
- }
81
-
82
- // Maximum images validation
83
- if (values.maxImages && imageArray.length > values.maxImages) {
84
- errors.push(
85
- `Maximum ${values.maxImages} image${values.maxImages > 1 ? "s" : ""} allowed`,
86
- );
87
- }
88
-
89
- return errors;
90
- };
91
-
92
- const hasError = () => {
93
- if (showError) return true;
94
- return validationErrors.length > 0;
95
- };
96
-
97
- const getErrorMessage = () => {
98
- if (errorMessage) return errorMessage;
99
- if (validationErrors.length > 0) {
100
- return validationErrors[0]; // Show first validation error
101
- }
102
- return `${field.values.label} is required`;
103
- };
104
-
105
- const getImageCountText = () => {
106
- const { minImages, maxImages } = field.values;
107
- const count = images.length;
108
-
109
- if (minImages && maxImages) {
110
- return `${count} / ${minImages}-${maxImages} images`;
111
- } else if (minImages) {
112
- return `${count} / ${minImages}+ images`;
113
- } else if (maxImages) {
114
- return `${count} / ${maxImages} images`;
115
- }
116
- return `${count} images`;
117
- };
118
-
119
- const removeImage = (indexToRemove) => {
120
- const newImages = images.filter((_, index) => index !== indexToRemove);
121
- setImages(newImages);
122
-
123
- const errors = validateGallery(newImages);
124
- setValidationErrors(errors);
125
-
126
- onChange(field.id, newImages);
127
- };
128
-
129
- return (
130
- <div>
131
- {/* Field header with icon and description */}
132
- <div className={styles.listingField__header}>
133
- <div className={styles.listingField__icon}>
134
- <FontAwesomeIcon icon={iconImports.image} />
135
- </div>
136
- <div className={styles.listingField__content}>
137
- <h3 className={styles.listingField__title}>Image Gallery</h3>
138
- <p className={styles.listingField__description}>
139
- Upload multiple photos or images to create a gallery
140
- </p>
141
- </div>
142
- </div>
143
-
144
- <Text type="formLabel" className={galleryStyles.formLabel}>
145
- {field.values.label}
146
- {field.values.isRequired && (
147
- <span className={styles.requiredAsterisk}>*</span>
148
- )}
149
- </Text>
150
-
151
- {/* Image count indicator */}
152
- <Text type="help" className={galleryStyles.imageCountText}>
153
- {getImageCountText()}
154
- </Text>
155
-
156
- <div className={galleryStyles.galleryContainer}>
157
- <div className={hasError() ? galleryStyles.galleryErrorWrapper : ""}>
158
- <ImageInput
159
- ref={imageInputRef}
160
- containerStyle={{ width: "100%", height: 200 }}
161
- className="imageInputOuter-grid"
162
- refreshCallback={handleGalleryChange}
163
- hasDefault={null}
164
- multiple={true}
165
- limit={field.values.maxImages}
166
- disabled={disabled}
167
- noCompress={false}
168
- grid={true}
169
- simpleStyle={true}
170
- />
171
- {/* ImageInput expects a string URL for hasDefault, but we have an array.
172
- For now, passing null to prevent url.split error */}
173
- </div>
174
-
175
- {/* Image preview grid */}
176
- {images.length > 0 && (
177
- <div className={galleryStyles.imagePreviewContainer}>
178
- <Text type="formLabel" className={galleryStyles.formLabel}>
179
- Current Images ({images.length})
180
- </Text>
181
- <div
182
- className={galleryStyles.imageGrid}
183
- style={{
184
- "--columns": Math.min(images.length, 4),
185
- }}
186
- >
187
- {images.map((image, index) => {
188
- return (
189
- <div
190
- key={`image-${index}`}
191
- className={galleryStyles.imageItem}
192
- >
193
- <img
194
- src={image}
195
- alt={`Gallery item ${index + 1}`}
196
- className={galleryStyles.imageThumbnail}
197
- />
198
- {!disabled && (
199
- <button
200
- type="button"
201
- onClick={() => removeImage(index)}
202
- className={galleryStyles.removeImageButton}
203
- >
204
- <FontAwesomeIcon icon={iconImports.minusCircle} />
205
- </button>
206
- )}
207
- </div>
208
- );
209
- })}
210
- </div>
211
- </div>
212
- )}
213
-
214
- {hasError() && (
215
- <Text type="help" className={galleryStyles.errorMessage}>
216
- {getErrorMessage()}
217
- </Text>
218
- )}
219
- </div>
220
-
221
- {field.values.helpText && (
222
- <Text type="help" className={galleryStyles.helpText}>
223
- {field.values.helpText}
224
- </Text>
225
- )}
226
-
227
- {/* Validation hints */}
228
- {field.values.minImages || field.values.maxImages ? (
229
- <Text type="help" className={galleryStyles.validationHint}>
230
- {field.values.minImages && `Min: ${field.values.minImages} images`}
231
- {field.values.minImages && field.values.maxImages && " | "}
232
- {field.values.maxImages && `Max: ${field.values.maxImages} images`}
233
- </Text>
234
- ) : null}
235
- </div>
236
- );
237
- };
238
-
239
- export default ListingGalleryInput;
@@ -1,132 +0,0 @@
1
- /* ListingGalleryInput BEM CSS Module */
2
-
3
- /* Gallery container styles */
4
- .galleryContainer {
5
- margin-top: 0.75rem;
6
- max-width: 200px;
7
- }
8
-
9
- /* Error border wrapper */
10
- .galleryErrorWrapper {
11
- border: 1px solid var(--colour-error);
12
- border-radius: var(--border-radius-base);
13
- padding: 0.25rem;
14
- background-color: var(--colour-error-light, rgba(220, 53, 69, 0.05));
15
- }
16
-
17
- /* Image preview grid container */
18
- .imagePreviewContainer {
19
- margin-top: 1rem;
20
- }
21
-
22
- .imageGrid {
23
- display: grid;
24
- grid-template-columns: repeat(4, 160px);
25
- gap: 1rem;
26
- margin-top: 0.5rem;
27
- }
28
-
29
- /* Responsive grid for different screen sizes */
30
- @media (max-width: 768px) {
31
- .imageGrid {
32
- grid-template-columns: repeat(3, 1fr);
33
- gap: 0.75rem;
34
- }
35
- }
36
-
37
- @media (max-width: 480px) {
38
- .imageGrid {
39
- grid-template-columns: repeat(2, 1fr);
40
- gap: 0.5rem;
41
- }
42
- }
43
-
44
- /* Individual image item */
45
- .imageItem {
46
- position: relative;
47
- border-radius: var(--border-radius-lg);
48
- overflow: hidden;
49
- background-color: var(--bg-bluegrey);
50
- transition:
51
- transform var(--transition-base) ease,
52
- box-shadow var(--transition-base) ease;
53
- }
54
-
55
- .imageItem:hover {
56
- transform: translateY(-2px);
57
- box-shadow: var(--shadow-lg);
58
- }
59
-
60
- .imageThumbnail {
61
- width: 100%;
62
- height: 100px;
63
- object-fit: cover;
64
- display: block;
65
- aspect-ratio: 1;
66
- }
67
-
68
- /* Remove image button */
69
- .removeImageButton {
70
- position: absolute;
71
- top: 0.5rem;
72
- right: 0.5rem;
73
- background: transparent;
74
- color: var(--colour-branding-primary);
75
- border: none;
76
- border-radius: var(--border-radius-full);
77
- width: 1.75rem;
78
- height: 1.75rem;
79
- display: flex;
80
- align-items: center;
81
- justify-content: center;
82
- cursor: pointer;
83
- opacity: 0;
84
- transition: opacity var(--transition-base) ease;
85
- }
86
-
87
- .imageItem:hover .removeImageButton {
88
- opacity: 1;
89
- }
90
-
91
- .removeImageButton:hover:not(:disabled) {
92
- color: var(--colour-branding-light);
93
- }
94
-
95
- .removeImageButton:disabled {
96
- opacity: 0.5;
97
- cursor: not-allowed;
98
- }
99
-
100
- /* Error message styling */
101
- .errorMessage {
102
- color: var(--colour-red);
103
- margin-top: 0.5rem;
104
- display: block;
105
- }
106
-
107
- /* Help text styling */
108
- .helpText {
109
- color: var(--text-bluegrey);
110
- margin-top: 0.5rem;
111
- display: block;
112
- }
113
-
114
- /* Validation hints */
115
- .validationHint {
116
- color: var(--text-bluegrey);
117
- margin-top: 0.25rem;
118
- display: block;
119
- font-size: var(--font-size-xxs);
120
- }
121
-
122
- /* Form label styling */
123
- .formLabel {
124
- display: block;
125
- margin-bottom: 8px;
126
- }
127
-
128
- /* Help text for image count */
129
- .imageCountText {
130
- margin-bottom: 0.5rem;
131
- display: block;
132
- }