@cdc/core 4.26.1 → 4.26.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 (249) hide show
  1. package/.claude/agents/qa-test-developer.md +126 -0
  2. package/CLAUDE.local.md +67 -0
  3. package/LICENSE +201 -0
  4. package/_stories/Gallery.Charts.stories.tsx +35 -42
  5. package/_stories/Gallery.DataBite.stories.tsx +15 -8
  6. package/_stories/Gallery.Maps.stories.tsx +37 -28
  7. package/_stories/Gallery.WaffleChart.stories.tsx +1 -1
  8. package/_stories/PageART.stories.tsx +5 -4
  9. package/_stories/PageBRFSS.stories.tsx +21 -16
  10. package/_stories/PageCancerRegistries.stories.tsx +15 -15
  11. package/_stories/PageEasternEquineEncephalitis.stories.tsx +33 -19
  12. package/_stories/PageExcessiveAlcoholUse.stories.tsx +148 -143
  13. package/_stories/PageMaternalMortality.stories.tsx +5 -4
  14. package/_stories/PageOralHealth.stories.tsx +15 -10
  15. package/_stories/PageRespiratory.stories.tsx +4 -4
  16. package/_stories/PageSmokingTobacco.stories.tsx +15 -10
  17. package/_stories/PageStateDiabetesProfiles.stories.tsx +15 -10
  18. package/_stories/PageWastewater.stories.tsx +44 -30
  19. package/_stories/VegaImport.stories.tsx +401 -0
  20. package/_stories/vega-fixtures/bars-with-line.json +444 -0
  21. package/_stories/vega-fixtures/bars.json +58 -0
  22. package/_stories/vega-fixtures/combo-bar-rolling-mean.json +88 -0
  23. package/_stories/vega-fixtures/combo.json +68 -0
  24. package/_stories/vega-fixtures/grouped-horizontal-bars.json +83 -0
  25. package/_stories/vega-fixtures/grouped-horizontal-bars2.json +231 -0
  26. package/_stories/vega-fixtures/horizontal-bar.json +427 -0
  27. package/_stories/vega-fixtures/horizontal-bars-with-bad-colors.json +197 -0
  28. package/_stories/vega-fixtures/horizontal-bars2.json +58 -0
  29. package/_stories/vega-fixtures/lines.json +227 -0
  30. package/_stories/vega-fixtures/measles-bars.json +348 -0
  31. package/_stories/vega-fixtures/measles-map.json +11101 -0
  32. package/_stories/vega-fixtures/measles-stacked-bars.json +2147 -0
  33. package/_stories/vega-fixtures/multi-dataset.json +255 -0
  34. package/_stories/vega-fixtures/no-data.json +14 -0
  35. package/_stories/vega-fixtures/pie-chart.json +94 -0
  36. package/_stories/vega-fixtures/repeat-spec.json +47 -0
  37. package/_stories/vega-fixtures/stacked-area.json +222 -0
  38. package/_stories/vega-fixtures/stacked-bar-with-rect.json +3412 -0
  39. package/_stories/vega-fixtures/stacked-bars-with-line.json +364 -0
  40. package/_stories/vega-fixtures/stacked-bars.json +212 -0
  41. package/_stories/vega-fixtures/stacked-horizontal-bars.json +140 -0
  42. package/_stories/vega-fixtures/warning-combo.json +59 -0
  43. package/_stories/vega-fixtures/warning-scatter-and-line.json +1182 -0
  44. package/assets/callout-flag.svg +7 -0
  45. package/assets/icon-chart-area.svg +1 -0
  46. package/assets/icon-chart-radar.svg +23 -0
  47. package/assets/logo2.svg +31 -0
  48. package/components/AdvancedEditor/EmbedEditor.tsx +270 -38
  49. package/components/Alert/components/Alert.styles.css +2 -2
  50. package/components/ComboBox/combobox.styles.css +48 -48
  51. package/components/CustomColorsEditor/CustomColorsEditor.css +53 -53
  52. package/components/CustomColorsEditor/CustomColorsEditor.tsx +3 -10
  53. package/components/DataTable/DataTable.tsx +46 -18
  54. package/components/DataTable/DataTableStandAlone.tsx +1 -0
  55. package/components/DataTable/components/ChartHeader.tsx +21 -12
  56. package/components/DataTable/components/MapHeader.tsx +34 -28
  57. package/components/DataTable/components/SortIcon/sort-icon.css +5 -5
  58. package/components/DataTable/data-table.css +50 -52
  59. package/components/DataTable/helpers/applyCustomOrder.ts +17 -0
  60. package/components/DataTable/helpers/getChartCellValue.ts +10 -7
  61. package/components/DataTable/helpers/getMapDataTableColumnKeys.ts +22 -0
  62. package/components/DataTable/helpers/getSeriesName.ts +6 -0
  63. package/components/DataTable/helpers/mapCellMatrix.tsx +33 -23
  64. package/components/DataTable/helpers/tests/mapCellMatrix.test.ts +33 -0
  65. package/components/DownloadButton.tsx +14 -6
  66. package/components/EditorPanel/ColumnsEditor.tsx +38 -31
  67. package/components/EditorPanel/CustomSortOrder.tsx +94 -0
  68. package/components/EditorPanel/DataTableEditor.tsx +139 -23
  69. package/components/EditorPanel/EditorPanel.styles.css +71 -71
  70. package/components/EditorPanel/EditorPanel.tsx +3 -8
  71. package/components/EditorPanel/EditorPanelDispatch.tsx +4 -4
  72. package/components/EditorPanel/FootnotesEditor.tsx +2 -2
  73. package/components/EditorPanel/VizFilterEditor/NestedDropdownEditor.tsx +21 -12
  74. package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +16 -10
  75. package/components/EditorPanel/VizFilterEditor/components/FilterOrder.tsx +33 -29
  76. package/components/EditorPanel/components/MarkupVariablesEditor.tsx +160 -106
  77. package/components/EditorPanel/components/PanelMarkup.tsx +5 -1
  78. package/{styles/v2/components → components/EditorPanel}/editor.scss +76 -22
  79. package/components/EditorPanel/sections/StyleTreatmentSection.tsx +99 -0
  80. package/components/EditorPanel/sections/VisualSection.tsx +11 -0
  81. package/components/EditorWrapper/editor-wrapper.style.css +1 -1
  82. package/components/Filters/Filters.tsx +3 -5
  83. package/components/Filters/components/Tabs.tsx +19 -7
  84. package/{styles → components/Filters}/filters.scss +3 -3
  85. package/components/Footnotes/FootnotesStandAlone.tsx +4 -2
  86. package/components/HeaderThemeSelector/HeaderThemeSelector.css +61 -5
  87. package/components/Layout/components/Responsive.tsx +14 -6
  88. package/components/Layout/components/Sidebar/components/Sidebar.tsx +1 -1
  89. package/components/Layout/components/Sidebar/components/sidebar.styles.scss +14 -20
  90. package/components/Layout/components/Visualization/index.tsx +50 -38
  91. package/components/Layout/components/Visualization/visualizations.scss +232 -15
  92. package/components/Layout/components/VisualizationContainer.test.tsx +67 -0
  93. package/components/Layout/components/VisualizationContainer.tsx +37 -0
  94. package/components/Layout/components/VisualizationContent.test.tsx +182 -0
  95. package/components/Layout/components/VisualizationContent.tsx +75 -0
  96. package/components/Layout/index.tsx +5 -5
  97. package/components/Layout/styles/editor-utils.scss +3 -3
  98. package/components/Layout/styles/editor.scss +4 -4
  99. package/components/Legend/Legend.Gradient.tsx +7 -1
  100. package/components/Loader/loader.styles.css +2 -2
  101. package/components/Loading.jsx +1 -1
  102. package/components/MediaControls.tsx +10 -3
  103. package/components/MultiSelect/multiselect.styles.css +19 -19
  104. package/components/NestedDropdown/nesteddropdown.styles.css +15 -15
  105. package/components/PaletteSelector/PaletteSelector.css +15 -15
  106. package/components/RichTooltip/richTooltip.css +6 -6
  107. package/components/Table/table.styles.css +2 -2
  108. package/components/Waiting.tsx +1 -1
  109. package/components/_stories/CustomColorsEditor.stories.tsx +37 -0
  110. package/components/_stories/DataTable.stories.tsx +1 -0
  111. package/components/_stories/Filters.stories.tsx +1 -1
  112. package/components/_stories/styles.scss +0 -1
  113. package/components/elements/Button.jsx +1 -1
  114. package/components/elements/Card.jsx +1 -1
  115. package/{styles/v2/components → components/elements}/button.scss +9 -8
  116. package/components/inputs/InputCheckbox.jsx +1 -1
  117. package/components/inputs/InputSelect.tsx +1 -1
  118. package/components/inputs/InputText.jsx +1 -1
  119. package/components/inputs/InputToggle.tsx +1 -1
  120. package/{styles/v2/components/input → components/inputs}/_input-check-radio.scss +2 -2
  121. package/{styles/v2/components/input → components/inputs}/_input-group.scss +3 -3
  122. package/{styles/v2/components/input → components/inputs}/_input-slider.scss +2 -2
  123. package/{styles/v2/components/input → components/inputs}/_input.scss +5 -5
  124. package/{styles/v2/components/input → components/inputs}/index.scss +2 -2
  125. package/{styles → components}/loading.scss +1 -1
  126. package/components/managers/DataDesigner.tsx +1 -1
  127. package/{styles/v2/components → components/managers}/data-designer.scss +6 -7
  128. package/components/ui/Accordion.jsx +1 -1
  129. package/components/ui/Icon.tsx +1 -1
  130. package/components/ui/LoadSpin.jsx +1 -1
  131. package/components/ui/Modal.jsx +1 -1
  132. package/components/ui/Overlay.jsx +1 -1
  133. package/components/ui/Title/index.test.tsx +34 -0
  134. package/components/ui/Title/index.tsx +24 -7
  135. package/components/ui/Title/title.styles.css +119 -25
  136. package/components/ui/Tooltip.tsx +1 -1
  137. package/components/ui/_stories/Title.stories.tsx +1 -1
  138. package/{styles/v2/components → components/ui}/accordion.scss +3 -3
  139. package/components/ui/accordion.styles.css +11 -11
  140. package/{styles/v2/components → components/ui}/modal.scss +2 -2
  141. package/{styles/v2/components → components/ui}/overlay.scss +6 -6
  142. package/{styles/v2/components → components}/ui/tooltip.scss +1 -1
  143. package/{styles → components}/waiting.scss +9 -3
  144. package/data/colorPalettes.ts +18 -5
  145. package/data/mapColorPalettes.ts +10 -0
  146. package/devTemplate/dev.js +285 -0
  147. package/devTemplate/index.html +30 -0
  148. package/devTemplate/preview.html +1503 -0
  149. package/devTemplate/sidebar.css +151 -0
  150. package/dist/cove-main.css +2530 -3901
  151. package/dist/cove-main.css.map +1 -1
  152. package/generateViteConfig.js +111 -2
  153. package/helpers/DataTransform.ts +1 -5
  154. package/helpers/backfillDefaults.ts +35 -0
  155. package/helpers/constants.ts +12 -0
  156. package/helpers/cove/date.ts +64 -3
  157. package/helpers/cove/number.ts +29 -15
  158. package/helpers/cove/string.ts +29 -0
  159. package/helpers/coveUpdateWorker.ts +14 -8
  160. package/helpers/displayDataAsText.ts +1 -1
  161. package/helpers/embed/embedCodeGenerator.ts +80 -0
  162. package/helpers/embed/embedHelper.js +169 -0
  163. package/helpers/embed/filterUtils.ts +121 -0
  164. package/helpers/embed/index.ts +17 -0
  165. package/helpers/embed/urlValidation.ts +119 -0
  166. package/helpers/extractDataAndMetadata.ts +20 -0
  167. package/helpers/fetchRemoteData.ts +14 -8
  168. package/helpers/filterVizData.ts +6 -1
  169. package/helpers/getFileExtension.ts +0 -6
  170. package/helpers/labelHash.ts +9 -0
  171. package/helpers/markupProcessor.ts +56 -38
  172. package/helpers/metrics/types.ts +3 -0
  173. package/helpers/palettes/colorDistributions.ts +1 -1
  174. package/helpers/palettes/utils.ts +12 -12
  175. package/helpers/parseCsvWithQuotes.ts +15 -14
  176. package/helpers/prepareScreenshot.ts +33 -10
  177. package/helpers/testing.ts +44 -0
  178. package/helpers/tests/DataTransform.test.ts +125 -0
  179. package/helpers/tests/abbreviateNumber.test.ts +59 -0
  180. package/helpers/tests/backfillDefaults.test.ts +253 -0
  181. package/helpers/tests/date.test.ts +110 -0
  182. package/helpers/tests/extractDataAndMetadata.test.ts +93 -0
  183. package/helpers/tests/markupProcessor.test.ts +315 -124
  184. package/helpers/tests/number.test.ts +42 -0
  185. package/helpers/tests/prepareScreenshot.test.ts +28 -28
  186. package/helpers/tests/testStandaloneBuild.ts +36 -26
  187. package/helpers/tests/useDataVizClasses.test.ts +66 -0
  188. package/helpers/tests/visualizationWrapperUsage.test.ts +57 -0
  189. package/helpers/useDataVizClasses.ts +13 -7
  190. package/helpers/vegaConfig.ts +1 -1
  191. package/helpers/vegaConfigImport.ts +160 -0
  192. package/helpers/ver/4.24.4.ts +24 -0
  193. package/helpers/ver/4.26.1.ts +1 -1
  194. package/helpers/ver/4.26.2.ts +84 -0
  195. package/helpers/ver/4.26.3.ts +44 -0
  196. package/helpers/ver/4.26.4.ts +31 -0
  197. package/helpers/ver/tests/4.26.1.test.ts +105 -0
  198. package/helpers/ver/tests/4.26.2.test.ts +298 -0
  199. package/helpers/ver/tests/4.26.3.test.ts +168 -0
  200. package/helpers/ver/tests/4.26.4.test.ts +88 -0
  201. package/helpers/ver/tests/coveUpdateWorker.test.ts +57 -0
  202. package/helpers/viewports.ts +2 -0
  203. package/package.json +27 -32
  204. package/styles/_global.scss +7 -7
  205. package/styles/_reset.scss +2 -2
  206. package/styles/{v2/base → base}/_file-selector.scss +4 -4
  207. package/styles/{v2/base → base}/_general.scss +2 -4
  208. package/styles/{v2/base → base}/index.scss +1 -1
  209. package/styles/base.scss +107 -165
  210. package/styles/cove-main.scss +3 -6
  211. package/styles/layout/_component.scss +110 -0
  212. package/styles/{v2/layout → layout}/_data-table.scss +7 -7
  213. package/styles/layout/_wrapper-padding.scss +27 -0
  214. package/styles/{v2/main.scss → main.scss} +3 -1
  215. package/styles/{v2/themes → themes}/_color-definitions.scss +46 -41
  216. package/styles/{_accessibility.scss → utils/_accessibility.scss} +1 -1
  217. package/styles/{v2/utils → utils}/_grid.scss +8 -3
  218. package/styles/{_global-variables.scss → utils/_properties.scss} +133 -112
  219. package/styles/{v2/utils → utils}/index.scss +2 -1
  220. package/types/Annotation.ts +10 -11
  221. package/types/Axis.ts +2 -0
  222. package/types/ComponentStyles.ts +1 -0
  223. package/types/ConfigureData.ts +1 -0
  224. package/types/General.ts +2 -0
  225. package/types/MarkupInclude.ts +1 -0
  226. package/types/MarkupVariable.ts +2 -1
  227. package/types/Palette.ts +22 -0
  228. package/types/Table.ts +9 -0
  229. package/types/Visualization.ts +7 -0
  230. package/_stories/StoryRenderingTests.stories.tsx +0 -164
  231. package/helpers/embedCodeGenerator.ts +0 -109
  232. package/styles/_common-components.css +0 -73
  233. package/styles/_variables.scss +0 -63
  234. package/styles/v2/layout/_component.scss +0 -21
  235. package/styles/v2/utils/_variables.scss +0 -9
  236. package/{styles/v2/components/card.scss → components/elements/card.css} +2 -2
  237. /package/{styles/v2/components → components/ui}/icon.scss +0 -0
  238. /package/{styles/v2/components → components/ui}/loadspin.scss +0 -0
  239. /package/styles/{v2/base → base}/_heading.scss +0 -0
  240. /package/styles/{v2/base → base}/_reset.scss +0 -0
  241. /package/styles/{v2/layout → layout}/_alert.scss +0 -0
  242. /package/styles/{v2/layout → layout}/_progression.scss +0 -0
  243. /package/styles/{v2/layout → layout}/_tooltip.scss +0 -0
  244. /package/styles/{v2/layout → layout}/index.scss +0 -0
  245. /package/styles/{v2/themes → themes}/index.scss +0 -0
  246. /package/styles/{v2/utils → utils}/_align.scss +0 -0
  247. /package/styles/{v2/utils → utils}/_animations.scss +0 -0
  248. /package/styles/{v2/utils → utils}/_breakpoints.scss +0 -0
  249. /package/styles/{v2/utils → utils}/_mixins.scss +0 -0
