@elliemae/ds-tabs 2.0.0-rc.1 → 2.0.0-rc.13

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 (105) hide show
  1. package/cjs/DSTabs.js +27 -108
  2. package/cjs/DSTabsCTX.js +30 -0
  3. package/cjs/DSTabsDatatestid.js +18 -0
  4. package/cjs/DSTabsTypes.js +2 -0
  5. package/cjs/config/useCrossRef.js +17 -0
  6. package/cjs/config/useTabs.js +51 -0
  7. package/cjs/defaultProps.js +27 -0
  8. package/cjs/{PanelContainer.js → exportsRelated/DSTab.js} +14 -30
  9. package/cjs/index.js +5 -5
  10. package/cjs/parts/carousel/Carousel.js +71 -0
  11. package/cjs/parts/carousel/styles.js +60 -0
  12. package/cjs/parts/carousel/useCarousel.js +91 -0
  13. package/cjs/parts/carousel/useCarouselCallbacks.js +72 -0
  14. package/cjs/parts/tabBar/TabBar.js +160 -0
  15. package/cjs/parts/tabBar/styles.js +199 -0
  16. package/cjs/parts/tabBar/useTabBar.js +98 -0
  17. package/cjs/parts/tabsContent/TabsContent.js +49 -0
  18. package/cjs/parts/tabsContent/styles.js +15 -0
  19. package/cjs/parts/tabsPanel/TabsPanels.js +77 -0
  20. package/cjs/parts/tabsPanel/styles.js +30 -0
  21. package/cjs/propTypes.js +33 -0
  22. package/cjs/utils/constants.js +29 -0
  23. package/cjs/utils/helpers.js +47 -0
  24. package/cjs/utils/hooks/useKeyboardNavigation.js +61 -0
  25. package/cjs/{useResize.js → utils/hooks/useResize.js} +8 -4
  26. package/cjs/utils/hooks/useTabsCallbacks.js +59 -0
  27. package/esm/DSTabs.js +29 -110
  28. package/esm/DSTabsCTX.js +25 -0
  29. package/esm/DSTabsDatatestid.js +14 -0
  30. package/esm/DSTabsTypes.js +1 -0
  31. package/esm/config/useCrossRef.js +13 -0
  32. package/esm/config/useTabs.js +47 -0
  33. package/esm/defaultProps.js +23 -0
  34. package/esm/{PanelContainer.js → exportsRelated/DSTab.js} +7 -26
  35. package/esm/index.js +4 -3
  36. package/esm/parts/carousel/Carousel.js +63 -0
  37. package/esm/parts/carousel/styles.js +49 -0
  38. package/esm/parts/carousel/useCarousel.js +87 -0
  39. package/esm/parts/carousel/useCarouselCallbacks.js +68 -0
  40. package/esm/parts/tabBar/TabBar.js +151 -0
  41. package/esm/parts/tabBar/styles.js +185 -0
  42. package/esm/parts/tabBar/useTabBar.js +90 -0
  43. package/esm/parts/tabsContent/TabsContent.js +40 -0
  44. package/esm/parts/tabsContent/styles.js +7 -0
  45. package/esm/parts/tabsPanel/TabsPanels.js +67 -0
  46. package/esm/parts/tabsPanel/styles.js +22 -0
  47. package/esm/propTypes.js +28 -0
  48. package/esm/utils/constants.js +21 -0
  49. package/esm/utils/helpers.js +37 -0
  50. package/esm/utils/hooks/useKeyboardNavigation.js +57 -0
  51. package/esm/{useResize.js → utils/hooks/useResize.js} +9 -5
  52. package/esm/utils/hooks/useTabsCallbacks.js +55 -0
  53. package/package.json +103 -36
  54. package/types/DSTabs.d.ts +10 -60
  55. package/types/DSTabsCTX.d.ts +4 -0
  56. package/types/DSTabsDatatestid.d.ts +12 -0
  57. package/types/DSTabsTypes.d.ts +202 -0
  58. package/types/config/useCrossRef.d.ts +2 -0
  59. package/types/config/useTabs.d.ts +2 -0
  60. package/types/defaultProps.d.ts +2 -0
  61. package/types/exportsRelated/DSTab.d.ts +14 -0
  62. package/types/index.d.ts +4 -3
  63. package/types/parts/carousel/Carousel.d.ts +3 -0
  64. package/types/parts/carousel/styles.d.ts +9 -0
  65. package/types/parts/carousel/useCarousel.d.ts +2 -0
  66. package/types/parts/carousel/useCarouselCallbacks.d.ts +2 -0
  67. package/types/parts/tabBar/TabBar.d.ts +2 -0
  68. package/types/parts/tabBar/styles.d.ts +7 -0
  69. package/types/parts/tabBar/useTabBar.d.ts +2 -0
  70. package/types/parts/tabsContent/TabsContent.d.ts +2 -0
  71. package/types/parts/tabsContent/styles.d.ts +1 -0
  72. package/types/parts/tabsPanel/TabsPanels.d.ts +2 -0
  73. package/types/parts/tabsPanel/styles.d.ts +2 -0
  74. package/types/propTypes.d.ts +54 -0
  75. package/types/tests/{DSTabs.events.test.d.ts → DSTabs.callbacks.test.d.ts} +0 -0
  76. package/types/tests/DSTabs.func.test.d.ts +1 -0
  77. package/types/tests/DSTabs.keyboard.test.d.ts +1 -0
  78. package/types/utils/constants.d.ts +19 -0
  79. package/types/utils/helpers.d.ts +8 -0
  80. package/types/utils/hooks/useKeyboardNavigation.d.ts +2 -0
  81. package/types/utils/hooks/useResize.d.ts +2 -0
  82. package/types/utils/hooks/useTabsCallbacks.d.ts +2 -0
  83. package/cjs/Carousel.js +0 -205
  84. package/cjs/DSTab.js +0 -46
  85. package/cjs/SubTabBar.js +0 -54
  86. package/cjs/TabBar.js +0 -421
  87. package/cjs/TabTypes.js +0 -15
  88. package/cjs/TabsImpl.js +0 -120
  89. package/cjs/TabsPanels.js +0 -85
  90. package/esm/Carousel.js +0 -196
  91. package/esm/DSTab.js +0 -36
  92. package/esm/SubTabBar.js +0 -48
  93. package/esm/TabBar.js +0 -410
  94. package/esm/TabTypes.js +0 -10
  95. package/esm/TabsImpl.js +0 -113
  96. package/esm/TabsPanels.js +0 -75
  97. package/types/Carousel.d.ts +0 -16
  98. package/types/DSTab.d.ts +0 -22
  99. package/types/PanelContainer.d.ts +0 -20
  100. package/types/SubTabBar.d.ts +0 -23
  101. package/types/TabBar.d.ts +0 -30
  102. package/types/TabTypes.d.ts +0 -5
  103. package/types/TabsImpl.d.ts +0 -37
  104. package/types/TabsPanels.d.ts +0 -5
  105. package/types/useResize.d.ts +0 -1
