@bydata/react-supertabs 1.2.4 → 1.2.6
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/Readme.md +322 -0
- package/dist/SuperTabs.js +10 -3
- package/dist/TabContext.js +46 -10
- package/package.json +1 -1
- package/README.md +0 -262
package/Readme.md
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
# SuperTabs
|
|
2
|
+
|
|
3
|
+
`SuperTabs` is a production-ready React navigation library for managing parent tabs and nested sub-tabs in complex applications.
|
|
4
|
+
|
|
5
|
+
It provides a robust tab lifecycle with route integration, privilege-aware module access, and extensible UI controls for module-level workflows.
|
|
6
|
+
|
|
7
|
+
### Highlights
|
|
8
|
+
|
|
9
|
+
- Parent tab and sub-tab navigation with URL synchronization
|
|
10
|
+
- Drag-and-drop tab reordering
|
|
11
|
+
- Configurable module actions (`showAddButton` and `customAddButtons`)
|
|
12
|
+
- Pluggable handlers for open, close, reorder, and fetch operations
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 1) Setup
|
|
17
|
+
|
|
18
|
+
Wrap your app with `TabProvider`.
|
|
19
|
+
|
|
20
|
+
```jsx
|
|
21
|
+
import { TabProvider } from "components/SuperTabs/TabContext";
|
|
22
|
+
import { alertService } from "super-notifier";
|
|
23
|
+
|
|
24
|
+
<TabProvider
|
|
25
|
+
SITE_PREFIX={SITE_PREFIX}
|
|
26
|
+
SITE_PAGES={SITE_PAGES}
|
|
27
|
+
homeUrl="/app"
|
|
28
|
+
getUserDetails={getUser}
|
|
29
|
+
hasPrivilege={(privilege) => checkPrivilege(getUser().privileges, privilege)}
|
|
30
|
+
getTabs={getTabs}
|
|
31
|
+
handleOpenTab={handleOpenTab}
|
|
32
|
+
handleCloseTab={handleCloseTab}
|
|
33
|
+
handleReorderTabs={handleReorderTabs}
|
|
34
|
+
isDefaultExpanded={true}
|
|
35
|
+
alertService={alertService}
|
|
36
|
+
>
|
|
37
|
+
<Main />
|
|
38
|
+
</TabProvider>;
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Render tabs in header:
|
|
42
|
+
|
|
43
|
+
```jsx
|
|
44
|
+
import SuperTabs from "components/SuperTabs/SuperTabs";
|
|
45
|
+
|
|
46
|
+
<header>
|
|
47
|
+
<SuperTabs />
|
|
48
|
+
</header>;
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## 2) `TabProvider` props
|
|
54
|
+
|
|
55
|
+
### Required
|
|
56
|
+
|
|
57
|
+
- `SITE_PREFIX: string`
|
|
58
|
+
- `SITE_PAGES: Array<SitePage>`
|
|
59
|
+
|
|
60
|
+
`SitePage` is typically:
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
{
|
|
64
|
+
id: 2,
|
|
65
|
+
title: "Recruiter",
|
|
66
|
+
url: "/app/recruiter/f-home",
|
|
67
|
+
privilege: "RECRUITER",
|
|
68
|
+
containsSubTabs: true,
|
|
69
|
+
showAddButton: true, // boolean or privilege array
|
|
70
|
+
customAddButtons: [
|
|
71
|
+
{ id: 1, name: "Data Grid", privilege: "RECRUITER" },
|
|
72
|
+
{ id: 2, name: "Trend Master", privilege: "RECRUITER" }
|
|
73
|
+
],
|
|
74
|
+
showInMobile: true, // optional
|
|
75
|
+
isExternal: false // optional
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Optional
|
|
80
|
+
|
|
81
|
+
- `homeUrl` (default: `/`)
|
|
82
|
+
- `entityIdentifierKey` (default: `organization_id`)
|
|
83
|
+
- `persistTabsAfterLogin` (default: `false`)
|
|
84
|
+
- `preventHomeRedirect` (default: `false`)
|
|
85
|
+
- When `true`, clicking home opens module menu instead of redirecting.
|
|
86
|
+
- `retainTabsAfterEntityChange` (default: `false`)
|
|
87
|
+
- `shouldRedirectToSubTab` (default: `true`)
|
|
88
|
+
- `isMobileView` (default: `false`)
|
|
89
|
+
- `isDefaultExpanded` (default: `false`)
|
|
90
|
+
- `getUserDetails` (function)
|
|
91
|
+
- `hasPrivilege` (function)
|
|
92
|
+
- `getTabs` (async function)
|
|
93
|
+
- `handleOpenTab` (async function)
|
|
94
|
+
- `handleCloseTab` (async function)
|
|
95
|
+
- `handleReorderTabs` (async function)
|
|
96
|
+
- `alertService` object with optional methods: `success`, `warning`, `error`, `info`, `alert`
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## 3) API callback contracts
|
|
101
|
+
|
|
102
|
+
### `getTabs({ payload, controller })`
|
|
103
|
+
|
|
104
|
+
Expected shape:
|
|
105
|
+
|
|
106
|
+
```js
|
|
107
|
+
{
|
|
108
|
+
status: 1,
|
|
109
|
+
tabs: [], // flat backend tabs with tab_info
|
|
110
|
+
current_tab: {} | null, // current tab from backend
|
|
111
|
+
hasNoTabs: false // true when backend has no open tabs
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### `handleOpenTab(payload[, controller])`
|
|
116
|
+
|
|
117
|
+
Expected shape:
|
|
118
|
+
|
|
119
|
+
```js
|
|
120
|
+
{
|
|
121
|
+
status: 1,
|
|
122
|
+
tabId: 123,
|
|
123
|
+
children: [] // optional child tabs
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### `handleCloseTab(tabId)`
|
|
128
|
+
|
|
129
|
+
Expected shape:
|
|
130
|
+
|
|
131
|
+
```js
|
|
132
|
+
{
|
|
133
|
+
status: 1,
|
|
134
|
+
current_tab_id: 456 // optional
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### `handleReorderTabs(payload)`
|
|
139
|
+
|
|
140
|
+
Expected shape:
|
|
141
|
+
|
|
142
|
+
```js
|
|
143
|
+
{
|
|
144
|
+
status: 1;
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## 4) Opening tabs
|
|
151
|
+
|
|
152
|
+
### Option 1: `SuperLink`
|
|
153
|
+
|
|
154
|
+
```jsx
|
|
155
|
+
import SuperLink from "components/SuperTabs/SuperLink";
|
|
156
|
+
|
|
157
|
+
<SuperLink tab={page} isSubTab={false}>
|
|
158
|
+
{page.title}
|
|
159
|
+
</SuperLink>;
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Open external links:
|
|
163
|
+
|
|
164
|
+
```jsx
|
|
165
|
+
<SuperLink tab={{ ...page, isExternal: true }} isSubTab={false} isExternal>
|
|
166
|
+
{page.title}
|
|
167
|
+
</SuperLink>
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Option 2: `openSuperTabOnRowClick`
|
|
171
|
+
|
|
172
|
+
```jsx
|
|
173
|
+
import { useTabContext } from "components/SuperTabs/TabContext";
|
|
174
|
+
|
|
175
|
+
const { openSuperTabOnRowClick } = useTabContext();
|
|
176
|
+
|
|
177
|
+
function handleRowClick(row) {
|
|
178
|
+
openSuperTabOnRowClick({
|
|
179
|
+
tab: {
|
|
180
|
+
id: row.id,
|
|
181
|
+
name: row.name,
|
|
182
|
+
url: `/app/recruiter/f-${row.id}`,
|
|
183
|
+
},
|
|
184
|
+
isSubTab: true, // defaults to true
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## 5) Tab object notes
|
|
192
|
+
|
|
193
|
+
Common fields:
|
|
194
|
+
|
|
195
|
+
- `id`
|
|
196
|
+
- `url`
|
|
197
|
+
- one of `name` / `title`
|
|
198
|
+
|
|
199
|
+
Optional:
|
|
200
|
+
|
|
201
|
+
- `showAvatar`, `firstName`, `lastName`, `imgSrc` or `img_url`
|
|
202
|
+
- `tooltip` for hover text; falls back to tab title/name when empty
|
|
203
|
+
- `isExternal`
|
|
204
|
+
- any extra metadata your module uses
|
|
205
|
+
|
|
206
|
+
```js
|
|
207
|
+
{
|
|
208
|
+
id: 100,
|
|
209
|
+
url: "/app/profile/100",
|
|
210
|
+
tooltip: "Profile: John Doe",
|
|
211
|
+
firstName: "John",
|
|
212
|
+
lastName: "Doe",
|
|
213
|
+
showAvatar: true,
|
|
214
|
+
imgSrc: "https://cdn.example.com/u100.png"
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## 6) Add button callbacks
|
|
221
|
+
|
|
222
|
+
Bind module add callbacks using emitter:
|
|
223
|
+
|
|
224
|
+
```jsx
|
|
225
|
+
import { useCallback, useEffect } from "react";
|
|
226
|
+
import { emitter } from "components/SuperTabs/SuperTabs";
|
|
227
|
+
import { useTabContext } from "components/SuperTabs/TabContext";
|
|
228
|
+
|
|
229
|
+
const { openSuperTabOnRowClick } = useTabContext();
|
|
230
|
+
|
|
231
|
+
const handleTabAddBtn = useCallback(
|
|
232
|
+
(customButtonData) => {
|
|
233
|
+
// customButtonData is passed when using customAddButtons
|
|
234
|
+
openSuperTabOnRowClick({
|
|
235
|
+
tab: {
|
|
236
|
+
id: 123,
|
|
237
|
+
name: "New Funnel",
|
|
238
|
+
url: "/app/recruiter/f-123",
|
|
239
|
+
},
|
|
240
|
+
isSubTab: true,
|
|
241
|
+
});
|
|
242
|
+
},
|
|
243
|
+
[openSuperTabOnRowClick],
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
useEffect(() => {
|
|
247
|
+
const unsub = emitter.emit("bindCallback", {
|
|
248
|
+
id: 2, // same parent module id from SITE_PAGES
|
|
249
|
+
callback: handleTabAddBtn,
|
|
250
|
+
});
|
|
251
|
+
return unsub;
|
|
252
|
+
}, [handleTabAddBtn]);
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## 7) Close cleanup event
|
|
258
|
+
|
|
259
|
+
```jsx
|
|
260
|
+
import { useEffect } from "react";
|
|
261
|
+
import { emitter } from "components/SuperTabs/SuperTabs";
|
|
262
|
+
|
|
263
|
+
useEffect(() => {
|
|
264
|
+
const unsub = emitter.subscribe(
|
|
265
|
+
"superTabClose",
|
|
266
|
+
({ appId, isSubTab, tab }) => {
|
|
267
|
+
if (appId === 2 && isSubTab) {
|
|
268
|
+
// cleanup here
|
|
269
|
+
}
|
|
270
|
+
},
|
|
271
|
+
);
|
|
272
|
+
return unsub;
|
|
273
|
+
}, []);
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## 8) Not Available page
|
|
279
|
+
|
|
280
|
+
```jsx
|
|
281
|
+
import NotAvailable from "components/SuperTabs/NotAvailable";
|
|
282
|
+
|
|
283
|
+
<NotAvailable subTabName="Profile" />;
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## 9) Keep-alive (optional)
|
|
289
|
+
|
|
290
|
+
Use shared keep-alive utility:
|
|
291
|
+
|
|
292
|
+
```jsx
|
|
293
|
+
import { withKeepAlive } from "components/KeepAlive";
|
|
294
|
+
|
|
295
|
+
const Component = () => <div>Component</div>;
|
|
296
|
+
export default withKeepAlive(Component, 2); // module id
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Cleanup keep-alive cache on parent tab close:
|
|
300
|
+
|
|
301
|
+
```jsx
|
|
302
|
+
import { useEffect } from "react";
|
|
303
|
+
import { useAliveScope } from "components/KeepAlive";
|
|
304
|
+
import { emitter } from "components/SuperTabs/SuperTabs";
|
|
305
|
+
|
|
306
|
+
const { drop } = useAliveScope();
|
|
307
|
+
|
|
308
|
+
useEffect(() => {
|
|
309
|
+
const unsub = emitter.subscribe("superTabClose", ({ appId, isSubTab }) => {
|
|
310
|
+
if (appId && !isSubTab) {
|
|
311
|
+
drop(appId);
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
return unsub;
|
|
315
|
+
}, [drop]);
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
## 10) Styling note
|
|
321
|
+
|
|
322
|
+
Home tab logo can be styled with `.brand-logo`.
|
package/dist/SuperTabs.js
CHANGED
|
@@ -56,6 +56,13 @@ var tooltipStyles = {
|
|
|
56
56
|
backgroundColor: 'var(--tab-background-overlay, #272B32)',
|
|
57
57
|
color: 'var(--tab-text-normal, #BDC3CC)'
|
|
58
58
|
};
|
|
59
|
+
var getTooltipContent = function getTooltipContent(item, fallbackTitle) {
|
|
60
|
+
var customTooltip = item === null || item === void 0 ? void 0 : item.tooltip;
|
|
61
|
+
if (typeof customTooltip === 'string' && customTooltip.trim()) {
|
|
62
|
+
return customTooltip.trim();
|
|
63
|
+
}
|
|
64
|
+
return fallbackTitle;
|
|
65
|
+
};
|
|
59
66
|
var filterSubTabs = function filterSubTabs(subTabs) {
|
|
60
67
|
// const subTabsInList = [];
|
|
61
68
|
var subTabsInNavbar = [];
|
|
@@ -884,7 +891,7 @@ var SuperTabs = function SuperTabs(_ref) {
|
|
|
884
891
|
className: 'child-tabs-wrapper tab-' + tab.id + (showSubTabs ? ' expanded' : '')
|
|
885
892
|
// (isActive ? ' active' : '')
|
|
886
893
|
}, /*#__PURE__*/_react["default"].createElement(_reactHoverText["default"], {
|
|
887
|
-
content: tab.title,
|
|
894
|
+
content: getTooltipContent(tab, tab.title),
|
|
888
895
|
delay: 500,
|
|
889
896
|
followCursor: true,
|
|
890
897
|
placement: "bottom",
|
|
@@ -1002,7 +1009,7 @@ var SuperTabs = function SuperTabs(_ref) {
|
|
|
1002
1009
|
isDragDisabled: isMobileScreen
|
|
1003
1010
|
}, function (provided, snapshot) {
|
|
1004
1011
|
return /*#__PURE__*/_react["default"].createElement(_reactHoverText["default"], {
|
|
1005
|
-
content: tab.title,
|
|
1012
|
+
content: getTooltipContent(tab, tab.title),
|
|
1006
1013
|
delay: 500,
|
|
1007
1014
|
followCursor: true,
|
|
1008
1015
|
placement: "bottom",
|
|
@@ -1186,7 +1193,7 @@ var SubTab = exports.SubTab = function SubTab(_ref0) {
|
|
|
1186
1193
|
index: subIndex
|
|
1187
1194
|
}, function (subprovided, snapshot) {
|
|
1188
1195
|
return /*#__PURE__*/_react["default"].createElement(_reactHoverText["default"], {
|
|
1189
|
-
content: title,
|
|
1196
|
+
content: getTooltipContent(subTab, title),
|
|
1190
1197
|
delay: 500,
|
|
1191
1198
|
followCursor: true,
|
|
1192
1199
|
show: showTooltip,
|
package/dist/TabContext.js
CHANGED
|
@@ -293,6 +293,8 @@ function TabProvider(_ref9) {
|
|
|
293
293
|
setTabModePreference = _useState26[1];
|
|
294
294
|
var previousEntityId = (0, _usePrevious["default"])(currentEntityId);
|
|
295
295
|
var closeTabFailedRequestsRef = (0, _react.useRef)({});
|
|
296
|
+
/** AbortController per client tab id for in-flight open_tab API */
|
|
297
|
+
var openTabAbortByClientIdRef = (0, _react.useRef)({});
|
|
296
298
|
(0, _react.useEffect)(function () {
|
|
297
299
|
localStorage.setItem(SITE_PREFIX + "tab_mode_preference", JSON.stringify(tabModePreference));
|
|
298
300
|
}, [tabModePreference]);
|
|
@@ -570,6 +572,7 @@ function TabProvider(_ref9) {
|
|
|
570
572
|
isSubTab,
|
|
571
573
|
_ref16$keepInactive,
|
|
572
574
|
keepInactive,
|
|
575
|
+
openTabAbortController,
|
|
573
576
|
_payload$tab_info,
|
|
574
577
|
isEditMode,
|
|
575
578
|
unique_identifier,
|
|
@@ -586,6 +589,8 @@ function TabProvider(_ref9) {
|
|
|
586
589
|
payload,
|
|
587
590
|
storedExpandedIds,
|
|
588
591
|
isExpanded,
|
|
592
|
+
clientTabId,
|
|
593
|
+
previousOpenAbort,
|
|
589
594
|
response,
|
|
590
595
|
_tabId,
|
|
591
596
|
newTab,
|
|
@@ -595,6 +600,8 @@ function TabProvider(_ref9) {
|
|
|
595
600
|
while (1) switch (_context5.p = _context5.n) {
|
|
596
601
|
case 0:
|
|
597
602
|
_ref16 = _args5.length > 0 && _args5[0] !== undefined ? _args5[0] : {}, tab = _ref16.tab, _ref16$isHomeTab = _ref16.isHomeTab, isHomeTab = _ref16$isHomeTab === void 0 ? false : _ref16$isHomeTab, _ref16$isSubTab = _ref16.isSubTab, isSubTab = _ref16$isSubTab === void 0 ? false : _ref16$isSubTab, _ref16$keepInactive = _ref16.keepInactive, keepInactive = _ref16$keepInactive === void 0 ? false : _ref16$keepInactive;
|
|
603
|
+
// return;
|
|
604
|
+
openTabAbortController = null;
|
|
598
605
|
_context5.p = 1;
|
|
599
606
|
if (!isSubTab) {
|
|
600
607
|
updateActiveTab(tab);
|
|
@@ -643,10 +650,16 @@ function TabProvider(_ref9) {
|
|
|
643
650
|
}
|
|
644
651
|
}
|
|
645
652
|
(_payload$tab_info = payload.tab_info) === null || _payload$tab_info === void 0 || delete _payload$tab_info.isClientChanged;
|
|
646
|
-
|
|
653
|
+
clientTabId = tab.id;
|
|
654
|
+
previousOpenAbort = openTabAbortByClientIdRef.current[clientTabId];
|
|
655
|
+
if (previousOpenAbort) {
|
|
656
|
+
previousOpenAbort.abort();
|
|
657
|
+
}
|
|
658
|
+
openTabAbortController = new AbortController();
|
|
659
|
+
openTabAbortByClientIdRef.current[clientTabId] = openTabAbortController;
|
|
647
660
|
currentOpenTabCallerRef.current = payload.tab_info;
|
|
648
661
|
_context5.n = 3;
|
|
649
|
-
return handleOpenTab(payload);
|
|
662
|
+
return handleOpenTab(payload, openTabAbortController);
|
|
650
663
|
case 3:
|
|
651
664
|
response = _context5.v;
|
|
652
665
|
if (!(response.status === 1)) {
|
|
@@ -749,12 +762,19 @@ function TabProvider(_ref9) {
|
|
|
749
762
|
case 9:
|
|
750
763
|
_context5.p = 9;
|
|
751
764
|
_t = _context5.v;
|
|
752
|
-
if (
|
|
753
|
-
|
|
765
|
+
if (_t instanceof DOMException && _t.name === 'AbortError') {
|
|
766
|
+
// Tab closed or superseded while open was in progress
|
|
767
|
+
} else {
|
|
768
|
+
if (window.isDebugOn) {
|
|
769
|
+
alertService === null || alertService === void 0 || alertService.error(_t.message ? _t.message : 'Error in opening tab!');
|
|
770
|
+
}
|
|
771
|
+
console.log("openTab - error", _t);
|
|
754
772
|
}
|
|
755
|
-
console.log("openTab - error", _t);
|
|
756
773
|
case 10:
|
|
757
774
|
_context5.p = 10;
|
|
775
|
+
if (openTabAbortController != null && openTabAbortByClientIdRef.current[tab.id] === openTabAbortController) {
|
|
776
|
+
delete openTabAbortByClientIdRef.current[tab.id];
|
|
777
|
+
}
|
|
758
778
|
currentOpenTabCallerRef.current = null;
|
|
759
779
|
return _context5.f(10);
|
|
760
780
|
case 11:
|
|
@@ -1094,8 +1114,10 @@ function TabProvider(_ref9) {
|
|
|
1094
1114
|
tab,
|
|
1095
1115
|
_ref25$isSubTab,
|
|
1096
1116
|
isSubTab,
|
|
1097
|
-
|
|
1117
|
+
pendingOpenAbort,
|
|
1098
1118
|
_activeSubTabRef$curr5,
|
|
1119
|
+
response,
|
|
1120
|
+
_activeSubTabRef$curr6,
|
|
1099
1121
|
tabId,
|
|
1100
1122
|
updatedTabModePreference,
|
|
1101
1123
|
_args8 = arguments,
|
|
@@ -1231,7 +1253,21 @@ function TabProvider(_ref9) {
|
|
|
1231
1253
|
break;
|
|
1232
1254
|
}
|
|
1233
1255
|
console.log('Tab id not found', tab);
|
|
1234
|
-
|
|
1256
|
+
pendingOpenAbort = openTabAbortByClientIdRef.current[tab.id];
|
|
1257
|
+
if (pendingOpenAbort) {
|
|
1258
|
+
pendingOpenAbort.abort();
|
|
1259
|
+
delete openTabAbortByClientIdRef.current[tab.id];
|
|
1260
|
+
}
|
|
1261
|
+
if (isSubTab && activeSubTabRef.current) {
|
|
1262
|
+
if (isSubTab && ((_activeSubTabRef$curr5 = activeSubTabRef.current) === null || _activeSubTabRef$curr5 === void 0 ? void 0 : _activeSubTabRef$curr5.open_order) > 2) {
|
|
1263
|
+
updateCurrentTab({
|
|
1264
|
+
tab: activeSubTabRef.current,
|
|
1265
|
+
isSubTab: true,
|
|
1266
|
+
parentId: activeTabRef.current.id
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1269
|
+
activeSubTabRef.current.open_order = 1;
|
|
1270
|
+
}
|
|
1235
1271
|
return _context8.a(2);
|
|
1236
1272
|
case 2:
|
|
1237
1273
|
_context8.n = 3;
|
|
@@ -1245,7 +1281,7 @@ function TabProvider(_ref9) {
|
|
|
1245
1281
|
console.log('closeTab - response.current_tab_id', response.current_tab_id, activeSubTabRef.current);
|
|
1246
1282
|
// if (isSubTab && response.current_tab_id !== activeSubTabRef.current?.tabId ) {
|
|
1247
1283
|
if (isSubTab && activeSubTabRef.current) {
|
|
1248
|
-
if (isSubTab && ((_activeSubTabRef$
|
|
1284
|
+
if (isSubTab && ((_activeSubTabRef$curr6 = activeSubTabRef.current) === null || _activeSubTabRef$curr6 === void 0 ? void 0 : _activeSubTabRef$curr6.open_order) > 2) {
|
|
1249
1285
|
updateCurrentTab({
|
|
1250
1286
|
tab: activeSubTabRef.current,
|
|
1251
1287
|
isSubTab: true,
|
|
@@ -1486,7 +1522,7 @@ function TabProvider(_ref9) {
|
|
|
1486
1522
|
if (currentEntityId != null && currentEntityId != undefined) {
|
|
1487
1523
|
var fetchTabDetails = /*#__PURE__*/function () {
|
|
1488
1524
|
var _ref29 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee10() {
|
|
1489
|
-
var href, orgId, payload, response, _response$current_tab, _response$current_tab2, _response$current_tab4, pathName, isHomeTab, tabsOutputWithoutDuplicates, tabsOutput, _activeTab, _activeSubTab, currentActiveTab, _activeTab2, _response$current_tab3, tab_info, rest, flattenedObj, isCurrentTabOpen, openedParentTab, updatedParentTab, flattenedParentObj, parentTab, _activeSubTabRef$
|
|
1525
|
+
var href, orgId, payload, response, _response$current_tab, _response$current_tab2, _response$current_tab4, pathName, isHomeTab, tabsOutputWithoutDuplicates, tabsOutput, _activeTab, _activeSubTab, currentActiveTab, _activeTab2, _response$current_tab3, tab_info, rest, flattenedObj, isCurrentTabOpen, openedParentTab, updatedParentTab, flattenedParentObj, parentTab, _activeSubTabRef$curr7, parentTabFromData, _flattenedParentObj, _tab_info, _rest, _flattenedObj, isSubTab, parent, parentTabInfo, parentRest, _flattenedParentObj2, tabsMap, restTabs, id, _tabsMap$id, _activeSubTab$tabId, _activeSubTab2, _activeTab3, tabId, isEditMode, tempTabModePreference, updated, _t8;
|
|
1490
1526
|
return _regenerator().w(function (_context10) {
|
|
1491
1527
|
while (1) switch (_context10.p = _context10.n) {
|
|
1492
1528
|
case 0:
|
|
@@ -1617,7 +1653,7 @@ function TabProvider(_ref9) {
|
|
|
1617
1653
|
name: 'Loading...',
|
|
1618
1654
|
title: ''
|
|
1619
1655
|
});
|
|
1620
|
-
if ((_activeSubTabRef$
|
|
1656
|
+
if ((_activeSubTabRef$curr7 = activeSubTabRef.current) !== null && _activeSubTabRef$curr7 !== void 0 && _activeSubTabRef$curr7.tabId) _activeSubTab.tabId = activeSubTabRef.current.tabId;
|
|
1621
1657
|
case 10:
|
|
1622
1658
|
_context10.n = 12;
|
|
1623
1659
|
break;
|
package/package.json
CHANGED
package/README.md
DELETED
|
@@ -1,262 +0,0 @@
|
|
|
1
|
-
# React SuperTabs
|
|
2
|
-
|
|
3
|
-
**SuperTabs** is a powerful and user-friendly tab management component. It enables seamless navigation by opening modules as tabs, with support for sub-tabs, smooth transitions. An experimental _keep-alive_ feature ensures components stay active across tab switches for enhanced usability.
|
|
4
|
-
|
|
5
|
-
### SuperTabs Features
|
|
6
|
-
------
|
|
7
|
-
- **Tab-based Navigation**: Open modules as tabs for an intuitive and efficient navigation experience.
|
|
8
|
-
- **Sub-Tabs Support**: Create nested sub-tabs within a main tab for hierarchical organization.
|
|
9
|
-
- **Smooth Transitions**: Enjoy a polished user experience with fluid tab-switching animations.
|
|
10
|
-
- **Keep-Alive (Experimental)**: Maintain the state of components across tab switches for seamless interaction (experimental feature).
|
|
11
|
-
|
|
12
|
-
### Installation
|
|
13
|
-
------
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
npm install @bydata/react-supertabs
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
### Usage
|
|
20
|
-
------
|
|
21
|
-
|
|
22
|
-
#### Basic
|
|
23
|
-
|
|
24
|
-
>**Import TabProvider and wrap it around the main wrapper**
|
|
25
|
-
|
|
26
|
-
- This is needed to access useTabContext.
|
|
27
|
-
- Make sure to pass `SITE_PREFIX` and `SITE_PAGES` (From Navigation.js)
|
|
28
|
-
- `homeUrl` is optional and defaults to "/"
|
|
29
|
-
- `persistTabsAfterLogin` is optional and defaults to "false"
|
|
30
|
-
- Used to preserve tab state upon login.
|
|
31
|
-
- `entityIdentifierKey` is optional and defaults to 'organization_id',
|
|
32
|
-
- Used to identify the organization / client id
|
|
33
|
-
- `preventHomeRedirect` is optional and defaults to false,
|
|
34
|
-
- This will open a menu on click of home button instead of redirecting
|
|
35
|
-
- `handleOpenTab` async function to open the tab in backend
|
|
36
|
-
- expects the following keys in return
|
|
37
|
-
- { status: 1, tabId, children: undefined }
|
|
38
|
-
- `handleCloseTab` async function to open the tab in backend
|
|
39
|
-
- expects the following keys in return
|
|
40
|
-
- { status: 1, current_tab_id: 123 }
|
|
41
|
-
- `handleReorderTabs` async function to open the tab in backend
|
|
42
|
-
- { status: 1 }
|
|
43
|
-
- `getTabs` async function to open the tab in backend async () => { },
|
|
44
|
-
- expects the following keys in return
|
|
45
|
-
- { status: 1, tabs: [], current_tab: {} || null, hasNoTabs }
|
|
46
|
-
- hasNoTabs = user for whom there are no tabs open
|
|
47
|
-
- `getUserDetails` function which return the user details from user token
|
|
48
|
-
- `hasPrivilege` function which checks if the user has the privilege passed in function param
|
|
49
|
-
- `alertService` An object containing different methods for displaying toast alerts.
|
|
50
|
-
- can call success, warning, error, info, alert methods.
|
|
51
|
-
- super-notifier - NPM package can be used here
|
|
52
|
-
- https://www.npmjs.com/package/super-notifier
|
|
53
|
-
|
|
54
|
-
```
|
|
55
|
-
Eg.
|
|
56
|
-
const SITE_PREFIX = 'teamLink_'
|
|
57
|
-
const API_BASE_URL = 'https://api.teamlink.com/data_stream'
|
|
58
|
-
const SITE_PAGES = [{
|
|
59
|
-
title: "Organization",
|
|
60
|
-
url: "/app/organization",
|
|
61
|
-
privilege: "ORGANIZATION",
|
|
62
|
-
id: 1,
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
title: "Recruiter",
|
|
66
|
-
url: "/app/recruiter/f-home",
|
|
67
|
-
privilege: "RECRUITER",
|
|
68
|
-
showAddButton: true,
|
|
69
|
-
customAddButtons: [
|
|
70
|
-
{
|
|
71
|
-
name: "Data Grid",
|
|
72
|
-
id: 1,
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
name: "Trend Master",
|
|
76
|
-
id: 2,
|
|
77
|
-
}
|
|
78
|
-
],
|
|
79
|
-
containsSubTabs: true,
|
|
80
|
-
id: 2,
|
|
81
|
-
}]
|
|
82
|
-
|
|
83
|
-
// showAddButton - can be added if the module needs plus button.
|
|
84
|
-
// containsSubTabs - can be added if the tab includes sub tabs.
|
|
85
|
-
// customAddButtons - These buttons will be added in the sub tab list. Clicking on the button will trigger the binded callback with object as param.
|
|
86
|
-
|
|
87
|
-
import { TabProvider } from '@bydata/react-supertabs';
|
|
88
|
-
<TabProvider
|
|
89
|
-
SITE_PREFIX={SITE_PREFIX}
|
|
90
|
-
SITE_PAGES={SITE_PAGES}
|
|
91
|
-
homeUrl="/app"
|
|
92
|
-
getTabs={getTabs}
|
|
93
|
-
handleOpenTab={handleOpenTab}
|
|
94
|
-
handleCloseTab={handleCloseTab}
|
|
95
|
-
handleReorderTabs={handleReorderTabs}
|
|
96
|
-
alertService={alertService}
|
|
97
|
-
>
|
|
98
|
-
<Main />
|
|
99
|
-
</TabProvider>
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
>**Render the component within the App's header**
|
|
103
|
-
```
|
|
104
|
-
import { SuperTabs, useTabContext } from '@bydata/react-supertabs';
|
|
105
|
-
|
|
106
|
-
<header>
|
|
107
|
-
<SuperTabs />
|
|
108
|
-
</header>
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
>**Wrap the hyperlink as follows:**
|
|
112
|
-
|
|
113
|
-
Each tab should contain all the necessary information to be stored.
|
|
114
|
-
|
|
115
|
-
Mandatory Fields:
|
|
116
|
-
- `id`
|
|
117
|
-
- `name`
|
|
118
|
-
- `url`
|
|
119
|
-
|
|
120
|
-
For Avatar:
|
|
121
|
-
- `showAvatar` must be set to `true`
|
|
122
|
-
- The following fields must be provided:
|
|
123
|
-
- `firstName`
|
|
124
|
-
- `lastName`
|
|
125
|
-
- `imgSrc`
|
|
126
|
-
|
|
127
|
-
```
|
|
128
|
-
// Tab object Examples
|
|
129
|
-
|
|
130
|
-
{
|
|
131
|
-
id: 1,
|
|
132
|
-
name: 'title',
|
|
133
|
-
url: '/recruiter/f-1`
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
{
|
|
137
|
-
id: 100,
|
|
138
|
-
url: '/profile/100`
|
|
139
|
-
firstName: 'John',
|
|
140
|
-
lastName: 'Doe',
|
|
141
|
-
showAvatar: true,
|
|
142
|
-
imgSrc: img_url,
|
|
143
|
-
department,
|
|
144
|
-
designation,
|
|
145
|
-
department_id,
|
|
146
|
-
}
|
|
147
|
-
```
|
|
148
|
-
```
|
|
149
|
-
import { SuperLink } from '@bydata/react-supertabs';
|
|
150
|
-
<SuperLink tab={page} isSubTab={false}>
|
|
151
|
-
{page.title}
|
|
152
|
-
</SuperLink>
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
OR
|
|
156
|
-
|
|
157
|
-
```
|
|
158
|
-
import { useTabContext } from '@bydata/react-supertabs';
|
|
159
|
-
const { openSuperTabOnRowClick } = useTabContext();
|
|
160
|
-
|
|
161
|
-
function handleRowClick(e) {
|
|
162
|
-
const tab = { id: e.id, name: e.name, url: `/recruiter/f-${e.id}` };
|
|
163
|
-
// isSubTab defaults to true
|
|
164
|
-
openSuperTabOnRowClick({ tab, isSubTab });
|
|
165
|
-
}
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
>**Bind the callback for the plus button to each module during the initial render.**
|
|
169
|
-
|
|
170
|
-
If a module requires a plus button for adding a new tab, you can include the following:
|
|
171
|
-
```
|
|
172
|
-
import { emitter, useTabContext } from '@bydata/react-supertabs';
|
|
173
|
-
|
|
174
|
-
const { openSuperTabOnRowClick } = useTabContext();
|
|
175
|
-
|
|
176
|
-
const handleTabAddBtn = useCallback(() => {
|
|
177
|
-
openSuperTabOnRowClick({
|
|
178
|
-
tab: {
|
|
179
|
-
id: 123,
|
|
180
|
-
name: `New Funnel`,
|
|
181
|
-
url: `/recruiter/f-${123}`,
|
|
182
|
-
},
|
|
183
|
-
isSubTab: true,
|
|
184
|
-
});
|
|
185
|
-
}, [openSuperTabOnRowClick]);
|
|
186
|
-
|
|
187
|
-
useEffect(() => {
|
|
188
|
-
const unsub = emitter.emit("bindCallback", {
|
|
189
|
-
id: 2, // Same id provided for this module in navigation.js
|
|
190
|
-
callback: handleTabAddBtn,
|
|
191
|
-
});
|
|
192
|
-
return unsub;
|
|
193
|
-
}, [handleTabAddBtn]);
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
> Note: The logo of the home tab can be updated by targeting the `brand-logo` class.
|
|
197
|
-
|
|
198
|
-
-----
|
|
199
|
-
|
|
200
|
-
#### Advanced
|
|
201
|
-
|
|
202
|
-
>**Cleanup**
|
|
203
|
-
|
|
204
|
-
When a tab is closed, it may be necessary to clean up some data. The `superTabClose` event can be utilized for this purpose.
|
|
205
|
-
|
|
206
|
-
```
|
|
207
|
-
import { emitter } from '@bydata/react-supertabs';
|
|
208
|
-
|
|
209
|
-
useEffect(() => {
|
|
210
|
-
const unsub = emitter.subscribe('superTabClose', ({ appId, isSubTab, tab }) => {
|
|
211
|
-
// Same id provided for this module in navigation.js
|
|
212
|
-
if (appId === 2 && isSubTab) {
|
|
213
|
-
// handle clean up here
|
|
214
|
-
}
|
|
215
|
-
})
|
|
216
|
-
return unsub;
|
|
217
|
-
}, []);
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
>**Not Available page**
|
|
221
|
-
- If a profile is deleted, the tab will remain open. To address this, we can display a "Page Not Available" message.
|
|
222
|
-
|
|
223
|
-
```
|
|
224
|
-
import { NotAvailable } from '@bydata/react-supertabs';
|
|
225
|
-
|
|
226
|
-
<NotAvailable subTabName='Profile' />
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
>**Keep Alive (Experimental)**
|
|
230
|
-
|
|
231
|
-
```
|
|
232
|
-
import { withKeepAlive } from '@bydata/react-supertabs';
|
|
233
|
-
|
|
234
|
-
// Same id provided for this module in navigation.js
|
|
235
|
-
export default withKeepAlive(Component, Unique ID);
|
|
236
|
-
|
|
237
|
-
Eg.
|
|
238
|
-
const Component = () => {
|
|
239
|
-
return <div>Component</div>
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
export default withKeepAlive(Component, 2);
|
|
243
|
-
```
|
|
244
|
-
Keep Alive Cleanup
|
|
245
|
-
```
|
|
246
|
-
// This can be incorporated into a component that is always rendered, such as the header.
|
|
247
|
-
|
|
248
|
-
import { useAliveScope, emitter } from '@bydata/react-supertabs';
|
|
249
|
-
|
|
250
|
-
const { drop } = useAliveScope();
|
|
251
|
-
|
|
252
|
-
useEffect(() => {
|
|
253
|
-
const unsub = emitter.subscribe('superTabClose', (e) => {
|
|
254
|
-
const { appId, isSubTab } = e;
|
|
255
|
-
if (appId && !isSubTab) {
|
|
256
|
-
// drop the cache of the tab
|
|
257
|
-
drop(appId);
|
|
258
|
-
}
|
|
259
|
-
})
|
|
260
|
-
return unsub;
|
|
261
|
-
}, []);
|
|
262
|
-
```
|