@cdc/core 4.25.10 → 4.26.1

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 (134) hide show
  1. package/_stories/Gallery.Charts.stories.tsx +307 -0
  2. package/_stories/Gallery.DataBite.stories.tsx +72 -0
  3. package/_stories/Gallery.Maps.stories.tsx +230 -0
  4. package/_stories/Gallery.WaffleChart.stories.tsx +187 -0
  5. package/_stories/PageART.stories.tsx +192 -0
  6. package/_stories/PageBRFSS.stories.tsx +289 -0
  7. package/_stories/PageCancerRegistries.stories.tsx +199 -0
  8. package/_stories/PageEasternEquineEncephalitis.stories.tsx +202 -0
  9. package/_stories/PageExcessiveAlcoholUse.stories.tsx +196 -0
  10. package/_stories/PageMaternalMortality.stories.tsx +192 -0
  11. package/_stories/PageOralHealth.stories.tsx +196 -0
  12. package/_stories/PageRespiratory.stories.tsx +332 -0
  13. package/_stories/PageSmokingTobacco.stories.tsx +195 -0
  14. package/_stories/PageStateDiabetesProfiles.stories.tsx +196 -0
  15. package/_stories/PageWastewater.stories.tsx +463 -0
  16. package/_stories/StoryRenderingTests.stories.tsx +164 -0
  17. package/assets/icon-magnifying-glass.svg +5 -0
  18. package/assets/icon-warming-stripes.svg +13 -0
  19. package/components/AdvancedEditor/AdvancedEditor.tsx +7 -1
  20. package/components/AdvancedEditor/EmbedEditor.tsx +281 -0
  21. package/components/ComboBox/ComboBox.tsx +345 -0
  22. package/components/ComboBox/combobox.styles.css +185 -0
  23. package/components/ComboBox/index.ts +1 -0
  24. package/components/CustomColorsEditor/CustomColorsEditor.css +299 -0
  25. package/components/CustomColorsEditor/CustomColorsEditor.tsx +209 -0
  26. package/components/CustomColorsEditor/index.ts +1 -0
  27. package/components/DataTable/DataTable.tsx +132 -58
  28. package/components/DataTable/DataTableStandAlone.tsx +8 -3
  29. package/components/DataTable/components/DataTableEditorPanel.tsx +12 -2
  30. package/components/DataTable/data-table.css +217 -210
  31. package/components/DataTable/helpers/mapCellMatrix.tsx +28 -9
  32. package/components/DataTable/helpers/standardizeState.js +2 -2
  33. package/components/DataTable/helpers/tests/standardizeState.test.js +54 -0
  34. package/components/EditorPanel/ColumnsEditor.tsx +37 -19
  35. package/components/EditorPanel/DataTableEditor.tsx +54 -28
  36. package/components/EditorPanel/EditorPanel.styles.css +439 -0
  37. package/components/EditorPanel/EditorPanel.tsx +144 -0
  38. package/components/EditorPanel/EditorPanelDispatch.tsx +75 -0
  39. package/components/EditorPanel/FieldSetWrapper.tsx +66 -23
  40. package/components/EditorPanel/FootnotesEditor.tsx +44 -37
  41. package/components/EditorPanel/Inputs.tsx +44 -8
  42. package/components/EditorPanel/VizFilterEditor/NestedDropdownEditor.tsx +35 -62
  43. package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +246 -175
  44. package/components/EditorPanel/components/MarkupVariablesEditor.tsx +61 -22
  45. package/components/EditorPanel/sections/VisualSection.tsx +169 -0
  46. package/components/Filters/Filters.tsx +57 -10
  47. package/components/Filters/components/Dropdown.tsx +6 -1
  48. package/components/Filters/helpers/getNestedOptions.ts +2 -1
  49. package/components/Filters/helpers/handleSorting.ts +1 -1
  50. package/components/Footnotes/Footnotes.tsx +35 -25
  51. package/components/Footnotes/FootnotesStandAlone.tsx +42 -6
  52. package/components/HeaderThemeSelector/HeaderThemeSelector.css +43 -0
  53. package/components/HeaderThemeSelector/HeaderThemeSelector.stories.tsx +74 -0
  54. package/components/HeaderThemeSelector/HeaderThemeSelector.tsx +61 -0
  55. package/components/HeaderThemeSelector/index.ts +2 -0
  56. package/components/Layout/components/Sidebar/components/sidebar.styles.scss +82 -0
  57. package/components/Layout/components/Visualization/index.tsx +16 -1
  58. package/components/Layout/components/Visualization/visualizations.scss +7 -0
  59. package/components/Layout/styles/editor.scss +2 -1
  60. package/components/Legend/Legend.Gradient.tsx +1 -1
  61. package/components/Loader/Loader.tsx +1 -1
  62. package/components/MediaControls.tsx +63 -34
  63. package/components/PaletteConversionModal.tsx +7 -4
  64. package/components/PaletteSelector/PaletteSelector.css +49 -6
  65. package/components/Table/components/Cell.tsx +23 -2
  66. package/components/Table/components/Row.tsx +5 -3
  67. package/components/_stories/Filters.stories.tsx +20 -1
  68. package/components/_stories/Footnotes.CSV.stories.tsx +247 -0
  69. package/components/_stories/Footnotes.stories.tsx +768 -3
  70. package/components/_stories/Inputs.stories.tsx +2 -2
  71. package/components/_stories/styles.scss +0 -1
  72. package/components/ui/Accordion.jsx +1 -1
  73. package/components/ui/Icon.tsx +3 -1
  74. package/components/ui/Title/index.tsx +30 -2
  75. package/components/ui/Title/title.styles.css +42 -0
  76. package/components/ui/accordion.styles.css +57 -0
  77. package/data/chartColorPalettes.ts +1 -1
  78. package/dist/cove-main.css +75 -6
  79. package/dist/cove-main.css.map +1 -1
  80. package/generateViteConfig.js +8 -1
  81. package/helpers/addValuesToFilters.ts +11 -1
  82. package/helpers/constants.ts +37 -0
  83. package/helpers/cove/number.ts +33 -12
  84. package/helpers/coveUpdateWorker.ts +20 -11
  85. package/helpers/embedCodeGenerator.ts +109 -0
  86. package/helpers/fetchRemoteData.ts +3 -15
  87. package/helpers/getUniqueValues.ts +19 -0
  88. package/helpers/hashObj.ts +25 -0
  89. package/helpers/isRightAlignedTableValue.js +5 -0
  90. package/helpers/markupProcessor.ts +27 -12
  91. package/helpers/mergeCustomOrderValues.ts +37 -0
  92. package/helpers/metrics/helpers.ts +1 -0
  93. package/helpers/parseCsvWithQuotes.ts +65 -0
  94. package/helpers/pivotData.ts +2 -2
  95. package/helpers/prepareScreenshot.ts +268 -0
  96. package/helpers/queryStringUtils.ts +29 -0
  97. package/helpers/testing.ts +17 -4
  98. package/helpers/tests/prepareScreenshot.test.ts +414 -0
  99. package/helpers/tests/queryStringUtils.test.ts +381 -0
  100. package/helpers/tests/testStandaloneBuild.ts +23 -5
  101. package/helpers/useDataVizClasses.ts +0 -1
  102. package/helpers/ver/4.25.11.ts +13 -0
  103. package/helpers/ver/4.26.1.ts +80 -0
  104. package/helpers/viewports.ts +2 -0
  105. package/hooks/useDataColumns.ts +63 -0
  106. package/hooks/useFilterManagement.ts +94 -0
  107. package/hooks/useLegendSeparators.ts +26 -0
  108. package/hooks/useListManagement.ts +192 -0
  109. package/package.json +6 -4
  110. package/styles/_button-section.scss +0 -3
  111. package/styles/_common-components.css +73 -0
  112. package/styles/_global.scss +25 -5
  113. package/styles/base.scss +0 -50
  114. package/styles/cove-main.scss +3 -1
  115. package/styles/filters.scss +10 -3
  116. package/styles/v2/base/index.scss +0 -1
  117. package/styles/v2/components/editor.scss +14 -6
  118. package/styles/v2/utils/_breakpoints.scss +1 -1
  119. package/styles/v2/utils/index.scss +0 -1
  120. package/styles/waiting.scss +1 -1
  121. package/types/Axis.ts +1 -0
  122. package/types/ForecastingSeriesKey.ts +1 -0
  123. package/types/MarkupInclude.ts +5 -3
  124. package/types/MarkupVariable.ts +1 -1
  125. package/types/Series.ts +3 -0
  126. package/types/Table.ts +1 -0
  127. package/types/Visualization.ts +1 -0
  128. package/types/VizFilter.ts +2 -0
  129. package/LICENSE +0 -201
  130. package/styles/_mixins.scss +0 -13
  131. package/styles/_typography.scss +0 -0
  132. package/styles/v2/base/_typography.scss +0 -0
  133. package/styles/v2/components/guidance-block.scss +0 -74
  134. package/styles/v2/utils/_functions.scss +0 -0
