@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,159 @@
1
+ /* Fields BEM CSS Module - Using pluss-maintenance styling pattern */
2
+
3
+ /* Field container - matches maintenance style */
4
+ .fieldContainer {
5
+ position: relative;
6
+ display: flex;
7
+ margin-top: 32px;
8
+ }
9
+
10
+ /* Field container error state */
11
+ .fieldContainer--error {
12
+ border: 2px solid #dc3545;
13
+ border-radius: 4px;
14
+ }
15
+
16
+ .fieldNumberContainer {
17
+ display: flex;
18
+ width: 40px;
19
+ }
20
+
21
+ .fieldNumber {
22
+ width: 40px;
23
+ text-align: center;
24
+ font-weight: 500;
25
+ font-size: 30px;
26
+ color: var(--colour-branding-action, #5c90df); /* COLOUR_BRANDING_ACTION */
27
+ }
28
+
29
+ .field {
30
+ flex: 1;
31
+ position: relative;
32
+ box-shadow: 0 1px 5px rgba(0, 0, 0, 0.1);
33
+ border-radius: 4px;
34
+ padding: 1rem;
35
+ }
36
+
37
+ .field--invalid {
38
+ border: 1px solid #dc3545;
39
+ }
40
+
41
+ .fieldHeader {
42
+ display: flex;
43
+ flex-direction: row;
44
+ justify-content: space-between;
45
+ padding: 15px 0;
46
+ border-bottom: 1px solid var(--border-line-grey, #dbddf1); /* BORDER_LINE_GREY */
47
+ }
48
+
49
+ .fieldHeader .group {
50
+ display: flex;
51
+ flex-direction: row;
52
+ align-items: center;
53
+ }
54
+
55
+ .fieldHeader .line {
56
+ background-color: var(--colour-dusk, #536280); /* COLOUR_DUSK */
57
+ width: 34px;
58
+ height: 6px;
59
+ margin: 3px 13px 0 0;
60
+ }
61
+
62
+ .fieldHeader .fieldType {
63
+ font-weight: 700;
64
+ font-size: 16px;
65
+ color: var(--colour-dusk, #536280); /* COLOUR_DUSK */
66
+ }
67
+
68
+ .fieldHeader .dropdownInput {
69
+ width: 150px !important;
70
+ margin: 5px 20px 0 0 !important;
71
+ }
72
+
73
+ .fieldHeader .dropdownInput div {
74
+ margin-bottom: unset !important;
75
+ }
76
+
77
+ .fieldHeader .dropdownInput .dropdownInput-bottomBorder {
78
+ border-bottom: unset;
79
+ }
80
+
81
+ .fieldHeader .dropdownInput input {
82
+ font-size: 14px;
83
+ color: var(--colour-branding-action, #5c90df); /* COLOUR_BRANDING_ACTION */
84
+ width: 80%;
85
+ }
86
+
87
+ .fieldHeader .dropdownInput .fa-chevron-down,
88
+ .fieldHeader .dropdownInput .fa-chevron-up {
89
+ color: var(
90
+ --colour-branding-action,
91
+ #5c90df
92
+ ) !important; /* COLOUR_BRANDING_ACTION */
93
+ }
94
+
95
+ .fieldHeader .changeTypeButton {
96
+ margin-left: 15px;
97
+ padding: 4px 8px;
98
+ font-size: 12px;
99
+ min-height: auto;
100
+ color: #5c90df;
101
+ }
102
+
103
+ .fieldHeader .changeTypeButton:hover {
104
+ color: #364196;
105
+ }
106
+
107
+ .fieldHeader .deleteButton {
108
+ margin-right: 15px;
109
+ padding: 4px 8px;
110
+ font-size: 12px;
111
+ min-height: auto;
112
+ color: #dc3545;
113
+ }
114
+
115
+ .fieldHeader .deleteButton:hover {
116
+ color: #a02622;
117
+ }
118
+
119
+ .fieldHeader .buttonLabel {
120
+ margin-left: 6px;
121
+ font-size: 12px;
122
+ }
123
+
124
+ .fieldInner {
125
+ padding: 15px;
126
+ }
127
+
128
+ /* Legacy compatibility styles */
129
+ .fieldWrapper {
130
+ position: relative;
131
+ margin-bottom: 1.5rem;
132
+ padding: 1rem;
133
+ border-radius: 12px;
134
+ }
135
+
136
+ /* Padlock icon for base fields */
137
+ .padlockIcon {
138
+ display: inline-flex;
139
+ align-items: center;
140
+ font-size: 12px;
141
+ color: var(--colour-dusk, #536280);
142
+ font-style: italic;
143
+ display: flex;
144
+ align-items: center;
145
+ gap: 1rem;
146
+ }
147
+
148
+ .padlockIcon p {
149
+ padding: 0;
150
+ margin: 0;
151
+ margin-right: 1rem;
152
+ }
153
+
154
+ .padlockIconPlaceholder {
155
+ display: inline-flex;
156
+ align-items: center;
157
+ font-size: 12px;
158
+ color: var(--colour-dusk, #536280);
159
+ }
@@ -0,0 +1,153 @@
1
+ import React, { useState, useRef } from "react";
2
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
3
+ import { PlussCore } from "../feature.config";
4
+ import { Button } from "./index";
5
+ import { iconImports } from "./iconImports";
6
+ import styles from "./IconLoader.module.css";
7
+
8
+ const { Apis } = PlussCore;
9
+ const { fileActions } = Apis;
10
+
11
+ const IconLoader = ({
12
+ value,
13
+ defaultValue,
14
+ onChange,
15
+ onRemove,
16
+ disableRemove = false,
17
+ featureId,
18
+ }) => {
19
+ const [isUploading, setIsUploading] = useState(false);
20
+ const fileInputRef = useRef(null);
21
+
22
+ // Determine which icon to show
23
+ const hasCustomIcon =
24
+ value && typeof value === "string" && value.startsWith("http");
25
+ const iconToShow = defaultValue || "star";
26
+
27
+ // Generate filename with feature ID
28
+ const generateFilename = (file, featureId) => {
29
+ const timestamp = Date.now();
30
+ const ext = file.name.split(".").pop();
31
+ return `build-your-feature-${featureId || "new"}-${timestamp}.${ext}`;
32
+ };
33
+
34
+ // Get FontAwesome icon component directly (no async needed)
35
+ const iconComponent = !hasCustomIcon ? iconImports[iconToShow] : null;
36
+
37
+ const handleFileSelect = async (event) => {
38
+ const file = event.target.files[0];
39
+ if (!file || isUploading) return;
40
+
41
+ // Validate file type - only accept images
42
+ if (!file.type.startsWith("image/")) {
43
+ alert("Please upload an image file (JPEG, PNG, GIF, or WebP)");
44
+ return;
45
+ }
46
+
47
+ setIsUploading(true);
48
+
49
+ try {
50
+ // Compress and upload the image
51
+ const compressedFile = await fileActions.compressImage(
52
+ file,
53
+ 1400,
54
+ 0.8,
55
+ false,
56
+ );
57
+
58
+ const filename = generateFilename(compressedFile, featureId);
59
+ const url = await fileActions.uploadMediaAsync(compressedFile, filename);
60
+
61
+ if (onChange) {
62
+ onChange(url);
63
+ }
64
+ } catch (error) {
65
+ alert("Failed to upload image. Please try again.");
66
+ } finally {
67
+ setIsUploading(false);
68
+ // Reset file input
69
+ if (fileInputRef.current) {
70
+ fileInputRef.current.value = "";
71
+ }
72
+ }
73
+ };
74
+
75
+ const handleUploadClick = () => {
76
+ if (fileInputRef.current && !isUploading) {
77
+ fileInputRef.current.click();
78
+ }
79
+ };
80
+
81
+ const handleRemove = () => {
82
+ if (onRemove && !disableRemove) {
83
+ onRemove();
84
+ }
85
+ };
86
+
87
+ // Container will use CSS modules classes
88
+ // Custom styling through props is deprecated in favor of CSS consistency
89
+
90
+ return (
91
+ <div className={`${styles.iconLoader} ${styles.iconLoader__container}`}>
92
+ {/* Show the icon/image */}
93
+ {hasCustomIcon ? (
94
+ <img
95
+ src={value}
96
+ alt="Custom icon"
97
+ className={styles.iconLoader__image}
98
+ />
99
+ ) : (
100
+ <div className={styles.iconLoader__iconContainer}>
101
+ {iconComponent ? (
102
+ <FontAwesomeIcon
103
+ icon={iconComponent}
104
+ className={`${styles.iconLoader__icon} ${styles.iconLoader__iconLarge}`}
105
+ />
106
+ ) : (
107
+ <div className={styles.iconLoader__fallbackText}>{iconToShow}</div>
108
+ )}
109
+ </div>
110
+ )}
111
+
112
+ {/* Upload/Delete button overlay - only shows on hover */}
113
+ <div className={styles.iconLoader__buttonOverlay}>
114
+ {hasCustomIcon ? (
115
+ <Button
116
+ buttonType="secondary"
117
+ isActive
118
+ onClick={handleRemove}
119
+ disabled={disableRemove || isUploading}
120
+ size="small"
121
+ leftIcon="times"
122
+ aria-label="Delete icon"
123
+ >
124
+ Delete
125
+ </Button>
126
+ ) : (
127
+ <Button
128
+ buttonType="primary"
129
+ onClick={() => { }}
130
+ disabled={isUploading}
131
+ size="small"
132
+ leftIcon="upload"
133
+ loading={isUploading}
134
+ aria-label="Upload icon"
135
+ >
136
+ Upload
137
+ </Button>
138
+ )}
139
+ </div>
140
+
141
+ {/* Hidden file input */}
142
+ <input
143
+ ref={fileInputRef}
144
+ type="file"
145
+ accept="image/jpeg,image/png,image/gif,image/webp,image/svg"
146
+ onChange={handleFileSelect}
147
+ className={styles.iconLoader__hiddenInput}
148
+ />
149
+ </div>
150
+ );
151
+ };
152
+
153
+ export default IconLoader;
@@ -0,0 +1,92 @@
1
+ /* IconLoader BEM CSS Module */
2
+
3
+ /* Block */
4
+ .iconLoader {
5
+ border-radius: 50%;
6
+ overflow: hidden;
7
+ position: relative;
8
+ background-color: #f8f9fa;
9
+ display: flex;
10
+ align-items: center;
11
+ justify-content: center;
12
+ }
13
+
14
+ /* Container variants */
15
+ .iconLoader__container {
16
+ width: 150px;
17
+ height: 150px;
18
+ border: 3px solid #e9ecef;
19
+ }
20
+
21
+ /* Elements */
22
+ .iconLoader__image {
23
+ width: 100%;
24
+ height: 100%;
25
+ object-fit: cover;
26
+ object-position: center;
27
+ position: absolute;
28
+ top: 0;
29
+ left: 0;
30
+ }
31
+
32
+ .iconLoader__iconContainer {
33
+ display: flex;
34
+ align-items: center;
35
+ justify-content: center;
36
+ width: 100%;
37
+ height: 100%;
38
+ }
39
+
40
+ .iconLoader__icon {
41
+ font-size: var(--font-size-5xl);
42
+ color: #333;
43
+ }
44
+
45
+ /* Icon size variants */
46
+ .iconLoader__iconLarge {
47
+ font-size: 60px;
48
+ color: #333;
49
+ }
50
+
51
+ /* Button icon styling - no longer needed since we use leftIcon prop */
52
+ /* .iconLoader__buttonIcon {
53
+ margin-right: 6px;
54
+ } */
55
+
56
+ /* Hidden input */
57
+ .iconLoader__hiddenInput {
58
+ display: none !important;
59
+ }
60
+
61
+ .iconLoader__fallbackText {
62
+ font-size: var(--font-size-sm);
63
+ color: #6c757d;
64
+ text-align: center;
65
+ padding: 8px;
66
+ }
67
+
68
+ /* Button overlay - shows on hover */
69
+ .iconLoader__buttonOverlay {
70
+ position: absolute;
71
+ top: 50%;
72
+ left: 50%;
73
+ transform: translate(-50%, -50%);
74
+ z-index: 10;
75
+ opacity: 0;
76
+ visibility: hidden;
77
+ transition: all 0.2s ease;
78
+ background-color: rgba(255, 255, 255, 0.95);
79
+ border-radius: 8px;
80
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
81
+ }
82
+
83
+ /* Show button overlay on hover */
84
+ .iconLoader:hover .iconLoader__buttonOverlay {
85
+ opacity: 1;
86
+ visibility: visible;
87
+ }
88
+
89
+ /* Hidden input */
90
+ .iconLoader__hiddenInput {
91
+ display: none !important;
92
+ }
@@ -0,0 +1,112 @@
1
+ import React, { useState } from "react";
2
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
3
+ import { PlussCore } from "../feature.config";
4
+ const { Text } = PlussCore.Components;
5
+ import { allIcons, defaultIcon } from "./iconCategories";
6
+ import { iconImports } from "./iconImports";
7
+ import styles from "./IconSelector.module.css";
8
+
9
+
10
+ const IconSelector = ({
11
+ selectedIcon,
12
+ onIconSelect,
13
+ className,
14
+ stepErrors,
15
+ showError,
16
+ }) => {
17
+ const [hoveredIcon, setHoveredIcon] = useState(null);
18
+
19
+ // Get selected icon directly (no async needed)
20
+ const iconToLoad = selectedIcon || defaultIcon;
21
+ const currentIconComponent = iconImports[iconToLoad];
22
+
23
+ const handleIconClick = (icon) => {
24
+ onIconSelect(icon);
25
+ };
26
+
27
+ return (
28
+ <div className={`${styles.iconSelector} ${className || ""}`}>
29
+ {/* Header */}
30
+ <Text type="formTitleSmall" className={styles.title}>
31
+ Select icon
32
+ </Text>
33
+ <Text type="body" className={styles.subtitle}>
34
+ Select an icon to use for the feature when shown in the Community
35
+ Manager's menu
36
+ </Text>
37
+
38
+ {/* Current Selection */}
39
+ <div className={styles.currentSelection}>
40
+ <div className={styles.currentSelectionHeader}>
41
+ <Text type="formLabel" className={styles.currentSelectionLabel}>
42
+ Current selection
43
+ </Text>
44
+ {showError && stepErrors && stepErrors.icon && (
45
+ <div className={styles.fieldError} role="alert" id="icon-error">
46
+ <span className={styles.errorIcon}>⚠️</span>
47
+ {stepErrors.icon}
48
+ </div>
49
+ )}
50
+ </div>
51
+ <div className={styles.currentSelectionDisplay}>
52
+ {currentIconComponent ? (
53
+ <FontAwesomeIcon
54
+ icon={currentIconComponent}
55
+ className={styles.selectedIcon}
56
+ />
57
+ ) : (
58
+ <span className={styles.iconName}>
59
+ {selectedIcon || defaultIcon}
60
+ </span>
61
+ )}
62
+ </div>
63
+ </div>
64
+
65
+ {/* Simple Flat Icon Grid - matching image exactly */}
66
+ <div className={styles.iconGridContainer}>
67
+ <div className={styles.iconGrid}>
68
+ {allIcons.map((icon) => {
69
+ const isSelected = selectedIcon === icon;
70
+ const isHovered = hoveredIcon === icon;
71
+
72
+ return (
73
+ <button
74
+ key={icon}
75
+ className={`
76
+ ${styles.iconButton}
77
+ ${isSelected ? styles.selected : ""}
78
+ ${isHovered ? styles.hovered : ""}
79
+ `}
80
+ onClick={() => handleIconClick(icon)}
81
+ onMouseEnter={() => setHoveredIcon(icon)}
82
+ onMouseLeave={() => setHoveredIcon(null)}
83
+ aria-pressed={isSelected}
84
+ aria-label={`Select ${icon} icon${isSelected ? ", currently selected" : ""}`}
85
+ aria-describedby={
86
+ showError && stepErrors && stepErrors.icon
87
+ ? "icon-error"
88
+ : undefined
89
+ }
90
+ >
91
+ <Icon iconName={icon} size="lg" />
92
+ </button>
93
+ );
94
+ })}
95
+ </div>
96
+ </div>
97
+ </div>
98
+ );
99
+ };
100
+
101
+ // Simple static icon component
102
+ const Icon = ({ iconName, size }) => {
103
+ const iconDef = iconImports[iconName];
104
+
105
+ if (!iconDef) {
106
+ return <span className={styles.iconFallback}>{iconName}</span>;
107
+ }
108
+
109
+ return <FontAwesomeIcon icon={iconDef} size={size} />;
110
+ };
111
+
112
+ export default IconSelector;
@@ -0,0 +1,197 @@
1
+ /* Icon Selector Component Styles - Simple Flat Grid Layout */
2
+ .iconSelector {
3
+ width: 100%;
4
+ margin: 16px 0px;
5
+ }
6
+
7
+ /* Header */
8
+ .title {
9
+ font-weight: 600;
10
+ font-size: 18px;
11
+ color: var(--text-dark);
12
+ }
13
+
14
+ .subtitle {
15
+ color: var(--text-light);
16
+ line-height: 1.4;
17
+ }
18
+
19
+ /* Current Selection */
20
+ .currentSelection {
21
+ display: flex;
22
+ align-items: center;
23
+ gap: 8px;
24
+ padding: 12px 0;
25
+ }
26
+
27
+ .currentSelectionHeader {
28
+ display: flex;
29
+ justify-content: space-between;
30
+ align-items: center;
31
+ }
32
+
33
+ .currentSelectionDisplay {
34
+ display: flex;
35
+ align-items: center;
36
+ justify-content: flex-start;
37
+ }
38
+
39
+ .selectedIcon {
40
+ aspect-ratio: 1;
41
+ font-size: 24px;
42
+ border: 2px solid var(--colour-branding-main);
43
+ border-radius: 4px;
44
+ padding: 0.25em;
45
+ color: var(--colour-branding-main);
46
+ }
47
+
48
+ .iconName {
49
+ background: var(--bg-bluegrey);
50
+ font-size: 14px;
51
+ color: var(--text-light);
52
+ }
53
+
54
+ /* Simple Flat Icon Grid */
55
+ .iconGridContainer {
56
+ }
57
+
58
+ .iconGrid {
59
+ display: grid;
60
+ grid-template-columns: repeat(auto-fill, minmax(32px, 1fr));
61
+ gap: 8px;
62
+ max-width: 500px;
63
+ }
64
+
65
+ .iconButton {
66
+ display: flex;
67
+ aspect-ratio: 1;
68
+ overflow: hidden;
69
+ align-items: center;
70
+ justify-content: center;
71
+ aspect-ratio: 1;
72
+ border: 2px solid var(--text-dark-alpha10);
73
+ border-radius: 6px;
74
+ background: var(--bg-white);
75
+ color: var(--text-light);
76
+ cursor: pointer;
77
+ transition: all 0.2s ease;
78
+ position: relative;
79
+ font-size: 12px;
80
+ padding: 0.5em;
81
+ }
82
+
83
+ .iconButton svg {
84
+ color: var(--text-light);
85
+ }
86
+
87
+ .iconButton:hover {
88
+ border-color: var(--colour-branding-main);
89
+ background: var(--bg-white);
90
+ color: var(--colour-branding-main);
91
+ }
92
+
93
+ .iconButton:hover svg {
94
+ color: var(--colour-branding-main);
95
+ }
96
+
97
+ .iconButton.selected {
98
+ border-color: var(--colour-branding-main);
99
+ background: var(--bg-white);
100
+ color: var(--colour-branding-main);
101
+ }
102
+
103
+ .iconButton.selected svg {
104
+ color: var(--colour-branding-main);
105
+ }
106
+
107
+ .iconButton.selected:hover {
108
+ border-color: var(--colour-branding-dark);
109
+ color: var(--colour-branding-dark);
110
+ }
111
+
112
+ .iconButton.selected:hover svg {
113
+ color: var(--colour-branding-dark);
114
+ }
115
+
116
+ .iconFallback {
117
+ font-size: 10px;
118
+ text-align: center;
119
+ word-break: break-all;
120
+ padding: 2px;
121
+ line-height: 1.2;
122
+ }
123
+
124
+ /* Scrollbar styling */
125
+ .iconGridContainer::-webkit-scrollbar {
126
+ width: 8px;
127
+ }
128
+
129
+ .iconGridContainer::-webkit-scrollbar-track {
130
+ background: var(--bg-grey);
131
+ border-radius: 4px;
132
+ }
133
+
134
+ .iconGridContainer::-webkit-scrollbar-thumb {
135
+ background: var(--text-dark-alpha10);
136
+ border-radius: 4px;
137
+ }
138
+
139
+ .iconGridContainer::-webkit-scrollbar-thumb:hover {
140
+ background: var(--text-light);
141
+ }
142
+
143
+ /* Field error styles */
144
+ .fieldError {
145
+ color: var(--colour-branding-secondary);
146
+ font-size: 0.875rem;
147
+ font-weight: 500;
148
+ margin-top: 0.5rem;
149
+ display: flex;
150
+ align-items: center;
151
+ gap: 0.25rem;
152
+ }
153
+
154
+ .errorIcon {
155
+ font-size: 1rem;
156
+ }
157
+
158
+ /* Responsive design */
159
+ @media (max-width: 768px) {
160
+ .iconSelector {
161
+ padding: 16px;
162
+ }
163
+
164
+ .iconGrid {
165
+ grid-template-columns: repeat(auto-fill, minmax(40px, 1fr));
166
+ gap: 6px;
167
+ padding: 12px;
168
+ }
169
+
170
+ .iconButton {
171
+ width: 40px;
172
+ height: 40px;
173
+ font-size: 16px;
174
+ }
175
+
176
+ .selectedIcon {
177
+ font-size: 24px !important;
178
+ }
179
+ }
180
+
181
+ @media (max-width: 480px) {
182
+ .iconGrid {
183
+ grid-template-columns: repeat(auto-fill, minmax(36px, 1fr));
184
+ gap: 4px;
185
+ padding: 8px;
186
+ }
187
+
188
+ .iconButton {
189
+ width: 36px;
190
+ height: 36px;
191
+ font-size: 14px;
192
+ }
193
+
194
+ .selectedIcon {
195
+ font-size: 20px !important;
196
+ }
197
+ }