@cdc/dashboard 1.1.2 → 9.22.9

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 (36) hide show
  1. package/dist/cdcdashboard.js +159 -48
  2. package/examples/default-filter-control.json +175 -0
  3. package/examples/default-multi-dataset.json +498 -0
  4. package/examples/default.json +36 -348
  5. package/examples/private/chart-issue.json +3467 -0
  6. package/examples/private/no-issue.json +3467 -0
  7. package/examples/private/totals-two.json +104 -0
  8. package/examples/private/totals.json +103 -0
  9. package/examples/temp-example-data.json +130 -0
  10. package/examples/test-example.json +1 -0
  11. package/package.json +14 -8
  12. package/src/CdcDashboard.js +282 -156
  13. package/src/CdcDashboard.jsx +668 -0
  14. package/src/{context.tsx → ConfigContext.js} +0 -0
  15. package/src/components/{Column.js → Column.jsx} +9 -7
  16. package/src/components/DataTable.tsx +55 -54
  17. package/src/components/EditorPanel.js +207 -45
  18. package/src/components/{Grid.js → Grid.jsx} +5 -4
  19. package/src/components/Header.jsx +242 -0
  20. package/src/components/Row.js +1 -0
  21. package/src/components/Row.jsx +181 -0
  22. package/src/components/Row.jsx~HEAD +212 -0
  23. package/src/components/Widget.js +24 -5
  24. package/src/components/Widget.jsx +191 -0
  25. package/src/index.html +14 -11
  26. package/src/scss/editor-panel.scss +53 -49
  27. package/src/scss/grid.scss +61 -14
  28. package/src/scss/main.scss +73 -9
  29. package/LICENSE +0 -201
  30. package/src/components/Header.js +0 -15
  31. package/src/images/icon-close.svg +0 -1
  32. package/src/images/icon-down.svg +0 -1
  33. package/src/images/icon-edit.svg +0 -1
  34. package/src/images/icon-move.svg +0 -8
  35. package/src/images/icon-up.svg +0 -1
  36. package/src/images/warning.svg +0 -1
@@ -1,11 +1,11 @@
1
- import React, { useContext, memo } from 'react'
1
+ import React, { useContext } from 'react'
2
2
  import { useDrop } from 'react-dnd'
3
3
 
4
- import Context from '../context'
4
+ import ConfigContext from '../ConfigContext'
5
5
  import Widget from './Widget'
6
6
 
