@cdc/core 4.23.11 → 4.24.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/components/DataTable/DataTable.tsx +27 -9
  2. package/components/DataTable/components/ChartHeader.tsx +17 -5
  3. package/components/DataTable/components/ExpandCollapse.tsx +1 -1
  4. package/components/DataTable/helpers/chartCellMatrix.tsx +16 -2
  5. package/components/DataTable/helpers/customColumns.ts +25 -0
  6. package/components/DataTable/helpers/getChartCellValue.ts +1 -0
  7. package/components/DataTable/helpers/getDataSeriesColumns.ts +1 -0
  8. package/components/DataTable/types/TableConfig.ts +5 -10
  9. package/components/EditorPanel/DataTableEditor.tsx +133 -0
  10. package/components/EditorPanel/Inputs.tsx +150 -0
  11. package/components/Filters.jsx +3 -3
  12. package/components/MediaControls.jsx +1 -1
  13. package/components/MultiSelect/MultiSelect.tsx +95 -0
  14. package/components/MultiSelect/index.ts +1 -0
  15. package/components/MultiSelect/multiselect.styles.css +50 -0
  16. package/components/Table/Table.tsx +23 -3
  17. package/components/Table/components/Cell.tsx +3 -3
  18. package/components/Table/components/GroupRow.tsx +6 -2
  19. package/components/Table/components/Row.tsx +9 -2
  20. package/components/Table/types/RowType.ts +5 -0
  21. package/components/_stories/DataTable.stories.tsx +41 -0
  22. package/components/_stories/EditorPanel.stories.tsx +53 -0
  23. package/components/_stories/Inputs.stories.tsx +37 -0
  24. package/components/_stories/MultiSelect.stories.tsx +24 -0
  25. package/components/_stories/_mocks/row_type.json +42 -0
  26. package/components/inputs/{InputSelect.jsx → InputSelect.tsx} +15 -5
  27. package/components/managers/DataDesigner.tsx +8 -8
  28. package/components/ui/{Icon.jsx → Icon.tsx} +3 -3
  29. package/helpers/DataTransform.ts +30 -2
  30. package/helpers/getFileExtension.ts +28 -5
  31. package/package.json +2 -2
  32. package/styles/_data-table.scss +2 -0
  33. package/types/Axis.ts +37 -2
  34. package/types/Column.ts +15 -0
  35. package/types/FilterBehavior.ts +1 -0
  36. package/types/Runtime.ts +21 -1
  37. package/types/Series.ts +1 -1
  38. package/types/Table.ts +18 -0
  39. package/types/UpdateFieldFunc.ts +1 -0
  40. package/types/Visualization.ts +9 -9
