@cdc/dashboard 4.26.2 → 4.26.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/LICENSE +201 -0
  2. package/dist/cdcdashboard-vr9HZwRt.es.js +6 -0
  3. package/dist/cdcdashboard.js +53345 -49681
  4. package/examples/custom/css/respiratory.css +1 -1
  5. package/examples/data/data-with-metadata.json +18 -0
  6. package/examples/default.json +7 -36
  7. package/examples/private/inline-markup.json +775 -0
  8. package/examples/private/recent-update.json +1456 -0
  9. package/examples/private/toggle.json +10137 -0
  10. package/package.json +9 -9
  11. package/src/CdcDashboard.tsx +2 -1
  12. package/src/CdcDashboardComponent.tsx +47 -27
  13. package/src/_stories/Dashboard.DataSetup.stories.tsx +6 -1
  14. package/src/_stories/Dashboard.Pages.stories.tsx +22 -0
  15. package/src/_stories/Dashboard.stories.tsx +4406 -7
  16. package/src/_stories/_mock/tab-simple-filter.json +153 -0
  17. package/src/components/DashboardFilters/DashboardFilters.tsx +19 -3
  18. package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +7 -4
  19. package/src/components/DashboardFilters/DashboardFiltersEditor/components/APIModal.tsx +1 -1
  20. package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +1 -2
  21. package/src/components/DashboardFilters/DashboardFiltersEditor/components/NestedDropDownDashboard.tsx +8 -7
  22. package/src/components/DashboardFilters/DashboardFiltersWrapper.tsx +8 -8
  23. package/src/components/DashboardFilters/_stories/DashboardFilters.stories.tsx +1 -1
  24. package/src/components/DashboardFilters/dashboardfilter.styles.css +3 -3
  25. package/src/components/DataDesignerModal.tsx +2 -2
  26. package/src/components/Header/Header.tsx +27 -5
  27. package/src/components/Header/index.scss +1 -1
  28. package/src/components/MultiConfigTabs/multiconfigtabs.styles.css +6 -6
  29. package/src/components/Row.tsx +21 -0
  30. package/src/components/Toggle/toggle-style.css +7 -7
  31. package/src/components/VisualizationRow.tsx +12 -4
  32. package/src/components/VisualizationsPanel/VisualizationsPanel.tsx +1 -54
  33. package/src/components/VisualizationsPanel/visualizations-panel-styles.css +2 -2
  34. package/src/components/Widget/Widget.tsx +2 -2
  35. package/src/components/Widget/widget.styles.css +12 -12
  36. package/src/data/initial-state.js +1 -1
  37. package/src/helpers/addVisualization.ts +71 -0
  38. package/src/helpers/formatConfigBeforeSave.ts +1 -1
  39. package/src/helpers/getVizConfig.ts +13 -3
  40. package/src/helpers/iconHash.tsx +45 -36
  41. package/src/helpers/processDataLegacy.ts +19 -14
  42. package/src/helpers/tests/addVisualization.test.ts +52 -0
  43. package/src/helpers/tests/formatConfigBeforeSave.test.ts +81 -1
  44. package/src/scss/editor-panel.scss +1 -1
  45. package/src/scss/main.scss +164 -39
  46. package/src/store/dashboard.reducer.ts +1 -1
  47. package/src/test/CdcDashboard.test.jsx +2 -2
  48. package/src/test/CdcDashboardComponent.test.tsx +74 -0
  49. package/src/types/FilterStyles.ts +2 -1
  50. package/tests/fixtures/dashboard-config-with-metadata.json +89 -0
  51. package/vite.config.js +2 -2
  52. package/dist/cdcdashboard-Cf9_fbQf.es.js +0 -6
