@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.
Files changed (121) hide show
  1. package/build/dateLookup/dateTrigger/DateTrigger.js +8 -4
  2. package/build/dateLookup/dateTrigger/DateTrigger.js.map +1 -1
  3. package/build/dateLookup/dateTrigger/DateTrigger.mjs +8 -4
  4. package/build/dateLookup/dateTrigger/DateTrigger.mjs.map +1 -1
  5. package/build/field/Field.js +36 -8
  6. package/build/field/Field.js.map +1 -1
  7. package/build/field/Field.mjs +37 -9
  8. package/build/field/Field.mjs.map +1 -1
  9. package/build/i18n/en.json +1 -0
  10. package/build/i18n/en.json.js +1 -0
  11. package/build/i18n/en.json.js.map +1 -1
  12. package/build/i18n/en.json.mjs +1 -0
  13. package/build/i18n/en.json.mjs.map +1 -1
  14. package/build/index.js +2 -2
  15. package/build/index.mjs +1 -1
  16. package/build/inlineAlert/InlineAlert.js +13 -6
  17. package/build/inlineAlert/InlineAlert.js.map +1 -1
  18. package/build/inlineAlert/InlineAlert.mjs +13 -6
  19. package/build/inlineAlert/InlineAlert.mjs.map +1 -1
  20. package/build/label/Label.js +35 -4
  21. package/build/label/Label.js.map +1 -1
  22. package/build/label/Label.messages.js +12 -0
  23. package/build/label/Label.messages.js.map +1 -0
  24. package/build/label/Label.messages.mjs +10 -0
  25. package/build/label/Label.messages.mjs.map +1 -0
  26. package/build/label/Label.mjs +36 -5
  27. package/build/label/Label.mjs.map +1 -1
  28. package/build/main.css +4 -8
  29. package/build/styles/dateLookup/dateTrigger/DateTrigger.css +0 -8
  30. package/build/styles/field/Field.css +4 -0
  31. package/build/styles/main.css +4 -8
  32. package/build/tabs/Tab.js +13 -38
  33. package/build/tabs/Tab.js.map +1 -1
  34. package/build/tabs/Tab.mjs +13 -34
  35. package/build/tabs/Tab.mjs.map +1 -1
  36. package/build/tabs/TabList.js +3 -11
  37. package/build/tabs/TabList.js.map +1 -1
  38. package/build/tabs/TabList.mjs +3 -7
  39. package/build/tabs/TabList.mjs.map +1 -1
  40. package/build/tabs/TabPanel.js +3 -16
  41. package/build/tabs/TabPanel.js.map +1 -1
  42. package/build/tabs/TabPanel.mjs +3 -12
  43. package/build/tabs/TabPanel.mjs.map +1 -1
  44. package/build/tabs/Tabs.js +24 -48
  45. package/build/tabs/Tabs.js.map +1 -1
  46. package/build/tabs/Tabs.mjs +24 -47
  47. package/build/tabs/Tabs.mjs.map +1 -1
  48. package/build/tabs/utils.js +0 -1
  49. package/build/tabs/utils.js.map +1 -1
  50. package/build/tabs/utils.mjs +0 -1
  51. package/build/tabs/utils.mjs.map +1 -1
  52. package/build/types/dateLookup/dateTrigger/DateTrigger.d.ts.map +1 -1
  53. package/build/types/field/Field.d.ts +4 -2
  54. package/build/types/field/Field.d.ts.map +1 -1
  55. package/build/types/index.d.ts +2 -1
  56. package/build/types/index.d.ts.map +1 -1
  57. package/build/types/inlineAlert/InlineAlert.d.ts +9 -0
  58. package/build/types/inlineAlert/InlineAlert.d.ts.map +1 -1
  59. package/build/types/label/Label.d.ts +21 -1
  60. package/build/types/label/Label.d.ts.map +1 -1
  61. package/build/types/label/Label.messages.d.ts +8 -0
  62. package/build/types/label/Label.messages.d.ts.map +1 -0
  63. package/build/types/label/index.d.ts +3 -0
  64. package/build/types/label/index.d.ts.map +1 -0
  65. package/build/types/tabs/Tab.d.ts +12 -1
  66. package/build/types/tabs/Tab.d.ts.map +1 -1
  67. package/build/types/tabs/TabList.d.ts +3 -8
  68. package/build/types/tabs/TabList.d.ts.map +1 -1
  69. package/build/types/tabs/TabPanel.d.ts +6 -14
  70. package/build/types/tabs/TabPanel.d.ts.map +1 -1
  71. package/build/types/tabs/Tabs.d.ts +83 -30
  72. package/build/types/tabs/Tabs.d.ts.map +1 -1
  73. package/build/types/tabs/index.d.ts +2 -1
  74. package/build/types/tabs/index.d.ts.map +1 -1
  75. package/build/types/tabs/utils.d.ts +12 -7
  76. package/build/types/tabs/utils.d.ts.map +1 -1
  77. package/package.json +2 -2
  78. package/src/dateInput/DateInput.tests.story.tsx +6 -42
  79. package/src/dateLookup/DateLookup.rtl.spec.tsx +1 -1
  80. package/src/dateLookup/dateTrigger/DateTrigger.css +0 -8
  81. package/src/dateLookup/dateTrigger/DateTrigger.less +0 -8
  82. package/src/dateLookup/dateTrigger/DateTrigger.spec.js +1 -1
  83. package/src/dateLookup/dateTrigger/DateTrigger.tsx +9 -4
  84. package/src/field/Field.css +4 -0
  85. package/src/field/Field.less +5 -0
  86. package/src/field/Field.spec.tsx +41 -5
  87. package/src/field/Field.story.tsx +105 -7
  88. package/src/field/Field.tsx +34 -10
  89. package/src/i18n/en.json +1 -0
  90. package/src/index.ts +2 -1
  91. package/src/inlineAlert/InlineAlert.story.tsx +7 -72
  92. package/src/inlineAlert/InlineAlert.tsx +14 -3
  93. package/src/inputWithDisplayFormat/InputWithDisplayFormat.story.js +5 -10
  94. package/src/inputs/InputGroup.spec.tsx +1 -1
  95. package/src/inputs/SearchInput.spec.tsx +1 -1
  96. package/src/inputs/SelectInput.spec.tsx +1 -1
  97. package/src/label/Label.messages.tsx +8 -0
  98. package/src/label/Label.spec.tsx +53 -4
  99. package/src/label/Label.story.tsx +32 -26
  100. package/src/label/Label.tsx +47 -2
  101. package/src/label/index.ts +2 -0
  102. package/src/main.css +4 -8
  103. package/src/main.less +1 -0
  104. package/src/moneyInput/MoneyInput.story.tsx +11 -11
  105. package/src/radioGroup/RadioGroup.rtl.spec.tsx +1 -1
  106. package/src/select/Select.rtl.spec.tsx +1 -1
  107. package/src/switch/Switch.spec.tsx +1 -1
  108. package/src/switch/Switch.story.tsx +19 -21
  109. package/src/tabs/Tab.tsx +72 -0
  110. package/src/tabs/TabList.tsx +11 -0
  111. package/src/tabs/TabPanel.tsx +14 -0
  112. package/src/tabs/{Tabs.story.js → Tabs.story.tsx} +1 -1
  113. package/src/tabs/{Tabs.js → Tabs.tsx} +111 -74
  114. package/src/tabs/index.ts +2 -0
  115. package/src/tabs/{utils.spec.js → utils.spec.ts} +24 -21
  116. package/src/tabs/{utils.js → utils.ts} +15 -9
  117. package/src/field/Field.tests.story.tsx +0 -33
  118. package/src/tabs/Tab.js +0 -71
  119. package/src/tabs/TabList.js +0 -15
  120. package/src/tabs/TabPanel.js +0 -20
  121. package/src/tabs/index.js +0 -1
