@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dvrd/dvr-controls",
3
- "version": "1.0.67",
3
+ "version": "1.0.69",
4
4
  "description": "Custom web controls",
5
5
  "main": "index.ts",
6
6
  "files": [
@@ -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 ViewProps {
14
- onClick: any;
13
+ interface Props {
14
+ onClick: MouseEventHandler;
15
15
  snack: Snack | null;
16
- containerClass: string;
17
- textClass: string;
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 class Snackbar extends React.Component<ViewProps> {
24
- // noinspection JSUnusedGlobalSymbols
25
- declare context: React.ContextType<typeof ControlContext>;
26
- static contextType = ControlContext;
27
-
28
- getBackgroundColor = (): string => {
29
- const {backgroundColor, snack} = this.props;
30
- if (snack?.config?.backgroundColor) return convertColor(snack.config.backgroundColor);
31
- if (backgroundColor) return convertColor(backgroundColor);
32
- // noinspection JSDeprecatedSymbols
33
- return convertColor(this.context.baseColor);
34
- };
35
-
36
- getTextColor = (): string => {
37
- const {textColor, snack} = this.props;
38
- if (snack?.config?.textColor) return convertColor(snack.config.textColor);
39
- if (textColor !== undefined && textColor !== null) return convertColor(textColor);
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: React.ReactNode = null;
53
- if (typeof text === 'string') element =
54
- <label className={classNames('snackBarText', textClass)} style={this.getTextStyle()}>{text}</label>;
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 = Object.assign({}, text.props?.className, classNames('snackBarText', textClass)),
57
- style = Object.assign({}, text.props?.style, this.getTextStyle());
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
- render = () => {
64
- const {containerClass, active, onClick} = this.props;
65
- return (
66
- <div className={classNames('snackbar', active && 'active', containerClass)}
67
- style={this.getBackgroundStyle()} onClick={onClick}>
68
- {this.renderText()}
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 {Snackbar} from "./snackbar";
7
- import {CustomAppEvent, DialogConfig, Snack} from "../util/interfaces";
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
- containerClass: string;
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
- interface State {
22
- active: boolean;
23
- snack: Snack | null;
24
- config: DialogConfig;
25
- }
26
-
27
- export default class SnackbarController extends React.Component<Props, State> {
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
- snackQueue: Snack[] = [];
41
- timeout: number | null = null;
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 (this.snackQueue.length > maxSnacks - 1)
49
- this.snackQueue.shift();
50
- if (!this.state.active)
51
- this.activate();
52
- };
34
+ while (snackQueue.current.length > maxSnacks - 1)
35
+ snackQueue.current.shift();
36
+ if (!active) activate();
37
+ }
53
38
 
54
- onClick = () => {
55
- if (this.timeout !== null)
56
- window.clearTimeout(this.timeout);
57
- this.deactivate();
58
- };
39
+ function onClick() {
40
+ clearTimeout();
41
+ deactivate();
42
+ }
59
43
 
60
- isDuplicate = (snack: Snack): boolean => {
61
- return this.state.snack?.text === snack.text || this.snackQueue.pop()?.text === snack.text;
44
+ function isDuplicate(_snack: Snack): boolean {
45
+ return snack?.text === _snack.text || snackQueue.current.pop()?.text === _snack.text;
62
46
  }
63
47
 
64
- deactivate = () => {
65
- this.setState({active: false}, () => {
66
- delay(() => {
67
- // Clear snack after 200ms (css transition)
68
- this.setState({snack: null}, () => {
69
- if (this.snackQueue.length > 0)
70
- this.timeout = window.setTimeout(this.activate, 500);
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
- activate = () => {
77
- const snack: Snack | undefined = this.snackQueue.pop();
78
- if (snack) {
79
- this.setState({snack}, () => {
80
- this.setState({active: true});
81
- this.timeout = window.setTimeout(this.deactivate, this.props.activeTime);
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
- componentWillUnmount = () => {
87
- if (this.timeout !== null)
88
- window.clearTimeout(this.timeout);
89
- };
70
+ function clearTimeout() {
71
+ if (timeoutID.current)
72
+ window.clearTimeout(timeoutID.current);
73
+ timeoutID.current = null;
74
+ }
90
75
 
91
- getEvents = (): CustomAppEvent[] => [
92
- {eventName: 'onShowSnackbar', handler: this.onAddSnack}
93
- ];
76
+ useEffect(() => {
77
+ return function () {
78
+ clearTimeout();
79
+ }
80
+ }, []);
94
81
 
95
- render = () => {
96
- const {snack, active} = this.state, {containerClass, textClass, textColor, backgroundColor} = this.props;
97
- return (
98
- <WithEvents events={this.getEvents()}>
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
  }
@@ -29,7 +29,7 @@
29
29
  transform: translateY(0) translateX(-50%);
30
30
  }
31
31
 
32
- .snackBarText {
32
+ .snackbar-text {
33
33
  text-align: center;
34
34
  user-select: none;
35
35
  }
@@ -4,7 +4,7 @@
4
4
 
5
5
  import React from 'react';
6
6
  import {DialogActions} from "../dialog/dialogController";
7
- import {DialogConfig} from "./interfaces";
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?: DialogConfig) => {
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 DialogConfig {
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?: DialogConfig;
251
+ config?: SnackConfig;
251
252
  }
252
253
  export type SelectValueType = string | number;