@kaizen/components 1.68.11 → 1.68.12

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 (26) hide show
  1. package/dist/cjs/__future__/Tabs/constants.cjs +4 -0
  2. package/dist/cjs/__future__/Tabs/subcomponents/Tab/Tab.cjs +3 -1
  3. package/dist/cjs/__future__/Tabs/subcomponents/TabList/TabList.cjs +117 -3
  4. package/dist/cjs/__future__/Tabs/subcomponents/TabList/TabList.module.css.cjs +4 -1
  5. package/dist/cjs/__utilities__/isRTL/isRTL.cjs +11 -0
  6. package/dist/esm/__future__/Tabs/constants.mjs +2 -0
  7. package/dist/esm/__future__/Tabs/subcomponents/Tab/Tab.mjs +3 -1
  8. package/dist/esm/__future__/Tabs/subcomponents/TabList/TabList.mjs +121 -5
  9. package/dist/esm/__future__/Tabs/subcomponents/TabList/TabList.module.css.mjs +4 -1
  10. package/dist/esm/__utilities__/isRTL/isRTL.mjs +9 -0
  11. package/dist/styles.css +130 -78
  12. package/dist/types/__future__/Tabs/constants.d.ts +1 -0
  13. package/dist/types/__future__/Tabs/subcomponents/TabList/TabList.d.ts +1 -0
  14. package/dist/types/__utilities__/isRTL/index.d.ts +1 -0
  15. package/dist/types/__utilities__/isRTL/isRTL.d.ts +5 -0
  16. package/package.json +3 -3
  17. package/src/__future__/Tabs/_docs/Tabs.spec.stories.tsx +118 -0
  18. package/src/__future__/Tabs/_docs/Tabs.stickersheet.stories.tsx +84 -0
  19. package/src/__future__/Tabs/_docs/Tabs.stories.tsx +12 -1
  20. package/src/__future__/Tabs/constants.ts +1 -0
  21. package/src/__future__/Tabs/subcomponents/Tab/Tab.tsx +1 -1
  22. package/src/__future__/Tabs/subcomponents/TabList/TabList.module.css +53 -1
  23. package/src/__future__/Tabs/subcomponents/TabList/TabList.tsx +138 -10
  24. package/src/__utilities__/isRTL/index.ts +1 -0
  25. package/src/__utilities__/isRTL/isRTL.spec.tsx +38 -0
  26. package/src/__utilities__/isRTL/isRTL.ts +6 -0