@@ -1,4 +1,4 @@
1
- import { render, screen } from '@testing-library/react';
1
+ import { render, screen } from '../test-utils';
2
2
 
3
3
  import RadioGroup from '.';
4
4
  import { Field } from '../field/Field';
@@ -12,6 +12,6 @@ describe('Select', () => {
12
12
  <Select options={options} selected={options[0]} onChange={() => {}} />
13
13
  </Field>,
14
14
  );
15
- expect(screen.getByLabelText('Currency')).toHaveTextContent('USD');
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('Dark mode')).toHaveAttribute('role', 'switch');
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
- <label id="labelID" htmlFor="switchId">
17
- A switch with a label
18
- </label>
19
- <Switch
20
- checked={checked1}
21
- className="a-class-name"
22
- aria-labelledby="labelID"
23
- id="switchId"
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
- <label id="labelID" htmlFor="switchId">
43
- A switch with a label
44
- </label>
45
- <Switch
46
- checked={checked}
47
- disabled
48
- className="a-class-name"
49
- aria-labelledby="labelID"
50
- id="switchId"
51
- onClick={() => setCheck(!checked)}
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}
@@ -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,11 @@
1
+ export interface TabListProps {
2
+ children?: React.ReactNode;
3
+ }
4
+
5
+ export default function TabList({ children }: TabListProps) {
6
+ return (
7
+ <ul className="tabs__tab-list" role="tablist">
8
+ {children}
9
+ </ul>
10
+ );
11
+ }
@@ -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
- animatePanels={false}
100
+ animatePanelsOnClick={false}
101
101
  className="tabs-custom-class"