@@ -0,0 +1,90 @@
1
+ import 'core-js/modules/web.dom-collections.iterator.js';
2
+ import { useContext, useState, useCallback, useEffect, useLayoutEffect } from 'react';
3
+ import useResizeObserver from '@react-hook/resize-observer';
4
+ import { isElementVisible, shouldHaveLeftGradient, shouldHaveRightGradient } from '../../utils/helpers.js';
5
+ import { DSTabsContext } from '../../DSTabsCTX.js';
6
+ import { defaultIndicatorStyles } from '../../utils/constants.js';
7
+
8
+ const useTabBar = () => {
9
+ const {
10
+ actualActiveTab,
11
+ actualActiveTabRef,
12
+ tabsListRef,
13
+ props: {
14
+ showSelectionIndicator,
15
+ isDSMobile,
16
+ fixedTabsHeaders
17
+ }
18
+ } = useContext(DSTabsContext);
19
+ const [mobileGradients, setMobileGradients] = useState({
20
+ left: false,
21
+ right: false
22
+ });
23
+ const [indicatorStyle, setIndicatorStyle] = useState(defaultIndicatorStyles);
24
+ const getIndicatorStyles = useCallback(() => {
25
+ if (tabsListRef.current) {
26
+ const tabList = tabsListRef.current;
27
+ const activeTabButton = actualActiveTabRef.current;
28
+ if (!activeTabButton || !isElementVisible(activeTabButton, tabList)) return defaultIndicatorStyles; // The missingMargins are to make the selection indicator white marks work properly in desktop, we need
29
+ // the full width of the tab button with margins.
30
+
31
+ const missingMargins = isDSMobile ? 0 : 16;
32
+ const tabClientWidth = isDSMobile && !fixedTabsHeaders ? activeTabButton.clientWidth : activeTabButton.clientWidth + missingMargins * 2; // The reason why we use the tabList.scrollLeft is due to the centerOnTabChange (only in Mobile and FlexibleTabs)
33
+ // behavior that adds scroll to the tabList
34
+
35
+ const position = isDSMobile && !fixedTabsHeaders ? activeTabButton.getBoundingClientRect().left - tabList.getBoundingClientRect().left + tabList.scrollLeft : activeTabButton.getBoundingClientRect().left - tabList.getBoundingClientRect().left - missingMargins;
36
+ const backgroundWithTransparentAndWhite = "linear-gradient(to right, transparent 0px, transparent 12px, white 12px, white 16px, #1E79C2 16px, #1E79C2 ".concat(tabClientWidth - 16, "px,white ").concat(tabClientWidth - 16, "px,white ").concat(tabClientWidth - 12, "px,transparent ").concat(tabClientWidth - 12, "px,transparent ").concat(tabClientWidth, "px )");
37
+ const backgroundWithTransparent = "linear-gradient(to right, transparent 0px, transparent 16px, #1E79C2 16px, #1E79C2 ".concat(tabClientWidth - 16, "px,transparent ").concat(tabClientWidth - 16, "px,transparent ").concat(tabClientWidth, "px )");
38
+ return {
39
+ left: "".concat(position, "px"),
40
+ width: "".concat(tabClientWidth, "px"),
41
+ backgroundWithTransparentAndWhite,
42
+ backgroundWithTransparent
43
+ };
44
+ }
45
+
46
+ return defaultIndicatorStyles;
47
+ }, [actualActiveTabRef, fixedTabsHeaders, isDSMobile, tabsListRef]);
48
+ const updateIndicatorStyle = useCallback(() => setTimeout(() => setIndicatorStyle(getIndicatorStyles()), 100), [getIndicatorStyles, setIndicatorStyle]);
49
+ useResizeObserver(tabsListRef, updateIndicatorStyle);
50
+ const updateMobileGradients = useCallback(() => {
51
+ if (tabsListRef.current && isDSMobile && !fixedTabsHeaders) {
52
+ const {
53
+ scrollLeft,
54
+ scrollWidth,
55
+ clientWidth
56
+ } = tabsListRef.current;
57
+ setMobileGradients({
58
+ left: shouldHaveLeftGradient(scrollLeft),
59
+ right: shouldHaveRightGradient(scrollLeft, clientWidth, scrollWidth)
60
+ });
61
+ }
62
+ }, [fixedTabsHeaders, isDSMobile, tabsListRef]);
63
+ useEffect(() => {
64
+ if (showSelectionIndicator) {
65
+ updateIndicatorStyle();
66
+ }
67
+
68
+ if (isDSMobile && !fixedTabsHeaders) {
69
+ updateMobileGradients();
70
+ }
71
+ }, [updateIndicatorStyle, showSelectionIndicator, actualActiveTab, updateMobileGradients, isDSMobile, fixedTabsHeaders]);
72
+ useLayoutEffect(() => {
73
+ if (showSelectionIndicator) {
74
+ updateIndicatorStyle();
75
+ }
76
+
77
+ if (isDSMobile && !fixedTabsHeaders) {
78
+ updateMobileGradients();
79
+ }
80
+ }, [updateMobileGradients, showSelectionIndicator, updateIndicatorStyle, isDSMobile, fixedTabsHeaders]);
81
+ return {
82
+ updateIndicatorStyle,
83
+ setIndicatorStyle,
84
+ indicatorStyle,
85
+ mobileGradients,
86
+ updateMobileGradients
87
+ };
88
+ };
89
+
90
+ export { useTabBar };
@@ -0,0 +1,40 @@
1
+ import 'core-js/modules/esnext.async-iterator.filter.js';
2
+ import 'core-js/modules/esnext.iterator.constructor.js';
3
+ import 'core-js/modules/esnext.iterator.filter.js';
4
+ import 'core-js/modules/esnext.async-iterator.for-each.js';
5
+ import 'core-js/modules/esnext.iterator.for-each.js';
6
+ import _defineProperty from '@babel/runtime/helpers/esm/defineProperty';
7
+ import _jsx from '@babel/runtime/helpers/esm/jsx';
8
+ import { useContext } from 'react';
9
+ import { TabsPanels } from '../tabsPanel/TabsPanels.js';
10
+ import { TabBar } from '../tabBar/TabBar.js';
11
+ import { StyledTabBarContainer } from './styles.js';
12
+ import { DSTabsContext } from '../../DSTabsCTX.js';
13
+ import { DSTabsDatatestid } from '../../DSTabsDatatestid.js';
14
+ import { jsxs } from 'react/jsx-runtime';
15
+
16
+ var _TabBar, _TabsPanels;
17
+
18
+ function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) { symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); } keys.push.apply(keys, symbols); } return keys; }
19
+
20
+ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
21
+ const TabsContent = () => {
22
+ const {
23
+ props: {
24
+ containerProps,
25
+ innerRef,
26
+ tabsListAriaLabel,
27
+ tabBarExtraContent
28
+ }
29
+ } = useContext(DSTabsContext);
30
+ return /*#__PURE__*/jsxs("div", _objectSpread(_objectSpread({}, containerProps), {}, {
31
+ ref: innerRef,
32
+ "data-testid": DSTabsDatatestid.TABS_CONTAINER,
33
+ children: [/*#__PURE__*/_jsx(StyledTabBarContainer, {
34
+ "aria-label": tabsListAriaLabel,
35
+ role: "tablist"
36
+ }, void 0, _TabBar || (_TabBar = /*#__PURE__*/_jsx(TabBar, {})), tabBarExtraContent), _TabsPanels || (_TabsPanels = /*#__PURE__*/_jsx(TabsPanels, {}))]
37
+ }));
38
+ };
39
+
40
+ export { TabsContent };
@@ -0,0 +1,7 @@
1
+ import styled from 'styled-components';
2
+
3
+ const StyledTabBarContainer = /*#__PURE__*/styled.div.withConfig({
4
+ componentId: "sc-vm3k1y-0"
5
+ })(["display:flex;overflow-x:hidden;"]);
6
+
7
+ export { StyledTabBarContainer };
@@ -0,0 +1,67 @@
1
+ import _jsx from '@babel/runtime/helpers/esm/jsx';
2
+ import 'core-js/modules/esnext.async-iterator.map.js';
3
+ import 'core-js/modules/esnext.iterator.map.js';
4
+ import React, { useContext, useCallback, useMemo } from 'react';
5
+ import SwipeableViews from 'react-swipeable-views';
6
+ import { getIdByIndex, getIndexById } from '../../utils/helpers.js';
7
+ import { StyledPanelContainer } from './styles.js';
8
+ import { DSTabsContext } from '../../DSTabsCTX.js';
9
+ import { useTabsCallbacks } from '../../utils/hooks/useTabsCallbacks.js';
10
+ import { jsx, Fragment } from 'react/jsx-runtime';
11
+
12
+ const TabsPanels = () => {
13
+ const {
14
+ actualActiveTab,
15
+ focusableTabsRef,
16
+ props: {
17
+ animated,
18
+ enableMouseEvents,
19
+ onlyRenderActiveTab,
20
+ children: tabs,
21
+ isDSMobile
22
+ }
23
+ } = useContext(DSTabsContext);
24
+ const {
25
+ handleOnTabChange,
26
+ handleOnMouseDown
27
+ } = useTabsCallbacks();
28
+ const handleOnChangeIndex = useCallback(index => {
29
+ handleOnTabChange(getIdByIndex(tabs, index));
30
+ if (focusableTabsRef.current) focusableTabsRef.current[index].click();
31
+ }, [focusableTabsRef, handleOnTabChange, tabs]);
32
+ const panels = useMemo(() => React.Children.map(tabs, tab => {
33
+ const {
34
+ tabId: panelId = '',
35
+ style,
36
+ children: content
37
+ } = tab.props;
38
+ const isActive = actualActiveTab === panelId;
39
+ const shouldRender = !onlyRenderActiveTab || isActive;
40
+ return shouldRender && /*#__PURE__*/_jsx(StyledPanelContainer, {
41
+ id: panelId,
42
+ "aria-labelledby": "".concat(panelId, "-label"),
43
+ "aria-hidden": !isActive,
44
+ "data-panel-id": panelId,
45
+ "data-testid": "ds-tabs-tab-panel",
46
+ hidden: !isActive,
47
+ onMouseDown: handleOnMouseDown,
48
+ role: "tabpanel",
49
+ style: style,
50
+ isDSMobile: isDSMobile
51
+ }, panelId, content);
52
+ }), [actualActiveTab, handleOnMouseDown, isDSMobile, onlyRenderActiveTab, tabs]);
53
+
54
+ if (animated) {
55
+ return /*#__PURE__*/_jsx(SwipeableViews, {
56
+ enableMouseEvents: enableMouseEvents,
57
+ index: getIndexById(tabs, actualActiveTab),
58
+ onChangeIndex: handleOnChangeIndex
59
+ }, void 0, panels);
60
+ }
61
+
62
+ return /*#__PURE__*/jsx(Fragment, {
63
+ children: panels
64
+ });
65
+ };
66
+
67
+ export { TabsPanels };
@@ -0,0 +1,22 @@
1
+ import styled from 'styled-components';
2
+
3
+ const StyledPanelContainer = /*#__PURE__*/styled.div.withConfig({
4
+ componentId: "sc-oxw5jq-0"
5
+ })(["padding-top:", ";&:focus{outline:none;}", " ", ""], _ref => {
6
+ let {
7
+ theme
8
+ } = _ref;
9
+ return theme.space.xs;
10
+ }, _ref2 => {
11
+ let {
12
+ hidden
13
+ } = _ref2;
14
+ return hidden ? 'visibility: hidden;' : '';
15
+ }, _ref3 => {
16
+ let {
17
+ isDSMobile
18
+ } = _ref3;
19
+ return isDSMobile ? "padding-top: 0;" : "";
20
+ });
21
+
22
+ export { StyledPanelContainer };
@@ -0,0 +1,28 @@
1
+ import { PropTypes } from 'react-desc';
2
+ import { TabTypesValuesAsArray } from './utils/constants.js';
3
+
4
+ const tabPropTypes = {
5
+ tabId: PropTypes.string.description('Unique Tab id.').defaultValue(''),
6
+ title: PropTypes.string.description('Tab title.').defaultValue(''),
7
+ required: PropTypes.bool.description('Show a required mark next to the title.').defaultValue(false),
8
+ disabled: PropTypes.bool.description('Disables Tab.').defaultValue(false)
9
+ };
10
+ const tabsPropTypes = {
11
+ animated: PropTypes.bool.description('Whether the Tab transition should animate or not.').defaultValue(true),
12
+ enableMouseEvents: PropTypes.bool.description('Enables Tab transition with mouse drag events.').defaultValue(false),
13
+ allowTextSelection: PropTypes.bool.description('Allows text selection when mouse drag events are active.').defaultValue(false),
14
+ onTabChange: PropTypes.func.description('Handler when a user clicks on a different Tab.').defaultValue('() => {}'),
15
+ type: PropTypes.oneOf(TabTypesValuesAsArray).description('Tab type.').defaultValue(TabTypesValuesAsArray[0]),
16
+ tabBarExtraContent: PropTypes.element.description('Extra content next to the Tab Bar.').defaultValue(undefined),
17
+ onlyRenderActiveTab: PropTypes.bool.description('Only renders the active Tab in the DOM.').defaultValue(false),
18
+ withCarousel: PropTypes.bool.description('Activate carousel behavior.').defaultValue(false),
19
+ activeTab: PropTypes.string.description('Passing a value makes DSTabs a controlled component.').defaultValue('First DSTab children.'),
20
+ children: PropTypes.node.description('Use DSTab component as children.').isRequired,
21
+ tabsListAriaLabel: PropTypes.string.description('Aria label for the list.').defaultValue('Tab list.'),
22
+ containerProps: PropTypes.object.description('Additional properties for the container.').defaultValue({}),
23
+ innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.shape({
24
+ current: PropTypes.any
25
+ })]).description('Ref to the container.').defaultValue(undefined)
26
+ };
27
+
28
+ export { tabPropTypes, tabsPropTypes };
@@ -0,0 +1,21 @@
1
+ const TabTypes = {
2
+ NORMAL: 'normal',
3
+ NORMAL_SMALL: 'normal_small',
4
+ SUBTABS: 'subtabs'
5
+ };
6
+ const TabTypesValuesAsArray = Object.values(TabTypes);
7
+ const MOBILE_GRADIENT_WIDTH = 24;
8
+ const defaultIndicatorStyles = {
9
+ left: '0px',
10
+ width: '0px',
11
+ backgroundWithTransparentAndWhite: '',
12
+ backgroundWithTransparent: ''
13
+ };
14
+ const gradientOffsetsAccordingType = {
15
+ normal_small: 14,
16
+ normal: 16,
17
+ subtabs: 16,
18
+ fixedTabsHeaders: 0
19
+ };
20
+
21
+ export { MOBILE_GRADIENT_WIDTH, TabTypes, TabTypesValuesAsArray, defaultIndicatorStyles, gradientOffsetsAccordingType };
@@ -0,0 +1,37 @@
1
+ import { MOBILE_GRADIENT_WIDTH } from './constants.js';
2
+
3
+ const isElementVisible = (tab, tabList) => {
4
+ const tabDimensions = tab.getBoundingClientRect();
5
+ const tabListDimensions = tabList.getBoundingClientRect();
6
+ return tabDimensions.left <= tabListDimensions.left ? tabListDimensions.left - tabDimensions.left <= tabDimensions.width : tabDimensions.right - tabListDimensions.right <= tabDimensions.width;
7
+ };
8
+ const isElementVisibleCarousel = function (tab, tabContainer) {
9
+ let firstOrLastItem = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
10
+
11
+ if (tab && tabContainer) {
12
+ const tabDimensions = tab.getBoundingClientRect();
13
+ const tabContainerDimensions = tabContainer.getBoundingClientRect();
14
+ const offset = firstOrLastItem ? 16 : 32;
15
+ const tabDimensionsLeft = Math.floor(tabDimensions.left - offset);
16
+ const tabDimensionsRight = Math.floor(tabDimensions.right + offset);
17
+ const tabContainerDimensionsLeft = Math.floor(tabContainerDimensions.left);
18
+ const tabContainerDimensionsRight = Math.floor(tabContainerDimensions.right);
19
+ return tabDimensionsLeft >= tabContainerDimensionsLeft && tabDimensionsRight <= tabContainerDimensionsRight;
20
+ }
21
+
22
+ return false;
23
+ };
24
+ const getVisibleDimensions = (node, referenceNode) => {
25
+ const nodeDimensions = node.getBoundingClientRect();
26
+ const referenceNodeDimensions = referenceNode.getBoundingClientRect();
27
+ return {
28
+ width: Math.min(node.clientWidth, referenceNodeDimensions.left + referenceNode.clientWidth - nodeDimensions.left, node.clientWidth - (referenceNodeDimensions.left - nodeDimensions.left)),
29
+ height: Math.min(node.clientHeight, referenceNodeDimensions.top + referenceNode.clientHeight - nodeDimensions.top, node.clientHeight - (referenceNodeDimensions.top - nodeDimensions.top))
30
+ };
31
+ };
32
+ const getIndexById = (children, activeId) => children && children.findIndex(item => item.props.tabId === activeId);
33
+ const getIdByIndex = (children, index) => children && children[index].props.tabId || '';
34
+ const shouldHaveLeftGradient = scrollLeft => Math.abs(scrollLeft) > MOBILE_GRADIENT_WIDTH;
35
+ const shouldHaveRightGradient = (scrollLeft, clientWidth, scrollWidth) => Math.abs(scrollLeft) <= scrollWidth - (clientWidth + MOBILE_GRADIENT_WIDTH);
36
+
37
+ export { getIdByIndex, getIndexById, getVisibleDimensions, isElementVisible, isElementVisibleCarousel, shouldHaveLeftGradient, shouldHaveRightGradient };
@@ -0,0 +1,57 @@
1
+ import { useContext, useCallback } from 'react';
2
+ import { DSTabsContext, DSTabsCrossRefContext } from '../../DSTabsCTX.js';
3
+
4
+ /* eslint-disable max-statements */
5
+ const useKeyboardNavigation = () => {
6
+ const {
7
+ focusableTabsRef
8
+ } = useContext(DSTabsContext);
9
+ const {
10
+ firstSubtabInternalRef,
11
+ lastTabInternalRef
12
+ } = useContext(DSTabsCrossRefContext);
13
+ const handleOnKeyDown = useCallback(e => {
14
+ if (e.key === 'ArrowRight' && focusableTabsRef.current) {
15
+ var _focusableTabsRef$cur, _focusableTabsRef$cur2;
16
+
17
+ e.preventDefault();
18
+ e.stopPropagation();
19
+ const nextTab = focusableTabsRef.current[(focusableTabsRef === null || focusableTabsRef === void 0 ? void 0 : (_focusableTabsRef$cur = focusableTabsRef.current) === null || _focusableTabsRef$cur === void 0 ? void 0 : (_focusableTabsRef$cur2 = _focusableTabsRef$cur.indexOf) === null || _focusableTabsRef$cur2 === void 0 ? void 0 : _focusableTabsRef$cur2.call(_focusableTabsRef$cur, e.target)) + 1];
20
+ if (nextTab) nextTab.focus();
21
+ if (!nextTab) return;
22
+ }
23
+
24
+ if (e.key === 'ArrowLeft' && focusableTabsRef.current) {
25
+ var _focusableTabsRef$cur3, _focusableTabsRef$cur4;
26
+
27
+ e.preventDefault();
28
+ e.stopPropagation();
29
+ const prevTab = focusableTabsRef.current[(focusableTabsRef === null || focusableTabsRef === void 0 ? void 0 : (_focusableTabsRef$cur3 = focusableTabsRef.current) === null || _focusableTabsRef$cur3 === void 0 ? void 0 : (_focusableTabsRef$cur4 = _focusableTabsRef$cur3.indexOf) === null || _focusableTabsRef$cur4 === void 0 ? void 0 : _focusableTabsRef$cur4.call(_focusableTabsRef$cur3, e.target)) - 1];
30
+ if (prevTab) prevTab.focus();
31
+ if (!prevTab) return;
32
+ }
33
+
34
+ if (e.key === 'ArrowDown') {
35
+ var _firstSubtabInternalR;
36
+
37
+ e.preventDefault();
38
+ e.stopPropagation();
39
+ const canFocusCrossTabs = (firstSubtabInternalRef === null || firstSubtabInternalRef === void 0 ? void 0 : firstSubtabInternalRef.current) && (lastTabInternalRef === null || lastTabInternalRef === void 0 ? void 0 : lastTabInternalRef.current);
40
+ if (canFocusCrossTabs) (_firstSubtabInternalR = firstSubtabInternalRef.current) === null || _firstSubtabInternalR === void 0 ? void 0 : _firstSubtabInternalR.focus();
41
+ }
42
+
43
+ if (e.key === 'ArrowUp') {
44
+ var _lastTabInternalRef$c;
45
+
46
+ e.preventDefault();
47
+ e.stopPropagation();
48
+ const canFocusCrossTabs = (firstSubtabInternalRef === null || firstSubtabInternalRef === void 0 ? void 0 : firstSubtabInternalRef.current) && (lastTabInternalRef === null || lastTabInternalRef === void 0 ? void 0 : lastTabInternalRef.current);
49
+ if (canFocusCrossTabs) (_lastTabInternalRef$c = lastTabInternalRef.current) === null || _lastTabInternalRef$c === void 0 ? void 0 : _lastTabInternalRef$c.focus();
50
+ }
51
+ }, [firstSubtabInternalRef, lastTabInternalRef, focusableTabsRef]);
52
+ return {
53
+ handleOnKeyDown
54
+ };
55
+ };
56
+
57
+ export { useKeyboardNavigation };
@@ -1,9 +1,13 @@
1
- import { useLayoutEffect } from 'react';
1
+ import { useCallback, useLayoutEffect } from 'react';
2
2
  import { debounce } from 'lodash';
