@cdc/core 4.25.8 → 4.25.11

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 (163) hide show
  1. package/_stories/StoryRenderingTests.stories.tsx +164 -0
  2. package/components/AdvancedEditor/AdvancedEditor.tsx +32 -9
  3. package/components/CustomColorsEditor/CustomColorsEditor.css +299 -0
  4. package/components/CustomColorsEditor/CustomColorsEditor.tsx +209 -0
  5. package/components/CustomColorsEditor/index.ts +1 -0
  6. package/components/DataTable/DataTable.tsx +56 -38
  7. package/components/DataTable/DataTableStandAlone.tsx +8 -3
  8. package/components/DataTable/components/ChartHeader.tsx +44 -14
  9. package/components/DataTable/components/DataTableEditorPanel.tsx +12 -2
  10. package/components/DataTable/components/ExpandCollapse.tsx +10 -1
  11. package/components/DataTable/components/MapHeader.tsx +24 -13
  12. package/components/DataTable/data-table.css +12 -0
  13. package/components/DataTable/helpers/chartCellMatrix.tsx +11 -8
  14. package/components/DataTable/helpers/mapCellMatrix.tsx +33 -4
  15. package/components/DataTable/helpers/standardizeState.js +2 -2
  16. package/components/DataTable/helpers/tests/standardizeState.test.js +54 -0
  17. package/components/DownloadButton.tsx +40 -14
  18. package/components/EditorPanel/DataTableEditor.tsx +3 -3
  19. package/components/EditorPanel/EditorPanel.styles.css +423 -0
  20. package/components/EditorPanel/FootnotesEditor.tsx +44 -37
  21. package/components/EditorPanel/Inputs.tsx +12 -2
  22. package/components/EditorPanel/VizFilterEditor/NestedDropdownEditor.tsx +35 -62
  23. package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +12 -2
  24. package/components/EditorPanel/components/MarkupHighlightedTextField.tsx +227 -0
  25. package/components/EditorPanel/components/MarkupVariablesEditor.tsx +450 -0
  26. package/components/EditorPanel/components/PanelMarkup.tsx +59 -0
  27. package/components/ErrorBoundary.jsx +3 -1
  28. package/components/Filters/Filters.tsx +52 -24
  29. package/components/Filters/components/Dropdown.tsx +6 -1
  30. package/components/Filters/components/Tabs.tsx +1 -0
  31. package/components/Footnotes/Footnotes.tsx +35 -25
  32. package/components/Footnotes/FootnotesStandAlone.tsx +42 -6
  33. package/components/HeaderThemeSelector/HeaderThemeSelector.css +43 -0
  34. package/components/HeaderThemeSelector/HeaderThemeSelector.stories.tsx +74 -0
  35. package/components/HeaderThemeSelector/HeaderThemeSelector.tsx +61 -0
  36. package/components/HeaderThemeSelector/index.ts +2 -0
  37. package/components/Layout/styles/editor.scss +2 -1
  38. package/components/Legend/Legend.Gradient.tsx +3 -6
  39. package/components/LegendShape.tsx +121 -3
  40. package/components/Loader/Loader.tsx +1 -1
  41. package/components/MediaControls.tsx +72 -21
  42. package/components/PaletteConversionModal.tsx +90 -0
  43. package/components/PaletteSelector/DeveloperPaletteRollback.tsx +114 -0
  44. package/components/PaletteSelector/PaletteSelector.css +94 -0
  45. package/components/PaletteSelector/PaletteSelector.tsx +112 -0
  46. package/components/PaletteSelector/index.ts +2 -0
  47. package/components/RichTooltip/RichTooltip.tsx +1 -0
  48. package/components/Table/Table.tsx +3 -1
  49. package/components/Table/components/Cell.tsx +23 -2
  50. package/components/Table/components/Row.tsx +5 -3
  51. package/components/_stories/BlurStrokeTest.stories.tsx +1 -1
  52. package/components/_stories/DataTable.stories.tsx +1 -1
  53. package/components/_stories/Filters.stories.tsx +21 -2
  54. package/components/_stories/Footnotes.CSV.stories.tsx +247 -0
  55. package/components/_stories/Footnotes.stories.tsx +769 -4
  56. package/components/_stories/Inputs.stories.tsx +3 -3
  57. package/components/_stories/MultiSelect.stories.tsx +3 -3
  58. package/components/_stories/NestedDropdown.stories.tsx +1 -1
  59. package/components/_stories/Table.stories.tsx +1 -1
  60. package/components/_stories/styles.scss +0 -1
  61. package/components/elements/_stories/Button.stories.tsx +1 -1
  62. package/components/elements/_stories/Card.stories.tsx +1 -1
  63. package/components/inputs/InputToggle.tsx +2 -0
  64. package/components/managers/DataDesigner.tsx +10 -9
  65. package/components/managers/_stories/DataDesigner.stories.tsx +1 -1
  66. package/components/ui/Accordion.jsx +1 -1
  67. package/components/ui/Tooltip.tsx +2 -1
  68. package/components/ui/_stories/Accordion.stories.tsx +1 -1
  69. package/components/ui/_stories/ColorPaletteMigration.stories.mdx +275 -0
  70. package/components/ui/_stories/Colors.stories.tsx +330 -0
  71. package/components/ui/_stories/IconGallery.stories.tsx +316 -0
  72. package/components/ui/_stories/Title.stories.tsx +1 -1
  73. package/components/ui/accordion.styles.css +57 -0
  74. package/contexts/EditorContext.ts +18 -0
  75. package/contexts/editor.actions.ts +28 -0
  76. package/contexts/editor.reducer.ts +94 -0
  77. package/data/chartColorPalettes.ts +118 -0
  78. package/data/colorPalettes.ts +9 -0
  79. package/data/mapColorPalettes.ts +45 -0
  80. package/data/sharedPalettes.ts +50 -0
  81. package/dist/cove-main.css +63 -14
  82. package/dist/cove-main.css.map +1 -1
  83. package/generateViteConfig.js +80 -0
  84. package/helpers/addValuesToFilters.ts +7 -3
  85. package/helpers/cloneConfig.ts +31 -0
  86. package/helpers/configDataHelpers.ts +128 -0
  87. package/helpers/configHelpers.ts +27 -0
  88. package/helpers/constants.ts +42 -2
  89. package/helpers/cove/number.ts +33 -12
  90. package/helpers/coveUpdateWorker.ts +15 -3
  91. package/helpers/fetchRemoteData.ts +3 -15
  92. package/helpers/filterColorPalettes.ts +152 -0
  93. package/helpers/generateColorsArray.ts +13 -0
  94. package/helpers/getColorPaletteVersion.ts +33 -0
  95. package/helpers/getPaletteAccessor.ts +18 -0
  96. package/helpers/markupProcessor.ts +220 -0
  97. package/helpers/mergeCustomOrderValues.ts +37 -0
  98. package/helpers/metrics/helpers.ts +42 -19
  99. package/helpers/metrics/types.ts +48 -9
  100. package/helpers/metrics/utils.ts +34 -0
  101. package/helpers/palettes/colorDistributions.ts +56 -0
  102. package/helpers/palettes/migratePaletteName.ts +150 -0
  103. package/helpers/palettes/standardizePaletteNames.ts +77 -0
  104. package/helpers/palettes/utils.ts +267 -0
  105. package/helpers/parseCsvWithQuotes.ts +65 -0
  106. package/helpers/queryStringUtils.ts +13 -0
  107. package/helpers/testing.ts +358 -0
  108. package/helpers/tests/addValuesToFilters.test.ts +1 -2
  109. package/helpers/tests/generateColorsArray.test.ts +24 -0
  110. package/helpers/tests/markupProcessor.test.ts +538 -0
  111. package/helpers/tests/testStandaloneBuild.ts +44 -0
  112. package/helpers/useMarkupVariables.ts +31 -0
  113. package/helpers/vegaConfig.ts +0 -1
  114. package/helpers/ver/4.24.10.ts +2 -1
  115. package/helpers/ver/4.24.11.ts +2 -1
  116. package/helpers/ver/4.24.3.ts +2 -1
  117. package/helpers/ver/4.24.4.ts +2 -1
  118. package/helpers/ver/4.24.5.ts +2 -1
  119. package/helpers/ver/4.24.7.ts +2 -1
  120. package/helpers/ver/4.24.9.ts +2 -1
  121. package/helpers/ver/4.25.1.ts +2 -1
  122. package/helpers/ver/4.25.10.ts +36 -0
  123. package/helpers/ver/4.25.11.ts +13 -0
  124. package/helpers/ver/4.25.3.ts +2 -1
  125. package/helpers/ver/4.25.4.ts +2 -1
  126. package/helpers/ver/4.25.6.ts +2 -1
  127. package/helpers/ver/4.25.7.ts +2 -1
  128. package/helpers/ver/4.25.8.ts +2 -1
  129. package/helpers/ver/4.25.9.ts +293 -0
  130. package/helpers/ver/tests/4.25.10.test.ts +204 -0
  131. package/helpers/ver/tests/4.25.8.test.ts +1 -1
  132. package/helpers/ver/tests/4.25.9.test.ts +51 -0
  133. package/helpers/viewports.ts +2 -0
  134. package/hooks/useColorPalette.ts +79 -0
  135. package/package.json +13 -4
  136. package/styles/_common-components.css +73 -0
  137. package/styles/_global.scss +32 -10
  138. package/styles/base.scss +8 -55
  139. package/styles/cove-main.scss +3 -1
  140. package/styles/filters.scss +10 -3
  141. package/styles/v2/base/index.scss +0 -1
  142. package/styles/v2/components/button.scss +4 -3
  143. package/styles/v2/components/editor.scss +16 -7
  144. package/styles/v2/layout/_data-table.scss +3 -2
  145. package/styles/v2/themes/_color-definitions.scss +18 -17
  146. package/styles/v2/utils/_breakpoints.scss +1 -1
  147. package/styles/v2/utils/index.scss +0 -1
  148. package/styles/waiting.scss +1 -1
  149. package/testing-setup.js +32 -0
  150. package/types/MarkupInclude.ts +8 -2
  151. package/types/MarkupVariable.ts +19 -0
  152. package/types/VizFilter.ts +2 -0
  153. package/vitest.config.ts +16 -0
  154. package/components/ui/_stories/Colors.stories.mdx +0 -220
  155. package/components/ui/_stories/IconGallery.stories.mdx +0 -14
  156. package/data/colorPalettes.js +0 -171
  157. package/helpers/formatConfigBeforeSave.ts +0 -135
  158. package/helpers/tests/formatConfigBeforeSave.test.ts +0 -68
  159. package/styles/_mixins.scss +0 -13
  160. package/styles/v2/base/_typography.scss +0 -0
  161. package/styles/v2/components/guidance-block.scss +0 -74
  162. package/styles/v2/utils/_functions.scss +0 -0
  163. /package/{styles/_typography.scss → testBuild.js} +0 -0
