@transferwise/components 0.0.0-experimental-e6637c6 → 0.0.0-experimental-6e04f56
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/build/Snackbar/Snackbar.js +48 -36
- package/build/Snackbar/Snackbar.js.map +1 -1
- package/build/Snackbar/Snackbar.mjs +48 -36
- package/build/Snackbar/Snackbar.mjs.map +1 -1
- package/build/types/Snackbar/Snackbar.d.ts +1 -1
- package/build/types/Snackbar/Snackbar.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/Snackbar/Snackbar.tsx +39 -38
|
@@ -10,22 +10,14 @@ var WithNextPortal = require('../WithNextPortal/WithNextPortal.js');
|
|
|
10
10
|
var jsxRuntime = require('react/jsx-runtime');
|
|
11
11
|
|
|
12
12
|
const CSS_TRANSITION_DURATION = 400;
|
|
13
|
-
const Snackbar =
|
|
14
|
-
action,
|
|
15
|
-
text,
|
|
16
|
-
timeout,
|
|
17
|
-
timestamp
|
|
18
|
-
}) => {
|
|
13
|
+
const Snackbar = props => {
|
|
19
14
|
const bodyRef = React.useRef(null);
|
|
20
15
|
const leaveTimeout = React.useRef(0);
|
|
21
16
|
const transitionTimeout = React.useRef(0);
|
|
22
|
-
/**
|
|
23
|
-
const committedProps = React.useRef(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
});
|
|
27
|
-
const [visible, setVisible] = React.useState(false);
|
|
28
|
-
const [notification, setNotification] = React.useState({
|
|
17
|
+
/** The props of the last committed render, as `this.props` was in the class component. */
|
|
18
|
+
const committedProps = React.useRef(props);
|
|
19
|
+
const [state, setState] = React.useState({
|
|
20
|
+
visible: false,
|
|
29
21
|
text: ''
|
|
30
22
|
});
|
|
31
23
|
React.useEffect(() => () => {
|
|
@@ -38,14 +30,14 @@ const Snackbar = ({
|
|
|
38
30
|
// updates are what keep this conversion behaviour-identical to the class component.
|
|
39
31
|
/* eslint-disable react-hooks/exhaustive-deps, react-hooks/set-state-in-effect */
|
|
40
32
|
React.useEffect(() => {
|
|
33
|
+
const previousProps = committedProps.current;
|
|
34
|
+
committedProps.current = props;
|
|
41
35
|
const {
|
|
42
|
-
|
|
43
|
-
timestamp: previousTimestamp
|
|
44
|
-
} = committedProps.current;
|
|
45
|
-
committedProps.current = {
|
|
36
|
+
action,
|
|
46
37
|
text,
|
|
38
|
+
timeout,
|
|
47
39
|
timestamp
|
|
48
|
-
};
|
|
40
|
+
} = props;
|
|
49
41
|
// shouldComponentUpdate bailed out whenever the incoming text was empty. That also blocked the
|
|
50
42
|
// updates scheduled below, so a Snackbar whose text is cleared stays on screen until it is
|
|
51
43
|
// given text again. Preserved here deliberately to keep this refactor behaviour-identical.
|
|
@@ -54,32 +46,52 @@ const Snackbar = ({
|
|
|
54
46
|
update();
|
|
55
47
|
}
|
|
56
48
|
};
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
49
|
+
const setLeaveTimeout = () => {
|
|
50
|
+
leaveTimeout.current = window.setTimeout(() => {
|
|
51
|
+
updateWhileTextIsPresent(() => setState(current => ({
|
|
52
|
+
...current,
|
|
53
|
+
visible: false
|
|
54
|
+
})));
|
|
55
|
+
}, timeout);
|
|
56
|
+
};
|
|
60
57
|
const show = () => {
|
|
61
|
-
|
|
62
|
-
|
|
58
|
+
setState(current => ({
|
|
59
|
+
...current,
|
|
60
|
+
visible: true,
|
|
63
61
|
action,
|
|
64
62
|
text
|
|
65
|
-
});
|
|
66
|
-
|
|
63
|
+
}));
|
|
64
|
+
setLeaveTimeout();
|
|
67
65
|
};
|
|
68
|
-
if (!
|
|
69
|
-
show();
|
|
66
|
+
if (!text) {
|
|
70
67
|
return;
|
|
71
68
|
}
|
|
72
|
-
if (
|
|
69
|
+
if (!previousProps.text) {
|
|
70
|
+
show();
|
|
71
|
+
} else if (previousProps.timestamp !== timestamp) {
|
|
73
72
|
window.clearTimeout(leaveTimeout.current);
|
|
74
|
-
if (visible) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
73
|
+
if (state.visible) {
|
|
74
|
+
setState(current => ({
|
|
75
|
+
...current,
|
|
76
|
+
visible: false
|
|
77
|
+
}));
|
|
78
|
+
transitionTimeout.current = window.setTimeout(() => {
|
|
79
|
+
updateWhileTextIsPresent(show);
|
|
80
|
+
}, CSS_TRANSITION_DURATION);
|
|
81
|
+
} else {
|
|
82
|
+
show();
|
|
78
83
|
}
|
|
79
|
-
show();
|
|
80
84
|
}
|
|
81
85
|
});
|
|
82
86
|
/* eslint-enable react-hooks/exhaustive-deps, react-hooks/set-state-in-effect */
|
|
87
|
+
const {
|
|
88
|
+
action,
|
|
89
|
+
text,
|
|
90
|
+
visible
|
|
91
|
+
} = state;
|
|
92
|
+
const {
|
|
93
|
+
timeout
|
|
94
|
+
} = props;
|
|
83
95
|
return /*#__PURE__*/jsxRuntime.jsx("div", {
|
|
84
96
|
className: "snackbar",
|
|
85
97
|
"aria-live": "assertive",
|
|
@@ -98,12 +110,12 @@ const Snackbar = ({
|
|
|
98
110
|
ref: bodyRef,
|
|
99
111
|
as: "span",
|
|
100
112
|
className: "snackbar__text",
|
|
101
|
-
children: [
|
|
113
|
+
children: [text, action ? /*#__PURE__*/jsxRuntime.jsx(Button_resolver.default, {
|
|
102
114
|
className: "snackbar__text__action",
|
|
103
115
|
v2: true,
|
|
104
116
|
size: "sm",
|
|
105
|
-
onClick:
|
|
106
|
-
children:
|
|
117
|
+
onClick: action.onClick,
|
|
118
|
+
children: action.label
|
|
107
119
|
}) : null]
|
|
108
120
|
})
|
|
109
121
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Snackbar.js","sources":["../../src/Snackbar/Snackbar.tsx"],"sourcesContent":["import { useEffect, useRef, useState } from 'react';\nimport { CSSTransition } from 'react-transition-group';\n\nimport Button from '../Button';\nimport Body from '../Body';\nimport type { ThemeDark, ThemeLight } from '../common';\nimport withNextPortal from '../WithNextPortal/WithNextPortal';\n\nconst CSS_TRANSITION_DURATION = 400;\n\nexport interface SnackbarProps {\n action?: {\n label: string;\n onClick?: React.MouseEventHandler<HTMLButtonElement>;\n };\n text: React.ReactNode;\n /** @deprecated */\n theme?: ThemeLight | ThemeDark;\n timeout: number;\n timestamp: number;\n}\n\
|
|
1
|
+
{"version":3,"file":"Snackbar.js","sources":["../../src/Snackbar/Snackbar.tsx"],"sourcesContent":["import { useEffect, useRef, useState } from 'react';\nimport { CSSTransition } from 'react-transition-group';\n\nimport Button from '../Button';\nimport Body from '../Body';\nimport type { ThemeDark, ThemeLight } from '../common';\nimport withNextPortal from '../WithNextPortal/WithNextPortal';\n\nconst CSS_TRANSITION_DURATION = 400;\n\nexport interface SnackbarProps {\n action?: {\n label: string;\n onClick?: React.MouseEventHandler<HTMLButtonElement>;\n };\n text: React.ReactNode;\n /** @deprecated */\n theme?: ThemeLight | ThemeDark;\n timeout: number;\n timestamp: number;\n}\n\ninterface SnackbarState extends Pick<SnackbarProps, 'action' | 'text' | 'theme'> {\n visible: boolean;\n}\n\nexport const Snackbar = (props: SnackbarProps) => {\n const bodyRef = useRef<HTMLSpanElement>(null);\n const leaveTimeout = useRef(0);\n const transitionTimeout = useRef(0);\n /** The props of the last committed render, as `this.props` was in the class component. */\n const committedProps = useRef(props);\n\n const [state, setState] = useState<SnackbarState>({\n visible: false,\n text: '',\n });\n\n useEffect(\n () => () => {\n window.clearTimeout(leaveTimeout.current);\n window.clearTimeout(transitionTimeout.current);\n },\n [],\n );\n\n // Runs after every render to mirror the componentDidUpdate lifecycle this component previously\n // relied on, including the shouldComponentUpdate guard that ignored every update without text.\n // Both rules below are disabled on purpose: the dependency-less effect and its synchronous state\n // updates are what keep this conversion behaviour-identical to the class component.\n /* eslint-disable react-hooks/exhaustive-deps, react-hooks/set-state-in-effect */\n useEffect(() => {\n const previousProps = committedProps.current;\n committedProps.current = props;\n\n const { action, text, timeout, timestamp } = props;\n\n // shouldComponentUpdate bailed out whenever the incoming text was empty. That also blocked the\n // updates scheduled below, so a Snackbar whose text is cleared stays on screen until it is\n // given text again. Preserved here deliberately to keep this refactor behaviour-identical.\n const updateWhileTextIsPresent = (update: () => void) => {\n if (committedProps.current.text) {\n update();\n }\n };\n\n const setLeaveTimeout = () => {\n leaveTimeout.current = window.setTimeout(() => {\n updateWhileTextIsPresent(() => setState((current) => ({ ...current, visible: false })));\n }, timeout);\n };\n\n const show = () => {\n setState((current) => ({ ...current, visible: true, action, text }));\n setLeaveTimeout();\n };\n\n if (!text) {\n return;\n }\n\n if (!previousProps.text) {\n show();\n } else if (previousProps.timestamp !== timestamp) {\n window.clearTimeout(leaveTimeout.current);\n\n if (state.visible) {\n setState((current) => ({ ...current, visible: false }));\n transitionTimeout.current = window.setTimeout(() => {\n updateWhileTextIsPresent(show);\n }, CSS_TRANSITION_DURATION);\n } else {\n show();\n }\n }\n });\n /* eslint-enable react-hooks/exhaustive-deps, react-hooks/set-state-in-effect */\n\n const { action, text, visible } = state;\n const { timeout } = props;\n\n return (\n <div className=\"snackbar\" aria-live=\"assertive\" role=\"alert\">\n <CSSTransition\n nodeRef={bodyRef}\n in={visible}\n classNames=\"snackbar__text-container\"\n timeout={{\n appear: 0,\n enter: timeout,\n exit: CSS_TRANSITION_DURATION,\n }}\n unmountOnExit\n >\n <Body ref={bodyRef} as=\"span\" className=\"snackbar__text\">\n {text}\n {action ? (\n <Button className=\"snackbar__text__action\" v2 size=\"sm\" onClick={action.onClick}>\n {action.label}\n </Button>\n ) : null}\n </Body>\n </CSSTransition>\n </div>\n );\n};\n\nexport default withNextPortal(Snackbar);\n"],"names":["CSS_TRANSITION_DURATION","Snackbar","props","bodyRef","useRef","leaveTimeout","transitionTimeout","committedProps","state","setState","useState","visible","text","useEffect","window","clearTimeout","current","previousProps","action","timeout","timestamp","updateWhileTextIsPresent","update","setLeaveTimeout","setTimeout","show","_jsx","className","role","children","CSSTransition","nodeRef","in","classNames","appear","enter","exit","unmountOnExit","_jsxs","Body","ref","as","Button","v2","size","onClick","label","withNextPortal"],"mappings":"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"}
|
|
@@ -6,22 +6,14 @@ import withNextPortalWrapper from '../WithNextPortal/WithNextPortal.mjs';
|
|
|
6
6
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
7
7
|
|
|
8
8
|
const CSS_TRANSITION_DURATION = 400;
|
|
9
|
-
const Snackbar =
|
|
10
|
-
action,
|
|
11
|
-
text,
|
|
12
|
-
timeout,
|
|
13
|
-
timestamp
|
|
14
|
-
}) => {
|
|
9
|
+
const Snackbar = props => {
|
|
15
10
|
const bodyRef = useRef(null);
|
|
16
11
|
const leaveTimeout = useRef(0);
|
|
17
12
|
const transitionTimeout = useRef(0);
|
|
18
|
-
/**
|
|
19
|
-
const committedProps = useRef(
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
});
|
|
23
|
-
const [visible, setVisible] = useState(false);
|
|
24
|
-
const [notification, setNotification] = useState({
|
|
13
|
+
/** The props of the last committed render, as `this.props` was in the class component. */
|
|
14
|
+
const committedProps = useRef(props);
|
|
15
|
+
const [state, setState] = useState({
|
|
16
|
+
visible: false,
|
|
25
17
|
text: ''
|
|
26
18
|
});
|
|
27
19
|
useEffect(() => () => {
|
|
@@ -34,14 +26,14 @@ const Snackbar = ({
|
|
|
34
26
|
// updates are what keep this conversion behaviour-identical to the class component.
|
|
35
27
|
/* eslint-disable react-hooks/exhaustive-deps, react-hooks/set-state-in-effect */
|
|
36
28
|
useEffect(() => {
|
|
29
|
+
const previousProps = committedProps.current;
|
|
30
|
+
committedProps.current = props;
|
|
37
31
|
const {
|
|
38
|
-
|
|
39
|
-
timestamp: previousTimestamp
|
|
40
|
-
} = committedProps.current;
|
|
41
|
-
committedProps.current = {
|
|
32
|
+
action,
|
|
42
33
|
text,
|
|
34
|
+
timeout,
|
|
43
35
|
timestamp
|
|
44
|
-
};
|
|
36
|
+
} = props;
|
|
45
37
|
// shouldComponentUpdate bailed out whenever the incoming text was empty. That also blocked the
|
|
46
38
|
// updates scheduled below, so a Snackbar whose text is cleared stays on screen until it is
|
|
47
39
|
// given text again. Preserved here deliberately to keep this refactor behaviour-identical.
|
|
@@ -50,32 +42,52 @@ const Snackbar = ({
|
|
|
50
42
|
update();
|
|
51
43
|
}
|
|
52
44
|
};
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
45
|
+
const setLeaveTimeout = () => {
|
|
46
|
+
leaveTimeout.current = window.setTimeout(() => {
|
|
47
|
+
updateWhileTextIsPresent(() => setState(current => ({
|
|
48
|
+
...current,
|
|
49
|
+
visible: false
|
|
50
|
+
})));
|
|
51
|
+
}, timeout);
|
|
52
|
+
};
|
|
56
53
|
const show = () => {
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
setState(current => ({
|
|
55
|
+
...current,
|
|
56
|
+
visible: true,
|
|
59
57
|
action,
|
|
60
58
|
text
|
|
61
|
-
});
|
|
62
|
-
|
|
59
|
+
}));
|
|
60
|
+
setLeaveTimeout();
|
|
63
61
|
};
|
|
64
|
-
if (!
|
|
65
|
-
show();
|
|
62
|
+
if (!text) {
|
|
66
63
|
return;
|
|
67
64
|
}
|
|
68
|
-
if (
|
|
65
|
+
if (!previousProps.text) {
|
|
66
|
+
show();
|
|
67
|
+
} else if (previousProps.timestamp !== timestamp) {
|
|
69
68
|
window.clearTimeout(leaveTimeout.current);
|
|
70
|
-
if (visible) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
69
|
+
if (state.visible) {
|
|
70
|
+
setState(current => ({
|
|
71
|
+
...current,
|
|
72
|
+
visible: false
|
|
73
|
+
}));
|
|
74
|
+
transitionTimeout.current = window.setTimeout(() => {
|
|
75
|
+
updateWhileTextIsPresent(show);
|
|
76
|
+
}, CSS_TRANSITION_DURATION);
|
|
77
|
+
} else {
|
|
78
|
+
show();
|
|
74
79
|
}
|
|
75
|
-
show();
|
|
76
80
|
}
|
|
77
81
|
});
|
|
78
82
|
/* eslint-enable react-hooks/exhaustive-deps, react-hooks/set-state-in-effect */
|
|
83
|
+
const {
|
|
84
|
+
action,
|
|
85
|
+
text,
|
|
86
|
+
visible
|
|
87
|
+
} = state;
|
|
88
|
+
const {
|
|
89
|
+
timeout
|
|
90
|
+
} = props;
|
|
79
91
|
return /*#__PURE__*/jsx("div", {
|
|
80
92
|
className: "snackbar",
|
|
81
93
|
"aria-live": "assertive",
|
|
@@ -94,12 +106,12 @@ const Snackbar = ({
|
|
|
94
106
|
ref: bodyRef,
|
|
95
107
|
as: "span",
|
|
96
108
|
className: "snackbar__text",
|
|
97
|
-
children: [
|
|
109
|
+
children: [text, action ? /*#__PURE__*/jsx(Button, {
|
|
98
110
|
className: "snackbar__text__action",
|
|
99
111
|
v2: true,
|
|
100
112
|
size: "sm",
|
|
101
|
-
onClick:
|
|
102
|
-
children:
|
|
113
|
+
onClick: action.onClick,
|
|
114
|
+
children: action.label
|
|
103
115
|
}) : null]
|
|
104
116
|
})
|
|
105
117
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Snackbar.mjs","sources":["../../src/Snackbar/Snackbar.tsx"],"sourcesContent":["import { useEffect, useRef, useState } from 'react';\nimport { CSSTransition } from 'react-transition-group';\n\nimport Button from '../Button';\nimport Body from '../Body';\nimport type { ThemeDark, ThemeLight } from '../common';\nimport withNextPortal from '../WithNextPortal/WithNextPortal';\n\nconst CSS_TRANSITION_DURATION = 400;\n\nexport interface SnackbarProps {\n action?: {\n label: string;\n onClick?: React.MouseEventHandler<HTMLButtonElement>;\n };\n text: React.ReactNode;\n /** @deprecated */\n theme?: ThemeLight | ThemeDark;\n timeout: number;\n timestamp: number;\n}\n\
|
|
1
|
+
{"version":3,"file":"Snackbar.mjs","sources":["../../src/Snackbar/Snackbar.tsx"],"sourcesContent":["import { useEffect, useRef, useState } from 'react';\nimport { CSSTransition } from 'react-transition-group';\n\nimport Button from '../Button';\nimport Body from '../Body';\nimport type { ThemeDark, ThemeLight } from '../common';\nimport withNextPortal from '../WithNextPortal/WithNextPortal';\n\nconst CSS_TRANSITION_DURATION = 400;\n\nexport interface SnackbarProps {\n action?: {\n label: string;\n onClick?: React.MouseEventHandler<HTMLButtonElement>;\n };\n text: React.ReactNode;\n /** @deprecated */\n theme?: ThemeLight | ThemeDark;\n timeout: number;\n timestamp: number;\n}\n\ninterface SnackbarState extends Pick<SnackbarProps, 'action' | 'text' | 'theme'> {\n visible: boolean;\n}\n\nexport const Snackbar = (props: SnackbarProps) => {\n const bodyRef = useRef<HTMLSpanElement>(null);\n const leaveTimeout = useRef(0);\n const transitionTimeout = useRef(0);\n /** The props of the last committed render, as `this.props` was in the class component. */\n const committedProps = useRef(props);\n\n const [state, setState] = useState<SnackbarState>({\n visible: false,\n text: '',\n });\n\n useEffect(\n () => () => {\n window.clearTimeout(leaveTimeout.current);\n window.clearTimeout(transitionTimeout.current);\n },\n [],\n );\n\n // Runs after every render to mirror the componentDidUpdate lifecycle this component previously\n // relied on, including the shouldComponentUpdate guard that ignored every update without text.\n // Both rules below are disabled on purpose: the dependency-less effect and its synchronous state\n // updates are what keep this conversion behaviour-identical to the class component.\n /* eslint-disable react-hooks/exhaustive-deps, react-hooks/set-state-in-effect */\n useEffect(() => {\n const previousProps = committedProps.current;\n committedProps.current = props;\n\n const { action, text, timeout, timestamp } = props;\n\n // shouldComponentUpdate bailed out whenever the incoming text was empty. That also blocked the\n // updates scheduled below, so a Snackbar whose text is cleared stays on screen until it is\n // given text again. Preserved here deliberately to keep this refactor behaviour-identical.\n const updateWhileTextIsPresent = (update: () => void) => {\n if (committedProps.current.text) {\n update();\n }\n };\n\n const setLeaveTimeout = () => {\n leaveTimeout.current = window.setTimeout(() => {\n updateWhileTextIsPresent(() => setState((current) => ({ ...current, visible: false })));\n }, timeout);\n };\n\n const show = () => {\n setState((current) => ({ ...current, visible: true, action, text }));\n setLeaveTimeout();\n };\n\n if (!text) {\n return;\n }\n\n if (!previousProps.text) {\n show();\n } else if (previousProps.timestamp !== timestamp) {\n window.clearTimeout(leaveTimeout.current);\n\n if (state.visible) {\n setState((current) => ({ ...current, visible: false }));\n transitionTimeout.current = window.setTimeout(() => {\n updateWhileTextIsPresent(show);\n }, CSS_TRANSITION_DURATION);\n } else {\n show();\n }\n }\n });\n /* eslint-enable react-hooks/exhaustive-deps, react-hooks/set-state-in-effect */\n\n const { action, text, visible } = state;\n const { timeout } = props;\n\n return (\n <div className=\"snackbar\" aria-live=\"assertive\" role=\"alert\">\n <CSSTransition\n nodeRef={bodyRef}\n in={visible}\n classNames=\"snackbar__text-container\"\n timeout={{\n appear: 0,\n enter: timeout,\n exit: CSS_TRANSITION_DURATION,\n }}\n unmountOnExit\n >\n <Body ref={bodyRef} as=\"span\" className=\"snackbar__text\">\n {text}\n {action ? (\n <Button className=\"snackbar__text__action\" v2 size=\"sm\" onClick={action.onClick}>\n {action.label}\n </Button>\n ) : null}\n </Body>\n </CSSTransition>\n </div>\n );\n};\n\nexport default withNextPortal(Snackbar);\n"],"names":["CSS_TRANSITION_DURATION","Snackbar","props","bodyRef","useRef","leaveTimeout","transitionTimeout","committedProps","state","setState","useState","visible","text","useEffect","window","clearTimeout","current","previousProps","action","timeout","timestamp","updateWhileTextIsPresent","update","setLeaveTimeout","setTimeout","show","_jsx","className","role","children","CSSTransition","nodeRef","in","classNames","appear","enter","exit","unmountOnExit","_jsxs","Body","ref","as","Button","v2","size","onClick","label","withNextPortal"],"mappings":"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"}
|
|
@@ -10,7 +10,7 @@ export interface SnackbarProps {
|
|
|
10
10
|
timeout: number;
|
|
11
11
|
timestamp: number;
|
|
12
12
|
}
|
|
13
|
-
export declare const Snackbar: (
|
|
13
|
+
export declare const Snackbar: (props: SnackbarProps) => import("react").JSX.Element;
|
|
14
14
|
declare const _default: (props: SnackbarProps) => import("react").ReactPortal | null;
|
|
15
15
|
export default _default;
|
|
16
16
|
//# sourceMappingURL=Snackbar.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Snackbar.d.ts","sourceRoot":"","sources":["../../../src/Snackbar/Snackbar.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAKvD,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE;QACP,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,CAAC,EAAE,KAAK,CAAC,iBAAiB,CAAC,iBAAiB,CAAC,CAAC;KACtD,CAAC;IACF,IAAI,EAAE,KAAK,CAAC,SAAS,CAAC;IACtB,kBAAkB;IAClB,KAAK,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;
|
|
1
|
+
{"version":3,"file":"Snackbar.d.ts","sourceRoot":"","sources":["../../../src/Snackbar/Snackbar.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAKvD,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE;QACP,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,CAAC,EAAE,KAAK,CAAC,iBAAiB,CAAC,iBAAiB,CAAC,CAAC;KACtD,CAAC;IACF,IAAI,EAAE,KAAK,CAAC,SAAS,CAAC;IACtB,kBAAkB;IAClB,KAAK,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAMD,eAAO,MAAM,QAAQ,GAAI,OAAO,aAAa,gCAmG5C,CAAC;;AAEF,wBAAwC"}
|
package/package.json
CHANGED
|
@@ -20,17 +20,21 @@ export interface SnackbarProps {
|
|
|
20
20
|
timestamp: number;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
interface SnackbarState extends Pick<SnackbarProps, 'action' | 'text' | 'theme'> {
|
|
24
|
+
visible: boolean;
|
|
25
|
+
}
|
|
24
26
|
|
|
25
|
-
export const Snackbar = (
|
|
27
|
+
export const Snackbar = (props: SnackbarProps) => {
|
|
26
28
|
const bodyRef = useRef<HTMLSpanElement>(null);
|
|
27
29
|
const leaveTimeout = useRef(0);
|
|
28
30
|
const transitionTimeout = useRef(0);
|
|
29
|
-
/**
|
|
30
|
-
const committedProps = useRef(
|
|
31
|
+
/** The props of the last committed render, as `this.props` was in the class component. */
|
|
32
|
+
const committedProps = useRef(props);
|
|
31
33
|
|
|
32
|
-
const [
|
|
33
|
-
|
|
34
|
+
const [state, setState] = useState<SnackbarState>({
|
|
35
|
+
visible: false,
|
|
36
|
+
text: '',
|
|
37
|
+
});
|
|
34
38
|
|
|
35
39
|
useEffect(
|
|
36
40
|
() => () => {
|
|
@@ -46,8 +50,10 @@ export const Snackbar = ({ action, text, timeout, timestamp }: SnackbarProps) =>
|
|
|
46
50
|
// updates are what keep this conversion behaviour-identical to the class component.
|
|
47
51
|
/* eslint-disable react-hooks/exhaustive-deps, react-hooks/set-state-in-effect */
|
|
48
52
|
useEffect(() => {
|
|
49
|
-
const
|
|
50
|
-
committedProps.current =
|
|
53
|
+
const previousProps = committedProps.current;
|
|
54
|
+
committedProps.current = props;
|
|
55
|
+
|
|
56
|
+
const { action, text, timeout, timestamp } = props;
|
|
51
57
|
|
|
52
58
|
// shouldComponentUpdate bailed out whenever the incoming text was empty. That also blocked the
|
|
53
59
|
// updates scheduled below, so a Snackbar whose text is cleared stays on screen until it is
|
|
@@ -58,41 +64,41 @@ export const Snackbar = ({ action, text, timeout, timestamp }: SnackbarProps) =>
|
|
|
58
64
|
}
|
|
59
65
|
};
|
|
60
66
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
67
|
+
const setLeaveTimeout = () => {
|
|
68
|
+
leaveTimeout.current = window.setTimeout(() => {
|
|
69
|
+
updateWhileTextIsPresent(() => setState((current) => ({ ...current, visible: false })));
|
|
70
|
+
}, timeout);
|
|
71
|
+
};
|
|
64
72
|
|
|
65
73
|
const show = () => {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
leaveTimeout.current = window.setTimeout(
|
|
69
|
-
() => updateWhileTextIsPresent(() => setVisible(false)),
|
|
70
|
-
timeout,
|
|
71
|
-
);
|
|
74
|
+
setState((current) => ({ ...current, visible: true, action, text }));
|
|
75
|
+
setLeaveTimeout();
|
|
72
76
|
};
|
|
73
77
|
|
|
74
|
-
if (!
|
|
75
|
-
show();
|
|
78
|
+
if (!text) {
|
|
76
79
|
return;
|
|
77
80
|
}
|
|
78
81
|
|
|
79
|
-
if (
|
|
82
|
+
if (!previousProps.text) {
|
|
83
|
+
show();
|
|
84
|
+
} else if (previousProps.timestamp !== timestamp) {
|
|
80
85
|
window.clearTimeout(leaveTimeout.current);
|
|
81
86
|
|
|
82
|
-
if (visible) {
|
|
83
|
-
|
|
84
|
-
transitionTimeout.current = window.setTimeout(
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
if (state.visible) {
|
|
88
|
+
setState((current) => ({ ...current, visible: false }));
|
|
89
|
+
transitionTimeout.current = window.setTimeout(() => {
|
|
90
|
+
updateWhileTextIsPresent(show);
|
|
91
|
+
}, CSS_TRANSITION_DURATION);
|
|
92
|
+
} else {
|
|
93
|
+
show();
|
|
89
94
|
}
|
|
90
|
-
|
|
91
|
-
show();
|
|
92
95
|
}
|
|
93
96
|
});
|
|
94
97
|
/* eslint-enable react-hooks/exhaustive-deps, react-hooks/set-state-in-effect */
|
|
95
98
|
|
|
99
|
+
const { action, text, visible } = state;
|
|
100
|
+
const { timeout } = props;
|
|
101
|
+
|
|
96
102
|
return (
|
|
97
103
|
<div className="snackbar" aria-live="assertive" role="alert">
|
|
98
104
|
<CSSTransition
|
|
@@ -107,15 +113,10 @@ export const Snackbar = ({ action, text, timeout, timestamp }: SnackbarProps) =>
|
|
|
107
113
|
unmountOnExit
|
|
108
114
|
>
|
|
109
115
|
<Body ref={bodyRef} as="span" className="snackbar__text">
|
|
110
|
-
{
|
|
111
|
-
{
|
|
112
|
-
<Button
|
|
113
|
-
|
|
114
|
-
v2
|
|
115
|
-
size="sm"
|
|
116
|
-
onClick={notification.action.onClick}
|
|
117
|
-
>
|
|
118
|
-
{notification.action.label}
|
|
116
|
+
{text}
|
|
117
|
+
{action ? (
|
|
118
|
+
<Button className="snackbar__text__action" v2 size="sm" onClick={action.onClick}>
|
|
119
|
+
{action.label}
|
|
119
120
|
</Button>
|
|
120
121
|
) : null}
|
|
121
122
|
</Body>
|