@transferwise/components 46.52.3 → 46.53.0
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/dateLookup/dateTrigger/DateTrigger.js +8 -4
- package/build/dateLookup/dateTrigger/DateTrigger.js.map +1 -1
- package/build/dateLookup/dateTrigger/DateTrigger.mjs +8 -4
- package/build/dateLookup/dateTrigger/DateTrigger.mjs.map +1 -1
- package/build/field/Field.js +36 -8
- package/build/field/Field.js.map +1 -1
- package/build/field/Field.mjs +37 -9
- package/build/field/Field.mjs.map +1 -1
- package/build/i18n/en.json +1 -0
- package/build/i18n/en.json.js +1 -0
- package/build/i18n/en.json.js.map +1 -1
- package/build/i18n/en.json.mjs +1 -0
- package/build/i18n/en.json.mjs.map +1 -1
- package/build/index.js +2 -2
- package/build/index.mjs +1 -1
- package/build/inlineAlert/InlineAlert.js +13 -6
- package/build/inlineAlert/InlineAlert.js.map +1 -1
- package/build/inlineAlert/InlineAlert.mjs +13 -6
- package/build/inlineAlert/InlineAlert.mjs.map +1 -1
- package/build/label/Label.js +35 -4
- package/build/label/Label.js.map +1 -1
- package/build/label/Label.messages.js +12 -0
- package/build/label/Label.messages.js.map +1 -0
- package/build/label/Label.messages.mjs +10 -0
- package/build/label/Label.messages.mjs.map +1 -0
- package/build/label/Label.mjs +36 -5
- package/build/label/Label.mjs.map +1 -1
- package/build/main.css +4 -8
- package/build/styles/dateLookup/dateTrigger/DateTrigger.css +0 -8
- package/build/styles/field/Field.css +4 -0
- package/build/styles/main.css +4 -8
- package/build/tabs/Tab.js +13 -38
- package/build/tabs/Tab.js.map +1 -1
- package/build/tabs/Tab.mjs +13 -34
- package/build/tabs/Tab.mjs.map +1 -1
- package/build/tabs/TabList.js +3 -11
- package/build/tabs/TabList.js.map +1 -1
- package/build/tabs/TabList.mjs +3 -7
- package/build/tabs/TabList.mjs.map +1 -1
- package/build/tabs/TabPanel.js +3 -16
- package/build/tabs/TabPanel.js.map +1 -1
- package/build/tabs/TabPanel.mjs +3 -12
- package/build/tabs/TabPanel.mjs.map +1 -1
- package/build/tabs/Tabs.js +24 -48
- package/build/tabs/Tabs.js.map +1 -1
- package/build/tabs/Tabs.mjs +24 -47
- package/build/tabs/Tabs.mjs.map +1 -1
- package/build/tabs/utils.js +0 -1
- package/build/tabs/utils.js.map +1 -1
- package/build/tabs/utils.mjs +0 -1
- package/build/tabs/utils.mjs.map +1 -1
- package/build/types/dateLookup/dateTrigger/DateTrigger.d.ts.map +1 -1
- package/build/types/field/Field.d.ts +4 -2
- package/build/types/field/Field.d.ts.map +1 -1
- package/build/types/index.d.ts +2 -1
- package/build/types/index.d.ts.map +1 -1
- package/build/types/inlineAlert/InlineAlert.d.ts +9 -0
- package/build/types/inlineAlert/InlineAlert.d.ts.map +1 -1
- package/build/types/label/Label.d.ts +21 -1
- package/build/types/label/Label.d.ts.map +1 -1
- package/build/types/label/Label.messages.d.ts +8 -0
- package/build/types/label/Label.messages.d.ts.map +1 -0
- package/build/types/label/index.d.ts +3 -0
- package/build/types/label/index.d.ts.map +1 -0
- package/build/types/tabs/Tab.d.ts +12 -1
- package/build/types/tabs/Tab.d.ts.map +1 -1
- package/build/types/tabs/TabList.d.ts +3 -8
- package/build/types/tabs/TabList.d.ts.map +1 -1
- package/build/types/tabs/TabPanel.d.ts +6 -14
- package/build/types/tabs/TabPanel.d.ts.map +1 -1
- package/build/types/tabs/Tabs.d.ts +83 -30
- package/build/types/tabs/Tabs.d.ts.map +1 -1
- package/build/types/tabs/index.d.ts +2 -1
- package/build/types/tabs/index.d.ts.map +1 -1
- package/build/types/tabs/utils.d.ts +12 -7
- package/build/types/tabs/utils.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/dateInput/DateInput.tests.story.tsx +6 -42
- package/src/dateLookup/DateLookup.rtl.spec.tsx +1 -1
- package/src/dateLookup/dateTrigger/DateTrigger.css +0 -8
- package/src/dateLookup/dateTrigger/DateTrigger.less +0 -8
- package/src/dateLookup/dateTrigger/DateTrigger.spec.js +1 -1
- package/src/dateLookup/dateTrigger/DateTrigger.tsx +9 -4
- package/src/field/Field.css +4 -0
- package/src/field/Field.less +5 -0
- package/src/field/Field.spec.tsx +41 -5
- package/src/field/Field.story.tsx +105 -7
- package/src/field/Field.tsx +34 -10
- package/src/i18n/en.json +1 -0
- package/src/index.ts +2 -1
- package/src/inlineAlert/InlineAlert.story.tsx +7 -72
- package/src/inlineAlert/InlineAlert.tsx +14 -3
- package/src/inputWithDisplayFormat/InputWithDisplayFormat.story.js +5 -10
- package/src/inputs/InputGroup.spec.tsx +1 -1
- package/src/inputs/SearchInput.spec.tsx +1 -1
- package/src/inputs/SelectInput.spec.tsx +1 -1
- package/src/label/Label.messages.tsx +8 -0
- package/src/label/Label.spec.tsx +53 -4
- package/src/label/Label.story.tsx +32 -26
- package/src/label/Label.tsx +47 -2
- package/src/label/index.ts +2 -0
- package/src/main.css +4 -8
- package/src/main.less +1 -0
- package/src/moneyInput/MoneyInput.story.tsx +11 -11
- package/src/radioGroup/RadioGroup.rtl.spec.tsx +1 -1
- package/src/select/Select.rtl.spec.tsx +1 -1
- package/src/switch/Switch.spec.tsx +1 -1
- package/src/switch/Switch.story.tsx +19 -21
- package/src/tabs/Tab.tsx +72 -0
- package/src/tabs/TabList.tsx +11 -0
- package/src/tabs/TabPanel.tsx +14 -0
- package/src/tabs/{Tabs.story.js → Tabs.story.tsx} +1 -1
- package/src/tabs/{Tabs.js → Tabs.tsx} +111 -74
- package/src/tabs/index.ts +2 -0
- package/src/tabs/{utils.spec.js → utils.spec.ts} +24 -21
- package/src/tabs/{utils.js → utils.ts} +15 -9
- package/src/field/Field.tests.story.tsx +0 -33
- package/src/tabs/Tab.js +0 -71
- package/src/tabs/TabList.js +0 -15
- package/src/tabs/TabPanel.js +0 -20
- package/src/tabs/index.js +0 -1
|
@@ -12,6 +12,6 @@ describe('Select', () => {
|
|
|
12
12
|
<Select options={options} selected={options[0]} onChange={() => {}} />
|
|
13
13
|
</Field>,
|
|
14
14
|
);
|
|
15
|
-
expect(screen.getByLabelText(
|
|
15
|
+
expect(screen.getByLabelText(/Currency/)).toHaveTextContent('USD');
|
|
16
16
|
});
|
|
17
17
|
});
|
|
@@ -89,6 +89,6 @@ describe('Switch', () => {
|
|
|
89
89
|
<Switch checked onClick={props.onClick} />
|
|
90
90
|
</Field>,
|
|
91
91
|
);
|
|
92
|
-
expect(screen.getByLabelText(
|
|
92
|
+
expect(screen.getByLabelText(/Dark mode/)).toHaveAttribute('role', 'switch');
|
|
93
93
|
});
|
|
94
94
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useState } from 'react';
|
|
2
2
|
|
|
3
3
|
import Switch from './Switch';
|
|
4
|
+
import { Field } from '../field/Field';
|
|
4
5
|
|
|
5
6
|
export default {
|
|
6
7
|
component: Switch,
|
|
@@ -13,16 +14,14 @@ export const Basic = () => {
|
|
|
13
14
|
|
|
14
15
|
return (
|
|
15
16
|
<div className="d-flex flex-column">
|
|
16
|
-
<
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
onClick={() => setCheck1(!checked1)}
|
|
25
|
-
/>
|
|
17
|
+
<Field id="switchId" label="A switch with a label" required>
|
|
18
|
+
<Switch
|
|
19
|
+
checked={checked1}
|
|
20
|
+
className="a-class-name"
|
|
21
|
+
id="switchId"
|
|
22
|
+
onClick={() => setCheck1(!checked1)}
|
|
23
|
+
/>
|
|
24
|
+
</Field>
|
|
26
25
|
|
|
27
26
|
<Switch
|
|
28
27
|
checked={checked2}
|
|
@@ -39,17 +38,16 @@ export const Disabled = () => {
|
|
|
39
38
|
|
|
40
39
|
return (
|
|
41
40
|
<div className="d-flex flex-column">
|
|
42
|
-
<
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
/>
|
|
41
|
+
<Field id="switchId" label="A switch with a label" required>
|
|
42
|
+
<Switch
|
|
43
|
+
checked={checked}
|
|
44
|
+
disabled
|
|
45
|
+
className="a-class-name"
|
|
46
|
+
aria-labelledby="labelID"
|
|
47
|
+
id="switchId"
|
|
48
|
+
onClick={() => setCheck(!checked)}
|
|
49
|
+
/>
|
|
50
|
+
</Field>
|
|
53
51
|
|
|
54
52
|
<Switch
|
|
55
53
|
checked={!checked}
|
package/src/tabs/Tab.tsx
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { clsx } from 'clsx';
|
|
2
|
+
import { forwardRef, useEffect, useRef } from 'react';
|
|
3
|
+
import { useEffectEvent } from '../common/hooks/useEffectEvent';
|
|
4
|
+
|
|
5
|
+
export interface TabProps {
|
|
6
|
+
children?: React.ReactNode;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
selected?: boolean;
|
|
9
|
+
id: string;
|
|
10
|
+
panelId: string;
|
|
11
|
+
style?: React.CSSProperties;
|
|
12
|
+
focusTab?: () => void;
|
|
13
|
+
onKeyDown: React.KeyboardEventHandler<HTMLLIElement>;
|
|
14
|
+
onClick?: React.MouseEventHandler<HTMLLIElement>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const noop = () => {};
|
|
18
|
+
|
|
19
|
+
const Tab = forwardRef(function Tab(
|
|
20
|
+
{
|
|
21
|
+
children,
|
|
22
|
+
id,
|
|
23
|
+
disabled,
|
|
24
|
+
panelId,
|
|
25
|
+
selected,
|
|
26
|
+
onKeyDown,
|
|
27
|
+
onClick,
|
|
28
|
+
style,
|
|
29
|
+
focusTab = noop,
|
|
30
|
+
}: TabProps,
|
|
31
|
+
reference: React.ForwardedRef<HTMLLIElement>,
|
|
32
|
+
) {
|
|
33
|
+
const handleFocusTab = useEffectEvent(focusTab);
|
|
34
|
+
const firstUpdate = useRef(true);
|
|
35
|
+
useEffect(
|
|
36
|
+
function checkFocus() {
|
|
37
|
+
if (firstUpdate.current) {
|
|
38
|
+
firstUpdate.current = false;
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (selected) {
|
|
43
|
+
handleFocusTab();
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
[handleFocusTab, selected],
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<li
|
|
51
|
+
ref={reference}
|
|
52
|
+
className={clsx('tabs__tab', selected ? 'np-text-body-large-bold' : 'np-text-body-large', {
|
|
53
|
+
'tabs__tab--selected': selected,
|
|
54
|
+
clickable: !disabled,
|
|
55
|
+
disabled,
|
|
56
|
+
})}
|
|
57
|
+
role="tab"
|
|
58
|
+
id={id}
|
|
59
|
+
aria-selected={selected ? 'true' : 'false'}
|
|
60
|
+
aria-disabled={disabled ? 'true' : 'false'}
|
|
61
|
+
aria-controls={disabled ? undefined : panelId}
|
|
62
|
+
tabIndex={0}
|
|
63
|
+
style={style}
|
|
64
|
+
onKeyDown={disabled ? undefined : onKeyDown}
|
|
65
|
+
onClick={onClick}
|
|
66
|
+
>
|
|
67
|
+
<span className="tabs__tab__content">{children}</span>
|
|
68
|
+
</li>
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
export default Tab;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface TabPanelProps {
|
|
2
|
+
children?: React.ReactNode;
|
|
3
|
+
id: string;
|
|
4
|
+
tabId: string;
|
|
5
|
+
style: React.CSSProperties;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export default function TabPanel({ children, id, tabId, style }: TabPanelProps) {
|
|
9
|
+
return (
|
|
10
|
+
<div className="tabs__panel" role="tabpanel" id={id} aria-labelledby={tabId} style={style}>
|
|
11
|
+
{children}
|
|
12
|
+
</div>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
@@ -97,7 +97,7 @@ export const NoPanelAnimation = () => {
|
|
|
97
97
|
const [selected, setSelected] = useState(0);
|
|
98
98
|
return (
|
|
99
99
|
<Tabs
|
|
100
|
-
|
|
100
|
+
animatePanelsOnClick={false}
|
|
101
101
|
className="tabs-custom-class"
|
|
102
102
|
name="tabs-docs"
|
|
103
103
|
transitionSpacing={Size.MEDIUM}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
import { Spring, SpringValue, animated } from '@react-spring/web';
|
|
1
|
+
import { Spring, animated } from '@react-spring/web';
|
|
3
2
|
import { clsx } from 'clsx';
|
|
4
3
|
import clamp from 'lodash.clamp';
|
|
5
|
-
import PropTypes from 'prop-types';
|
|
6
4
|
import { Component, createRef, Fragment } from 'react';
|
|
7
5
|
|
|
8
6
|
import { Size, Width, Direction } from '../common';
|
|
@@ -18,19 +16,69 @@ import {
|
|
|
18
16
|
swipedRightToLeft,
|
|
19
17
|
swipeShouldChangeTab,
|
|
20
18
|
getVelocity,
|
|
19
|
+
Swipe,
|
|
21
20
|
} from './utils';
|
|
22
21
|
|
|
23
22
|
const MIN_INDEX = 0;
|
|
24
23
|
|
|
25
|
-
|
|
24
|
+
export interface TabItem {
|
|
25
|
+
title: string;
|
|
26
|
+
content: React.ReactNode;
|
|
27
|
+
disabled: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
type TabsTransitionSpacing =
|
|
31
|
+
| 'default'
|
|
32
|
+
| `${Size.EXTRA_SMALL | Size.SMALL | Size.MEDIUM | Size.LARGE}`;
|
|
33
|
+
|
|
34
|
+
export interface TabsProps {
|
|
35
|
+
tabs: TabItem[];
|
|
36
|
+
selected: number;
|
|
37
|
+
name: string;
|
|
38
|
+
animatePanelsOnClick?: boolean;
|
|
39
|
+
changeTabOnSwipe?: boolean;
|
|
40
|
+
className?: string;
|
|
41
|
+
transitionSpacing?: TabsTransitionSpacing;
|
|
42
|
+
headerWidth?: `${Width}`;
|
|
43
|
+
id?: string;
|
|
44
|
+
onTabSelect: (index: number) => void;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface TabsState {
|
|
48
|
+
start: Swipe | null;
|
|
49
|
+
translateX: number;
|
|
50
|
+
translateFrom: number;
|
|
51
|
+
translateTo: number;
|
|
52
|
+
translateLineX: string | null;
|
|
53
|
+
isAnimating: boolean;
|
|
54
|
+
isSwiping: boolean;
|
|
55
|
+
isScrolling: boolean;
|
|
56
|
+
lastSwipeVelocity: number;
|
|
57
|
+
fullWidthTabs: boolean;
|
|
58
|
+
currentSwipe: Swipe[];
|
|
59
|
+
selectedTabIndex: number;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const SpacerWidth = {
|
|
63
|
+
default: 0,
|
|
64
|
+
xs: 8,
|
|
65
|
+
sm: 16,
|
|
66
|
+
md: 24,
|
|
67
|
+
lg: 32,
|
|
68
|
+
} satisfies Record<TabsTransitionSpacing, number>;
|
|
26
69
|
|
|
27
|
-
|
|
70
|
+
export default class Tabs extends Component<TabsProps, TabsState> {
|
|
71
|
+
declare props: TabsProps & Required<Pick<TabsProps, keyof typeof Tabs.defaultProps>>;
|
|
28
72
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
73
|
+
static defaultProps = {
|
|
74
|
+
changeTabOnSwipe: true,
|
|
75
|
+
transitionSpacing: 'default',
|
|
76
|
+
headerWidth: Width.BLOCK,
|
|
77
|
+
} satisfies Partial<TabsProps>;
|
|
32
78
|
|
|
33
|
-
|
|
79
|
+
containerReference = createRef<HTMLDivElement>();
|
|
80
|
+
|
|
81
|
+
constructor(props: Tabs['props']) {
|
|
34
82
|
super(props);
|
|
35
83
|
this.state = {
|
|
36
84
|
start: null,
|
|
@@ -43,17 +91,19 @@ class Tabs extends Component {
|
|
|
43
91
|
isScrolling: false,
|
|
44
92
|
lastSwipeVelocity: 0,
|
|
45
93
|
fullWidthTabs: props.headerWidth === Width.BLOCK,
|
|
94
|
+
currentSwipe: [],
|
|
95
|
+
selectedTabIndex: props.selected,
|
|
46
96
|
};
|
|
47
97
|
}
|
|
48
98
|
|
|
49
|
-
container = null;
|
|
99
|
+
container: HTMLDivElement | null = null;
|
|
50
100
|
|
|
51
101
|
containerWidth = 0;
|
|
52
102
|
|
|
53
|
-
tabRefs = [];
|
|
103
|
+
tabRefs: (HTMLLIElement | null)[] = [];
|
|
54
104
|
|
|
55
105
|
get filteredTabsLength() {
|
|
56
|
-
return this.props.tabs.filter(
|
|
106
|
+
return this.props.tabs.filter((tab) => !tab.disabled).length;
|
|
57
107
|
}
|
|
58
108
|
|
|
59
109
|
get MAX_INDEX() {
|
|
@@ -71,17 +121,17 @@ class Tabs extends Component {
|
|
|
71
121
|
window.addEventListener('resize', this.handleResize);
|
|
72
122
|
}
|
|
73
123
|
|
|
74
|
-
componentDidUpdate(previousProps, previousState) {
|
|
124
|
+
componentDidUpdate(previousProps: TabsProps, previousState: TabsState) {
|
|
75
125
|
const currentSelected = this.props.selected;
|
|
76
126
|
const previousSelected = previousProps.selected;
|
|
77
127
|
const currentSelectedTab = this.props.tabs[currentSelected];
|
|
78
|
-
const currentSelectedTabIsDisabled = currentSelectedTab
|
|
128
|
+
const currentSelectedTabIsDisabled = currentSelectedTab?.disabled;
|
|
79
129
|
const previousSelectedTab = previousProps.tabs[previousSelected];
|
|
80
|
-
const previousSelectedTabIsDisabled = previousSelectedTab
|
|
130
|
+
const previousSelectedTabIsDisabled = previousSelectedTab?.disabled;
|
|
81
131
|
const currentTabsLength = this.props.tabs.length;
|
|
82
132
|
const previousTabsLength = previousProps.tabs.length;
|
|
83
|
-
const currentDisabledTabsLength = this.props.tabs.filter(
|
|
84
|
-
const previousDisabledTabsLength = previousProps.tabs.filter(
|
|
133
|
+
const currentDisabledTabsLength = this.props.tabs.filter((tab) => !tab.disabled).length;
|
|
134
|
+
const previousDisabledTabsLength = previousProps.tabs.filter((tab) => !tab.disabled).length;
|
|
85
135
|
const currentHeaderWidth = this.props.headerWidth;
|
|
86
136
|
const previousFullHeaderWidth = previousProps.headerWidth;
|
|
87
137
|
const { animatePanelsOnClick } = this.props;
|
|
@@ -120,12 +170,12 @@ class Tabs extends Component {
|
|
|
120
170
|
}));
|
|
121
171
|
};
|
|
122
172
|
|
|
123
|
-
setContainerRefAndWidth = (node) => {
|
|
173
|
+
setContainerRefAndWidth = (node: HTMLDivElement | null) => {
|
|
124
174
|
this.container = node;
|
|
125
175
|
this.setContainerWidth(node);
|
|
126
176
|
};
|
|
127
177
|
|
|
128
|
-
setContainerWidth = (node) => {
|
|
178
|
+
setContainerWidth = (node: HTMLDivElement | null) => {
|
|
129
179
|
if (!node) {
|
|
130
180
|
return;
|
|
131
181
|
}
|
|
@@ -135,10 +185,10 @@ class Tabs extends Component {
|
|
|
135
185
|
this.containerWidth = width;
|
|
136
186
|
};
|
|
137
187
|
|
|
138
|
-
isTabDisabled = (index) => {
|
|
188
|
+
isTabDisabled = (index: number) => {
|
|
139
189
|
const { tabs } = this.props;
|
|
140
190
|
|
|
141
|
-
return tabs[index]
|
|
191
|
+
return tabs[index]?.disabled ?? false;
|
|
142
192
|
};
|
|
143
193
|
|
|
144
194
|
getAllTabsWidth = () => {
|
|
@@ -149,7 +199,7 @@ class Tabs extends Component {
|
|
|
149
199
|
.reduce((a, b) => a + b, 0);
|
|
150
200
|
};
|
|
151
201
|
|
|
152
|
-
getDistanceToSelectedTab = (selectedTabIndex) => {
|
|
202
|
+
getDistanceToSelectedTab = (selectedTabIndex: number) => {
|
|
153
203
|
return this.tabRefs
|
|
154
204
|
.filter((_, idx) => idx < selectedTabIndex)
|
|
155
205
|
.map((reference) => (reference ? reference.getBoundingClientRect().width : 0))
|
|
@@ -190,7 +240,7 @@ class Tabs extends Component {
|
|
|
190
240
|
* Gets the next tab that should be selected based on the swipe direction
|
|
191
241
|
* and the current selected tab (is called recursively to account for disabled tabs).
|
|
192
242
|
*/
|
|
193
|
-
getTabToSelect = (selected, start, end) => {
|
|
243
|
+
getTabToSelect = (selected: number, start: Swipe, end: Swipe): number => {
|
|
194
244
|
let nextSelected = selected;
|
|
195
245
|
|
|
196
246
|
if (swipedLeftToRight(start, end)) {
|
|
@@ -220,9 +270,19 @@ class Tabs extends Component {
|
|
|
220
270
|
return nextSelected;
|
|
221
271
|
};
|
|
222
272
|
|
|
223
|
-
swipedOverHalfOfContainer = (difference) => difference / this.containerWidth >= 0.5;
|
|
224
|
-
|
|
225
|
-
calculateApplicableDragDifference = ({
|
|
273
|
+
swipedOverHalfOfContainer = (difference: number) => difference / this.containerWidth >= 0.5;
|
|
274
|
+
|
|
275
|
+
calculateApplicableDragDifference = ({
|
|
276
|
+
currentSelected,
|
|
277
|
+
nextSelected,
|
|
278
|
+
start,
|
|
279
|
+
end,
|
|
280
|
+
}: {
|
|
281
|
+
currentSelected: number;
|
|
282
|
+
nextSelected: number;
|
|
283
|
+
start: Swipe;
|
|
284
|
+
end: Swipe;
|
|
285
|
+
}) => {
|
|
226
286
|
const difference = getSwipeDifference(start, end);
|
|
227
287
|
const elasticDrag = getElasticDragDifference(difference);
|
|
228
288
|
|
|
@@ -242,22 +302,22 @@ class Tabs extends Component {
|
|
|
242
302
|
return false;
|
|
243
303
|
};
|
|
244
304
|
|
|
245
|
-
switchTab = (index) => {
|
|
305
|
+
switchTab = (index: number) => {
|
|
246
306
|
const { onTabSelect } = this.props;
|
|
247
307
|
onTabSelect(index);
|
|
248
308
|
};
|
|
249
309
|
|
|
250
|
-
getTabIndexWithoutDisabledTabs(index) {
|
|
251
|
-
return index - this.props.tabs.slice(0, index).filter((tab) =>
|
|
310
|
+
getTabIndexWithoutDisabledTabs(index: number) {
|
|
311
|
+
return index - this.props.tabs.slice(0, index).filter((tab) => tab.disabled).length;
|
|
252
312
|
}
|
|
253
313
|
|
|
254
|
-
animateToTab = (index, instant) => {
|
|
314
|
+
animateToTab = (index: number, instant?: boolean) => {
|
|
255
315
|
this.animateLine(index);
|
|
256
316
|
|
|
257
317
|
this.animatePanel(this.getTabIndexWithoutDisabledTabs(index), instant);
|
|
258
318
|
};
|
|
259
319
|
|
|
260
|
-
animateLine = (index) => {
|
|
320
|
+
animateLine = (index: number) => {
|
|
261
321
|
this.setState((previousState) => ({
|
|
262
322
|
translateLineX: previousState.fullWidthTabs
|
|
263
323
|
? `${index * 100}%`
|
|
@@ -266,7 +326,7 @@ class Tabs extends Component {
|
|
|
266
326
|
};
|
|
267
327
|
|
|
268
328
|
// Pass `instant` to set the `translateX` to the new panel with no transition
|
|
269
|
-
animatePanel = (index, instant = false) => {
|
|
329
|
+
animatePanel = (index: number, instant = false) => {
|
|
270
330
|
const { translateTo: currentTranslateTo } = this.state;
|
|
271
331
|
|
|
272
332
|
const translateFrom = currentTranslateTo;
|
|
@@ -280,7 +340,7 @@ class Tabs extends Component {
|
|
|
280
340
|
});
|
|
281
341
|
};
|
|
282
342
|
|
|
283
|
-
disableScroll = (event) => {
|
|
343
|
+
disableScroll = (event: Event) => {
|
|
284
344
|
const { isSwiping } = this.state;
|
|
285
345
|
|
|
286
346
|
if (isSwiping) {
|
|
@@ -288,17 +348,17 @@ class Tabs extends Component {
|
|
|
288
348
|
}
|
|
289
349
|
};
|
|
290
350
|
|
|
291
|
-
handleTabClick = (index) => () => {
|
|
351
|
+
handleTabClick = (index: number) => () => {
|
|
292
352
|
this.switchTab(index);
|
|
293
353
|
};
|
|
294
354
|
|
|
295
|
-
onKeyDown = (index) => (event) => {
|
|
355
|
+
onKeyDown = (index: number) => (event: React.KeyboardEvent<HTMLLIElement>) => {
|
|
296
356
|
if (event && event.key === 'Enter') {
|
|
297
357
|
this.switchTab(index);
|
|
298
358
|
}
|
|
299
359
|
};
|
|
300
360
|
|
|
301
|
-
handleTouchStart = (event) => {
|
|
361
|
+
handleTouchStart: React.TouchEventHandler<HTMLDivElement> = (event) => {
|
|
302
362
|
const start = {
|
|
303
363
|
x: event.nativeEvent.touches[0].clientX,
|
|
304
364
|
y: event.nativeEvent.touches[0].clientY,
|
|
@@ -312,11 +372,15 @@ class Tabs extends Component {
|
|
|
312
372
|
event.persist();
|
|
313
373
|
};
|
|
314
374
|
|
|
315
|
-
handleTouchMove = (event) => {
|
|
375
|
+
handleTouchMove: React.TouchEventHandler<HTMLDivElement> = (event) => {
|
|
316
376
|
const { start } = this.state;
|
|
377
|
+
if (start == null) {
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
|
|
317
381
|
const { selected: currentSelectedFromProps } = this.props;
|
|
318
382
|
const selected = this.getTabIndexWithoutDisabledTabs(currentSelectedFromProps);
|
|
319
|
-
const end = {
|
|
383
|
+
const end: Swipe = {
|
|
320
384
|
x: event.nativeEvent.changedTouches[0].clientX,
|
|
321
385
|
y: event.nativeEvent.changedTouches[0].clientY,
|
|
322
386
|
time: Date.now(),
|
|
@@ -363,10 +427,14 @@ class Tabs extends Component {
|
|
|
363
427
|
}
|
|
364
428
|
};
|
|
365
429
|
|
|
366
|
-
handleTouchEnd = (event) => {
|
|
430
|
+
handleTouchEnd: React.TouchEventHandler<HTMLDivElement> = (event) => {
|
|
367
431
|
const { start, isSwiping } = this.state;
|
|
432
|
+
if (start == null) {
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
|
|
368
436
|
const { selected } = this.props;
|
|
369
|
-
const end = {
|
|
437
|
+
const end: Swipe = {
|
|
370
438
|
x: event.nativeEvent.changedTouches[0].clientX,
|
|
371
439
|
y: event.nativeEvent.changedTouches[0].clientY,
|
|
372
440
|
time: Date.now(),
|
|
@@ -433,7 +501,7 @@ class Tabs extends Component {
|
|
|
433
501
|
const sliderWidth = tabsLength * this.containerWidth + spacer * 2;
|
|
434
502
|
|
|
435
503
|
// Uses `props.panelTransitionSpacing` to add a spacer in-between the `TabPanel` you're transitioning to/from
|
|
436
|
-
const Spacer = ({ id }) =>
|
|
504
|
+
const Spacer = ({ id }: { id: string }) =>
|
|
437
505
|
spacer > 0 && (
|
|
438
506
|
<div
|
|
439
507
|
key={id}
|
|
@@ -472,10 +540,10 @@ class Tabs extends Component {
|
|
|
472
540
|
disabled={disabled}
|
|
473
541
|
focusTab={() => {
|
|
474
542
|
if (this.containerReference.current?.contains(document.activeElement)) {
|
|
475
|
-
this.tabRefs[index]
|
|
543
|
+
this.tabRefs[index]?.focus();
|
|
476
544
|
}
|
|
477
545
|
}}
|
|
478
|
-
onClick={disabled ?
|
|
546
|
+
onClick={disabled ? undefined : this.handleTabClick(index)}
|
|
479
547
|
onKeyDown={this.onKeyDown(index)}
|
|
480
548
|
{...(fullWidthTabs
|
|
481
549
|
? { style: { width: `${(1 / tabs.length) * 100}%` } }
|
|
@@ -564,34 +632,3 @@ class Tabs extends Component {
|
|
|
564
632
|
);
|
|
565
633
|
}
|
|
566
634
|
}
|
|
567
|
-
|
|
568
|
-
const SpacerSizes = { ...Size, NONE: 'default' };
|
|
569
|
-
|
|
570
|
-
Tabs.propTypes = {
|
|
571
|
-
tabs: PropTypes.arrayOf(
|
|
572
|
-
PropTypes.shape({
|
|
573
|
-
title: PropTypes.string.isRequired,
|
|
574
|
-
content: PropTypes.node.isRequired,
|
|
575
|
-
disabled: PropTypes.bool.isRequired,
|
|
576
|
-
}),
|
|
577
|
-
).isRequired,
|
|
578
|
-
selected: PropTypes.number.isRequired,
|
|
579
|
-
name: PropTypes.string.isRequired,
|
|
580
|
-
animatePanelsOnClick: PropTypes.bool,
|
|
581
|
-
changeTabOnSwipe: PropTypes.bool,
|
|
582
|
-
className: PropTypes.string,
|
|
583
|
-
transitionSpacing: PropTypes.oneOf(['default', 'xs', 'sm', 'md', 'lg']),
|
|
584
|
-
headerWidth: PropTypes.oneOf(['auto', 'block']),
|
|
585
|
-
id: PropTypes.string,
|
|
586
|
-
onTabSelect: PropTypes.func.isRequired,
|
|
587
|
-
};
|
|
588
|
-
|
|
589
|
-
Tabs.defaultProps = {
|
|
590
|
-
animatePanelsOnClick: false,
|
|
591
|
-
changeTabOnSwipe: true,
|
|
592
|
-
className: '',
|
|
593
|
-
transitionSpacing: SpacerSizes.NONE,
|
|
594
|
-
headerWidth: Width.BLOCK,
|
|
595
|
-
};
|
|
596
|
-
|
|
597
|
-
export default Tabs;
|
|
@@ -5,12 +5,13 @@ import {
|
|
|
5
5
|
getSwipeDifference,
|
|
6
6
|
swipeShouldChangeTab,
|
|
7
7
|
getVelocity,
|
|
8
|
+
Swipe,
|
|
8
9
|
} from './utils';
|
|
9
10
|
|
|
10
11
|
describe('Tabs Utility', () => {
|
|
11
|
-
let start;
|
|
12
|
-
let end;
|
|
13
|
-
let coords;
|
|
12
|
+
let start: Swipe;
|
|
13
|
+
let end: Swipe;
|
|
14
|
+
let coords: Swipe[];
|
|
14
15
|
|
|
15
16
|
beforeEach(() => {
|
|
16
17
|
jest.clearAllMocks();
|
|
@@ -18,8 +19,8 @@ describe('Tabs Utility', () => {
|
|
|
18
19
|
|
|
19
20
|
describe('when the ending x axis is greater than the starting x axis', () => {
|
|
20
21
|
beforeEach(() => {
|
|
21
|
-
start = { x: 0, time: 1569538800000 };
|
|
22
|
-
end = { x: 50, time: 1569538800300 };
|
|
22
|
+
start = { x: 0, y: 0, time: 1569538800000 };
|
|
23
|
+
end = { x: 50, y: 0, time: 1569538800300 };
|
|
23
24
|
});
|
|
24
25
|
|
|
25
26
|
it('determines that the swipe was left to right', () => {
|
|
@@ -34,8 +35,8 @@ describe('Tabs Utility', () => {
|
|
|
34
35
|
|
|
35
36
|
describe('when the ending x axis is less than the starting x axis', () => {
|
|
36
37
|
beforeEach(() => {
|
|
37
|
-
start = { x: 50, time: 1569538800000 };
|
|
38
|
-
end = { x: 0, time: 1569538800300 };
|
|
38
|
+
start = { x: 50, y: 0, time: 1569538800000 };
|
|
39
|
+
end = { x: 0, y: 0, time: 1569538800300 };
|
|
39
40
|
});
|
|
40
41
|
|
|
41
42
|
it('determines that the swipe was right to left', () => {
|
|
@@ -50,20 +51,20 @@ describe('Tabs Utility', () => {
|
|
|
50
51
|
|
|
51
52
|
describe('checking if a swipe should change tabs', () => {
|
|
52
53
|
it('should change tab when the difference and velocity are significant', () => {
|
|
53
|
-
start = { x: 0, time: 1569538800000 };
|
|
54
|
-
end = { x: 100, time: 1569538800001 };
|
|
54
|
+
start = { x: 0, y: 0, time: 1569538800000 };
|
|
55
|
+
end = { x: 100, y: 0, time: 1569538800001 };
|
|
55
56
|
expect(swipeShouldChangeTab(start, end)).toBe(true);
|
|
56
57
|
});
|
|
57
58
|
|
|
58
59
|
it('should not change tab when the difference is small', () => {
|
|
59
|
-
start = { x: 0, time: 1569538800000 };
|
|
60
|
-
end = { x: 1, time: 1569538800001 };
|
|
60
|
+
start = { x: 0, y: 0, time: 1569538800000 };
|
|
61
|
+
end = { x: 1, y: 0, time: 1569538800001 };
|
|
61
62
|
expect(swipeShouldChangeTab(start, end)).toBe(false);
|
|
62
63
|
});
|
|
63
64
|
|
|
64
65
|
it('should not change tab when the velocity is small', () => {
|
|
65
|
-
start = { x: 0, time: 1569538800000 };
|
|
66
|
-
end = { x: 100, time: 1569538900000 };
|
|
66
|
+
start = { x: 0, y: 0, time: 1569538800000 };
|
|
67
|
+
end = { x: 100, y: 0, time: 1569538900000 };
|
|
67
68
|
expect(swipeShouldChangeTab(start, end)).toBe(false);
|
|
68
69
|
});
|
|
69
70
|
});
|
|
@@ -71,11 +72,11 @@ describe('Tabs Utility', () => {
|
|
|
71
72
|
describe('checking for velocity against an array of touches', () => {
|
|
72
73
|
beforeEach(() => {
|
|
73
74
|
coords = [
|
|
74
|
-
{ x: 0, time: 1569538800000 },
|
|
75
|
-
{ x: 100, time: 1569538825000 },
|
|
76
|
-
{ x: 200, time: 1569538850000 },
|
|
77
|
-
{ x: 300, time: 1569538875000 },
|
|
78
|
-
{ x: 400, time: 1569538900000 },
|
|
75
|
+
{ x: 0, y: 0, time: 1569538800000 },
|
|
76
|
+
{ x: 100, y: 0, time: 1569538825000 },
|
|
77
|
+
{ x: 200, y: 0, time: 1569538850000 },
|
|
78
|
+
{ x: 300, y: 0, time: 1569538875000 },
|
|
79
|
+
{ x: 400, y: 0, time: 1569538900000 },
|
|
79
80
|
];
|
|
80
81
|
});
|
|
81
82
|
|
|
@@ -84,13 +85,15 @@ describe('Tabs Utility', () => {
|
|
|
84
85
|
});
|
|
85
86
|
|
|
86
87
|
it('should only take into account the last 5 coordinates', () => {
|
|
87
|
-
coords.unshift(
|
|
88
|
+
coords.unshift(
|
|
89
|
+
{ x: 10000, y: 0, time: 1569538800000 },
|
|
90
|
+
{ x: 100000, y: 0, time: 1569538800000 },
|
|
91
|
+
);
|
|
88
92
|
expect(getVelocity(coords)).toBe(0.004);
|
|
89
93
|
});
|
|
90
94
|
|
|
91
95
|
it('returns a default 0 if there are any issues with the touches passed in', () => {
|
|
92
96
|
expect(getVelocity([])).toBe(0);
|
|
93
|
-
expect(getVelocity([null, null, null])).toBe(0);
|
|
94
97
|
});
|
|
95
98
|
});
|
|
96
99
|
|
|
@@ -119,5 +122,5 @@ describe('Tabs Utility', () => {
|
|
|
119
122
|
});
|
|
120
123
|
});
|
|
121
124
|
|
|
122
|
-
const getApproximateElasticDragDifference = (difference) =>
|
|
125
|
+
const getApproximateElasticDragDifference = (difference: number) =>
|
|
123
126
|
Math.round(getElasticDragDifference(difference));
|