@@ -0,0 +1,164 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { within, expect } from 'storybook/test'
3
+ import { performAndAssert } from '@cdc/core/helpers/testing'
4
+
5
+ const ChartRenderingValidator = () => (
6
+ <div data-testid='chart-rendering-validator'>
7
+ <h2>Simple COVE Visualization Tests</h2>
8
+ <p>This test validates all stories load and render.</p>
9
+ </div>
10
+ )
11
+
12
+ const meta: Meta<typeof ChartRenderingValidator> = {
13
+ title: 'Testing/Story Rendering Tests',
14
+ component: ChartRenderingValidator,
15
+ parameters: {
16
+ layout: 'fullscreen'
17
+ },
18
+ tags: ['!dev', '!autodocs']
19
+ }
20
+
21
+ export default meta
22
+ type Story = StoryObj<typeof ChartRenderingValidator>
23
+
24
+ /**
25
+ * Fetch all stories from Storybook's JSON API and filter for visualization stories
26
+ * @returns Promise that resolves to an array of story URLs to test
27
+ */
28
+ const getVisualizationStoryUrls = async (): Promise<string[]> => {
29
+ let response
30
+ try {
31
+ response = await fetch('http://localhost:6006/index.json')
32
+ } catch (error) {
33
+ console.error('Error fetching visualization story URLs:', error)
34
+ return []
35
+ }
36
+
37
+ const data = await response.json()
38
+
39
+ const storyUrls: string[] = []
40
+
41
+ Object.values(data.entries).forEach((story: any) => {
42
+ if (story.type === 'story') {
43
+ const isVisualizationStory =
44
+ story.title.includes('Components/Templates/') &&
45
+ !story.name.toLowerCase().includes('test') &&
46
+ !story.title.includes('Guide')
47
+
48
+ if (isVisualizationStory) {
49
+ const iframeUrl = `http://localhost:6006/iframe.html?id=${story.id}`
50
+ storyUrls.push(iframeUrl)
51
+ }
52
+ }
53
+ })
54
+ return storyUrls
55
+ }
56
+
57
+ /**
58
+ * Convert iframe URL to Storybook story URL for better debugging
59
+ * @param iframeUrl - The iframe URL (e.g., 'http://localhost:6006/iframe.html?id=components-templates-chart--multiple-lines')
60
+ * @returns The Storybook story URL (e.g., 'http://localhost:6006/?path=/story/components-templates-chart--multiple-lines')
61
+ */
62
+ const iframeUrlToStoryUrl = (iframeUrl: string): string => {
63
+ const url = new URL(iframeUrl)
64
+ const storyId = url.searchParams.get('id')
65
+ return `http://localhost:6006/?path=/story/${storyId}`
66
+ }
67
+
68
+ /**
69
+ * Test a single Storybook iframe URL for successful visualization rendering
70
+ * @param iframeUrl - The complete iframe URL to test (e.g., 'http://localhost:6006/iframe.html?id=...')
71
+ * @returns Promise that resolves with test results
72
+ */
73
+ const testIframeVisualization = async (iframeUrl: string) => {
74
+ iframeUrl = iframeUrl
75
+
76
+ const iframe = document.createElement('iframe')
77
+ iframe.style.width = '1200px'
78
+ iframe.style.height = '800px'
79
+ iframe.src = iframeUrl
80
+ document.body.appendChild(iframe)
81
+
82
+ try {
83
+ await performAndAssert(
84
+ 'Wait for iframe to load',
85
+ () => {
86
+ try {
87
+ const iframeDoc = iframe.contentDocument || iframe.contentWindow?.document
88
+ return {
89
+ loaded: !!iframeDoc && iframeDoc.readyState !== 'loading',
90
+ readyState: iframeDoc?.readyState || 'unknown'
91
+ }
92
+ } catch (error: any) {
93
+ return { loaded: false, readyState: 'error', error: error.message }
94
+ }
95
+ },
96
+ async () => {},
97
+ (before, after) => {
98
+ return after.loaded
99
+ }
100
+ )
101
+
102
+ await performAndAssert(
103
+ 'Wait for SVG elements to render in iframe',
104
+ () => {
105
+ try {
106
+ const iframeDoc = iframe?.contentDocument || iframe?.contentWindow?.document
107
+ if (!iframeDoc) return { svgCount: 0, hasCoveModule: false, error: 'No document access' }
108
+
109
+ const svgCount = iframeDoc.querySelectorAll('svg').length
110
+ const hasCoveModule = !!iframeDoc.querySelector('.cdc-open-viz-module')
111
+ const isDataBite = !!iframeDoc.querySelector('.bite-content')
112
+ const isDataTable = !!iframeDoc.querySelector('.data-table')
113
+
114
+ return { svgCount, hasCoveModule, isDataBite, isDataTable, error: null }
115
+ } catch (error: any) {
116
+ return { svgCount: 0, hasCoveModule: false, isDataBite: false, isDataTable: false, error: error.message }
117
+ }
118
+ },
119
+ async () => {},
120
+ (before, after) => {
121
+ return (after.svgCount > 0 && after.hasCoveModule) || after.isDataBite || after.isDataTable
122
+ }
123
+ )
124
+ } finally {
125
+ if (iframe.parentNode) {
126
+ document.body.removeChild(iframe)
127
+ }
128
+ }
129
+ }
130
+
131
+ export const StoryRenderingTests: Story = {
132
+ play: async ({ canvasElement }) => {
133
+ const canvas = within(canvasElement)
134
+ expect(canvas.getByTestId('chart-rendering-validator')).toBeInTheDocument()
135
+
136
+ const storyUrls = await getVisualizationStoryUrls()
137
+
138
+ if (storyUrls.length === 0) {
139
+ console.warn('No visualization stories found to test')
140
+ return
141
+ }
142
+
143
+ const results: { iframeUrl: string; storyUrl: string; success: boolean; error?: string }[] = []
144
+
145
+ for (const [i, iframeUrl] of storyUrls.entries()) {
146
+ const storyUrl = iframeUrlToStoryUrl(iframeUrl)
147
+
148
+ try {
149
+ await testIframeVisualization(iframeUrl)
150
+ results.push({ iframeUrl, storyUrl, success: true })
151
+ } catch (error: any) {
152
+ if (i > 0) {
153
+ results.push({ iframeUrl, storyUrl, success: false, error: error.message })
154
+ }
155
+ }
156
+ }
157
+
158
+ const failed = results.filter(r => !r.success).length
159
+
160
+ if (failed > 0) {
161
+ throw new Error(`${failed} out of ${storyUrls.length} visualization stories failed to render`)
162
+ }
163
+ }
164
+ }
@@ -3,12 +3,17 @@ import MapIcon from '../../assets/map-folded.svg'
3
3
  import ChartIcon from '../../assets/icon-chart-bar.svg'
