@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dvrd/dvr-controls",
3
- "version": "1.0.68",
3
+ "version": "1.0.70",
4
4
  "description": "Custom web controls",
5
5
  "main": "index.ts",
6
6
  "files": [
@@ -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)}>{selected &&
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 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;