@@ -3,37 +3,41 @@ import { DragDropContext, Droppable, Draggable } from '@hello-pangea/dnd'
3
3
  type FilterOrderProps = {
4
4
  orderedValues: string[]
5
5
  handleFilterOrder?: (index1: number, index2: number) => void
6
+ onNestedDragAreaHover?: (isHovering: boolean) => void
6
7
  }
7
- const FilterOrder: React.FC<FilterOrderProps> = ({ orderedValues, handleFilterOrder }) => {
8
+
9
+ const FilterOrder: React.FC<FilterOrderProps> = ({ orderedValues, handleFilterOrder, onNestedDragAreaHover }) => {
8
10
  return (
9
- <DragDropContext onDragEnd={({ source, destination }) => handleFilterOrder(source?.index, destination?.index)}>
10
- <Droppable droppableId='filter_order'>
11
- {provided => (
12
- <ul {...provided.droppableProps} className='sort-list' ref={provided.innerRef} style={{ marginTop: '1em' }}>
13
- {orderedValues?.map((value, index) => {
14
- return (
15
- <Draggable key={value} draggableId={`draggableFilter-${value}`} index={index}>
16
- {(provided, snapshot) => (
17
- <li>
18
- <div
19
- className={snapshot.isDragging ? 'currently-dragging' : ''}
20
- style={provided.draggableProps.style}
21
- ref={provided.innerRef}
22
- {...provided.draggableProps}
23
- {...provided.dragHandleProps}
24
- >
25
- {value}
26
- </div>
27
- </li>
28
- )}
29
- </Draggable>
30
- )
31
- })}
32
- {provided.placeholder}
33
- </ul>
34
- )}
35
- </Droppable>
36
- </DragDropContext>
11
+ <div onMouseEnter={() => onNestedDragAreaHover?.(true)} onMouseLeave={() => onNestedDragAreaHover?.(false)}>
12
+ <DragDropContext onDragEnd={({ source, destination }) => handleFilterOrder(source?.index, destination?.index)}>
13
+ <Droppable droppableId='filter_order'>
14
+ {provided => (
15
+ <ul {...provided.droppableProps} className='sort-list' ref={provided.innerRef} style={{ marginTop: '1em' }}>
16
+ {orderedValues?.map((value, index) => {
17
+ return (
18
+ <Draggable key={value} draggableId={`draggableFilter-${value}`} index={index}>
19
+ {(provided, snapshot) => (
20
+ <li>
21
+ <div
22
+ className={snapshot.isDragging ? 'currently-dragging' : ''}
23
+ style={provided.draggableProps.style}
24
+ ref={provided.innerRef}
25
+ {...provided.draggableProps}
26
+ {...provided.dragHandleProps}
27
+ >
28
+ {value}
29
+ </div>
30
+ </li>
31
+ )}
32
+ </Draggable>
33
+ )
34
+ })}
35
+ {provided.placeholder}
36
+ </ul>
37
+ )}
38
+ </Droppable>
39
+ </DragDropContext>
40
+ </div>
37
41
  )
38
42
  }
39
43
 
@@ -22,6 +22,8 @@ type MarkupVariablesEditorProps = {
22
22
  enableMarkupVariables?: boolean
23
23
  /** Callback when enable/disable toggle changes */
24
24
  onToggleEnable?: (enabled: boolean) => void
25
+ /** File-level metadata extracted from the data source */
26
+ dataMetadata?: Record<string, string>
25
27
  }
26
28
 
27
29
  export type { MarkupVariablesEditorProps }
@@ -37,7 +39,8 @@ const MarkupVariablesEditor: React.FC<MarkupVariablesEditorProps> = ({
37
39
  config,
38
40
  onChange,
39
41
  enableMarkupVariables = false,
40
- onToggleEnable
42
+ onToggleEnable,
43
+ dataMetadata = {}
41
44
  }) => {
42
45
  const [editingIndex, setEditingIndex] = useState<number | null>(null)
43
46
  const [validationErrors, setValidationErrors] = useState<Record<number, string[]>>({})
@@ -63,6 +66,9 @@ const MarkupVariablesEditor: React.FC<MarkupVariablesEditorProps> = ({
63
66
  return []
64
67
  }, [data, datasets, config?.dataKey])
65
68
 
69
+ const metadataKeys = useMemo(() => Object.keys(dataMetadata || {}), [dataMetadata])
70
+ const hasMetadataKeys = metadataKeys.length > 0
71
+
66
72
  // Get columns from the available data (memoized for performance)
67
73
  const getAvailableColumns = useMemo((): string[] => {
68
74
  const targetData = getTargetData()
@@ -92,18 +98,20 @@ const MarkupVariablesEditor: React.FC<MarkupVariablesEditorProps> = ({
92
98
  if (!variable.tag || variable.tag.trim() === '') {
93
99
  errors.push('Variable tag is required')
94
100
  }
95
- if (!variable.columnName || variable.columnName.trim() === '') {
101
+ if (!variable.metadataKey && (!variable.columnName || variable.columnName.trim() === '')) {
96
102
  errors.push('Data column is required')
97
103
  }
98
- // Validate conditions
99
- variable.conditions?.forEach((condition, index) => {
100
- if (!condition.columnName) {
101
- errors.push(`Condition ${index + 1}: Column is required`)
102
- }
103
- if (!condition.value) {
104
- errors.push(`Condition ${index + 1}: Value is required`)
105
- }
106
- })
104
+ // Validate conditions (not applicable to metadata-sourced variables)
105
+ if (!variable.metadataKey) {
106
+ variable.conditions?.forEach((condition, index) => {
107
+ if (!condition.columnName) {
108
+ errors.push(`Condition ${index + 1}: Column is required`)
109
+ }
110
+ if (!condition.value) {
111
+ errors.push(`Condition ${index + 1}: Value is required`)
112
+ }
113
+ })
114
+ }
107
115
  return errors
108
116
  }, [])
109
117
 
@@ -240,9 +248,15 @@ const MarkupVariablesEditor: React.FC<MarkupVariablesEditorProps> = ({
240
248
  {variable.tag}
241
249
  </div>
242
250
  <div style={{ fontSize: '13px', color: '#6c757d' }}>
243
- Column: <strong>{variable.columnName || 'Not selected'}</strong>
244
- {variable.conditions && variable.conditions.length > 0 && (
245
- <span> • {variable.conditions.length} condition{variable.conditions.length !== 1 ? 's' : ''}</span>
251
+ {variable.metadataKey ? (
252
+ <>Metadata: <strong>{variable.metadataKey}</strong></>
253
+ ) : (
254
+ <>
255
+ Column: <strong>{variable.columnName || 'Not selected'}</strong>
256
+ {variable.conditions && variable.conditions.length > 0 && (
257
+ <span> • {variable.conditions.length} condition{variable.conditions.length !== 1 ? 's' : ''}</span>
258
+ )}
259
+ </>
246
260
  )}
247
261
  </div>
248
262
  {validationErrors[index] && validationErrors[index].length > 0 && (
@@ -270,6 +284,60 @@ const MarkupVariablesEditor: React.FC<MarkupVariablesEditorProps> = ({
270
284
  <div className='mt-3 pt-3 border-t'>
271
285
  <Accordion>
272
286
  <Accordion.Section title='Basic Settings'>
287
+ {hasMetadataKeys && (
288
+ <div className='mb-3'>
289
+ <Select
290
+ value={variable.metadataKey ? 'metadata' : 'column'}
291
+ fieldName='variableSource'
292
+ label='Source'
293
+ options={[
294
+ { value: 'column', label: 'Data Column' },
295
+ { value: 'metadata', label: 'Data File Metadata' }
296
+ ]}
297
+ updateField={(_section, _subsection, _fieldName, value) => {
298
+ if (value === 'metadata') {
299
+ updateVariable(index, { metadataKey: metadataKeys[0] || '', columnName: '', conditions: [] })
300
+ } else {
301
+ updateVariable(index, { metadataKey: undefined, columnName: '' })
302
+ }
303
+ }}
304
+ />
305
+ </div>
306
+ )}
307
+
308
+ {variable.metadataKey !== undefined && variable.metadataKey !== null && hasMetadataKeys ? (
309
+ <div className='mb-3'>
310
+ <Select
311
+ value={variable.metadataKey}
312
+ fieldName='metadataKey'
313
+ label='Metadata Field'
314
+ options={metadataKeys.map(key => ({ value: key, label: `${key}: ${dataMetadata[key]}` }))}
315
+ updateField={(_section, _subsection, _fieldName, value) => {
316
+ updateVariable(index, {
317
+ metadataKey: value,
318
+ name: variable.name || value,
319
+ tag: variable.tag || generateTag(value)
320
+ })
321
+ }}
322
+ />
323
+ </div>
324
+ ) : (
325
+ <div className='mb-3'>
326
+ <Select
327
+ value={variable.columnName}
328
+ fieldName='columnName'
329
+ label='Data Column'
330
+ options={[
331
+ { value: '', label: 'Select Column...' },
332
+ ...getAvailableColumns.map(col => ({ value: col, label: col }))
333
+ ]}
334
+ updateField={(_section, _subsection, _fieldName, value) => {
335
+ updateVariable(index, { columnName: value })
336
+ }}
337
+ />
338
+ </div>
339
+ )}
340
+
273
341
  <div className='mb-3'>
274
342
  <TextField
275
343
  value={variable.name}
@@ -296,102 +364,88 @@ const MarkupVariablesEditor: React.FC<MarkupVariablesEditorProps> = ({
296
364
  />
297
365
  </label>
298
366
  </div>
299
-
300
- <div className='mb-3'>
301
- <Select
302
- value={variable.columnName}
303
- fieldName='columnName'
304
- label='Data Column'
305
- options={[
306
- { value: '', label: 'Select Column...' },
307
- ...getAvailableColumns.map(col => ({ value: col, label: col }))
308
- ]}
309
- updateField={(_section, _subsection, _fieldName, value) => {
310
- updateVariable(index, { columnName: value })
311
- }}
312
- />
313
- </div>
314
367
  </Accordion.Section>
315
368
 
316
- <Accordion.Section title='Conditions'>
317
- <div className='text-sm text-gray-500 mb-2'>
318
- Add conditions to filter when this variable should display data
319
- </div>
320
-
321
- {variable.conditions && variable.conditions.length > 0 && (
322
- <div className='conditions-list mb-2'>
323
- {variable.conditions.map((condition, conditionIndex) => (
324
- <div key={`condition-${index}-${conditionIndex}`} className='condition-item p-2 border rounded mb-2' style={{ backgroundColor: '#f8f9fa' }}>
325
- <div className='mb-2'>
326
- <Select
327
- value={condition.columnName || ''}
328
- fieldName={`condition-column-${index}-${conditionIndex}`}
329
- label='Column'
330
- options={[
331
- { value: '', label: 'Select Column...' },
332
- ...getAvailableColumns.map(col => ({ value: col, label: col }))
333
- ]}
334
- updateField={(_section, _subsection, _fieldName, newColumnName) => {
335
- // Reset value when column changes
336
- updateCondition(index, conditionIndex, {
337
- columnName: newColumnName,
338
- value: ''
339
- })
340
- }}
341
- />
342
- </div>
343
- <div className='mb-2'>
344
- <Select
345
- value={condition.isOrIsNotEqualTo || 'is'}
346
- fieldName={`condition-operator-${index}-${conditionIndex}`}
347
- label='Operator'
348
- options={[
349
- { value: 'is', label: 'is' },
350
- { value: 'is not', label: 'is not' }
351
- ]}
352
- updateField={(_section, _subsection, _fieldName, value) => {
353
- updateCondition(index, conditionIndex, { isOrIsNotEqualTo: value as 'is' | 'is not' })
354
- }}
355
- />
356
- </div>
357
- <div className='mb-2'>
358
- <Select
359
- value={condition.value || ''}
360
- fieldName={`condition-value-${index}-${conditionIndex}`}
361
- label='Value'
362
- options={[
363
- { value: '', label: 'Select Value...' },
364
- ...(condition.columnName
365
- ? getColumnValues(condition.columnName).map(val => ({
366
- value: String(val),
367
- label: String(val)
368
- }))
369
- : [])
370
- ]}
371
- updateField={(_section, _subsection, _fieldName, value) => {
372
- updateCondition(index, conditionIndex, { value })
373
- }}
374
- />
375
- </div>
376
- <Button
377
- className='btn-sm btn-danger'
378
- onClick={() => removeCondition(index, conditionIndex)}
379
- >
380
- Remove Condition
381
- </Button>
382
- </div>
383
- ))}
369
+ {!variable.metadataKey && (
370
+ <Accordion.Section title='Conditions'>
371
+ <div className='text-sm text-gray-500 mb-2'>
372
+ Add conditions to filter when this variable should display data
384
373
  </div>
385
- )}
386
374
 
387
- <Button
388
- className='btn-sm'
389
- onClick={() => addCondition(index)}
390
- >
391
- <Icon display='plus' size={14} className='mr-1' />
392
- Add Condition
393
- </Button>
394
- </Accordion.Section>
375
+ {variable.conditions && variable.conditions.length > 0 && (
376
+ <div className='conditions-list mb-2'>
377
+ {variable.conditions.map((condition, conditionIndex) => (
378
+ <div key={`condition-${index}-${conditionIndex}`} className='condition-item p-2 border rounded mb-2' style={{ backgroundColor: '#f8f9fa' }}>
379
+ <div className='mb-2'>
380
+ <Select
381
+ value={condition.columnName || ''}
382
+ fieldName={`condition-column-${index}-${conditionIndex}`}
383
+ label='Column'
384
+ options={[
385
+ { value: '', label: 'Select Column...' },
386
+ ...getAvailableColumns.map(col => ({ value: col, label: col }))
387
+ ]}
388
+ updateField={(_section, _subsection, _fieldName, newColumnName) => {
389
+ updateCondition(index, conditionIndex, {
390
+ columnName: newColumnName,
391
+ value: ''
392
+ })
393
+ }}
394
+ />
395
+ </div>
396
+ <div className='mb-2'>
397
+ <Select
398
+ value={condition.isOrIsNotEqualTo || 'is'}
399
+ fieldName={`condition-operator-${index}-${conditionIndex}`}
400
+ label='Operator'
401
+ options={[
402
+ { value: 'is', label: 'is' },
403
+ { value: 'is not', label: 'is not' }
404
+ ]}
405
+ updateField={(_section, _subsection, _fieldName, value) => {
406
+ updateCondition(index, conditionIndex, { isOrIsNotEqualTo: value as 'is' | 'is not' })
407
+ }}
408
+ />
409
+ </div>
410
+ <div className='mb-2'>
411
+ <Select
412
+ value={condition.value || ''}
413
+ fieldName={`condition-value-${index}-${conditionIndex}`}
414
+ label='Value'
415
+ options={[
416
+ { value: '', label: 'Select Value...' },
417
+ ...(condition.columnName
418
+ ? getColumnValues(condition.columnName).map(val => ({
419
+ value: String(val),
420
+ label: String(val)
421
+ }))
422
+ : [])
423
+ ]}
424
+ updateField={(_section, _subsection, _fieldName, value) => {
425
+ updateCondition(index, conditionIndex, { value })
426
+ }}
427
+ />
428
+ </div>
429
+ <Button
430
+ className='btn-sm btn-danger'
431
+ onClick={() => removeCondition(index, conditionIndex)}
432
+ >
433
+ Remove Condition
434
+ </Button>
435
+ </div>
436
+ ))}
437
+ </div>
438
+ )}
439
+
440
+ <Button
441
+ className='btn-sm'
442
+ onClick={() => addCondition(index)}
443
+ >
444
+ <Icon display='plus' size={14} className='mr-1' />
445
+ Add Condition
446
+ </Button>
447
+ </Accordion.Section>
448
+ )}
395
449
 
396
450
  <Accordion.Section title='Formatting Options'>
397
451
  <div className='mb-3'>
@@ -18,6 +18,8 @@ type PanelMarkupProps = {
18
18
  onToggleEnable: (enabled: boolean) => void
19
19
  /** Optional: wrap in accordion. Default true */
20
20
  withAccordion?: boolean
21
+ /** File-level metadata extracted from the data source */
22
+ dataMetadata?: Record<string, string>
21
23
  }
22
24
 
23
25
  /**
@@ -31,7 +33,8 @@ const PanelMarkup: React.FC<PanelMarkupProps> = ({
31
33
  enableMarkupVariables,
32
34
  onMarkupVariablesChange,
33
35
  onToggleEnable,
34
- withAccordion = true
36
+ withAccordion = true,
37
+ dataMetadata
35
38
  }) => {
36
39
  const content = (
37
40
  <MarkupVariablesEditor
@@ -40,6 +43,7 @@ const PanelMarkup: React.FC<PanelMarkupProps> = ({
40
43
  onChange={onMarkupVariablesChange}
41
44
  enableMarkupVariables={enableMarkupVariables || false}
42
45
  onToggleEnable={onToggleEnable}
46
+ dataMetadata={dataMetadata}
43
47
  />
44
48
  )
45
49
 
@@ -1,7 +1,6 @@
1
- @import '../utils/variables';
2
- @import '../themes/index';
3
- @import '../../../components/HeaderThemeSelector/HeaderThemeSelector.css';
4
- @import '../../_common-components';
1
+ @import '../../styles/utils/properties';
2
+ @import '../../styles/themes/index';
3
+ @import '../HeaderThemeSelector/HeaderThemeSelector.css';
5
4
 
6
5
  .cove-editor {
7
6
  display: grid;
@@ -9,11 +8,11 @@
9
8
  grid-template-areas: 'panel content';
10
9
  height: 100vh;
11
10
 
12
- @import './../base/reset';
11
+ @import '../../styles/base/reset';
13
12
 
14
13
  .cove-editor__panel {
15
14
  grid-area: panel;
16
- width: $editorWidth;
15
+ width: var(--editorWidth);
17
16
  transition: width 500ms ease;
18
17
  font-size: 15px;
19
18
  background: #fff;
@@ -30,8 +29,8 @@
30
29
  }
31
30
 
32
31
  .cove-editor__panel-container {
33
- width: $editorWidth;
34
- min-width: $editorWidth;
32
+ width: var(--editorWidth);
33
+ min-width: var(--editorWidth);
35
34
  }
36
35
 
37
36
  .cove-editor__content {
@@ -93,7 +92,7 @@
93
92
  margin-top: 1em;
94
93
  justify-content: space-between;
95
94
 
96
- >label {
95
+ > label {
97
96
  width: 48%;
98
97
  margin-top: 0 !important;
99
98
  }
@@ -107,7 +106,7 @@
107
106
 
108
107
  .series-list {
109
108
  list-style: none;
110
- border: $lightGray 1px solid;
109
+ border: var(--lightGray) 1px solid;
111
110
 
112
111
  &:empty {
113
112
  border: none !important;
@@ -121,7 +120,7 @@
121
120
  font-size: 0.9em;
122
121
 
123
122
  &:hover {
124
- background-color: $lightestGray;
123
+ background-color: var(--lightestGray);
125
124
  }
126
125
 
127
126
  span {
@@ -130,14 +129,14 @@
130
129
  cursor: pointer;
131
130
  }
132
131
 
133
- +li {
134
- border-top: $lightGray 1px solid;
132
+ + li {
133
+ border-top: var(--lightGray) 1px solid;
135
134
  }
136
135
  }
137
136
  }
138
137
 
139
138
  .form-container {
140
- border-right: $lightGray 1px solid;
139
+ border-right: var(--lightGray) 1px solid;
141
140
  flex-grow: 1;
142
141
  }
143
142
 
@@ -147,7 +146,7 @@
147
146
 
148
147
  svg {
149
148
  width: 60px;
150
- color: $blue;
149
+ color: var(--blue);
151
150
  margin-right: 1rem;
152
151
  height: 60px; // IE11
153
152
 
@@ -189,7 +188,7 @@
189
188
  display: flex;
190
189
  justify-content: space-between;
191
190
 
192
- >label {
191
+ > label {
193
192
  margin-top: 0;
194
193
  width: 30%;
195
194
  display: inline-block;
@@ -237,7 +236,7 @@
237
236
  margin-top: 0;
238
237
  }
239
238
 
240
- label+label {
239
+ label + label {
241
240
  margin-top: 1em;
242
241
  }
243
242
 
@@ -256,13 +255,13 @@
256
255
 
257
256
  .sort-list {
258
257
  list-style: none;
258
+ padding: 0;
259
259
 
260
- >li {
261
- margin-right: 0.3em;
260
+ > li {
262
261
  margin-bottom: 0.3em;
263
262
  }
264
263
 
265
- li>div {
264
+ li > div {
266
265
  display: block;
267
266
  box-sizing: border-box;
268
267
  border: 1px solid #d1d1d1;
@@ -270,8 +269,8 @@
270
269
  background: #f1f1f1;
271
270
  padding: 0.4em 0.6em;
272
271
  font-size: 0.8em;
273
- margin-bottom: 0.3em;
274
272
  cursor: move;
273
+ width: 100%;
275
274
  }
276
275
  }
277
276
 
@@ -491,4 +490,59 @@ ul.color-palette {
491
490
  // CheckBox component styling for EditorPanel/Inputs
492
491
  .edit-checkbox {
493
492
  margin-right: 8px;
494
- }
493
+ }
494
+
495
+ // Warning icon used in accordion panel headers
496
+ .accordion__button svg.warning-icon,
497
+ .cove-accordion__button svg.warning-icon {
498
+ left: auto;
499
+ position: absolute;
500
+ right: 35px;
501
+ top: 50%;
502
+ transform: translateY(-50%);
503
+ }
504
+
505
+ .warning-icon {
506
+ color: #d72f00;
507
+ height: 15px;
508
+ width: 15px;
509
+ }
510
+
511
+ .warning-icon path {
512
+ fill: #d8000c;
513
+ }
514
+
515
+ // Checkbox group used in editor panel visual sections
516
+ .checkbox-group {
517
+ border: 1px solid #c4c4c4;
518
+ border-radius: 8px;
519
+ margin-bottom: 24px;
520
+ margin-top: 8px;
521
+ padding: 16px;
522
+ }
523
+
524
+ .cove-accordion__panel.panel-visual .checkbox-group label.checkbox,
525
+ .cove-accordion__panel.panel-visual .reverse-labels label.checkbox,
526
+ .panel-visual .checkbox-group label.checkbox,
527
+ .panel-visual .reverse-labels label.checkbox {
528
+ align-items: center !important;
529
+ display: flex !important;
530
+ margin-bottom: 8px !important;
531
+ width: 100% !important;
532
+ }
533
+
534
+ .cove-accordion__panel.panel-visual .checkbox-group label.checkbox input[type='checkbox'],
535
+ .cove-accordion__panel.panel-visual .reverse-labels label.checkbox input[type='checkbox'],
536
+ .panel-visual .checkbox-group label.checkbox input[type='checkbox'],
537
+ .panel-visual .reverse-labels label.checkbox input[type='checkbox'] {
538
+ flex-shrink: 0 !important;
539
+ margin-right: 8px !important;
540
+ }
541
+
542
+ .cove-accordion__panel.panel-visual .checkbox-group label.checkbox span,
543
+ .cove-accordion__panel.panel-visual .reverse-labels label.checkbox span,
544
+ .panel-visual .checkbox-group label.checkbox span,
545
+ .panel-visual .reverse-labels label.checkbox span {
546
+ display: inline-block !important;
547
+ flex: 1 !important;
548
+ }