@@ -0,0 +1,299 @@
1
+ .custom-colors-editor {
2
+ padding: 0.75rem;
3
+ border: 1px solid #dee2e6;
4
+ border-radius: 0.25rem;
5
+ background-color: #f8f9fa;
6
+ }
7
+
8
+ .custom-colors-editor .custom-colors-label {
9
+ display: block;
10
+ font-weight: 600;
11
+ margin-bottom: 0.5rem;
12
+ font-size: 0.875rem;
13
+ color: #495057;
14
+ }
15
+
16
+ .custom-colors-editor .custom-colors-notice {
17
+ display: flex;
18
+ align-items: flex-start;
19
+ gap: 0.5rem;
20
+ padding: 0.75rem;
21
+ margin-bottom: 0.75rem;
22
+ background-color: #d1ecf1;
23
+ border: 1px solid #bee5eb;
24
+ border-radius: 0.25rem;
25
+ color: #0c5460;
26
+ font-size: 0.8125rem;
27
+ line-height: 1.5;
28
+ }
29
+
30
+ .custom-colors-editor .custom-colors-notice .notice-icon {
31
+ width: 16px;
32
+ height: 16px;
33
+ flex-shrink: 0;
34
+ margin-top: 0.125rem;
35
+ color: #17a2b8;
36
+ }
37
+
38
+ .custom-colors-editor .custom-colors-notice strong {
39
+ font-weight: 600;
40
+ }
41
+
42
+ .custom-colors-editor .custom-colors-notice a {
43
+ color: #0c5460;
44
+ text-decoration: underline;
45
+ font-weight: 500;
46
+ }
47
+
48
+ .custom-colors-editor .custom-colors-notice a:hover {
49
+ color: #062a30;
50
+ text-decoration: none;
51
+ }
52
+
53
+ .custom-colors-editor .custom-colors-preview {
54
+ display: flex;
55
+ gap: 2px;
56
+ margin-bottom: 0.75rem;
57
+ padding: 0.5rem;
58
+ background-color: #fff;
59
+ border: 1px solid #dee2e6;
60
+ border-radius: 0.25rem;
61
+ min-height: 36px;
62
+ overflow: hidden;
63
+ }
64
+
65
+ .custom-colors-editor .custom-colors-preview .preview-swatch {
66
+ flex: 1;
67
+ min-width: 8px;
68
+ border-radius: 2px;
69
+ transition: transform 0.15s ease;
70
+ }
71
+
72
+ .custom-colors-editor .custom-colors-preview .preview-swatch:hover {
73
+ transform: scale(1.1);
74
+ }
75
+
76
+ .custom-colors-editor .custom-colors-list {
77
+ display: flex;
78
+ flex-direction: column;
79
+ gap: 0.5rem;
80
+ margin-bottom: 0.75rem;
81
+ max-height: 400px;
82
+ overflow-y: auto;
83
+ padding-right: 0.25rem;
84
+ }
85
+
86
+ .custom-colors-editor .custom-colors-list::-webkit-scrollbar {
87
+ width: 6px;
88
+ }
89
+
90
+ .custom-colors-editor .custom-colors-list::-webkit-scrollbar-track {
91
+ background: #f1f1f1;
92
+ border-radius: 3px;
93
+ }
94
+
95
+ .custom-colors-editor .custom-colors-list::-webkit-scrollbar-thumb {
96
+ background: #c1c1c1;
97
+ border-radius: 3px;
98
+ }
99
+
100
+ .custom-colors-editor .custom-colors-list::-webkit-scrollbar-thumb:hover {
101
+ background: #a8a8a8;
102
+ }
103
+
104
+ .custom-colors-editor .custom-color-item {
105
+ background-color: #fff;
106
+ border: 1px solid #dee2e6;
107
+ border-radius: 0.25rem;
108
+ padding: 0.5rem;
109
+ transition: box-shadow 0.15s ease, opacity 0.15s ease;
110
+ }
111
+
112
+ .custom-colors-editor .custom-color-item.dragging {
113
+ opacity: 0.5;
114
+ box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
115
+ cursor: grabbing;
116
+ }
117
+
118
+ .custom-colors-editor .custom-color-item:hover:not(.dragging) {
119
+ box-shadow: 0 0.125rem 0.5rem rgba(0, 0, 0, 0.1);
120
+ border-color: #adb5bd;
121
+ }
122
+
123
+ .custom-colors-editor .color-item-controls {
124
+ display: flex;
125
+ align-items: center;
126
+ gap: 0.5rem;
127
+ flex-wrap: wrap;
128
+ }
129
+
130
+ @media (max-width: 576px) {
131
+ .custom-colors-editor .color-item-controls {
132
+ gap: 0.375rem;
133
+ }
134
+ }
135
+
136
+ .custom-colors-editor .color-item-drag-handle {
137
+ color: #adb5bd;
138
+ font-size: 0.875rem;
139
+ cursor: grab;
140
+ user-select: none;
141
+ line-height: 1;
142
+ flex-shrink: 0;
143
+ padding: 0 0.125rem;
144
+ }
145
+
146
+ .custom-colors-editor .color-item-drag-handle:active {
147
+ cursor: grabbing;
148
+ }
149
+
150
+ .custom-colors-editor .custom-color-item:hover .color-item-drag-handle {
151
+ color: #6c757d;
152
+ }
153
+
154
+ .custom-colors-editor .color-item-number {
155
+ font-weight: 600;
156
+ min-width: 24px;
157
+ text-align: right;
158
+ color: #6c757d;
159
+ font-size: 0.75rem;
160
+ flex-shrink: 0;
161
+ }
162
+
163
+ .custom-colors-editor .color-picker {
164
+ width: 48px;
165
+ height: 34px;
166
+ border: 1px solid #ced4da;
167
+ border-radius: 0.25rem;
168
+ cursor: pointer;
169
+ padding: 2px;
170
+ flex-shrink: 0;
171
+ transition: border-color 0.15s ease;
172
+ }
173
+
174
+ .custom-colors-editor .color-picker:hover {
175
+ border-color: #80bdff;
176
+ }
177
+
178
+ .custom-colors-editor .color-picker:focus {
179
+ outline: none;
180
+ border-color: #80bdff;
181
+ box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
182
+ }
183
+
184
+ .custom-colors-editor .color-input-wrapper {
185
+ flex: 1;
186
+ min-width: 0;
187
+ margin-bottom: 0;
188
+ }
189
+
190
+ /* Override TextField label wrapper styles */
191
+ .custom-colors-editor .color-input-wrapper label {
192
+ margin-bottom: 0;
193
+ }
194
+
195
+ /* Hide the label text since we're passing empty string */
196
+ .custom-colors-editor .color-input-wrapper label .edit-label {
197
+ display: none;
198
+ }
199
+
200
+ .custom-colors-editor .color-input-wrapper label input[type='text'] {
201
+ font-family: 'SFMono-Regular', 'Menlo', 'Monaco', 'Consolas', 'Liberation Mono', 'Courier New', monospace;
202
+ text-transform: uppercase;
203
+ }
204
+
205
+ @media (max-width: 576px) {
206
+ .custom-colors-editor .color-input-wrapper {
207
+ flex-basis: 100%;
208
+ }
209
+ }
210
+
211
+ .custom-colors-editor .color-item-buttons {
212
+ display: flex;
213
+ gap: 0.25rem;
214
+ margin-left: auto;
215
+ flex-shrink: 0;
216
+ }
217
+
218
+ .custom-colors-editor .btn-move,
219
+ .custom-colors-editor .btn-remove {
220
+ padding: 0.25rem 0.5rem;
221
+ border: 1px solid #ced4da;
222
+ border-radius: 0.25rem;
223
+ background-color: #fff;
224
+ cursor: pointer;
225
+ font-size: 0.875rem;
226
+ line-height: 1.5;
227
+ transition: all 0.15s ease;
228
+ display: inline-flex;
229
+ align-items: center;
230
+ justify-content: center;
231
+ min-width: 28px;
232
+ height: 28px;
233
+ }
234
+
235
+ .custom-colors-editor .btn-move:hover:not(:disabled),
236
+ .custom-colors-editor .btn-remove:hover:not(:disabled) {
237
+ background-color: #e9ecef;
238
+ border-color: #adb5bd;
239
+ }
240
+
241
+ .custom-colors-editor .btn-move:active:not(:disabled),
242
+ .custom-colors-editor .btn-remove:active:not(:disabled) {
243
+ background-color: #dee2e6;
244
+ transform: scale(0.95);
245
+ }
246
+
247
+ .custom-colors-editor .btn-move:disabled,
248
+ .custom-colors-editor .btn-remove:disabled {
249
+ opacity: 0.35;
250
+ cursor: not-allowed;
251
+ }
252
+
253
+ .custom-colors-editor .btn-remove {
254
+ color: #dc3545;
255
+ font-weight: 700;
256
+ font-size: 1.125rem;
257
+ }
258
+
259
+ .custom-colors-editor .btn-remove:hover:not(:disabled) {
260
+ background-color: #dc3545;
261
+ color: #fff;
262
+ border-color: #dc3545;
263
+ }
264
+
265
+ .custom-colors-editor .btn-add-color {
266
+ width: 100%;
267
+ padding: 0.5rem;
268
+ border: 2px dashed #ced4da;
269
+ border-radius: 0.25rem;
270
+ background-color: #fff;
271
+ color: #6c757d;
272
+ font-weight: 600;
273
+ font-size: 0.875rem;
274
+ cursor: pointer;
275
+ transition: all 0.15s ease;
276
+ }
277
+
278
+ .custom-colors-editor .btn-add-color:hover:not(:disabled) {
279
+ border-color: #007bff;
280
+ color: #007bff;
281
+ background-color: #e7f1ff;
282
+ }
283
+
284
+ .custom-colors-editor .btn-add-color:active:not(:disabled) {
285
+ transform: scale(0.98);
286
+ }
287
+
288
+ .custom-colors-editor .btn-add-color:disabled {
289
+ opacity: 0.5;
290
+ cursor: not-allowed;
291
+ }
292
+
293
+ .custom-colors-editor .custom-colors-info {
294
+ text-align: center;
295
+ font-size: 0.75rem;
296
+ color: #6c757d;
297
+ margin-top: 0.5rem;
298
+ font-style: italic;
299
+ }
@@ -0,0 +1,209 @@
1
+ import React, { useState } from 'react'
2
+ import { TextField } from '../EditorPanel/Inputs'
3
+ import './CustomColorsEditor.css'
4
+
5
+ interface CustomColorsEditorProps {
6
+ colors: string[]
7
+ onChange: (colors: string[]) => void
8
+ label?: string
9
+ minColors?: number
10
+ maxColors?: number
11
+ }
12
+
13
+ const CustomColorsEditor: React.FC<CustomColorsEditorProps> = ({
14
+ colors = [],
15
+ onChange,
16
+ label = 'Custom Colors',
17
+ minColors = 1,
18
+ maxColors = 20
19
+ }) => {
20
+ const [draggedIndex, setDraggedIndex] = useState<number | null>(null)
21
+
22
+ const handleColorChange = (index: number, newColor: string) => {
23
+ const newColors = [...colors]
24
+ newColors[index] = newColor
25
+ onChange(newColors)
26
+ }
27
+
28
+ const handleAddColor = () => {
29
+ if (colors.length < maxColors) {
30
+ // Add a new color (default to the last color or a neutral color)
31
+ const defaultColor = colors.length > 0 ? colors[colors.length - 1] : '#3366cc'
32
+ onChange([...colors, defaultColor])
33
+ }
34
+ }
35
+
36
+ const handleRemoveColor = (index: number) => {
37
+ if (colors.length > minColors) {
38
+ const newColors = colors.filter((_, i) => i !== index)
39
+ onChange(newColors)
40
+ }
41
+ }
42
+
43
+ const handleMoveUp = (index: number) => {
44
+ if (index > 0) {
45
+ const newColors = [...colors]
46
+ ;[newColors[index - 1], newColors[index]] = [newColors[index], newColors[index - 1]]
47
+ onChange(newColors)
48
+ }
49
+ }
50
+
51
+ const handleMoveDown = (index: number) => {
52
+ if (index < colors.length - 1) {
53
+ const newColors = [...colors]
54
+ ;[newColors[index], newColors[index + 1]] = [newColors[index + 1], newColors[index]]
55
+ onChange(newColors)
56
+ }
57
+ }
58
+
59
+ const handleDragStart = (index: number) => {
60
+ setDraggedIndex(index)
61
+ }
62
+
63
+ const handleDragOver = (e: React.DragEvent, index: number) => {
64
+ e.preventDefault()
65
+ }
66
+
67
+ const handleDrop = (e: React.DragEvent, dropIndex: number) => {
68
+ e.preventDefault()
69
+
70
+ if (draggedIndex === null || draggedIndex === dropIndex) {
71
+ setDraggedIndex(null)
72
+ return
73
+ }
74
+
75
+ const newColors = [...colors]
76
+ const draggedColor = newColors[draggedIndex]
77
+ newColors.splice(draggedIndex, 1)
78
+ newColors.splice(dropIndex, 0, draggedColor)
79
+
80
+ onChange(newColors)
81
+ setDraggedIndex(null)
82
+ }
83
+
84
+ return (
85
+ <div className="custom-colors-editor">
86
+ <label className="custom-colors-label">{label}</label>
87
+
88
+ <div className="custom-colors-notice">
89
+ <svg className="notice-icon" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
90
+ <path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/>
91
+ <path d="m8.93 6.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533L8.93 6.588zM9 4.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0z"/>
92
+ </svg>
93
+ <span>
94
+ <strong>Accessibility Notice:</strong> When using custom colors, conduct a{' '}
95
+ Section 508 review to ensure adequate color contrast and accessibility compliance.{' '}
96
+ </span>
97
+ </div>
98
+
99
+ <div className="custom-colors-preview">
100
+ {colors.map((color, index) => (
101
+ <div
102
+ key={index}
103
+ className="preview-swatch"
104
+ style={{ backgroundColor: color }}
105
+ title={`Color ${index + 1}: ${color}`}
106
+ />
107
+ ))}
108
+ </div>
109
+
110
+ <div className="custom-colors-list">
111
+ {colors.map((color, index) => (
112
+ <div
113
+ key={index}
114
+ className={`custom-color-item ${draggedIndex === index ? 'dragging' : ''}`}
115
+ draggable
116
+ onDragStart={() => handleDragStart(index)}
117
+ onDragOver={(e) => handleDragOver(e, index)}
118
+ onDrop={(e) => handleDrop(e, index)}
119
+ >
120
+ <div className="color-item-controls">
121
+ <span className="color-item-drag-handle" title="Drag to reorder">
122
+ ⋮⋮
123
+ </span>
124
+ {/* <span className="color-item-number">{index + 1}.</span> */}
125
+
126
+ {/* <input
127
+ type="color"
128
+ value={color}
129
+ onChange={(e) => handleColorChange(index, e.target.value)}
130
+ className="color-picker"
131
+ title="Click to change color"
132
+ aria-label={`Color picker for color ${index + 1}`}
133
+ /> */}
134
+
135
+ <div className="color-input-wrapper">
136
+ <TextField
137
+ type="text"
138
+ value={color}
139
+ label=""
140
+ section="colors"
141
+ subsection={null}
142
+ fieldName={`color-${index}`}
143
+ updateField={(section, subsection, fieldName, value) => {
144
+ handleColorChange(index, value)
145
+ }}
146
+ placeholder="#000000"
147
+ maxLength={15}
148
+ className="color-text-input"
149
+ />
150
+ </div>
151
+
152
+ <div className="color-item-buttons">
153
+ <button
154
+ type="button"
155
+ onClick={() => handleMoveUp(index)}
156
+ disabled={index === 0}
157
+ className="btn-move"
158
+ title="Move up"
159
+ aria-label="Move color up"
160
+ >
161
+
162
+ </button>
163
+
164
+ <button
165
+ type="button"
166
+ onClick={() => handleMoveDown(index)}
167
+ disabled={index === colors.length - 1}
168
+ className="btn-move"
169
+ title="Move down"
170
+ aria-label="Move color down"
171
+ >
172
+
173
+ </button>
174
+
175
+ <button
176
+ type="button"
177
+ onClick={() => handleRemoveColor(index)}
178
+ disabled={colors.length <= minColors}
179
+ className="btn-remove"
180
+ title="Remove color"
181
+ aria-label="Remove color"
182
+ >
183
+ ×
184
+ </button>
185
+ </div>
186
+ </div>
187
+ </div>
188
+ ))}
189
+ </div>
190
+
191
+ <button
192
+ type="button"
193
+ onClick={handleAddColor}
194
+ disabled={colors.length >= maxColors}
195
+ className="btn-add-color"
196
+ >
197
+ + Add Color
198
+ </button>
199
+
200
+ <div className="custom-colors-info">
201
+ {colors.length} color{colors.length !== 1 ? 's' : ''}
202
+ {colors.length < minColors && ` (minimum ${minColors} required)`}
203
+ {colors.length >= maxColors && ` (maximum reached)`}
204
+ </div>
205
+ </div>
206
+ )
207
+ }
208
+
209
+ export default CustomColorsEditor
@@ -0,0 +1 @@
1
+ export { default as CustomColorsEditor } from './CustomColorsEditor'