@transferwise/components 46.165.0 → 46.165.1
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 +98 -101
- package/build/Snackbar/Snackbar.js.map +1 -1
- package/build/Snackbar/Snackbar.mjs +99 -102
- package/build/Snackbar/Snackbar.mjs.map +1 -1
- package/build/types/Body/Body.d.ts +2 -2
- package/build/types/Carousel/_stories/constants.d.ts +25 -0
- package/build/types/Carousel/_stories/constants.d.ts.map +1 -1
- package/build/types/IconButton/IconButton.d.ts +1 -1
- package/build/types/ListItem/_stories/variants/helpers.d.ts +38 -1
- package/build/types/ListItem/_stories/variants/helpers.d.ts.map +1 -1
- package/build/types/MoneyInput/MoneyInput.d.ts +1 -1
- package/build/types/MoneyInput/MoneyInput.d.ts.map +1 -1
- package/build/types/Snackbar/Snackbar.d.ts +1 -15
- package/build/types/Snackbar/Snackbar.d.ts.map +1 -1
- package/build/types/Title/Title.d.ts +2 -2
- package/build/types/Upload/Steps/UploadImageStep/UploadImageStep.d.ts +1 -1
- package/build/types/UploadInput/UploadItem/UploadItemLink.d.ts +1 -1
- package/package.json +1 -1
- package/src/Accordion/_stories/Accordion.test.story.tsx +74 -48
- package/src/AvatarLayout/_stories/AvatarLayout.test.story.tsx +46 -30
- package/src/Button/LegacyButton/_stories/LegacyButton.test.story.tsx +29 -19
- package/src/Button/_stories/Button.test.story.tsx +96 -62
- package/src/Calendar/_stories/Calendar.test.story.tsx +34 -21
- package/src/Carousel/_stories/Carousel.test.story.tsx +18 -8
- package/src/Carousel/_stories/constants.tsx +29 -4
- package/src/Checkbox/_stories/Checkbox.test.story.tsx +22 -13
- package/src/CheckboxButton/_stories/CheckboxButton.test.story.tsx +23 -14
- package/src/Chips/_stories/Chips.test.story.tsx +51 -36
- package/src/DateInput/_stories/DateInput.rtl.test.story.tsx +62 -12
- package/src/DateLookup/_stories/DateLookup.rtl.test.story.tsx +2 -2
- package/src/Decision/_stories/Decision.test.story.tsx +74 -58
- package/src/DefinitionList/_stories/DefinitionList.test.story.tsx +58 -42
- package/src/Drawer/_stories/Drawer.rtl.test.story.tsx +65 -38
- package/src/ExpressiveMoneyInput/_stories/ExpressiveMoneyInput.rtl.test.story.tsx +107 -74
- package/src/Field/_stories/Field.test.story.tsx +18 -18
- package/src/FlowNavigation/_stories/FlowNavigation.rtl.test.story.tsx +81 -51
- package/src/Header/_stories/Header.test.story.tsx +30 -18
- package/src/Info/_stories/Info.rtl.test.story.tsx +124 -50
- package/src/InputWithDisplayFormat/_stories/InputWithDisplayFormat.test.story.tsx +4 -4
- package/src/Inputs/InputGroup/_stories/InputGroup.test.story.tsx +9 -8
- package/src/Inputs/SearchInput/_stories/SearchInput.test.story.tsx +5 -3
- package/src/Inputs/SelectInput/_stories/SelectInput.rtl.test.story.tsx +108 -79
- package/src/Inputs/TextArea/_stories/TextArea.test.story.tsx +10 -10
- package/src/InstructionsList/_stories/InstructionsList.test.story.tsx +43 -16
- package/src/Label/_stories/Label.test.story.tsx +7 -7
- package/src/Link/_stories/Link.test.story.tsx +27 -13
- package/src/ListItem/_stories/ListItem.rtl.test.story.tsx +126 -6
- package/src/ListItem/_stories/variants/helpers.tsx +174 -126
- package/src/Loader/_stories/Loader.test.story.tsx +17 -10
- package/src/Modal/_stories/Modal.rtl.test.story.tsx +58 -39
- package/src/MoneyInput/_stories/MoneyInput.rtl.test.story.tsx +21 -3
- package/src/Nudge/_stories/Nudge.test.story.tsx +70 -21
- package/src/PhoneNumberInput/_stories/PhoneNumberInput.rtl.test.story.tsx +1 -1
- package/src/Popover/_stories/Popover.rtl.test.story.tsx +92 -49
- package/src/ProgressBar/_stories/ProgressBar.test.story.tsx +6 -6
- package/src/PromoCard/_stories/PromoCard.test.story.tsx +53 -29
- package/src/PromoCard/_stories/PromoCardGroup.test.story.tsx +60 -36
- package/src/Prompt/ActionPrompt/_stories/ActionPrompt.test.story.tsx +74 -38
- package/src/Prompt/CriticalBanner/_stories/CriticalBanner.test.story.tsx +68 -31
- package/src/Prompt/InfoPrompt/_stories/InfoPrompt.test.story.tsx +52 -22
- package/src/Prompt/InlinePrompt/_stories/InlinePrompt.test.story.tsx +36 -21
- package/src/Radio/_stories/Radio.test.story.tsx +60 -46
- package/src/RadioGroup/_stories/RadioGroup.test.story.tsx +70 -51
- package/src/Section/_stories/Section.test.story.tsx +30 -14
- package/src/SegmentedControl/_stories/SegmentedControl.test.story.tsx +38 -15
- package/src/SlidingPanel/_stories/SlidingPanel.rtl.test.story.tsx +58 -34
- package/src/Snackbar/Snackbar.test.tsx +16 -0
- package/src/Snackbar/Snackbar.tsx +91 -88
- package/src/Snackbar/_stories/Snackbar.rtl.test.story.tsx +24 -12
- package/src/Stepper/_stories/Stepper.test.story.tsx +27 -9
- package/src/Switch/_stories/Switch.test.story.tsx +20 -12
- package/src/Table/_stories/Table.test.story.tsx +107 -86
- package/src/Tabs/_stories/Tabs.test.story.tsx +92 -52
- package/src/Tag/_stories/Tag.test.story.tsx +27 -16
- package/src/TextareaWithDisplayFormat/_stories/TextareaWithDisplayFormat.test.story.tsx +4 -4
- package/src/Tooltip/_stories/Tooltip.rtl.test.story.tsx +68 -25
- package/src/Typeahead/_stories/Typeahead.rtl.test.story.tsx +4 -4
- package/src/Upload/_stories/Upload.rtl.test.story.tsx +49 -26
- package/src/UploadInput/_stories/UploadInput.test.story.tsx +63 -41
- package/src/_deprecated/Alert/_stories/Alert.test.story.tsx +45 -24
- package/src/_deprecated/Card/_stories/Card.test.story.tsx +47 -23
- package/src/_deprecated/CriticalBanner/_stories/CriticalCommsBanner.test.story.tsx +21 -15
- package/src/_deprecated/InlineAlert/_stories/InlineAlert.test.story.tsx +23 -12
- package/src/_deprecated/Select/_stories/Select.rtl.test.story.tsx +78 -42
- package/src/common/BottomSheet/_stories/BottomSheet.test.story.tsx +38 -20
- package/src/common/Panel/_stories/Panel.rtl.test.story.tsx +135 -51
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
2
2
|
|
|
3
|
-
import { withRTLDiff } from '../../../.storybook/decorators/RtlDiffDecorator';
|
|
3
|
+
import { useRtlDiffI18n, withRTLDiff } from '../../../.storybook/decorators/RtlDiffDecorator';
|
|
4
4
|
import Header from '../../Header';
|
|
5
5
|
import Section from '..';
|
|
6
6
|
|
|
@@ -13,17 +13,33 @@ export default {
|
|
|
13
13
|
type Story = StoryObj<typeof Section>;
|
|
14
14
|
|
|
15
15
|
export const RTLDiff: Story = {
|
|
16
|
-
render: () =>
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
Section
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
Section
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
16
|
+
render: () => {
|
|
17
|
+
const i18n = useRtlDiffI18n();
|
|
18
|
+
return (
|
|
19
|
+
<>
|
|
20
|
+
<Section>
|
|
21
|
+
<Header title={i18n.defaultTitle} />
|
|
22
|
+
{i18n.defaultContent}
|
|
23
|
+
</Section>
|
|
24
|
+
<Section withHorizontalPadding>
|
|
25
|
+
<Header title={i18n.paddedTitle} />
|
|
26
|
+
{i18n.paddedContent}
|
|
27
|
+
</Section>
|
|
28
|
+
</>
|
|
29
|
+
);
|
|
30
|
+
},
|
|
31
|
+
...withRTLDiff({
|
|
32
|
+
i18n: {
|
|
33
|
+
defaultTitle: { en: 'Default section', ar: 'قسم افتراضي' },
|
|
34
|
+
defaultContent: {
|
|
35
|
+
en: 'Section content without horizontal padding.',
|
|
36
|
+
ar: 'محتوى القسم بدون حشو أفقي.',
|
|
37
|
+
},
|
|
38
|
+
paddedTitle: { en: 'With horizontal padding', ar: 'مع حشو أفقي' },
|
|
39
|
+
paddedContent: {
|
|
40
|
+
en: 'Section content with horizontal padding applied.',
|
|
41
|
+
ar: 'محتوى القسم مع تطبيق الحشو الأفقي.',
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
}),
|
|
29
45
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useState } from 'react';
|
|
2
2
|
import type { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
3
|
-
import { withRTLDiff } from '../../../.storybook/decorators/RtlDiffDecorator';
|
|
3
|
+
import { useRtlDiffI18n, withRTLDiff } from '../../../.storybook/decorators/RtlDiffDecorator';
|
|
4
4
|
import SegmentedControl from '../SegmentedControl';
|
|
5
5
|
|
|
6
6
|
export default {
|
|
@@ -11,19 +11,15 @@ export default {
|
|
|
11
11
|
|
|
12
12
|
type Story = StoryObj<typeof SegmentedControl>;
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
{ id:
|
|
16
|
-
{ id:
|
|
17
|
-
|
|
18
|
-
] as const;
|
|
19
|
-
|
|
20
|
-
const viewSegments = [
|
|
21
|
-
{ id: 'seg-alpha', label: 'Alpha', value: 'alpha', controls: 'panel-alpha' },
|
|
22
|
-
{ id: 'seg-beta', label: 'Beta', value: 'beta', controls: 'panel-beta' },
|
|
23
|
-
{ id: 'seg-gamma', label: 'Gamma', value: 'gamma', controls: 'panel-gamma' },
|
|
24
|
-
] as const;
|
|
14
|
+
type InteractiveSegmentedControlProps = {
|
|
15
|
+
inputSegments: readonly { id: string; label: string; value: string }[];
|
|
16
|
+
viewSegments: readonly { id: string; label: string; value: string; controls: string }[];
|
|
17
|
+
};
|
|
25
18
|
|
|
26
|
-
function InteractiveSegmentedControl(
|
|
19
|
+
function InteractiveSegmentedControl({
|
|
20
|
+
inputSegments,
|
|
21
|
+
viewSegments,
|
|
22
|
+
}: InteractiveSegmentedControlProps) {
|
|
27
23
|
const [inputValue, setInputValue] = useState('one');
|
|
28
24
|
const [viewValue, setViewValue] = useState('beta');
|
|
29
25
|
|
|
@@ -48,6 +44,33 @@ function InteractiveSegmentedControl() {
|
|
|
48
44
|
}
|
|
49
45
|
|
|
50
46
|
export const RTLDiff: Story = {
|
|
51
|
-
render: () =>
|
|
52
|
-
|
|
47
|
+
render: () => {
|
|
48
|
+
const i18n = useRtlDiffI18n();
|
|
49
|
+
return (
|
|
50
|
+
<InteractiveSegmentedControl
|
|
51
|
+
inputSegments={[
|
|
52
|
+
{ id: 'seg-one', label: i18n.segmentOne, value: 'one' },
|
|
53
|
+
{ id: 'seg-two', label: i18n.segmentTwo, value: 'two' },
|
|
54
|
+
{ id: 'seg-three', label: i18n.segmentThree, value: 'three' },
|
|
55
|
+
]}
|
|
56
|
+
viewSegments={[
|
|
57
|
+
{ id: 'seg-alpha', label: i18n.segmentAlpha, value: 'alpha', controls: 'panel-alpha' },
|
|
58
|
+
{ id: 'seg-beta', label: i18n.segmentBeta, value: 'beta', controls: 'panel-beta' },
|
|
59
|
+
{ id: 'seg-gamma', label: i18n.segmentGamma, value: 'gamma', controls: 'panel-gamma' },
|
|
60
|
+
]}
|
|
61
|
+
/>
|
|
62
|
+
);
|
|
63
|
+
},
|
|
64
|
+
...withRTLDiff({
|
|
65
|
+
width: 'stretch',
|
|
66
|
+
renderAs: 'iframe',
|
|
67
|
+
i18n: {
|
|
68
|
+
segmentOne: { en: 'One', ar: 'واحد' },
|
|
69
|
+
segmentTwo: { en: 'Two', ar: 'اثنان' },
|
|
70
|
+
segmentThree: { en: 'Three', ar: 'ثلاثة' },
|
|
71
|
+
segmentAlpha: { en: 'Alpha', ar: 'ألفا' },
|
|
72
|
+
segmentBeta: { en: 'Beta', ar: 'بيتا' },
|
|
73
|
+
segmentGamma: { en: 'Gamma', ar: 'غاما' },
|
|
74
|
+
},
|
|
75
|
+
}),
|
|
53
76
|
};
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
useRtlDiffI18n,
|
|
5
|
+
withRTLDiff,
|
|
6
|
+
type I18nMap,
|
|
7
|
+
} from '../../../.storybook/decorators/RtlDiffDecorator';
|
|
4
8
|
import SlidingPanel from '../SlidingPanel';
|
|
5
9
|
|
|
6
10
|
export default {
|
|
@@ -11,7 +15,7 @@ export default {
|
|
|
11
15
|
|
|
12
16
|
type Story = StoryObj<typeof SlidingPanel>;
|
|
13
17
|
|
|
14
|
-
const iframeDiff = withRTLDiff({ renderAs: 'iframe', width: 'stretch' });
|
|
18
|
+
const iframeDiff = (i18n: I18nMap) => withRTLDiff({ renderAs: 'iframe', width: 'stretch', i18n });
|
|
15
19
|
|
|
16
20
|
const containerStyle: React.CSSProperties = {
|
|
17
21
|
position: 'relative',
|
|
@@ -27,45 +31,65 @@ const panelContentStyle: React.CSSProperties = {
|
|
|
27
31
|
};
|
|
28
32
|
|
|
29
33
|
export const PositionLeft: Story = {
|
|
30
|
-
render: () =>
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
render: () => {
|
|
35
|
+
const i18n = useRtlDiffI18n();
|
|
36
|
+
return (
|
|
37
|
+
<div style={containerStyle}>
|
|
38
|
+
<SlidingPanel open position="left">
|
|
39
|
+
<div style={panelContentStyle}>{i18n.panelContent}</div>
|
|
40
|
+
</SlidingPanel>
|
|
41
|
+
</div>
|
|
42
|
+
);
|
|
43
|
+
},
|
|
44
|
+
...iframeDiff({
|
|
45
|
+
panelContent: { en: 'Left panel content', ar: 'محتوى اللوحة اليسرى' },
|
|
46
|
+
}),
|
|
38
47
|
};
|
|
39
48
|
|
|
40
49
|
export const PositionRight: Story = {
|
|
41
|
-
render: () =>
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
50
|
+
render: () => {
|
|
51
|
+
const i18n = useRtlDiffI18n();
|
|
52
|
+
return (
|
|
53
|
+
<div style={containerStyle}>
|
|
54
|
+
<SlidingPanel open position="right">
|
|
55
|
+
<div style={panelContentStyle}>{i18n.panelContent}</div>
|
|
56
|
+
</SlidingPanel>
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
},
|
|
60
|
+
...iframeDiff({
|
|
61
|
+
panelContent: { en: 'Right panel content', ar: 'محتوى اللوحة اليمنى' },
|
|
62
|
+
}),
|
|
49
63
|
};
|
|
50
64
|
|
|
51
65
|
export const PositionLeftWithBorder: Story = {
|
|
52
|
-
render: () =>
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
66
|
+
render: () => {
|
|
67
|
+
const i18n = useRtlDiffI18n();
|
|
68
|
+
return (
|
|
69
|
+
<div style={containerStyle}>
|
|
70
|
+
<SlidingPanel open position="left" showSlidingPanelBorder>
|
|
71
|
+
<div style={panelContentStyle}>{i18n.panelContent}</div>
|
|
72
|
+
</SlidingPanel>
|
|
73
|
+
</div>
|
|
74
|
+
);
|
|
75
|
+
},
|
|
76
|
+
...iframeDiff({
|
|
77
|
+
panelContent: { en: 'Left panel with border', ar: 'اللوحة اليسرى مع حدود' },
|
|
78
|
+
}),
|
|
60
79
|
};
|
|
61
80
|
|
|
62
81
|
export const PositionRightWithBorder: Story = {
|
|
63
|
-
render: () =>
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
82
|
+
render: () => {
|
|
83
|
+
const i18n = useRtlDiffI18n();
|
|
84
|
+
return (
|
|
85
|
+
<div style={containerStyle}>
|
|
86
|
+
<SlidingPanel open position="right" showSlidingPanelBorder>
|
|
87
|
+
<div style={panelContentStyle}>{i18n.panelContent}</div>
|
|
88
|
+
</SlidingPanel>
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
},
|
|
92
|
+
...iframeDiff({
|
|
93
|
+
panelContent: { en: 'Right panel with border', ar: 'اللوحة اليمنى مع حدود' },
|
|
94
|
+
}),
|
|
71
95
|
};
|
|
@@ -122,6 +122,22 @@ describe('Snackbar', () => {
|
|
|
122
122
|
expect(screen.queryByText('Second message')).not.toBeInTheDocument();
|
|
123
123
|
});
|
|
124
124
|
|
|
125
|
+
it('dismisses a delayed replacement on the timeout current when it appears', () => {
|
|
126
|
+
const { rerender } = renderSnackbar();
|
|
127
|
+
|
|
128
|
+
rerender(<Snackbar text="First message" timeout={TIMEOUT} timestamp={1} />);
|
|
129
|
+
rerender(<Snackbar text="Second message" timeout={TIMEOUT} timestamp={2} />);
|
|
130
|
+
// Shortened while the replacement is still waiting out the exit transition.
|
|
131
|
+
rerender(<Snackbar text="Second message" timeout={1000} timestamp={2} />);
|
|
132
|
+
|
|
133
|
+
advanceTimers(CSS_TRANSITION_DURATION);
|
|
134
|
+
expect(screen.getByText('Second message')).toBeInTheDocument();
|
|
135
|
+
|
|
136
|
+
advanceTimers(1000);
|
|
137
|
+
advanceTimers(CSS_TRANSITION_DURATION);
|
|
138
|
+
expect(screen.queryByText('Second message')).not.toBeInTheDocument();
|
|
139
|
+
});
|
|
140
|
+
|
|
125
141
|
// Documents existing behaviour rather than endorsing it: clearing the text leaves the notification
|
|
126
142
|
// on screen indefinitely, because the previous shouldComponentUpdate blocked the dismissal too.
|
|
127
143
|
it('keeps the notification visible for as long as text is empty', () => {
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
|
2
2
|
import { CSSTransition } from 'react-transition-group';
|
|
3
3
|
|
|
4
4
|
import Button from '../Button';
|
|
5
5
|
import Body from '../Body';
|
|
6
6
|
import type { ThemeDark, ThemeLight } from '../common';
|
|
7
|
-
import { DirectionContext } from '../Provider/Direction';
|
|
8
7
|
import withNextPortal from '../WithNextPortal/WithNextPortal';
|
|
9
8
|
|
|
10
9
|
const CSS_TRANSITION_DURATION = 400;
|
|
@@ -25,102 +24,106 @@ interface SnackbarState extends Pick<SnackbarProps, 'action' | 'text' | 'theme'>
|
|
|
25
24
|
visible: boolean;
|
|
26
25
|
}
|
|
27
26
|
|
|
28
|
-
export
|
|
29
|
-
bodyRef =
|
|
30
|
-
|
|
31
|
-
transitionTimeout = 0;
|
|
27
|
+
export const Snackbar = (props: SnackbarProps) => {
|
|
28
|
+
const bodyRef = useRef<HTMLSpanElement>(null);
|
|
29
|
+
const leaveTimeout = useRef(0);
|
|
30
|
+
const transitionTimeout = useRef(0);
|
|
31
|
+
/** The props of the last committed render, as `this.props` was in the class component. */
|
|
32
|
+
const committedProps = useRef(props);
|
|
33
|
+
|
|
34
|
+
const [state, setState] = useState<SnackbarState>({
|
|
35
|
+
visible: false,
|
|
36
|
+
text: '',
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
useEffect(
|
|
40
|
+
() => () => {
|
|
41
|
+
window.clearTimeout(leaveTimeout.current);
|
|
42
|
+
window.clearTimeout(transitionTimeout.current);
|
|
43
|
+
},
|
|
44
|
+
[],
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// Runs after every render to mirror the componentDidUpdate lifecycle this component previously
|
|
48
|
+
// relied on, including the shouldComponentUpdate guard that ignored every update without text.
|
|
49
|
+
// Layout, not passive, so a notification is shown or replaced before the browser paints, as
|
|
50
|
+
// componentDidUpdate did. Both rules below are disabled on purpose: the dependency-less effect
|
|
51
|
+
// and its synchronous state updates are what keep this conversion behaviour-identical.
|
|
52
|
+
/* eslint-disable react-hooks/exhaustive-deps, react-hooks/set-state-in-effect */
|
|
53
|
+
useLayoutEffect(() => {
|
|
54
|
+
const previousProps = committedProps.current;
|
|
55
|
+
committedProps.current = props;
|
|
56
|
+
|
|
57
|
+
const { action, text, timestamp } = props;
|
|
58
|
+
|
|
59
|
+
// shouldComponentUpdate bailed out whenever the incoming text was empty. That also blocked the
|
|
60
|
+
// updates scheduled below, so a Snackbar whose text is cleared stays on screen until it is
|
|
61
|
+
// given text again. Preserved here deliberately to keep this refactor behaviour-identical.
|
|
62
|
+
const updateWhileTextIsPresent = (update: () => void) => {
|
|
63
|
+
if (committedProps.current.text) {
|
|
64
|
+
update();
|
|
65
|
+
}
|
|
66
|
+
};
|
|
32
67
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
text: '',
|
|
68
|
+
const setLeaveTimeout = () => {
|
|
69
|
+
leaveTimeout.current = window.setTimeout(() => {
|
|
70
|
+
updateWhileTextIsPresent(() => setState((current) => ({ ...current, visible: false })));
|
|
71
|
+
}, committedProps.current.timeout);
|
|
38
72
|
};
|
|
39
|
-
}
|
|
40
73
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
74
|
+
const show = () => {
|
|
75
|
+
setState((current) => ({ ...current, visible: true, action, text }));
|
|
76
|
+
setLeaveTimeout();
|
|
77
|
+
};
|
|
45
78
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
return false;
|
|
79
|
+
if (!text) {
|
|
80
|
+
return;
|
|
49
81
|
}
|
|
50
|
-
if (
|
|
51
|
-
nextProps.timestamp === this.props.timestamp &&
|
|
52
|
-
nextState.visible === this.state.visible &&
|
|
53
|
-
nextState.text === this.state.text
|
|
54
|
-
) {
|
|
55
|
-
return false;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return true;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
setLeaveTimeout = () => {
|
|
62
|
-
const { timeout } = this.props;
|
|
63
|
-
|
|
64
|
-
this.timeout = window.setTimeout(() => {
|
|
65
|
-
this.setState({ visible: false });
|
|
66
|
-
}, timeout);
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
componentDidUpdate(previousProps: SnackbarProps) {
|
|
70
|
-
const { action, text, timestamp } = this.props;
|
|
71
82
|
|
|
72
83
|
if (!previousProps.text) {
|
|
73
|
-
|
|
74
|
-
this.setLeaveTimeout();
|
|
75
|
-
});
|
|
84
|
+
show();
|
|
76
85
|
} else if (previousProps.timestamp !== timestamp) {
|
|
77
|
-
window.clearTimeout(
|
|
78
|
-
|
|
79
|
-
if (
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}, CSS_TRANSITION_DURATION);
|
|
85
|
-
});
|
|
86
|
+
window.clearTimeout(leaveTimeout.current);
|
|
87
|
+
|
|
88
|
+
if (state.visible) {
|
|
89
|
+
setState((current) => ({ ...current, visible: false }));
|
|
90
|
+
transitionTimeout.current = window.setTimeout(() => {
|
|
91
|
+
updateWhileTextIsPresent(show);
|
|
92
|
+
}, CSS_TRANSITION_DURATION);
|
|
86
93
|
} else {
|
|
87
|
-
|
|
88
|
-
this.setLeaveTimeout();
|
|
94
|
+
show();
|
|
89
95
|
}
|
|
90
96
|
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
Snackbar.contextType = DirectionContext;
|
|
97
|
+
});
|
|
98
|
+
/* eslint-enable react-hooks/exhaustive-deps, react-hooks/set-state-in-effect */
|
|
99
|
+
|
|
100
|
+
const { action, text, visible } = state;
|
|
101
|
+
const { timeout } = props;
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<div className="snackbar" aria-live="assertive" role="alert">
|
|
105
|
+
<CSSTransition
|
|
106
|
+
nodeRef={bodyRef}
|
|
107
|
+
in={visible}
|
|
108
|
+
classNames="snackbar__text-container"
|
|
109
|
+
timeout={{
|
|
110
|
+
appear: 0,
|
|
111
|
+
enter: timeout,
|
|
112
|
+
exit: CSS_TRANSITION_DURATION,
|
|
113
|
+
}}
|
|
114
|
+
unmountOnExit
|
|
115
|
+
>
|
|
116
|
+
<Body ref={bodyRef} as="span" className="snackbar__text">
|
|
117
|
+
{text}
|
|
118
|
+
{action ? (
|
|
119
|
+
<Button className="snackbar__text__action" v2 size="sm" onClick={action.onClick}>
|
|
120
|
+
{action.label}
|
|
121
|
+
</Button>
|
|
122
|
+
) : null}
|
|
123
|
+
</Body>
|
|
124
|
+
</CSSTransition>
|
|
125
|
+
</div>
|
|
126
|
+
);
|
|
127
|
+
};
|
|
125
128
|
|
|
126
129
|
export default withNextPortal(Snackbar);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useEffect, useState } from 'react';
|
|
2
2
|
import type { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
3
3
|
|
|
4
|
-
import { withRTLDiff } from '../../../.storybook/decorators/RtlDiffDecorator';
|
|
4
|
+
import { useRtlDiffI18n, withRTLDiff } from '../../../.storybook/decorators/RtlDiffDecorator';
|
|
5
5
|
import { Snackbar } from '../Snackbar';
|
|
6
6
|
|
|
7
7
|
export default {
|
|
@@ -14,7 +14,7 @@ type Story = StoryObj<typeof Snackbar>;
|
|
|
14
14
|
|
|
15
15
|
const TIMEOUT = 10_000_000;
|
|
16
16
|
|
|
17
|
-
function SnackbarStory({ action }: { action?: { label: string } }) {
|
|
17
|
+
function SnackbarStory({ text, action }: { text: string; action?: { label: string } }) {
|
|
18
18
|
const [timestamp, setTimestamp] = useState(0);
|
|
19
19
|
|
|
20
20
|
useEffect(() => {
|
|
@@ -23,21 +23,33 @@ function SnackbarStory({ action }: { action?: { label: string } }) {
|
|
|
23
23
|
}, []);
|
|
24
24
|
|
|
25
25
|
return (
|
|
26
|
-
<Snackbar
|
|
27
|
-
text={<span>Transfer completed successfully</span>}
|
|
28
|
-
timestamp={timestamp}
|
|
29
|
-
timeout={TIMEOUT}
|
|
30
|
-
action={action}
|
|
31
|
-
/>
|
|
26
|
+
<Snackbar text={<span>{text}</span>} timestamp={timestamp} timeout={TIMEOUT} action={action} />
|
|
32
27
|
);
|
|
33
28
|
}
|
|
34
29
|
|
|
35
30
|
export const WithAction: Story = {
|
|
36
|
-
render: () =>
|
|
37
|
-
|
|
31
|
+
render: () => {
|
|
32
|
+
const i18n = useRtlDiffI18n();
|
|
33
|
+
return <SnackbarStory text={i18n.text} action={{ label: i18n.actionLabel }} />;
|
|
34
|
+
},
|
|
35
|
+
...withRTLDiff({
|
|
36
|
+
renderAs: 'iframe',
|
|
37
|
+
i18n: {
|
|
38
|
+
text: { en: 'Transfer completed successfully', ar: 'تم إتمام التحويل بنجاح' },
|
|
39
|
+
actionLabel: { en: 'View', ar: 'عرض' },
|
|
40
|
+
},
|
|
41
|
+
}),
|
|
38
42
|
};
|
|
39
43
|
|
|
40
44
|
export const WithoutAction: Story = {
|
|
41
|
-
render: () =>
|
|
42
|
-
|
|
45
|
+
render: () => {
|
|
46
|
+
const i18n = useRtlDiffI18n();
|
|
47
|
+
return <SnackbarStory text={i18n.text} />;
|
|
48
|
+
},
|
|
49
|
+
...withRTLDiff({
|
|
50
|
+
renderAs: 'iframe',
|
|
51
|
+
i18n: {
|
|
52
|
+
text: { en: 'Transfer completed successfully', ar: 'تم إتمام التحويل بنجاح' },
|
|
53
|
+
},
|
|
54
|
+
}),
|
|
43
55
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
2
2
|
import { fn, within, expect, screen } from 'storybook/test';
|
|
3
3
|
import Stepper from '../Stepper';
|
|
4
|
-
import { withRTLDiff } from '../../../.storybook/decorators/RtlDiffDecorator';
|
|
4
|
+
import { useRtlDiffI18n, withRTLDiff } from '../../../.storybook/decorators/RtlDiffDecorator';
|
|
5
5
|
|
|
6
6
|
const STEPS = [
|
|
7
7
|
{
|
|
@@ -90,18 +90,36 @@ export const ProgressbarWidth: Story = {
|
|
|
90
90
|
},
|
|
91
91
|
};
|
|
92
92
|
|
|
93
|
+
// `steps` is a required prop, so `args` must supply it even though `render` builds its own
|
|
94
|
+
// translated steps from the i18n map and ignores this value.
|
|
93
95
|
const RTL_STEPS = [{ label: 'Amount' }, { label: 'You' }, { label: 'Review' }, { label: 'Pay' }];
|
|
94
96
|
|
|
95
97
|
export const RTLDiff: Story = {
|
|
96
98
|
args: {
|
|
97
99
|
steps: RTL_STEPS,
|
|
98
100
|
},
|
|
99
|
-
render: () =>
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
101
|
+
render: () => {
|
|
102
|
+
const i18n = useRtlDiffI18n();
|
|
103
|
+
const steps = [
|
|
104
|
+
{ label: i18n.amount },
|
|
105
|
+
{ label: i18n.you },
|
|
106
|
+
{ label: i18n.review },
|
|
107
|
+
{ label: i18n.pay },
|
|
108
|
+
];
|
|
109
|
+
return (
|
|
110
|
+
<div style={{ width: '80%', marginLeft: 'auto', marginRight: 'auto' }}>
|
|
111
|
+
<Stepper steps={steps} activeStep={0} />
|
|
112
|
+
<Stepper steps={steps} activeStep={2} />
|
|
113
|
+
<Stepper steps={steps} activeStep={4} />
|
|
114
|
+
</div>
|
|
115
|
+
);
|
|
116
|
+
},
|
|
117
|
+
...withRTLDiff({
|
|
118
|
+
i18n: {
|
|
119
|
+
amount: { en: 'Amount', ar: 'المبلغ' },
|
|
120
|
+
you: { en: 'You', ar: 'بياناتك' },
|
|
121
|
+
review: { en: 'Review', ar: 'المراجعة' },
|
|
122
|
+
pay: { en: 'Pay', ar: 'الدفع' },
|
|
123
|
+
},
|
|
124
|
+
}),
|
|
107
125
|
};
|
|
@@ -3,7 +3,7 @@ import { expect, userEvent, within } from 'storybook/test';
|
|
|
3
3
|
import { useState } from 'react';
|
|
4
4
|
|
|
5
5
|
import { withVariantConfig } from '../../../.storybook/helpers';
|
|
6
|
-
import { withRTLDiff } from '../../../.storybook/decorators/RtlDiffDecorator';
|
|
6
|
+
import { useRtlDiffI18n, withRTLDiff } from '../../../.storybook/decorators/RtlDiffDecorator';
|
|
7
7
|
import Field from '../../Field/Field';
|
|
8
8
|
import Switch from '../Switch';
|
|
9
9
|
|
|
@@ -103,15 +103,23 @@ export const Zoom400: Story = {
|
|
|
103
103
|
* the thumb direction flips correctly between writing modes.
|
|
104
104
|
*/
|
|
105
105
|
export const RTLDiff: Story = {
|
|
106
|
-
render: () =>
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
106
|
+
render: () => {
|
|
107
|
+
const i18n = useRtlDiffI18n();
|
|
108
|
+
return (
|
|
109
|
+
<>
|
|
110
|
+
<Field label={i18n.unchecked}>
|
|
111
|
+
<Switch checked={false} onClick={() => {}} />
|
|
112
|
+
</Field>
|
|
113
|
+
<Field label={i18n.checked}>
|
|
114
|
+
<Switch checked onClick={() => {}} />
|
|
115
|
+
</Field>
|
|
116
|
+
</>
|
|
117
|
+
);
|
|
118
|
+
},
|
|
119
|
+
...withRTLDiff({
|
|
120
|
+
i18n: {
|
|
121
|
+
unchecked: { en: 'Unchecked', ar: 'غير محدد' },
|
|
122
|
+
checked: { en: 'Checked', ar: 'محدد' },
|
|
123
|
+
},
|
|
124
|
+
}),
|
|
117
125
|
};
|