@cdc/data-bite 4.24.10 → 4.24.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.
@@ -1,6 +1,12 @@
1
1
  import React, { memo, useContext, useEffect, useState } from 'react'
2
2
 
3
- import { Accordion, AccordionItem, AccordionItemButton, AccordionItemHeading, AccordionItemPanel } from 'react-accessible-accordion'
3
+ import {
4
+ Accordion,
5
+ AccordionItem,
6
+ AccordionItemButton,
7
+ AccordionItemHeading,
8
+ AccordionItemPanel
9
+ } from 'react-accessible-accordion'
4
10
 
5
11
  import { useDebounce } from 'use-debounce'
6
12
  import Context from '../context'
@@ -12,122 +18,167 @@ import { updateFieldFactory } from '@cdc/core/helpers/updateFieldFactory'
12
18
  import { BITE_LOCATIONS, DATA_FUNCTIONS, IMAGE_POSITIONS, DATA_OPERATORS } from '../CdcDataBite'
13
19
  import Layout from '@cdc/core/components/Layout'
14
20
 
15
- const TextField = memo(({ label, section = null, subsection = null, fieldName, updateField, value: stateValue, tooltip, type = 'input', i = null, min = null, max = null, ...attributes }) => {
16
- const [value, setValue] = useState(stateValue)
17
-
18
- const [debouncedValue] = useDebounce(value, 500)
19
-
20
- useEffect(() => {
21
- if ('string' === typeof debouncedValue && stateValue !== debouncedValue) {
22
- updateField(section, subsection, fieldName, debouncedValue, i)
23
- }
24
- }, [debouncedValue, section, subsection, fieldName, i, stateValue, updateField])
21
+ const TextField = memo(
22
+ ({
23
+ label,
24
+ section = null,
25
+ subsection = null,
26
+ fieldName,
27
+ updateField,
28
+ value: stateValue,
29
+ tooltip,
30
+ type = 'input',
31
+ i = null,
32
+ min = null,
33
+ max = null,
34
+ ...attributes
35
+ }) => {
36
+ const [value, setValue] = useState(stateValue)
37
+
38
+ const [debouncedValue] = useDebounce(value, 500)
39
+
40
+ useEffect(() => {
41
+ if ('string' === typeof debouncedValue && stateValue !== debouncedValue) {
42
+ updateField(section, subsection, fieldName, debouncedValue, i)
43
+ }
44
+ }, [debouncedValue, section, subsection, fieldName, i, stateValue, updateField])
25
45
 
26
- let name = subsection ? `${section}-${subsection}-${fieldName}` : `${section}-${subsection}-${fieldName}`
46
+ let name = subsection ? `${section}-${subsection}-${fieldName}` : `${section}-${subsection}-${fieldName}`
27
47
 
28
- const onChange = e => {
29
- //TODO: This block gives a warning/error in the console, but it still works.
30
- if ('number' !== type || min === null) {
31
- setValue(e.target.value)
32
- } else {
33
- if (!e.target.value || (parseFloat(min) <= parseFloat(e.target.value)) & (parseFloat(max) >= parseFloat(e.target.value))) {
48
+ const onChange = e => {
49
+ //TODO: This block gives a warning/error in the console, but it still works.
50
+ if ('number' !== type || min === null) {
34
51
  setValue(e.target.value)
35
52
  } else {
36
- setValue(min.toString())
53
+ if (
54
+ !e.target.value ||
55
+ (parseFloat(min) <= parseFloat(e.target.value)) & (parseFloat(max) >= parseFloat(e.target.value))
56
+ ) {
57
+ setValue(e.target.value)
58
+ } else {
59
+ setValue(min.toString())
60
+ }
37
61
  }
38
62
  }
39
- }
40
-
41
- let formElement = <input type='text' name={name} onChange={onChange} {...attributes} value={value} />
42
63
 
43
- if ('textarea' === type) {
44
- formElement = <textarea name={name} onChange={onChange} {...attributes} value={value}></textarea>
45
- }
64
+ let formElement = <input type='text' name={name} onChange={onChange} {...attributes} value={value} />
46
65
 
47
- if ('number' === type) {
48
- formElement = <input type='number' name={name} onChange={onChange} {...attributes} value={value} />
49
- }
50
-
51
- return (
52
- <>
53
- {label && label.length > 0 && (
54
- <label>
55
- <span className='edit-label column-heading'>
56
- {label}
57
- {tooltip}
58
- </span>
59
- {formElement}
60
- </label>
61
- )}
62
- {(!label || label.length === 0) && formElement}
63
- </>
64
- )
65
- })
66
+ if ('textarea' === type) {
67
+ formElement = <textarea name={name} onChange={onChange} {...attributes} value={value}></textarea>
68
+ }
66
69
 
67
- const CheckBox = memo(({ label, value, fieldName, section = null, subsection = null, tooltip, updateField, ...attributes }) => (
68
- <label className='checkbox'>
69
- <input
70
- type='checkbox'
71
- name={fieldName}
72
- checked={value}
73
- onChange={() => {
74
- updateField(section, subsection, fieldName, !value)
75
- }}
76
- {...attributes}
77
- />
78
- <span className='edit-label column-heading'>{label}</span>
79
- <span className='cove-icon'>{tooltip}</span>
80
- </label>
81
- ))
82
-
83
- const Select = memo(({ label, value, options, fieldName, section = null, subsection = null, required = false, updateField, initial: initialValue, ...attributes }) => {
84
- let optionsJsx = ''
85
- if (Array.isArray(options)) {
86
- //Handle basic array
87
- optionsJsx = options.map(optionName => (
88
- <option value={optionName} key={optionName}>
89
- {optionName}
90
- </option>
91
- ))
92
- } else {
93
- //Handle object with value/name pairs
94
- optionsJsx = []
95
- for (const [optionValue, optionName] of Object.entries(options)) {
96
- optionsJsx.push(
97
- <option value={optionValue} key={optionValue}>
98
- {optionName}
99
- </option>
100
- )
70
+ if ('number' === type) {
71
+ formElement = <input type='number' name={name} onChange={onChange} {...attributes} value={value} />
101
72
  }
102
- }
103
73
 
104
- if (initialValue) {
105
- optionsJsx.unshift(
106
- <option value='' key='initial'>
107
- {initialValue}
108
- </option>
74
+ return (
75
+ <>
76
+ {label && label.length > 0 && (
77
+ <label>
78
+ <span className='edit-label column-heading'>
79
+ {label}
80
+ {tooltip}
81
+ </span>
82
+ {formElement}
83
+ </label>
84
+ )}
85
+ {(!label || label.length === 0) && formElement}
86
+ </>
109
87
  )
110
88
  }
89
+ )
111
90
 
112
- return (
113
- <label>
114
- <span className='edit-label'>{label}</span>
115
- <select
116
- className={required && !value ? 'warning' : ''}
91
+ const CheckBox = memo(
92
+ ({ label, value, fieldName, section = null, subsection = null, tooltip, updateField, ...attributes }) => (
93
+ <label className='checkbox'>
94
+ <input
95
+ type='checkbox'
117
96
  name={fieldName}
118
- value={value}
119
- onChange={event => {
120
- updateField(section, subsection, fieldName, event.target.value)
97
+ checked={value}
98
+ onChange={() => {
99
+ updateField(section, subsection, fieldName, !value)
121
100
  }}
122
101
  {...attributes}
123
- >
124
- {optionsJsx}
125
- </select>
102
+ />
103
+ <span className='edit-label column-heading'>{label}</span>
104
+ <span className='cove-icon'>{tooltip}</span>
126
105
  </label>
127
106
  )
128
- })
107
+ )
108
+
109
+ const Select = memo(
110
+ ({
111
+ label,
112
+ value,
113
+ options,
114
+ fieldName,
115
+ section = null,
116
+ subsection = null,
117
+ required = false,
118
+ updateField,
119
+ initial: initialValue,
120
+ ...attributes
121
+ }) => {
122
+ let optionsJsx = ''
123
+ if (Array.isArray(options)) {
124
+ //Handle basic array
125
+ optionsJsx = options.map(optionName => (
126
+ <option value={optionName} key={optionName}>
127
+ {optionName}
128
+ </option>
129
+ ))
130
+ } else {
131
+ //Handle object with value/name pairs
132
+ optionsJsx = []
133
+ for (const [optionValue, optionName] of Object.entries(options)) {
134
+ optionsJsx.push(
135
+ <option value={optionValue} key={optionValue}>
136
+ {optionName}
137
+ </option>
138
+ )
139
+ }
140
+ }
129
141
 
130
- const headerColors = ['theme-blue', 'theme-purple', 'theme-brown', 'theme-teal', 'theme-pink', 'theme-orange', 'theme-slate', 'theme-indigo', 'theme-cyan', 'theme-green', 'theme-amber']
142
+ if (initialValue) {
143
+ optionsJsx.unshift(
144
+ <option value='' key='initial'>
145
+ {initialValue}
146
+ </option>
147
+ )
148
+ }
149
+
150
+ return (
151
+ <label>
152
+ <span className='edit-label'>{label}</span>
153
+ <select
154
+ className={required && !value ? 'warning' : ''}
155
+ name={fieldName}
156
+ value={value}
157
+ onChange={event => {
158
+ updateField(section, subsection, fieldName, event.target.value)
159
+ }}
160
+ {...attributes}
161
+ >
162
+ {optionsJsx}
163
+ </select>
164
+ </label>
165
+ )
166
+ }
167
+ )
168
+
169
+ const headerColors = [
170
+ 'theme-blue',
171
+ 'theme-purple',
172
+ 'theme-brown',
173
+ 'theme-teal',
174
+ 'theme-pink',
175
+ 'theme-orange',
176
+ 'theme-slate',
177
+ 'theme-indigo',
178
+ 'theme-cyan',
179
+ 'theme-green',
180
+ 'theme-amber'
181
+ ]
131
182
 
132
183
  const EditorPanel = memo(() => {
133
184
  const { config, updateConfig, loading, data, setParentConfig, isDashboard, isEditor } = useContext(Context)
@@ -274,7 +325,13 @@ const EditorPanel = memo(() => {
274
325
 
275
326
  return (
276
327
  <ErrorBoundary component='EditorPanel'>
277
- <Layout.Sidebar isEditor={true} config={config} title='Configure Data Bites' onBackClick={onBackClick} displayPanel={displayPanel}>
328
+ <Layout.Sidebar
329
+ isEditor={true}
330
+ config={config}
331
+ title='Configure Data Bites'
332
+ onBackClick={onBackClick}
333
+ displayPanel={displayPanel}
334
+ >
278
335
  <section className='form-container'>
279
336
  <form>
280
337
  <Accordion allowZeroExpanded={true}>
@@ -285,8 +342,21 @@ const EditorPanel = memo(() => {
285
342
  <AccordionItemButton>General</AccordionItemButton>
286
343
  </AccordionItemHeading>
287
344
  <AccordionItemPanel>
288
- <Select value={config.biteStyle} fieldName='biteStyle' label='Data Bite Style' updateField={updateField} options={BITE_LOCATIONS} initial='Select' />
289
- <TextField value={config.title} fieldName='title' label='Title' placeholder='Data Bite Title' updateField={updateField} />
345
+ <Select
346
+ value={config.biteStyle}
347
+ fieldName='biteStyle'
348
+ label='Data Bite Style'
349
+ updateField={updateField}
350
+ options={BITE_LOCATIONS}
351
+ initial='Select'
352
+ />
353
+ <TextField
354
+ value={config.title}
355
+ fieldName='title'
356
+ label='Title'
357
+ placeholder='Data Bite Title'
358
+ updateField={updateField}
359
+ />
290
360
 
291
361
  <TextField
292
362
  type='textarea'
@@ -300,7 +370,10 @@ const EditorPanel = memo(() => {
300
370
  <Icon display='question' style={{ marginLeft: '0.5rem' }} />
301
371
  </Tooltip.Target>
302
372
  <Tooltip.Content>
303
- <p>Enter the message text for the visualization. The following HTML tags are supported: strong, em, sup, and sub.</p>
373
+ <p>
374
+ Enter the message text for the visualization. The following HTML tags are supported: strong,
375
+ em, sup, and sub.
376
+ </p>
304
377
  </Tooltip.Content>
305
378
  </Tooltip>
306
379
  }
@@ -317,7 +390,10 @@ const EditorPanel = memo(() => {
317
390
  <Icon display='question' style={{ marginLeft: '0.5rem' }} />
318
391
  </Tooltip.Target>
319
392
  <Tooltip.Content>
320
- <p>Enter supporting text to display below the data visualization, if applicable. The following HTML tags are supported: strong, em, sup, and sub.</p>
393
+ <p>
394
+ Enter supporting text to display below the data visualization, if applicable. The following
395
+ HTML tags are supported: strong, em, sup, and sub.
396
+ </p>
321
397
  </Tooltip.Content>
322
398
  </Tooltip>
323
399
  }
@@ -329,25 +405,79 @@ const EditorPanel = memo(() => {
329
405
  {' '}
330
406
  {/*Data*/}
331
407
  <AccordionItemHeading>
332
- <AccordionItemButton>Data {(!config.dataColumn || !config.dataFunction) && <WarningImage width='25' className='warning-icon' />}</AccordionItemButton>
408
+ <AccordionItemButton>
409
+ Data{' '}
410
+ {(!config.dataColumn || !config.dataFunction) && (
411
+ <WarningImage width='25' className='warning-icon' />
412
+ )}
413
+ </AccordionItemButton>
333
414
  </AccordionItemHeading>
334
415
  <AccordionItemPanel>
335
416
  <ul className='column-edit'>
336
417
  <li className='two-col'>
337
- <Select value={config.dataColumn || ''} fieldName='dataColumn' label='Data Column' updateField={updateField} initial='Select' required={true} options={getColumns()} />
338
- <Select value={config.dataFunction || ''} fieldName='dataFunction' label='Data Function' updateField={updateField} initial='Select' required={true} options={DATA_FUNCTIONS} />
418
+ <Select
419
+ value={config.dataColumn || ''}
420
+ fieldName='dataColumn'
421
+ label='Data Column'
422
+ updateField={updateField}
423
+ initial='Select'
424
+ required={true}
425
+ options={getColumns()}
426
+ />
427
+ <Select
428
+ value={config.dataFunction || ''}
429
+ fieldName='dataFunction'
430
+ label='Data Function'
431
+ updateField={updateField}
432
+ initial='Select'
433
+ required={true}
434
+ options={DATA_FUNCTIONS}
435
+ />
339
436
  </li>
340
437
  </ul>
341
438
  <span className='divider-heading'>Number Formatting</span>
342
439
  <ul className='column-edit'>
343
440
  <li className='three-col'>
344
- <TextField value={config.dataFormat.prefix} section='dataFormat' fieldName='prefix' label='Prefix' updateField={updateField} />
345
- <TextField value={config.dataFormat.suffix} section='dataFormat' fieldName='suffix' label='Suffix' updateField={updateField} />
346
- <TextField type='number' value={config.dataFormat.roundToPlace} section='dataFormat' fieldName='roundToPlace' label='Round' updateField={updateField} min='0' max='99' />
441
+ <TextField
442
+ value={config.dataFormat.prefix}
443
+ section='dataFormat'
444
+ fieldName='prefix'
445
+ label='Prefix'
446
+ updateField={updateField}
447
+ />
448
+ <TextField
449
+ value={config.dataFormat.suffix}
450
+ section='dataFormat'
451
+ fieldName='suffix'
452
+ label='Suffix'
453
+ updateField={updateField}
454
+ />
455
+ <TextField
456
+ type='number'
457
+ value={config.dataFormat.roundToPlace}
458
+ section='dataFormat'
459
+ fieldName='roundToPlace'
460
+ label='Round'
461
+ updateField={updateField}
462
+ min='0'
463
+ max='99'
464
+ />
347
465
  </li>
348
466
  </ul>
349
- <CheckBox value={config.dataFormat.commas} section='dataFormat' fieldName='commas' label='Add commas' updateField={updateField} />
350
- <CheckBox value={config.dataFormat.ignoreZeros} section='dataFormat' fieldName='ignoreZeros' label='Ignore Zeros' updateField={updateField} />
467
+ <CheckBox
468
+ value={config.dataFormat.commas}
469
+ section='dataFormat'
470
+ fieldName='commas'
471
+ label='Add commas'
472
+ updateField={updateField}
473
+ />
474
+ <CheckBox
475
+ value={config.dataFormat.ignoreZeros}
476
+ section='dataFormat'
477
+ fieldName='ignoreZeros'
478
+ label='Ignore Zeros'
479
+ updateField={updateField}
480
+ />
351
481
  <hr className='accordion__divider' />
352
482
 
353
483
  <label style={{ marginBottom: '1rem' }}>
@@ -358,7 +488,10 @@ const EditorPanel = memo(() => {
358
488
  <Icon display='question' style={{ marginLeft: '0.5rem' }} />
359
489
  </Tooltip.Target>
360
490
  <Tooltip.Content>
361
- <p>To refine the highlighted data point, specify one or more filters (e.g., "Male" and "Female" for a column called "Sex").</p>
491
+ <p>
492
+ To refine the highlighted data point, specify one or more filters (e.g., "Male" and "Female"
493
+ for a column called "Sex").
494
+ </p>
362
495
  </Tooltip.Content>
363
496
  </Tooltip>
364
497
  </span>
@@ -419,7 +552,7 @@ const EditorPanel = memo(() => {
419
552
  </fieldset>
420
553
  </div>
421
554
  )}
422
- <button type='button' onClick={addNewFilter} className='btn full-width'>
555
+ <button type='button' onClick={addNewFilter} className='btn btn-primary full-width'>
423
556
  Add Filter
424
557
  </button>
425
558
  </AccordionItemPanel>
@@ -432,14 +565,58 @@ const EditorPanel = memo(() => {
432
565
  <AccordionItemButton>Visual</AccordionItemButton>
433
566
  </AccordionItemHeading>
434
567
  <AccordionItemPanel>
435
- <TextField type='number' value={config.biteFontSize} fieldName='biteFontSize' label='Bite Font Size' updateField={updateField} min='17' max='65' />
436
- <Select value={config.fontSize} fieldName='fontSize' label='Overall Font Size' updateField={updateField} options={['small', 'medium', 'large']} />
568
+ <TextField
569
+ type='number'
570
+ value={config.biteFontSize}
571
+ fieldName='biteFontSize'
572
+ label='Bite Font Size'
573
+ updateField={updateField}
574
+ min='17'
575
+ max='65'
576
+ />
577
+ <Select
578
+ value={config.fontSize}
579
+ fieldName='fontSize'
580
+ label='Overall Font Size'
581
+ updateField={updateField}
582
+ options={['small', 'medium', 'large']}
583
+ />
437
584
  <div className='checkbox-group'>
438
- <CheckBox value={config.visual?.border} section='visual' fieldName='border' label='Display Border' updateField={updateField} />
439
- <CheckBox value={config.visual?.borderColorTheme} section='visual' fieldName='borderColorTheme' label='Use Border Color Theme' updateField={updateField} />
440
- <CheckBox value={config.visual?.accent} section='visual' fieldName='accent' label='Use Accent Style' updateField={updateField} />
441
- <CheckBox value={config.visual?.background} section='visual' fieldName='background' label='Use Theme Background Color' updateField={updateField} />
442
- <CheckBox value={config.visual?.hideBackgroundColor} section='visual' fieldName='hideBackgroundColor' label='Hide Background Color' updateField={updateField} />
585
+ <CheckBox
586
+ value={config.visual?.border}
587
+ section='visual'
588
+ fieldName='border'
589
+ label='Display Border'
590
+ updateField={updateField}
591
+ />
592
+ <CheckBox
593
+ value={config.visual?.borderColorTheme}
594
+ section='visual'
595
+ fieldName='borderColorTheme'
596
+ label='Use Border Color Theme'
597
+ updateField={updateField}
598
+ />
599
+ <CheckBox
600
+ value={config.visual?.accent}
601
+ section='visual'
602
+ fieldName='accent'
603
+ label='Use Accent Style'
604
+ updateField={updateField}
605
+ />
606
+ <CheckBox
607
+ value={config.visual?.background}
608
+ section='visual'
609
+ fieldName='background'
610
+ label='Use Theme Background Color'
611
+ updateField={updateField}
612
+ />
613
+ <CheckBox
614
+ value={config.visual?.hideBackgroundColor}
615
+ section='visual'
616
+ fieldName='hideBackgroundColor'
617
+ label='Hide Background Color'
618
+ updateField={updateField}
619
+ />
443
620
  </div>
444
621
  <label>
445
622
  <span className='edit-label'>Theme</span>
@@ -470,23 +647,63 @@ const EditorPanel = memo(() => {
470
647
  </AccordionItemButton>
471
648
  </AccordionItemHeading>
472
649
  <AccordionItemPanel>
473
- <Select value={config.imageData.display || ''} section='imageData' fieldName='display' label='Image Display Type' updateField={updateField} options={['none', 'static', 'dynamic']} />
474
- <Select value={config.bitePosition || ''} fieldName='bitePosition' label='Image/Graphic Position' updateField={updateField} initial='Select' options={IMAGE_POSITIONS} />
650
+ <Select
651
+ value={config.imageData.display || ''}
652
+ section='imageData'
653
+ fieldName='display'
654
+ label='Image Display Type'
655
+ updateField={updateField}
656
+ options={['none', 'static', 'dynamic']}
657
+ />
658
+ <Select
659
+ value={config.bitePosition || ''}
660
+ fieldName='bitePosition'
661
+ label='Image/Graphic Position'
662
+ updateField={updateField}
663
+ initial='Select'
664
+ options={IMAGE_POSITIONS}
665
+ />
475
666
  {['static'].includes(config.imageData.display) && (
476
667
  <>
477
- <TextField value={config.imageData.url} section='imageData' fieldName='url' label='Image URL' updateField={updateField} />
478
- <TextField value={config.imageData.alt} section='imageData' fieldName='alt' label='Alt Text' updateField={updateField} />
668
+ <TextField
669
+ value={config.imageData.url}
670
+ section='imageData'
671
+ fieldName='url'
672
+ label='Image URL'
673
+ updateField={updateField}
674
+ />
675
+ <TextField
676
+ value={config.imageData.alt}
677
+ section='imageData'
678
+ fieldName='alt'
679
+ label='Alt Text'
680
+ updateField={updateField}
681
+ />
479
682
  </>
480
683
  )}
481
684
 
482
685
  {['dynamic'].includes(config.imageData.display) && (
483
686
  <>
484
- <TextField value={config.imageData.url || ''} section='imageData' fieldName='url' label='Image URL (default)' updateField={updateField} />
485
- <TextField value={config.imageData.alt} section='imageData' fieldName='alt' label='Alt Text (default)' updateField={updateField} />
687
+ <TextField
688
+ value={config.imageData.url || ''}
689
+ section='imageData'
690
+ fieldName='url'
691
+ label='Image URL (default)'
692
+ updateField={updateField}
693
+ />
694
+ <TextField
695
+ value={config.imageData.alt}
696
+ section='imageData'
697
+ fieldName='alt'
698
+ label='Alt Text (default)'
699
+ updateField={updateField}
700
+ />
486
701
 
487
702
  <hr className='accordion__divider' />
488
703
 
489
- {(!config.imageData.options || config.imageData.options.length === 0) && <p style={{ textAlign: 'center' }}>There are currently no dynamic images.</p>}
704
+ {(!config.imageData.options || config.imageData.options.length === 0) && (
705
+ <p style={{ textAlign: 'center' }}>There are currently no dynamic images.</p>
706
+ )}
490
707
  {config.imageData.options && config.imageData.options.length > 0 && (
491
708
  <>
492
709
  <ul>
@@ -621,7 +838,7 @@ const EditorPanel = memo(() => {
621
838
  </ul>
622
839
  </>
623
840
  )}
624
- <button type='button' onClick={addDynamicImage} className='btn full-width'>
841
+ <button type='button' onClick={addDynamicImage} className='btn btn-primary full-width'>
625
842
  Add Dynamic Image
626
843
  </button>
627
844
  </>
@@ -418,9 +418,6 @@
418
418
  font-weight: normal;
419
419
  }
420
420
 
421
- .btn {
422
- margin-top: 1em;
423
- }
424
421
  .sort-list {
425
422
  list-style: none;
426
423
  > li {
@@ -26,22 +26,6 @@
26
26
  }
27
27
  }
28
28
 
29
- .btn {
30
- background: #005eaa;
31
- color: #fff;
32
- border: 0;
33
- padding: 0.4em 0.8em;
34
- display: block;
35
- border-radius: 5px;
36
- transition: 0.1s all;
37
- cursor: pointer;
38
-
39
- &[disabled] {
40
- opacity: 0.5;
41
- z-index: -1;
42
- position: relative;
43
- }
44
- }
45
29
 
46
30
  .warning-icon {
47
31
  position: relative;