@@ -0,0 +1,95 @@
1
+ import React, { useEffect, useRef, useState } from 'react'
2
+ import Icon from '../ui/Icon'
3
+
4
+ import './multiselect.styles.css'
5
+ import { UpdateFieldFunc } from '../../types/UpdateFieldFunc'
6
+
7
+ interface Option {
8
+ value: string
9
+ label: string
10
+ }
11
+
12
+ interface MultiSelectProps {
13
+ section?: string
14
+ subsection?: string
15
+ fieldName: string
16
+ options: Option[]
17
+ updateField: UpdateFieldFunc<string[]>
18
+ label?: string
19
+ }
20
+
21
+ const MultiSelect: React.FC<MultiSelectProps> = ({ section = null, subsection = null, fieldName, label, options, updateField }) => {
22
+ const [selectedItems, setSelectedItems] = useState<Option[]>([])
23
+ const [expanded, setExpanded] = useState(false)
24
+ const multiSelectRef = useRef(null)
25
+
26
+ useEffect(() => {
27
+ const handleClickOutside = event => {
28
+ if (multiSelectRef.current && !multiSelectRef.current.contains(event.target)) {
29
+ setExpanded(false)
30
+ }
31
+ }
32
+
33
+ document.addEventListener('mousedown', handleClickOutside)
34
+
35
+ return () => {
36
+ document.removeEventListener('mousedown', handleClickOutside)
37
+ }
38
+ }, [])
39
+
40
+ const update = newItems =>
41
+ updateField(
42
+ section,
43
+ subsection,
44
+ fieldName,
45
+ newItems.map(item => item.value)
46
+ )
47
+
48
+ const handleItemSelect = (option: Option) => {
49
+ const newItems = [...selectedItems, option]
50
+ setSelectedItems(newItems)
51
+ update(newItems)
52
+ }
53
+
54
+ const handleItemRemove = (option: Option, caller: string) => {
55
+ const newItems = selectedItems.filter(item => item.value !== option.value)
56
+ setSelectedItems(newItems)
57
+ update(newItems)
58
+ }
59
+
60
+ const multiID = 'multiSelect_' + label
61
+ return (
62
+ <div ref={multiSelectRef} className='cove-multiselect'>
63
+ {label && (
64
+ <span id={multiID} className='edit-label cove-input__label'>
65
+ {label}
66
+ </span>
67
+ )}
68
+
69
+ <div aria-labelledby={label ? multiID : undefined} className='selected'>
70
+ {selectedItems.map(item => (
71
+ <div key={item.value} role='button' tabIndex={0} onClick={() => handleItemRemove(item, 'button click')} onKeyUp={() => handleItemRemove(item, 'button key up')}>
72
+ {item.label}
73
+ <button aria-label='Remove' onClick={() => handleItemRemove(item, 'X')}>
74
+ x
75
+ </button>
76
+ </div>
77
+ ))}
78
+ <button aria-label={expanded ? 'Collapse' : 'Expand'} className='expand' onClick={() => setExpanded(!expanded)}>
79
+ <Icon display={expanded ? 'caretDown' : 'caretUp'} style={{ cursor: 'pointer' }} />
80
+ </button>
81
+ </div>
82
+ <ul className={'dropdown' + (expanded ? '' : ' hide')}>
83
+ {options
84
+ .filter(option => !selectedItems.find(item => item.value === option.value))
85
+ .map(option => (
86
+ <li className='cove-multiselect-li' key={option.value} role='option' tabIndex={0} onClick={() => handleItemSelect(option)} onKeyUp={() => handleItemSelect(option)}>
87
+ {option.label}
88
+ </li>
89
+ ))}
90
+ </ul>
91
+ </div>
92
+ )
93
+ }
94
+
95
+ export default MultiSelect
@@ -0,0 +1 @@
1
+ export { default } from './MultiSelect'
@@ -0,0 +1,50 @@
1
+ .cove-multiselect {
2
+ position: relative;
3
+ .selected {
4
+ border: 1px solid #ccc;
5
+ padding: 5px;
6
+ min-height: 40px;
7
+ :is(button) {
8
+ border: none;
9
+ background: none;
10
+ }
11
+ :is(div) {
12
+ display: inline-block;
13
+ padding: 0 0 0 5px;
14
+ margin-right: 5px;
15
+ margin-bottom: 2px;
16
+ background: #ccc;
17
+ border-radius: 5px;
18
+ }
19
+ .expand {
20
+ padding: 0 5px;
21
+ border-radius: 5px;
22
+ background: #ccc;
23
+ float: right;
24
+ }
25
+ border-radius: 5px;
26
+ }
27
+ .dropdown {
28
+ background: white;
29
+ position: absolute;
30
+ margin-top: 5px;
31
+ border: 1px solid #ccc;
32
+ padding: 0;
33
+ min-height: 40px;
34
+ overflow: scroll;
35
+ max-height: 200px;
36
+ z-index: 1;
37
+ &.hide {
38
+ display: none;
39
+ }
40
+
41
+ :is(li) {
42
+ cursor: pointer;
43
+ list-style: none;
44
+ padding-left: 10px;
45
+ &:hover {
46
+ background: #ccc;
47
+ }
48
+ }
49
+ }
50
+ }
@@ -2,6 +2,7 @@ import { ReactNode } from 'react'
2
2
  import Row from './components/Row'
