@cdc/core 4.25.10 → 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.
- package/_stories/StoryRenderingTests.stories.tsx +164 -0
- package/components/AdvancedEditor/AdvancedEditor.tsx +3 -1
- package/components/CustomColorsEditor/CustomColorsEditor.css +299 -0
- package/components/CustomColorsEditor/CustomColorsEditor.tsx +209 -0
- package/components/CustomColorsEditor/index.ts +1 -0
- package/components/DataTable/DataTableStandAlone.tsx +8 -3
- package/components/DataTable/components/DataTableEditorPanel.tsx +12 -2
- package/components/DataTable/data-table.css +6 -0
- package/components/DataTable/helpers/mapCellMatrix.tsx +14 -3
- package/components/DataTable/helpers/standardizeState.js +2 -2
- package/components/DataTable/helpers/tests/standardizeState.test.js +54 -0
- package/components/EditorPanel/DataTableEditor.tsx +3 -3
- package/components/EditorPanel/EditorPanel.styles.css +423 -0
- package/components/EditorPanel/FootnotesEditor.tsx +44 -37
- package/components/EditorPanel/Inputs.tsx +12 -2
- package/components/EditorPanel/VizFilterEditor/NestedDropdownEditor.tsx +35 -62
- package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +12 -2
- package/components/EditorPanel/components/MarkupVariablesEditor.tsx +61 -22
- package/components/Filters/Filters.tsx +26 -5
- package/components/Filters/components/Dropdown.tsx +6 -1
- package/components/Footnotes/Footnotes.tsx +35 -25
- package/components/Footnotes/FootnotesStandAlone.tsx +42 -6
- package/components/HeaderThemeSelector/HeaderThemeSelector.css +43 -0
- package/components/HeaderThemeSelector/HeaderThemeSelector.stories.tsx +74 -0
- package/components/HeaderThemeSelector/HeaderThemeSelector.tsx +61 -0
- package/components/HeaderThemeSelector/index.ts +2 -0
- package/components/Layout/styles/editor.scss +2 -1
- package/components/Loader/Loader.tsx +1 -1
- package/components/MediaControls.tsx +21 -18
- package/components/PaletteConversionModal.tsx +7 -4
- package/components/PaletteSelector/PaletteSelector.css +49 -6
- package/components/Table/components/Cell.tsx +23 -2
- package/components/Table/components/Row.tsx +5 -3
- package/components/_stories/Filters.stories.tsx +20 -1
- package/components/_stories/Footnotes.CSV.stories.tsx +247 -0
- package/components/_stories/Footnotes.stories.tsx +768 -3
- package/components/_stories/Inputs.stories.tsx +2 -2
- package/components/_stories/styles.scss +0 -1
- package/components/ui/Accordion.jsx +1 -1
- package/components/ui/accordion.styles.css +57 -0
- package/data/chartColorPalettes.ts +1 -1
- package/dist/cove-main.css +49 -3
- package/dist/cove-main.css.map +1 -1
- package/helpers/addValuesToFilters.ts +5 -0
- package/helpers/constants.ts +37 -0
- package/helpers/cove/number.ts +33 -12
- package/helpers/coveUpdateWorker.ts +3 -1
- package/helpers/fetchRemoteData.ts +3 -15
- package/helpers/markupProcessor.ts +27 -12
- package/helpers/mergeCustomOrderValues.ts +37 -0
- package/helpers/parseCsvWithQuotes.ts +65 -0
- package/helpers/testing.ts +17 -4
- package/helpers/ver/4.25.11.ts +13 -0
- package/helpers/viewports.ts +2 -0
- package/package.json +4 -3
- package/styles/_common-components.css +73 -0
- package/styles/_global.scss +25 -5
- package/styles/base.scss +0 -50
- package/styles/cove-main.scss +3 -1
- package/styles/filters.scss +10 -3
- package/styles/v2/base/index.scss +0 -1
- package/styles/v2/components/editor.scss +14 -6
- package/styles/v2/utils/_breakpoints.scss +1 -1
- package/styles/v2/utils/index.scss +0 -1
- package/styles/waiting.scss +1 -1
- package/types/MarkupInclude.ts +4 -3
- package/types/MarkupVariable.ts +1 -1
- package/types/VizFilter.ts +1 -0
- package/styles/_mixins.scss +0 -13
- package/styles/_typography.scss +0 -0
- package/styles/v2/base/_typography.scss +0 -0
- package/styles/v2/components/guidance-block.scss +0 -74
- package/styles/v2/utils/_functions.scss +0 -0
|
@@ -1,17 +1,45 @@
|
|
|
1
1
|
import Footnotes from './Footnotes'
|
|
2
|
-
import FootnotesConfig from '../../types/Footnotes'
|
|
2
|
+
import FootnotesConfig, { Footnote } from '../../types/Footnotes'
|
|
3
3
|
import _ from 'lodash'
|
|
4
4
|
import { useMemo } from 'react'
|
|
5
5
|
import { filterVizData } from '../../helpers/filterVizData'
|
|
6
6
|
import { VizFilter } from '../../types/VizFilter'
|
|
7
|
+
import { MarkupVariable } from '../../types/MarkupVariable'
|
|
8
|
+
import { processMarkupVariables } from '../../helpers/markupProcessor'
|
|
7
9
|
|
|
8
10
|
type StandAloneProps = {
|
|
9
11
|
config: FootnotesConfig
|
|
10
12
|
filters?: VizFilter[]
|
|
13
|
+
markupVariables?: MarkupVariable[]
|
|
14
|
+
enableMarkupVariables?: boolean
|
|
15
|
+
data?: Object[]
|
|
11
16
|
}
|
|
12
17
|
|
|
13
|
-
const FootnotesStandAlone: React.FC<StandAloneProps> = ({ config, filters }) => {
|
|
18
|
+
const FootnotesStandAlone: React.FC<StandAloneProps> = ({ config, filters, markupVariables = [], enableMarkupVariables = false, data = [] }) => {
|
|
14
19
|
if (!config) return null
|
|
20
|
+
|
|
21
|
+
// Helper function to process markup variables in footnote text
|
|
22
|
+
const processFootnoteText = (text: string): string => {
|
|
23
|
+
if (!enableMarkupVariables || !markupVariables || markupVariables.length === 0) {
|
|
24
|
+
return text
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Use data from props if available, otherwise use config.data
|
|
28
|
+
const footnoteData = data.length > 0 ? data : config.data || []
|
|
29
|
+
|
|
30
|
+
const { processedContent } = processMarkupVariables(
|
|
31
|
+
text,
|
|
32
|
+
footnoteData,
|
|
33
|
+
markupVariables,
|
|
34
|
+
{
|
|
35
|
+
filters,
|
|
36
|
+
isEditor: false
|
|
37
|
+
}
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
return processedContent
|
|
41
|
+
}
|
|
42
|
+
|
|
15
43
|
// get the api footnotes from the config
|
|
16
44
|
const apiFootnotes = useMemo(() => {
|
|
17
45
|
// If filters exist and should filter footnotes, apply them, otherwise use data as-is
|
|
@@ -20,13 +48,21 @@ const FootnotesStandAlone: React.FC<StandAloneProps> = ({ config, filters }) =>
|
|
|
20
48
|
const { symbolColumn, textColumn, orderColumn } = config.dynamicFootnotes
|
|
21
49
|
const _data = configData.map(row => _.pick(row, [symbolColumn, textColumn, orderColumn]))
|
|
22
50
|
_data.sort((a, b) => a[orderColumn] - b[orderColumn])
|
|
23
|
-
return _data.map(row => ({
|
|
51
|
+
return _data.map(row => ({
|
|
52
|
+
symbol: row[symbolColumn],
|
|
53
|
+
text: processFootnoteText(row[textColumn])
|
|
54
|
+
}))
|
|
24
55
|
}
|
|
25
56
|
return []
|
|
26
|
-
}, [config.dynamicFootnotes, config.data, filters])
|
|
57
|
+
}, [config.dynamicFootnotes, config.data, filters, markupVariables, enableMarkupVariables, data])
|
|
27
58
|
|
|
28
|
-
// get static footnotes from the config.footnotes
|
|
29
|
-
const staticFootnotes =
|
|
59
|
+
// get static footnotes from the config.footnotes and process their text
|
|
60
|
+
const staticFootnotes: Footnote[] = useMemo(() => {
|
|
61
|
+
return (config.staticFootnotes || []).map(footnote => ({
|
|
62
|
+
...footnote,
|
|
63
|
+
text: processFootnoteText(footnote.text)
|
|
64
|
+
}))
|
|
65
|
+
}, [config.staticFootnotes, markupVariables, enableMarkupVariables, data, filters])
|
|
30
66
|
|
|
31
67
|
return <Footnotes footnotes={[...apiFootnotes, ...staticFootnotes]} />
|
|
32
68
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/* HeaderThemeSelector component styles */
|
|
2
|
+
|
|
3
|
+
.header {
|
|
4
|
+
margin-bottom: 1rem;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.header .edit-label {
|
|
8
|
+
display: block;
|
|
9
|
+
margin-bottom: 0.5rem;
|
|
10
|
+
font-weight: 500;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.header .color-palette {
|
|
14
|
+
display: flex;
|
|
15
|
+
flex-wrap: wrap;
|
|
16
|
+
gap: 0.5rem;
|
|
17
|
+
list-style: none;
|
|
18
|
+
margin: 0;
|
|
19
|
+
padding: 0;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.header .color-palette button {
|
|
23
|
+
width: 30px;
|
|
24
|
+
height: 30px;
|
|
25
|
+
border-radius: 50%;
|
|
26
|
+
border: 2px solid transparent;
|
|
27
|
+
cursor: pointer;
|
|
28
|
+
transition: all 0.2s ease;
|
|
29
|
+
outline: none;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.header .color-palette button:hover {
|
|
33
|
+
transform: scale(1.1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.header .color-palette button.selected {
|
|
37
|
+
border-color: #000;
|
|
38
|
+
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.header .color-palette button:focus {
|
|
42
|
+
box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.25);
|
|
43
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react'
|
|
2
|
+
import HeaderThemeSelector from '../HeaderThemeSelector'
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof HeaderThemeSelector> = {
|
|
5
|
+
title: 'Components/Atoms/HeaderThemeSelector',
|
|
6
|
+
component: HeaderThemeSelector,
|
|
7
|
+
parameters: {
|
|
8
|
+
docs: {
|
|
9
|
+
description: {
|
|
10
|
+
component: 'A reusable component for selecting header themes across different visualization types.'
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
argTypes: {
|
|
15
|
+
onThemeSelect: { action: 'theme-selected' },
|
|
16
|
+
selectedTheme: {
|
|
17
|
+
control: 'select',
|
|
18
|
+
options: [
|
|
19
|
+
'theme-blue',
|
|
20
|
+
'theme-purple',
|
|
21
|
+
'theme-brown',
|
|
22
|
+
'theme-teal',
|
|
23
|
+
'theme-pink',
|
|
24
|
+
'theme-orange',
|
|
25
|
+
'theme-slate',
|
|
26
|
+
'theme-indigo',
|
|
27
|
+
'theme-cyan',
|
|
28
|
+
'theme-green',
|
|
29
|
+
'theme-amber'
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
} satisfies Meta<typeof HeaderThemeSelector>
|
|
34
|
+
|
|
35
|
+
export default meta
|
|
36
|
+
type Story = StoryObj<typeof meta>
|
|
37
|
+
|
|
38
|
+
const defaultHeaderColors = [
|
|
39
|
+
'theme-blue',
|
|
40
|
+
'theme-purple',
|
|
41
|
+
'theme-brown',
|
|
42
|
+
'theme-teal',
|
|
43
|
+
'theme-pink',
|
|
44
|
+
'theme-orange',
|
|
45
|
+
'theme-slate',
|
|
46
|
+
'theme-indigo',
|
|
47
|
+
'theme-cyan',
|
|
48
|
+
'theme-green',
|
|
49
|
+
'theme-amber'
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
export const Default: Story = {
|
|
53
|
+
args: {}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const WithSelectedTheme: Story = {
|
|
57
|
+
args: {
|
|
58
|
+
selectedTheme: 'theme-purple'
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const CustomLabel: Story = {
|
|
63
|
+
args: {
|
|
64
|
+
label: 'Choose Color Theme',
|
|
65
|
+
selectedTheme: 'theme-teal'
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const CustomColors: Story = {
|
|
70
|
+
args: {
|
|
71
|
+
headerColors: ['theme-blue', 'theme-purple', 'theme-orange', 'theme-green'],
|
|
72
|
+
selectedTheme: 'theme-blue'
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import './HeaderThemeSelector.css'
|
|
3
|
+
|
|
4
|
+
// Default header theme colors used across all CDC Open Viz packages
|
|
5
|
+
const DEFAULT_HEADER_COLORS = [
|
|
6
|
+
'theme-blue',
|
|
7
|
+
'theme-purple',
|
|
8
|
+
'theme-brown',
|
|
9
|
+
'theme-teal',
|
|
10
|
+
'theme-pink',
|
|
11
|
+
'theme-orange',
|
|
12
|
+
'theme-slate',
|
|
13
|
+
'theme-indigo',
|
|
14
|
+
'theme-cyan',
|
|
15
|
+
'theme-green',
|
|
16
|
+
'theme-amber'
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
interface HeaderThemeSelectorProps {
|
|
20
|
+
/** Array of theme color names to display. Defaults to standard CDC theme colors */
|
|
21
|
+
headerColors?: string[]
|
|
22
|
+
/** Currently selected theme */
|
|
23
|
+
selectedTheme?: string
|
|
24
|
+
/** Callback when a theme is selected */
|
|
25
|
+
onThemeSelect: (theme: string) => void
|
|
26
|
+
/** Optional label for the selector */
|
|
27
|
+
label?: string
|
|
28
|
+
/** Optional CSS class name */
|
|
29
|
+
className?: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const HeaderThemeSelector: React.FC<HeaderThemeSelectorProps> = ({
|
|
33
|
+
headerColors = DEFAULT_HEADER_COLORS,
|
|
34
|
+
selectedTheme,
|
|
35
|
+
onThemeSelect,
|
|
36
|
+
label = 'Header Theme',
|
|
37
|
+
className = 'color-palette'
|
|
38
|
+
}) => {
|
|
39
|
+
const handleThemeSelection = (theme: string) => (e: React.MouseEvent) => {
|
|
40
|
+
e.preventDefault()
|
|
41
|
+
onThemeSelect(theme)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<label className='header'>
|
|
46
|
+
<span className='edit-label'>{label}</span>
|
|
47
|
+
<ul className={className}>
|
|
48
|
+
{headerColors.map(theme => (
|
|
49
|
+
<button
|
|
50
|
+
title={theme}
|
|
51
|
+
key={theme}
|
|
52
|
+
onClick={handleThemeSelection(theme)}
|
|
53
|
+
className={selectedTheme === theme ? `selected ${theme}` : theme}
|
|
54
|
+
/>
|
|
55
|
+
))}
|
|
56
|
+
</ul>
|
|
57
|
+
</label>
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export default HeaderThemeSelector
|
|
@@ -12,7 +12,7 @@ type LoaderProps = {
|
|
|
12
12
|
|
|
13
13
|
const Spinner = ({ spinnerType }: { spinnerType: SpinnerType }) => (
|
|
14
14
|
<div className={`spinner-border ${spinnerType}`} role='status'>
|
|
15
|
-
<span className='sr-only'>Loading...</span>
|
|
15
|
+
<span className='sr-only' style={{ display: 'none' }}>Loading...</span>
|
|
16
16
|
</div>
|
|
17
17
|
)
|
|
18
18
|
|
|
@@ -47,50 +47,50 @@ const generateMedia = (state, type, elementToCapture, interactionLabel) => {
|
|
|
47
47
|
return undefined
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
// Generate timestamp once for consistency
|
|
51
|
+
const date = new Date()
|
|
52
|
+
const day = date.getDate()
|
|
53
|
+
const month = date.getMonth() + 1
|
|
54
|
+
const year = date.getFullYear()
|
|
55
|
+
const timestamp = `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`
|
|
56
|
+
|
|
50
57
|
// Handles different state title locations between components
|
|
51
58
|
// Apparently some packages use state.title where others use state.general.title
|
|
52
59
|
const handleFileName = state => {
|
|
53
60
|
// dashboard titles
|
|
54
61
|
if (state?.dashboard?.title)
|
|
55
62
|
return (
|
|
56
|
-
state.dashboard.title.replace(/\s+/g, '-').toLowerCase()
|
|
57
|
-
'-' +
|
|
58
|
-
date.getDate() +
|
|
59
|
-
date.getMonth() +
|
|
60
|
-
date.getFullYear()
|
|
63
|
+
`${state.dashboard.title.replace(/\s+/g, '-').toLowerCase()}-${timestamp}`
|
|
61
64
|
)
|
|
62
65
|
|
|
63
66
|
// map titles
|
|
64
67
|
if (state?.general?.title)
|
|
65
68
|
return (
|
|
66
|
-
state.general.title.replace(/\s+/g, '-').toLowerCase()
|
|
67
|
-
'-' +
|
|
68
|
-
date.getDate() +
|
|
69
|
-
date.getMonth() +
|
|
70
|
-
date.getFullYear()
|
|
69
|
+
`${state.general.title.replace(/\s+/g, '-').toLowerCase()}-${timestamp}`
|
|
71
70
|
)
|
|
72
71
|
|
|
73
72
|
// chart titles
|
|
74
73
|
if (state?.title)
|
|
75
74
|
return (
|
|
76
|
-
state.title.replace(/\s+/g, '-').toLowerCase()
|
|
75
|
+
`${state.title.replace(/\s+/g, '-').toLowerCase()}-${timestamp}`
|
|
77
76
|
)
|
|
78
77
|
|
|
79
78
|
return 'no-title'
|
|
80
79
|
}
|
|
81
80
|
|
|
82
|
-
// Construct filename with timestamp
|
|
83
|
-
const date = new Date()
|
|
84
81
|
const filename = handleFileName(state)
|
|
85
82
|
|
|
86
83
|
switch (type) {
|
|
87
84
|
case 'image':
|
|
88
85
|
const container = document.createElement('div')
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
86
|
+
|
|
87
|
+
// Simple configurable padding (main fix for spacing issues)
|
|
88
|
+
const downloadPadding = state.downloadImagePadding !== undefined ? state.downloadImagePadding : (!state.showTitle ? 35 : 0)
|
|
89
|
+
if (downloadPadding > 0) {
|
|
90
|
+
container.style.padding = `${downloadPadding}px`
|
|
92
91
|
}
|
|
93
|
-
|
|
92
|
+
|
|
93
|
+
container.appendChild(baseSvg.cloneNode(true));
|
|
94
94
|
|
|
95
95
|
const downloadImage = async () => {
|
|
96
96
|
document.body.appendChild(container) // Append container to the DOM
|
|
@@ -116,7 +116,10 @@ const generateMedia = (state, type, elementToCapture, interactionLabel) => {
|
|
|
116
116
|
.default(container, {
|
|
117
117
|
ignoreElements: el =>
|
|
118
118
|
el.className?.indexOf &&
|
|
119
|
-
el.className.search(/download-buttons|download-links|data-table-container/) !== -1
|
|
119
|
+
el.className.search(/download-buttons|download-links|data-table-container/) !== -1,
|
|
120
|
+
useCORS: true,
|
|
121
|
+
scale: 2, // Better quality
|
|
122
|
+
allowTaint: true,
|
|
120
123
|
})
|
|
121
124
|
.then(canvas => {
|
|
122
125
|
document.body.removeChild(container) // Clean up container
|
|
@@ -43,11 +43,14 @@ const PaletteConversionModal: React.FC<PaletteConversionModalProps> = ({
|
|
|
43
43
|
<div
|
|
44
44
|
className='modal-header'
|
|
45
45
|
style={{
|
|
46
|
-
padding: '
|
|
47
|
-
borderBottom: '1px solid #e0e0e0'
|
|
46
|
+
padding: '15px',
|
|
47
|
+
borderBottom: '1px solid #e0e0e0',
|
|
48
|
+
backgroundColor: '#005eaa',
|
|
49
|
+
display: 'flex',
|
|
50
|
+
justifyContent: 'center',
|
|
48
51
|
}}
|
|
49
52
|
>
|
|
50
|
-
<h3 style={{
|
|
53
|
+
<h3 style={{ color: 'white', textAlign: 'center' }}>Color Palette Conversion</h3>
|
|
51
54
|
</div>
|
|
52
55
|
|
|
53
56
|
<div className='modal-body' style={{ padding: '20px' }}>
|
|
@@ -84,4 +87,4 @@ const PaletteConversionModal: React.FC<PaletteConversionModalProps> = ({
|
|
|
84
87
|
)
|
|
85
88
|
}
|
|
86
89
|
|
|
87
|
-
export default PaletteConversionModal
|
|
90
|
+
export default PaletteConversionModal
|
|
@@ -2,9 +2,28 @@
|
|
|
2
2
|
/* Shared styles for palette color swatches across all visualization types */
|
|
3
3
|
|
|
4
4
|
.color-palette {
|
|
5
|
-
|
|
5
|
+
display: flex;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
+
/* List item-based palette selector (used by maps) */
|
|
9
|
+
.color-palette li {
|
|
10
|
+
width: 1.5em;
|
|
11
|
+
height: 1.5em;
|
|
12
|
+
display: inline-block;
|
|
13
|
+
margin-right: 0.5em;
|
|
14
|
+
cursor: pointer;
|
|
15
|
+
border: rgba(0, 0, 0, 0.3) 3px solid;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.color-palette li.active {
|
|
19
|
+
border: rgba(0, 0, 0, 0.8) 3px solid;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.color-palette li.selected {
|
|
23
|
+
border: black 2px solid;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/* Button-based palette selector (used by charts) */
|
|
8
27
|
.color-palette button:not(.selected) {
|
|
9
28
|
border: var(--cool-gray-30) 2px solid !important;
|
|
10
29
|
}
|
|
@@ -13,13 +32,37 @@
|
|
|
13
32
|
border: black 2px solid !important;
|
|
14
33
|
}
|
|
15
34
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
border:
|
|
35
|
+
.color-palette a {
|
|
36
|
+
display: inline-block;
|
|
37
|
+
border-bottom: 1px solid rgba(0, 0, 0, 0.8);
|
|
19
38
|
}
|
|
20
39
|
|
|
21
|
-
|
|
22
|
-
|
|
40
|
+
/* Series list variant */
|
|
41
|
+
.color-palette.series-list {
|
|
42
|
+
flex-direction: column;
|
|
43
|
+
padding: 0;
|
|
44
|
+
border: none;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.color-palette.series-list li {
|
|
48
|
+
padding: 0.3em 0.5em;
|
|
49
|
+
display: flex;
|
|
50
|
+
align-items: center;
|
|
51
|
+
justify-content: space-between;
|
|
52
|
+
width: auto;
|
|
53
|
+
height: auto;
|
|
54
|
+
border: 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.color-palette.series-list li:not(:last-child) {
|
|
58
|
+
border-bottom: rgba(0, 0, 0, 0.2) 1px solid;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* Header variant */
|
|
62
|
+
.header .color-palette li {
|
|
63
|
+
width: 1.5em;
|
|
64
|
+
height: 1.5em;
|
|
65
|
+
display: inline-block;
|
|
23
66
|
}
|
|
24
67
|
|
|
25
68
|
/* Developer rollback component styles */
|
|
@@ -1,7 +1,28 @@
|
|
|
1
1
|
const Cell = ({ children, style, isBold = false, ariaLabel }) => {
|
|
2
|
+
// Use whiteSpace from style prop, defaulting to 'pre-line' for backwards compatibility
|
|
3
|
+
const whiteSpace = style?.whiteSpace || 'pre-line'
|
|
4
|
+
|
|
5
|
+
const contentWrapperStyle = {
|
|
6
|
+
whiteSpace: whiteSpace as any,
|
|
7
|
+
lineHeight: '1.4',
|
|
8
|
+
display: 'block' as const,
|
|
9
|
+
margin: 0,
|
|
10
|
+
padding: 0,
|
|
11
|
+
wordBreak: 'break-word' as const
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Only include aria-label if it has a value
|
|
15
|
+
const ariaProps = ariaLabel ? { 'aria-label': ariaLabel } : {}
|
|
16
|
+
|
|
17
|
+
// Keep whiteSpace on td style so it can be detected by tests and for proper rendering
|
|
18
|
+
const tdStyle = { ...style }
|
|
19
|
+
delete tdStyle.textOverflow
|
|
20
|
+
|
|
2
21
|
return (
|
|
3
|
-
<td
|
|
4
|
-
|
|
22
|
+
<td {...ariaProps} role='gridcell' style={tdStyle}>
|
|
23
|
+
<div style={contentWrapperStyle}>
|
|
24
|
+
{isBold ? <strong>{children}</strong> : children}
|
|
25
|
+
</div>
|
|
5
26
|
</td>
|
|
6
27
|
)
|
|
7
28
|
}
|
|
@@ -16,8 +16,7 @@ type RowProps = {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
const Row: FC<RowProps> = props => {
|
|
19
|
-
const { childRow, rowKey,
|
|
20
|
-
const whiteSpace = wrapColumns ? 'unset' : 'nowrap'
|
|
19
|
+
const { childRow, rowKey, cellMinWidth = 0, isTotal, preliminaryData, rightAlignedCols, wrapColumns } = props
|
|
21
20
|
const minWidth = cellMinWidth + 'px'
|
|
22
21
|
const isHtmlString = (str: any): str is string => typeof str === 'string' && /<\/?[a-z][\s\S]*>/i.test(str)
|
|
23
22
|
const isReactNode = (val: any): boolean => React.isValidElement(val) || typeof val === 'object'
|
|
@@ -32,6 +31,9 @@ const Row: FC<RowProps> = props => {
|
|
|
32
31
|
{}
|
|
33
32
|
|
|
34
33
|
const textAlign = rightAlignedCols && rightAlignedCols[i] ? 'right' : ''
|
|
34
|
+
// Set whiteSpace based on wrapColumns prop (default to wrapping for backwards compatibility)
|
|
35
|
+
const whiteSpace = wrapColumns === false ? 'nowrap' : 'normal'
|
|
36
|
+
|
|
35
37
|
// handle Parsing
|
|
36
38
|
let content: ReactNode
|
|
37
39
|
if (isHtmlString(child)) {
|
|
@@ -46,7 +48,7 @@ const Row: FC<RowProps> = props => {
|
|
|
46
48
|
<Cell
|
|
47
49
|
ariaLabel={style?.color ? 'suppressed data' : ''}
|
|
48
50
|
key={rowKey + '__' + i}
|
|
49
|
-
style={{
|
|
51
|
+
style={{ minWidth, textAlign, whiteSpace, ...style }}
|
|
50
52
|
isBold={isTotal}
|
|
51
53
|
>
|
|
52
54
|
{content}
|
|
@@ -8,7 +8,14 @@ import { Visualization } from '../../types/Visualization'
|
|
|
8
8
|
|
|
9
9
|
const meta: Meta<typeof Filters> = {
|
|
10
10
|
title: 'Components/Molecules/Visualization Filters',
|
|
11
|
-
component: Filters
|
|
11
|
+
component: Filters,
|
|
12
|
+
decorators: [
|
|
13
|
+
Story => (
|
|
14
|
+
<div className='cdc-open-viz-module'>
|
|
15
|
+
<Story />
|
|
16
|
+
</div>
|
|
17
|
+
)
|
|
18
|
+
]
|
|
12
19
|
}
|
|
13
20
|
|
|
14
21
|
type Story = StoryObj<typeof Filters>
|
|
@@ -54,4 +61,16 @@ export const Tab: Story = generateConfig('tab')
|
|
|
54
61
|
|
|
55
62
|
export const TabBar: Story = generateConfig('tab bar')
|
|
56
63
|
|
|
64
|
+
export const WithApplyButton: Story = {
|
|
65
|
+
args: {
|
|
66
|
+
config: {
|
|
67
|
+
filters: generateFilters('dropdown'),
|
|
68
|
+
data: animalData,
|
|
69
|
+
filterBehavior: 'Apply Button',
|
|
70
|
+
type: 'chart'
|
|
71
|
+
} as any,
|
|
72
|
+
setFilters: () => {}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
57
76
|
export default meta
|