@@ -0,0 +1,4 @@
1
+ 'use strict';
2
+
3
+ var SCROLL_AMOUNT = 120;
4
+ exports.SCROLL_AMOUNT = SCROLL_AMOUNT;
@@ -25,7 +25,9 @@ var Tab = function (props) {
25
25
  var tabProps = tslib.__assign({
26
26
  className: classnames__default.default(Tab_module.tab, className)
27
27
  }, restProps);
28
- return React__default.default.createElement(reactAriaComponents.Tab, tslib.__assign({}, tabProps), function (_a) {
28
+ return React__default.default.createElement(reactAriaComponents.Tab, tslib.__assign({
29
+ "data-kz-tab": true
30
+ }, tabProps), function (_a) {
29
31
  var isSelected = _a.isSelected,
30
32
  isFocusVisible = _a.isFocusVisible,
31
33
  isHovered = _a.isHovered;
@@ -4,6 +4,9 @@ var tslib = require('tslib');
4
4
  var React = require('react');
5
5
  var classnames = require('classnames');
6
6
  var reactAriaComponents = require('react-aria-components');
7
+ var Icon = require('../../../Icon/Icon.cjs');
8
+ var isRTL = require('../../../../__utilities__/isRTL/isRTL.cjs');
9
+ var constants = require('../../constants.cjs');
7
10
  var TabList_module = require('./TabList.module.css.cjs');
8
11
  function _interopDefault(e) {
9
12
  return e && e.__esModule ? e : {
@@ -22,10 +25,121 @@ var TabList = function (props) {
22
25
  noPadding = _a === void 0 ? false : _a,
23
26
  children = props.children,
24
27
  className = props.className,
25
- restProps = tslib.__rest(props, ['aria-label', "noPadding", "children", "className"]);
26
- return React__default.default.createElement(reactAriaComponents.TabList, tslib.__assign({
28
+ testId = props["data-testid"],
29
+ restProps = tslib.__rest(props, ['aria-label', "noPadding", "children", "className", 'data-testid']);
30
+ var _b = React.useState(false),
31
+ isDocumentReady = _b[0],
32
+ setIsDocumentReady = _b[1];
33
+ var _c = React.useState(false),
34
+ leftArrowEnabled = _c[0],
35
+ setLeftArrowEnabled = _c[1];
36
+ var _d = React.useState(false),
37
+ rightArrowEnabled = _d[0],
38
+ setRightArrowEnabled = _d[1];
39
+ var tabListRef = React.useRef(null);
40
+ var tabListId = React.useId();
41
+ var _e = React.useState(false),
42
+ isRTL$1 = _e[0],
43
+ setIsRTL = _e[1];
44
+ var _f = React.useState(),
45
+ containerElement = _f[0],
46
+ setContainerElement = _f[1];
47
+ var tabListContext = React.useContext(reactAriaComponents.TabListStateContext);
48
+ var selectedKey = tabListContext === null || tabListContext === void 0 ? void 0 : tabListContext.selectedKey;
49
+ React.useEffect(function () {
50
+ if (!isDocumentReady) {
51
+ setIsDocumentReady(true);
52
+ return;
53
+ }
54
+ var container = document.getElementById(tabListId);
55
+ setContainerElement(container);
56
+ setIsRTL(container ? isRTL.isRTL(container) : false);
57
+ }, [isDocumentReady, tabListId]);
58
+ React.useEffect(function () {
59
+ if (!isDocumentReady) {
60
+ return;
61
+ }
62
+ var tabs = containerElement === null || containerElement === void 0 ? void 0 : containerElement.querySelectorAll('[data-kz-tab]');
63
+ if (!tabs) {
64
+ return;
65
+ }
66
+ var firstTabObserver = new IntersectionObserver(function (entries) {
67
+ if (!entries[0].isIntersecting) {
68
+ setLeftArrowEnabled(true);
69
+ return;
70
+ }
71
+ setLeftArrowEnabled(false);
72
+ }, {
73
+ threshold: 0.75,
74
+ root: containerElement
75
+ });
76
+ firstTabObserver.observe(isRTL$1 ? tabs[tabs.length - 1] : tabs[0]);
77
+ var lastTabObserver = new IntersectionObserver(function (entries) {
78
+ if (!entries[0].isIntersecting) {
79
+ setRightArrowEnabled(true);
80
+ return;
81
+ }
82
+ setRightArrowEnabled(false);
83
+ }, {
84
+ threshold: 0.75,
85
+ root: containerElement
86
+ });
87
+ lastTabObserver.observe(isRTL$1 ? tabs[0] : tabs[tabs.length - 1]);
88
+ return function () {
89
+ firstTabObserver.disconnect();
90
+ lastTabObserver.disconnect();
91
+ };
92
+ }, [isDocumentReady, containerElement, isRTL$1]);
93
+ React.useEffect(function () {
94
+ var _a;
95
+ if (!isDocumentReady) {
96
+ return;
97
+ }
98
+ // Scroll selected tab into view
99
+ (_a = containerElement === null || containerElement === void 0 ? void 0 : containerElement.querySelector('[role="tab"][data-selected=true]')) === null || _a === void 0 ? void 0 : _a.scrollIntoView({
100
+ block: 'nearest',
101
+ inline: 'center'
102
+ });
103
+ }, [selectedKey, containerElement, isDocumentReady]);
104
+ var handleArrowPress = function (direction) {
105
+ if (tabListRef.current) {
106
+ var tabListScrollPos = tabListRef.current.scrollLeft;
107
+ var newSpot = direction === 'left' ? tabListScrollPos - constants.SCROLL_AMOUNT : tabListScrollPos + constants.SCROLL_AMOUNT;
108
+ tabListRef.current.scrollLeft = newSpot;
109
+ }
110
+ };
111
+ return React__default.default.createElement("div", {
112
+ className: TabList_module.container,
113
+ id: tabListId
114
+ }, leftArrowEnabled &&
115
+ // making a conscious decision to use <div onClick> over <button> here, because:
116
+ // - <button> would add pointless noise for a screen reader user
117
+ // - keyboard only user can toggle through tabs with left/right arrow keys already
118
+ // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
119
+ React__default.default.createElement("div", {
120
+ onClick: function () {
121
+ return handleArrowPress('left');
122
+ },
123
+ className: TabList_module.leftArrow,
124
+ "data-testid": testId ? "".concat(testId, "-kz-tablist-left-arrow") : undefined
125
+ }, React__default.default.createElement(Icon.Icon, {
126
+ name: "chevron_left",
127
+ isPresentational: true
128
+ })), React__default.default.createElement(reactAriaComponents.TabList, tslib.__assign({
27
129
  "aria-label": ariaLabel,
130
+ ref: tabListRef,
28
131
  className: classnames__default.default(TabList_module.tabList, className, noPadding && TabList_module.noPadding)
29
- }, restProps), children);
132
+ }, restProps), children), rightArrowEnabled &&
133
+ // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
134
+ React__default.default.createElement("div", {
135
+ onClick: function () {
136
+ return handleArrowPress('right');
137
+ },
138
+ className: TabList_module.rightArrow,
139
+ "data-testid": testId ? "".concat(testId, "-kz-tablist-right-arrow") : undefined
140
+ }, React__default.default.createElement(Icon.Icon, {
141
+ name: "chevron_right",
142
+ isPresentational: true
143
+ })));
30
144
  };
31
145
  exports.TabList = TabList;
@@ -1,7 +1,10 @@
1
1
  'use strict';
2
2
 
3
3
  var styles = {
4
+ "container": "TabList-module_container__Z8JCa",
4
5
  "tabList": "TabList-module_tabList__e1qAi",
5
- "noPadding": "TabList-module_noPadding__YM23K"
6
+ "noPadding": "TabList-module_noPadding__YM23K",
7
+ "leftArrow": "TabList-module_leftArrow__CPchY",
8
+ "rightArrow": "TabList-module_rightArrow__0xcW1"
6
9
  };
7
10
  module.exports = styles;
@@ -0,0 +1,11 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Finds the first ancestor with a `dir` property on it
5
+ * Returning true is that is `dir=rtl` and returning false in all other cases
6
+ */
7
+ var isRTL = function (element) {
8
+ var _a;
9
+ return !!((_a = element.closest('[dir]')) === null || _a === void 0 ? void 0 : _a.matches('[dir="rtl"]'));
10
+ };
11
+ exports.isRTL = isRTL;
@@ -0,0 +1,2 @@
1
+ var SCROLL_AMOUNT = 120;
2
+ export { SCROLL_AMOUNT };
@@ -16,7 +16,9 @@ var Tab = function (props) {
16
16
  var tabProps = __assign({
17
17
  className: classnames(styles.tab, className)
18
18
  }, restProps);
19
- return /*#__PURE__*/React.createElement(Tab$1, __assign({}, tabProps), function (_a) {
19
+ return /*#__PURE__*/React.createElement(Tab$1, __assign({
20
+ "data-kz-tab": true
21
+ }, tabProps), function (_a) {
20
22
  var isSelected = _a.isSelected,
21
23
  isFocusVisible = _a.isFocusVisible,
22
24
  isHovered = _a.isHovered;
@@ -1,7 +1,10 @@
1
1
  import { __rest, __assign } from 'tslib';
2
- import React from 'react';
2
+ import React, { useState, useRef, useId, useContext, useEffect } from 'react';
3
3
  import classnames from 'classnames';
4
- import { TabList as TabList$1 } from 'react-aria-components';
4
+ import { TabListStateContext, TabList as TabList$1 } from 'react-aria-components';
5
+ import { Icon } from '../../../Icon/Icon.mjs';
6
+ import { isRTL } from '../../../../__utilities__/isRTL/isRTL.mjs';
7
+ import { SCROLL_AMOUNT } from '../../constants.mjs';
5
8
  import styles from './TabList.module.css.mjs';
6
9
 
7
10
  /**
@@ -13,10 +16,123 @@ var TabList = function (props) {
13
16
  noPadding = _a === void 0 ? false : _a,
14
17
  children = props.children,
15
18
  className = props.className,
16
- restProps = __rest(props, ['aria-label', "noPadding", "children", "className"]);
17
- return /*#__PURE__*/React.createElement(TabList$1, __assign({
19
+ testId = props["data-testid"],
20
+ restProps = __rest(props, ['aria-label', "noPadding", "children", "className", 'data-testid']);
21
+ var _b = useState(false),
22
+ isDocumentReady = _b[0],
23
+ setIsDocumentReady = _b[1];
24
+ var _c = useState(false),
25
+ leftArrowEnabled = _c[0],
26
+ setLeftArrowEnabled = _c[1];
27
+ var _d = useState(false),
28
+ rightArrowEnabled = _d[0],
29
+ setRightArrowEnabled = _d[1];
30
+ var tabListRef = useRef(null);
31
+ var tabListId = useId();
32
+ var _e = useState(false),
33
+ isRTL$1 = _e[0],
34
+ setIsRTL = _e[1];
35
+ var _f = useState(),
36
+ containerElement = _f[0],
37
+ setContainerElement = _f[1];
38
+ var tabListContext = useContext(TabListStateContext);
39
+ var selectedKey = tabListContext === null || tabListContext === void 0 ? void 0 : tabListContext.selectedKey;
40
+ useEffect(function () {
41
+ if (!isDocumentReady) {
42
+ setIsDocumentReady(true);
43
+ return;
44
+ }
45
+ var container = document.getElementById(tabListId);
46
+ setContainerElement(container);
47
+ setIsRTL(container ? isRTL(container) : false);
48
+ }, [isDocumentReady, tabListId]);
49
+ useEffect(function () {
50
+ if (!isDocumentReady) {
51
+ return;
52
+ }
53
+ var tabs = containerElement === null || containerElement === void 0 ? void 0 : containerElement.querySelectorAll('[data-kz-tab]');
54
+ if (!tabs) {
55
+ return;
56
+ }
57
+ var firstTabObserver = new IntersectionObserver(function (entries) {
58
+ if (!entries[0].isIntersecting) {
59
+ setLeftArrowEnabled(true);
60
+ return;
61
+ }
62
+ setLeftArrowEnabled(false);
63
+ }, {
64
+ threshold: 0.75,
65
+ root: containerElement
66
+ });
67
+ firstTabObserver.observe(isRTL$1 ? tabs[tabs.length - 1] : tabs[0]);
68
+ var lastTabObserver = new IntersectionObserver(function (entries) {
69
+ if (!entries[0].isIntersecting) {
70
+ setRightArrowEnabled(true);
71
+ return;
72
+ }
73
+ setRightArrowEnabled(false);
74
+ }, {
75
+ threshold: 0.75,
76
+ root: containerElement
77
+ });
78
+ lastTabObserver.observe(isRTL$1 ? tabs[0] : tabs[tabs.length - 1]);
79
+ return function () {
80
+ firstTabObserver.disconnect();
81
+ lastTabObserver.disconnect();
82
+ };
83
+ }, [isDocumentReady, containerElement, isRTL$1]);
84
+ useEffect(function () {
85
+ var _a;
86
+ if (!isDocumentReady) {
87
+ return;
88
+ }
89
+ // Scroll selected tab into view
90
+ (_a = containerElement === null || containerElement === void 0 ? void 0 : containerElement.querySelector('[role="tab"][data-selected=true]')) === null || _a === void 0 ? void 0 : _a.scrollIntoView({
91
+ block: 'nearest',
92
+ inline: 'center'
93
+ });
94
+ }, [selectedKey, containerElement, isDocumentReady]);
95
+ var handleArrowPress = function (direction) {
96
+ if (tabListRef.current) {
97
+ var tabListScrollPos = tabListRef.current.scrollLeft;
98
+ var newSpot = direction === 'left' ? tabListScrollPos - SCROLL_AMOUNT : tabListScrollPos + SCROLL_AMOUNT;
99
+ tabListRef.current.scrollLeft = newSpot;
100
+ }
101
+ };
102
+ return /*#__PURE__*/React.createElement("div", {
103
+ className: styles.container,
104
+ id: tabListId
105
+ }, leftArrowEnabled && (
106
+ /*#__PURE__*/
107
+ // making a conscious decision to use <div onClick> over <button> here, because:
108
+ // - <button> would add pointless noise for a screen reader user
109
+ // - keyboard only user can toggle through tabs with left/right arrow keys already
110
+ // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
111
+ React.createElement("div", {
112
+ onClick: function () {
113
+ return handleArrowPress('left');
114
+ },
115
+ className: styles.leftArrow,
116
+ "data-testid": testId ? "".concat(testId, "-kz-tablist-left-arrow") : undefined
117
+ }, /*#__PURE__*/React.createElement(Icon, {
118
+ name: "chevron_left",
119
+ isPresentational: true
120
+ }))), /*#__PURE__*/React.createElement(TabList$1, __assign({
18
121
  "aria-label": ariaLabel,
122
+ ref: tabListRef,
19
123
  className: classnames(styles.tabList, className, noPadding && styles.noPadding)
20
- }, restProps), children);
124
+ }, restProps), children), rightArrowEnabled && (
125
+ /*#__PURE__*/
126
+ // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
127
+ React.createElement("div", {
128
+ onClick: function () {
129
+ return handleArrowPress('right');
130
+ },
131
+ className: styles.rightArrow,
132
+ "data-testid": testId ? "".concat(testId, "-kz-tablist-right-arrow") : undefined
133
+ }, /*#__PURE__*/React.createElement(Icon, {
134
+ name: "chevron_right",
135
+ isPresentational: true
136
+ }))));
21
137
  };
22
138
  export { TabList };
@@ -1,5 +1,8 @@
1
1
  var styles = {
2
+ "container": "TabList-module_container__Z8JCa",
2
3
  "tabList": "TabList-module_tabList__e1qAi",
3
- "noPadding": "TabList-module_noPadding__YM23K"
4
+ "noPadding": "TabList-module_noPadding__YM23K",
5
+ "leftArrow": "TabList-module_leftArrow__CPchY",
6
+ "rightArrow": "TabList-module_rightArrow__0xcW1"
4
7
  };
5
8
  export { styles as default };
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Finds the first ancestor with a `dir` property on it
3
+ * Returning true is that is `dir=rtl` and returning false in all other cases
4
+ */
5
+ var isRTL = function (element) {
6
+ var _a;
7
+ return !!((_a = element.closest('[dir]')) === null || _a === void 0 ? void 0 : _a.matches('[dir="rtl"]'));
8
+ };
9
+ export { isRTL };
package/dist/styles.css CHANGED
@@ -1,4 +1,37 @@
1
1
  @layer tokens, normalize, reset;@layer tokens{:root{--theme-key:heart;--animation-easing-function-ease-in-out:cubic-bezier(0.455,0.03,0.515,0.955);--animation-easing-function-ease-in:cubic-bezier(0.55,0.085,0.68,0.53);--animation-easing-function-ease-out:cubic-bezier(0.25,0.46,0.45,0.94);--animation-easing-function-linear:linear;--animation-easing-function-bounce-in:cubic-bezier(0.485,0.155,0.24,1.245);--animation-easing-function-bounce-out:cubic-bezier(0.485,0.155,0.515,0.845);--animation-easing-function-bounce-in-out:cubic-bezier(0.76,-0.245,0.24,1.245);--animation-duration-instant:0ms;--animation-duration-immediate:100ms;--animation-duration-rapid:200ms;--animation-duration-fast:300ms;--animation-duration-slow:400ms;--animation-duration-deliberate:700ms;--border-solid-border-width:2px;--border-solid-border-radius:7px;--border-solid-border-style:solid;--border-solid-border-color:#e1e2ea;--border-solid-border-color-rgb:225,226,234;--border-dashed-border-width:2px;--border-dashed-border-radius:7px;--border-dashed-border-style:dashed;--border-borderless-border-width:2px;--border-borderless-border-radius:7px;--border-borderless-border-style:solid;--border-borderless-border-color:transparent;--border-borderless-border-color-rgb:0,0,0;--border-focus-ring-border-width:2px;--border-focus-ring-border-radius:10px;--border-focus-ring-border-style:solid;--border-width-1:1px;--color-purple-100:#f4edf8;--color-purple-100-rgb:244,237,248;--color-purple-200:#dfc9ea;--color-purple-200-rgb:223,201,234;--color-purple-300:#c9a5dd;--color-purple-300-rgb:201,165,221;--color-purple-400:#ae67b1;--color-purple-400-rgb:174,103,177;--color-purple-500:#844587;--color-purple-500-rgb:132,69,135;--color-purple-600:#5f3361;--color-purple-600-rgb:95,51,97;--color-purple-700:#4a234d;--color-purple-700-rgb:74,35,77;--color-purple-800:#2f2438;--color-purple-800-rgb:47,36,56;--color-blue-100:#e6f6ff;--color-blue-100-rgb:230,246,255;--color-blue-200:#bde2f5;--color-blue-200-rgb:189,226,245;--color-blue-300:#73c0e8;--color-blue-300-rgb:115,192,232;--color-blue-400:#008bd6;--color-blue-400-rgb:0,139,214;--color-blue-500:#0168b3;--color-blue-500-rgb:1,104,179;--color-blue-600:#004970;--color-blue-600-rgb:0,73,112;--color-blue-700:#003157;--color-blue-700-rgb:0,49,87;--color-green-100:#e8f8f4;--color-green-100-rgb:232,248,244;--color-green-200:#c4ede2;--color-green-200-rgb:196,237,226;--color-green-300:#8fdbc7;--color-green-300-rgb:143,219,199;--color-green-400:#5dcaad;--color-green-400-rgb:93,202,173;--color-green-500:#3f9a86;--color-green-500-rgb:63,154,134;--color-green-600:#2c7d67;--color-green-600-rgb:44,125,103;--color-green-700:#22594a;--color-green-700-rgb:34,89,74;--color-yellow-100:#fff9e4;--color-yellow-100-rgb:255,249,228;--color-yellow-200:#ffeeb3;--color-yellow-200-rgb:255,238,179;--color-yellow-300:#ffe36e;--color-yellow-300-rgb:255,227,110;--color-yellow-400:#ffca4d;--color-yellow-400-rgb:255,202,77;--color-yellow-500:#ffb600;--color-yellow-500-rgb:255,182,0;--color-yellow-600:#c68600;--color-yellow-600-rgb:198,134,0;--color-yellow-700:#876400;--color-yellow-700-rgb:135,100,0;--color-red-100:#fdeaee;--color-red-100-rgb:253,234,238;--color-red-200:#f9c2cb;--color-red-200-rgb:249,194,203;--color-red-300:#f597a8;--color-red-300-rgb:245,151,168;--color-red-400:#e0707d;--color-red-400-rgb:224,112,125;--color-red-500:#c93b55;--color-red-500-rgb:201,59,85;--color-red-600:#a82433;--color-red-600-rgb:168,36,51;--color-red-700:#6c1e20;--color-red-700-rgb:108,30,32;--color-orange-100:#fff0e8;--color-orange-100-rgb:255,240,232;--color-orange-200:#ffd1b9;--color-orange-200-rgb:255,209,185;--color-orange-300:#ffb08a;--color-orange-300-rgb:255,176,138;--color-orange-400:#ff9461;--color-orange-400-rgb:255,148,97;--color-orange-500:#e96c2f;--color-orange-500-rgb:233,108,47;--color-orange-600:#b74302;--color-orange-600-rgb:183,67,2;--color-orange-700:#903c00;--color-orange-700-rgb:144,60,0;--color-gray-100:#f9f9f9;--color-gray-100-rgb:249,249,249;--color-gray-200:#f4f4f5;--color-gray-200-rgb:244,244,245;--color-gray-300:#eaeaec;--color-gray-300-rgb:234,234,236;--color-gray-400:#cdcdd0;--color-gray-400-rgb:205,205,208;--color-gray-500:#878792;--color-gray-500-rgb:135,135,146;--color-gray-600:#524e56;--color-gray-600-rgb:82,78,86;--color-white:#fff;--color-white-rgb:255,255,255;--color-black:#000;--color-black-rgb:0,0,0;--data-viz-favorable:#7dd5bd;--data-viz-favorable-rgb:125,213,189;--data-viz-unfavorable:#e68d97;--data-viz-unfavorable-rgb:230,141,151;--layout-content-max-width:1392px;--layout-content-max-width-with-sidebar:1080px;--layout-content-side-margin:72px;--layout-mobile-actions-drawer-height:60px;--layout-navigation-bar-height:72px;--layout-breakpoints-medium:768px;--layout-breakpoints-large:1080px;--shadow-small-box-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 3px 16px 0 rgba(0,0,0,.06);--shadow-large-box-shadow:0 3px 9px 0 rgba(0,0,0,.1),0 8px 40px 0 rgba(0,0,0,.08);--spacing-0:0;--spacing-1:.0625rem;--spacing-2:.125rem;--spacing-4:.25rem;--spacing-6:.375rem;--spacing-8:.5rem;--spacing-12:.75rem;--spacing-16:1rem;--spacing-20:1.25rem;--spacing-24:1.5rem;--spacing-32:2rem;--spacing-40:2.5rem;--spacing-48:3rem;--spacing-56:3.5rem;--spacing-64:4rem;--spacing-72:4.5rem;--spacing-80:5rem;--spacing-96:6rem;--spacing-112:7rem;--spacing-128:8rem;--spacing-160:10rem;--spacing-200:12.5rem;--spacing-240:15rem;--spacing-280:17.5rem;--spacing-320:20rem;--spacing-xs:0.375rem;--spacing-sm:0.75rem;--spacing-md:1.5rem;--spacing-lg:2.25rem;--spacing-xl:3rem;--spacing-xxl:3.75rem;--spacing-xxxl:4.5rem;--spacing-xxxxl:5.25rem;--spacing-xxxxxl:6rem;--typography-data-large-font-family:"Inter","Noto Sans",Helvetica,Arial,sans-serif;--typography-data-large-font-weight:700;--typography-data-large-font-size:5.25rem;--typography-data-large-line-height:5.25rem;--typography-data-large-letter-spacing:normal;--typography-data-large-units-font-family:"Inter","Noto Sans",Helvetica,Arial,sans-serif;--typography-data-large-units-font-weight:700;--typography-data-large-units-font-size:2.625rem;--typography-data-large-units-line-height:5.25rem;--typography-data-large-units-letter-spacing:normal;--typography-data-medium-font-family:"Inter","Noto Sans",Helvetica,Arial,sans-serif;--typography-data-medium-font-weight:700;--typography-data-medium-font-size:3rem;--typography-data-medium-line-height:5rem;--typography-data-medium-letter-spacing:normal;--typography-data-medium-units-font-family:"Inter","Noto Sans",Helvetica,Arial,sans-serif;--typography-data-medium-units-font-weight:700;--typography-data-medium-units-font-size:1.5rem;--typography-data-medium-units-line-height:5rem;--typography-data-medium-units-letter-spacing:normal;--typography-data-small-font-family:"Inter","Noto Sans",Helvetica,Arial,sans-serif;--typography-data-small-font-weight:700;--typography-data-small-font-size:1.5rem;--typography-data-small-line-height:1.5rem;--typography-data-small-letter-spacing:normal;--typography-data-small-units-font-family:"Inter","Noto Sans",Helvetica,Arial,sans-serif;--typography-data-small-units-font-weight:700;--typography-data-small-units-font-size:1.125rem;--typography-data-small-units-line-height:1.5rem;--typography-data-small-units-letter-spacing:normal;--typography-display-0-font-family:"Tiempos Headline",Georgia,serif;--typography-display-0-font-weight:800;--typography-display-0-font-size:4.5rem;--typography-display-0-line-height:5.25rem;--typography-display-0-letter-spacing:0em;--typography-heading-1-font-family:"Inter","Noto Sans",Helvetica,Arial,sans-serif;--typography-heading-1-font-weight:500;--typography-heading-1-font-size:2.125rem;--typography-heading-1-line-height:2.625rem;--typography-heading-1-letter-spacing:normal;--typography-heading-2-font-family:"Inter","Noto Sans",Helvetica,Arial,sans-serif;--typography-heading-2-font-weight:600;--typography-heading-2-font-size:1.75rem;--typography-heading-2-line-height:2.25rem;--typography-heading-2-letter-spacing:normal;--typography-heading-3-font-family:"Inter","Noto Sans",Helvetica,Arial,sans-serif;--typography-heading-3-font-weight:600;--typography-heading-3-font-size:1.375rem;--typography-heading-3-line-height:1.875rem;--typography-heading-3-letter-spacing:normal;--typography-heading-4-font-family:"Inter","Noto Sans",Helvetica,Arial,sans-serif;--typography-heading-4-font-weight:600;--typography-heading-4-font-size:1.125rem;--typography-heading-4-line-height:1.5rem;--typography-heading-4-letter-spacing:normal;--typography-heading-5-font-family:"Inter","Noto Sans",Helvetica,Arial,sans-serif;--typography-heading-5-font-weight:600;--typography-heading-5-font-size:1rem;--typography-heading-5-line-height:1.5rem;--typography-heading-5-letter-spacing:normal;--typography-heading-6-font-family:"Inter","Noto Sans",Helvetica,Arial,sans-serif;--typography-heading-6-font-weight:600;--typography-heading-6-font-size:0.875rem;--typography-heading-6-line-height:1.5rem;--typography-heading-6-letter-spacing:normal;--typography-paragraph-intro-lede-font-family:"Inter","Noto Sans",Helvetica,Arial,sans-serif;--typography-paragraph-intro-lede-font-weight:400;--typography-paragraph-intro-lede-font-size:1.25rem;--typography-paragraph-intro-lede-line-height:1.875rem;--typography-paragraph-intro-lede-letter-spacing:0;--typography-paragraph-intro-lede-max-width:975px;--typography-paragraph-body-font-family:"Inter","Noto Sans",Helvetica,Arial,sans-serif;--typography-paragraph-body-font-weight:400;--typography-paragraph-body-font-size:1rem;--typography-paragraph-body-line-height:1.5rem;--typography-paragraph-body-letter-spacing:normal;--typography-paragraph-body-max-width:780px;--typography-paragraph-small-font-family:"Inter","Noto Sans",Helvetica,Arial,sans-serif;--typography-paragraph-small-font-weight:400;--typography-paragraph-small-font-size:0.875rem;--typography-paragraph-small-line-height:1.125rem;--typography-paragraph-small-letter-spacing:normal;--typography-paragraph-small-max-width:680px;--typography-paragraph-extra-small-font-family:"Inter","Noto Sans",Helvetica,Arial,sans-serif;--typography-paragraph-extra-small-font-weight:400;--typography-paragraph-extra-small-font-size:0.75rem;--typography-paragraph-extra-small-line-height:1.125rem;--typography-paragraph-extra-small-letter-spacing:normal;--typography-paragraph-extra-small-max-width:600px;--typography-paragraph-bold-font-weight:600;--typography-button-primary-font-family:"Inter","Noto Sans",Helvetica,Arial,sans-serif;--typography-button-primary-font-weight:500;--typography-button-primary-font-size:1.125rem;--typography-button-primary-line-height:1.5rem;--typography-button-primary-letter-spacing:normal;--typography-button-secondary-font-family:"Inter","Noto Sans",Helvetica,Arial,sans-serif;--typography-button-secondary-font-weight:500;--typography-button-secondary-font-size:1rem;--typography-button-secondary-line-height:1.5rem;--typography-button-secondary-letter-spacing:normal}}@layer normalize{html{text-size-adjust:100%;line-height:1.15}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{appearance:none}::-webkit-file-upload-button{appearance:button;font:inherit}details{display:block}summary{display:list-item}[hidden],template{display:none}}@layer reset{@font-face{font-family:Tiempos Headline;font-weight:800;src:url(https://d1e7r7b0lb8p4d.cloudfront.net/fonts/tiempos/tiempos-headline-bold.woff2),url(https://d1e7r7b0lb8p4d.cloudfront.net/fonts/tiempos/tiempos-headline-bold.woff)}@font-face{font-family:Tiempos Headline;font-weight:500;src:url(https://d1e7r7b0lb8p4d.cloudfront.net/fonts/tiempos/tiempos-headline-medium.woff2),url(https://d1e7r7b0lb8p4d.cloudfront.net/fonts/tiempos/tiempos-headline-medium.woff)}@font-face{font-family:Greycliff CF;font-weight:300;src:url(https://d1e7r7b0lb8p4d.cloudfront.net/fonts/greycliff/greycliff-cf-light.woff) format("woff")}@font-face{font-family:Greycliff CF;font-weight:400;src:url(https://d1e7r7b0lb8p4d.cloudfront.net/fonts/greycliff/greycliff-cf-regular.woff) format("woff")}@font-face{font-family:Greycliff CF;font-weight:500;src:url(https://d1e7r7b0lb8p4d.cloudfront.net/fonts/greycliff/greycliff-cf-medium.woff) format("woff")}@font-face{font-family:Greycliff CF;font-weight:600;src:url(https://d1e7r7b0lb8p4d.cloudfront.net/fonts/greycliff/greycliff-cf-demi-bold.woff) format("woff")}@font-face{font-family:Greycliff CF;font-weight:700;src:url(https://d1e7r7b0lb8p4d.cloudfront.net/fonts/greycliff/greycliff-cf-bold.woff) format("woff")}@font-face{font-family:Greycliff CF;font-weight:800;src:url(https://d1e7r7b0lb8p4d.cloudfront.net/fonts/greycliff/greycliff-cf-extra-bold.woff) format("woff")}@font-face{font-family:Inter;font-weight:300;src:url(https://d1e7r7b0lb8p4d.cloudfront.net/fonts/inter/inter-light.woff2),url(https://d1e7r7b0lb8p4d.cloudfront.net/fonts/inter/inter-light.woff)}@font-face{font-family:Inter;font-weight:400;src:url(https://d1e7r7b0lb8p4d.cloudfront.net/fonts/inter/inter-regular.woff2),url(https://d1e7r7b0lb8p4d.cloudfront.net/fonts/inter/inter-regular.woff)}@font-face{font-family:Inter;font-weight:500;src:url(https://d1e7r7b0lb8p4d.cloudfront.net/fonts/inter/inter-medium.woff2),url(https://d1e7r7b0lb8p4d.cloudfront.net/fonts/inter/inter-medium.woff)}@font-face{font-family:Inter;font-weight:600;src:url(https://d1e7r7b0lb8p4d.cloudfront.net/fonts/inter/inter-demi-bold.woff2),url(https://d1e7r7b0lb8p4d.cloudfront.net/fonts/inter/inter-demi-bold.woff)}@font-face{font-family:Inter;font-weight:700;src:url(https://d1e7r7b0lb8p4d.cloudfront.net/fonts/inter/inter-bold.woff2),url(https://d1e7r7b0lb8p4d.cloudfront.net/fonts/inter/inter-bold.woff)}@font-face{font-family:Inter;font-weight:800;src:url(https://d1e7r7b0lb8p4d.cloudfront.net/fonts/inter/inter-extra-bold.woff2),url(https://d1e7r7b0lb8p4d.cloudfront.net/fonts/inter/inter-extra-bold.woff)}@font-face{font-family:IBM Plex Mono;src:url(https://d1e7r7b0lb8p4d.cloudfront.net/fonts/ibm-plex-mono/ibm-plex-mono-regular.woff2),url(https://d1e7r7b0lb8p4d.cloudfront.net/fonts/ibm-plex-mono/ibm-plex-mono-regular.woff)}}@layer reset{*,:after,:before{border-color:var(--border-solid-border-color,"currentColor");border-style:solid;border-width:0}}
2
+ .Menu-module_menu__AowD8 {
3
+ background-color: var(--color-white);
4
+ color: var(--color-purple-800);
5
+ width: 248px;
6
+ max-height: 22rem;
7
+ overflow: auto;
8
+ padding-block: var(--spacing-6);
9
+ outline: none;
10
+ border-radius: var(--border-solid-border-radius);
11
+ box-shadow: var(--shadow-large-box-shadow);
12
+ }
13
+
14
+ .Menu-module_menu__AowD8 .react-aria-Header {
15
+ font-family: var(--typography-heading-6-font-family);
16
+ font-size: var(--typography-heading-6-font-size);
17
+ letter-spacing: var(--typography-heading-6-letter-spacing);
18
+ font-weight: var(--typography-heading-6-font-weight);
19
+ line-height: var(--typography-heading-6-line-height);
20
+ padding: var(--spacing-6) 10px;
21
+ margin-inline: var(--spacing-6);
22
+ }
23
+
24
+ .Menu-module_menu__AowD8 section:not(:last-of-type) {
25
+ &::after {
26
+ width: 100%;
27
+ height: 1px;
28
+ background-color: var(--border-solid-border-color);
29
+ content: '';
30
+ display: block;
31
+ margin-block: var(--spacing-6);
32
+ }
33
+ }
34
+
2
35
  .MenuItem-module_item__uImZI {
3
36
  display: block;
4
37
  font-family: var(--typography-paragraph-body-font-family);
@@ -46,39 +79,6 @@
46
79
  opacity: 0.3;
47
80
  }
48
81
 
49
- .Menu-module_menu__AowD8 {
50
- background-color: var(--color-white);
51
- color: var(--color-purple-800);
52
- width: 248px;
53
- max-height: 22rem;
54
- overflow: auto;
55
- padding-block: var(--spacing-6);
56
- outline: none;
57
- border-radius: var(--border-solid-border-radius);
58
- box-shadow: var(--shadow-large-box-shadow);
59
- }
60
-
61
- .Menu-module_menu__AowD8 .react-aria-Header {
62
- font-family: var(--typography-heading-6-font-family);
63
- font-size: var(--typography-heading-6-font-size);
64
- letter-spacing: var(--typography-heading-6-letter-spacing);
65
- font-weight: var(--typography-heading-6-font-weight);
66
- line-height: var(--typography-heading-6-line-height);
67
- padding: var(--spacing-6) 10px;
68
- margin-inline: var(--spacing-6);
69
- }
70
-
71
- .Menu-module_menu__AowD8 section:not(:last-of-type) {
72
- &::after {
73
- width: 100%;
74
- height: 1px;
75
- background-color: var(--border-solid-border-color);
76
- content: '';
77
- display: block;
78
- margin-block: var(--spacing-6);
79
- }
80
- }
81
-
82
82
  .Button-module_button__vlUCI {
83
83
  /* RESET */
84
84
  appearance: none;
@@ -312,26 +312,6 @@
312
312
  visibility: hidden;
313
313
  }
314
314
 
315
- .ButtonContent-module_buttonContent__v5mHZ {
316
- display: inline-flex;
317
- align-items: center;
318
- gap: var(--button-icon-gap, var(--spacing-6));
319
- }
320
-
321
- .ButtonContent-module_large__mLOdb {
322
- --button-icon-gap: var(--spacing-8);
323
- }
324
-
325
- .ButtonContent-module_buttonLabel__T5XAq {
326
- display: inline-flex;
327
- align-items: center;
328
- }
329
-
330
- .ButtonContent-module_buttonIcon__qkAX- {
331
- display: inline-flex;
332
- align-items: center;
333
- }
334
-
335
315
  .PendingContent-module_pendingContent__c4IFS {
336
316
  display: inline-flex;
337
317
  align-items: center;
@@ -349,34 +329,26 @@
349
329
  transform: translate(-50%, -50%);
350
330
  }
351
331
 
352
- /** THIS IS AN AUTOGENERATED FILE **/
353
- /** THIS IS AN AUTOGENERATED FILE **/
354
- .OverlayArrow-module_overlayArrow__hoDyK {
355
- display: flex;
356
- padding: 8px;
357
- }
358
- .OverlayArrow-module_overlayArrow__hoDyK[data-placement=top], .OverlayArrow-module_overlayArrow__hoDyK[data-placement=bottom] {
359
- padding: 0 8px;
360
- }
361
- .OverlayArrow-module_overlayArrow__hoDyK[data-placement=left], .OverlayArrow-module_overlayArrow__hoDyK[data-placement=right] {
362
- padding: 8px 0;
363
- }
364
- .OverlayArrow-module_overlayArrow__hoDyK path {
365
- fill: var(--color-purple-800, #2f2438);
366
- box-shadow: var(--shadow-small-box-shadow, 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 3px 16px 0 rgba(0, 0, 0, 0.06));
367
- }
368
- .OverlayArrow-module_overlayArrow__hoDyK[data-placement=right] svg {
369
- transform: rotate(90deg);
332
+ .ButtonContent-module_buttonContent__v5mHZ {
333
+ display: inline-flex;
334
+ align-items: center;
335
+ gap: var(--button-icon-gap, var(--spacing-6));
370
336
  }
371
- .OverlayArrow-module_overlayArrow__hoDyK[data-placement=bottom] svg {
372
- transform: rotate(180deg);
337
+
338
+ .ButtonContent-module_large__mLOdb {
339
+ --button-icon-gap: var(--spacing-8);
373
340
  }
374
- .OverlayArrow-module_overlayArrow__hoDyK[data-placement=left] svg {
375
- transform: rotate(270deg);
341
+
342
+ .ButtonContent-module_buttonLabel__T5XAq {
343
+ display: inline-flex;
344
+ align-items: center;
376
345
  }
377
- .OverlayArrow-module_overlayArrow__hoDyK.OverlayArrow-module_reversed__-WGcR path {
378
- fill: var(--color-white, #ffffff);
346
+
347
+ .ButtonContent-module_buttonIcon__qkAX- {
348
+ display: inline-flex;
349
+ align-items: center;
379
350
  }
351
+
380
352
  .Focusable-module_focusableWrapper__NfuIi {
381
353
  display: inline-flex;
382
354
  }
@@ -436,6 +408,34 @@
436
408
  opacity: 1;
437
409
  }
438
410
  }
411
+ /** THIS IS AN AUTOGENERATED FILE **/
412
+ /** THIS IS AN AUTOGENERATED FILE **/
413
+ .OverlayArrow-module_overlayArrow__hoDyK {
414
+ display: flex;
415
+ padding: 8px;
416
+ }
417
+ .OverlayArrow-module_overlayArrow__hoDyK[data-placement=top], .OverlayArrow-module_overlayArrow__hoDyK[data-placement=bottom] {
418
+ padding: 0 8px;
419
+ }
420
+ .OverlayArrow-module_overlayArrow__hoDyK[data-placement=left], .OverlayArrow-module_overlayArrow__hoDyK[data-placement=right] {
421
+ padding: 8px 0;
422
+ }
423
+ .OverlayArrow-module_overlayArrow__hoDyK path {
424
+ fill: var(--color-purple-800, #2f2438);
425
+ box-shadow: var(--shadow-small-box-shadow, 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 3px 16px 0 rgba(0, 0, 0, 0.06));
426
+ }
427
+ .OverlayArrow-module_overlayArrow__hoDyK[data-placement=right] svg {
428
+ transform: rotate(90deg);
429
+ }
430
+ .OverlayArrow-module_overlayArrow__hoDyK[data-placement=bottom] svg {
431
+ transform: rotate(180deg);
432
+ }
433
+ .OverlayArrow-module_overlayArrow__hoDyK[data-placement=left] svg {
434
+ transform: rotate(270deg);
435
+ }
436
+ .OverlayArrow-module_overlayArrow__hoDyK.OverlayArrow-module_reversed__-WGcR path {
437
+ fill: var(--color-white, #ffffff);
438
+ }
439
439
  /*
440
440
  * This is taken from the Material Symbols CDN
441
441
  * font-weight & font-size removed as overridden in .icon
@@ -5657,15 +5657,67 @@ input[type=range].InputRange-module_ratingScaleRange__gI-rs::-ms-thumb:not(:disa
5657
5657
  }
5658
5658
  }
5659
5659
 
5660
+ .TabList-module_container__Z8JCa {
5661
+ position: relative;
5662
+ }
5663
+
5660
5664
  .TabList-module_tabList__e1qAi {
5661
5665
  border-bottom: 1px solid rgba(var(--color-gray-600-rgb), 0.1);
5662
- padding: var(--spacing-xs) var(--spacing-md) 0;
5666
+ padding: var(--spacing-6) 0 0;
5667
+ width: 100%;
5668
+ height: 100%;
5669
+ overflow-x: scroll;
5670
+ white-space: nowrap;
5671
+ scrollbar-width: none;
5672
+ scroll-behavior: smooth;
5663
5673
  }
5664
5674
 
5665
5675
  .TabList-module_noPadding__YM23K {
5666
5676
  padding: 0;
5667
5677
  }
5668
5678
 
5679
+ .TabList-module_leftArrow__CPchY,
5680
+ .TabList-module_rightArrow__0xcW1 {
5681
+ --icon-size: 24;
5682
+
5683
+ display: flex;
5684
+ align-items: center;
5685
+ justify-content: center;
5686
+ position: absolute;
5687
+ z-index: 10000;
5688
+ background: var(--color-white);
5689
+ inset-block: 0 1px;
5690
+ width: 48px;
5691
+ cursor: default;
5692
+ user-select: none;
5693
+ }
5694
+
5695
+ /*
5696
+ * Note: we're purposefully using directional properties instead of start/end for positioning and styling related to the carousel arrows
5697
+ */
5698
+ .TabList-module_leftArrow__CPchY {
5699
+ left: 0;
5700
+ }
5701
+
5702
+ .TabList-module_leftArrow__CPchY,
5703
+ .TabList-module_leftArrow__CPchY:hover {
5704
+ border-right: 1px solid rgba(var(--color-gray-600-rgb), 0.1);
5705
+ }
5706
+
5707
+ .TabList-module_rightArrow__0xcW1 {
5708
+ right: 0;
5709
+ }
5710
+
5711
+ .TabList-module_rightArrow__0xcW1,
5712
+ .TabList-module_rightArrow__0xcW1:hover {
5713
+ border-left: 1px solid rgba(var(--color-gray-600-rgb), 0.1);
5714
+ }
5715
+
5716
+ .TabList-module_leftArrow__CPchY:hover,
5717
+ .TabList-module_rightArrow__0xcW1:hover {
5718
+ background: var(--color-gray-200);
5719
+ }
5720
+
5669
5721
  .TabPanel-module_tabPanel__ae5Rx {
5670
5722
  border: 2px solid transparent;
5671
5723
 
@@ -0,0 +1 @@
1
+ export declare const SCROLL_AMOUNT = 120;
@@ -10,6 +10,7 @@ export type TabListProps = {
10
10
  */
11
11
  'noPadding'?: boolean;
12
12
  'children': ReactNode;
13
+ 'data-testid'?: string;
13
14
  } & RACTabListProps<HTMLElement>;
14
15
  /**
15
16
  * Wrapper for the tabs themselves
@@ -0,0 +1 @@
1
+ export * from './isRTL';
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Finds the first ancestor with a `dir` property on it
3
+ * Returning true is that is `dir=rtl` and returning false in all other cases
4
+ */
5
+ export declare const isRTL: (element: Element) => boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaizen/components",
3
- "version": "1.68.11",
3
+ "version": "1.68.12",
4
4
  "description": "Kaizen component library",
5
5
  "author": "Geoffrey Chong <geoff.chong@cultureamp.com>",
6
6
  "homepage": "https://cultureamp.design",
@@ -117,8 +117,8 @@
117
117
  "svgo": "^3.3.2",
118
118
  "tslib": "^2.8.1",
119
119
  "tsx": "^4.19.2",
120
- "@kaizen/design-tokens": "10.8.6",
121
- "@kaizen/package-bundler": "2.0.3"
120
+ "@kaizen/package-bundler": "2.0.3",
121
+ "@kaizen/design-tokens": "10.8.6"
122
122
  },
123
123
  "devDependenciesComments": {
124
124
  "sass": "Prevent deprecation warnings introduced in 1.80 as we plan to move away from sass",
@@ -0,0 +1,118 @@
1
+ import React from 'react'
2
+ import { Meta, StoryObj } from '@storybook/react'
3
+ import { within, userEvent, expect } from '@storybook/test'
4
+ import { Text } from '~components/Text'
5
+ import { Tab, TabList, TabPanel, Tabs } from '../index'
6
+
7
+ const meta = {
8
+ title: 'Components/Tabs/Tabs (Future)/Tests',
9
+ parameters: {
10
+ controls: { disable: true },
11
+ },
12
+ args: {
13
+ children: (
14
+ <>
15
+ <TabList aria-label="Tabs" data-testid="sb-arrows">
16
+ <Tab id="one">Tab 1</Tab>
17
+ <Tab id="two">Tab 2</Tab>
18
+ <Tab id="three" badge="3">
19
+ Tab 3
20
+ </Tab>
21
+ <Tab id="disabled" isDisabled>
22
+ Disabled Tab
23
+ </Tab>
24
+ <Tab id="four">Tab 4</Tab>
25
+ <Tab id="five">Tab 5</Tab>
26
+ </TabList>
27
+ <TabPanel id="one" className="p-24">
28
+ <Text variant="body">Content 1</Text>
29
+ </TabPanel>
30
+ <TabPanel id="two" className="p-24">
31
+ <Text variant="body">Content 2</Text>
32
+ </TabPanel>
33
+ <TabPanel id="three" className="p-24">
34
+ <Text variant="body">Content 3</Text>
35
+ </TabPanel>
36
+ <TabPanel id="disabled" className="p-24">
37
+ <Text variant="body">Disabled content</Text>
38
+ </TabPanel>
39
+ <TabPanel id="four" className="p-24">
40
+ <Text variant="body">Content 4</Text>
41
+ </TabPanel>
42
+ <TabPanel id="five" className="p-24">
43
+ <Text variant="body">Content 5</Text>
44
+ </TabPanel>
45
+ </>
46
+ ),
47
+ },
48
+ } satisfies Meta<typeof Tabs>
49
+
50
+ export default meta
51
+
52
+ type Story = StoryObj<typeof meta>
53
+
54
+ export const ArrowsShowingAndHiding: Story = {
55
+ render: (args) => {
56
+ return (
57
+ <div style={{ maxWidth: '500px' }}>
58
+ <Tabs {...args} />
59
+ </div>
60
+ )
61
+ },
62
+ play: async ({ canvasElement }) => {
63
+ const canvas = within(canvasElement.parentElement!)
64
+
65
+ expect(canvas.queryByTestId('kz-tablist-left-arrow')).not.toBeInTheDocument()
66
+
67
+ const rightArrow = await canvas.findByTestId('sb-arrows-kz-tablist-right-arrow')
68
+
69
+ await userEvent.click(rightArrow)
70
+ await new Promise((r) => setTimeout(r, 500))
71
+
72
+ const leftArrow = await canvas.findByTestId('sb-arrows-kz-tablist-left-arrow')
73
+
74
+ expect(leftArrow).toBeInTheDocument()
75
+ expect(rightArrow).toBeInTheDocument()
76
+
77
+ await userEvent.click(rightArrow)
78
+ await new Promise((r) => setTimeout(r, 500))
79
+
80
+ expect(leftArrow).toBeInTheDocument()
81
+ expect(rightArrow).not.toBeInTheDocument()
82
+ },
83
+ }
84
+
85
+ export const ArrowsShowingAndHidingRTL: Story = {
86
+ name: 'Arrows Showing and Hiding (RTL)',
87
+ parameters: {
88
+ textDirection: 'rtl',
89
+ },
90
+ render: (args) => {
91
+ return (
92
+ <div style={{ maxWidth: '500px' }}>
93
+ <Tabs {...args} />
94
+ </div>
95
+ )
96
+ },
97
+ play: async ({ canvasElement }) => {
98
+ const canvas = within(canvasElement.parentElement!)
99
+
100
+ expect(canvas.queryByTestId('kz-tablist-right-arrow')).not.toBeInTheDocument()
101
+
102
+ const leftArrow = await canvas.findByTestId('sb-arrows-kz-tablist-left-arrow')
103
+
104
+ await userEvent.click(leftArrow)
105
+ await new Promise((r) => setTimeout(r, 500))
106
+
107
+ const rightArrow = await canvas.findByTestId('sb-arrows-kz-tablist-right-arrow')
108
+
109
+ expect(leftArrow).toBeInTheDocument()
110
+ expect(rightArrow).toBeInTheDocument()
111
+
112
+ await userEvent.click(leftArrow)
113
+ await new Promise((r) => setTimeout(r, 500))
114
+
115
+ expect(rightArrow).toBeInTheDocument()
116
+ expect(leftArrow).not.toBeInTheDocument()
117
+ },
118
+ }
@@ -0,0 +1,84 @@
1
+ import React from 'react'
2
+ import { Meta } from '@storybook/react'
3
+ import { Text } from '~components/Text'
4
+ import { StickerSheet, StickerSheetStory } from '~storybook/components/StickerSheet'
5
+ import { Tab, TabList, TabPanel, Tabs } from '../index'
6
+
7
+ export default {
8
+ title: 'Components/Tabs/Tabs (Future)',
9
+ parameters: {
10
+ chromatic: { disable: false },
11
+ controls: { disable: true },
12
+ },
13
+ } satisfies Meta
14
+
15
+ const ExampleTabs = ({ id }: { id: string }): JSX.Element => (
16
+ <Tabs>
17
+ <TabList aria-label="Tabs">
18
+ <Tab id={`${id}-1`}>Tab 1</Tab>
19
+ <Tab id={`${id}-2`}>Tab 2</Tab>
20
+ <Tab id={`${id}-3`} badge="3">
21
+ Tab 3
22
+ </Tab>
23
+ <Tab id={`${id}-disabled`} isDisabled>
24
+ Disabled Tab
25
+ </Tab>
26
+ <Tab id={`${id}-4`}>Tab 4</Tab>
27
+ <Tab id={`${id}-5`}>Tab 5</Tab>
28
+ </TabList>
29
+ <TabPanel id={`${id}-1`} className="p-24">
30
+ <Text variant="body">Content 1</Text>
31
+ </TabPanel>
32
+ <TabPanel id={`${id}-2`} className="p-24">
33
+ <Text variant="body">Content 2</Text>
34
+ </TabPanel>
35
+ <TabPanel id={`${id}-3`} className="p-24">
36
+ <Text variant="body">Content 3</Text>
37
+ </TabPanel>
38
+ <TabPanel id={`${id}-disabled`} className="p-24">
39
+ <Text variant="body">Disabled content</Text>
40
+ </TabPanel>
41
+ <TabPanel id={`${id}-4`} className="p-24">
42
+ <Text variant="body">Content 4</Text>
43
+ </TabPanel>
44
+ <TabPanel id={`${id}-5`} className="p-24">
45
+ <Text variant="body">Content 5</Text>
46
+ </TabPanel>
47
+ </Tabs>
48
+ )
49
+
50
+ const StickerSheetTemplate: StickerSheetStory = {
51
+ render: () => {
52
+ return (
53
+ <>
54
+ <StickerSheet title="Tabs" layout="stretch">
55
+ <StickerSheet.Row>
56
+ <ExampleTabs id="fullwidth" />
57
+ </StickerSheet.Row>
58
+ </StickerSheet>
59
+
60
+ <StickerSheet title="Overflow (container 500px)" layout="stretch">
61
+ <StickerSheet.Row>
62
+ <div style={{ maxWidth: '500px' }}>
63
+ <ExampleTabs id="overflow" />
64
+ </div>
65
+ </StickerSheet.Row>
66
+ </StickerSheet>
67
+ </>
68
+ )
69
+ },
70
+ }
71
+
72
+ export const StickerSheetDefault: StickerSheetStory = {
73
+ ...StickerSheetTemplate,
74
+ name: 'Sticker Sheet (Default)',
75
+ }
76
+
77
+ export const StickerSheetRTL: StickerSheetStory = {
78
+ ...StickerSheetTemplate,
79
+ name: 'Sticker Sheet (RTL)',
80
+ parameters: {
81
+ ...StickerSheetTemplate.parameters,
82
+ textDirection: 'rtl',
83
+ },
84
+ }
@@ -16,9 +16,11 @@ const meta = {
16
16
  <Tab id="three" badge="3">
17
17
  Tab 3
18
18
  </Tab>
19
- <Tab id="four" isDisabled>
19
+ <Tab id="disabled" isDisabled>
20
20
  Disabled Tab
21
21
  </Tab>
22
+ <Tab id="four">Tab 4</Tab>
23
+ <Tab id="five">Tab 5</Tab>
22
24
  </TabList>
23
25
  <TabPanel id="one" className="p-24">
24
26
  <Text variant="body">Content 1</Text>
@@ -29,6 +31,15 @@ const meta = {
29
31
  <TabPanel id="three" className="p-24">
30
32
  <Text variant="body">Content 3</Text>
31
33
  </TabPanel>
34
+ <TabPanel id="disabled" className="p-24">
35
+ <Text variant="body">Content 4</Text>
36
+ </TabPanel>
37
+ <TabPanel id="four" className="p-24">
38
+ <Text variant="body">Content 4</Text>
39
+ </TabPanel>
40
+ <TabPanel id="five" className="p-24">
41
+ <Text variant="body">Content 5</Text>
42
+ </TabPanel>
32
43
  </>
33
44
  ),
34
45
  },
@@ -0,0 +1 @@
1
+ export const SCROLL_AMOUNT = 120
@@ -28,7 +28,7 @@ export const Tab = (props: TabProps): JSX.Element => {
28
28
  }
29
29
 
30
30
  return (
31
- <RACTab {...tabProps}>
31
+ <RACTab data-kz-tab {...tabProps}>
32
32
  {({ isSelected, isFocusVisible, isHovered }) => (
33
33
  <>
34
34
  {children}
@@ -1,8 +1,60 @@
1
+ .container {
2
+ position: relative;
3
+ }
4
+
1
5
  .tabList {
2
6
  border-bottom: 1px solid rgba(var(--color-gray-600-rgb), 0.1);
3
- padding: var(--spacing-xs) var(--spacing-md) 0;
7
+ padding: var(--spacing-6) 0 0;
8
+ width: 100%;
9
+ height: 100%;
10
+ overflow-x: scroll;
11
+ white-space: nowrap;
12
+ scrollbar-width: none;
13
+ scroll-behavior: smooth;
4
14
  }
5
15
 
6
16
  .noPadding {
7
17
  padding: 0;
8
18
  }
19
+
20
+ .leftArrow,
21
+ .rightArrow {
22
+ --icon-size: 24;
23
+
24
+ display: flex;
25
+ align-items: center;
26
+ justify-content: center;
27
+ position: absolute;
28
+ z-index: 10000;
29
+ background: var(--color-white);
30
+ inset-block: 0 1px;
31
+ width: 48px;
32
+ cursor: default;
33
+ user-select: none;
34
+ }
35
+
36
+ /*
37
+ * Note: we're purposefully using directional properties instead of start/end for positioning and styling related to the carousel arrows
38
+ */
39
+ .leftArrow {
40
+ left: 0;
41
+ }
42
+
43
+ .leftArrow,
44
+ .leftArrow:hover {
45
+ border-right: 1px solid rgba(var(--color-gray-600-rgb), 0.1);
46
+ }
47
+
48
+ .rightArrow {
49
+ right: 0;
50
+ }
51
+
52
+ .rightArrow,
53
+ .rightArrow:hover {
54
+ border-left: 1px solid rgba(var(--color-gray-600-rgb), 0.1);
55
+ }
56
+
57
+ .leftArrow:hover,
58
+ .rightArrow:hover {
59
+ background: var(--color-gray-200);
60
+ }
@@ -1,6 +1,13 @@
1
- import React, { ReactNode } from 'react'
1
+ import React, { ReactNode, useContext, useEffect, useId, useRef, useState } from 'react'
2
2
  import classnames from 'classnames'
3
- import { TabList as RACTabList, TabListProps as RACTabListProps } from 'react-aria-components'
3
+ import {
4
+ TabList as RACTabList,
5
+ TabListProps as RACTabListProps,
6
+ TabListStateContext,
7
+ } from 'react-aria-components'
8
+ import { Icon } from '~components/__future__/Icon'
9
+ import { isRTL as isRTLCheck } from '~components/__utilities__/isRTL'
10
+ import { SCROLL_AMOUNT } from '../../constants'
4
11
  import styles from './TabList.module.css'
5
12
 
6
13
  export type TabListProps = {
@@ -13,20 +20,141 @@ export type TabListProps = {
13
20
  */
14
21
  'noPadding'?: boolean
15
22
  'children': ReactNode
23
+ 'data-testid'?: string
16
24
  } & RACTabListProps<HTMLElement>
17
25
 
18
26
  /**
19
27
  * Wrapper for the tabs themselves
20
28
  */
21
29
  export const TabList = (props: TabListProps): JSX.Element => {
22
- const { 'aria-label': ariaLabel, noPadding = false, children, className, ...restProps } = props
30
+ const {
31
+ 'aria-label': ariaLabel,
32
+ noPadding = false,
33
+ children,
34
+ className,
35
+ 'data-testid': testId,
36
+ ...restProps
37
+ } = props
38
+ const [isDocumentReady, setIsDocumentReady] = useState<boolean>(false)
39
+ const [leftArrowEnabled, setLeftArrowEnabled] = useState<boolean>(false)
40
+ const [rightArrowEnabled, setRightArrowEnabled] = useState<boolean>(false)
41
+ const tabListRef = useRef<HTMLDivElement | null>(null)
42
+ const tabListId = useId()
43
+ const [isRTL, setIsRTL] = useState<boolean>(false)
44
+ const [containerElement, setContainerElement] = useState<HTMLElement | null>()
45
+ const tabListContext = useContext(TabListStateContext)
46
+ const selectedKey = tabListContext?.selectedKey
47
+
48
+ useEffect(() => {
49
+ if (!isDocumentReady) {
50
+ setIsDocumentReady(true)
51
+ return
52
+ }
53
+
54
+ const container = document.getElementById(tabListId)
55
+ setContainerElement(container)
56
+ setIsRTL(container ? isRTLCheck(container) : false)
57
+ }, [isDocumentReady, tabListId])
58
+
59
+ useEffect(() => {
60
+ if (!isDocumentReady) {
61
+ return
62
+ }
63
+
64
+ const tabs = containerElement?.querySelectorAll('[data-kz-tab]')
65
+ if (!tabs) {
66
+ return
67
+ }
68
+
69
+ const firstTabObserver = new IntersectionObserver(
70
+ (entries) => {
71
+ if (!entries[0].isIntersecting) {
72
+ setLeftArrowEnabled(true)
73
+ return
74
+ }
75
+ setLeftArrowEnabled(false)
76
+ },
77
+ {
78
+ threshold: 0.75,
79
+ root: containerElement,
80
+ },
81
+ )
82
+ firstTabObserver.observe(isRTL ? tabs[tabs.length - 1] : tabs[0])
83
+
84
+ const lastTabObserver = new IntersectionObserver(
85
+ (entries) => {
86
+ if (!entries[0].isIntersecting) {
87
+ setRightArrowEnabled(true)
88
+ return
89
+ }
90
+ setRightArrowEnabled(false)
91
+ },
92
+ {
93
+ threshold: 0.75,
94
+ root: containerElement,
95
+ },
96
+ )
97
+ lastTabObserver.observe(isRTL ? tabs[0] : tabs[tabs.length - 1])
98
+
99
+ return () => {
100
+ firstTabObserver.disconnect()
101
+ lastTabObserver.disconnect()
102
+ }
103
+ }, [isDocumentReady, containerElement, isRTL])
104
+
105
+ useEffect(() => {
106
+ if (!isDocumentReady) {
107
+ return
108
+ }
109
+
110
+ // Scroll selected tab into view
111
+ containerElement
112
+ ?.querySelector('[role="tab"][data-selected=true]')
113
+ ?.scrollIntoView({ block: 'nearest', inline: 'center' })
114
+ }, [selectedKey, containerElement, isDocumentReady])
115
+
116
+ const handleArrowPress = (direction: 'left' | 'right'): void => {
117
+ if (tabListRef.current) {
118
+ const tabListScrollPos = tabListRef.current.scrollLeft
119
+ const newSpot =
120
+ direction === 'left' ? tabListScrollPos - SCROLL_AMOUNT : tabListScrollPos + SCROLL_AMOUNT
121
+ tabListRef.current.scrollLeft = newSpot
122
+ }
123
+ }
124
+
23
125
  return (
24
- <RACTabList
25
- aria-label={ariaLabel}
26
- className={classnames(styles.tabList, className, noPadding && styles.noPadding)}
27
- {...restProps}
28
- >
29
- {children}
30
- </RACTabList>
126
+ <div className={styles.container} id={tabListId}>
127
+ {leftArrowEnabled && (
128
+ // making a conscious decision to use <div onClick> over <button> here, because:
129
+ // - <button> would add pointless noise for a screen reader user
130
+ // - keyboard only user can toggle through tabs with left/right arrow keys already
131
+ // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
132
+ <div
133
+ onClick={() => handleArrowPress('left')}
134
+ className={styles.leftArrow}
135
+ data-testid={testId ? `${testId}-kz-tablist-left-arrow` : undefined}
136
+ >
137
+ <Icon name="chevron_left" isPresentational />
138
+ </div>
139
+ )}
140
+ <RACTabList
141
+ aria-label={ariaLabel}
142
+ ref={tabListRef}
143
+ className={classnames(styles.tabList, className, noPadding && styles.noPadding)}
144
+ {...restProps}
145
+ >
146
+ {children}
147
+ </RACTabList>
148
+ {rightArrowEnabled && (
149
+ // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
150
+ <div
151
+ onClick={() => handleArrowPress('right')}
152
+ className={styles.rightArrow}
153
+ data-testid={testId ? `${testId}-kz-tablist-right-arrow` : undefined}
154
+ >
155
+ <Icon name="chevron_right" isPresentational />
156
+ </div>
157
+ )}
158
+ </div>
31
159
  )
32
160
  }
@@ -0,0 +1 @@
1
+ export * from './isRTL'
@@ -0,0 +1,38 @@
1
+ import React from 'react'
2
+ import { render, screen } from '@testing-library/react'
3
+ import { isRTL } from './isRTL'
4
+
5
+ describe('isRTL', () => {
6
+ it('returns false when no element with dir found', () => {
7
+ const Example = (): JSX.Element => <button type="button">Test</button>
8
+ render(<Example />)
9
+ const button = screen.getByRole('button')
10
+ expect(isRTL(button)).toBe(false)
11
+ })
12
+
13
+ it('returns false when greater parent is dir=rtl, but closer parent is dir=ltr', () => {
14
+ const Example = (): JSX.Element => (
15
+ <div dir="rtl">
16
+ <div dir="ltr">
17
+ <button type="button">Test</button>
18
+ </div>
19
+ </div>
20
+ )
21
+ render(<Example />)
22
+ const button = screen.getByRole('button')
23
+ expect(isRTL(button)).toBe(false)
24
+ })
25
+
26
+ it('returns true when greater parent is dir=ltr, but closer parent is dir=rtl', () => {
27
+ const Example = (): JSX.Element => (
28
+ <div dir="ltr">
29
+ <div dir="rtl">
30
+ <button type="button">Test</button>
31
+ </div>
32
+ </div>
33
+ )
34
+ render(<Example />)
35
+ const button = screen.getByRole('button')
36
+ expect(isRTL(button)).toBe(true)
37
+ })
38
+ })
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Finds the first ancestor with a `dir` property on it
3
+ * Returning true is that is `dir=rtl` and returning false in all other cases
4
+ */
5
+ export const isRTL = (element: Element): boolean =>
6
+ !!element.closest('[dir]')?.matches('[dir="rtl"]')