@dvrd/dvr-controls 1.0.67 → 1.0.69
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/date/dvrdDatePicker.tsx +5 -4
- 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
|
@@ -104,7 +104,7 @@ export default function DvrdDatePicker(props: Props) {
|
|
|
104
104
|
alwaysShowArrows={alwaysShowArrows} max={max} min={min}/>
|
|
105
105
|
{!!timeMode &&
|
|
106
106
|
<TimePicker onChange={onChangeTimePicker} onClose={onClosePicker} value={value}
|
|
107
|
-
open={timePickerOpen} timeMode={timeMode}/>}
|
|
107
|
+
open={timePickerOpen} timeMode={timeMode} closeOnChange={closeOnChange}/>}
|
|
108
108
|
<label className='error-label'>{error}</label>
|
|
109
109
|
</div>
|
|
110
110
|
)
|
|
@@ -432,6 +432,7 @@ interface TimePickerProps {
|
|
|
432
432
|
value: IDate | null;
|
|
433
433
|
open: boolean;
|
|
434
434
|
timeMode: DatePickerTimeMode;
|
|
435
|
+
closeOnChange?: boolean;
|
|
435
436
|
}
|
|
436
437
|
|
|
437
438
|
type TimePartValues = [string, string, string, string, string, string];
|
|
@@ -450,7 +451,7 @@ function timePartsToNumber(part1: string, part2: string): number {
|
|
|
450
451
|
}
|
|
451
452
|
|
|
452
453
|
function TimePicker(props: TimePickerProps) {
|
|
453
|
-
const {onChange, value, open, onClose, timeMode} = props;
|
|
454
|
+
const {onChange, value, open, onClose, timeMode, closeOnChange} = props;
|
|
454
455
|
const [timeParts, setTimeParts] = useState<TimePartValues>(dateToTimeParts(value));
|
|
455
456
|
const secondHourMax = useMemo(() => {
|
|
456
457
|
if (Number(timeParts[0]) === 2) return 3;
|
|
@@ -460,14 +461,14 @@ function TimePicker(props: TimePickerProps) {
|
|
|
460
461
|
|
|
461
462
|
function onClickNow() {
|
|
462
463
|
setTimeParts(dateToTimeParts(new IDate()));
|
|
464
|
+
if (closeOnChange)
|
|
465
|
+
onSubmit();
|
|
463
466
|
}
|
|
464
467
|
|
|
465
468
|
function onSubmit() {
|
|
466
|
-
console.log(timeParts);
|
|
467
469
|
const hours = timePartsToNumber(timeParts[0], timeParts[1]);
|
|
468
470
|
const minutes = timePartsToNumber(timeParts[2], timeParts[3]);
|
|
469
471
|
const seconds = timePartsToNumber(timeParts[4], timeParts[5]);
|
|
470
|
-
console.log(hours, minutes, seconds);
|
|
471
472
|
const _value = value?.clone() ?? new IDate();
|
|
472
473
|
onChange(_value.set({hours, minutes, seconds}));
|
|
473
474
|
onClose();
|
|
@@ -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;
|