genesys-react-components 0.1.6 → 0.2.0-devengage-1376.132
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 +52 -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,74 @@
|
|
|
1
|
+
.dx-multiselect-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
|
+
width: 100%;
|
|
15
|
+
appearance: none;
|
|
16
|
+
|
|
17
|
+
&:focus-visible {
|
|
18
|
+
outline: 2px solid #aac9ff;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
option {
|
|
22
|
+
overflow: hidden;
|
|
23
|
+
white-space: pre;
|
|
24
|
+
text-overflow: ellipsis;
|
|
25
|
+
-webkit-appearance: none;
|
|
26
|
+
font-style: normal;
|
|
27
|
+
font-weight: 300;
|
|
28
|
+
font-size: 12px;
|
|
29
|
+
line-height: 31px;
|
|
30
|
+
padding: 8px 12px;
|
|
31
|
+
color: #75757a;
|
|
32
|
+
|
|
33
|
+
&:checked {
|
|
34
|
+
background: #aac9ff -webkit-linear-gradient(bottom, #aac9ff 0%, #aac9ff 100%);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
&:disabled {
|
|
38
|
+
color: #8a9a9e;
|
|
39
|
+
cursor: not-allowed;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Webkit scrollbars (Chrome, Opera, Safari, Edge... every browser but firefox
|
|
44
|
+
// https://developer.mozilla.org/en-US/docs/Web/CSS/::-webkit-scrollbar
|
|
45
|
+
&::-webkit-scrollbar {
|
|
46
|
+
-webkit-appearance: none;
|
|
47
|
+
width: 7px;
|
|
48
|
+
height: 7px;
|
|
49
|
+
}
|
|
50
|
+
&::-webkit-scrollbar-thumb {
|
|
51
|
+
border-radius: 4px;
|
|
52
|
+
background-color: #b0b2b5;
|
|
53
|
+
}
|
|
54
|
+
&::-webkit-scrollbar-corner {
|
|
55
|
+
background: transparent;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Just firefox
|
|
59
|
+
// https://developer.mozilla.org/en-US/docs/Web/CSS/scrollbar-color
|
|
60
|
+
scrollbar-color: #b0b2b5 transparent;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
&.disabled {
|
|
64
|
+
select:disabled {
|
|
65
|
+
background-color: #e6ebec;
|
|
66
|
+
border-color: #e8eaed;
|
|
67
|
+
cursor: not-allowed;
|
|
68
|
+
|
|
69
|
+
option {
|
|
70
|
+
color: #8a9a9e;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
.dx-label {
|
|
2
|
+
margin: 20px 0;
|
|
3
|
+
display: block;
|
|
4
|
+
|
|
5
|
+
.label-text,
|
|
6
|
+
.input-description {
|
|
7
|
+
display: block;
|
|
8
|
+
font-family: Roboto;
|
|
9
|
+
font-style: normal;
|
|
10
|
+
font-weight: 400;
|
|
11
|
+
font-size: 12px;
|
|
12
|
+
line-height: 14px;
|
|
13
|
+
color: #75757a;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.label-text {
|
|
17
|
+
margin: 0 0 4px 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.input-description {
|
|
21
|
+
padding: 6px 20px;
|
|
22
|
+
display: flex;
|
|
23
|
+
flex-flow: row nowrap;
|
|
24
|
+
gap: 8px;
|
|
25
|
+
|
|
26
|
+
.icon {
|
|
27
|
+
color: #597393;
|
|
28
|
+
line-height: 0;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { GenesysDevIcon, GenesysDevIcons } from 'genesys-dev-icons';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
import './DxLabel.scss';
|
|
5
|
+
|
|
6
|
+
interface IProps {
|
|
7
|
+
label?: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
useFieldset?: boolean;
|
|
10
|
+
className?: string;
|
|
11
|
+
children: React.ReactNode;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default function DxLabel(props: IProps) {
|
|
15
|
+
const hasLabel = props.label && props.label !== '';
|
|
16
|
+
|
|
17
|
+
const description = props.description ? (
|
|
18
|
+
<div className='input-description'>
|
|
19
|
+
<GenesysDevIcon icon={GenesysDevIcons.AppInfoSolid} />
|
|
20
|
+
<span>{props.description}</span>
|
|
21
|
+
</div>
|
|
22
|
+
) : undefined;
|
|
23
|
+
|
|
24
|
+
const contents = (
|
|
25
|
+
<React.Fragment>
|
|
26
|
+
{' '}
|
|
27
|
+
{hasLabel ? <span className='label-text'>{props.label}</span> : undefined}
|
|
28
|
+
{props.children}
|
|
29
|
+
{description}
|
|
30
|
+
</React.Fragment>
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const className = `dx-label${props.className ? ' ' + props.className : ''}`;
|
|
34
|
+
|
|
35
|
+
if (props.useFieldset) {
|
|
36
|
+
return <fieldset className={className}>{contents}</fieldset>;
|
|
37
|
+
}
|
|
38
|
+
return <label className={className}>{contents}</label>;
|
|
39
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { DxTabPanelProps } from '..';
|
|
3
|
+
|
|
4
|
+
import './DxTabPanel.scss';
|
|
5
|
+
|
|
6
|
+
export default function DxTabPanel(props: DxTabPanelProps) {
|
|
7
|
+
return <div className={`dx-tab-panel${props.className ? ' ' + props.className : ''}`}>{props.children}</div>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
.dx-tabbed-content {
|
|
2
|
+
margin: 40px 0;
|
|
3
|
+
|
|
4
|
+
.tab-titles {
|
|
5
|
+
border-bottom: 1px solid #bfd4e4;
|
|
6
|
+
font-weight: normal;
|
|
7
|
+
font-size: 14px;
|
|
8
|
+
line-height: 20px;
|
|
9
|
+
|
|
10
|
+
.tab-title {
|
|
11
|
+
display: inline-block;
|
|
12
|
+
padding: 5px 20px;
|
|
13
|
+
border-bottom: 1px solid transparent;
|
|
14
|
+
cursor: pointer;
|
|
15
|
+
|
|
16
|
+
&:hover {
|
|
17
|
+
border-color: #bfd4e4;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
&.active {
|
|
21
|
+
border-bottom-color: #597393;
|
|
22
|
+
font-weight: bold;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Make markdown-parsed paragraphs look normal
|
|
26
|
+
& p {
|
|
27
|
+
margin: 0;
|
|
28
|
+
display: inline;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.tab-content {
|
|
34
|
+
padding: 13px 20px 20px 20px;
|
|
35
|
+
border-bottom: 1px solid #bfd4e4;
|
|
36
|
+
|
|
37
|
+
// Clear excess margins from content, we provide the padding
|
|
38
|
+
& > *:first-child {
|
|
39
|
+
margin-top: 0;
|
|
40
|
+
}
|
|
41
|
+
& > *:last-child {
|
|
42
|
+
margin-bottom: 0;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { DxTabbedContentProps } from '..';
|
|
3
|
+
|
|
4
|
+
import './DxTabbedContent.scss';
|
|
5
|
+
|
|
6
|
+
export default function DxTabbedContent(props: DxTabbedContentProps) {
|
|
7
|
+
const [activeTab, setActiveTab] = useState(props.initialTabId || 0);
|
|
8
|
+
const [titles] = useState<React.ReactNode[]>(
|
|
9
|
+
// Scrape titles from child elements
|
|
10
|
+
React.Children.toArray(props.children).map((child: any) => {
|
|
11
|
+
if (!child || !child.props || !child.props.title) return 'Unknown title';
|
|
12
|
+
return child.props.title;
|
|
13
|
+
})
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<div className={`dx-tabbed-content${props.className ? ' ' + props.className : ''}`}>
|
|
18
|
+
<div className='tab-titles'>
|
|
19
|
+
{titles.map((title, i) => (
|
|
20
|
+
<span key={i} className={`tab-title${i === activeTab ? ' active' : ''}`} onClick={() => setActiveTab(i)}>
|
|
21
|
+
{title}
|
|
22
|
+
</span>
|
|
23
|
+
))}
|
|
24
|
+
</div>
|
|
25
|
+
<div className='tab-content'>{React.Children.toArray(props.children)[activeTab]}</div>
|
|
26
|
+
</div>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
.dx-textbox {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-flow: row nowrap;
|
|
4
|
+
justify-content: space-between;
|
|
5
|
+
align-items: center;
|
|
6
|
+
gap: 10px;
|
|
7
|
+
border: 1px solid #c6cbd1;
|
|
8
|
+
border-radius: 2px;
|
|
9
|
+
margin: 0;
|
|
10
|
+
padding: 0 10px;
|
|
11
|
+
height: 32px;
|
|
12
|
+
background-color: #ffffff;
|
|
13
|
+
|
|
14
|
+
&.with-label {
|
|
15
|
+
margin-top: 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
&:focus-within {
|
|
19
|
+
outline: #aac9ff solid 2px;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.icon {
|
|
23
|
+
display: block;
|
|
24
|
+
flex: none;
|
|
25
|
+
color: #75757a;
|
|
26
|
+
|
|
27
|
+
&.input-icon {
|
|
28
|
+
font-size: 14px;
|
|
29
|
+
line-height: 0;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
&.clear-icon {
|
|
33
|
+
font-size: 11px;
|
|
34
|
+
line-height: 0;
|
|
35
|
+
cursor: pointer;
|
|
36
|
+
padding: 4px;
|
|
37
|
+
margin-right: -4px;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.dx-input {
|
|
42
|
+
flex-grow: 1;
|
|
43
|
+
border: 0;
|
|
44
|
+
background: transparent;
|
|
45
|
+
box-sizing: border-box;
|
|
46
|
+
height: 32px;
|
|
47
|
+
width: 100%;
|
|
48
|
+
padding: 0;
|
|
49
|
+
margin: 0;
|
|
50
|
+
font-family: Roboto;
|
|
51
|
+
font-style: normal;
|
|
52
|
+
font-weight: normal;
|
|
53
|
+
font-size: 14px;
|
|
54
|
+
line-height: 16px;
|
|
55
|
+
color: #272d2d;
|
|
56
|
+
|
|
57
|
+
&:focus-visible {
|
|
58
|
+
outline: 0;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
&::placeholder {
|
|
62
|
+
font-style: normal;
|
|
63
|
+
font-weight: 300;
|
|
64
|
+
font-size: 14px;
|
|
65
|
+
line-height: 16px;
|
|
66
|
+
color: #757576;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
&.disabled {
|
|
71
|
+
background-color: #e6ebec;
|
|
72
|
+
border-color: #e8eaed;
|
|
73
|
+
cursor: not-allowed;
|
|
74
|
+
|
|
75
|
+
input {
|
|
76
|
+
cursor: not-allowed;
|
|
77
|
+
color: #75757a;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.icon,
|
|
81
|
+
input::placeholder {
|
|
82
|
+
color: #ffffff;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.dx-textarea {
|
|
88
|
+
padding: 10px;
|
|
89
|
+
border: 1px solid #c6cbd1;
|
|
90
|
+
border-radius: 2px;
|
|
91
|
+
width: 100%;
|
|
92
|
+
font-family: 'Roboto', sans-serif;
|
|
93
|
+
box-sizing: border-box;
|
|
94
|
+
|
|
95
|
+
&:focus-within {
|
|
96
|
+
outline: #aac9ff solid 2px;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
&::placeholder {
|
|
100
|
+
font-family: 'Roboto', sans-serif;
|
|
101
|
+
font-style: normal;
|
|
102
|
+
font-weight: 300;
|
|
103
|
+
font-size: 14px;
|
|
104
|
+
line-height: 16px;
|
|
105
|
+
color: #757576;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { GenesysDevIcon, GenesysDevIcons } from 'genesys-dev-icons';
|
|
2
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
3
|
+
import DxLabel from '../dxlabel/DxLabel';
|
|
4
|
+
import { DxTextboxProps } from '..';
|
|
5
|
+
|
|
6
|
+
import './DxTextbox.scss';
|
|
7
|
+
|
|
8
|
+
export default function DxTextbox(props: DxTextboxProps) {
|
|
9
|
+
const [debounceMs, setDebounceMs] = useState(props.changeDebounceMs || 300);
|
|
10
|
+
const [value, setValue] = useState(props.initialValue || props.value || '');
|
|
11
|
+
const [isFocused, setIsFocused] = useState(false);
|
|
12
|
+
const [escapePressed, setEscapePressed] = useState(Date.now());
|
|
13
|
+
const [step, setStep] = useState<string | number | undefined>(undefined);
|
|
14
|
+
let [timer, setTimer] = useState(undefined as unknown as ReturnType<typeof setTimeout>);
|
|
15
|
+
|
|
16
|
+
// Constructor
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
// Register global key bindings
|
|
19
|
+
document.addEventListener('keydown', globalKeyBindings, false);
|
|
20
|
+
|
|
21
|
+
return () => {
|
|
22
|
+
document.removeEventListener('keydown', globalKeyBindings, false);
|
|
23
|
+
};
|
|
24
|
+
}, []);
|
|
25
|
+
|
|
26
|
+
// Value prop updated
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
// Ignore value changed if initial value was set; they're mutually exclusive
|
|
29
|
+
if (!props.initialValue) {
|
|
30
|
+
setValue(props.value || '');
|
|
31
|
+
}
|
|
32
|
+
}, [props.value]);
|
|
33
|
+
|
|
34
|
+
// Escape pressed
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (!isFocused) return;
|
|
37
|
+
setValue('');
|
|
38
|
+
inputRef.current?.blur();
|
|
39
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
40
|
+
}, [escapePressed]);
|
|
41
|
+
|
|
42
|
+
// Value changed
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (props.inputType === 'decimal') {
|
|
45
|
+
// Normalize step setting
|
|
46
|
+
if (!isNaN(parseFloat(value))) {
|
|
47
|
+
const match = /\.(.+)/.exec(value);
|
|
48
|
+
console.log(match);
|
|
49
|
+
if (match) {
|
|
50
|
+
const s = `0.${Array.apply(null, Array(match[1].length - 1))
|
|
51
|
+
.map(() => '0')
|
|
52
|
+
.join('')}1`;
|
|
53
|
+
console.log(s);
|
|
54
|
+
setStep(s);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
} else if (props.inputType === 'integer') {
|
|
58
|
+
// Overwrite value as integer to forcibly truncate floating point numbers
|
|
59
|
+
setValue(parseInt(value).toString());
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Debounce onChange notification
|
|
63
|
+
if (!props.onChange) return;
|
|
64
|
+
clearTimeout(timer);
|
|
65
|
+
setTimer(setTimeout(() => (props.onChange ? props.onChange(value) : undefined), debounceMs));
|
|
66
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
67
|
+
}, [value]);
|
|
68
|
+
|
|
69
|
+
// Update state from props
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
setDebounceMs(props.changeDebounceMs || 300);
|
|
72
|
+
}, [props.changeDebounceMs]);
|
|
73
|
+
|
|
74
|
+
// Normalize inputRef
|
|
75
|
+
let inputRef; // = useRef<HTMLInputElement>(null);
|
|
76
|
+
if (props.inputRef) inputRef = props.inputRef;
|
|
77
|
+
else if (props.inputType === 'textarea') inputRef = useRef<HTMLTextAreaElement>(null);
|
|
78
|
+
else inputRef = useRef<HTMLInputElement>(null);
|
|
79
|
+
|
|
80
|
+
const hasLabel = props.label && props.label !== '';
|
|
81
|
+
|
|
82
|
+
// Global key bindings
|
|
83
|
+
function globalKeyBindings(event: KeyboardEvent) {
|
|
84
|
+
// Escape - cancel search
|
|
85
|
+
if (event.key === 'Escape') {
|
|
86
|
+
event.stopPropagation();
|
|
87
|
+
event.preventDefault();
|
|
88
|
+
setEscapePressed(Date.now());
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Normalize input type
|
|
94
|
+
let inputType: React.HTMLInputTypeAttribute | undefined = props.inputType;
|
|
95
|
+
if (inputType === 'integer' || inputType === 'decimal') inputType = 'number';
|
|
96
|
+
|
|
97
|
+
let component;
|
|
98
|
+
switch (inputType) {
|
|
99
|
+
case 'textarea': {
|
|
100
|
+
component = (
|
|
101
|
+
<textarea
|
|
102
|
+
className="dx-textarea"
|
|
103
|
+
placeholder={props.placeholder}
|
|
104
|
+
ref={inputRef}
|
|
105
|
+
value={value}
|
|
106
|
+
onChange={(e) => setValue(e.target.value)}
|
|
107
|
+
onFocus={() => {
|
|
108
|
+
setIsFocused(true);
|
|
109
|
+
if (props.onFocus) props.onFocus();
|
|
110
|
+
}}
|
|
111
|
+
onBlur={() => {
|
|
112
|
+
setIsFocused(false);
|
|
113
|
+
if (props.onBlur) props.onBlur();
|
|
114
|
+
}}
|
|
115
|
+
disabled={props.disabled === true}
|
|
116
|
+
autoFocus={props.autoFocus}
|
|
117
|
+
/>
|
|
118
|
+
);
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
// TODO: special handling for other inputType values
|
|
122
|
+
default: {
|
|
123
|
+
component = (
|
|
124
|
+
<div className={`dx-textbox${hasLabel ? ' with-label' : ''}${props.disabled ? ' disabled' : ''}`}>
|
|
125
|
+
{props.icon ? <GenesysDevIcon icon={props.icon} className="input-icon" /> : undefined}
|
|
126
|
+
<input
|
|
127
|
+
className="dx-input"
|
|
128
|
+
type={inputType}
|
|
129
|
+
step={step}
|
|
130
|
+
value={value}
|
|
131
|
+
placeholder={props.placeholder}
|
|
132
|
+
onChange={(e) => setValue(e.target.value)}
|
|
133
|
+
ref={inputRef}
|
|
134
|
+
onFocus={() => {
|
|
135
|
+
setIsFocused(true);
|
|
136
|
+
if (props.onFocus) props.onFocus();
|
|
137
|
+
}}
|
|
138
|
+
onBlur={() => {
|
|
139
|
+
setIsFocused(false);
|
|
140
|
+
if (props.onBlur) props.onBlur();
|
|
141
|
+
}}
|
|
142
|
+
disabled={props.disabled === true}
|
|
143
|
+
autoFocus={props.autoFocus}
|
|
144
|
+
/>
|
|
145
|
+
{props.clearButton && (value || isFocused) && !props.disabled ? (
|
|
146
|
+
<GenesysDevIcon icon={GenesysDevIcons.AppTimes} className="clear-icon" onClick={() => setValue('')} />
|
|
147
|
+
) : undefined}
|
|
148
|
+
</div>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Render
|
|
154
|
+
return (
|
|
155
|
+
<DxLabel label={props.label} description={props.description} className={props.className}>
|
|
156
|
+
{component}
|
|
157
|
+
</DxLabel>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
.dx-toggle-container {
|
|
2
|
+
display: inline-block;
|
|
3
|
+
.dx-toggle {
|
|
4
|
+
background: #f5f8fb;
|
|
5
|
+
border: 1px solid #c6cbd1;
|
|
6
|
+
border-radius: 6px;
|
|
7
|
+
height: 26px;
|
|
8
|
+
// width: 72px;
|
|
9
|
+
padding: 0px 4px;
|
|
10
|
+
display: flex;
|
|
11
|
+
flex-flow: row nowrap;
|
|
12
|
+
justify-content: space-between;
|
|
13
|
+
align-items: center;
|
|
14
|
+
gap: 2px;
|
|
15
|
+
cursor: pointer;
|
|
16
|
+
|
|
17
|
+
&:hover {
|
|
18
|
+
.slider {
|
|
19
|
+
border-color: #aac9ff;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.icon {
|
|
24
|
+
font-size: 10px;
|
|
25
|
+
line-height: 0;
|
|
26
|
+
margin: 0 5px;
|
|
27
|
+
color: #c4c4c4;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.clear-placeholder {
|
|
31
|
+
width: 19px;
|
|
32
|
+
padding: 0 1px 0 0;
|
|
33
|
+
margin: 0;
|
|
34
|
+
display: block;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.slider {
|
|
38
|
+
height: 22px;
|
|
39
|
+
width: 22px;
|
|
40
|
+
border-radius: 22px;
|
|
41
|
+
background-color: #419bb2;
|
|
42
|
+
box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.25);
|
|
43
|
+
display: flex;
|
|
44
|
+
align-items: center;
|
|
45
|
+
justify-content: center;
|
|
46
|
+
border: 1px solid transparent;
|
|
47
|
+
|
|
48
|
+
.icon {
|
|
49
|
+
font-size: 10px;
|
|
50
|
+
line-height: 0;
|
|
51
|
+
color: #ffffff;
|
|
52
|
+
padding: 0;
|
|
53
|
+
margin: 0;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
&.disabled {
|
|
59
|
+
.dx-toggle {
|
|
60
|
+
border-color: #e8eaed;
|
|
61
|
+
color: #ffffff;
|
|
62
|
+
cursor: not-allowed;
|
|
63
|
+
|
|
64
|
+
&:hover {
|
|
65
|
+
.slider {
|
|
66
|
+
border-color: transparent;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.slider {
|
|
71
|
+
color: #8a9a9e;
|
|
72
|
+
background-color: #e0e6e8;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { GenesysDevIcon, GenesysDevIcons } from 'genesys-dev-icons';
|
|
3
|
+
import { BooleanChangedCallback, DxToggleProps } from '..';
|
|
4
|
+
|
|
5
|
+
import './DxToggle.scss';
|
|
6
|
+
import DxLabel from '../dxlabel/DxLabel';
|
|
7
|
+
|
|
8
|
+
export default function DxToggle(props: DxToggleProps) {
|
|
9
|
+
let initialValue: boolean | undefined = props.value !== undefined ? props.value : props.initialValue;
|
|
10
|
+
if (!props.isTriState) initialValue = initialValue || false;
|
|
11
|
+
|
|
12
|
+
const [value, setValue] = useState<boolean | undefined>(initialValue);
|
|
13
|
+
|
|
14
|
+
const trueIcon = props.trueIcon || GenesysDevIcons.AppCheck;
|
|
15
|
+
const falseIcon = props.falseIcon || GenesysDevIcons.AppTimes;
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (props.initialValue || props.value === value || (!props.isTriState && props.value === undefined)) return;
|
|
19
|
+
setValue(props.value);
|
|
20
|
+
}, [props.value]);
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (props.onChange) props.onChange(value);
|
|
24
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
25
|
+
}, [value]);
|
|
26
|
+
|
|
27
|
+
const toggleValue = () => {
|
|
28
|
+
if (props.disabled) return;
|
|
29
|
+
if (props.isTriState) {
|
|
30
|
+
if (value === undefined) setValue(true);
|
|
31
|
+
else if (value === true) setValue(false);
|
|
32
|
+
else setValue(undefined);
|
|
33
|
+
} else {
|
|
34
|
+
setValue(!value);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<DxLabel label={props.label} description={props.description} className={props.className}>
|
|
40
|
+
<div className={`dx-toggle-container${props.disabled ? ' disabled' : ''}`}>
|
|
41
|
+
<div className='dx-toggle' onClick={toggleValue}>
|
|
42
|
+
{value !== false ? <GenesysDevIcon icon={falseIcon} /> : undefined}
|
|
43
|
+
{value === true && props.isTriState ? <div className='clear-placeholder'> </div> : undefined}
|
|
44
|
+
<div className='slider'>{value !== undefined ? <GenesysDevIcon icon={value ? trueIcon : falseIcon} /> : undefined}</div>
|
|
45
|
+
{value === false && props.isTriState ? <div className='clear-placeholder'> </div> : undefined}
|
|
46
|
+
{value !== true ? <GenesysDevIcon icon={trueIcon} /> : undefined}
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</DxLabel>
|
|
50
|
+
);
|
|
51
|
+
}
|