@@ -0,0 +1,153 @@
1
+ {
2
+ "dashboard": {
3
+ "theme": "theme-blue",
4
+ "sharedFilters": [
5
+ {
6
+ "key": "category",
7
+ "showDropdown": true,
8
+ "filterStyle": "tab-simple",
9
+ "values": ["Category A", "Category B", "Category C"],
10
+ "orderedValues": ["Category A", "Category B", "Category C"],
11
+ "type": "datafilter",
12
+ "columnName": "category",
13
+ "tier": 1,
14
+ "order": "cust",
15
+ "defaultValue": "Category A"
16
+ }
17
+ ]
18
+ },
19
+ "rows": [
20
+ {
21
+ "columns": [
22
+ {
23
+ "width": 12,
24
+ "widget": "dashboardFilters1"
25
+ }
26
+ ]
27
+ },
28
+ {
29
+ "columns": [
30
+ {
31
+ "width": 12,
32
+ "widget": "chart1"
33
+ }
34
+ ]
35
+ }
36
+ ],
37
+ "visualizations": {
38
+ "dashboardFilters1": {
39
+ "filters": [],
40
+ "filterBehavior": "Filter Change",
41
+ "newViz": true,
42
+ "uid": "dashboardFilters1",
43
+ "type": "dashboardFilters",
44
+ "sharedFilterIndexes": [0],
45
+ "visualizationType": "dashboardFilters"
46
+ },
47
+ "chart1": {
48
+ "filters": [],
49
+ "filterBehavior": "Filter Change",
50
+ "uid": "chart1",
51
+ "type": "chart",
52
+ "visualizationType": "Bar",
53
+ "title": "Sales by Category",
54
+ "showTitle": true,
55
+ "theme": "theme-blue",
56
+ "animate": false,
57
+ "dataKey": "./tab-simple-data.json",
58
+ "dataDescription": {
59
+ "horizontal": false,
60
+ "series": false
61
+ },
62
+ "xAxis": {
63
+ "dataKey": "product",
64
+ "type": "categorical",
65
+ "hideAxis": false,
66
+ "hideLabel": false,
67
+ "hideTicks": false,
68
+ "size": 75,
69
+ "tickRotation": 0,
70
+ "labelColor": "#1c1d1f",
71
+ "tickLabelColor": "#1c1d1f",
72
+ "tickColor": "#1c1d1f",
73
+ "axisPadding": 200,
74
+ "padding": 5,
75
+ "sortDates": false,
76
+ "anchors": []
77
+ },
78
+ "yAxis": {
79
+ "hideAxis": false,
80
+ "hideLabel": false,
81
+ "hideTicks": false,
82
+ "size": 50,
83
+ "gridLines": false,
84
+ "labelColor": "#1c1d1f",
85
+ "tickLabelColor": "#1c1d1f",
86
+ "tickColor": "#1c1d1f",
87
+ "anchors": [],
88
+ "categories": []
89
+ },
90
+ "series": [
91
+ {
92
+ "dataKey": "sales",
93
+ "type": "Bar",
94
+ "axis": "Left",
95
+ "tooltip": true
96
+ }
97
+ ],
98
+ "orientation": "vertical",
99
+ "general": {
100
+ "showZeroValueData": true,
101
+ "palette": {
102
+ "name": "qualitative_bold",
103
+ "version": "1.0"
104
+ }
105
+ },
106
+ "columns": {},
107
+ "legend": {
108
+ "hide": true
109
+ },
110
+ "table": {
111
+ "label": "Data Table",
112
+ "show": false,
113
+ "sharedFilterColumns": ["category"]
114
+ },
115
+ "heights": {
116
+ "vertical": 300,
117
+ "horizontal": 750
118
+ },
119
+ "visual": {
120
+ "border": true,
121
+ "accent": true,
122
+ "background": true
123
+ },
124
+ "tooltips": {
125
+ "opacity": 90,
126
+ "singleSeries": false
127
+ },
128
+ "barThickness": 0.35
129
+ }
130
+ },
131
+ "datasets": {
132
+ "./tab-simple-data.json": {
133
+ "data": [
134
+ { "category": "Category A", "product": "Widget", "sales": "120" },
135
+ { "category": "Category A", "product": "Gadget", "sales": "85" },
136
+ { "category": "Category A", "product": "Doohickey", "sales": "210" },
137
+ { "category": "Category B", "product": "Widget", "sales": "95" },
138
+ { "category": "Category B", "product": "Gadget", "sales": "150" },
139
+ { "category": "Category B", "product": "Doohickey", "sales": "60" },
140
+ { "category": "Category C", "product": "Widget", "sales": "175" },
141
+ { "category": "Category C", "product": "Gadget", "sales": "110" },
142
+ { "category": "Category C", "product": "Doohickey", "sales": "140" }
143
+ ],
144
+ "dataFileSize": 500,
145
+ "dataFileName": "./tab-simple-data.json",
146
+ "dataFileSourceType": "file",
147
+ "dataFileFormat": "JSON",
148
+ "preview": true
149
+ }
150
+ },
151
+ "type": "dashboard",
152
+ "version": "4.26.1"
153
+ }
@@ -11,6 +11,7 @@ import { MouseEventHandler } from 'react'
11
11
  import Loader from '@cdc/core/components/Loader'
