@cdc/editor 4.26.4 → 4.26.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/dist/cdceditor.js +51430 -50567
- package/example/private/dashboard-filter-issue/dashboard-filter-issue.json +957 -0
- package/package.json +9 -9
- package/src/_stories/Editor.stories.tsx +62 -0
- package/src/assets.d.ts +4 -0
- package/src/components/ChooseTab.test.tsx +64 -1
- package/src/components/ChooseTab.tsx +77 -1
- package/src/components/DataImport/components/DataImport.tsx +21 -20
- package/src/components/DataImport/components/SampleData.test.tsx +16 -0
- package/src/components/DataImport/components/SampleData.tsx +6 -0
- package/src/components/DataImport/components/samples/valid-heatmap-varicella-cases.csv +13 -0
- package/src/scss/choose-vis-tab.scss +7 -1
- package/src/scss/main.scss +0 -5
package/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cdc/editor",
|
|
3
|
-
"version": "4.26.
|
|
3
|
+
"version": "4.26.5",
|
|
4
4
|
"description": "React component for generating a new component entry",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"bugs": "https://github.com/CDCgov/cdc-open-viz/issues",
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"@cdc/chart": "^4.26.
|
|
9
|
-
"@cdc/core": "^4.26.
|
|
10
|
-
"@cdc/dashboard": "^4.26.
|
|
11
|
-
"@cdc/data-bite": "^4.26.
|
|
12
|
-
"@cdc/map": "^4.26.
|
|
13
|
-
"@cdc/markup-include": "^4.26.
|
|
14
|
-
"@cdc/waffle-chart": "^4.26.
|
|
8
|
+
"@cdc/chart": "^4.26.5",
|
|
9
|
+
"@cdc/core": "^4.26.5",
|
|
10
|
+
"@cdc/dashboard": "^4.26.5",
|
|
11
|
+
"@cdc/data-bite": "^4.26.5",
|
|
12
|
+
"@cdc/map": "^4.26.5",
|
|
13
|
+
"@cdc/markup-include": "^4.26.5",
|
|
14
|
+
"@cdc/waffle-chart": "^4.26.5",
|
|
15
15
|
"axios": "^1.13.2",
|
|
16
16
|
"d3": "^7.9.0",
|
|
17
17
|
"react-dropzone": "^14.3.8",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"vite-plugin-css-injected-by-js": "^2.4.0",
|
|
24
24
|
"vite-plugin-svgr": "^4.2.0"
|
|
25
25
|
},
|
|
26
|
-
"gitHead": "
|
|
26
|
+
"gitHead": "61c025165d96b45a6002c34582c5a622a9d865a9",
|
|
27
27
|
"main": "dist/cdceditor",
|
|
28
28
|
"moduleName": "CdcEditor",
|
|
29
29
|
"peerDependencies": {
|
|
@@ -165,6 +165,68 @@ export const DownloadSingleVizCSV: Story = {
|
|
|
165
165
|
}
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
+
export const LoadFromApiUrlPreview: Story = {
|
|
169
|
+
args: { config: {} },
|
|
170
|
+
play: async ({ canvasElement }) => {
|
|
171
|
+
const canvas = within(canvasElement)
|
|
172
|
+
const user = userEvent.setup()
|
|
173
|
+
|
|
174
|
+
// Intercept fetch and return JSON with a charset in the Content-Type header.
|
|
175
|
+
// This exercises the MIME type normalisation fix: fetch preserves the full
|
|
176
|
+
// Content-Type value (e.g. 'application/json; charset=utf-8') while the old
|
|
177
|
+
// axios code would strip parameters, so the blob re-typing comparison
|
|
178
|
+
// must use the base type before the semicolon.
|
|
179
|
+
const mockData = [
|
|
180
|
+
{ state: 'Alabama', value: '42' },
|
|
181
|
+
{ state: 'Alaska', value: '37' },
|
|
182
|
+
{ state: 'Arizona', value: '55' }
|
|
183
|
+
]
|
|
184
|
+
const mockBlob = new Blob([JSON.stringify(mockData)], { type: 'application/json; charset=utf-8' })
|
|
185
|
+
const originalFetch = window.fetch
|
|
186
|
+
window.fetch = () => Promise.resolve({ ok: true, blob: () => Promise.resolve(mockBlob) } as Response)
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
// Select Dashboard so the dataset name becomes meaningful and the
|
|
190
|
+
// multi-dataset import flow is exercised
|
|
191
|
+
await user.click(await canvas.findByRole('button', { name: 'Dashboard' }))
|
|
192
|
+
|
|
193
|
+
await user.click(canvas.getByText('2. Import Data'))
|
|
194
|
+
|
|
195
|
+
// Switch to the URL tab
|
|
196
|
+
await user.click(await canvas.findByText('Load from URL'))
|
|
197
|
+
|
|
198
|
+
// Both fields are required before the button is enabled
|
|
199
|
+
const nameInput = await canvas.findByLabelText('Enter Dataset Name')
|
|
200
|
+
await user.type(nameInput, 'api-data')
|
|
201
|
+
|
|
202
|
+
const urlInput = await canvas.findByLabelText('Load data from external URL')
|
|
203
|
+
await user.type(urlInput, 'https://example.gov/api/data.json')
|
|
204
|
+
|
|
205
|
+
await user.click(await canvas.findByRole('button', { name: 'Save & Load' }))
|
|
206
|
+
|
|
207
|
+
// After a successful dashboard dataset load the Data Sources table appears
|
|
208
|
+
// and the preview panel should auto-populate (dataset is created with preview: true)
|
|
209
|
+
await expect(canvas.findByText('Data Sources')).resolves.toBeTruthy()
|
|
210
|
+
await expect(canvas.findByText('Data Preview')).resolves.toBeTruthy()
|
|
211
|
+
await expect(canvas.findByText('state')).resolves.toBeTruthy()
|
|
212
|
+
await expect(canvas.findByText('Alabama')).resolves.toBeTruthy()
|
|
213
|
+
|
|
214
|
+
// Navigate away to tab 3 then back to tab 2 — the dataset must survive the round-trip
|
|
215
|
+
await new Promise(r => setTimeout(r, 1500))
|
|
216
|
+
await user.click(canvas.getByText('3. Configure'))
|
|
217
|
+
await new Promise(r => setTimeout(r, 500))
|
|
218
|
+
await user.click(canvas.getByText('2. Import Data'))
|
|
219
|
+
await new Promise(r => setTimeout(r, 500))
|
|
220
|
+
|
|
221
|
+
await expect(canvas.findByText('Data Sources')).resolves.toBeTruthy()
|
|
222
|
+
await expect(canvas.findByText('Data Preview')).resolves.toBeTruthy()
|
|
223
|
+
await expect(canvas.findByText('Alabama')).resolves.toBeTruthy()
|
|
224
|
+
} finally {
|
|
225
|
+
window.fetch = originalFetch
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
168
230
|
export const InvalidJsonShowsValidationAlert: Story = {
|
|
169
231
|
args: {
|
|
170
232
|
config: {}
|
package/src/assets.d.ts
ADDED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
|
-
import { render, waitFor } from '@testing-library/react'
|
|
2
|
+
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
|
3
3
|
|
|
4
4
|
import ConfigContext, { EditorDispatchContext } from '@cdc/core/contexts/EditorContext'
|
|
5
5
|
import ChooseTab from './ChooseTab'
|
|
@@ -33,4 +33,67 @@ describe('ChooseTab', () => {
|
|
|
33
33
|
expect(dispatch).toHaveBeenCalledWith({ type: 'EDITOR_SAVE', payload: tempConfig })
|
|
34
34
|
})
|
|
35
35
|
})
|
|
36
|
+
|
|
37
|
+
it('creates a HeatMap starter config when the HeatMap button is selected', () => {
|
|
38
|
+
const dispatch = vi.fn()
|
|
39
|
+
|
|
40
|
+
render(
|
|
41
|
+
<ConfigContext.Provider
|
|
42
|
+
value={
|
|
43
|
+
{
|
|
44
|
+
config: { type: 'chart' },
|
|
45
|
+
tempConfig: null,
|
|
46
|
+
errors: [],
|
|
47
|
+
currentViewport: 'lg',
|
|
48
|
+
globalActive: 0,
|
|
49
|
+
setTempConfig: vi.fn()
|
|
50
|
+
} as any
|
|
51
|
+
}
|
|
52
|
+
>
|
|
53
|
+
<EditorDispatchContext.Provider value={dispatch}>
|
|
54
|
+
<ChooseTab />
|
|
55
|
+
</EditorDispatchContext.Provider>
|
|
56
|
+
</ConfigContext.Provider>
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
expect(
|
|
60
|
+
screen.getByRole('button', { name: 'HeatMap' }).querySelector('.choose-vis__heatmap-icon')
|
|
61
|
+
).toBeInTheDocument()
|
|
62
|
+
|
|
63
|
+
fireEvent.click(screen.getByRole('button', { name: 'HeatMap' }))
|
|
64
|
+
|
|
65
|
+
expect(dispatch).toHaveBeenCalledWith(
|
|
66
|
+
expect.objectContaining({
|
|
67
|
+
type: 'EDITOR_SET_CONFIG',
|
|
68
|
+
payload: expect.objectContaining({
|
|
69
|
+
visualizationType: 'HeatMap',
|
|
70
|
+
type: 'chart',
|
|
71
|
+
title: 'Synthetic Varicella Cases by HHS Region',
|
|
72
|
+
xAxis: expect.objectContaining({
|
|
73
|
+
dataKey: 'Month',
|
|
74
|
+
label: 'Month'
|
|
75
|
+
}),
|
|
76
|
+
yAxis: expect.objectContaining({
|
|
77
|
+
label: 'HHS Region',
|
|
78
|
+
titlePlacement: 'side'
|
|
79
|
+
}),
|
|
80
|
+
series: expect.arrayContaining([
|
|
81
|
+
expect.objectContaining({
|
|
82
|
+
dataKey: 'HHS Region 1',
|
|
83
|
+
name: 'Region 1',
|
|
84
|
+
type: 'HeatMap'
|
|
85
|
+
})
|
|
86
|
+
]),
|
|
87
|
+
heatmap: expect.objectContaining({
|
|
88
|
+
cellPadding: 2
|
|
89
|
+
}),
|
|
90
|
+
legend: expect.objectContaining({
|
|
91
|
+
position: 'top',
|
|
92
|
+
style: 'gradient',
|
|
93
|
+
label: 'Reported cases'
|
|
94
|
+
})
|
|
95
|
+
})
|
|
96
|
+
})
|
|
97
|
+
)
|
|
98
|
+
})
|
|
36
99
|
})
|
|
@@ -18,6 +18,7 @@ import EpiChartIcon from '@cdc/core/assets/icon-epi-chart.svg'
|
|
|
18
18
|
import ForecastIcon from '@cdc/core/assets/icon-chart-forecast.svg'
|
|
19
19
|
import GaugeChartIcon from '@cdc/core/assets/icon-linear-gauge.svg'
|
|
20
20
|
import GlobeIcon from '@cdc/core/assets/icon-map-world.svg'
|
|
21
|
+
import HeatMapIconSrc from '@cdc/core/assets/icon-heatmap.png'
|
|
21
22
|
import HorizonChartIcon from '@cdc/core/assets/icon-chart-area.svg'
|
|
22
23
|
import HorizontalStackIcon from '@cdc/core/assets/icon-chart-bar-stacked.svg'
|
|
23
24
|
import Icon from '@cdc/core/components/ui/Icon'
|
|
@@ -69,6 +70,11 @@ const VizButton: React.FC<VizButtonProps> = ({ activeVizButtonID, onConfigure, .
|
|
|
69
70
|
)
|
|
70
71
|
}
|
|
71
72
|
|
|
73
|
+
const HeatMapIcon = () => <img className='choose-vis__heatmap-icon' src={HeatMapIconSrc} alt='' aria-hidden='true' />
|
|
74
|
+
|
|
75
|
+
const heatMapRegionColumns = Array.from({ length: 10 }, (_, index) => `HHS Region ${index + 1}`)
|
|
76
|
+
const heatMapSampleColumns = ['Month', ...heatMapRegionColumns]
|
|
77
|
+
|
|
72
78
|
const ChooseTab: React.FC = (): JSX.Element => {
|
|
73
79
|
const { config, tempConfig } = useContext(ConfigContext)
|
|
74
80
|
|
|
@@ -441,6 +447,76 @@ const buttons = [
|
|
|
441
447
|
icon: <ForecastIcon />,
|
|
442
448
|
content: 'Display a forecasting chart to predict future data trends.'
|
|
443
449
|
},
|
|
450
|
+
{
|
|
451
|
+
id: 28,
|
|
452
|
+
category: 'Charts',
|
|
453
|
+
label: 'HeatMap',
|
|
454
|
+
type: 'chart',
|
|
455
|
+
subType: 'HeatMap',
|
|
456
|
+
title: 'Synthetic Varicella Cases by HHS Region',
|
|
457
|
+
showTitle: true,
|
|
458
|
+
description: 'Example data are synthetic and for demonstration only.',
|
|
459
|
+
orientation: 'vertical',
|
|
460
|
+
xAxis: {
|
|
461
|
+
type: 'categorical',
|
|
462
|
+
dataKey: 'Month',
|
|
463
|
+
label: 'Month',
|
|
464
|
+
size: 75,
|
|
465
|
+
maxTickRotation: 0,
|
|
466
|
+
tickRotation: 0,
|
|
467
|
+
labelOffset: 0
|
|
468
|
+
},
|
|
469
|
+
yAxis: {
|
|
470
|
+
type: 'categorical',
|
|
471
|
+
label: 'HHS Region',
|
|
472
|
+
size: 120,
|
|
473
|
+
titlePlacement: 'side'
|
|
474
|
+
},
|
|
475
|
+
heatmap: {
|
|
476
|
+
cellPadding: 2,
|
|
477
|
+
rowLabelGap: 32,
|
|
478
|
+
columnLabelGap: 48,
|
|
479
|
+
xAxisPosition: 'top',
|
|
480
|
+
showCellValues: false
|
|
481
|
+
},
|
|
482
|
+
series: heatMapRegionColumns.map((region, index) => ({
|
|
483
|
+
dataKey: region,
|
|
484
|
+
name: `Region ${index + 1}`,
|
|
485
|
+
type: 'HeatMap',
|
|
486
|
+
axis: 'Left',
|
|
487
|
+
tooltip: true
|
|
488
|
+
})),
|
|
489
|
+
columns: heatMapSampleColumns.reduce(
|
|
490
|
+
(columns, columnName) => ({
|
|
491
|
+
...columns,
|
|
492
|
+
[columnName]: {
|
|
493
|
+
name: columnName,
|
|
494
|
+
label: columnName.replace('HHS ', ''),
|
|
495
|
+
dataTable: true
|
|
496
|
+
}
|
|
497
|
+
}),
|
|
498
|
+
{}
|
|
499
|
+
),
|
|
500
|
+
dataFormat: {
|
|
501
|
+
commas: true,
|
|
502
|
+
roundTo: 0
|
|
503
|
+
},
|
|
504
|
+
general: {
|
|
505
|
+
palette: {
|
|
506
|
+
isReversed: false,
|
|
507
|
+
version: '2.0',
|
|
508
|
+
name: 'sequential_blue'
|
|
509
|
+
}
|
|
510
|
+
},
|
|
511
|
+
legend: {
|
|
512
|
+
position: 'top',
|
|
513
|
+
style: 'gradient',
|
|
514
|
+
subStyle: 'smooth',
|
|
515
|
+
label: 'Reported cases'
|
|
516
|
+
},
|
|
517
|
+
icon: <HeatMapIcon />,
|
|
518
|
+
content: 'Display a heatmap to compare intensity across two dimensions.'
|
|
519
|
+
},
|
|
444
520
|
{
|
|
445
521
|
id: 27,
|
|
446
522
|
category: 'Charts',
|
|
@@ -493,7 +569,7 @@ const buttons = [
|
|
|
493
569
|
content: 'Present the numerical proportions of a data series.'
|
|
494
570
|
},
|
|
495
571
|
{
|
|
496
|
-
id:
|
|
572
|
+
id: 29,
|
|
497
573
|
category: 'Charts',
|
|
498
574
|
label: 'Radar',
|
|
499
575
|
type: 'chart',
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import React, { useState, useContext, useEffect } from 'react'
|
|
2
2
|
import { useDropzone } from 'react-dropzone'
|
|
3
|
-
import axios from 'axios'
|
|
4
3
|
import { csvFormat } from 'd3'
|
|
5
4
|
|
|
6
5
|
import { DataTransform } from '@cdc/core/helpers/DataTransform'
|
|
@@ -102,25 +101,27 @@ const DataImport = () => {
|
|
|
102
101
|
|
|
103
102
|
try {
|
|
104
103
|
// eslint-disable-next-line no-unused-vars
|
|
105
|
-
await
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
104
|
+
const response = await fetch(dataURL.toString())
|
|
105
|
+
if (!response.ok) {
|
|
106
|
+
throw new Error(`HTTP error ${response.status}`)
|
|
107
|
+
}
|
|
108
|
+
responseBlob = await response.blob()
|
|
109
|
+
|
|
110
|
+
// Strip charset/parameters — fetch preserves the full Content-Type (e.g. 'application/json; charset=utf-8')
|
|
111
|
+
// while XHR (axios) would strip them, so normalise before comparing.
|
|
112
|
+
const blobBaseType = responseBlob.type.split(';')[0].trim()
|
|
113
|
+
|
|
114
|
+
// Sometimes the files are coming in as plain text types... Maybe when saved from Macs
|
|
115
|
+
const csvTypes = ['text/csv', 'text/plain']
|
|
116
|
+
if ((fileExtension === '.csv' && csvTypes.includes(blobBaseType)) || isSolrCsv(externalURL)) {
|
|
117
|
+
responseBlob = responseBlob.slice(0, responseBlob.size, 'text/csv')
|
|
118
|
+
} else if (
|
|
119
|
+
blobBaseType === 'application/json' ||
|
|
120
|
+
(fileExtension === '.json' && blobBaseType === 'text/plain') ||
|
|
121
|
+
isSolrJson(externalURL)
|
|
122
|
+
) {
|
|
123
|
+
responseBlob = responseBlob.slice(0, responseBlob.size, 'application/json')
|
|
124
|
+
}
|
|
124
125
|
} catch (err) {
|
|
125
126
|
// eslint-disable-next-line no-console
|
|
126
127
|
console.error('Error in loadExternal', err)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import SampleData from './SampleData'
|
|
2
|
+
|
|
3
|
+
describe('SampleData', () => {
|
|
4
|
+
it('includes a synthetic varicella HeatMap sample dataset', () => {
|
|
5
|
+
const sample = SampleData.data.charts.find(sample => sample.fileName === 'valid-heatmap-varicella-cases.csv')
|
|
6
|
+
|
|
7
|
+
expect(sample).toBeDefined()
|
|
8
|
+
expect(sample).toEqual(
|
|
9
|
+
expect.objectContaining({
|
|
10
|
+
text: 'HeatMap Data (Synthetic Varicella Cases)',
|
|
11
|
+
data: expect.stringContaining('Month,HHS Region 1,HHS Region 2')
|
|
12
|
+
})
|
|
13
|
+
)
|
|
14
|
+
expect(sample?.data).toContain('Apr,55,61,78,69,72,64,58,57,66,50')
|
|
15
|
+
})
|
|
16
|
+
})
|
|
@@ -10,6 +10,7 @@ import validBoxPlotData from './samples/valid-boxplot.csv?raw'
|
|
|
10
10
|
import validChartData from './samples/valid-data-chart.csv?raw'
|
|
11
11
|
import validCountyMapData from './samples/valid-county-data.csv?raw'
|
|
12
12
|
import validForecastData from './samples/valid-forecast-data.csv?raw'
|
|
13
|
+
import validHeatMapData from './samples/valid-heatmap-varicella-cases.csv?raw'
|
|
13
14
|
import validGeoPoint from './samples/valid-geo-point.csv?raw'
|
|
14
15
|
import validHorizonData from './samples/valid-horizon-chart.json?raw'
|
|
15
16
|
import validMapData from './samples/valid-data-map.csv?raw'
|
|
@@ -60,6 +61,11 @@ const sampleData = {
|
|
|
60
61
|
fileName: 'valid-horizon-data.json',
|
|
61
62
|
data: validHorizonData
|
|
62
63
|
},
|
|
64
|
+
{
|
|
65
|
+
text: 'HeatMap Data (Synthetic Varicella Cases)',
|
|
66
|
+
fileName: 'valid-heatmap-varicella-cases.csv',
|
|
67
|
+
data: validHeatMapData
|
|
68
|
+
},
|
|
63
69
|
{
|
|
64
70
|
text: 'Sankey Chart Data',
|
|
65
71
|
fileName: 'valid-sankey-data.json',
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Month,HHS Region 1,HHS Region 2,HHS Region 3,HHS Region 4,HHS Region 5,HHS Region 6,HHS Region 7,HHS Region 8,HHS Region 9,HHS Region 10
|
|
2
|
+
Jan,18,22,35,28,31,24,19,21,26,16
|
|
3
|
+
Feb,24,29,41,34,38,31,27,28,33,22
|
|
4
|
+
Mar,42,48,63,56,59,51,46,44,53,39
|
|
5
|
+
Apr,55,61,78,69,72,64,58,57,66,50
|
|
6
|
+
May,49,54,70,62,65,58,52,51,60,46
|
|
7
|
+
Jun,31,36,49,43,45,40,35,34,41,30
|
|
8
|
+
Jul,17,21,30,25,28,23,20,19,24,16
|
|
9
|
+
Aug,12,15,22,18,20,16,14,13,17,11
|
|
10
|
+
Sep,14,18,26,21,23,19,16,15,20,13
|
|
11
|
+
Oct,20,24,33,28,30,25,22,21,27,18
|
|
12
|
+
Nov,27,32,44,37,40,34,29,30,35,24
|
|
13
|
+
Dec,34,39,52,45,48,41,36,37,43,31
|
|
@@ -84,7 +84,8 @@
|
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
svg
|
|
87
|
+
svg,
|
|
88
|
+
.choose-vis__heatmap-icon {
|
|
88
89
|
display: block;
|
|
89
90
|
margin: 0 auto;
|
|
90
91
|
box-sizing: border-box;
|
|
@@ -92,6 +93,11 @@
|
|
|
92
93
|
height: 80px;
|
|
93
94
|
flex-shrink: 0;
|
|
94
95
|
}
|
|
96
|
+
|
|
97
|
+
.choose-vis__heatmap-icon {
|
|
98
|
+
object-fit: contain;
|
|
99
|
+
mix-blend-mode: multiply;
|
|
100
|
+
}
|
|
95
101
|
}
|
|
96
102
|
}
|
|
97
103
|
}
|
package/src/scss/main.scss
CHANGED
|
@@ -31,7 +31,6 @@
|
|
|
31
31
|
background: #f2f2f2;
|
|
32
32
|
line-height: 3rem;
|
|
33
33
|
border-bottom: 1px solid var(--lightGray);
|
|
34
|
-
color: #333;
|
|
35
34
|
flex-grow: 1;
|
|
36
35
|
flex-shrink: 0;
|
|
37
36
|
width: 25%;
|
|
@@ -225,10 +224,6 @@
|
|
|
225
224
|
}
|
|
226
225
|
}
|
|
227
226
|
|
|
228
|
-
section.introText {
|
|
229
|
-
padding: 15px 0;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
227
|
section.footnotes {
|
|
233
228
|
border-top: 1px solid #ddd;
|
|
234
229
|
margin-top: 70px;
|