@bento-core/tab 1.0.0-c3dc.1 → 1.0.0-c3dc.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Tabs.js +226 -27
- package/dist/assets/icons/more-vertical.svg +17 -0
- package/package.json +2 -2
- package/src/Tabs.js +236 -25
- package/src/assets/icons/more-vertical.svg +17 -0
package/dist/Tabs.js
CHANGED
|
@@ -4,26 +4,161 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.default = void 0;
|
|
7
|
-
var _react =
|
|
7
|
+
var _react = _interopRequireWildcard(require("react"));
|
|
8
8
|
var _core = require("@material-ui/core");
|
|
9
9
|
var _toolTip = _interopRequireDefault(require("@bento-core/tool-tip"));
|
|
10
10
|
var _defaultTheme = require("./defaultTheme");
|
|
11
|
+
var _moreVertical = _interopRequireDefault(require("./assets/icons/more-vertical.svg"));
|
|
11
12
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
13
|
+
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
|
14
|
+
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
|
12
15
|
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
|
13
16
|
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
|
|
14
17
|
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
|
|
15
18
|
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
|
16
19
|
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
|
|
17
20
|
function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
|
|
21
|
+
// Calculate default window width for SSR (ensures max tabs)
|
|
22
|
+
const getDefaultWindowWidth = responsiveBreakpoints => {
|
|
23
|
+
if (!responsiveBreakpoints || !Array.isArray(responsiveBreakpoints.breakpoints) || responsiveBreakpoints.breakpoints.length === 0) {
|
|
24
|
+
return 1800; // Fallback if no config provided
|
|
25
|
+
}
|
|
26
|
+
// Use width above the highest breakpoint to ensure default tab limit
|
|
27
|
+
const {
|
|
28
|
+
breakpoints
|
|
29
|
+
} = responsiveBreakpoints;
|
|
30
|
+
const highestBreakpoint = breakpoints[breakpoints.length - 1];
|
|
31
|
+
return highestBreakpoint.maxWidth + 100;
|
|
32
|
+
};
|
|
18
33
|
const TabItems = _ref => {
|
|
19
34
|
let {
|
|
20
35
|
tabItems,
|
|
21
36
|
handleTabChange,
|
|
22
37
|
currentTab,
|
|
23
38
|
orientation,
|
|
24
|
-
customTheme = {}
|
|
39
|
+
customTheme = {},
|
|
40
|
+
maxVisibleTabs = 6,
|
|
41
|
+
enableGrouping = false,
|
|
42
|
+
responsiveBreakpoints = null
|
|
25
43
|
} = _ref;
|
|
26
|
-
const
|
|
44
|
+
const [currentGroup, setCurrentGroup] = (0, _react.useState)(0);
|
|
45
|
+
const [showMorePopup, setShowMorePopup] = (0, _react.useState)(false);
|
|
46
|
+
const [moreButtonAnchor, setMoreButtonAnchor] = (0, _react.useState)(null);
|
|
47
|
+
const [windowWidth, setWindowWidth] = (0, _react.useState)(typeof window !== 'undefined' ? window.innerWidth : getDefaultWindowWidth(responsiveBreakpoints));
|
|
48
|
+
|
|
49
|
+
// Calculate tab limit based on screen width breakpoints
|
|
50
|
+
const getTabLimitByWidth = width => {
|
|
51
|
+
if (!responsiveBreakpoints) {
|
|
52
|
+
// Fallback to original hardcoded values if no config provided
|
|
53
|
+
if (width < 1250) return 2;
|
|
54
|
+
if (width < 1400) return 3;
|
|
55
|
+
if (width < 1550) return 4;
|
|
56
|
+
if (width < 1700) return 5;
|
|
57
|
+
return 6; // >= 1700px
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Use configuration-based breakpoints
|
|
61
|
+
for (let i = 0; i < responsiveBreakpoints.breakpoints.length; i += 1) {
|
|
62
|
+
const {
|
|
63
|
+
maxWidth,
|
|
64
|
+
tabLimit
|
|
65
|
+
} = responsiveBreakpoints.breakpoints[i];
|
|
66
|
+
if (width <= maxWidth) {
|
|
67
|
+
return tabLimit;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return responsiveBreakpoints.defaultTabLimit;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// Grouping logic with responsive breakpoints
|
|
74
|
+
const tabLimit = enableGrouping ? getTabLimitByWidth(windowWidth) : maxVisibleTabs;
|
|
75
|
+
const shouldShowMoreButton = enableGrouping && tabItems.length > tabLimit;
|
|
76
|
+
|
|
77
|
+
// Window resize listener for responsive breakpoints
|
|
78
|
+
(0, _react.useEffect)(() => {
|
|
79
|
+
if (!enableGrouping || typeof window === 'undefined') {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
const handleResize = () => {
|
|
83
|
+
const newWidth = window.innerWidth;
|
|
84
|
+
setWindowWidth(newWidth);
|
|
85
|
+
};
|
|
86
|
+
window.addEventListener('resize', handleResize);
|
|
87
|
+
return () => {
|
|
88
|
+
window.removeEventListener('resize', handleResize);
|
|
89
|
+
};
|
|
90
|
+
}, [enableGrouping]);
|
|
91
|
+
|
|
92
|
+
// Consolidated effect: handle tab limit changes and active tab group recalculation
|
|
93
|
+
(0, _react.useEffect)(() => {
|
|
94
|
+
if (!enableGrouping) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const newActiveTabGroup = Math.floor(currentTab / tabLimit);
|
|
98
|
+
if (newActiveTabGroup !== currentGroup) {
|
|
99
|
+
setCurrentGroup(newActiveTabGroup);
|
|
100
|
+
}
|
|
101
|
+
}, [tabLimit, currentTab, currentGroup, enableGrouping]);
|
|
102
|
+
|
|
103
|
+
// Get visible tabs for current group
|
|
104
|
+
const getVisibleTabs = () => {
|
|
105
|
+
if (!enableGrouping) {
|
|
106
|
+
return tabItems;
|
|
107
|
+
}
|
|
108
|
+
const startIndex = currentGroup * tabLimit;
|
|
109
|
+
const endIndex = Math.min(startIndex + tabLimit, tabItems.length);
|
|
110
|
+
return tabItems.slice(startIndex, endIndex);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// Get popup tabs with wrap-around logic (memoized for performance)
|
|
114
|
+
const popupTabs = (0, _react.useMemo)(() => {
|
|
115
|
+
if (!enableGrouping || !shouldShowMoreButton) {
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
const visibleStart = currentGroup * tabLimit;
|
|
119
|
+
const visibleEnd = Math.min(visibleStart + tabLimit, tabItems.length);
|
|
120
|
+
|
|
121
|
+
// Create hidden tabs with their original indices to avoid O(n²) findIndex
|
|
122
|
+
const hiddenTabsWithIndex = [];
|
|
123
|
+
|
|
124
|
+
// Add tabs after visible range
|
|
125
|
+
for (let i = visibleEnd; i < tabItems.length; i += 1) {
|
|
126
|
+
const tab = tabItems[i];
|
|
127
|
+
if (tab) {
|
|
128
|
+
hiddenTabsWithIndex.push({
|
|
129
|
+
tab,
|
|
130
|
+
originalIndex: i
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Add tabs before visible range (wrap-around)
|
|
136
|
+
for (let i = 0; i < visibleStart; i += 1) {
|
|
137
|
+
const tab = tabItems[i];
|
|
138
|
+
if (tab) {
|
|
139
|
+
hiddenTabsWithIndex.push({
|
|
140
|
+
tab,
|
|
141
|
+
originalIndex: i
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return hiddenTabsWithIndex;
|
|
146
|
+
}, [enableGrouping, shouldShowMoreButton, currentGroup, tabLimit, tabItems]);
|
|
147
|
+
const handleMoreButtonClick = event => {
|
|
148
|
+
setMoreButtonAnchor(event.currentTarget);
|
|
149
|
+
setShowMorePopup(true);
|
|
150
|
+
};
|
|
151
|
+
const handlePopupClose = () => {
|
|
152
|
+
setShowMorePopup(false);
|
|
153
|
+
setMoreButtonAnchor(null);
|
|
154
|
+
};
|
|
155
|
+
const handlePopupTabClick = tabIndex => {
|
|
156
|
+
const newGroup = Math.floor(tabIndex / tabLimit);
|
|
157
|
+
setCurrentGroup(newGroup);
|
|
158
|
+
handleTabChange(null, tabIndex);
|
|
159
|
+
handlePopupClose();
|
|
160
|
+
};
|
|
161
|
+
const getTabLabel = _ref2 => {
|
|
27
162
|
let {
|
|
28
163
|
name,
|
|
29
164
|
count,
|
|
@@ -50,42 +185,106 @@ const TabItems = _ref => {
|
|
|
50
185
|
}
|
|
51
186
|
}, count));
|
|
52
187
|
};
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
})),
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
188
|
+
const visibleTabs = getVisibleTabs();
|
|
189
|
+
const TABs = visibleTabs.map((tab, visibleIndex) => {
|
|
190
|
+
// Calculate the actual tab index in the full tabItems array
|
|
191
|
+
const actualIndex = enableGrouping ? currentGroup * tabLimit + visibleIndex : visibleIndex;
|
|
192
|
+
return tab.hasToolTip ? /*#__PURE__*/_react.default.createElement(_toolTip.default, _extends({}, tab.tooltipStyles, {
|
|
193
|
+
title: tab.toolTipText || '.',
|
|
194
|
+
arrow: true,
|
|
195
|
+
placement: "top",
|
|
196
|
+
key: actualIndex
|
|
197
|
+
}), /*#__PURE__*/_react.default.createElement(_core.Tab, {
|
|
198
|
+
index: actualIndex,
|
|
199
|
+
label: getTabLabel(_objectSpread(_objectSpread({}, tab), {}, {
|
|
200
|
+
index: actualIndex
|
|
201
|
+
})),
|
|
202
|
+
className: tab.clsName,
|
|
203
|
+
disableRipple: true
|
|
204
|
+
})) : /*#__PURE__*/_react.default.createElement(_core.Tab, {
|
|
205
|
+
index: actualIndex,
|
|
206
|
+
label: getTabLabel(_objectSpread(_objectSpread({}, tab), {}, {
|
|
207
|
+
index: actualIndex
|
|
208
|
+
})),
|
|
209
|
+
key: actualIndex,
|
|
210
|
+
className: tab.clsName,
|
|
211
|
+
disableRipple: true
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// Add More button if needed
|
|
216
|
+
if (shouldShowMoreButton) {
|
|
217
|
+
const hiddenTabsCount = popupTabs.length;
|
|
218
|
+
TABs.push( /*#__PURE__*/_react.default.createElement(_core.Button, {
|
|
219
|
+
key: "more-button",
|
|
220
|
+
onClick: handleMoreButtonClick,
|
|
221
|
+
className: "more-button"
|
|
222
|
+
}, /*#__PURE__*/_react.default.createElement("span", null, /*#__PURE__*/_react.default.createElement("img", {
|
|
223
|
+
src: _moreVertical.default,
|
|
224
|
+
alt: "More options"
|
|
225
|
+
}), "More(".concat(hiddenTabsCount, ")"))));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Adjust currentTab value for visible tabs when grouping is enabled
|
|
229
|
+
const adjustedCurrentTab = enableGrouping ? currentTab - currentGroup * tabLimit : currentTab;
|
|
74
230
|
const themeConfig = (0, _core.createTheme)({
|
|
75
231
|
overrides: _objectSpread(_objectSpread({}, (0, _defaultTheme.defaultTheme)()), customTheme)
|
|
76
232
|
});
|
|
77
233
|
return /*#__PURE__*/_react.default.createElement(_core.ThemeProvider, {
|
|
78
234
|
theme: themeConfig
|
|
235
|
+
}, /*#__PURE__*/_react.default.createElement("div", {
|
|
236
|
+
style: {
|
|
237
|
+
position: 'relative'
|
|
238
|
+
}
|
|
79
239
|
}, /*#__PURE__*/_react.default.createElement(_core.Tabs, {
|
|
80
|
-
onChange: (event, value) =>
|
|
81
|
-
|
|
240
|
+
onChange: (event, value) => {
|
|
241
|
+
// Convert relative position to actual tab index when grouping is enabled
|
|
242
|
+
const actualTabIndex = enableGrouping ? currentGroup * tabLimit + value : value;
|
|
243
|
+
handleTabChange(event, actualTabIndex);
|
|
244
|
+
},
|
|
245
|
+
value: adjustedCurrentTab,
|
|
82
246
|
TabIndicatorProps: {
|
|
83
247
|
style: {
|
|
84
248
|
background: 'none'
|
|
85
249
|
}
|
|
86
250
|
},
|
|
87
251
|
orientation: orientation
|
|
88
|
-
}, TABs)
|
|
252
|
+
}, TABs), shouldShowMoreButton && /*#__PURE__*/_react.default.createElement(_core.Popover, {
|
|
253
|
+
open: showMorePopup,
|
|
254
|
+
anchorEl: moreButtonAnchor,
|
|
255
|
+
onClose: handlePopupClose,
|
|
256
|
+
anchorOrigin: {
|
|
257
|
+
vertical: 'bottom',
|
|
258
|
+
horizontal: 'center'
|
|
259
|
+
},
|
|
260
|
+
transformOrigin: {
|
|
261
|
+
vertical: 'top',
|
|
262
|
+
horizontal: 'center'
|
|
263
|
+
},
|
|
264
|
+
style: {
|
|
265
|
+
marginTop: '10px'
|
|
266
|
+
}
|
|
267
|
+
}, /*#__PURE__*/_react.default.createElement(_core.List, {
|
|
268
|
+
className: "popover-list"
|
|
269
|
+
}, popupTabs.map(_ref3 => {
|
|
270
|
+
let {
|
|
271
|
+
tab,
|
|
272
|
+
originalIndex
|
|
273
|
+
} = _ref3;
|
|
274
|
+
if (!tab || !tab.name) {
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
return /*#__PURE__*/_react.default.createElement(_core.ListItem, {
|
|
278
|
+
key: originalIndex,
|
|
279
|
+
button: true,
|
|
280
|
+
onClick: () => handlePopupTabClick(originalIndex),
|
|
281
|
+
className: "popover-list-item"
|
|
282
|
+
}, /*#__PURE__*/_react.default.createElement("span", {
|
|
283
|
+
className: "popover-tab-name"
|
|
284
|
+
}, tab.name), /*#__PURE__*/_react.default.createElement("span", {
|
|
285
|
+
className: "popover-tab-count"
|
|
286
|
+
}, tab.count || ''));
|
|
287
|
+
})))));
|
|
89
288
|
};
|
|
90
289
|
var _default = TabItems;
|
|
91
290
|
exports.default = _default;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<svg width="4" height="16" viewBox="0 0 4 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<circle cx="1.8811" cy="2.4519" r="1.84937" fill="black"/>
|
|
3
|
+
<circle cx="1.8811" cy="2.4519" r="1.84937" fill="black"/>
|
|
4
|
+
<circle cx="1.8811" cy="2.4519" r="1.84937" fill="black"/>
|
|
5
|
+
<circle cx="1.8811" cy="2.4519" r="1.84937" fill="black"/>
|
|
6
|
+
<circle cx="1.8811" cy="2.4519" r="1.84937" fill="black"/>
|
|
7
|
+
<circle cx="1.8811" cy="7.99976" r="1.84937" fill="black"/>
|
|
8
|
+
<circle cx="1.8811" cy="7.99976" r="1.84937" fill="black"/>
|
|
9
|
+
<circle cx="1.8811" cy="7.99976" r="1.84937" fill="black"/>
|
|
10
|
+
<circle cx="1.8811" cy="7.99976" r="1.84937" fill="black"/>
|
|
11
|
+
<circle cx="1.8811" cy="7.99976" r="1.84937" fill="black"/>
|
|
12
|
+
<circle cx="1.8811" cy="13.5481" r="1.84937" fill="black"/>
|
|
13
|
+
<circle cx="1.8811" cy="13.5481" r="1.84937" fill="black"/>
|
|
14
|
+
<circle cx="1.8811" cy="13.5481" r="1.84937" fill="black"/>
|
|
15
|
+
<circle cx="1.8811" cy="13.5481" r="1.84937" fill="black"/>
|
|
16
|
+
<circle cx="1.8811" cy="13.5481" r="1.84937" fill="black"/>
|
|
17
|
+
</svg>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bento-core/tab",
|
|
3
|
-
"version": "1.0.0-c3dc.
|
|
3
|
+
"version": "1.0.0-c3dc.3",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
},
|
|
15
15
|
"peerDependencies": {
|
|
16
16
|
"@material-ui/core": "^4.12.4",
|
|
17
|
-
"@bento-core/tool-tip": "
|
|
17
|
+
"@bento-core/tool-tip": "1.0.0-c3dc.0",
|
|
18
18
|
"react": "^17.0.2",
|
|
19
19
|
"react-dom": "^17.0.0",
|
|
20
20
|
"react-redux": "^7.2.1"
|
package/src/Tabs.js
CHANGED
|
@@ -1,12 +1,32 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useState, useEffect, useMemo } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
Tab,
|
|
4
4
|
Tabs,
|
|
5
5
|
createTheme,
|
|
6
6
|
ThemeProvider,
|
|
7
|
+
Button,
|
|
8
|
+
Popover,
|
|
9
|
+
List,
|
|
10
|
+
ListItem,
|
|
7
11
|
} from '@material-ui/core';
|
|
8
12
|
import ToolTip from '@bento-core/tool-tip';
|
|
9
13
|
import { defaultTheme } from './defaultTheme';
|
|
14
|
+
import MoreVerticalIcon from './assets/icons/more-vertical.svg';
|
|
15
|
+
|
|
16
|
+
// Calculate default window width for SSR (ensures max tabs)
|
|
17
|
+
const getDefaultWindowWidth = (responsiveBreakpoints) => {
|
|
18
|
+
if (
|
|
19
|
+
!responsiveBreakpoints
|
|
20
|
+
|| !Array.isArray(responsiveBreakpoints.breakpoints)
|
|
21
|
+
|| responsiveBreakpoints.breakpoints.length === 0
|
|
22
|
+
) {
|
|
23
|
+
return 1800; // Fallback if no config provided
|
|
24
|
+
}
|
|
25
|
+
// Use width above the highest breakpoint to ensure default tab limit
|
|
26
|
+
const { breakpoints } = responsiveBreakpoints;
|
|
27
|
+
const highestBreakpoint = breakpoints[breakpoints.length - 1];
|
|
28
|
+
return highestBreakpoint.maxWidth + 100;
|
|
29
|
+
};
|
|
10
30
|
|
|
11
31
|
const TabItems = ({
|
|
12
32
|
tabItems,
|
|
@@ -14,8 +34,128 @@ const TabItems = ({
|
|
|
14
34
|
currentTab,
|
|
15
35
|
orientation,
|
|
16
36
|
customTheme = {},
|
|
37
|
+
maxVisibleTabs = 6,
|
|
38
|
+
enableGrouping = false,
|
|
39
|
+
responsiveBreakpoints = null,
|
|
17
40
|
}) => {
|
|
18
|
-
const
|
|
41
|
+
const [currentGroup, setCurrentGroup] = useState(0);
|
|
42
|
+
const [showMorePopup, setShowMorePopup] = useState(false);
|
|
43
|
+
const [moreButtonAnchor, setMoreButtonAnchor] = useState(null);
|
|
44
|
+
const [windowWidth, setWindowWidth] = useState(typeof window !== 'undefined' ? window.innerWidth : getDefaultWindowWidth(responsiveBreakpoints));
|
|
45
|
+
|
|
46
|
+
// Calculate tab limit based on screen width breakpoints
|
|
47
|
+
const getTabLimitByWidth = (width) => {
|
|
48
|
+
if (!responsiveBreakpoints) {
|
|
49
|
+
// Fallback to original hardcoded values if no config provided
|
|
50
|
+
if (width < 1250) return 2;
|
|
51
|
+
if (width < 1400) return 3;
|
|
52
|
+
if (width < 1550) return 4;
|
|
53
|
+
if (width < 1700) return 5;
|
|
54
|
+
return 6; // >= 1700px
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Use configuration-based breakpoints
|
|
58
|
+
for (let i = 0; i < responsiveBreakpoints.breakpoints.length; i += 1) {
|
|
59
|
+
const { maxWidth, tabLimit } = responsiveBreakpoints.breakpoints[i];
|
|
60
|
+
if (width <= maxWidth) {
|
|
61
|
+
return tabLimit;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return responsiveBreakpoints.defaultTabLimit;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// Grouping logic with responsive breakpoints
|
|
68
|
+
const tabLimit = enableGrouping ? getTabLimitByWidth(windowWidth) : maxVisibleTabs;
|
|
69
|
+
const shouldShowMoreButton = enableGrouping && tabItems.length > tabLimit;
|
|
70
|
+
|
|
71
|
+
// Window resize listener for responsive breakpoints
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
if (!enableGrouping || typeof window === 'undefined') {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const handleResize = () => {
|
|
78
|
+
const newWidth = window.innerWidth;
|
|
79
|
+
setWindowWidth(newWidth);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
window.addEventListener('resize', handleResize);
|
|
83
|
+
return () => {
|
|
84
|
+
window.removeEventListener('resize', handleResize);
|
|
85
|
+
};
|
|
86
|
+
}, [enableGrouping]);
|
|
87
|
+
|
|
88
|
+
// Consolidated effect: handle tab limit changes and active tab group recalculation
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
if (!enableGrouping) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const newActiveTabGroup = Math.floor(currentTab / tabLimit);
|
|
95
|
+
if (newActiveTabGroup !== currentGroup) {
|
|
96
|
+
setCurrentGroup(newActiveTabGroup);
|
|
97
|
+
}
|
|
98
|
+
}, [tabLimit, currentTab, currentGroup, enableGrouping]);
|
|
99
|
+
|
|
100
|
+
// Get visible tabs for current group
|
|
101
|
+
const getVisibleTabs = () => {
|
|
102
|
+
if (!enableGrouping) {
|
|
103
|
+
return tabItems;
|
|
104
|
+
}
|
|
105
|
+
const startIndex = currentGroup * tabLimit;
|
|
106
|
+
const endIndex = Math.min(startIndex + tabLimit, tabItems.length);
|
|
107
|
+
return tabItems.slice(startIndex, endIndex);
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// Get popup tabs with wrap-around logic (memoized for performance)
|
|
111
|
+
const popupTabs = useMemo(() => {
|
|
112
|
+
if (!enableGrouping || !shouldShowMoreButton) {
|
|
113
|
+
return [];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const visibleStart = currentGroup * tabLimit;
|
|
117
|
+
const visibleEnd = Math.min(visibleStart + tabLimit, tabItems.length);
|
|
118
|
+
|
|
119
|
+
// Create hidden tabs with their original indices to avoid O(n²) findIndex
|
|
120
|
+
const hiddenTabsWithIndex = [];
|
|
121
|
+
|
|
122
|
+
// Add tabs after visible range
|
|
123
|
+
for (let i = visibleEnd; i < tabItems.length; i += 1) {
|
|
124
|
+
const tab = tabItems[i];
|
|
125
|
+
if (tab) {
|
|
126
|
+
hiddenTabsWithIndex.push({ tab, originalIndex: i });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Add tabs before visible range (wrap-around)
|
|
131
|
+
for (let i = 0; i < visibleStart; i += 1) {
|
|
132
|
+
const tab = tabItems[i];
|
|
133
|
+
if (tab) {
|
|
134
|
+
hiddenTabsWithIndex.push({ tab, originalIndex: i });
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return hiddenTabsWithIndex;
|
|
139
|
+
}, [enableGrouping, shouldShowMoreButton, currentGroup, tabLimit, tabItems]);
|
|
140
|
+
|
|
141
|
+
const handleMoreButtonClick = (event) => {
|
|
142
|
+
setMoreButtonAnchor(event.currentTarget);
|
|
143
|
+
setShowMorePopup(true);
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const handlePopupClose = () => {
|
|
147
|
+
setShowMorePopup(false);
|
|
148
|
+
setMoreButtonAnchor(null);
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const handlePopupTabClick = (tabIndex) => {
|
|
152
|
+
const newGroup = Math.floor(tabIndex / tabLimit);
|
|
153
|
+
setCurrentGroup(newGroup);
|
|
154
|
+
handleTabChange(null, tabIndex);
|
|
155
|
+
handlePopupClose();
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const getTabLabel = ({
|
|
19
159
|
name, count, clsName, index,
|
|
20
160
|
}) => (
|
|
21
161
|
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
|
|
@@ -35,17 +175,20 @@ const TabItems = ({
|
|
|
35
175
|
</div>
|
|
36
176
|
);
|
|
37
177
|
|
|
38
|
-
const
|
|
178
|
+
const visibleTabs = getVisibleTabs();
|
|
39
179
|
|
|
40
|
-
|
|
180
|
+
const TABs = visibleTabs.map((tab, visibleIndex) => {
|
|
181
|
+
// Calculate the actual tab index in the full tabItems array
|
|
182
|
+
const actualIndex = enableGrouping
|
|
183
|
+
? (currentGroup * tabLimit) + visibleIndex
|
|
184
|
+
: visibleIndex;
|
|
185
|
+
|
|
186
|
+
return tab.hasToolTip
|
|
41
187
|
? (
|
|
42
|
-
<ToolTip {...tab.tooltipStyles} title={tab.toolTipText || '.'} arrow placement="top">
|
|
188
|
+
<ToolTip {...tab.tooltipStyles} title={tab.toolTipText || '.'} arrow placement="top" key={actualIndex}>
|
|
43
189
|
<Tab
|
|
44
|
-
index={
|
|
45
|
-
label={
|
|
46
|
-
getTabLalbel({ ...tab, index })
|
|
47
|
-
}
|
|
48
|
-
key={index}
|
|
190
|
+
index={actualIndex}
|
|
191
|
+
label={getTabLabel({ ...tab, index: actualIndex })}
|
|
49
192
|
className={tab.clsName}
|
|
50
193
|
disableRipple
|
|
51
194
|
/>
|
|
@@ -53,29 +196,97 @@ const TabItems = ({
|
|
|
53
196
|
)
|
|
54
197
|
: (
|
|
55
198
|
<Tab
|
|
56
|
-
index={
|
|
57
|
-
label={
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
key={index}
|
|
199
|
+
index={actualIndex}
|
|
200
|
+
label={getTabLabel({ ...tab, index: actualIndex })}
|
|
201
|
+
key={actualIndex}
|
|
61
202
|
className={tab.clsName}
|
|
62
203
|
disableRipple
|
|
63
204
|
/>
|
|
64
|
-
)
|
|
205
|
+
);
|
|
206
|
+
});
|
|
65
207
|
|
|
66
|
-
|
|
208
|
+
// Add More button if needed
|
|
209
|
+
if (shouldShowMoreButton) {
|
|
210
|
+
const hiddenTabsCount = popupTabs.length;
|
|
211
|
+
TABs.push(
|
|
212
|
+
<Button
|
|
213
|
+
key="more-button"
|
|
214
|
+
onClick={handleMoreButtonClick}
|
|
215
|
+
className="more-button"
|
|
216
|
+
>
|
|
217
|
+
<span>
|
|
218
|
+
<img src={MoreVerticalIcon} alt="More options" />
|
|
219
|
+
{`More(${hiddenTabsCount})`}
|
|
220
|
+
</span>
|
|
221
|
+
</Button>,
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Adjust currentTab value for visible tabs when grouping is enabled
|
|
226
|
+
const adjustedCurrentTab = enableGrouping
|
|
227
|
+
? currentTab - (currentGroup * tabLimit)
|
|
228
|
+
: currentTab;
|
|
67
229
|
|
|
68
230
|
const themeConfig = createTheme({ overrides: { ...defaultTheme(), ...customTheme } });
|
|
69
231
|
return (
|
|
70
232
|
<ThemeProvider theme={themeConfig}>
|
|
71
|
-
<
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
233
|
+
<div style={{ position: 'relative' }}>
|
|
234
|
+
<Tabs
|
|
235
|
+
onChange={(event, value) => {
|
|
236
|
+
// Convert relative position to actual tab index when grouping is enabled
|
|
237
|
+
const actualTabIndex = enableGrouping
|
|
238
|
+
? (currentGroup * tabLimit) + value
|
|
239
|
+
: value;
|
|
240
|
+
handleTabChange(event, actualTabIndex);
|
|
241
|
+
}}
|
|
242
|
+
value={adjustedCurrentTab}
|
|
243
|
+
TabIndicatorProps={{ style: { background: 'none' } }}
|
|
244
|
+
orientation={orientation}
|
|
245
|
+
>
|
|
246
|
+
{TABs}
|
|
247
|
+
</Tabs>
|
|
248
|
+
|
|
249
|
+
{/* More button popup */}
|
|
250
|
+
{shouldShowMoreButton && (
|
|
251
|
+
<Popover
|
|
252
|
+
open={showMorePopup}
|
|
253
|
+
anchorEl={moreButtonAnchor}
|
|
254
|
+
onClose={handlePopupClose}
|
|
255
|
+
anchorOrigin={{
|
|
256
|
+
vertical: 'bottom',
|
|
257
|
+
horizontal: 'center',
|
|
258
|
+
}}
|
|
259
|
+
transformOrigin={{
|
|
260
|
+
vertical: 'top',
|
|
261
|
+
horizontal: 'center',
|
|
262
|
+
}}
|
|
263
|
+
style={{ marginTop: '10px' }}
|
|
264
|
+
>
|
|
265
|
+
<List className="popover-list">
|
|
266
|
+
{popupTabs.map(({ tab, originalIndex }) => {
|
|
267
|
+
if (!tab || !tab.name) {
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
270
|
+
return (
|
|
271
|
+
<ListItem
|
|
272
|
+
key={originalIndex}
|
|
273
|
+
button
|
|
274
|
+
onClick={() => handlePopupTabClick(originalIndex)}
|
|
275
|
+
className="popover-list-item"
|
|
276
|
+
>
|
|
277
|
+
<span className="popover-tab-name">
|
|
278
|
+
{tab.name}
|
|
279
|
+
</span>
|
|
280
|
+
<span className="popover-tab-count">
|
|
281
|
+
{tab.count || ''}
|
|
282
|
+
</span>
|
|
283
|
+
</ListItem>
|
|
284
|
+
);
|
|
285
|
+
})}
|
|
286
|
+
</List>
|
|
287
|
+
</Popover>
|
|
288
|
+
)}
|
|
289
|
+
</div>
|
|
79
290
|
</ThemeProvider>
|
|
80
291
|
);
|
|
81
292
|
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<svg width="4" height="16" viewBox="0 0 4 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<circle cx="1.8811" cy="2.4519" r="1.84937" fill="black"/>
|
|
3
|
+
<circle cx="1.8811" cy="2.4519" r="1.84937" fill="black"/>
|
|
4
|
+
<circle cx="1.8811" cy="2.4519" r="1.84937" fill="black"/>
|
|
5
|
+
<circle cx="1.8811" cy="2.4519" r="1.84937" fill="black"/>
|
|
6
|
+
<circle cx="1.8811" cy="2.4519" r="1.84937" fill="black"/>
|
|
7
|
+
<circle cx="1.8811" cy="7.99976" r="1.84937" fill="black"/>
|
|
8
|
+
<circle cx="1.8811" cy="7.99976" r="1.84937" fill="black"/>
|
|
9
|
+
<circle cx="1.8811" cy="7.99976" r="1.84937" fill="black"/>
|
|
10
|
+
<circle cx="1.8811" cy="7.99976" r="1.84937" fill="black"/>
|
|
11
|
+
<circle cx="1.8811" cy="7.99976" r="1.84937" fill="black"/>
|
|
12
|
+
<circle cx="1.8811" cy="13.5481" r="1.84937" fill="black"/>
|
|
13
|
+
<circle cx="1.8811" cy="13.5481" r="1.84937" fill="black"/>
|
|
14
|
+
<circle cx="1.8811" cy="13.5481" r="1.84937" fill="black"/>
|
|
15
|
+
<circle cx="1.8811" cy="13.5481" r="1.84937" fill="black"/>
|
|
16
|
+
<circle cx="1.8811" cy="13.5481" r="1.84937" fill="black"/>
|
|
17
|
+
</svg>
|