@cdc/core 1.1.1 → 1.1.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.
- package/assets/alabama-graphic.svg +23 -0
- package/assets/check.svg +3 -0
- package/assets/dashboard.svg +11 -0
- package/assets/file-upload-solid.svg +1 -0
- package/assets/horizontal-stacked-bar.svg +1 -0
- package/assets/icon-code.svg +3 -0
- package/assets/icon-grid.svg +4 -0
- package/assets/icon-info.svg +3 -0
- package/assets/icon-warning.svg +3 -0
- package/assets/link.svg +1 -0
- package/assets/map-folded.svg +1 -0
- package/assets/paired-bar.svg +11 -0
- package/assets/upload-solid.svg +1 -0
- package/assets/usa-region-graphic.svg +393 -0
- package/components/AdvancedEditor.js +74 -0
- package/components/GlobalContext.jsx +41 -0
- package/components/Loading.js +3 -2
- package/components/elements/Button.jsx +12 -0
- package/components/inputs/InputCheckbox.jsx +59 -0
- package/components/inputs/InputSelect.jsx +49 -0
- package/components/inputs/InputText.jsx +68 -0
- package/components/inputs/InputToggle.jsx +95 -0
- package/components/ui/Accordion.jsx +64 -0
- package/components/ui/Icon.jsx +63 -0
- package/components/ui/Modal.jsx +87 -0
- package/components/ui/Overlay.jsx +84 -0
- package/components/ui/OverlayFrame.jsx +16 -0
- package/components/ui/Tooltip.jsx +55 -0
- package/data/colorPalettes.js +240 -0
- package/helpers/events.js +15 -0
- package/helpers/numberFromString.js +4 -3
- package/helpers/updatePaletteNames.js +18 -0
- package/helpers/validateFipsCodeLength.js +67 -0
- package/package.json +16 -2
- package/styles/_data-table.scss +8 -2
- package/styles/_global.scss +5 -3
- package/styles/v2/base/_general.scss +46 -0
- package/styles/v2/base/_reset.scss +81 -0
- package/styles/v2/base/_typography.scss +0 -0
- package/styles/v2/base/index.scss +3 -0
- package/styles/v2/components/accordion.scss +156 -0
- package/styles/v2/components/button.scss +178 -0
- package/styles/v2/components/editor.scss +487 -0
- package/styles/v2/components/icon.scss +23 -0
- package/styles/v2/components/input.scss +372 -0
- package/styles/v2/components/modal.scss +64 -0
- package/styles/v2/components/overlay.scss +80 -0
- package/styles/v2/layout/_alert.scss +36 -0
- package/styles/v2/layout/_component.scss +31 -0
- package/styles/v2/layout/_data-table.scss +278 -0
- package/styles/v2/layout/_header.scss +13 -0
- package/styles/v2/layout/_link.scss +46 -0
- package/styles/v2/layout/_progression.scss +80 -0
- package/styles/v2/layout/_tooltip.scss +132 -0
- package/styles/v2/layout/index.scss +7 -0
- package/styles/v2/main.scss +15 -0
- package/styles/v2/themes/_color-definitions.scss +129 -0
- package/styles/v2/themes/index.scss +1 -0
- package/styles/v2/utils/_animations.scss +63 -0
- package/styles/v2/utils/_functions.scss +0 -0
- package/styles/v2/utils/_mixins.scss +29 -0
- package/styles/v2/utils/_variables.scss +2 -0
- package/styles/v2/utils/index.scss +4 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React, { createContext, useContext, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
export const GlobalContext = createContext({})
|
|
4
|
+
export const useGlobalContext = () => useContext(GlobalContext)
|
|
5
|
+
|
|
6
|
+
export const GlobalContextProvider = ({ children }) => {
|
|
7
|
+
const [ globalContextData, setGlobalContextData ] = useState({})
|
|
8
|
+
|
|
9
|
+
const openOverlay = (obj, disableBgClose = false) => {
|
|
10
|
+
let payload = { object: obj, show: true, disableBgClose: disableBgClose }
|
|
11
|
+
setGlobalContextData(context => ({ ...context, overlay: { ...payload } }))
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const toggleOverlay = (display = false) => {
|
|
15
|
+
setGlobalContextData(context => ({
|
|
16
|
+
...context,
|
|
17
|
+
overlay: {
|
|
18
|
+
...context.overlay,
|
|
19
|
+
show: display
|
|
20
|
+
}
|
|
21
|
+
}))
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const globalSettings = {
|
|
25
|
+
overlay: {
|
|
26
|
+
object: globalContextData.overlay?.object || null,
|
|
27
|
+
show: globalContextData.overlay?.show || false,
|
|
28
|
+
disableBgClose: globalContextData.overlay?.disableBgClose || false,
|
|
29
|
+
actions: {
|
|
30
|
+
openOverlay,
|
|
31
|
+
toggleOverlay
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<GlobalContext.Provider value={globalSettings}>
|
|
38
|
+
{children}
|
|
39
|
+
</GlobalContext.Provider>
|
|
40
|
+
)
|
|
41
|
+
}
|
package/components/Loading.js
CHANGED
|
@@ -3,7 +3,8 @@ import '../styles/loading.scss';
|
|
|
3
3
|
|
|
4
4
|
export default function Loading({viewport = "lg"}) {
|
|
5
5
|
return (
|
|
6
|
-
|
|
6
|
+
<section className="loading" aria-live="assertive">
|
|
7
|
+
<span className="sr-only" style={{display: 'none'}}>Content is loading.</span>
|
|
7
8
|
<div className={`la-ball-beat la-dark ${viewport}`}>
|
|
8
9
|
<div />
|
|
9
10
|
<div />
|
|
@@ -11,4 +12,4 @@ export default function Loading({viewport = "lg"}) {
|
|
|
11
12
|
</div>
|
|
12
13
|
</section>
|
|
13
14
|
)
|
|
14
|
-
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
import '../../styles/v2/components/button.scss'
|
|
4
|
+
|
|
5
|
+
const Button = ({fluid, children, onClick, className, ...attributes}) => {
|
|
6
|
+
|
|
7
|
+
return (
|
|
8
|
+
<button className={`cove-button${fluid ? ' fluid' : ''}${className ? className : ''}`} {...attributes} onClick={(e) => {e.preventDefault(); onClick();}}>{children}</button>
|
|
9
|
+
)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default Button
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import React, { useState, useEffect, memo } from 'react'
|
|
2
|
+
|
|
3
|
+
import Check from '../../assets/check.svg'
|
|
4
|
+
import '../../styles/v2/components/input.scss'
|
|
5
|
+
|
|
6
|
+
const InputCheckbox = memo((
|
|
7
|
+
{
|
|
8
|
+
label,
|
|
9
|
+
size = 'medium',
|
|
10
|
+
activeColor = null,
|
|
11
|
+
activeCheckColor = null,
|
|
12
|
+
section = null,
|
|
13
|
+
subsection = null,
|
|
14
|
+
fieldName,
|
|
15
|
+
updateField,
|
|
16
|
+
value: stateValue,
|
|
17
|
+
|
|
18
|
+
i = null, min = null, max = null,
|
|
19
|
+
...attributes
|
|
20
|
+
}
|
|
21
|
+
) => {
|
|
22
|
+
|
|
23
|
+
const [ value, setValue ] = useState(stateValue)
|
|
24
|
+
|
|
25
|
+
let name = subsection ? `${section}-${subsection}-${fieldName}` : `${section}-${subsection}-${fieldName}`
|
|
26
|
+
if(fieldName === 'border') {
|
|
27
|
+
console.table({fieldName, value, stateValue})
|
|
28
|
+
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (stateValue !== undefined && stateValue !== value) {
|
|
33
|
+
setValue(stateValue)
|
|
34
|
+
}
|
|
35
|
+
}, [ stateValue ])
|
|
36
|
+
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
if (stateValue !== undefined && stateValue !== value && updateField) {
|
|
39
|
+
updateField(section, subsection, fieldName, value, i)
|
|
40
|
+
}
|
|
41
|
+
}, [ value ])
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<div className="input-group">
|
|
45
|
+
{label && <label>{label}</label>}
|
|
46
|
+
<div
|
|
47
|
+
className={'cove-input__checkbox' + (size === 'small' ? '--small' : size === 'large' ? '--large' : '') + (value ? ' active' : '')}
|
|
48
|
+
onClick={() => setValue(!value)}>
|
|
49
|
+
<div className={`cove-input__checkbox-box${activeColor ? ' custom-color' : ''}`}
|
|
50
|
+
style={value && activeColor ? { backgroundColor: activeColor } : null}>
|
|
51
|
+
<Check className="cove-input__checkbox-check" style={{fill: activeCheckColor || '#025eaa'}}/>
|
|
52
|
+
</div>
|
|
53
|
+
<input className="cove-input--hidden" type="checkbox" name={name} checked={value || false} readOnly/>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
export default InputCheckbox
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import React, { memo } from 'react'
|
|
2
|
+
|
|
3
|
+
import '../../styles/v2/components/input.scss'
|
|
4
|
+
|
|
5
|
+
const InputSelect = memo((
|
|
6
|
+
{
|
|
7
|
+
label,
|
|
8
|
+
value,
|
|
9
|
+
options,
|
|
10
|
+
fieldName,
|
|
11
|
+
section = null,
|
|
12
|
+
subsection = null,
|
|
13
|
+
required = false,
|
|
14
|
+
updateField,
|
|
15
|
+
initial: initialValue,
|
|
16
|
+
...attributes
|
|
17
|
+
}
|
|
18
|
+
) => {
|
|
19
|
+
|
|
20
|
+
let optionsJsx = ''
|
|
21
|
+
|
|
22
|
+
if (Array.isArray(options)) {
|
|
23
|
+
//Handle basic array
|
|
24
|
+
optionsJsx = options.map(optionName => <option value={optionName} key={optionName}>{optionName}</option>)
|
|
25
|
+
} else {
|
|
26
|
+
//Handle object with value/name pairs
|
|
27
|
+
optionsJsx = []
|
|
28
|
+
for (const [ optionValue, optionName ] of Object.entries(options)) {
|
|
29
|
+
optionsJsx.push(<option value={optionValue} key={optionValue}>{optionName}</option>)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (initialValue) {
|
|
34
|
+
optionsJsx.unshift(<option value="" key="initial">{initialValue}</option>)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<label>
|
|
39
|
+
{label && <span className="edit-label">{label}</span>}
|
|
40
|
+
<select className={required && !value ? 'warning' : ''} name={fieldName} value={value} onChange={(event) => {
|
|
41
|
+
updateField(section, subsection, fieldName, event.target.value)
|
|
42
|
+
}} {...attributes}>
|
|
43
|
+
{optionsJsx}
|
|
44
|
+
</select>
|
|
45
|
+
</label>
|
|
46
|
+
)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
export default InputSelect
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import React, { useState, useEffect, memo } from 'react'
|
|
2
|
+
import { useDebounce } from 'use-debounce'
|
|
3
|
+
|
|
4
|
+
import '../../styles/v2/components/input.scss'
|
|
5
|
+
|
|
6
|
+
const InputText = memo((
|
|
7
|
+
{
|
|
8
|
+
label,
|
|
9
|
+
section = null,
|
|
10
|
+
subsection = null,
|
|
11
|
+
fieldName,
|
|
12
|
+
updateField,
|
|
13
|
+
value: stateValue,
|
|
14
|
+
type = 'input',
|
|
15
|
+
tooltip,
|
|
16
|
+
i = null, min = null, max = null,
|
|
17
|
+
...attributes
|
|
18
|
+
}
|
|
19
|
+
) => {
|
|
20
|
+
|
|
21
|
+
const [ value, setValue ] = useState(stateValue)
|
|
22
|
+
const [ debouncedValue ] = useDebounce(value, 500)
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if ('string' === typeof debouncedValue && stateValue !== debouncedValue && updateField) {
|
|
26
|
+
updateField(section, subsection, fieldName, debouncedValue, i)
|
|
27
|
+
}
|
|
28
|
+
}, [ debouncedValue, section, subsection, fieldName, i, stateValue, updateField ])
|
|
29
|
+
|
|
30
|
+
let name = subsection ? `${section}-${subsection}-${fieldName}` : `${section}-${subsection}-${fieldName}`
|
|
31
|
+
|
|
32
|
+
const onChange = (e) => {
|
|
33
|
+
if ('number' !== type || min === null) {
|
|
34
|
+
setValue(e.target.value)
|
|
35
|
+
} else {
|
|
36
|
+
if (!e.target.value || (parseFloat(min) <= parseFloat(e.target.value) & parseFloat(max) >= parseFloat(e.target.value))) {
|
|
37
|
+
setValue(e.target.value)
|
|
38
|
+
} else {
|
|
39
|
+
setValue(min.toString())
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let formElement = <input type="text" name={name} onChange={onChange} {...attributes} value={value}/>
|
|
45
|
+
|
|
46
|
+
if ('textarea' === type) {
|
|
47
|
+
formElement = (
|
|
48
|
+
<textarea name={name} onChange={onChange} {...attributes} value={value}/>
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if ('number' === type) {
|
|
53
|
+
formElement = <input type="number" name={name} onChange={onChange} {...attributes} value={value}/>
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<div className="cove-input-group">
|
|
58
|
+
{label &&
|
|
59
|
+
<>
|
|
60
|
+
<label>{label}{tooltip}</label>
|
|
61
|
+
</>
|
|
62
|
+
}
|
|
63
|
+
{formElement}
|
|
64
|
+
</div>
|
|
65
|
+
)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
export default InputText
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
|
|
4
|
+
import '../../styles/v2/components/input.scss'
|
|
5
|
+
|
|
6
|
+
const InputToggle = (
|
|
7
|
+
{
|
|
8
|
+
label,
|
|
9
|
+
toggleType = 'flat',
|
|
10
|
+
size = 'medium',
|
|
11
|
+
activeColor = null,
|
|
12
|
+
section = null,
|
|
13
|
+
subsection = null,
|
|
14
|
+
fieldName,
|
|
15
|
+
updateField,
|
|
16
|
+
value: stateValue,
|
|
17
|
+
|
|
18
|
+
i = null, min = null, max = null,
|
|
19
|
+
...attributes
|
|
20
|
+
}
|
|
21
|
+
) => {
|
|
22
|
+
|
|
23
|
+
const [ value, setValue ] = useState(stateValue)
|
|
24
|
+
|
|
25
|
+
let name = () => {
|
|
26
|
+
let str = ''
|
|
27
|
+
if (section)
|
|
28
|
+
str += section + '-'
|
|
29
|
+
if (subsection)
|
|
30
|
+
str += subsection + '-'
|
|
31
|
+
if (fieldName)
|
|
32
|
+
str += fieldName
|
|
33
|
+
return str
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const toggleTypeClass = () => {
|
|
37
|
+
const typeArr = {
|
|
38
|
+
'flat': ' toggle--flat',
|
|
39
|
+
'block': ' toggle--block',
|
|
40
|
+
'pill': ' toggle--pill',
|
|
41
|
+
'3d': ' toggle--3d'
|
|
42
|
+
}
|
|
43
|
+
return typeArr[toggleType] || ''
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (stateValue !== undefined && stateValue !== value) {
|
|
48
|
+
setValue(stateValue)
|
|
49
|
+
}
|
|
50
|
+
}, [ stateValue ])
|
|
51
|
+
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (stateValue !== undefined && stateValue !== value && updateField) {
|
|
54
|
+
updateField(section, subsection, fieldName, value, i)
|
|
55
|
+
}
|
|
56
|
+
}, [ value ])
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<div className="input-group">
|
|
60
|
+
{label && <label>{label}</label>}
|
|
61
|
+
<div className={'cove-input__toggle' + (size === 'small' ? '--small' : size === 'large' ? '--large' : '') + (toggleTypeClass()) + (value ? ' active' : '')} onClick={() => setValue(!value)}>
|
|
62
|
+
<div className="cove-input__toggle-button"/>
|
|
63
|
+
<div className="cove-input__toggle-track" style={value && activeColor ? { backgroundColor: activeColor } : null }/>
|
|
64
|
+
<input className="cove-input--hidden" type="checkbox" name={name()} checked={value || false} readOnly/>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
InputToggle.propTypes = {
|
|
71
|
+
/** Add label to the input field */
|
|
72
|
+
label: PropTypes.string,
|
|
73
|
+
/** Select the preferred display style of the toggle slider */
|
|
74
|
+
toggleType: PropTypes.oneOf(['flat', 'block', 'pill', '3d']),
|
|
75
|
+
/** Select the preferred size of the toggle slider */
|
|
76
|
+
size: PropTypes.oneOf(['small', 'medium', 'large']),
|
|
77
|
+
/** Select the preferred color for the toggle slider when active */
|
|
78
|
+
activeColor: PropTypes.string,
|
|
79
|
+
/** Parent key value of nested target config option
|
|
80
|
+
*
|
|
81
|
+
* (optional)*/
|
|
82
|
+
section: PropTypes.string,
|
|
83
|
+
/** Child key value of nested target config option
|
|
84
|
+
*
|
|
85
|
+
* (optional, requires `section` value)*/
|
|
86
|
+
subsection: PropTypes.string,
|
|
87
|
+
/** Key value of targeted config option */
|
|
88
|
+
fieldName: PropTypes.string,
|
|
89
|
+
/** Prop drill down of the updateField function */
|
|
90
|
+
updateField: PropTypes.func,
|
|
91
|
+
/** Current value of the input, usually the current config option value */
|
|
92
|
+
stateValue: PropTypes.object
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export default InputToggle
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import React, { Children } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
Accordion as AccordionComponent,
|
|
4
|
+
AccordionItem,
|
|
5
|
+
AccordionItemHeading,
|
|
6
|
+
AccordionItemPanel,
|
|
7
|
+
AccordionItemButton,
|
|
8
|
+
} from 'react-accessible-accordion'
|
|
9
|
+
import PropTypes from 'prop-types'
|
|
10
|
+
|
|
11
|
+
import Icon from './Icon'
|
|
12
|
+
import Tooltip from './Tooltip'
|
|
13
|
+
|
|
14
|
+
import '../../styles/v2/components/accordion.scss'
|
|
15
|
+
|
|
16
|
+
//Define the "slots" to be populated by subcomponents
|
|
17
|
+
const AccordionSection = () => null
|
|
18
|
+
|
|
19
|
+
const Accordion = ({children}) => {
|
|
20
|
+
const childNodes = Children.toArray(children)
|
|
21
|
+
const accordionSections = childNodes.filter(child => child?.type === AccordionSection)
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<AccordionComponent allowZeroExpanded={true}>
|
|
25
|
+
{accordionSections && accordionSections.map((section, index) => (
|
|
26
|
+
<AccordionItem className="cove-accordion__item" key={index}>
|
|
27
|
+
<AccordionItemHeading className="cove-accordion__heading">
|
|
28
|
+
<AccordionItemButton className="cove-accordion__button">
|
|
29
|
+
{section.props.title}
|
|
30
|
+
{section.props.tooltipText
|
|
31
|
+
? (
|
|
32
|
+
<Tooltip position="bottom">
|
|
33
|
+
<Tooltip.Target>
|
|
34
|
+
<Icon display="question" size={14}/>
|
|
35
|
+
</Tooltip.Target>
|
|
36
|
+
<Tooltip.Content>
|
|
37
|
+
{section.props.tooltipText}
|
|
38
|
+
</Tooltip.Content>
|
|
39
|
+
</Tooltip>
|
|
40
|
+
)
|
|
41
|
+
: null
|
|
42
|
+
}
|
|
43
|
+
</AccordionItemButton>
|
|
44
|
+
</AccordionItemHeading>
|
|
45
|
+
<AccordionItemPanel className="cove-accordion__panel">
|
|
46
|
+
{section.props.children}
|
|
47
|
+
</AccordionItemPanel>
|
|
48
|
+
</AccordionItem>
|
|
49
|
+
))}
|
|
50
|
+
</AccordionComponent>
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
//Create subcomponents as "slots" within component namespace
|
|
55
|
+
Accordion.Section = AccordionSection
|
|
56
|
+
|
|
57
|
+
Accordion.Section.propTypes = {
|
|
58
|
+
/* Title for the accordion label*/
|
|
59
|
+
title: PropTypes.string,
|
|
60
|
+
/* Tooltip for the accordion label*/
|
|
61
|
+
tooltipText: PropTypes.object
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export default Accordion
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
|
|
4
|
+
import iconClose from '../../assets/icon-close.svg'
|
|
5
|
+
import iconQuestion from '../../assets/question-circle.svg'
|
|
6
|
+
import iconLink from '../../assets/link.svg'
|
|
7
|
+
import iconUpload from '../../assets/upload-solid.svg'
|
|
8
|
+
import iconFileUpload from '../../assets/file-upload-solid.svg'
|
|
9
|
+
import iconWarning from '../../assets/icon-warning.svg'
|
|
10
|
+
import iconInfo from '../../assets/icon-info.svg'
|
|
11
|
+
|
|
12
|
+
import '../../styles/v2/components/icon.scss'
|
|
13
|
+
|
|
14
|
+
const iconHash = {
|
|
15
|
+
"close": iconClose,
|
|
16
|
+
"question": iconQuestion,
|
|
17
|
+
"link": iconLink,
|
|
18
|
+
"upload": iconUpload,
|
|
19
|
+
"fileUpload": iconFileUpload,
|
|
20
|
+
"warning": iconWarning,
|
|
21
|
+
"info": iconInfo
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const Icon = ({ display = null, base, alt = '', size, color, style, ...attributes }) => {
|
|
25
|
+
const IconObj = iconHash[display] || null
|
|
26
|
+
|
|
27
|
+
const filteredAttrs = { ...attributes }
|
|
28
|
+
delete filteredAttrs.className
|
|
29
|
+
|
|
30
|
+
const styles = {
|
|
31
|
+
...style,
|
|
32
|
+
color: color ? color : null,
|
|
33
|
+
width: size ? size + 'px' : null
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<>
|
|
38
|
+
{base
|
|
39
|
+
? <IconObj title={alt}/>
|
|
40
|
+
: (
|
|
41
|
+
<span className={`cove-icon${attributes.className ? ' ' + attributes.className : ''}`} style={styles} {...filteredAttrs}>
|
|
42
|
+
<IconObj title={alt}/>
|
|
43
|
+
</span>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
</>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
Icon.propTypes = {
|
|
51
|
+
/* Define the icon to display */
|
|
52
|
+
display: PropTypes.oneOf(Object.keys(iconHash)),
|
|
53
|
+
/* Returns icon data as plain svg */
|
|
54
|
+
base: PropTypes.bool,
|
|
55
|
+
/* Sets alt text for the icon */
|
|
56
|
+
alt: PropTypes.string,
|
|
57
|
+
/* Override the width of the icon (scales height proportionally)*/
|
|
58
|
+
size: PropTypes.number,
|
|
59
|
+
/* Override the color of the icon */
|
|
60
|
+
color: PropTypes.string
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export default Icon
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import React, { Children } from 'react'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
|
|
4
|
+
import { useGlobalContext } from '../GlobalContext'
|
|
5
|
+
import Icon from './Icon'
|
|
6
|
+
|
|
7
|
+
import '../../styles/v2/components/modal.scss'
|
|
8
|
+
|
|
9
|
+
//Define the "slots" to be populated by subcomponents
|
|
10
|
+
const ModalHeader = () => null
|
|
11
|
+
const ModalContent = () => null
|
|
12
|
+
const ModalFooter = () => null
|
|
13
|
+
|
|
14
|
+
const Modal = ({
|
|
15
|
+
fontTheme = 'dark',
|
|
16
|
+
headerBgColor = '#fff',
|
|
17
|
+
showDividerTop = true,
|
|
18
|
+
showDividerBottom = true,
|
|
19
|
+
showClose = true,
|
|
20
|
+
children,
|
|
21
|
+
override = null
|
|
22
|
+
}) => {
|
|
23
|
+
|
|
24
|
+
//Access global overlay state
|
|
25
|
+
let { overlay } = useGlobalContext()
|
|
26
|
+
|
|
27
|
+
//Parse, organize, and pull "slotted" children data from subcomponents
|
|
28
|
+
const childNodes = Children.toArray(children)
|
|
29
|
+
const modalHeaderChildren = childNodes.find(child => child?.type === ModalHeader)
|
|
30
|
+
const modalContentChildren = childNodes.find(child => child?.type === ModalContent)
|
|
31
|
+
const modalFooterChildren = childNodes.find(child => child?.type === ModalFooter)
|
|
32
|
+
|
|
33
|
+
//Modal computed style options
|
|
34
|
+
const dividerBorder = (bool) => {
|
|
35
|
+
return !bool ? 'none' : null
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
//Render output
|
|
39
|
+
return (
|
|
40
|
+
<div className={`cove-modal cove-modal__theme--${fontTheme}`}>
|
|
41
|
+
{(showClose || modalHeaderChildren) &&
|
|
42
|
+
<div className="cove-modal__header" style={{
|
|
43
|
+
backgroundColor: headerBgColor,
|
|
44
|
+
boxShadow: dividerBorder(modalHeaderChildren && showDividerTop),
|
|
45
|
+
padding: !modalHeaderChildren ? '0' : null
|
|
46
|
+
}}>
|
|
47
|
+
{modalHeaderChildren && modalHeaderChildren.props.children}
|
|
48
|
+
{showClose &&
|
|
49
|
+
<button className="cove-modal--close"
|
|
50
|
+
onClick={(e) => override ? override.actions.toggleOverlay(false) : overlay ? overlay?.actions.toggleOverlay(false) : e.preventDefault()}>
|
|
51
|
+
<Icon display="close"/>
|
|
52
|
+
</button>
|
|
53
|
+
}
|
|
54
|
+
</div>
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
<div className="cove-modal__content">
|
|
58
|
+
{modalContentChildren && modalContentChildren.props.children}
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
{modalFooterChildren &&
|
|
62
|
+
<div className="cove-modal__footer" style={{
|
|
63
|
+
boxShadow: dividerBorder(showDividerBottom),
|
|
64
|
+
paddingTop: showDividerBottom ? '1rem' : null
|
|
65
|
+
}}>
|
|
66
|
+
{modalFooterChildren.props.children}
|
|
67
|
+
</div>
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
</div>
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
//Create subcomponents as "slots" within component namespace
|
|
75
|
+
Modal.Header = ModalHeader
|
|
76
|
+
Modal.Content = ModalContent
|
|
77
|
+
Modal.Footer = ModalFooter
|
|
78
|
+
|
|
79
|
+
Modal.propTypes = {
|
|
80
|
+
fontTheme: PropTypes.oneOf([ 'dark', 'light' ]),
|
|
81
|
+
headerBgColor: PropTypes.string,
|
|
82
|
+
showDividerTop: PropTypes.bool,
|
|
83
|
+
showDividerBottom: PropTypes.bool,
|
|
84
|
+
showClose: PropTypes.bool
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export default Modal
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
import { useGlobalContext } from '../GlobalContext'
|
|
4
|
+
|
|
5
|
+
import '../../styles/v2/components/overlay.scss'
|
|
6
|
+
|
|
7
|
+
const Overlay = ({ disableBgClose, children, override = null }) => {
|
|
8
|
+
//Access global modal state
|
|
9
|
+
let { overlay } = useGlobalContext()
|
|
10
|
+
|
|
11
|
+
//Set local states
|
|
12
|
+
const [ displayOverlay, setDisplayOverlay ] = useState(false)
|
|
13
|
+
const [ overlayAnimationState, setOverlayAnimationState ] = useState(null)
|
|
14
|
+
const [ overlayErrorState, setOverlayErrorState ] = useState(false)
|
|
15
|
+
|
|
16
|
+
const overlayDisplay = overlay?.show || override?.show
|
|
17
|
+
|
|
18
|
+
//Animate In effect
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
if (overlayDisplay === false) return //Reject
|
|
21
|
+
|
|
22
|
+
document.body.style.overflow = 'hidden'
|
|
23
|
+
setDisplayOverlay(true)
|
|
24
|
+
setOverlayAnimationState('animate-in')
|
|
25
|
+
|
|
26
|
+
const timeoutShow = setTimeout(() => {
|
|
27
|
+
setOverlayAnimationState('show')
|
|
28
|
+
}, 750)
|
|
29
|
+
|
|
30
|
+
return () => clearTimeout(timeoutShow)
|
|
31
|
+
}, [ overlayDisplay ])
|
|
32
|
+
|
|
33
|
+
//Animate Out effect
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (overlayDisplay === true) return //Reject
|
|
36
|
+
|
|
37
|
+
setOverlayAnimationState('animate-out')
|
|
38
|
+
|
|
39
|
+
const timeoutHide = setTimeout(() => {
|
|
40
|
+
setOverlayAnimationState(null)
|
|
41
|
+
setDisplayOverlay(false)
|
|
42
|
+
document.body.style.overflow = null
|
|
43
|
+
}, 400)
|
|
44
|
+
|
|
45
|
+
return () => clearTimeout(timeoutHide)
|
|
46
|
+
}, [ overlayDisplay ])
|
|
47
|
+
|
|
48
|
+
//Error animate effect
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
if (overlayErrorState === false) return //Reject
|
|
51
|
+
|
|
52
|
+
const timeoutHide = setTimeout(() => {
|
|
53
|
+
setOverlayErrorState(false)
|
|
54
|
+
}, 400)
|
|
55
|
+
|
|
56
|
+
return () => clearTimeout(timeoutHide)
|
|
57
|
+
}, [ overlayErrorState ])
|
|
58
|
+
|
|
59
|
+
//Render output
|
|
60
|
+
return (
|
|
61
|
+
<>
|
|
62
|
+
{displayOverlay &&
|
|
63
|
+
<div
|
|
64
|
+
className={`cove-overlay${overlayAnimationState ? (' ' + overlayAnimationState) : ''}${overlayErrorState ? ' overlay-error' : ''}`}>
|
|
65
|
+
<div className="cove-overlay__bg" style={{ cursor: disableBgClose ? 'default' : null }}
|
|
66
|
+
onClick={(e) =>
|
|
67
|
+
disableBgClose ? setOverlayErrorState(true) :
|
|
68
|
+
overlay ? overlay.actions.toggleOverlay(false) :
|
|
69
|
+
override ? override.actions.toggleOverlay(false) :
|
|
70
|
+
e.preventDefault()
|
|
71
|
+
}
|
|
72
|
+
role="alert"
|
|
73
|
+
/>
|
|
74
|
+
<div className="cove-overlay__wrapper">
|
|
75
|
+
<div className="cove-overlay__container">
|
|
76
|
+
{children}
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
}
|
|
81
|
+
</>
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
export default Overlay
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
import { useGlobalContext } from '../GlobalContext'
|
|
4
|
+
|
|
5
|
+
import Overlay from './Overlay'
|
|
6
|
+
|
|
7
|
+
const OverlayFrame = () => {
|
|
8
|
+
const { overlay } = useGlobalContext()
|
|
9
|
+
return (
|
|
10
|
+
<Overlay disableBgClose={overlay.disableBgClose}>
|
|
11
|
+
{ overlay.object }
|
|
12
|
+
</Overlay>
|
|
13
|
+
)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default OverlayFrame
|