@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.
@@ -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
- /** Mirrors the props of the last committed render, as `this.props` did on the class component. */
23
- const committedProps = React.useRef({
24
- text,
25
- timestamp
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
- text: previousText,
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
- if (!text) {
58
- return;
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
- setVisible(true);
62
- setNotification({
58
+ setState(current => ({
59
+ ...current,
60
+ visible: true,
63
61
  action,
64
62
  text
65
- });
66
- leaveTimeout.current = window.setTimeout(() => updateWhileTextIsPresent(() => setVisible(false)), timeout);
63
+ }));
64
+ setLeaveTimeout();
67
65
  };
68
- if (!previousText) {
69
- show();
66
+ if (!text) {
70
67
  return;
71
68
  }
72
- if (previousTimestamp !== timestamp) {
69
+ if (!previousProps.text) {
70
+ show();
71
+ } else if (previousProps.timestamp !== timestamp) {
73
72
  window.clearTimeout(leaveTimeout.current);
74
- if (visible) {
75
- setVisible(false);
76
- transitionTimeout.current = window.setTimeout(() => updateWhileTextIsPresent(show), CSS_TRANSITION_DURATION);
77
- return;
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: [notification.text, notification.action ? /*#__PURE__*/jsxRuntime.jsx(Button_resolver.default, {
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: notification.action.onClick,
106
- children: notification.action.label
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\ntype SnackbarNotification = Pick<SnackbarProps, 'action' | 'text'>;\n\nexport const Snackbar = ({ action, text, timeout, timestamp }: SnackbarProps) => {\n const bodyRef = useRef<HTMLSpanElement>(null);\n const leaveTimeout = useRef(0);\n const transitionTimeout = useRef(0);\n /** Mirrors the props of the last committed render, as `this.props` did on the class component. */\n const committedProps = useRef({ text, timestamp });\n\n const [visible, setVisible] = useState(false);\n const [notification, setNotification] = useState<SnackbarNotification>({ text: '' });\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 { text: previousText, timestamp: previousTimestamp } = committedProps.current;\n committedProps.current = { text, timestamp };\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 if (!text) {\n return;\n }\n\n const show = () => {\n setVisible(true);\n setNotification({ action, text });\n leaveTimeout.current = window.setTimeout(\n () => updateWhileTextIsPresent(() => setVisible(false)),\n timeout,\n );\n };\n\n if (!previousText) {\n show();\n return;\n }\n\n if (previousTimestamp !== timestamp) {\n window.clearTimeout(leaveTimeout.current);\n\n if (visible) {\n setVisible(false);\n transitionTimeout.current = window.setTimeout(\n () => updateWhileTextIsPresent(show),\n CSS_TRANSITION_DURATION,\n );\n return;\n }\n\n show();\n }\n });\n /* eslint-enable react-hooks/exhaustive-deps, react-hooks/set-state-in-effect */\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 {notification.text}\n {notification.action ? (\n <Button\n className=\"snackbar__text__action\"\n v2\n size=\"sm\"\n onClick={notification.action.onClick}\n >\n {notification.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","action","text","timeout","timestamp","bodyRef","useRef","leaveTimeout","transitionTimeout","committedProps","visible","setVisible","useState","notification","setNotification","useEffect","window","clearTimeout","current","previousText","previousTimestamp","updateWhileTextIsPresent","update","show","setTimeout","_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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"}
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
- /** Mirrors the props of the last committed render, as `this.props` did on the class component. */
19
- const committedProps = useRef({
20
- text,
21
- timestamp
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
- text: previousText,
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
- if (!text) {
54
- return;
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
- setVisible(true);
58
- setNotification({
54
+ setState(current => ({
55
+ ...current,
56
+ visible: true,
59
57
  action,
60
58
  text
61
- });
62
- leaveTimeout.current = window.setTimeout(() => updateWhileTextIsPresent(() => setVisible(false)), timeout);
59
+ }));
60
+ setLeaveTimeout();
63
61
  };
64
- if (!previousText) {
65
- show();
62
+ if (!text) {
66
63
  return;
67
64
  }
68
- if (previousTimestamp !== timestamp) {
65
+ if (!previousProps.text) {
66
+ show();
67
+ } else if (previousProps.timestamp !== timestamp) {
69
68
  window.clearTimeout(leaveTimeout.current);
70
- if (visible) {
71
- setVisible(false);
72
- transitionTimeout.current = window.setTimeout(() => updateWhileTextIsPresent(show), CSS_TRANSITION_DURATION);
73
- return;
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: [notification.text, notification.action ? /*#__PURE__*/jsx(Button, {
109
+ children: [text, action ? /*#__PURE__*/jsx(Button, {
98
110
  className: "snackbar__text__action",
99
111
  v2: true,
100
112
  size: "sm",
101
- onClick: notification.action.onClick,
102
- children: notification.action.label
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\ntype SnackbarNotification = Pick<SnackbarProps, 'action' | 'text'>;\n\nexport const Snackbar = ({ action, text, timeout, timestamp }: SnackbarProps) => {\n const bodyRef = useRef<HTMLSpanElement>(null);\n const leaveTimeout = useRef(0);\n const transitionTimeout = useRef(0);\n /** Mirrors the props of the last committed render, as `this.props` did on the class component. */\n const committedProps = useRef({ text, timestamp });\n\n const [visible, setVisible] = useState(false);\n const [notification, setNotification] = useState<SnackbarNotification>({ text: '' });\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 { text: previousText, timestamp: previousTimestamp } = committedProps.current;\n committedProps.current = { text, timestamp };\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 if (!text) {\n return;\n }\n\n const show = () => {\n setVisible(true);\n setNotification({ action, text });\n leaveTimeout.current = window.setTimeout(\n () => updateWhileTextIsPresent(() => setVisible(false)),\n timeout,\n );\n };\n\n if (!previousText) {\n show();\n return;\n }\n\n if (previousTimestamp !== timestamp) {\n window.clearTimeout(leaveTimeout.current);\n\n if (visible) {\n setVisible(false);\n transitionTimeout.current = window.setTimeout(\n () => updateWhileTextIsPresent(show),\n CSS_TRANSITION_DURATION,\n );\n return;\n }\n\n show();\n }\n });\n /* eslint-enable react-hooks/exhaustive-deps, react-hooks/set-state-in-effect */\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 {notification.text}\n {notification.action ? (\n <Button\n className=\"snackbar__text__action\"\n v2\n size=\"sm\"\n onClick={notification.action.onClick}\n >\n {notification.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","action","text","timeout","timestamp","bodyRef","useRef","leaveTimeout","transitionTimeout","committedProps","visible","setVisible","useState","notification","setNotification","useEffect","window","clearTimeout","current","previousText","previousTimestamp","updateWhileTextIsPresent","update","show","setTimeout","_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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"}
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: ({ action, text, timeout, timestamp }: SnackbarProps) => import("react").JSX.Element;
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;AAID,eAAO,MAAM,QAAQ,GAAI,sCAAsC,aAAa,gCAoG3E,CAAC;;AAEF,wBAAwC"}
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@transferwise/components",
3
- "version": "0.0.0-experimental-e6637c6",
3
+ "version": "0.0.0-experimental-6e04f56",
4
4
  "description": "Neptune React components",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -20,17 +20,21 @@ export interface SnackbarProps {
20
20
  timestamp: number;
21
21
  }
22
22
 
23
- type SnackbarNotification = Pick<SnackbarProps, 'action' | 'text'>;
23
+ interface SnackbarState extends Pick<SnackbarProps, 'action' | 'text' | 'theme'> {
24
+ visible: boolean;
25
+ }
24
26
 
25
- export const Snackbar = ({ action, text, timeout, timestamp }: SnackbarProps) => {
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
- /** Mirrors the props of the last committed render, as `this.props` did on the class component. */
30
- const committedProps = useRef({ text, timestamp });
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 [visible, setVisible] = useState(false);
33
- const [notification, setNotification] = useState<SnackbarNotification>({ text: '' });
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 { text: previousText, timestamp: previousTimestamp } = committedProps.current;
50
- committedProps.current = { text, timestamp };
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
- if (!text) {
62
- return;
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
- setVisible(true);
67
- setNotification({ action, text });
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 (!previousText) {
75
- show();
78
+ if (!text) {
76
79
  return;
77
80
  }
78
81
 
79
- if (previousTimestamp !== timestamp) {
82
+ if (!previousProps.text) {
83
+ show();
84
+ } else if (previousProps.timestamp !== timestamp) {
80
85
  window.clearTimeout(leaveTimeout.current);
81
86
 
82
- if (visible) {
83
- setVisible(false);
84
- transitionTimeout.current = window.setTimeout(
85
- () => updateWhileTextIsPresent(show),
86
- CSS_TRANSITION_DURATION,
87
- );
88
- return;
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
- {notification.text}
111
- {notification.action ? (
112
- <Button
113
- className="snackbar__text__action"
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>