3
3
 
4
- function useResize(ref, callback) {
4
+ const useResize = _ref => {
5
5
  var _ref$current;
6
6
 
7
+ let {
8
+ ref,
9
+ callback
10
+ } = _ref;
7
11
  const {
8
12
  right,
9
13
  left
@@ -11,10 +15,10 @@ function useResize(ref, callback) {
11
15
  right: null,
12
16
  left: null
13
17
  };
14
- const handleResize = debounce(callback, 300);
18
+ const handleResize = useCallback(() => debounce(callback, 300), [callback]);
15
19
  useLayoutEffect(() => {
16
20
  handleResize();
17
- }, [right, left]);
18
- }
21
+ }, [right, left, handleResize]);
22
+ };
19
23
 
20
24
  export { useResize };
@@ -0,0 +1,55 @@
1
+ import { useContext, useCallback } from 'react';
2
+ import { DSTabsContext } from '../../DSTabsCTX.js';
3
+
4
+ const useTabsCallbacks = () => {
5
+ const {
6
+ setInternalActiveTab,
7
+ tabsListRef,
8
+ props: {
9
+ onTabChange,
10
+ isDSMobile,
11
+ allowTextSelection,
12
+ fixedTabsHeaders
13
+ }
14
+ } = useContext(DSTabsContext);
15
+ const centerTabWhenTabChangeMobile = useCallback(e => {
16
+ if (tabsListRef.current) {
17
+ const {
18
+ offsetLeft
19
+ } = e === null || e === void 0 ? void 0 : e.target;
20
+ const {
21
+ width: tabWidth
22
+ } = (e === null || e === void 0 ? void 0 : e.target).getBoundingClientRect();
23
+ const {
24
+ width: listWidth
25
+ } = tabsListRef.current.getBoundingClientRect();
26
+ const extraSideSpace = listWidth - tabWidth;
27
+ const leftOffsetToCenter = extraSideSpace > 0 ? extraSideSpace / 2 : 0;
28
+ const centeredLeftOffset = offsetLeft > leftOffsetToCenter ? offsetLeft - leftOffsetToCenter : 0;
29
+ tabsListRef.current.scrollTo({
30
+ top: 0,
31
+ left: centeredLeftOffset,
32
+ behavior: 'smooth'
33
+ });
34
+ e === null || e === void 0 ? void 0 : e.preventDefault();
35
+ }
36
+ }, [tabsListRef]);
37
+ const handleOnTabChange = useCallback((tabId, e) => {
38
+ setInternalActiveTab(tabId);
39
+ onTabChange(tabId, e);
40
+
41
+ if (isDSMobile && !fixedTabsHeaders && e) {
42
+ centerTabWhenTabChangeMobile(e);
43
+ }
44
+ }, [centerTabWhenTabChangeMobile, fixedTabsHeaders, isDSMobile, onTabChange, setInternalActiveTab]);
45
+ const handleOnMouseDown = useCallback(e => {
46
+ const role = e.target.getAttribute('role');
47
+ if (role !== 'tabpanel' && allowTextSelection) e.stopPropagation();
48
+ }, [allowTextSelection]);
49
+ return {
50
+ handleOnTabChange,
51
+ handleOnMouseDown
52
+ };
53
+ };
54
+
55
+ export { useTabsCallbacks };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elliemae/ds-tabs",
3
- "version": "2.0.0-rc.1",
3
+ "version": "2.0.0-rc.13",
4
4
  "license": "MIT",