4
4
  import MarkupIncludeIcon from '../../assets/icon-code.svg'
5
5
  import { FilterFunction, JsonEditor, UpdateFunction } from 'json-edit-react'
6
- import { formatConfigBeforeSave as stripConfig } from '../../helpers/formatConfigBeforeSave'
7
6
  import './advanced-editor-styles.css'
8
7
  import _ from 'lodash'
9
8
  import Tooltip from '../ui/Tooltip'
10
9
 
11
- export const AdvancedEditor = ({ loadConfig, config, convertStateToConfig, onExpandCollapse = () => {} }) => {
10
+ export const AdvancedEditor = ({
11
+ loadConfig,
12
+ config,
13
+ convertStateToConfig,
14
+ stripConfig = config => config,
15
+ onExpandCollapse = () => {}
16
+ }) => {
12
17
  const [advancedToggle, _setAdvancedToggle] = useState(false)
13
18
  const [configTextboxValue, setConfigTextbox] = useState<Record<string, any>>({})
14
19
  const setAdvancedToggle = val => {
@@ -26,19 +31,37 @@ export const AdvancedEditor = ({ loadConfig, config, convertStateToConfig, onExp
26
31
  }
27
32
 
28
33
  useEffect(() => {
29
- let parsedConfig = stripConfig(config)
30
- if (config.type !== 'dashboard') {
31
- parsedConfig = convertStateToConfig()
34
+ // Only process config when advanced editor is open to improve performance
35
+ if (advancedToggle) {
36
+ let parsedConfig = stripConfig(config)
37
+ if (config.type !== 'dashboard') {
38
+ parsedConfig = convertStateToConfig()
39
+ }
40
+
41
+ setConfigTextbox(parsedConfig)
32
42
  }
43
+ }, [config, advancedToggle])
33
44
 
34
- setConfigTextbox(parsedConfig)
35
- }, [config])
45
+ // Initialize config when advanced editor is first opened
46
+ const handleToggleOpen = () => {
47
+ if (!advancedToggle) {
48
+ // Process config only when opening for the first time
49
+ let parsedConfig = stripConfig(config)
50
+ if (config.type !== 'dashboard') {
51
+ parsedConfig = convertStateToConfig()
52
+ }
53
+ setConfigTextbox(parsedConfig)
54
+ }
55
+ setAdvancedToggle(!advancedToggle)
56
+ }
36
57
 
37
58
  const typeLookup = {
38
59
  chart: ['Charts', 'https://www.cdc.gov/cove/index.html', <ChartIcon />],
39
60
  dashboard: ['Dashboard', 'https://www.cdc.gov/cove/index.html', <ChartIcon />],
40
61
  map: ['Maps', 'https://www.cdc.gov/cove/index.html', <MapIcon />],
41
- 'markup-include': ['Markup Include', 'https://www.cdc.gov/cove/index.html', <MarkupIncludeIcon />]
62
+ 'markup-include': ['Markup Include', 'https://www.cdc.gov/cove/index.html', <MarkupIncludeIcon />],
63
+ 'data-bite': ['Data Bite', 'https://www.cdc.gov/cove/index.html', <ChartIcon />],
64
+ 'waffle-chart': ['Waffle Chart', 'https://www.cdc.gov/cove/index.html', <ChartIcon />]
42
65
  }
43
66
 
44
67
  if (!config.type) return <></>
@@ -58,7 +81,7 @@ export const AdvancedEditor = ({ loadConfig, config, convertStateToConfig, onExp
58
81
  </div>
59
82
  </a>
60
83
  <div className='advanced'>
61
- <span className='advanced-toggle-link' onClick={() => setAdvancedToggle(!advancedToggle)}>
84
+ <span className='advanced-toggle-link' onClick={handleToggleOpen}>
62
85
  <span>{advancedToggle ? `— ` : `+ `}</span>Advanced Options
63
86
  </span>
64
87
  {advancedToggle && (
@@ -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
+ }