12
12
  import _ from 'lodash'
13
13
  import { DROPDOWN_STYLES } from '@cdc/core/components/Filters/components/Dropdown'
14
+ import Tabs from '@cdc/core/components/Filters/components/Tabs'
14
15
 
15
16
  type DashboardFilterProps = {
16
17
  show: number[]
@@ -63,7 +64,12 @@ const DashboardFilters: React.FC<DashboardFilterProps> = ({
63
64
  const urlFilterType = filter.type === 'urlfilter'
64
65
  const label = stripDuplicateLabelIncrement(filter.key || '')
65
66
 
66
- if (!urlFilterType && !filter.showDropdown && filter.filterStyle !== FILTER_STYLE.nestedDropdown)
67
+ if (
68
+ !urlFilterType &&
69
+ !filter.showDropdown &&
70
+ filter.filterStyle !== FILTER_STYLE.nestedDropdown &&
71
+ filter.filterStyle !== FILTER_STYLE.tabSimple
72
+ )
67
73
  return <React.Fragment key={`${filter.key}-filtersection-${filterIndex}-option`} />
68
74
  const values: JSX.Element[] = []
69
75
 
@@ -125,7 +131,10 @@ const DashboardFilters: React.FC<DashboardFilterProps> = ({
125
131
  )
126
132
  }
127
133
 
128
- const formGroupClass = `form-group me-4 mb-1${loading ? ' loading-filter' : ''}`
134
+ const isTabSimple = filter.filterStyle === FILTER_STYLE.tabSimple
135
+ const formGroupClass = `form-group${isTabSimple ? '' : ' me-4'} mb-1${loading ? ' loading-filter' : ''}${
136
+ isTabSimple ? ' w-100' : ''
137
+ }`
129
138
  return (
130
139
  <div className={formGroupClass} key={`${filter.key}-filtersection-${filterIndex}`}>
131
140
  {label && (
@@ -133,7 +142,14 @@ const DashboardFilters: React.FC<DashboardFilterProps> = ({
133
142
  {label}
134
143
  </label>
135
144
  )}
136
- {filter.filterStyle === FILTER_STYLE.multiSelect ? (
145
+ {filter.filterStyle === FILTER_STYLE.tabSimple ? (
146
+ <Tabs
147
+ filter={filter}
148
+ index={filterIndex}
149
+ changeFilterActive={(index, value) => handleOnChange(index, value)}
150
+ loading={loading}
151
+ />
152
+ ) : filter.filterStyle === FILTER_STYLE.multiSelect ? (
137
153
  <MultiSelect
138
154
  label={label}
139
155
  options={multiValues}
@@ -12,7 +12,7 @@ import Icon from '@cdc/core/components/ui/Icon'
12
12
  import FieldSetWrapper from '@cdc/core/components/EditorPanel/FieldSetWrapper'
13
13
  import FilterEditor from './components/FilterEditor'
14
14
  import { DashboardContext, DashboardDispatchContext } from '../../../DashboardContext'
15
- import _ from 'lodash'
15
+ import cloneDeep from 'lodash/cloneDeep'
16
16
  import { DashboardFilters } from '../../../types/DashboardFilters'
17
17
  import { SharedFilter } from '../../../types/SharedFilter'
18
18
  import { useGlobalContext } from '@cdc/core/components/GlobalContext'
@@ -55,7 +55,7 @@ const DashboardFiltersEditor: React.FC<DashboardFitlersEditorProps> = ({ vizConf
55
55
  const [isNestedDragHovered, setIsNestedDragHovered] = useState(false)
56
56
 
57
57
  const updateFilterProp = (prop: string, index: number, value) => {
58
- const newSharedFilters = _.cloneDeep(sharedFilters)
58
+ const newSharedFilters = cloneDeep(sharedFilters)
59
59
  const {
60
60
  apiEndpoint: oldEndpoint,
61
61
  valueSelector: oldValueSelector,
@@ -102,6 +102,9 @@ const DashboardFiltersEditor: React.FC<DashboardFitlersEditorProps> = ({ vizConf
102
102
  // changing a api filter and want to load the api data into the preview.
103
103
  // automatically dispatches SET_SHARED_FILTERS
104
104
  loadAPIFilters(newSharedFilters, {})
105
+ } else if (prop === 'defaultValue') {
106
+ newSharedFilters[index].active = value
107
+ dispatch({ type: 'SET_SHARED_FILTERS', payload: newSharedFilters })
105
108
  } else {
106
109
  handleSorting(newSharedFilters[index])
107
110
  dispatch({ type: 'SET_SHARED_FILTERS', payload: newSharedFilters })
@@ -109,7 +112,7 @@ const DashboardFiltersEditor: React.FC<DashboardFitlersEditorProps> = ({ vizConf
109
112
  }
110
113
 
111
114
  const toggleNestedQueryParameters = (index, checked: boolean) => {
112
- const newSharedFilters = _.cloneDeep(sharedFilters)
115
+ const newSharedFilters = cloneDeep(sharedFilters)
113
116
  const filter = newSharedFilters[index]
114
117
  const isUrlFilter = filter.type === 'urlfilter'
115
118
  const groupColumnName = isUrlFilter ? filter.apiFilter.valueSelector : filter.columnName
@@ -149,7 +152,7 @@ const DashboardFiltersEditor: React.FC<DashboardFitlersEditorProps> = ({ vizConf
149
152
  }
150
153
 
151
154
  const addNewFilter = () => {
152
- const _sharedFilters = _.cloneDeep(sharedFilters) || []
155
+ const _sharedFilters = cloneDeep(sharedFilters) || []
153
156
  const columnName = 'New Dashboard Filter ' + (_sharedFilters.length + 1)
154
157
  const newFilter = { key: columnName, showDropdown: true, values: [] } as SharedFilter
155
158
  dispatch({ type: 'SET_SHARED_FILTERS', payload: [..._sharedFilters, newFilter] })
@@ -16,7 +16,7 @@ const APIModal: React.FC<APIModalProps> = ({ filter, isNestedDropdown, updateAPI
16
16
  const [APISubGroupValueSelector, setAPISubGroupValueSelector] = useState(filter.apiFilter?.subgroupValueSelector)
17
17
  const [APISubGroupTextSelector, setAPISubGroupTextSelector] = useState(filter.apiFilter?.subgroupTextSelector)
18
18
  return (
19
- <fieldset className='mb-1 px-3 cdc-open-viz-module'>
19
+ <fieldset className='mb-1 px-3 cove-visualization'>
20
20
  <label className='d-block'>
21
21
  <span>API Endpoint: </span>
22
22
  <textarea
@@ -1,4 +1,3 @@
1
- import _ from 'lodash'
2
1
  import { getVizRowColumnLocator } from '../../../../helpers/getVizRowColumnLocator'
3
2
  import { Select, TextField } from '@cdc/core/components/EditorPanel/Inputs'
4
3
  import DataTransform from '@cdc/core/helpers/DataTransform'
@@ -105,7 +104,7 @@ const FilterEditor: React.FC<FilterEditorProps> = ({
105
104
  let _dataSet = config.datasets[dataKey]
106
105
  if (!_dataSet.data && _dataSet.dataUrl) {
107
106
  setDataFiltersLoading(true)
108
- let data = await fetchRemoteData(_dataSet.dataUrl)
107
+ let data = (await fetchRemoteData(_dataSet.dataUrl)).data
109
108
  if (_dataSet.dataDescription && data && data.length > 0) {
110
109
  try {
111
110
  data = transform.autoStandardize(data)
@@ -1,6 +1,7 @@
1
1
  import { DashboardConfig } from '../../../../types/DashboardConfig'
2
2
  import { SharedFilter } from '../../../../types/SharedFilter'
3
- import _ from 'lodash'
3
+ import cloneDeep from 'lodash/cloneDeep'
4
+ import uniq from 'lodash/uniq'
4
5
  import { SubGrouping, OrderBy } from '@cdc/core/types/VizFilter'
5
6
  import { TextField, Select } from '@cdc/core/components/EditorPanel/Inputs'
6
7
  import { handleSorting } from '@cdc/core/components/Filters/helpers/handleSorting'
@@ -72,7 +73,7 @@ const NestedDropDownDashboard: React.FC<NestedDropDownEditorDashboardProps> = ({
72
73
  const order = subGrouping?.order || 'asc'
73
74
 
74
75
  const valuesLookup = filter.values.reduce((acc, groupName) => {
75
- const rawValues: string[] = _.uniq(
76
+ const rawValues: string[] = uniq(
76
77
  config.datasets[selectedOptionDatasetName].data
77
78
  .map(d => {
78
79
  return d[filter.columnName] === groupName ? d[newColumnName] : ''
@@ -104,7 +105,7 @@ const NestedDropDownDashboard: React.FC<NestedDropDownEditorDashboardProps> = ({
104
105
  // Handle group order change (asc/desc/cust)
105
106
  const handleGroupingOrderBy = (order: OrderBy) => {
106
107
  const groupSortObject = {
107
- values: _.cloneDeep(filter.values),
108
+ values: cloneDeep(filter.values),
108
109
  order
109
110
  }
110
111
  const { values: newOrderedValues } = handleSorting(groupSortObject)
@@ -128,7 +129,7 @@ const NestedDropDownDashboard: React.FC<NestedDropDownEditorDashboardProps> = ({
128
129
  const handleGroupingCustomOrder = (sourceIndex: number, destinationIndex: number) => {
129
130
  if (sourceIndex === undefined || destinationIndex === undefined || sourceIndex === destinationIndex) return
130
131
 
131
- const orderedValues = _.cloneDeep(filter.orderedValues || filter.values)
132
+ const orderedValues = cloneDeep(filter.orderedValues || filter.values)
132
133
  const [movedItem] = orderedValues.splice(sourceIndex, 1)
133
134
  orderedValues.splice(destinationIndex, 0, movedItem)
134
135
 
@@ -143,7 +144,7 @@ const NestedDropDownDashboard: React.FC<NestedDropDownEditorDashboardProps> = ({
143
144
  const handleSubGroupingOrderBy = (order: OrderBy) => {
144
145
  const newValuesLookup = Object.keys(subGrouping.valuesLookup).reduce((acc, groupName) => {
145
146
  const subGroup = subGrouping.valuesLookup[groupName]
146
- const { values: sortedValues } = handleSorting({ values: _.cloneDeep(subGroup.values), order })
147
+ const { values: sortedValues } = handleSorting({ values: cloneDeep(subGroup.values), order })
147
148
 
148
149
  acc[groupName] = {
149
150
  values: sortedValues,
@@ -170,11 +171,11 @@ const NestedDropDownDashboard: React.FC<NestedDropDownEditorDashboardProps> = ({
170
171
  ) => {
171
172
  if (sourceIndex === undefined || destinationIndex === undefined || sourceIndex === destinationIndex) return
172
173
 
173
- const updatedGroupOrderedValues = _.cloneDeep(currentOrderedValues)
174
+ const updatedGroupOrderedValues = cloneDeep(currentOrderedValues)
174
175
  const [movedItem] = updatedGroupOrderedValues.splice(sourceIndex, 1)
175
176
  updatedGroupOrderedValues.splice(destinationIndex, 0, movedItem)
176
177
 
177
- const newSubGrouping = _.cloneDeep(subGrouping)
178
+ const newSubGrouping = cloneDeep(subGrouping)
178
179
  newSubGrouping.valuesLookup[groupName].values = updatedGroupOrderedValues
179
180
  newSubGrouping.valuesLookup[groupName].orderedValues = updatedGroupOrderedValues
180
181
  newSubGrouping.order = 'cust'
@@ -7,7 +7,7 @@ import { FilterBehavior } from '../../helpers/FilterBehavior'
7
7
  import { getFilteredData } from '../../helpers/getFilteredData'
8
8
  import { DashboardFilters } from '../../types/DashboardFilters'
9
9
  import { getQueryParams, updateQueryString } from '@cdc/core/helpers/queryStringUtils'
10
- import Layout from '@cdc/core/components/Layout'
10
+ import { VisualizationWrapper, Sidebar, Responsive } from '@cdc/core/components/Layout'
11
11
  import DashboardFiltersEditor from './DashboardFiltersEditor'
12
12
  import { ViewPort } from '@cdc/core/types/ViewPort'
13
13
  import { hasDashboardApplyBehavior } from '../../helpers/hasDashboardApplyBehavior'
@@ -302,24 +302,24 @@ const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
302
302
  const displayNone = filters?.length ? filters.every(filter => filter.showDropdown === false) : false
303
303
  if (displayNone && !isEditor) return <></>
304
304
  return (
305
- <Layout.VisualizationWrapper config={visualizationConfig} isEditor={isEditor} currentViewport={currentViewport}>
305
+ <VisualizationWrapper config={visualizationConfig} isEditor={isEditor} currentViewport={currentViewport}>
306
306
  {isEditor && (
307
- <Layout.Sidebar
307
+ <Sidebar
308
308
  displayPanel={displayPanel}
309
309
  isDashboard={true}
310
310
  title={'Configure Dashboard Filters'}
311
311
  onBackClick={onBackClick}
312
312
  >
313
313
  <DashboardFiltersEditor updateConfig={updateConfig} vizConfig={visualizationConfig} />
314
- </Layout.Sidebar>
314
+ </Sidebar>
315
315
  )}
316
316
 
317
317
  {!displayNone && (
318
- <Layout.Responsive isEditor={isEditor}>
318
+ <Responsive isEditor={isEditor}>
319
319
  <div
320
320
  className={`${
321
321
  isEditor ? ' is-editor' : ''
322
- } cove-component__content col-12 cove-dashboard-filters-container`}
322
+ } cove-visualization__inner cove-visualization__body col-12 cove-dashboard-filters-container`}
323
323
  >
324
324
  <Filters
325
325
  show={visualizationConfig?.sharedFilterIndexes?.map(Number)}
@@ -337,9 +337,9 @@ const DashboardFiltersWrapper: React.FC<DashboardFiltersProps> = ({
337
337
  }
338
338
  />
339
339
  </div>
340
- </Layout.Responsive>
340
+ </Responsive>
341
341
  )}
342
- </Layout.VisualizationWrapper>
342
+ </VisualizationWrapper>
343
343
  )
344
344
  }
345
345
 
@@ -7,7 +7,7 @@ const meta: Meta<typeof DashboardFilters> = {
7
7
  component: DashboardFilters,
8
8
  decorators: [
9
9
  Story => (
10
- <div className='cdc-open-viz-module type-dashboard'>
10
+ <div className='cove-visualization type-dashboard'>
11
11
  <Story />
12
12
  </div>
13
13
  )
@@ -4,18 +4,18 @@
4
4
  font-weight: 700;
5
5
  }
6
6
  .btn {
7
+ align-self: flex-end;
7
8
  /* this is the height that is defined for the .form-control class in _forms.scss in bootstrap. */
8
9
  height: calc(1.5em + 0.75rem + 2px);
9
- align-self: flex-end;
10
10
  }
11
11
  .loading-filter {
12
12
  position: relative;
13
13
  .spinner-border {
14
+ height: 1.5rem;
14
15
  position: absolute;
15
- top: 55%;
16
16
  right: 10%;
17
+ top: 55%;
17
18
  width: 1.5rem;
18
- height: 1.5rem;
19
19
  }
20
20
  }
21
21
  :is(select):disabled {
@@ -59,8 +59,8 @@ export const DataDesignerModal: React.FC<DataDesignerModalProps> = ({ vizKey, ro
59
59
  if (dataSetChanged || noCachedData) {
60
60
  setLoadingAPIData(true)
61
61
  try {
62
- newData = await fetchRemoteData(dataUrl)
63
- newData = transform.autoStandardize(newData)
62
+ const result = await fetchRemoteData(dataUrl)
63
+ newData = transform.autoStandardize(result.data)
64
64
  } catch (e) {
65
65
  setErrorMessage('There was an issue loading the data source. Please check the datasource URL and try again.')
66
66
  }
@@ -176,7 +176,6 @@ const Header = (props: HeaderProps) => {
176
176
  Show Data Table(s)
177
177
  </label>
178
178
  <br />
179
-
180
179
  <label>
181
180
  <input
182
181
  type='checkbox'
@@ -185,7 +184,6 @@ const Header = (props: HeaderProps) => {
185
184
  />
186
185
  Expanded by Default
187
186
  </label>
188
- <br />
189
187
  </div>
190
188
 
191
189
  <div className='wrap'>
@@ -206,9 +204,6 @@ const Header = (props: HeaderProps) => {
206
204
  onChange={e => changeConfigValue('table', 'height', e.target.value)}
207
205
  />
208
206
  )}
209
- </div>
210
-
211
- <div className='wrap'>
212
207
  <label>
213
208
  <input
214
209
  type='checkbox'
@@ -217,6 +212,17 @@ const Header = (props: HeaderProps) => {
217
212
  />
218
213
  Show Download CSV Link
219
214
  </label>
215
+ {config.table.download && (
216
+ <input
217
+ type='text'
218
+ placeholder='Customize label'
219
+ defaultValue={config.table.downloadDataLabel}
220
+ onChange={e => changeConfigValue('table', 'downloadDataLabel', e.target.value)}
221
+ />
222
+ )}
223
+ </div>
224
+
225
+ <div className='wrap'>
220
226
  <label>
221
227
  <input
222
228
  type='checkbox'
@@ -225,6 +231,22 @@ const Header = (props: HeaderProps) => {
225
231
  />
226
232
  Show URL to Automatically Updated Data
227
233
  </label>
234
+ <label>
235
+ <input
236
+ type='checkbox'
237
+ defaultChecked={config.table.downloadImageButton}
238
+ onChange={e => changeConfigValue('table', 'downloadImageButton', e.target.checked)}
239
+ />
240
+ Show Download Image Button
241
+ </label>
242
+ {config.table.downloadImageButton && (
243
+ <input
244
+ type='text'
245
+ placeholder='Customize label'
246
+ defaultValue={config.table.downloadImageLabel}
247
+ onChange={e => changeConfigValue('table', 'downloadImageLabel', e.target.value)}
248
+ />
249
+ )}
228
250
  </div>
229
251
  </>
230
252
  )}
@@ -1,4 +1,4 @@
1
- .cdc-open-viz-module .shared-filter-modal,
1
+ .cove-visualization .shared-filter-modal,
2
2
  .cove .shared-filter-modal,
3
3
  .shared-filter-modal {
4
4
  &__title {
@@ -1,17 +1,17 @@
1
1
  .multi-config-tabs {
2
2
  .nav-link {
3
+ border-color: var(--lightGray);
3
4
  border-radius: 6px 6px 0 0;
4
- font-weight: 400;
5
+ color: var(--primary);
5
6
  display: block;
7
+ font-weight: 400;
6
8
  padding: 0.5rem 1rem;
7
9
  @media (max-width: 480px) {
8
10
  padding: 0.5rem 0.8rem;
9
11
  }
10
- color: var(--primary);
11
- border-color: var(--lightGray);
12
12
  :is(button) {
13
- display: none;
14
13
  background: none;
14
+ display: none;
15
15
  }
16
16
  &:hover {
17
17
  :is(button) {
@@ -31,9 +31,9 @@
31
31
  font-weight: bold;
32
32
  }
33
33
  .btn-danger {
34
- text-decoration: none;
35
- padding: 0px 5px;
36
34
  font-size: inherit;
35
+ padding: 0px 5px;
36
+ text-decoration: none;
37
37
  }
38
38
  }
39
39
  .add {
@@ -70,6 +70,12 @@ const RowMenu: React.FC<RowMenuProps> = ({ rowIdx }) => {
70
70
  updateConfig({ ...config, rows: newRows })
71
71
  }
72
72
 
73
+ const toggleEqualHeight = () => {
74
+ const newRows = _.cloneDeep(rows)
75
+ newRows[rowIdx].equalHeight = !newRows[rowIdx].equalHeight
76
+ updateConfig({ ...config, rows: newRows })
77
+ }
78
+
73
79
  const moveRow = (dir = 'down') => {
74
80
  if (rowIdx === rows.length - 1 && dir === 'down') return
75
81
 
@@ -179,9 +185,24 @@ const RowMenu: React.FC<RowMenuProps> = ({ rowIdx }) => {
179
185
  </li>
180
186
  ]
181
187
 
188
+ const isMultiColumn = curr !== '12' && curr !== 'toggle'
189
+
182
190
  return (
183
191
  <nav className='row-menu'>
184
192
  <ul className='row-menu__flyout'>{layoutList}</ul>
193
+ {isMultiColumn && (
194
+ <button
195
+ className={`btn row-menu__btn border-0${row.equalHeight ? ' btn-primary' : ''}`}
196
+ title={row.equalHeight ? 'Disable Equal Height Rows' : 'Enable Equal Height Rows'}
197
+ onClick={toggleEqualHeight}
198
+ >
199
+ <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='25' height='20' fill='#fff'>
200
+ <rect x='1' y='2' width='9' height='14' rx='1' />
201
+ <rect x='14' y='2' width='9' height='14' rx='1' />
202
+ <line x1='0' y1='19' x2='24' y2='19' stroke='#fff' strokeWidth='2' strokeDasharray='3 2' />
203
+ </svg>
204
+ </button>
205
+ )}
185
206
  <div className='spacer'></div>
186
207
  <button
187
208
  className={`btn btn-primary row-menu__btn border-0`}
@@ -1,10 +1,10 @@
1
- .cdc-open-viz-module {
1
+ .cove-visualization {
2
2
  --border: 1px solid var(--lightGray);
3
3
  .toggle-component {
4
4
  display: flex;
5
5
  justify-content: right;
6
- width: 100%;
7
6
  margin-bottom: 15px;
7
+ width: 100%;
8
8
  :first-child:is(div) {
9
9
  border: var(--border);
10
10
  border-radius: 5px 0 0 5px;
@@ -14,18 +14,18 @@
14
14
  border-radius: 0 5px 5px 0;
15
15
  }
16
16
  :is(div) {
17
- border-top: var(--border);
17
+ background-color: var(--white);
18
18
  border-bottom: var(--border);
19
- padding: 7px 15px;
19
+ border-top: var(--border);
20
+ color: var(--primary);
21
+ cursor: pointer;
20
22
  display: inline;
21
23
  float: right;
22
- cursor: pointer;
24
+ padding: 7px 15px;
23
25
  &.selected {
24
26
  background-color: var(--primary);
25
27
  color: white;
26
28
  }
27
- background-color: var(--white);
28
- color: var(--primary);
29
29
  :is(svg) {
30
30
  height: 25px;
31
31
  }