@dvrd/dvr-controls 1.0.68 → 1.0.70
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/package.json +1 -1
- package/src/js/select/dvrdMultiSelect.tsx +1 -1
- package/src/js/snackbar/snackbar.tsx +34 -47
- package/src/js/snackbar/snackbarController.tsx +61 -79
- package/src/js/snackbar/style/snackbar.scss +1 -1
- package/src/js/util/eventUtil.ts +2 -2
- package/src/js/util/interfaces.ts +3 -2
package/package.json
CHANGED
|
@@ -159,7 +159,7 @@ export default function DvrdMultiSelect(props: Props) {
|
|
|
159
159
|
const label = selectableItemLabel ?? item.label;
|
|
160
160
|
if (['number', 'string'].includes(typeof label)) return (
|
|
161
161
|
<label key={index} className={classNames('dvrd-select-item', isSelected && 'selected', itemClassName)}
|
|
162
|
-
onClick={onClickItem(value)}>{
|
|
162
|
+
onClick={onClickItem(value)}>{isSelected &&
|
|
163
163
|
<AwesomeIcon name='check' className='check-icon'/>} {label}</label>
|
|
164
164
|
);
|
|
165
165
|
const labelElement: ReactElement = label as ReactElement;
|
|
@@ -4,69 +4,56 @@
|
|
|
4
4
|
|
|
5
5
|
import './style/snackbar.scss';
|
|
6
6
|
|
|
7
|
-
import React from 'react';
|
|
7
|
+
import React, {CSSProperties, MouseEventHandler, ReactElement, useContext, useMemo} from 'react';
|
|
8
8
|
import classNames from 'classnames';
|
|
9
9
|
import {convertColor} from "../util/colorUtil";
|
|
10
10
|
import {ControlContext} from "../util/controlContext";
|
|
11
11
|
import {Snack} from "../util/interfaces";
|
|
12
12
|
|
|
13
|
-
interface
|
|
14
|
-
onClick:
|
|
13
|
+
interface Props {
|
|
14
|
+
onClick: MouseEventHandler;
|
|
15
15
|
snack: Snack | null;
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
active: boolean;
|
|
17
|
+
containerClass?: string;
|
|
18
|
+
textClass?: string;
|
|
18
19
|
backgroundColor?: string;
|
|
19
20
|
textColor?: string;
|
|
20
|
-
active: boolean;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
export
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
// noinspection JSDeprecatedSymbols
|
|
41
|
-
return convertColor(this.context.contrastColor);
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
getBackgroundStyle = (): { backgroundColor: string } => ({backgroundColor: this.getBackgroundColor()});
|
|
45
|
-
|
|
46
|
-
getTextStyle = (): { color: string } => ({color: this.getTextColor()});
|
|
47
|
-
|
|
48
|
-
renderText = () => {
|
|
49
|
-
const {snack, textClass} = this.props;
|
|
23
|
+
export default function Snackbar(props: Props) {
|
|
24
|
+
const context = useContext(ControlContext);
|
|
25
|
+
const {textClass, containerClass, active, onClick, snack} = props;
|
|
26
|
+
const backgroundStyle: CSSProperties = useMemo(() => {
|
|
27
|
+
let backgroundColor: string = context.baseColor;
|
|
28
|
+
if (snack?.config?.backgroundColor) backgroundColor = snack.config.backgroundColor;
|
|
29
|
+
else if (props.backgroundColor) backgroundColor = props.backgroundColor;
|
|
30
|
+
return {backgroundColor: convertColor(backgroundColor)};
|
|
31
|
+
}, [props.backgroundColor, props.snack, context.baseColor])
|
|
32
|
+
const textStyle: CSSProperties = useMemo(() => {
|
|
33
|
+
let color: string = context.contrastColor;
|
|
34
|
+
if (snack?.config?.textColor) color = snack.config.textColor;
|
|
35
|
+
else if (props.textColor) color = props.textColor;
|
|
36
|
+
return {color: convertColor(color)};
|
|
37
|
+
}, [props.textColor, context.contrastColor, props.snack]);
|
|
38
|
+
|
|
39
|
+
function renderText() {
|
|
50
40
|
if (!snack) return null;
|
|
51
41
|
const {text} = snack;
|
|
52
|
-
let element:
|
|
53
|
-
if (typeof text === 'string')
|
|
54
|
-
<label className={classNames('
|
|
42
|
+
let element: ReactElement | null = null;
|
|
43
|
+
if (typeof text === 'string')
|
|
44
|
+
element = <label className={classNames('snackbar-text', textClass)} style={textStyle}>{text}</label>;
|
|
55
45
|
else if (text) {
|
|
56
|
-
const className =
|
|
57
|
-
|
|
46
|
+
const className = classNames(text.props?.className, 'snackbar-text', textClass);
|
|
47
|
+
const style = {...text.props?.style, ...textStyle};
|
|
58
48
|
element = React.cloneElement(text, {className, style});
|
|
59
49
|
}
|
|
60
50
|
return element;
|
|
61
51
|
}
|
|
62
52
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
</div>
|
|
70
|
-
);
|
|
71
|
-
};
|
|
53
|
+
return (
|
|
54
|
+
<div className={classNames('snackbar', active && 'active', containerClass)} style={backgroundStyle}
|
|
55
|
+
onClick={onClick}>
|
|
56
|
+
{renderText()}
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
72
59
|
}
|
|
@@ -2,103 +2,85 @@
|
|
|
2
2
|
* Copyright (c) 2024. Dave van Rijn Development
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import React from 'react';
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
8
|
-
import WithEvents from '../events/withEvents';
|
|
5
|
+
import React, {useEffect, useRef, useState} from 'react';
|
|
6
|
+
import Snackbar from "./snackbar";
|
|
7
|
+
import {Snack} from "../util/interfaces";
|
|
9
8
|
import delay from 'lodash.delay';
|
|
9
|
+
import defer from 'lodash.defer';
|
|
10
|
+
import {useEvent} from "../util/componentUtil";
|
|
10
11
|
|
|
11
12
|
interface Props {
|
|
12
|
-
|
|
13
|
-
textClass: string;
|
|
14
|
-
activeTime: number;
|
|
13
|
+
activeTime?: number;
|
|
15
14
|
backgroundColor?: string;
|
|
16
15
|
textColor?: string;
|
|
17
16
|
maxSnacks?: number;
|
|
18
17
|
noDuplicates?: boolean;
|
|
18
|
+
containerClass?: string;
|
|
19
|
+
textClass?: string;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
static defaultProps = {
|
|
29
|
-
activeTime: 3000,
|
|
30
|
-
containerClass: '',
|
|
31
|
-
textClass: '',
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
state: State = {
|
|
35
|
-
active: false,
|
|
36
|
-
snack: null,
|
|
37
|
-
config: {}
|
|
38
|
-
};
|
|
22
|
+
export default function SnackbarController(props: Props) {
|
|
23
|
+
const {containerClass, textClass, textColor, backgroundColor, noDuplicates, maxSnacks, activeTime} = props;
|
|
24
|
+
const [active, setActive] = useState(false);
|
|
25
|
+
const [snack, setSnack] = useState<Snack | null>(null);
|
|
26
|
+
const snackQueue = useRef<Array<Snack>>([]);
|
|
27
|
+
const timeoutID = useRef<number | null>(null);
|
|
28
|
+
useEvent('onShowSnackbar', onAddSnack);
|
|
39
29
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
onAddSnack = (snack: Snack) => {
|
|
44
|
-
const {maxSnacks, noDuplicates} = this.props;
|
|
45
|
-
if (noDuplicates && this.isDuplicate(snack)) return;
|
|
46
|
-
this.snackQueue.push(snack);
|
|
30
|
+
function onAddSnack(snack: Snack) {
|
|
31
|
+
if (noDuplicates && isDuplicate(snack)) return;
|
|
32
|
+
snackQueue.current.push(snack);
|
|
47
33
|
if (maxSnacks !== undefined)
|
|
48
|
-
while (
|
|
49
|
-
|
|
50
|
-
if (!
|
|
51
|
-
|
|
52
|
-
};
|
|
34
|
+
while (snackQueue.current.length > maxSnacks - 1)
|
|
35
|
+
snackQueue.current.shift();
|
|
36
|
+
if (!active) activate();
|
|
37
|
+
}
|
|
53
38
|
|
|
54
|
-
onClick
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
};
|
|
39
|
+
function onClick() {
|
|
40
|
+
clearTimeout();
|
|
41
|
+
deactivate();
|
|
42
|
+
}
|
|
59
43
|
|
|
60
|
-
isDuplicate
|
|
61
|
-
return
|
|
44
|
+
function isDuplicate(_snack: Snack): boolean {
|
|
45
|
+
return snack?.text === _snack.text || snackQueue.current.pop()?.text === _snack.text;
|
|
62
46
|
}
|
|
63
47
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}, 200);
|
|
73
|
-
})
|
|
74
|
-
};
|
|
48
|
+
function activate() {
|
|
49
|
+
const snack: Snack | undefined = snackQueue.current.pop();
|
|
50
|
+
if (!snack) return;
|
|
51
|
+
const _activeTime = snack.config?.duration ?? activeTime ?? 3000;
|
|
52
|
+
setSnack(snack);
|
|
53
|
+
defer(() => setActive(true));
|
|
54
|
+
timeoutID.current = window.setTimeout(deactivate, _activeTime);
|
|
55
|
+
}
|
|
75
56
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
57
|
+
function deactivate() {
|
|
58
|
+
setActive(false);
|
|
59
|
+
// Clear snack after 200ms (css transition)
|
|
60
|
+
delay(() => {
|
|
61
|
+
setSnack(null);
|
|
62
|
+
defer(() => {
|
|
63
|
+
if (snackQueue.current.length)
|
|
64
|
+
// Open next
|
|
65
|
+
timeoutID.current = window.setTimeout(activate, 500);
|
|
82
66
|
})
|
|
83
|
-
}
|
|
84
|
-
}
|
|
67
|
+
}, 200);
|
|
68
|
+
}
|
|
85
69
|
|
|
86
|
-
|
|
87
|
-
if (
|
|
88
|
-
window.clearTimeout(
|
|
89
|
-
|
|
70
|
+
function clearTimeout() {
|
|
71
|
+
if (timeoutID.current)
|
|
72
|
+
window.clearTimeout(timeoutID.current);
|
|
73
|
+
timeoutID.current = null;
|
|
74
|
+
}
|
|
90
75
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
return function () {
|
|
78
|
+
clearTimeout();
|
|
79
|
+
}
|
|
80
|
+
}, []);
|
|
94
81
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
<Snackbar snack={snack} containerClass={containerClass} textClass={textClass} active={active}
|
|
100
|
-
onClick={this.onClick} textColor={textColor} backgroundColor={backgroundColor}/>
|
|
101
|
-
</WithEvents>
|
|
102
|
-
)
|
|
103
|
-
};
|
|
82
|
+
return (
|
|
83
|
+
<Snackbar snack={snack} containerClass={containerClass} textClass={textClass} active={active} onClick={onClick}
|
|
84
|
+
textColor={textColor} backgroundColor={backgroundColor}/>
|
|
85
|
+
);
|
|
104
86
|
}
|
package/src/js/util/eventUtil.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import React from 'react';
|
|
6
6
|
import {DialogActions} from "../dialog/dialogController";
|
|
7
|
-
import {
|
|
7
|
+
import {SnackConfig} from "./interfaces";
|
|
8
8
|
|
|
9
9
|
interface ListenerShape {
|
|
10
10
|
component: string,
|
|
@@ -64,6 +64,6 @@ export const showDialog = (message: string | React.ReactElement, title: string =
|
|
|
64
64
|
dispatchCustomEvent('onOpenDialog', {message, title, actions, transparent, persistent});
|
|
65
65
|
};
|
|
66
66
|
|
|
67
|
-
export const showSnackbar = (text: string | React.ReactElement, config?:
|
|
67
|
+
export const showSnackbar = (text: string | React.ReactElement, config?: SnackConfig) => {
|
|
68
68
|
dispatchCustomEvent('onShowSnackbar', {text, config});
|
|
69
69
|
};
|
|
@@ -79,9 +79,10 @@ export interface GroupedSelectItem {
|
|
|
79
79
|
style?: CSSProperties;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
export interface
|
|
82
|
+
export interface SnackConfig {
|
|
83
83
|
backgroundColor?: string;
|
|
84
84
|
textColor?: string;
|
|
85
|
+
duration?: number; // Duration in milliseconds
|
|
85
86
|
}
|
|
86
87
|
|
|
87
88
|
export interface PDFOptions {
|
|
@@ -247,6 +248,6 @@ export type RenderFile = {
|
|
|
247
248
|
}
|
|
248
249
|
export type Snack = {
|
|
249
250
|
text: string | React.ReactElement;
|
|
250
|
-
config?:
|
|
251
|
+
config?: SnackConfig;
|
|
251
252
|
}
|
|
252
253
|
export type SelectValueType = string | number;
|