7
7
  const Column = ({ data, rowIdx, colIdx }) => {
8
- const { visualizations } = useContext(Context)
8
+ const { visualizations } = useContext(ConfigContext)
9
9
 
10
10
  const [ { isOver, canDrop }, drop ] = useDrop(() => ({
11
11
  accept: 'vis-widget',
@@ -28,19 +28,21 @@ const Column = ({ data, rowIdx, colIdx }) => {
28
28
  'column-size--' + data.width,
29
29
  ]
30
30
 
31
- if(isOver && canDrop) {
31
+ if (isOver && canDrop) {
32
32
  classNames.push('column--drop')
33
33
  }
34
34
 
35
- if(widget) {
35
+ if (widget) {
36
36
  classNames.push('column--populated')
37
37
  }
38
38
 
39
39
  return (
40
40
  <div className={classNames.join(' ')} ref={drop}>
41
- {widget ? <Widget data={{rowIdx, colIdx, ...widget}} type={widget.visualizationType ?? widget.general?.geoType} /> : <p className="builder-column__text">Drag and drop <br/> visualization</p>}
41
+ {widget ?
42
+ <Widget data={{ rowIdx, colIdx, ...widget }} type={widget.visualizationType ?? widget.general?.geoType}/> :
43
+ <p className="builder-column__text">Drag and drop <br/> visualization</p>}
42
44
  </div>
43
45
  )
44
46
  }
45
47
 
46
- export default Column
48
+ export default Column
@@ -15,11 +15,9 @@ import { Base64 } from 'js-base64';
15
15
 
16
16
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary';
17
17
 
18
- import Context from '../context';
18
+ export default function DataTable(props) {
19
19
 
20
- export default function DataTable() {
21
-
22
- const { data, config } = useContext<any>(Context);
20
+ const { data, datasetKey, config } = props;
23
21
 
24
22
  const [tableExpanded, setTableExpanded] = useState<boolean>(config.table ? config.table.expanded : false);
25
23
  const [accessibilityLabel, setAccessibilityLabel] = useState('');
@@ -30,9 +28,11 @@ export default function DataTable() {
30
28
  const csvData = Papa.unparse(data);
31
29
 
32
30
  const saveBlob = () => {
33
- if (navigator.msSaveBlob) {
34
- const dataBlob = new Blob([csvData], {type: "text/csv;charset=utf-8;"});
35
- navigator.msSaveBlob(dataBlob, fileName);
31
+ //@ts-ignore
32
+ if (typeof window.navigator.msSaveBlob === 'function') {
33
+ const dataBlob = new Blob([csvData], { type: "text/csv;charset=utf-8;" });
34
+ //@ts-ignore
35
+ window.navigator.msSaveBlob(dataBlob, fileName);
36
36
  }
37
37
  }
38
38
 
@@ -44,47 +44,45 @@ export default function DataTable() {
44
44
  aria-label="Download this data in a CSV file format."
45
45
  className={`btn btn-download no-border`}
46
46
  >
47
- Download Data (CSV)
47
+ Download {datasetKey ? `"${datasetKey}"` : 'Data'} (CSV)
48
48
  </a>
49
49
  )
50
50
  });
51
51
 
52
52
  // Creates columns structure for the table
53
53
  const tableColumns = useMemo(() => {
54
- const newTableColumns = []/*[{
55
- Header: '',
56
- Cell: ({ row }) => {
57
- return (
58
- <>
59
- </>
60
- )
61
- },
62
- id: 'series-label'
63
- }];*/
64
-
65
- Object.keys(data[0]).map((key) => {
66
- const newCol = {
67
- Header: key,
68
- Cell: ({ row }) => {
69
- return (
70
- <>
71
- {row.original[key]}
72
- </>
73
- );
74
- },
75
- id: key,
76
- canSort: true
77
- };
78
-
79
- newTableColumns.push(newCol);
80
- });
54
+ const newTableColumns = [];
55
+
56
+ // catch no data errors and update the table header.
57
+ if(data.length === 0) {
58
+ return [{
59
+ Header : 'No Data Found'
60
+ }];
61
+ } else {
62
+ Object.keys(data[0]).map((key) => {
63
+ const newCol = {
64
+ Header: key,
65
+ Cell: ({ row }) => {
66
+ return (
67
+ <>
68
+ {row.original[key]}
69
+ </>
70
+ );
71
+ },
72
+ id: key,
73
+ canSort: true
74
+ };
75
+
76
+ newTableColumns.push(newCol);
77
+ });
78
+ }
81
79
 
82
80
  return newTableColumns;
83
81
  }, [config]);
84
82
 
85
83
  const tableData = useMemo(
86
- () => config.data,
87
- [config.data]
84
+ () => data,
85
+ [data]
88
86
  );
89
87
 
90
88
  // Change accessibility label depending on expanded status
@@ -123,18 +121,19 @@ export default function DataTable() {
123
121
  <ErrorBoundary component="DataTable">
124
122
  <section className={`data-table-container`} aria-label={accessibilityLabel}>
125
123
  <div
124
+ role="button"
126
125
  className={tableExpanded ? 'data-table-heading' : 'collapsed data-table-heading'}
127
126
  onClick={() => { setTableExpanded(!tableExpanded); }}
128
127
  tabIndex={0}
129
128
  onKeyDown={(e) => { if (e.keyCode === 13) { setTableExpanded(!tableExpanded); } }}
130
129
  >
131
- {config.table.label}
130
+ {config.table.label}{datasetKey ? `: ${datasetKey}` : ''}
132
131
  </div>
133
132
  <div className="table-container">
134
133
  <table className={tableExpanded ? 'data-table' : 'data-table cdcdataviz-sr-only'} hidden={!tableExpanded} {...getTableProps()}>
135
134
  <caption className="visually-hidden">{config.table.label}</caption>
136
135
  <thead>
137
- {headerGroups.map((headerGroup) => (
136
+ {headerGroups && headerGroups.map((headerGroup) => (
138
137
  <tr {...headerGroup.getHeaderGroupProps()}>
139
138
  {headerGroup.headers.map((column) => (
140
139
  <th tabIndex="0" {...column.getHeaderProps(column.getSortByToggleProps())} className={column.isSorted ? column.isSortedDesc ? 'sort sort-desc' : 'sort sort-asc' : 'sort'} title={column.Header}>
@@ -145,24 +144,26 @@ export default function DataTable() {
145
144
  </tr>
146
145
  ))}
147
146
  </thead>
148
- <tbody {...getTableBodyProps()}>
149
- {rows.map((row) => {
150
- prepareRow(row);
151
- return (
152
- <tr {...row.getRowProps()}>
153
- {row.cells.map((cell) => (
154
- <td tabIndex="0" {...cell.getCellProps()}>
155
- {cell.render('Cell')}
156
- </td>
157
- ))}
158
- </tr>
159
- );
160
- })}
161
- </tbody>
147
+ {rows &&
148
+ <tbody {...getTableBodyProps()}>
149
+ {rows.map((row) => {
150
+ prepareRow(row);
151
+ return (
152
+ <tr {...row.getRowProps()}>
153
+ {row.cells && row.cells.map((cell) => (
154
+ <td tabIndex="0" {...cell.getCellProps()}>
155
+ {cell.render('Cell')}
156
+ </td>
157
+ ))}
158
+ </tr>
159
+ );
160
+ })}
161
+ </tbody>
162
+ }
162
163
  </table>
163
164
  </div>
164
165
  {config.table.download && <DownloadButton data={data} />}
165
166
  </section>
166
167
  </ErrorBoundary>
167
168
  );
168
- }
169
+ }
@@ -1,4 +1,4 @@
1
- import React, { useState, useEffect, useCallback, memo, useContext } from 'react'
1
+ import React, { useState, useEffect, memo, useContext } from 'react'
2
2
  import ReactTooltip from 'react-tooltip'
3
3
 
4
4
  import {
@@ -14,6 +14,8 @@ import Context from '../context';
14
14
 
15
15
  import ErrorBoundary from '@cdc/core/components/ErrorBoundary';
16
16
  import QuestionIcon from '@cdc/core/assets/question-circle.svg';
17
+ import Tooltip from '@cdc/core/components/ui/Tooltip'
18
+ import Icon from '@cdc/core/components/ui/Icon'
17
19
 
18
20
  const Helper = ({text}) => {
19
21
  return (
@@ -38,7 +40,7 @@ const Helper = ({text}) => {
38
40
  window.CustomEvent = CustomEvent;
39
41
  })();
40
42
 
41
- const TextField = memo(({label, section = null, subsection = null, fieldName, updateField, value: stateValue, type = "input", i = null, min = null, ...attributes}) => {
43
+ const TextField = memo(({label, section = null, subsection = null, fieldName, updateField, value: stateValue, tooltip, type = "input", i = null, min = null, ...attributes}) => {
42
44
  const [ value, setValue ] = useState(stateValue);
43
45
 
44
46
  const [ debouncedValue ] = useDebounce(value, 500);
@@ -77,7 +79,7 @@ const TextField = memo(({label, section = null, subsection = null, fieldName, up
77
79
 
78
80
  return (
79
81
  <label>
80
- <span className="edit-label column-heading">{label}</span>
82
+ <span className="edit-label column-heading">{label}{tooltip}</span>
81
83
  {formElement}
82
84
  </label>
83
85
  )
@@ -108,8 +110,6 @@ const Select = memo(({label, value, options, fieldName, section = null, subsecti
108
110
  )
109
111
  })
110
112
 
111
- const headerColors = ['theme-blue','theme-purple','theme-brown','theme-teal','theme-pink','theme-orange','theme-slate','theme-indigo','theme-cyan','theme-green','theme-amber']
112
-
113
113
  const EditorPanel = memo(() => {
114
114
  const {
115
115
  config,
@@ -203,7 +203,6 @@ const EditorPanel = memo(() => {
203
203
  </section>
204
204
  </section>
205
205
  );
206
-
207
206
  }
208
207
 
209
208
  const convertStateToConfig = (type = "JSON") => {
@@ -266,83 +265,246 @@ const EditorPanel = memo(() => {
266
265
 
267
266
  updateConfig({...config, dashboard: dashboardConfig});
268
267
  }
269
-
268
+
270
269
  return (
271
270
  <ErrorBoundary component="EditorPanel">
272
- {config.runtime && config.runtime.editorErrorMessage && <Error /> }
273
- <button className={displayPanel ? `editor-toggle` : `editor-toggle collapsed`} title={displayPanel ? `Collapse Editor` : `Expand Editor`} onClick={() => setDisplayPanel(!displayPanel) }></button>
274
- <section className={displayPanel ? 'editor-panel' : 'hidden editor-panel'}>
271
+ {config.runtime && config.runtime.editorErrorMessage && <Error />}
272
+ <button
273
+ className={displayPanel ? `editor-toggle` : `editor-toggle collapsed`}
274
+ title={displayPanel ? `Collapse Editor` : `Expand Editor`}
275
+ onClick={() => setDisplayPanel(!displayPanel)}
276
+ ></button>
277
+ <section
278
+ className={
279
+ displayPanel ? "editor-panel cove" : "hidden editor-panel cove"
280
+ }
281
+ >
275
282
  <div className="heading-2">Configure</div>
276
283
  <section className="form-container">
277
284
  <form>
278
285
  <Accordion allowZeroExpanded={true}>
279
- <AccordionItem> {/* General */}
286
+ <AccordionItem>
287
+ {" "}
288
+ {/* General */}
280
289
  <AccordionItemHeading>
281
- <AccordionItemButton>
282
- General
283
- </AccordionItemButton>
290
+ <AccordionItemButton>General</AccordionItemButton>
284
291
  </AccordionItemHeading>
285
292
  <AccordionItemPanel>
286
- <TextField value={config.dashboard.title} section="dashboard" fieldName="title" label="Title" updateField={updateField} />
287
- <TextField type="textarea" value={config.dashboard.description} section="dashboard" fieldName="description" label="Description" updateField={updateField} />
293
+ <TextField
294
+ value={config.dashboard.title}
295
+ section="dashboard"
296
+ fieldName="title"
297
+ label="Title"
298
+ updateField={updateField}
299
+ />
300
+
301
+ <TextField
302
+ value={"Super Title"}
303
+ updateField={updateField}
304
+ section="general"
305
+ fieldName="superTitle"
306
+ label="Super Title"
307
+ placeholder="Super Title"
308
+ tooltip={
309
+ <Tooltip style={{ textTransform: "none" }}>
310
+ <Tooltip.Target>
311
+ <Icon
312
+ display="question"
313
+ style={{ marginLeft: "0.5rem" }}
314
+ />
315
+ </Tooltip.Target>
316
+ <Tooltip.Content>
317
+ <p>Super Title</p>
318
+ </Tooltip.Content>
319
+ </Tooltip>
320
+ }
321
+ />
322
+
323
+ <TextField
324
+ type="textarea"
325
+ value={"Intro Text"}
326
+ updateField={updateField}
327
+ section="general"
328
+ fieldName="introText"
329
+ label="Intro Text"
330
+ tooltip={
331
+ <Tooltip style={{ textTransform: "none" }}>
332
+ <Tooltip.Target>
333
+ <Icon
334
+ display="question"
335
+ style={{ marginLeft: "0.5rem" }}
336
+ />
337
+ </Tooltip.Target>
338
+ <Tooltip.Content>
339
+ <p>Intro Text</p>
340
+ </Tooltip.Content>
341
+ </Tooltip>
342
+ }
343
+ />
344
+
345
+ <TextField
346
+ type="textarea"
347
+ value={config.dashboard.description}
348
+ section="dashboard"
349
+ fieldName="description"
350
+ label="Description"
351
+ updateField={updateField}
352
+ tooltip={
353
+ <Tooltip style={{ textTransform: "none" }}>
354
+ <Tooltip.Target>
355
+ <Icon
356
+ display="question"
357
+ style={{ marginLeft: "0.5rem" }}
358
+ />
359
+ </Tooltip.Target>
360
+ <Tooltip.Content>
361
+ <p>
362
+ Enter supporting text to display below the data
363
+ visualization, if applicable. The following HTML
364
+ tags are supported: strong, em, sup, and sub.
365
+ </p>
366
+ </Tooltip.Content>
367
+ </Tooltip>
368
+ }
369
+ />
370
+
371
+ <TextField
372
+ type="textarea"
373
+ value={"Foootnotes"}
374
+ updateField={updateField}
375
+ section="general"
376
+ fieldName="footnotes"
377
+ label="Footnotes"
378
+ tooltip={
379
+ <Tooltip style={{ textTransform: "none" }}>
380
+ <Tooltip.Target>
381
+ <Icon
382
+ display="question"
383
+ style={{ marginLeft: "0.5rem" }}
384
+ />
385
+ </Tooltip.Target>
386
+ <Tooltip.Content>
387
+ <p>Footnotes</p>
388
+ </Tooltip.Content>
389
+ </Tooltip>
390
+ }
391
+ />
288
392
  </AccordionItemPanel>
289
393
  </AccordionItem>
290
394
  <AccordionItem>
291
395
  <AccordionItemHeading>
292
- <AccordionItemButton>
293
- Filters
294
- </AccordionItemButton>
396
+ <AccordionItemButton>Filters</AccordionItemButton>
295
397
  </AccordionItemHeading>
296
398
  <AccordionItemPanel>
297
399
  <ul className="filters-list">
298
- {config.dashboard.filters && config.dashboard.filters.map((filter, index) => (
299
- <fieldset className="edit-block" key={filter.columnName + index}>
300
- <button type="button" className="remove-column" onClick={() => {removeFilter(index)}}>Remove</button>
400
+ {config.dashboard.filters &&
401
+ config.dashboard.filters.map((filter, index) => (
402
+ <fieldset
403
+ className="edit-block"
404
+ key={filter.columnName + index}
405
+ >
406
+ <button
407
+ type="button"
408
+ className="remove-column"
409
+ onClick={() => {
410
+ removeFilter(index);
411
+ }}
412
+ >
413
+ Remove
414
+ </button>
301
415
  <label>
302
- <span className="edit-label column-heading">Filter</span>
303
- <select value={filter.columnName} onChange={(e) => {updateFilterProp('columnName', index, e.target.value)}}>
416
+ <span className="edit-label column-heading">
417
+ Filter
418
+ </span>
419
+ <select
420
+ value={filter.columnName}
421
+ onChange={(e) => {
422
+ updateFilterProp(
423
+ "columnName",
424
+ index,
425
+ e.target.value
426
+ );
427
+ }}
428
+ >
304
429
  <option value="">- Select Option -</option>
305
430
  {getColumns().map((dataKey) => (
306
- <option value={dataKey} key={dataKey}>{dataKey}</option>
431
+ <option value={dataKey} key={dataKey}>
432
+ {dataKey}
433
+ </option>
307
434
  ))}
308
435
  </select>
309
436
  </label>
310
437
  <label>
311
- <span className="edit-label column-heading">Label</span>
312
- <input type="text" value={filter.label} onChange={(e) => {updateFilterProp('label', index, e.target.value)}}/>
438
+ <span className="edit-label column-heading">
439
+ Label
440
+ </span>
441
+ <input
442
+ type="text"
443
+ value={filter.label}
444
+ onChange={(e) => {
445
+ updateFilterProp(
446
+ "label",
447
+ index,
448
+ e.target.value
449
+ );
450
+ }}
451
+ />
313
452
  </label>
314
453
  </fieldset>
315
- )
316
- )}
454
+ ))}
317
455
  </ul>
318
456
 
319
- <button type="button" onClick={addNewFilter} className="btn btn-primary">Add Filter</button>
457
+ <button
458
+ type="button"
459
+ onClick={addNewFilter}
460
+ className="btn btn-primary"
461
+ >
462
+ Add Filter
463
+ </button>
320
464
  </AccordionItemPanel>
321
465
  </AccordionItem>
322
466
  <AccordionItem>
323
467
  <AccordionItemHeading>
324
- <AccordionItemButton>
325
- Data Table
326
- </AccordionItemButton>
468
+ <AccordionItemButton>Data Table</AccordionItemButton>
327
469
  </AccordionItemHeading>
328
470
  <AccordionItemPanel>
329
- <CheckBox value={config.table.show} section="table" fieldName="show" label="Show Table" updateField={updateField} />
330
- <CheckBox value={config.table.expanded} section="table" fieldName="expanded" label="Expanded by Default" updateField={updateField} />
331
- <CheckBox value={config.table.download} section="table" fieldName="download" label="Display Download Button" updateField={updateField} />
332
- <TextField value={config.table.label} section="table" fieldName="label" label="Label" updateField={updateField} />
471
+ <CheckBox
472
+ value={config.table.show}
473
+ section="table"
474
+ fieldName="show"
475
+ label="Show Table"
476
+ updateField={updateField}
477
+ />
478
+ <CheckBox
479
+ value={config.table.expanded}
480
+ section="table"
481
+ fieldName="expanded"
482
+ label="Expanded by Default"
483
+ updateField={updateField}
484
+ />
485
+ <CheckBox
486
+ value={config.table.download}
487
+ section="table"
488
+ fieldName="download"
489
+ label="Display Download Button"
490
+ updateField={updateField}
491
+ />
492
+ <TextField
493
+ value={config.table.label}
494
+ section="table"
495
+ fieldName="label"
496
+ label="Label"
497
+ updateField={updateField}
498
+ />
333
499
  </AccordionItemPanel>
334
500
  </AccordionItem>
335
- </Accordion>
501
+ </Accordion>
336
502
  </form>
337
503
  </section>
338
- <ReactTooltip
339
- html={true}
340
- multiline={true}
341
- className="helper-tooltip"
342
- />
504
+ <ReactTooltip html={true} multiline={true} className="helper-tooltip" />
343
505
  </section>
344
506
  </ErrorBoundary>
345
- )
507
+ );
346
508
  })
347
509
 
348
- export default EditorPanel;
510
+ export default EditorPanel;
@@ -1,17 +1,18 @@
1
1
  import React, { useContext } from 'react'
2
2
  import Row from './Row'
3
3
 
4
- import Context from '../context'
4
+ import ConfigContext from '../ConfigContext'
5
5
 
6
6
  const Grid = () => {
7
- const { rows, config, updateConfig } = useContext(Context)
7
+ const { rows, config, updateConfig } = useContext(ConfigContext)
8
8
 
9
9
  const addRow = () => {
10
10
  updateConfig({
11
11
  ...config,
12
12
  rows: [
13
13
  ...rows,
14
- [{width: 12}, {}, {}]
14
+ [ { width: 12 }, { equalHeight: false }, {}, {} ]
15
+ //[{width: 12}, {}, {}] ],
15
16
  ],
16
17
  uuid: Date.now()
17
18
  })
@@ -25,4 +26,4 @@ const Grid = () => {
25
26
  )
26
27
  }
27
28
 
28
- export default Grid
29
+ export default Grid