3
3
  import GroupRow from './components/GroupRow'
4
4
  import { CellMatrix, GroupCellMatrix } from './types/CellMatrix'
5
+ import { RowType } from './types/RowType'
5
6
 
6
7
  type TableProps = {
7
8
  childrenMatrix: CellMatrix | GroupCellMatrix
@@ -14,12 +15,15 @@ type TableProps = {
14
15
  'aria-live'?: 'off' | 'assertive' | 'polite'
15
16
  hidden?: boolean
16
17
  'aria-rowcount'?: number
18
+ cellMinWidth?: number
17
19
  }
20
+ wrapColumns?: boolean
21
+ hasRowType?: boolean // if it has row type then the first column is the row type which will explain how to render the row
18
22
  }
19
23
 
20
24
  type Position = 'sticky'
21
25
 
22
- const Table = ({ childrenMatrix, tableName, caption, stickyHeader, headContent, tableOptions }: TableProps) => {
26
+ const Table = ({ childrenMatrix, tableName, caption, stickyHeader, headContent, tableOptions, wrapColumns, hasRowType }: TableProps) => {
23
27
  const headStyle = stickyHeader ? { position: 'sticky' as Position, top: 0, zIndex: 999 } : {}
24
28
  const isGroupedMatrix = !Array.isArray(childrenMatrix)
25
29
  return (
@@ -33,13 +37,29 @@ const Table = ({ childrenMatrix, tableName, caption, stickyHeader, headContent,
33
37
  const rows = childrenMatrix[groupName].map((row, i) => {
34
38
  colSpan = row.length
35
39
  const key = `${tableName}-${groupName}-row-${i}`
36
- return <Row key={key} rowKey={key} childRow={row} />
40
+ return <Row key={key} rowKey={key} childRow={row} wrapColumns={wrapColumns} cellMinWidth={tableOptions.cellMinWidth} />
37
41
  })
38
42
  return [<GroupRow label={groupName} colSpan={colSpan} key={`${tableName}-${groupName}`} />, ...rows]
39
43
  })
40
44
  : childrenMatrix.map((childRow, i) => {
45
+ let childRowCopy = [...childRow]
46
+ let rowType = undefined
47
+ if (hasRowType) rowType = childRowCopy.shift()
41
48
  const key = `${tableName}-row-${i}`
42
- return <Row key={key} rowKey={key} childRow={childRow} />
49
+ if (rowType === undefined) {
50
+ return <Row key={key} rowKey={key} childRow={childRow} wrapColumns={wrapColumns} cellMinWidth={tableOptions.cellMinWidth} />
51
+ } else {
52
+ switch (rowType) {
53
+ case RowType.row_group:
54
+ return <GroupRow label={childRowCopy[0]} colSpan={childRowCopy.length} key={key} />
55
+ case RowType.total:
56
+ return <Row key={key} rowKey={key} childRow={childRowCopy} isTotal={true} wrapColumns={wrapColumns} cellMinWidth={tableOptions.cellMinWidth} />
57
+ case RowType.row_group_total:
58
+ return <GroupRow label={childRowCopy[0]} colSpan={1} key={key} data={childRowCopy.slice(1)} />
59
+ default:
60
+ return <Row key={key} rowKey={key} childRow={childRowCopy} wrapColumns={wrapColumns} cellMinWidth={tableOptions.cellMinWidth} />
61
+ }
62
+ }
43
63
  })}
44
64
  </tbody>
45
65
  </table>
@@ -1,7 +1,7 @@
1
- const Cell = ({ children }) => {
1
+ const Cell = ({ children, style, isBold = false }) => {
2
2
  return (
3
- <td tabIndex={0} role='gridcell'>
4
- {children}
3
+ <td tabIndex={0} role='gridcell' style={style}>
4
+ {isBold ? <strong>{children}</strong> : children}
5
5
  </td>
6
6
  )
7
7
  }
@@ -1,14 +1,18 @@
1
+ import { ReactNode } from 'react'
2
+
1
3
  type GroupRowProps = {
2
- label: string
4
+ label: ReactNode
3
5
  colSpan: number
6
+ data?: ReactNode[]
4
7
  }
5
8
 
6
- const GroupRow = ({ label, colSpan }: GroupRowProps) => {
9
+ const GroupRow = ({ label, colSpan, data }: GroupRowProps) => {
7
10
  return (
8
11
  <tr>
9
12
  <th scope='colgroup' colSpan={colSpan}>
10
13
  {label}
11
14
  </th>
15
+ {data && data.map((item, i) => <th key={`${label}-${i}`}>{item}</th>)}
12
16
  </tr>
13
17
  )
14
18
  }
@@ -4,13 +4,20 @@ import Cell from './Cell'
4
4
  type RowProps = {
5
5
  childRow: ReactNode[]
6
6
  rowKey: string
7
+ wrapColumns: boolean
8
+ isTotal?: boolean
9
+ cellMinWidth?: number
7
10
  }
8
11
 
9
- const Row = ({ childRow, rowKey }: RowProps) => {
12
+ const Row = ({ childRow, rowKey, wrapColumns, cellMinWidth = 0, isTotal }: RowProps) => {
13
+ const whiteSpace = wrapColumns ? 'unset' : 'nowrap'
14
+ const minWidth = cellMinWidth + 'px'
10
15
  return (
11
16
  <tr>
12
17
  {childRow.map((child, i) => (
13
- <Cell key={rowKey + '__' + i}>{child}</Cell>
18
+ <Cell key={rowKey + '__' + i} style={{ whiteSpace, minWidth }} isBold={isTotal}>
19
+ {child}
20
+ </Cell>
14
21
  ))}
15
22
  </tr>
16
23
  )
@@ -0,0 +1,5 @@
1
+ export enum RowType {
2
+ row_group = 'row_group',
3
+ total = 'total',
4
+ row_group_total = 'row_group_total'
5
+ }
@@ -5,6 +5,8 @@ import './styles.scss'
5
5
  import Example_1 from './_mocks/dashboard_no_filter.json'
6
6
  import CityStateExample from './_mocks/example-city-state.json'
7
7
  import { displayGeoName } from '@cdc/map/src/helpers/displayGeoName'
8
+ import rowTypeData from './_mocks/row_type.json'
9
+ import { TableConfig } from '../DataTable/types/TableConfig'
8
10
 
9
11
  const meta: Meta<typeof DataTable> = {
10
12
  title: 'Components/Organisms/DataTable',
@@ -60,3 +62,42 @@ export const Grouped: Story = {
60
62
  tabbingId: datasetKey
61
63
  }
62
64
  }
65
+
66
+ export const RowType: Story = {
67
+ args: {
68
+ config: {
69
+ dashboard: {
70
+ theme: 'theme-blue',
71
+ title: 'RowType'
72
+ },
73
+ title: 'RowType',
74
+ dataUrl: '/examples/feature/__data__/ardi.json',
75
+ animate: false,
76
+ animateReplay: true,
77
+ palette: 'qualitative-soft',
78
+ aspectRatio: 1,
79
+ dataFormat: {
80
+ roundTo: 1,
81
+ commas: false,
82
+ prefix: '',
83
+ suffix: ''
84
+ },
85
+ legend: {
86
+ hide: false
87
+ },
88
+ table: {
89
+ label: 'Data Table',
90
+ expanded: true,
91
+ show: true,
92
+ customTableConfig: true
93
+ }
94
+ } as unknown as TableConfig,
95
+ dataConfig: { data: rowTypeData },
96
+ rawData: rowTypeData,
97
+ runtimeData: rowTypeData,
98
+ expandDataTable: true,
99
+ tableTitle: 'DataTable',
100
+ viewport: 'lg',
101
+ tabbingId: '#asdf'
102
+ }
103
+ }
@@ -0,0 +1,53 @@
1
+ import { Meta, StoryObj } from '@storybook/react'
2
+
3
+ import DataTableEditor from '../EditorPanel/DataTableEditor'
4
+ import { Accordion, AccordionItem, AccordionItemButton, AccordionItemHeading, AccordionItemPanel } from 'react-accessible-accordion'
5
+ import { useState } from 'react'
6
+
7
+ const EditorPanel = () => {
8
+ const { config, isDashboard } = Primary.args
9
+ const [_config, setConfig] = useState(config)
10
+ const updateField = (section, subsection, fieldName, value) => {
11
+ setConfig({
12
+ ..._config,
13
+ [section]: {
14
+ ..._config[section],
15
+ [fieldName]: value
16
+ }
17
+ })
18
+ }
19
+ return (
20
+ <Accordion>
21
+ <AccordionItem>
22
+ <AccordionItemHeading>
23
+ <AccordionItemButton>Data Table</AccordionItemButton>
24
+ </AccordionItemHeading>
25
+ <AccordionItemPanel>
26
+ <DataTableEditor config={_config} isDashboard={isDashboard} updateField={updateField} isLoadedFromUrl={false} />
27
+ </AccordionItemPanel>
28
+ </AccordionItem>
29
+ </Accordion>
30
+ )
31
+ }
32
+
33
+ const meta: Meta<typeof DataTableEditor> = {
34
+ title: 'Components/Organisms/EditorPanel',
35
+ component: EditorPanel
36
+ }
37
+
38
+ export default meta
39
+
40
+ type Story = StoryObj<typeof DataTableEditor>
41
+
42
+ export const Primary: Story = {
43
+ args: {
44
+ config: {
45
+ table: {
46
+ label: 'Data Table',
47
+ show: true
48
+ },
49
+ visualizationType: 'Pie'
50
+ },
51
+ isDashboard: true
52
+ }
53
+ }
@@ -0,0 +1,37 @@
1
+ import { Meta, StoryObj } from '@storybook/react'
2
+
3
+ import InputSelect from '../inputs/InputSelect'
4
+ import { useState } from 'react'
5
+
6
+ const Inputs: React.FC = ({ config }: any) => {
7
+ const [_config, setConfig] = useState(config)
8
+ const updateField = (section, subsection, fieldName, value) => {
9
+ setConfig({
10
+ ..._config,
11
+ [section]: {
12
+ ..._config[section],
13
+ [fieldName]: value
14
+ }
15
+ })
16
+ }
17
+ return (
18
+ <div>
19
+ <InputSelect label='Select' options={['apple', 'banana', 'orange']} fieldName='inputselect' updateField={updateField} />
20
+ </div>
21
+ )
22
+ }
23
+
24
+ const meta: Meta<typeof Inputs> = {
25
+ title: 'Components/Atoms/Inputs',
26
+ component: Inputs
27
+ }
28
+
29
+ export default meta
30
+
31
+ type Story = StoryObj<typeof Inputs>
32
+
33
+ export const Select: Story = {
34
+ args: {
35
+ config: {}
36
+ }
37
+ }
@@ -0,0 +1,24 @@
1
+ import { Meta, StoryObj } from '@storybook/react'
2
+
3
+ import MultiSelect from '../MultiSelect'
4
+
5
+ const meta: Meta<typeof MultiSelect> = {
6
+ title: 'Components/Molecules/MultiSelect',
7
+ component: MultiSelect
8
+ }
9
+
10
+ type Story = StoryObj<typeof MultiSelect>
11
+
12
+ export const Primary: Story = {
13
+ args: {
14
+ options: [
15
+ { value: '1', label: 'One' },
16
+ { value: '2', label: 'Two' },
17
+ { value: '3', label: 'Three' }
18
+ ],
19
+ label: 'MultiSelect',
20
+ updateField: (section, subsection, fieldName, value) => {}
21
+ }
22
+ }
23
+
24
+ export default meta
@@ -0,0 +1,42 @@
1
+ [
2
+ {
3
+ "type": "row_group",
4
+ "overall": "",
5
+ "male": "",
6
+ "female": "",
7
+ "row_type": "row_group",
8
+ "nullColumn": null
9
+ },
10
+ {
11
+ "type": "row_group_total",
12
+ "overall": "100",
13
+ "male": "50",
14
+ "female": "50",
15
+ "row_type": "row_group_total",
16
+ "nullColumn": null
17
+ },
18
+ {
19
+ "type": "regular",
20
+ "overall": "50",
21
+ "male": "25",
22
+ "female": "25",
23
+ "row_type": null,
24
+ "nullColumn": null
25
+ },
26
+ {
27
+ "type": "regular",
28
+ "overall": "50",
29
+ "male": "25",
30
+ "female": "25",
31
+ "row_type": null,
32
+ "nullColumn": null
33
+ },
34
+ {
35
+ "type": "total",
36
+ "overall": "100",
37
+ "male": "50",
38
+ "female": "50",
39
+ "row_type": "total",
40
+ "nullColumn": null
41
+ }
42
+ ]
@@ -1,9 +1,19 @@
1
- import React, { memo } from 'react'
2
-
3
1
  import '../../styles/v2/components/input/index.scss'
4
2
 
5
- const InputSelect = memo(({ label, value, options, fieldName, section = null, subsection = null, required = false, updateField, initial: initialValue, ...attributes }) => {
6
- let optionsJsx = ''
3
+ interface InputProps {
4
+ label?
5
+ value?
6
+ options: string[] | { [key: string]: string }
7
+ fieldName
8
+ section?
9
+ subsection?
10
+ required?
11
+ updateField
12
+ initial?
13
+ }
14
+
15
+ const InputSelect = ({ label, value, options, fieldName, section = null, subsection = null, required = false, updateField, initial: initialValue, ...attributes }: InputProps) => {
16
+ let optionsJsx = []
7
17
 
8
18
  if (Array.isArray(options)) {
9
19
  //Handle basic array
@@ -48,6 +58,6 @@ const InputSelect = memo(({ label, value, options, fieldName, section = null, su
48
58
  </select>
49
59
  </label>
50
60
  )
51
- })
61
+ }
52
62
 
53
63
  export default InputSelect
@@ -8,7 +8,7 @@ import '../../styles/v2/components/data-designer.scss'
8
8
 
9
9
  type DataDesignerProps = {
10
10
  configureData?: any // todo: add description here when understood better
11
- updateDescriptionProp?: () => void // used to update data description fields
11
+ updateDescriptionProp?: (vizKey: string, dataKey: string, propName: string, value: any) => void // used to update data description fields
12
12
  visualizationKey?: any // todo: add description here when understood better
13
13
  dataKey?: string // appears to be the data file name, ie valid-data.csv
14
14
  config?: any // can be one of many different types of chart configs that aren't fully established yet
@@ -198,16 +198,16 @@ const DataDesigner = (props: DataDesignerProps) => {
198
198
  </div>
199
199
  <div className='mb-2'>
200
200
  <div className='mb-1'>Which properties in the dataset represent the numeric value? (all remaining properties will be treated as filters)</div>
201
- {configureData.dataDescription.valueKeys && configureData.dataDescription.valueKeys.length > 0 && (
201
+ {configureData.dataDescription.valueKeysTallSupport && configureData.dataDescription.valueKeysTallSupport.length > 0 && (
202
202
  <ul className='value-list'>
203
- {configureData.dataDescription.valueKeys.map((valueKey, index) => (
203
+ {configureData.dataDescription.valueKeysTallSupport.map((valueKey, index) => (
204
204
  <li key={`value-keys-list-${index}`}>
205
205
  {valueKey}
206
206
  <button
207
207
  onClick={() => {
208
- let newValueKeys = configureData.dataDescription.valueKeys
208
+ let newValueKeys = configureData.dataDescription.valueKeysTallSupport
209
209
  newValueKeys.splice(index, 1)
210
- updateDescriptionProp(visualizationKey, dataKey, 'valueKeys', newValueKeys)
210
+ updateDescriptionProp(visualizationKey, dataKey, 'valueKeysTallSupport', newValueKeys)
211
211
  }}
212
212
  >
213
213
  X
@@ -218,14 +218,14 @@ const DataDesigner = (props: DataDesignerProps) => {
218
218
  )}
219
219
  <select
220
220
  onChange={e => {
221
- if (e.target.value && (!configureData.dataDescription.valueKeys || configureData.dataDescription.valueKeys.indexOf(e.target.value) === -1)) {
222
- updateDescriptionProp(visualizationKey, dataKey, 'valueKeys', [...(configureData.dataDescription.valueKeys || []), e.target.value])
221
+ if (e.target.value && (!configureData.dataDescription.valueKeysTallSupport || configureData.dataDescription.valueKeysTallSupport.indexOf(e.target.value) === -1)) {
222
+ updateDescriptionProp(visualizationKey, dataKey, 'valueKeysTallSupport', [...(configureData.dataDescription.valueKeysTallSupport || []), e.target.value])
223
223
  }
224
224
  }}
225
225
  >
226
226
  <option value=''>Choose an option</option>
227
227
  {Object.keys(configureData.data[0])
228
- .filter(value => !configureData.dataDescription.valueKeys || configureData.dataDescription.valueKeys.indexOf(value) === -1)
228
+ .filter(value => !configureData.dataDescription.valueKeysTallSupport || configureData.dataDescription.valueKeysTallSupport.indexOf(value) === -1)
229
229
  .map((value, index) => (
230
230
  <option value={value} key={`value-keys-option-${index}`}>
231
231
  {value}
@@ -69,17 +69,17 @@ const iconHash = {
69
69
 
70
70
  export const ICON_TYPES = Object.keys(iconHash)
71
71
 
72
- const Icon = ({ display = '', base, alt = '', size, color, style, ...attributes }) => {
72
+ const Icon = ({ display = '', base = undefined, alt = '', size = undefined, color = undefined, style = undefined, ...attributes }) => {
73
73
  const IconObj = iconHash[display] || null
74
74
 
75
75
  const filteredAttrs = { ...attributes }
76
76
  delete filteredAttrs.className
77
77
 
78
78
  const styles = {
79
- ...style,
80
79
  color: color ? color : null,
81
80
  width: size ? size + 'px' : null,
82
- cursor: display === 'move' ? 'move' : 'default'
81
+ cursor: display === 'move' ? 'move' : 'default',
82
+ ...style
83
83
  }
84
84
 
85
85
  return (
@@ -128,8 +128,36 @@ export class DataTransform {
128
128
  return standardized
129
129
  }
130
130
  } else if (description.series === true && description.singleRow === false) {
131
- if (description.seriesKey !== undefined && description.xKey !== undefined && (description.valueKey !== undefined || (description.valueKeys !== undefined && description.valueKeys.length > 0))) {
132
- if (description.valueKeys !== undefined) {
131
+ if (description.seriesKey !== undefined && description.xKey !== undefined && (description.valueKey !== undefined || (description.valueKeys !== undefined && description.valueKeys.length > 0) || (description.valueKeysTallSupport !== undefined && description.valueKeysTallSupport.length > 0))) {
132
+ if (description.valueKeysTallSupport !== undefined) {
133
+ let standardizedMapped = {}
134
+ let standardized: string[] = []
135
+
136
+ data.forEach(row => {
137
+ let uniqueKey = row[description.xKey];
138
+ Object.keys(row).forEach(key => {
139
+ if(key !== description.xKey && key !== description.seriesKey && description.valueKeysTallSupport.indexOf(key) === -1 && (!description.ignoredKeys || description.ignoredKeys.indexOf(key) === -1)){
140
+ uniqueKey += "|" + row[key];
141
+ }
142
+ })
143
+
144
+ if(!standardizedMapped[uniqueKey]){
145
+ standardizedMapped[uniqueKey] = {[description.xKey]: row[description.xKey]}
146
+ }
147
+ Object.keys(row).forEach(key => {
148
+ if(key !== description.xKey && key !== description.seriesKey && description.valueKeysTallSupport.indexOf(key) === -1 && (!description.ignoredKeys || description.ignoredKeys.indexOf(key) === -1)){
149
+ standardizedMapped[uniqueKey][key] = row[key];
150
+ }
151
+ })
152
+ description.valueKeysTallSupport.forEach(valueKey => {
153
+ standardizedMapped[uniqueKey][row[description.seriesKey] + '-' + valueKey] = row[valueKey];
154
+ })
155
+ })
156
+
157
+ standardized = Object.keys(standardizedMapped).map(key => standardizedMapped[key]);
158
+
159
+ return standardized
160
+ } else if (description.valueKeys !== undefined) {
133
161
  let standardizedMapped = {}
134
162
  let standardized: string[] = []
135
163
  let valueKeys = description.valueKeys
@@ -1,5 +1,28 @@
1
- export const getFileExtension = (path: string): string => {
2
- const regex = /(?:\.([^.]+))?$/
3
- const outCome: RegExpExecArray | null = regex.exec(path)
4
- return outCome ? outCome[1] : ''
5
- }
1
+ // export const getFileExtensionx = (path: string): string => {
2
+ // const regex = /(?:\.([^.]+))?$/
3
+ // const outCome: RegExpExecArray | null = regex.exec(path)
4
+ // return outCome ? outCome[1] : ''
5
+ // }
6
+
7
+ export const getFileExtension = (url: string): string => {
8
+ const regexForExtension = /(?:\.([^.]+))$/
9
+ const regexForQueryParam = /[?&]wt=(csv|json)(?:&|$)/ // Regular expression for 'wt' query parameter
10
+
11
+ const urlObject = new URL(url, window.location.origin)
12
+ const pathname = urlObject.pathname
13
+ const searchParams = urlObject.search
14
+
15
+ // First, try to get the extension from the pathname
16
+ const pathnameMatch = regexForExtension.exec(pathname)
17
+ if (pathnameMatch && pathnameMatch[1]) {
18
+ return pathnameMatch[1]
19
+ }
20
+
21
+ // Then, try to get it from the query parameter 'wt'
22
+ const queryParamsMatch = regexForQueryParam.exec(searchParams)
23
+ if (queryParamsMatch && queryParamsMatch[1]) {
24
+ return queryParamsMatch[1]
25
+ }
26
+
27
+ return ''
28
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cdc/core",
3
- "version": "4.23.11",
3
+ "version": "4.24.1",
4
4
  "description": "Core components, styles, hooks, and helpers, for the CDC Open Visualization project",
5
5
  "moduleName": "CdcCore",
6
6
  "main": "dist/cdccore",
@@ -30,5 +30,5 @@
30
30
  "react": "^18.2.0",
31
31
  "react-dom": "^18.2.0"
32
32
  },
33
- "gitHead": "ecad213667a3cb96c921eaddba43a31c84caaa08"
33
+ "gitHead": "a352a3f74f4b681191e3244061dbb3621f36eec3"
34
34
  }