@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.
Files changed (73) hide show
  1. package/_stories/StoryRenderingTests.stories.tsx +164 -0
  2. package/components/AdvancedEditor/AdvancedEditor.tsx +3 -1
  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/DataTableStandAlone.tsx +8 -3
  7. package/components/DataTable/components/DataTableEditorPanel.tsx +12 -2
  8. package/components/DataTable/data-table.css +6 -0
  9. package/components/DataTable/helpers/mapCellMatrix.tsx +14 -3
  10. package/components/DataTable/helpers/standardizeState.js +2 -2
  11. package/components/DataTable/helpers/tests/standardizeState.test.js +54 -0
  12. package/components/EditorPanel/DataTableEditor.tsx +3 -3
  13. package/components/EditorPanel/EditorPanel.styles.css +423 -0
  14. package/components/EditorPanel/FootnotesEditor.tsx +44 -37
  15. package/components/EditorPanel/Inputs.tsx +12 -2
  16. package/components/EditorPanel/VizFilterEditor/NestedDropdownEditor.tsx +35 -62
  17. package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +12 -2
  18. package/components/EditorPanel/components/MarkupVariablesEditor.tsx +61 -22
  19. package/components/Filters/Filters.tsx +26 -5
  20. package/components/Filters/components/Dropdown.tsx +6 -1
  21. package/components/Footnotes/Footnotes.tsx +35 -25
  22. package/components/Footnotes/FootnotesStandAlone.tsx +42 -6
  23. package/components/HeaderThemeSelector/HeaderThemeSelector.css +43 -0
  24. package/components/HeaderThemeSelector/HeaderThemeSelector.stories.tsx +74 -0
  25. package/components/HeaderThemeSelector/HeaderThemeSelector.tsx +61 -0
  26. package/components/HeaderThemeSelector/index.ts +2 -0
  27. package/components/Layout/styles/editor.scss +2 -1
  28. package/components/Loader/Loader.tsx +1 -1
  29. package/components/MediaControls.tsx +21 -18
  30. package/components/PaletteConversionModal.tsx +7 -4
  31. package/components/PaletteSelector/PaletteSelector.css +49 -6
  32. package/components/Table/components/Cell.tsx +23 -2
  33. package/components/Table/components/Row.tsx +5 -3
  34. package/components/_stories/Filters.stories.tsx +20 -1
  35. package/components/_stories/Footnotes.CSV.stories.tsx +247 -0
  36. package/components/_stories/Footnotes.stories.tsx +768 -3
  37. package/components/_stories/Inputs.stories.tsx +2 -2
  38. package/components/_stories/styles.scss +0 -1
  39. package/components/ui/Accordion.jsx +1 -1
  40. package/components/ui/accordion.styles.css +57 -0
  41. package/data/chartColorPalettes.ts +1 -1
  42. package/dist/cove-main.css +49 -3
  43. package/dist/cove-main.css.map +1 -1
  44. package/helpers/addValuesToFilters.ts +5 -0
  45. package/helpers/constants.ts +37 -0
  46. package/helpers/cove/number.ts +33 -12
  47. package/helpers/coveUpdateWorker.ts +3 -1
  48. package/helpers/fetchRemoteData.ts +3 -15
  49. package/helpers/markupProcessor.ts +27 -12
  50. package/helpers/mergeCustomOrderValues.ts +37 -0
  51. package/helpers/parseCsvWithQuotes.ts +65 -0
  52. package/helpers/testing.ts +17 -4
  53. package/helpers/ver/4.25.11.ts +13 -0
  54. package/helpers/viewports.ts +2 -0
  55. package/package.json +4 -3
  56. package/styles/_common-components.css +73 -0
  57. package/styles/_global.scss +25 -5
  58. package/styles/base.scss +0 -50
  59. package/styles/cove-main.scss +3 -1
  60. package/styles/filters.scss +10 -3
  61. package/styles/v2/base/index.scss +0 -1
  62. package/styles/v2/components/editor.scss +14 -6
  63. package/styles/v2/utils/_breakpoints.scss +1 -1
  64. package/styles/v2/utils/index.scss +0 -1
  65. package/styles/waiting.scss +1 -1
  66. package/types/MarkupInclude.ts +4 -3
  67. package/types/MarkupVariable.ts +1 -1
  68. package/types/VizFilter.ts +1 -0
  69. package/styles/_mixins.scss +0 -13
  70. package/styles/_typography.scss +0 -0
  71. package/styles/v2/base/_typography.scss +0 -0
  72. package/styles/v2/components/guidance-block.scss +0 -74
  73. 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 => ({ symbol: row[symbolColumn], text: row[textColumn] }))
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 = config.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
@@ -0,0 +1,2 @@
1
+ export { default as HeaderThemeSelector } from './HeaderThemeSelector'
2
+ export { default } from './HeaderThemeSelector'
@@ -1,5 +1,6 @@
1
+ @import '../../../styles/v2/utils/variables';
2
+
1
3
  $editorAnimationTimer: 400ms;
2
- $editorWidth: 350px;
3
4
  $mediumGray: #e6e6e6;
4
5
 
5
6
  @import 'editor-grid-view.scss';
@@ -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() + '-' + date.getDate() + date.getMonth() + date.getFullYear()
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
- // On screenshots without a title (like some charts), add padding around the chart svg
90
- if (!state.showTitle) {
91
- container.style.padding = '35px'
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
- container.appendChild(baseSvg.cloneNode(true)) // Clone baseSvg to avoid modifying the original
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: '20px 20px 0 20px',
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={{ margin: '0 0 20px 0' }}>Color Palette Conversion</h3>
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
- /* Button-based palette selector (used by charts) */
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
- /* List item-based palette selector (used by maps) */
17
- .color-palette li {
18
- border: var(--cool-gray-30) 2px solid;
35
+ .color-palette a {
36
+ display: inline-block;
37
+ border-bottom: 1px solid rgba(0, 0, 0, 0.8);
19
38
  }
20
39
 
21
- .color-palette li.selected {
22
- border: black 2px solid;
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 aria-label={ariaLabel} tabIndex={0} role='gridcell' style={style}>
4
- {isBold ? <strong>{children}</strong> : children}
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, wrapColumns, cellMinWidth = 0, isTotal, preliminaryData, rightAlignedCols } = props
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={{ whiteSpace, minWidth, textAlign, textOverflow: 'ellipsis', ...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