5
5
  "description": "ICE MT - Dimsum - Tabs",
6
6
  "module": "./esm/index.js",
@@ -11,45 +11,105 @@
11
11
  "import": "./esm/index.js",
12
12
  "require": "./cjs/index.js"
13
13
  },
14
- "./useResize": {
15
- "import": "./esm/useResize.js",
16
- "require": "./cjs/useResize.js"
14
+ "./utils/hooks/useTabsCallbacks": {
15
+ "import": "./esm/utils/hooks/useTabsCallbacks.js",
16
+ "require": "./cjs/utils/hooks/useTabsCallbacks.js"
17
17
  },
18
- "./TabTypes": {
19
- "import": "./esm/TabTypes.js",
20
- "require": "./cjs/TabTypes.js"
18
+ "./utils/hooks/useResize": {
19
+ "import": "./esm/utils/hooks/useResize.js",
20
+ "require": "./cjs/utils/hooks/useResize.js"
21
21
  },
22
- "./TabsPanels": {
23
- "import": "./esm/TabsPanels.js",
24
- "require": "./cjs/TabsPanels.js"
22
+ "./utils/hooks/useKeyboardNavigation": {
23
+ "import": "./esm/utils/hooks/useKeyboardNavigation.js",
24
+ "require": "./cjs/utils/hooks/useKeyboardNavigation.js"
25
25
  },
26
- "./TabsImpl": {
27
- "import": "./esm/TabsImpl.js",
28
- "require": "./cjs/TabsImpl.js"
26
+ "./utils/helpers": {
27
+ "import": "./esm/utils/helpers.js",
28
+ "require": "./cjs/utils/helpers.js"
29
29
  },
30
- "./TabBar": {
31
- "import": "./esm/TabBar.js",
32
- "require": "./cjs/TabBar.js"
30
+ "./utils/constants": {
31
+ "import": "./esm/utils/constants.js",
32
+ "require": "./cjs/utils/constants.js"
33
33
  },
34
- "./SubTabBar": {
35
- "import": "./esm/SubTabBar.js",
36
- "require": "./cjs/SubTabBar.js"
34
+ "./propTypes": {
35
+ "import": "./esm/propTypes.js",
36
+ "require": "./cjs/propTypes.js"
37
37
  },
38
- "./PanelContainer": {
39
- "import": "./esm/PanelContainer.js",
40
- "require": "./cjs/PanelContainer.js"
38
+ "./parts/tabsPanel/TabsPanels": {
39
+ "import": "./esm/parts/tabsPanel/TabsPanels.js",
40
+ "require": "./cjs/parts/tabsPanel/TabsPanels.js"
41
+ },
42
+ "./parts/tabsPanel/styles": {
43
+ "import": "./esm/parts/tabsPanel/styles.js",
44
+ "require": "./cjs/parts/tabsPanel/styles.js"
45
+ },
46
+ "./parts/tabsContent/TabsContent": {
47
+ "import": "./esm/parts/tabsContent/TabsContent.js",
48
+ "require": "./cjs/parts/tabsContent/TabsContent.js"
49
+ },
50
+ "./parts/tabsContent/styles": {
51
+ "import": "./esm/parts/tabsContent/styles.js",
52
+ "require": "./cjs/parts/tabsContent/styles.js"
53
+ },
54
+ "./parts/tabBar/useTabBar": {
55
+ "import": "./esm/parts/tabBar/useTabBar.js",
56
+ "require": "./cjs/parts/tabBar/useTabBar.js"
57
+ },
58
+ "./parts/tabBar/TabBar": {
59
+ "import": "./esm/parts/tabBar/TabBar.js",
60
+ "require": "./cjs/parts/tabBar/TabBar.js"
61
+ },
62
+ "./parts/tabBar/styles": {
63
+ "import": "./esm/parts/tabBar/styles.js",
64
+ "require": "./cjs/parts/tabBar/styles.js"
65
+ },
66
+ "./parts/carousel/useCarouselCallbacks": {
67
+ "import": "./esm/parts/carousel/useCarouselCallbacks.js",
68
+ "require": "./cjs/parts/carousel/useCarouselCallbacks.js"
69
+ },
70
+ "./parts/carousel/useCarousel": {
71
+ "import": "./esm/parts/carousel/useCarousel.js",
72
+ "require": "./cjs/parts/carousel/useCarousel.js"
73
+ },
74
+ "./parts/carousel/styles": {
75
+ "import": "./esm/parts/carousel/styles.js",
76
+ "require": "./cjs/parts/carousel/styles.js"
77
+ },
78
+ "./parts/carousel/Carousel": {
79
+ "import": "./esm/parts/carousel/Carousel.js",
80
+ "require": "./cjs/parts/carousel/Carousel.js"
81
+ },
82
+ "./exportsRelated/DSTab": {
83
+ "import": "./esm/exportsRelated/DSTab.js",
84
+ "require": "./cjs/exportsRelated/DSTab.js"
85
+ },
86
+ "./DSTabsTypes": {
87
+ "import": "./esm/DSTabsTypes.js",
88
+ "require": "./cjs/DSTabsTypes.js"
89
+ },
90
+ "./DSTabsDatatestid": {
91
+ "import": "./esm/DSTabsDatatestid.js",
92
+ "require": "./cjs/DSTabsDatatestid.js"
93
+ },
94
+ "./DSTabsCTX": {
95
+ "import": "./esm/DSTabsCTX.js",
96
+ "require": "./cjs/DSTabsCTX.js"
41
97
  },
42
98
  "./DSTabs": {
43
99
  "import": "./esm/DSTabs.js",
44
100
  "require": "./cjs/DSTabs.js"
45
101
  },
46
- "./DSTab": {
47
- "import": "./esm/DSTab.js",
48
- "require": "./cjs/DSTab.js"
102
+ "./defaultProps": {
103
+ "import": "./esm/defaultProps.js",
104
+ "require": "./cjs/defaultProps.js"
49
105
  },
50
- "./Carousel": {
51
- "import": "./esm/Carousel.js",
52
- "require": "./cjs/Carousel.js"
106
+ "./config/useTabs": {
107
+ "import": "./esm/config/useTabs.js",
108
+ "require": "./cjs/config/useTabs.js"
109
+ },
110
+ "./config/useCrossRef": {
111
+ "import": "./esm/config/useCrossRef.js",
112
+ "require": "./cjs/config/useCrossRef.js"
53
113
  }
54
114
  },
55
115
  "sideEffects": [
@@ -72,20 +132,27 @@
72
132
  "build": "node ../../scripts/build/build.js"
73
133
  },
74
134
  "dependencies": {
75
- "@elliemae/ds-button": "2.0.0-rc.1",
76
- "@elliemae/ds-classnames": "2.0.0-rc.1",
77
- "@elliemae/ds-form": "2.0.0-rc.1",
78
- "@elliemae/ds-icons": "2.0.0-rc.1",
79
- "memoize-one": "~5.1.1",
135
+ "@elliemae/ds-button": "2.0.0-rc.13",
136
+ "@elliemae/ds-icon": "2.0.0-rc.13",
137
+ "@elliemae/ds-icons": "2.0.0-rc.13",
138
+ "@elliemae/ds-props-helpers": "2.0.0-rc.13",
139
+ "@elliemae/ds-system": "2.0.0-rc.13",
140
+ "@react-hook/resize-observer": "~1.2.5",
80
141
  "prop-types": "~15.7.2",
81
142
  "react-desc": "~4.1.3",
82
- "react-swipeable-views": "~0.13.9",
83
- "resize-observer": "~1.0.2"
143
+ "react-swipeable-views": "~0.13.9"
144
+ },
145
+ "devDependencies": {
146
+ "@testing-library/jest-dom": "~5.15.0",
147
+ "@testing-library/react": "~12.1.2",
148
+ "@testing-library/user-event": "~13.5.0",
149
+ "styled-components": "~5.3.3"
84
150
  },
85
151
  "peerDependencies": {
86
152
  "lodash": "^4.17.21",
87
153
  "react": "^17.0.2",
88
- "react-dom": "^17.0.2"
154
+ "react-dom": "^17.0.2",
155
+ "styled-components": "^5.3.3"
89
156
  },
90
157
  "publishConfig": {
91
158
  "access": "public",