@transferwise/components 46.160.0 → 46.160.2
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/Calendar/Calendar.js +9 -3
- package/build/Calendar/Calendar.js.map +1 -1
- package/build/Calendar/Calendar.mjs +9 -3
- package/build/Calendar/Calendar.mjs.map +1 -1
- package/build/Carousel/Carousel.js +17 -10
- package/build/Carousel/Carousel.js.map +1 -1
- package/build/Carousel/Carousel.mjs +17 -10
- package/build/Carousel/Carousel.mjs.map +1 -1
- package/build/ExpressiveMoneyInput/Chevron/Chevron.js +10 -2
- package/build/ExpressiveMoneyInput/Chevron/Chevron.js.map +1 -1
- package/build/ExpressiveMoneyInput/Chevron/Chevron.mjs +11 -3
- package/build/ExpressiveMoneyInput/Chevron/Chevron.mjs.map +1 -1
- package/build/ProcessIndicator/ProcessIndicator.js +1 -1
- package/build/ProcessIndicator/ProcessIndicator.js.map +1 -1
- package/build/ProcessIndicator/ProcessIndicator.mjs +1 -1
- package/build/ProcessIndicator/ProcessIndicator.mjs.map +1 -1
- package/build/Prompt/CriticalBanner/CriticalBanner.js +10 -1
- package/build/Prompt/CriticalBanner/CriticalBanner.js.map +1 -1
- package/build/Prompt/CriticalBanner/CriticalBanner.mjs +10 -1
- package/build/Prompt/CriticalBanner/CriticalBanner.mjs.map +1 -1
- package/build/SegmentedControl/SegmentedControl.js +9 -2
- package/build/SegmentedControl/SegmentedControl.js.map +1 -1
- package/build/SegmentedControl/SegmentedControl.mjs +9 -2
- package/build/SegmentedControl/SegmentedControl.mjs.map +1 -1
- package/build/Tooltip/Tooltip.js +6 -1
- package/build/Tooltip/Tooltip.js.map +1 -1
- package/build/Tooltip/Tooltip.mjs +6 -1
- package/build/Tooltip/Tooltip.mjs.map +1 -1
- package/build/common/Panel/Panel.js +13 -25
- package/build/common/Panel/Panel.js.map +1 -1
- package/build/common/Panel/Panel.mjs +10 -22
- package/build/common/Panel/Panel.mjs.map +1 -1
- package/build/common/Panel/Panel.positionUtils.js +68 -0
- package/build/common/Panel/Panel.positionUtils.js.map +1 -0
- package/build/common/Panel/Panel.positionUtils.mjs +63 -0
- package/build/common/Panel/Panel.positionUtils.mjs.map +1 -0
- package/build/common/PropsValues/position.js +0 -25
- package/build/common/PropsValues/position.js.map +1 -1
- package/build/common/PropsValues/position.mjs +1 -24
- package/build/common/PropsValues/position.mjs.map +1 -1
- package/build/i18n/tr.json +5 -0
- package/build/i18n/tr.json.js +5 -0
- package/build/i18n/tr.json.js.map +1 -1
- package/build/i18n/tr.json.mjs +5 -0
- package/build/i18n/tr.json.mjs.map +1 -1
- package/build/i18n/zh-CN.json +5 -0
- package/build/i18n/zh-CN.json.js +5 -0
- package/build/i18n/zh-CN.json.js.map +1 -1
- package/build/i18n/zh-CN.json.mjs +5 -0
- package/build/i18n/zh-CN.json.mjs.map +1 -1
- package/build/main.css +134 -88
- package/build/styles/AvatarLayout/AvatarLayout.css +26 -9
- package/build/styles/AvatarView/AvatarView.css +8 -0
- package/build/styles/AvatarView/Dot/Dot.css +8 -0
- package/build/styles/CheckboxButton/CheckboxButton.css +3 -0
- package/build/styles/Nudge/Nudge.css +4 -38
- package/build/styles/Prompt/CriticalBanner/CriticalBanner.css +34 -0
- package/build/styles/SegmentedControl/SegmentedControl.css +2 -1
- package/build/styles/SlidingPanel/SlidingPanel.css +8 -0
- package/build/styles/Tooltip/Tooltip.css +0 -6
- package/build/styles/UploadInput/UploadItem/UploadItem.css +4 -5
- package/build/styles/_deprecated/Badge/Badge.css +4 -2
- package/build/styles/common/Panel/Panel.css +16 -8
- package/build/styles/css/accordion.css +3 -0
- package/build/styles/css/buttons.css +1 -7
- package/build/styles/css/chevron.css +3 -0
- package/build/styles/css/neptune.css +25 -19
- package/build/styles/css/process.css +15 -9
- package/build/styles/css/tick.css +6 -3
- package/build/styles/main.css +134 -88
- package/build/styles/styles/less/accordion.css +3 -0
- package/build/styles/styles/less/buttons.css +1 -7
- package/build/styles/styles/less/chevron.css +3 -0
- package/build/styles/styles/less/neptune.css +25 -19
- package/build/styles/styles/less/process.css +15 -9
- package/build/styles/styles/less/tick.css +6 -3
- package/build/types/Calendar/Calendar.d.ts.map +1 -1
- package/build/types/Carousel/Carousel.d.ts.map +1 -1
- package/build/types/Carousel/_stories/constants.d.ts +3 -0
- package/build/types/Carousel/_stories/constants.d.ts.map +1 -0
- package/build/types/ExpressiveMoneyInput/Chevron/Chevron.d.ts.map +1 -1
- package/build/types/Prompt/CriticalBanner/CriticalBanner.d.ts +6 -1
- package/build/types/Prompt/CriticalBanner/CriticalBanner.d.ts.map +1 -1
- package/build/types/SegmentedControl/SegmentedControl.d.ts.map +1 -1
- package/build/types/Tooltip/Tooltip.d.ts.map +1 -1
- package/build/types/common/Panel/Panel.d.ts +1 -1
- package/build/types/common/Panel/Panel.d.ts.map +1 -1
- package/build/types/common/Panel/Panel.positionUtils.d.ts +7 -0
- package/build/types/common/Panel/Panel.positionUtils.d.ts.map +1 -0
- package/build/types/common/PropsValues/position.d.ts +0 -13
- package/build/types/common/PropsValues/position.d.ts.map +1 -1
- package/build/types/common/ResponsivePanel/ResponsivePanel.d.ts +1 -1
- package/package.json +8 -8
- package/src/AvatarLayout/AvatarLayout.css +26 -9
- package/src/AvatarLayout/AvatarLayout.less +46 -22
- package/src/AvatarLayout/_stories/AvatarLayout.test.story.tsx +17 -42
- package/src/AvatarView/AvatarView.css +8 -0
- package/src/AvatarView/Dot/Dot.css +8 -0
- package/src/AvatarView/Dot/Dot.less +7 -1
- package/src/Button/_stories/Button.test.story.tsx +1 -1
- package/src/Calendar/Calendar.test.tsx +82 -0
- package/src/Calendar/Calendar.tsx +6 -3
- package/src/Carousel/Carousel.test.tsx +50 -2
- package/src/Carousel/Carousel.tsx +13 -10
- package/src/Carousel/_stories/Carousel.story.tsx +12 -52
- package/src/Carousel/_stories/Carousel.test.story.tsx +7 -43
- package/src/Carousel/_stories/constants.tsx +46 -0
- package/src/Checkbox/_stories/Checkbox.story.tsx +1 -1
- package/src/Checkbox/_stories/Checkbox.test.story.tsx +3 -2
- package/src/CheckboxButton/CheckboxButton.css +3 -0
- package/src/CheckboxButton/CheckboxButton.less +4 -0
- package/src/Chevron/_stories/Chevron.test.story.tsx +2 -2
- package/src/DateLookup/_stories/DateLookup.rtl.test.story.tsx +59 -48
- package/src/ExpressiveMoneyInput/Chevron/Chevron.tsx +8 -3
- package/src/Field/_stories/Field.test.story.tsx +48 -37
- package/src/FlowNavigation/_stories/FlowNavigation.rtl.test.story.tsx +78 -0
- package/src/FlowNavigation/_stories/FlowNavigation.test.story.tsx +0 -44
- package/src/Header/_stories/Header.test.story.tsx +1 -1
- package/src/Info/_stories/Info.rtl.test.story.tsx +35 -10
- package/src/Info/_stories/Info.story.tsx +1 -1
- package/src/Inputs/SelectInput/_stories/SelectInput.test.story.tsx +1 -1
- package/src/Label/_stories/Label.test.story.tsx +35 -22
- package/src/ListItem/_stories/subcomponents.tsx +1 -1
- package/src/Loader/_stories/Loader.test.story.tsx +23 -0
- package/src/MoneyInput/_stories/MoneyInput.rtl.test.story.tsx +1 -1
- package/src/Nudge/Nudge.css +4 -38
- package/src/Nudge/Nudge.less +4 -4
- package/src/Nudge/_stories/Nudge.test.story.tsx +29 -12
- package/src/PhoneNumberInput/_stories/{PhoneNumberInput.test.story.tsx → PhoneNumberInput.rtl.test.story.tsx} +18 -10
- package/src/Popover/_stories/Popover.rtl.test.story.tsx +40 -50
- package/src/ProcessIndicator/ProcessIndicator.tsx +1 -1
- package/src/ProcessIndicator/_stories/ProcessIndicator.test.story.tsx +32 -0
- package/src/ProgressBar/_stories/ProgressBar.test.story.tsx +33 -19
- package/src/Prompt/CriticalBanner/CriticalBanner.css +34 -0
- package/src/Prompt/CriticalBanner/CriticalBanner.less +45 -0
- package/src/Prompt/CriticalBanner/CriticalBanner.test.tsx +26 -3
- package/src/Prompt/CriticalBanner/CriticalBanner.tsx +15 -1
- package/src/Prompt/CriticalBanner/_stories/CriticalBanner.accessibility.docs.mdx +4 -1
- package/src/Prompt/CriticalBanner/_stories/CriticalBanner.story.tsx +52 -0
- package/src/Prompt/CriticalBanner/_stories/CriticalBanner.test.story.tsx +22 -1
- package/src/SegmentedControl/SegmentedControl.css +2 -1
- package/src/SegmentedControl/SegmentedControl.less +4 -2
- package/src/SegmentedControl/SegmentedControl.tsx +8 -2
- package/src/SegmentedControl/_stories/SegmentedControl.test.story.tsx +16 -8
- package/src/SlidingPanel/SlidingPanel.css +8 -0
- package/src/SlidingPanel/SlidingPanel.less +8 -0
- package/src/SlidingPanel/_stories/SlidingPanel.rtl.test.story.tsx +4 -3
- package/src/Snackbar/_stories/Snackbar.rtl.test.story.tsx +14 -16
- package/src/Snackbar/_stories/Snackbar.story.tsx +1 -1
- package/src/Snackbar/_stories/Snackbar.test.story.tsx +1 -1
- package/src/StatusIcon/_stories/StatusIcon.test.story.tsx +12 -4
- package/src/Tooltip/Tooltip.css +0 -6
- package/src/Tooltip/Tooltip.less +0 -12
- package/src/Tooltip/Tooltip.test.tsx +51 -0
- package/src/Tooltip/Tooltip.tsx +11 -1
- package/src/Tooltip/_stories/Tooltip.rtl.test.story.tsx +16 -34
- package/src/UploadInput/UploadItem/UploadItem.css +4 -5
- package/src/UploadInput/UploadItem/UploadItem.less +7 -6
- package/src/UploadInput/_stories/UploadInput.test.story.tsx +1 -1
- package/src/_deprecated/Badge/Badge.css +4 -2
- package/src/_deprecated/Badge/Badge.less +7 -14
- package/src/_deprecated/CriticalBanner/_stories/CriticalCommsBanner.test.story.tsx +0 -16
- package/src/_deprecated/OverlayHeader/_stories/OverlayHeader.test.story.tsx +15 -0
- package/src/common/Panel/Panel.css +16 -8
- package/src/common/Panel/Panel.less +31 -13
- package/src/common/Panel/Panel.positionUtils.ts +79 -0
- package/src/common/Panel/Panel.test.tsx +40 -0
- package/src/common/Panel/Panel.tsx +13 -24
- package/src/common/Panel/_stories/Panel.rtl.test.story.tsx +110 -0
- package/src/common/PropsValues/position.ts +0 -50
- package/src/i18n/tr.json +5 -0
- package/src/i18n/zh-CN.json +5 -0
- package/src/main.css +134 -88
- package/src/styles/less/accordion.css +3 -0
- package/src/styles/less/buttons.css +1 -7
- package/src/styles/less/buttons.less +3 -11
- package/src/styles/less/chevron.css +3 -0
- package/src/styles/less/chevron.less +4 -0
- package/src/styles/less/neptune.css +25 -19
- package/src/styles/less/process.css +15 -9
- package/src/styles/less/process.less +13 -6
- package/src/styles/less/tick.css +6 -3
- package/src/styles/less/tick.less +7 -3
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { fireEvent, mockMatchMedia, render, screen, userEvent } from '../test-utils';
|
|
2
|
+
import { DirectionProvider } from '../Provider/Direction/DirectionProvider';
|
|
2
3
|
import Calendar from './Calendar';
|
|
3
4
|
import type { DisabledRules } from './Calendar.types';
|
|
4
5
|
|
|
@@ -445,4 +446,85 @@ describe('Calendar', () => {
|
|
|
445
446
|
expect(handleChange).not.toHaveBeenCalled();
|
|
446
447
|
});
|
|
447
448
|
});
|
|
449
|
+
|
|
450
|
+
describe('RTL keyboard navigation', () => {
|
|
451
|
+
describe('in day view', () => {
|
|
452
|
+
it.each([
|
|
453
|
+
['{ArrowLeft}', +1],
|
|
454
|
+
['{ArrowRight}', -1],
|
|
455
|
+
['{ArrowUp}', -7],
|
|
456
|
+
['{ArrowDown}', +7],
|
|
457
|
+
])(
|
|
458
|
+
"handles '%s' to step %d day(s) — horizontal arrows are reversed",
|
|
459
|
+
async (text: string, step: number) => {
|
|
460
|
+
const handleDateFocus = jest.fn();
|
|
461
|
+
render(
|
|
462
|
+
<DirectionProvider direction="rtl">
|
|
463
|
+
<Calendar value={initialValue} onDateFocus={handleDateFocus} />
|
|
464
|
+
</DirectionProvider>,
|
|
465
|
+
);
|
|
466
|
+
|
|
467
|
+
await user.click(screen.getByRole('button', { name: /selected day/iu }));
|
|
468
|
+
await user.keyboard(text);
|
|
469
|
+
|
|
470
|
+
const expected = new Date(initialValue);
|
|
471
|
+
expected.setDate(initialValue.getDate() + step);
|
|
472
|
+
expect(handleDateFocus).toHaveBeenCalledWith(expected, { isEnabled: true });
|
|
473
|
+
},
|
|
474
|
+
);
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
describe('in year view', () => {
|
|
478
|
+
it.each([
|
|
479
|
+
['{ArrowLeft}', +1],
|
|
480
|
+
['{ArrowRight}', -1],
|
|
481
|
+
['{ArrowUp}', -4],
|
|
482
|
+
['{ArrowDown}', +4],
|
|
483
|
+
])(
|
|
484
|
+
"handles '%s' to step %d year(s) — horizontal arrows are reversed",
|
|
485
|
+
async (text: string, step: number) => {
|
|
486
|
+
const handleDateFocus = jest.fn();
|
|
487
|
+
render(
|
|
488
|
+
<DirectionProvider direction="rtl">
|
|
489
|
+
<Calendar value={initialValue} onDateFocus={handleDateFocus} />
|
|
490
|
+
</DirectionProvider>,
|
|
491
|
+
);
|
|
492
|
+
|
|
493
|
+
await user.click(screen.getByRole('button', { name: /year view/iu }));
|
|
494
|
+
await user.keyboard(text);
|
|
495
|
+
|
|
496
|
+
const expected = new Date(initialValue);
|
|
497
|
+
expected.setFullYear(initialValue.getFullYear() + step);
|
|
498
|
+
expect(handleDateFocus).toHaveBeenCalledWith(expected, { isEnabled: true });
|
|
499
|
+
},
|
|
500
|
+
);
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
describe('in month view', () => {
|
|
504
|
+
it.each([
|
|
505
|
+
['{ArrowLeft}', +1],
|
|
506
|
+
['{ArrowRight}', -1],
|
|
507
|
+
['{ArrowUp}', -4],
|
|
508
|
+
['{ArrowDown}', +4],
|
|
509
|
+
])(
|
|
510
|
+
"handles '%s' to step %d month(s) — horizontal arrows are reversed",
|
|
511
|
+
async (text: string, step: number) => {
|
|
512
|
+
const handleDateFocus = jest.fn();
|
|
513
|
+
render(
|
|
514
|
+
<DirectionProvider direction="rtl">
|
|
515
|
+
<Calendar value={initialValue} onDateFocus={handleDateFocus} />
|
|
516
|
+
</DirectionProvider>,
|
|
517
|
+
);
|
|
518
|
+
|
|
519
|
+
await user.click(screen.getByRole('button', { name: /year view/iu }));
|
|
520
|
+
await user.keyboard(' ');
|
|
521
|
+
await user.keyboard(text);
|
|
522
|
+
|
|
523
|
+
const expected = new Date(initialValue);
|
|
524
|
+
expected.setMonth(initialValue.getMonth() + step);
|
|
525
|
+
expect(handleDateFocus).toHaveBeenCalledWith(expected, { isEnabled: true });
|
|
526
|
+
},
|
|
527
|
+
);
|
|
528
|
+
});
|
|
529
|
+
});
|
|
448
530
|
});
|
|
@@ -3,6 +3,7 @@ import { useIntl } from 'react-intl';
|
|
|
3
3
|
import { formatDate } from '@transferwise/formatting';
|
|
4
4
|
|
|
5
5
|
import { MonthFormat } from '../common';
|
|
6
|
+
import { useDirection } from '../common/hooks';
|
|
6
7
|
import { isWithinRange, moveToWithinRange, returnDateView } from '../common/DateUtils';
|
|
7
8
|
import { clampToDayViewBoundary, isDateDisabled, hasDisabledDatesInMonth } from './_helpers';
|
|
8
9
|
import { LONG_DAY_FORMAT } from './Calendar.constants';
|
|
@@ -53,6 +54,7 @@ const Calendar = ({
|
|
|
53
54
|
onChange,
|
|
54
55
|
}: CalendarProps) => {
|
|
55
56
|
const intl = useIntl();
|
|
57
|
+
const { isRTL } = useDirection();
|
|
56
58
|
const selectedDate = getStartOfDay(value);
|
|
57
59
|
const min = getStartOfDay(minProp);
|
|
58
60
|
const max = getStartOfDay(maxProp);
|
|
@@ -185,9 +187,10 @@ const Calendar = ({
|
|
|
185
187
|
|
|
186
188
|
const handleKeyDown = useCallback(
|
|
187
189
|
(event: KeyboardEvent<HTMLDivElement>) => {
|
|
190
|
+
const orientationSign = isRTL ? -1 : 1;
|
|
188
191
|
switch (event.key) {
|
|
189
192
|
case 'ArrowLeft':
|
|
190
|
-
adjustDate(-1, -1, -1);
|
|
193
|
+
adjustDate(-1 * orientationSign, -1 * orientationSign, -1 * orientationSign);
|
|
191
194
|
event.preventDefault();
|
|
192
195
|
break;
|
|
193
196
|
case 'ArrowUp':
|
|
@@ -195,7 +198,7 @@ const Calendar = ({
|
|
|
195
198
|
event.preventDefault();
|
|
196
199
|
break;
|
|
197
200
|
case 'ArrowRight':
|
|
198
|
-
adjustDate(1, 1, 1);
|
|
201
|
+
adjustDate(1 * orientationSign, 1 * orientationSign, 1 * orientationSign);
|
|
199
202
|
event.preventDefault();
|
|
200
203
|
break;
|
|
201
204
|
case 'ArrowDown':
|
|
@@ -217,7 +220,7 @@ const Calendar = ({
|
|
|
217
220
|
break;
|
|
218
221
|
}
|
|
219
222
|
},
|
|
220
|
-
[adjustDate, focusedDate, mode, handleSelectedDateUpdate],
|
|
223
|
+
[adjustDate, focusedDate, mode, handleSelectedDateUpdate, isRTL],
|
|
221
224
|
);
|
|
222
225
|
|
|
223
226
|
const focusedDateOutOfRange = focusedDate ? !isWithinRange(focusedDate, min, max) : false;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { userEvent, fireEvent, render, screen, mockMatchMedia } from '../test-utils';
|
|
2
2
|
|
|
3
3
|
import Display from '../Display';
|
|
4
|
+
import { DirectionProvider } from '../Provider/Direction/DirectionProvider';
|
|
4
5
|
import Title from '../Title';
|
|
5
6
|
|
|
6
7
|
import Carousel, { type CarouselCard } from './Carousel';
|
|
@@ -38,8 +39,9 @@ const carouselCards: CarouselCard[] = [
|
|
|
38
39
|
|
|
39
40
|
const onClick = jest.fn();
|
|
40
41
|
|
|
41
|
-
const renderCarousel = () => {
|
|
42
|
-
|
|
42
|
+
const renderCarousel = ({ direction }: { direction?: 'rtl' | 'ltr' } = {}) => {
|
|
43
|
+
const ui = <Carousel header="Cool stuff" cards={carouselCards} onClick={onClick} />;
|
|
44
|
+
render(direction ? <DirectionProvider direction={direction}>{ui}</DirectionProvider> : ui);
|
|
43
45
|
};
|
|
44
46
|
|
|
45
47
|
describe('Carousel', () => {
|
|
@@ -195,6 +197,52 @@ describe('Carousel', () => {
|
|
|
195
197
|
});
|
|
196
198
|
});
|
|
197
199
|
|
|
200
|
+
describe('Carousel RTL', () => {
|
|
201
|
+
const scrollBy = jest.fn();
|
|
202
|
+
|
|
203
|
+
beforeEach(() => {
|
|
204
|
+
window.HTMLElement.prototype.scrollBy = scrollBy;
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
afterEach(() => {
|
|
208
|
+
scrollBy.mockClear();
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('scrolls in negative direction when clicking forward in RTL', async () => {
|
|
212
|
+
mockPositions(948, 903);
|
|
213
|
+
renderCarousel({ direction: 'rtl' });
|
|
214
|
+
|
|
215
|
+
const scrollRightButton = screen.getByTestId('scroll-carousel-right');
|
|
216
|
+
await userEvent.click(scrollRightButton);
|
|
217
|
+
|
|
218
|
+
expect(scrollBy).toHaveBeenCalledWith({ left: -300, behavior: 'smooth' });
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('scrolls in positive direction when clicking backward in RTL', async () => {
|
|
222
|
+
mockPositions(948, 903);
|
|
223
|
+
renderCarousel({ direction: 'rtl' });
|
|
224
|
+
|
|
225
|
+
const carousel = screen.getByRole('list');
|
|
226
|
+
fireEvent.scroll(carousel, { target: { scrollLeft: -10 } });
|
|
227
|
+
|
|
228
|
+
const scrollLeftButton = screen.getByTestId('scroll-carousel-left');
|
|
229
|
+
await userEvent.click(scrollLeftButton);
|
|
230
|
+
|
|
231
|
+
expect(scrollBy).toHaveBeenCalledWith({ left: 300, behavior: 'smooth' });
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('normalises negative scrollLeft for button state in RTL', () => {
|
|
235
|
+
mockPositions(948, 903);
|
|
236
|
+
renderCarousel({ direction: 'rtl' });
|
|
237
|
+
|
|
238
|
+
const carousel = screen.getByRole('list');
|
|
239
|
+
fireEvent.scroll(carousel, { target: { scrollLeft: -10 } });
|
|
240
|
+
|
|
241
|
+
const scrollLeftButton = screen.getByTestId('scroll-carousel-left');
|
|
242
|
+
expect(scrollLeftButton).toBeEnabled();
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
|
|
198
246
|
const mockPositions = (scrollWidth: number, offsetWidth: number) => {
|
|
199
247
|
Object.defineProperty(HTMLElement.prototype, 'scrollWidth', {
|
|
200
248
|
configurable: true,
|
|
@@ -2,6 +2,7 @@ import { ChevronLeft, ChevronRight } from '@transferwise/icons';
|
|
|
2
2
|
import { clsx } from 'clsx';
|
|
3
3
|
import { type CSSProperties, type ReactNode, useEffect, useRef, useState } from 'react';
|
|
4
4
|
|
|
5
|
+
import { useDirection } from '../common/hooks';
|
|
5
6
|
import IconButton from '../IconButton';
|
|
6
7
|
import Title from '../Title';
|
|
7
8
|
import type { PromoCardLinkProps } from '../PromoCard/PromoCard';
|
|
@@ -48,10 +49,11 @@ const Carousel: React.FC<CarouselProps> = ({ header, className, cards, onClick }
|
|
|
48
49
|
const [visibleCardOnMobileView, setVisibleCardOnMobileView] = useState<string>('');
|
|
49
50
|
const carouselElementRef = useRef<HTMLDivElement>(null);
|
|
50
51
|
const carouselCardsRef = useRef<CardsReference[]>([]);
|
|
52
|
+
const { isRTL } = useDirection();
|
|
51
53
|
|
|
52
|
-
const
|
|
54
|
+
const isPrevButtonEnabled = scrollPosition > LEFT_SCROLL_OFFSET;
|
|
53
55
|
|
|
54
|
-
const areActionButtonsEnabled =
|
|
56
|
+
const areActionButtonsEnabled = isPrevButtonEnabled || !scrollIsAtEnd;
|
|
55
57
|
|
|
56
58
|
const [focusedCard, setFocusedCard] = useState(cards?.[0]?.id);
|
|
57
59
|
|
|
@@ -83,16 +85,17 @@ const Carousel: React.FC<CarouselProps> = ({ header, className, cards, onClick }
|
|
|
83
85
|
setPreviousScrollPosition(scrollPosition);
|
|
84
86
|
};
|
|
85
87
|
|
|
86
|
-
const scrollCarousel = (direction: '
|
|
88
|
+
const scrollCarousel = (direction: 'forward' | 'backward') => {
|
|
87
89
|
if (carouselElementRef.current) {
|
|
88
90
|
const { scrollWidth } = carouselElementRef.current;
|
|
89
91
|
|
|
90
92
|
const cardWidth = scrollWidth / carouselCardsRef.current.length;
|
|
91
93
|
|
|
92
|
-
const
|
|
94
|
+
const distance = Math.floor(cardWidth - cardWidth * 0.05);
|
|
95
|
+
const sign = direction === 'forward' ? 1 : -1;
|
|
93
96
|
|
|
94
97
|
carouselElementRef.current.scrollBy({
|
|
95
|
-
left:
|
|
98
|
+
left: isRTL ? -sign * distance : sign * distance,
|
|
96
99
|
behavior: 'smooth',
|
|
97
100
|
});
|
|
98
101
|
}
|
|
@@ -174,10 +177,10 @@ const Carousel: React.FC<CarouselProps> = ({ header, className, cards, onClick }
|
|
|
174
177
|
size={32}
|
|
175
178
|
tabIndex={-1}
|
|
176
179
|
priority="tertiary"
|
|
177
|
-
disabled={!
|
|
180
|
+
disabled={!isPrevButtonEnabled}
|
|
178
181
|
aria-hidden="true"
|
|
179
182
|
data-testid="scroll-carousel-left"
|
|
180
|
-
onClick={() => scrollCarousel('
|
|
183
|
+
onClick={() => scrollCarousel('backward')}
|
|
181
184
|
>
|
|
182
185
|
<ChevronLeft />
|
|
183
186
|
</IconButton>
|
|
@@ -189,7 +192,7 @@ const Carousel: React.FC<CarouselProps> = ({ header, className, cards, onClick }
|
|
|
189
192
|
aria-hidden="true"
|
|
190
193
|
data-testid="scroll-carousel-right"
|
|
191
194
|
disabled={scrollIsAtEnd}
|
|
192
|
-
onClick={() => scrollCarousel()}
|
|
195
|
+
onClick={() => scrollCarousel('forward')}
|
|
193
196
|
>
|
|
194
197
|
<ChevronRight />
|
|
195
198
|
</IconButton>
|
|
@@ -203,7 +206,7 @@ const Carousel: React.FC<CarouselProps> = ({ header, className, cards, onClick }
|
|
|
203
206
|
className="carousel"
|
|
204
207
|
onScroll={(event) => {
|
|
205
208
|
const target = event.target as HTMLElement;
|
|
206
|
-
setScrollPosition(target.scrollLeft);
|
|
209
|
+
setScrollPosition(Math.abs(target.scrollLeft));
|
|
207
210
|
}}
|
|
208
211
|
>
|
|
209
212
|
{cards?.map((card, index) => {
|
|
@@ -336,7 +339,7 @@ const Carousel: React.FC<CarouselProps> = ({ header, className, cards, onClick }
|
|
|
336
339
|
|
|
337
340
|
const isVisible = (container: HTMLElement, el: HTMLElement) => {
|
|
338
341
|
const cWidth = container.offsetWidth;
|
|
339
|
-
const cScrollOffset = container.scrollLeft;
|
|
342
|
+
const cScrollOffset = Math.abs(container.scrollLeft);
|
|
340
343
|
|
|
341
344
|
const elemLeft = el.offsetLeft - container.offsetLeft;
|
|
342
345
|
const elemRight = elemLeft + el.offsetWidth;
|
|
@@ -1,60 +1,27 @@
|
|
|
1
1
|
import type { Args } from '@storybook/react-webpack5';
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
import {
|
|
4
4
|
createSandboxStory,
|
|
5
5
|
globalScope,
|
|
6
6
|
} from '../../../.storybook/components/sandbox/SandboxEditor';
|
|
7
|
-
|
|
8
|
-
import
|
|
9
|
-
import Carousel, { type CarouselCard } from '..';
|
|
7
|
+
import Carousel from '..';
|
|
8
|
+
import { stubbedCarouselCards } from './constants';
|
|
10
9
|
|
|
11
10
|
export default {
|
|
11
|
+
args: {
|
|
12
|
+
cards: stubbedCarouselCards,
|
|
13
|
+
header: 'Carousel title',
|
|
14
|
+
},
|
|
12
15
|
component: Carousel,
|
|
13
16
|
title: 'Navigation/Carousel',
|
|
14
17
|
};
|
|
15
18
|
|
|
16
|
-
const
|
|
17
|
-
{
|
|
18
|
-
|
|
19
|
-
type: 'button',
|
|
20
|
-
onClick: () => {
|
|
21
|
-
console.log('Clicked on button');
|
|
22
|
-
},
|
|
23
|
-
content: (
|
|
24
|
-
<div className="d-flex p-a-1 p-t-0 flex-column align-items-center">
|
|
25
|
-
<div style={{ position: 'absolute' }}>
|
|
26
|
-
<Illustration disablePadding name="globe" size="small" alt="" />
|
|
27
|
-
</div>
|
|
28
|
-
<div style={{ position: 'absolute', zIndex: '2', bottom: '8px', left: '16px' }}>
|
|
29
|
-
<Display type="display-small">Here there, everywhere</Display>
|
|
30
|
-
</div>
|
|
31
|
-
</div>
|
|
32
|
-
),
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
id: 'YOUR_WEBSITE',
|
|
36
|
-
type: 'promo',
|
|
37
|
-
onClick: () => {
|
|
38
|
-
console.log('hi');
|
|
39
|
-
},
|
|
40
|
-
href: 'http://wise.com',
|
|
41
|
-
title: 'Wise',
|
|
42
|
-
description: 'The best way to move money internationally',
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
id: 'SHOP_OR_STALL',
|
|
46
|
-
type: 'anchor',
|
|
47
|
-
href: 'https://wise.com',
|
|
48
|
-
hrefTarget: '_blank',
|
|
49
|
-
onClick: () => {},
|
|
50
|
-
styles: {
|
|
51
|
-
backgroundImage:
|
|
52
|
-
'url(https://wise.com/public-resources/assets/team-page/tapestry/team_member_tapestry_01.jpg)',
|
|
53
|
-
backgroundSize: 'cover',
|
|
54
|
-
},
|
|
55
|
-
content: <Display type="display-small">Get Quick Pay</Display>,
|
|
19
|
+
export const Playground = {
|
|
20
|
+
render: (args: Args) => {
|
|
21
|
+
return <Carousel header="Pretty nifty stuff" cards={stubbedCarouselCards} {...args} />;
|
|
56
22
|
},
|
|
57
|
-
|
|
23
|
+
args: {},
|
|
24
|
+
};
|
|
58
25
|
|
|
59
26
|
export const Sandbox = createSandboxStory({
|
|
60
27
|
code: `const App = () => {
|
|
@@ -89,10 +56,3 @@ export const Sandbox = createSandboxStory({
|
|
|
89
56
|
};`,
|
|
90
57
|
scope: globalScope,
|
|
91
58
|
});
|
|
92
|
-
|
|
93
|
-
export const CarouselDefault = {
|
|
94
|
-
render: (args: Args) => {
|
|
95
|
-
return <Carousel header="Pretty nifty stuff" cards={carouselCards} {...args} />;
|
|
96
|
-
},
|
|
97
|
-
args: {},
|
|
98
|
-
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
2
2
|
|
|
3
3
|
import { withRTLDiff } from '../../../.storybook/decorators/RtlDiffDecorator';
|
|
4
|
-
import
|
|
5
|
-
import
|
|
4
|
+
import Carousel from '..';
|
|
5
|
+
import { stubbedCarouselCards } from './constants';
|
|
6
6
|
|
|
7
7
|
export default {
|
|
8
8
|
component: Carousel,
|
|
@@ -12,48 +12,12 @@ export default {
|
|
|
12
12
|
|
|
13
13
|
type Story = StoryObj<typeof Carousel>;
|
|
14
14
|
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
type: 'button',
|
|
19
|
-
content: (
|
|
20
|
-
<div className="d-flex p-a-1 flex-column align-items-center">
|
|
21
|
-
<Display type="display-small">Card one</Display>
|
|
22
|
-
</div>
|
|
23
|
-
),
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
id: 'card-2',
|
|
27
|
-
type: 'promo',
|
|
28
|
-
href: '#',
|
|
29
|
-
title: 'Promo card',
|
|
30
|
-
description: 'Description for promo card',
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
id: 'card-3',
|
|
34
|
-
type: 'anchor',
|
|
35
|
-
href: '#',
|
|
36
|
-
hrefTarget: '_blank',
|
|
37
|
-
content: <Display type="display-small">Anchor card</Display>,
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
id: 'card-4',
|
|
41
|
-
type: 'button',
|
|
42
|
-
content: (
|
|
43
|
-
<div className="d-flex p-a-1 flex-column align-items-center">
|
|
44
|
-
<Display type="display-small">Card four</Display>
|
|
45
|
-
</div>
|
|
46
|
-
),
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
id: 'card-5',
|
|
50
|
-
type: 'anchor',
|
|
51
|
-
href: '#',
|
|
52
|
-
content: <Display type="display-small">Card five</Display>,
|
|
53
|
-
},
|
|
15
|
+
const manyCards = [
|
|
16
|
+
...stubbedCarouselCards,
|
|
17
|
+
...stubbedCarouselCards.map((card) => ({ ...card, id: `${card.id}_2` })),
|
|
54
18
|
];
|
|
55
19
|
|
|
56
20
|
export const RTLDiff: Story = {
|
|
57
|
-
render: () => <Carousel header="
|
|
58
|
-
...withRTLDiff(),
|
|
21
|
+
render: () => <Carousel cards={manyCards} header="Pretty nifty stuff" />,
|
|
22
|
+
...withRTLDiff({ width: 'stretch' }),
|
|
59
23
|
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Illustration } from '@wise/art';
|
|
2
|
+
import Display from '../../Display';
|
|
3
|
+
import type { CarouselCard } from '../Carousel';
|
|
4
|
+
|
|
5
|
+
export const stubbedCarouselCards: CarouselCard[] = [
|
|
6
|
+
{
|
|
7
|
+
id: 'YOUR_INVOICE',
|
|
8
|
+
type: 'button',
|
|
9
|
+
onClick: () => {
|
|
10
|
+
console.log('Clicked on button');
|
|
11
|
+
},
|
|
12
|
+
content: (
|
|
13
|
+
<div className="d-flex p-a-1 p-t-0 flex-column align-items-center">
|
|
14
|
+
<div style={{ position: 'absolute' }}>
|
|
15
|
+
<Illustration disablePadding name="globe" size="small" alt="" />
|
|
16
|
+
</div>
|
|
17
|
+
<div style={{ position: 'absolute', zIndex: '2', bottom: '8px', left: '16px' }}>
|
|
18
|
+
<Display type="display-small">Here there, everywhere</Display>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
),
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: 'YOUR_WEBSITE',
|
|
25
|
+
type: 'promo',
|
|
26
|
+
onClick: () => {
|
|
27
|
+
console.log('hi');
|
|
28
|
+
},
|
|
29
|
+
href: 'http://wise.com',
|
|
30
|
+
title: 'Wise',
|
|
31
|
+
description: 'The best way to move money internationally',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: 'SHOP_OR_STALL',
|
|
35
|
+
type: 'anchor',
|
|
36
|
+
href: 'https://wise.com',
|
|
37
|
+
hrefTarget: '_blank',
|
|
38
|
+
onClick: () => {},
|
|
39
|
+
styles: {
|
|
40
|
+
backgroundImage:
|
|
41
|
+
'url(https://wise.com/public-resources/assets/team-page/tapestry/team_member_tapestry_01.jpg)',
|
|
42
|
+
backgroundSize: 'cover',
|
|
43
|
+
},
|
|
44
|
+
content: <Display type="display-small">Get Quick Pay</Display>,
|
|
45
|
+
},
|
|
46
|
+
];
|
|
@@ -30,7 +30,7 @@ export const MultipleMobile: Story = {
|
|
|
30
30
|
</>
|
|
31
31
|
);
|
|
32
32
|
},
|
|
33
|
-
...withVariantConfig(['default', 'dark', '
|
|
33
|
+
...withVariantConfig(['default', 'dark', 'mobile']),
|
|
34
34
|
};
|
|
35
35
|
|
|
36
36
|
/**
|
|
@@ -41,8 +41,9 @@ export const MultipleMobile: Story = {
|
|
|
41
41
|
export const RTLDiff: Story = {
|
|
42
42
|
render: () => (
|
|
43
43
|
<>
|
|
44
|
-
<Checkbox label="Label only" onChange={() => {}} />
|
|
44
|
+
<Checkbox checked label="Label only" onChange={() => {}} />
|
|
45
45
|
<Checkbox
|
|
46
|
+
checked
|
|
46
47
|
label="Label with secondary"
|
|
47
48
|
secondary="Secondary description text"
|
|
48
49
|
onChange={() => {}}
|
|
@@ -32,6 +32,9 @@
|
|
|
32
32
|
background: var(--color-background-screen);
|
|
33
33
|
transform: translate(-50%, -50%);
|
|
34
34
|
}
|
|
35
|
+
[dir="rtl"] .np-tw-checkbox-indeterminate::after {
|
|
36
|
+
transform: translate(50%, -50%);
|
|
37
|
+
}
|
|
35
38
|
.np-tw-checkbox-indeterminate .has-error::after {
|
|
36
39
|
background-color: #e74848;
|
|
37
40
|
background-color: var(--color-interactive-negative);
|
|
@@ -17,10 +17,10 @@ type Story = StoryObj<typeof Chevron>;
|
|
|
17
17
|
*/
|
|
18
18
|
export const RTLDiff: Story = {
|
|
19
19
|
render: () => (
|
|
20
|
-
|
|
20
|
+
<>
|
|
21
21
|
<Chevron orientation="left" size="md" />
|
|
22
22
|
<Chevron orientation="right" size="md" />
|
|
23
|
-
|
|
23
|
+
</>
|
|
24
24
|
),
|
|
25
25
|
...withRTLDiff(),
|
|
26
26
|
};
|