genesys-react-components 0.1.6 → 0.1.8-devengage-1376.128
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/README.md +4 -0
- package/build/index.d.ts +1 -2
- package/build/index.js +6 -6
- package/build/index.js.map +1 -1
- package/package.json +49 -47
- package/src/alertblock/AlertBlock.scss +108 -0
- package/src/alertblock/AlertBlock.tsx +80 -0
- package/src/copybutton/CopyButton.scss +8 -0
- package/src/copybutton/CopyButton.tsx +42 -0
- package/src/dxaccordion/DxAccordion.scss +41 -0
- package/src/dxaccordion/DxAccordion.tsx +49 -0
- package/src/dxaccordion/DxAccordionGroup.scss +3 -0
- package/src/dxaccordion/DxAccordionGroup.tsx +12 -0
- package/src/dxbutton/DxButton.scss +79 -0
- package/src/dxbutton/DxButton.tsx +31 -0
- package/src/dxitemgroup/DxCheckbox.scss +145 -0
- package/src/dxitemgroup/DxCheckbox.tsx +46 -0
- package/src/dxitemgroup/DxItemGroup.scss +6 -0
- package/src/dxitemgroup/DxItemGroup.tsx +133 -0
- package/src/dxitemgroup/dropdown.scss +53 -0
- package/src/dxitemgroup/multiselect.scss +74 -0
- package/src/dxitemgroup/radiobutton.scss +2 -0
- package/src/dxlabel/DxLabel.scss +31 -0
- package/src/dxlabel/DxLabel.tsx +39 -0
- package/src/dxtabbedcontent/DxTabPanel.scss +0 -0
- package/src/dxtabbedcontent/DxTabPanel.tsx +8 -0
- package/src/dxtabbedcontent/DxTabbedContent.scss +45 -0
- package/src/dxtabbedcontent/DxTabbedContent.tsx +28 -0
- package/src/dxtextbox/DxTextbox.scss +107 -0
- package/src/dxtextbox/DxTextbox.tsx +159 -0
- package/src/dxtoggle/DxToggle.scss +76 -0
- package/src/dxtoggle/DxToggle.tsx +51 -0
- package/src/index.ts +141 -0
- package/src/loadingplaceholder/LoadingPlaceholder.scss +58 -0
- package/src/loadingplaceholder/LoadingPlaceholder.tsx +17 -0
- package/src/tooltip/Tooltip.scss +108 -0
- package/src/tooltip/Tooltip.tsx +46 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { GenesysDevIcon, GenesysDevIcons } from 'genesys-dev-icons';
|
|
2
|
+
import React, { useState } from 'react';
|
|
3
|
+
import Tooltip from '../tooltip/Tooltip';
|
|
4
|
+
|
|
5
|
+
import './CopyButton.scss';
|
|
6
|
+
|
|
7
|
+
interface IProps {
|
|
8
|
+
copyText: string;
|
|
9
|
+
className?: string;
|
|
10
|
+
tooltipPosition?: 'top' | 'right' | 'bottom' | 'left';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default function CopyButton(props: IProps) {
|
|
14
|
+
let [copyState, setCopyState] = useState(false);
|
|
15
|
+
|
|
16
|
+
// Copy function will set the component state to indicate we have copied the record and then show the tool tip. With the copyState set to true we will see 'Copied'
|
|
17
|
+
const copyCode = (e: React.MouseEvent<HTMLElement>) => {
|
|
18
|
+
e.stopPropagation();
|
|
19
|
+
setCopyState(true);
|
|
20
|
+
navigator.clipboard.writeText(props.copyText);
|
|
21
|
+
return;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Once we lose focus on the copy button, we want to set the copyState to false so that we can hide the tool tip and set the default tool tip to ''
|
|
25
|
+
const loseFocus = () => {
|
|
26
|
+
setCopyState(false);
|
|
27
|
+
return;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const buttonClasses = ['copy-button'];
|
|
31
|
+
if (props.className) buttonClasses.push(props.className);
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<React.Fragment>
|
|
35
|
+
<Tooltip isShowing={copyState} text="Copied" position={props.tooltipPosition}>
|
|
36
|
+
<button type="button" className={buttonClasses.join(' ')} onClick={copyCode} onMouseOut={loseFocus}>
|
|
37
|
+
<GenesysDevIcon icon={GenesysDevIcons.AppCopy} />
|
|
38
|
+
</button>
|
|
39
|
+
</Tooltip>
|
|
40
|
+
</React.Fragment>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
.dx-accordion {
|
|
2
|
+
margin: 0;
|
|
3
|
+
|
|
4
|
+
.accordion-heading {
|
|
5
|
+
border-width: 0 0 1px 0;
|
|
6
|
+
border-style: solid;
|
|
7
|
+
border-color: #bfd4e4;
|
|
8
|
+
font-style: normal;
|
|
9
|
+
font-weight: bold;
|
|
10
|
+
font-size: 14px;
|
|
11
|
+
line-height: 14px;
|
|
12
|
+
color: #54565a;
|
|
13
|
+
padding: 13px 20px;
|
|
14
|
+
display: flex;
|
|
15
|
+
flex-flow: row nowrap;
|
|
16
|
+
justify-content: space-between;
|
|
17
|
+
align-items: center;
|
|
18
|
+
cursor: pointer;
|
|
19
|
+
|
|
20
|
+
.icon {
|
|
21
|
+
line-height: 0;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
&__left {
|
|
25
|
+
align-self: left;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.accordion-content {
|
|
30
|
+
padding: 13px 20px 20px 20px;
|
|
31
|
+
border-bottom: 1px solid #bfd4e4;
|
|
32
|
+
|
|
33
|
+
// Clear excess margins from content, we provide the padding
|
|
34
|
+
& > *:first-child {
|
|
35
|
+
margin-top: 0;
|
|
36
|
+
}
|
|
37
|
+
& > *:last-child {
|
|
38
|
+
margin-bottom: 0;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { GenesysDevIcon, GenesysDevIcons } from 'genesys-dev-icons';
|
|
2
|
+
import React, { useState } from 'react';
|
|
3
|
+
import { DxAccordionProps } from '..';
|
|
4
|
+
|
|
5
|
+
import './DxAccordion.scss';
|
|
6
|
+
|
|
7
|
+
export default function DxAccordion(props: DxAccordionProps) {
|
|
8
|
+
const [isOpen, setIsOpen] = useState(props.showOpen || false);
|
|
9
|
+
const [expandTrigger, setExpandTrigger] = useState(props.expandTrigger);
|
|
10
|
+
const [showOpenTrigger, setShowOpenTrigger] = useState(props.showOpenTrigger);
|
|
11
|
+
|
|
12
|
+
// This one forcibly opens the component
|
|
13
|
+
React.useEffect(() => {
|
|
14
|
+
if (props.expandTrigger !== expandTrigger) {
|
|
15
|
+
setIsOpen(true);
|
|
16
|
+
setExpandTrigger(props.expandTrigger);
|
|
17
|
+
}
|
|
18
|
+
}, [props.expandTrigger, expandTrigger]);
|
|
19
|
+
|
|
20
|
+
// This one forcibly recalculates the state based on the value for props.showOpen
|
|
21
|
+
React.useEffect(() => {
|
|
22
|
+
if (props.showOpenTrigger !== showOpenTrigger) {
|
|
23
|
+
setIsOpen(props.showOpen);
|
|
24
|
+
setShowOpenTrigger(props.showOpenTrigger);
|
|
25
|
+
}
|
|
26
|
+
}, [props.showOpenTrigger, showOpenTrigger, props.showOpen]);
|
|
27
|
+
|
|
28
|
+
React.useEffect(() => {
|
|
29
|
+
if (props.showOpen === true || props.showOpen === false) setIsOpen(props.showOpen);
|
|
30
|
+
}, [props.showOpen]);
|
|
31
|
+
|
|
32
|
+
let style = {} as React.CSSProperties;
|
|
33
|
+
if (props.headingColor) style.color = props.headingColor;
|
|
34
|
+
|
|
35
|
+
let icon;
|
|
36
|
+
if (props.headingIcon) icon = <GenesysDevIcon icon={props.headingIcon} className="heading-icon" />;
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div id={props.containerId || undefined} className={`dx-accordion${props.className ? ' ' + props.className : ''}`}>
|
|
40
|
+
<div className="accordion-heading" style={style} onClick={() => setIsOpen(!isOpen)}>
|
|
41
|
+
<span className="accordion-heading__left">
|
|
42
|
+
{icon} {props.title}
|
|
43
|
+
</span>{' '}
|
|
44
|
+
<GenesysDevIcon icon={isOpen ? GenesysDevIcons.AppChevronUp : GenesysDevIcons.AppChevronDown} />
|
|
45
|
+
</div>
|
|
46
|
+
{isOpen ? <div className="accordion-content">{props.children}</div> : undefined}
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import './DxAccordionGroup.scss';
|
|
4
|
+
|
|
5
|
+
interface IProps {
|
|
6
|
+
children: React.ReactNode;
|
|
7
|
+
className?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default function DxAccordionGroup(props: IProps) {
|
|
11
|
+
return <div className={`dx-accordion-group${props.className ? ' ' + props.className : ''}`}>{props.children}</div>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
.dx-button {
|
|
2
|
+
margin: 15px 10px;
|
|
3
|
+
border-radius: 2px;
|
|
4
|
+
padding: 8px 15px;
|
|
5
|
+
cursor: pointer;
|
|
6
|
+
font-weight: 500;
|
|
7
|
+
|
|
8
|
+
&-primary {
|
|
9
|
+
color: #ffffff;
|
|
10
|
+
border: 1px solid #419bb2;
|
|
11
|
+
background-color: #419bb2;
|
|
12
|
+
|
|
13
|
+
&:hover {
|
|
14
|
+
background-color: #317b8d;
|
|
15
|
+
border-color: #317b8d;
|
|
16
|
+
transition: 0.1s;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
&:focus {
|
|
20
|
+
background-color: #419bb2;
|
|
21
|
+
border-color: #419bb2;
|
|
22
|
+
box-shadow: 0 0 0 2px #aac9ff;
|
|
23
|
+
transition: 0.1s;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
&:disabled {
|
|
27
|
+
background-color: #9aafb540;
|
|
28
|
+
border-color: #9aafb540;
|
|
29
|
+
transition: 0.1s;
|
|
30
|
+
cursor: not-allowed;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
&-secondary {
|
|
35
|
+
color: #419bb2;
|
|
36
|
+
border: 1px solid #419bb2;
|
|
37
|
+
background-color: #ffffff;
|
|
38
|
+
|
|
39
|
+
&:hover {
|
|
40
|
+
color: #317b8d;
|
|
41
|
+
border-color: #317b8d;
|
|
42
|
+
transition: 0.1s;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
&:focus {
|
|
46
|
+
color: #419bb2;
|
|
47
|
+
border-color: #419bb2;
|
|
48
|
+
box-shadow: 0 0 0 2px #aac9ff;
|
|
49
|
+
transition: 0.1s;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
&:disabled {
|
|
53
|
+
color: #8a9a9e;
|
|
54
|
+
background-color: #e0e6e8;
|
|
55
|
+
border-color: #e0e6e8;
|
|
56
|
+
transition: 0.1s;
|
|
57
|
+
cursor: not-allowed;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
&-link {
|
|
62
|
+
color: #2f7bb1;
|
|
63
|
+
background: transparent;
|
|
64
|
+
margin: 0;
|
|
65
|
+
padding: 0 2px;
|
|
66
|
+
border: 0;
|
|
67
|
+
|
|
68
|
+
&:hover {
|
|
69
|
+
color: #1c5176;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
&:disabled {
|
|
73
|
+
color: #8a9a9e;
|
|
74
|
+
transition: 0.1s;
|
|
75
|
+
cursor: not-allowed;
|
|
76
|
+
text-decoration: line-through;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { VoidEventCallback } from '..';
|
|
3
|
+
|
|
4
|
+
import './DxButton.scss';
|
|
5
|
+
|
|
6
|
+
interface IProps {
|
|
7
|
+
type?: 'primary' | 'secondary' | 'link';
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
children?: React.ReactNode;
|
|
10
|
+
className?: string;
|
|
11
|
+
onClick?: VoidEventCallback;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default function DxButton(props: IProps) {
|
|
15
|
+
let classNames = ['dx-button'];
|
|
16
|
+
classNames.push(`dx-button-${props.type || 'primary'}`);
|
|
17
|
+
if (props.className) classNames.push(props.className);
|
|
18
|
+
|
|
19
|
+
const handleClick = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
|
20
|
+
if (!props.onClick) return;
|
|
21
|
+
e.preventDefault();
|
|
22
|
+
e.stopPropagation();
|
|
23
|
+
props.onClick();
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<button className={classNames.join(' ')} type="button" onClick={handleClick} disabled={props.disabled === true}>
|
|
28
|
+
{props.children}
|
|
29
|
+
</button>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
.dx-checkbox {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-flow: row nowrap;
|
|
4
|
+
align-items: center;
|
|
5
|
+
margin: 15px 0;
|
|
6
|
+
|
|
7
|
+
&:first-of-type {
|
|
8
|
+
margin-top: 0;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.label-text {
|
|
12
|
+
font-family: Roboto;
|
|
13
|
+
font-style: normal;
|
|
14
|
+
font-weight: normal;
|
|
15
|
+
font-size: 12px;
|
|
16
|
+
line-height: 18px;
|
|
17
|
+
color: #75757a;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
input[type='checkbox'] {
|
|
21
|
+
// Hide default appearance
|
|
22
|
+
-webkit-appearance: none;
|
|
23
|
+
appearance: none;
|
|
24
|
+
|
|
25
|
+
// Custom appearance
|
|
26
|
+
margin: 0 8px 0 0;
|
|
27
|
+
width: 16px;
|
|
28
|
+
height: 16px;
|
|
29
|
+
border: 1px solid #8a96a3;
|
|
30
|
+
border-radius: 2px;
|
|
31
|
+
background-color: #ffffff;
|
|
32
|
+
flex-shrink: 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Hide checkmark initially
|
|
36
|
+
input[type='checkbox']::before {
|
|
37
|
+
display: none;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Fill in background on check
|
|
41
|
+
input[type='checkbox']:checked {
|
|
42
|
+
background-color: #8a96a3;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Display checkmark on check
|
|
46
|
+
input[type='checkbox']:checked::before {
|
|
47
|
+
display: block;
|
|
48
|
+
position: relative;
|
|
49
|
+
top: 7px;
|
|
50
|
+
left: 3px;
|
|
51
|
+
font-size: 9px;
|
|
52
|
+
line-height: 0;
|
|
53
|
+
color: #ffffff;
|
|
54
|
+
|
|
55
|
+
// genesys-dev-icons app-check
|
|
56
|
+
content: '\f103';
|
|
57
|
+
font-family: genesys-dev-icons !important;
|
|
58
|
+
font-style: normal;
|
|
59
|
+
font-weight: normal !important;
|
|
60
|
+
font-feature-settings: normal;
|
|
61
|
+
font-variant: normal;
|
|
62
|
+
text-transform: none;
|
|
63
|
+
-webkit-font-smoothing: antialiased;
|
|
64
|
+
-moz-osx-font-smoothing: grayscale;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Hover - radio button
|
|
68
|
+
input[type='checkbox']:not(:disabled):hover {
|
|
69
|
+
border-color: #4d5061;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Focus - radio button
|
|
73
|
+
input[type='checkbox']:not(:disabled):focus {
|
|
74
|
+
outline: #aac9ff solid 2px;
|
|
75
|
+
}
|
|
76
|
+
// Disable default focus outline
|
|
77
|
+
input[type='checkbox']:not(:disabled):focus-visible {
|
|
78
|
+
outline: 0;
|
|
79
|
+
}
|
|
80
|
+
input[type='radio'] {
|
|
81
|
+
// Hide default appearance
|
|
82
|
+
-webkit-appearance: none;
|
|
83
|
+
appearance: none;
|
|
84
|
+
|
|
85
|
+
// Custom appearance
|
|
86
|
+
margin: 0 8px 0 0;
|
|
87
|
+
width: 16px;
|
|
88
|
+
height: 16px;
|
|
89
|
+
border: 1px solid #8a96a3;
|
|
90
|
+
border-radius: 8px;
|
|
91
|
+
background-color: #ffffff;
|
|
92
|
+
flex-shrink: 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Hide radio button initially
|
|
96
|
+
input[type='radio']::before {
|
|
97
|
+
display: none;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Display filled radio button
|
|
101
|
+
input[type='radio']:checked::before {
|
|
102
|
+
content: '';
|
|
103
|
+
display: block;
|
|
104
|
+
width: 10px;
|
|
105
|
+
height: 10px;
|
|
106
|
+
border-radius: 8px;
|
|
107
|
+
position: relative;
|
|
108
|
+
top: 2px;
|
|
109
|
+
left: 2px;
|
|
110
|
+
background-color: #8a96a3;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Hover - radio button
|
|
114
|
+
input[type='radio']:not(:disabled):hover {
|
|
115
|
+
border-color: #4d5061;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Focus - radio button
|
|
119
|
+
input[type='radio']:not(:disabled):focus {
|
|
120
|
+
outline: #aac9ff solid 2px;
|
|
121
|
+
}
|
|
122
|
+
// Disable default focus outline
|
|
123
|
+
input[type='radio']:not(:disabled):focus-visible {
|
|
124
|
+
outline: 0;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
&.disabled {
|
|
128
|
+
cursor: not-allowed;
|
|
129
|
+
|
|
130
|
+
input {
|
|
131
|
+
border-color: #e8eaed;
|
|
132
|
+
cursor: not-allowed;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Fill in background on check
|
|
136
|
+
input:checked {
|
|
137
|
+
background-color: #e8eaed;
|
|
138
|
+
cursor: not-allowed;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.dx-label .dx-checkbox .label-text {
|
|
144
|
+
margin: 0;
|
|
145
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { CheckedChangedCallback } from '..';
|
|
3
|
+
|
|
4
|
+
import './DxCheckbox.scss';
|
|
5
|
+
|
|
6
|
+
interface IProps {
|
|
7
|
+
label: string;
|
|
8
|
+
itemValue: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
checked?: boolean;
|
|
11
|
+
initiallyChecked?: boolean;
|
|
12
|
+
name?: string;
|
|
13
|
+
className?: string;
|
|
14
|
+
onCheckChanged?: CheckedChangedCallback;
|
|
15
|
+
useRadioType?: boolean;
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default function DxCheckbox(props: IProps) {
|
|
20
|
+
let initialValue: boolean = props.checked !== undefined ? props.checked : props.initiallyChecked || false;
|
|
21
|
+
|
|
22
|
+
const [checked, setChecked] = useState<boolean>(initialValue);
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (props.checked === undefined || props.checked === checked) return;
|
|
26
|
+
setChecked(props.checked);
|
|
27
|
+
}, [props.checked]);
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
if (props.onCheckChanged) props.onCheckChanged(checked);
|
|
31
|
+
}, [checked]);
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<label className={`dx-checkbox${props.className ? ' ' + props.className : ''}${props.disabled ? ' disabled' : ''}`}>
|
|
35
|
+
<input
|
|
36
|
+
type={props.useRadioType ? 'radio' : 'checkbox'}
|
|
37
|
+
name={props.name}
|
|
38
|
+
value={props.itemValue}
|
|
39
|
+
checked={checked}
|
|
40
|
+
onChange={(e) => setChecked(e.target.checked)}
|
|
41
|
+
disabled={props.disabled === true}
|
|
42
|
+
/>
|
|
43
|
+
<span className='label-text'>{props.label}</span>
|
|
44
|
+
</label>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { v4 as uuid } from 'uuid';
|
|
3
|
+
import { DxItemGroupItem, DxItemGroupItemValue, DxItemGroupProps, ItemChangedCallback, ItemGroupChangedCallback } from '..';
|
|
4
|
+
|
|
5
|
+
import './DxItemGroup.scss';
|
|
6
|
+
import './radiobutton.scss';
|
|
7
|
+
import './dropdown.scss';
|
|
8
|
+
import './multiselect.scss';
|
|
9
|
+
import DxLabel from '../dxlabel/DxLabel';
|
|
10
|
+
import DxCheckbox from './DxCheckbox';
|
|
11
|
+
|
|
12
|
+
export default function DxItemGroup(props: DxItemGroupProps) {
|
|
13
|
+
const [data, setData] = useState<DxItemGroupItemValue[]>(
|
|
14
|
+
props.items.map((item) => {
|
|
15
|
+
return { item, isSelected: item.isSelected !== undefined ? item.isSelected : false };
|
|
16
|
+
})
|
|
17
|
+
);
|
|
18
|
+
const [id] = useState(uuid());
|
|
19
|
+
const [title, setTitle] = useState(props.title);
|
|
20
|
+
const [description, setDescription] = useState(props.description);
|
|
21
|
+
const [format, setFormat] = useState(props.format);
|
|
22
|
+
const [disabled, setDisabled] = useState(props.disabled);
|
|
23
|
+
const [className, setClassName] = useState(props.className);
|
|
24
|
+
|
|
25
|
+
// data changed
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (props.onItemsChanged) props.onItemsChanged(data);
|
|
28
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
29
|
+
}, [data]);
|
|
30
|
+
|
|
31
|
+
// Recalculate on props changed
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
setTitle(props.title);
|
|
34
|
+
setDescription(props.description);
|
|
35
|
+
setFormat(props.format);
|
|
36
|
+
setDisabled(props.disabled);
|
|
37
|
+
setClassName(props.className);
|
|
38
|
+
}, [props.title, props.description, props.format, props.items, props.disabled, props.className]);
|
|
39
|
+
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
setData(
|
|
42
|
+
props.items.map((item) => {
|
|
43
|
+
return { item, isSelected: item.isSelected !== undefined ? item.isSelected : false };
|
|
44
|
+
})
|
|
45
|
+
);
|
|
46
|
+
}, [props.items]);
|
|
47
|
+
// Handle individual item changed
|
|
48
|
+
const itemChanged = (idx: number, item: DxItemGroupItem, checked: boolean) => {
|
|
49
|
+
if (props.onItemChanged) props.onItemChanged(item, checked);
|
|
50
|
+
let newData = [...data];
|
|
51
|
+
// Unselect everything if it's radio buttons
|
|
52
|
+
if (format === 'radio') newData.forEach((value) => (value.isSelected = false));
|
|
53
|
+
// Set the selected state of the new item
|
|
54
|
+
newData[idx].isSelected = checked;
|
|
55
|
+
setData(newData);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const selectChanged = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
|
59
|
+
const options = e.target.options;
|
|
60
|
+
let newData = [...data];
|
|
61
|
+
// Assign selected value for each item in the list
|
|
62
|
+
for (let i = 0; i < options.length; i++) {
|
|
63
|
+
const thisItem = newData.find((value) => value.item.value === options[i].value);
|
|
64
|
+
thisItem.isSelected = options[i].selected;
|
|
65
|
+
}
|
|
66
|
+
// Update entire data list
|
|
67
|
+
setData(newData);
|
|
68
|
+
// Trigger individual update
|
|
69
|
+
const changedItemIdx = newData.findIndex((value) => value.item.value === e.target.value);
|
|
70
|
+
if (changedItemIdx >= 0) itemChanged(changedItemIdx, newData[changedItemIdx].item, newData[changedItemIdx].isSelected);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
switch (format) {
|
|
74
|
+
case 'multiselect':
|
|
75
|
+
case 'dropdown': {
|
|
76
|
+
const isMulti = format === 'multiselect';
|
|
77
|
+
return (
|
|
78
|
+
<DxLabel label={title} description={description} className={className}>
|
|
79
|
+
<div className={`dx-item-group${isMulti ? ' dx-multiselect-group' : ' dx-select-group'}${disabled ? ' disabled' : ''}`}>
|
|
80
|
+
<select
|
|
81
|
+
multiple={isMulti}
|
|
82
|
+
disabled={disabled === true}
|
|
83
|
+
onChange={(e) => selectChanged(e)}
|
|
84
|
+
value={
|
|
85
|
+
isMulti
|
|
86
|
+
? data.filter((item) => item.isSelected)?.map((item) => item.item.value)
|
|
87
|
+
: data.find((item) => item.isSelected)?.item.value
|
|
88
|
+
}
|
|
89
|
+
>
|
|
90
|
+
{data.map((d, i) => (
|
|
91
|
+
<option key={i} value={d.item.value} disabled={d.item.disabled}>
|
|
92
|
+
{d.item.label}
|
|
93
|
+
</option>
|
|
94
|
+
))}
|
|
95
|
+
</select>
|
|
96
|
+
</div>
|
|
97
|
+
</DxLabel>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
case 'checkbox':
|
|
101
|
+
case 'radio':
|
|
102
|
+
default: {
|
|
103
|
+
return (
|
|
104
|
+
<DxLabel
|
|
105
|
+
label={title}
|
|
106
|
+
description={description}
|
|
107
|
+
className={`dx-item-group${disabled ? ' disabled' : ''}${className ? ' ' + className : ''}`}
|
|
108
|
+
useFieldset={true}
|
|
109
|
+
>
|
|
110
|
+
<div
|
|
111
|
+
onChange={(e: React.ChangeEvent<HTMLDivElement>) => {
|
|
112
|
+
const i = data.findIndex((d) => d.item.value === (e.target as any)?.value);
|
|
113
|
+
if (i < 0) return;
|
|
114
|
+
itemChanged(i, data[i].item, (e.target as any)?.checked);
|
|
115
|
+
}}
|
|
116
|
+
>
|
|
117
|
+
{data.map((d, i) => (
|
|
118
|
+
<DxCheckbox
|
|
119
|
+
key={d.item.value}
|
|
120
|
+
name={format === 'checkbox' ? `${id}-${d.item.value}` : id}
|
|
121
|
+
label={d.item.label}
|
|
122
|
+
itemValue={d.item.value}
|
|
123
|
+
checked={d.isSelected}
|
|
124
|
+
useRadioType={format === 'radio'}
|
|
125
|
+
disabled={disabled || d.item.disabled}
|
|
126
|
+
/>
|
|
127
|
+
))}
|
|
128
|
+
</div>
|
|
129
|
+
</DxLabel>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
.dx-select-group {
|
|
2
|
+
appearance: none;
|
|
3
|
+
position: relative;
|
|
4
|
+
|
|
5
|
+
select {
|
|
6
|
+
border: 1px solid #8a96a3;
|
|
7
|
+
border-radius: 2px;
|
|
8
|
+
background-color: #ffffff;
|
|
9
|
+
font-style: normal;
|
|
10
|
+
font-weight: 300;
|
|
11
|
+
font-size: 12px;
|
|
12
|
+
line-height: 14px;
|
|
13
|
+
color: #75757a;
|
|
14
|
+
padding: 8px 32px 8px 12px;
|
|
15
|
+
width: 100%;
|
|
16
|
+
appearance: none;
|
|
17
|
+
|
|
18
|
+
&:focus-visible {
|
|
19
|
+
outline: 2px solid #aac9ff;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
&::after {
|
|
24
|
+
position: absolute;
|
|
25
|
+
bottom: 12px;
|
|
26
|
+
right: 12px;
|
|
27
|
+
content: '\f104';
|
|
28
|
+
font-size: 8px;
|
|
29
|
+
font-family: genesys-dev-icons !important;
|
|
30
|
+
font-style: normal;
|
|
31
|
+
font-weight: normal !important;
|
|
32
|
+
font-feature-settings: normal;
|
|
33
|
+
font-variant: normal;
|
|
34
|
+
text-transform: none;
|
|
35
|
+
line-height: 1;
|
|
36
|
+
-webkit-font-smoothing: antialiased;
|
|
37
|
+
-moz-osx-font-smoothing: grayscale;
|
|
38
|
+
pointer-events: none;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
&.disabled {
|
|
42
|
+
&::after {
|
|
43
|
+
color: #8a9a9e;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
select:disabled {
|
|
47
|
+
background-color: #e6ebec;
|
|
48
|
+
border-color: #e8eaed;
|
|
49
|
+
color: #8a9a9e;
|
|
50
|
+
cursor: not-allowed;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|