102
102
  name="tabs-docs"
103
103
  transitionSpacing={Size.MEDIUM}
@@ -1,8 +1,6 @@
1
- /* eslint-disable react/forbid-dom-props */
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
- const enabledTabsFilter = (tab) => !tab.disabled;
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
- const SpacerWidth = { default: 0, xs: 8, sm: 16, md: 24, lg: 32 };
70
+ export default class Tabs extends Component<TabsProps, TabsState> {
71
+ declare props: TabsProps & Required<Pick<TabsProps, keyof typeof Tabs.defaultProps>>;
28
72
 
29
- class Tabs extends Component {
30
- /** @type {React.RefObject<HTMLDivElement | null>} */
31
- containerReference = createRef();
73
+ static defaultProps = {
74
+ changeTabOnSwipe: true,
75
+ transitionSpacing: 'default',
76
+ headerWidth: Width.BLOCK,
77
+ } satisfies Partial<TabsProps>;
32
78
 
33
- constructor(props) {
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(enabledTabsFilter).length;
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 && currentSelectedTab.disabled;
128
+ const currentSelectedTabIsDisabled = currentSelectedTab?.disabled;
79
129
  const previousSelectedTab = previousProps.tabs[previousSelected];
80
- const previousSelectedTabIsDisabled = previousSelectedTab && previousSelectedTab.disabled;
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(enabledTabsFilter).length;
84
- const previousDisabledTabsLength = previousProps.tabs.filter(enabledTabsFilter).length;
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] && tabs[index].disabled;
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 = ({ currentSelected, nextSelected, start, end }) => {
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) => !enabledTabsFilter(tab)).length;
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].focus();
543
+ this.tabRefs[index]?.focus();
476
544
  }
477
545
  }}
478
- onClick={disabled ? null : this.handleTabClick(index)}
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;
@@ -0,0 +1,2 @@
1
+ export { default } from './Tabs';
2
+ export type { TabItem, TabsProps } from './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({ x: 10000, time: 1569538800000 }, { x: 100000, time: 1569538800000 });
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));