@bydata/react-supertabs 1.1.9 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Readme.md +248 -186
- package/dist/TabContext.js +2 -2
- package/package.json +1 -1
package/dist/Readme.md
CHANGED
|
@@ -1,258 +1,320 @@
|
|
|
1
|
-
|
|
2
1
|
# SuperTabs
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
### SuperTabs Features
|
|
7
|
-
------
|
|
8
|
-
- **Tab-based Navigation**: Open modules as tabs for an intuitive and efficient navigation experience.
|
|
9
|
-
- **Sub-Tabs Support**: Create nested sub-tabs within a main tab for hierarchical organization.
|
|
10
|
-
- **Smooth Transitions**: Enjoy a polished user experience with fluid tab-switching animations.
|
|
11
|
-
- **Keep-Alive (Experimental)**: Maintain the state of components across tab switches for seamless interaction (experimental feature).
|
|
12
|
-
|
|
13
|
-
### Installation and usage
|
|
14
|
-
------
|
|
15
|
-
|
|
16
|
-
#### Basic
|
|
17
|
-
|
|
18
|
-
>**Import TabProvider and wrap it around the main wrapper**
|
|
19
|
-
|
|
20
|
-
- This is needed to access useTabContext.
|
|
21
|
-
- Make sure to pass `SITE_PREFIX` and `SITE_PAGES` (From Navigation.js)
|
|
22
|
-
- `homeUrl` is optional and defaults to "/"
|
|
23
|
-
- `persistTabsAfterLogin` is optional and defaults to "false"
|
|
24
|
-
- Used to preserve tab state upon login.
|
|
25
|
-
- `entityIdentifierKey` is optional and defaults to 'organization_id',
|
|
26
|
-
- Used to identify the organization / client id
|
|
27
|
-
- `preventHomeRedirect` is optional and defaults to false,
|
|
28
|
-
- This will open a menu on click of home button instead of redirecting
|
|
29
|
-
- `handleOpenTab` async function to open the tab in backend
|
|
30
|
-
- expects the following keys in return
|
|
31
|
-
- { status: 1, tabId, children: undefined }
|
|
32
|
-
- `handleCloseTab` async function to open the tab in backend
|
|
33
|
-
- expects the following keys in return
|
|
34
|
-
- { status: 1, current_tab_id: 123 }
|
|
35
|
-
- `handleReorderTabs` async function to open the tab in backend
|
|
36
|
-
- { status: 1 }
|
|
37
|
-
- `getTabs` async function to open the tab in backend async () => { },
|
|
38
|
-
- expects the following keys in return
|
|
39
|
-
- { status: 1, tabs: [], current_tab: {} || null, hasNoTabs }
|
|
40
|
-
- hasNoTabs = user for whom there are no tabs open
|
|
41
|
-
- `getUserDetails` function which return the user details from user token
|
|
42
|
-
- `hasPrivilege` function which checks if the user has the privilege passed in function param
|
|
43
|
-
- `alertService` An object containing different methods for displaying toast alerts.
|
|
44
|
-
- can call success, warning, error, info, alert methods.
|
|
45
|
-
- super-notifier - NPM package can be used here
|
|
46
|
-
- https://www.npmjs.com/package/super-notifier
|
|
3
|
+
`SuperTabs` is a production-ready React navigation library for managing parent tabs and nested sub-tabs in complex applications.
|
|
47
4
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
showAddButton: true,
|
|
63
|
-
customAddButtons: [
|
|
64
|
-
{
|
|
65
|
-
name: "Data Grid",
|
|
66
|
-
id: 1,
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
name: "Trend Master",
|
|
70
|
-
id: 2,
|
|
71
|
-
}
|
|
72
|
-
],
|
|
73
|
-
containsSubTabs: true,
|
|
74
|
-
id: 2,
|
|
75
|
-
}]
|
|
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`.
|
|
76
19
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
20
|
+
```jsx
|
|
21
|
+
import { TabProvider } from "components/SuperTabs/TabContext";
|
|
22
|
+
import { alertService } from "super-notifier";
|
|
80
23
|
|
|
81
|
-
import { TabProvider } from 'components/SuperTabs/TabContext';
|
|
82
24
|
<TabProvider
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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}
|
|
91
36
|
>
|
|
92
|
-
|
|
93
|
-
</TabProvider
|
|
37
|
+
<Main />
|
|
38
|
+
</TabProvider>;
|
|
94
39
|
```
|
|
95
40
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
41
|
+
Render tabs in header:
|
|
42
|
+
|
|
43
|
+
```jsx
|
|
44
|
+
import SuperTabs from "components/SuperTabs/SuperTabs";
|
|
99
45
|
|
|
100
46
|
<header>
|
|
101
|
-
|
|
102
|
-
</header
|
|
47
|
+
<SuperTabs />
|
|
48
|
+
</header>;
|
|
103
49
|
```
|
|
104
50
|
|
|
105
|
-
|
|
51
|
+
---
|
|
106
52
|
|
|
107
|
-
|
|
53
|
+
## 2) `TabProvider` props
|
|
108
54
|
|
|
109
|
-
|
|
110
|
-
- `id`
|
|
111
|
-
- `name`
|
|
112
|
-
- `url`
|
|
55
|
+
### Required
|
|
113
56
|
|
|
114
|
-
|
|
115
|
-
-
|
|
116
|
-
- The following fields must be provided:
|
|
117
|
-
- `firstName`
|
|
118
|
-
- `lastName`
|
|
119
|
-
- `imgSrc`
|
|
57
|
+
- `SITE_PREFIX: string`
|
|
58
|
+
- `SITE_PAGES: Array<SitePage>`
|
|
120
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
|
+
}
|
|
121
77
|
```
|
|
122
|
-
// Tab object Examples
|
|
123
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
|
|
124
107
|
{
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
|
128
112
|
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### `handleOpenTab(payload[, controller])`
|
|
116
|
+
|
|
117
|
+
Expected shape:
|
|
129
118
|
|
|
119
|
+
```js
|
|
130
120
|
{
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
lastName: 'Doe',
|
|
135
|
-
showAvatar: true,
|
|
136
|
-
imgSrc: img_url,
|
|
137
|
-
department,
|
|
138
|
-
designation,
|
|
139
|
-
department_id,
|
|
121
|
+
status: 1,
|
|
122
|
+
tabId: 123,
|
|
123
|
+
children: [] // optional child tabs
|
|
140
124
|
}
|
|
141
125
|
```
|
|
126
|
+
|
|
127
|
+
### `handleCloseTab(tabId)`
|
|
128
|
+
|
|
129
|
+
Expected shape:
|
|
130
|
+
|
|
131
|
+
```js
|
|
132
|
+
{
|
|
133
|
+
status: 1,
|
|
134
|
+
current_tab_id: 456 // optional
|
|
135
|
+
}
|
|
142
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
|
|
143
155
|
import SuperLink from "components/SuperTabs/SuperLink";
|
|
156
|
+
|
|
144
157
|
<SuperLink tab={page} isSubTab={false}>
|
|
145
|
-
|
|
146
|
-
</SuperLink
|
|
158
|
+
{page.title}
|
|
159
|
+
</SuperLink>;
|
|
147
160
|
```
|
|
148
161
|
|
|
149
|
-
|
|
162
|
+
Open external links:
|
|
150
163
|
|
|
164
|
+
```jsx
|
|
165
|
+
<SuperLink tab={{ ...page, isExternal: true }} isSubTab={false} isExternal>
|
|
166
|
+
{page.title}
|
|
167
|
+
</SuperLink>
|
|
151
168
|
```
|
|
169
|
+
|
|
170
|
+
### Option 2: `openSuperTabOnRowClick`
|
|
171
|
+
|
|
172
|
+
```jsx
|
|
152
173
|
import { useTabContext } from "components/SuperTabs/TabContext";
|
|
174
|
+
|
|
153
175
|
const { openSuperTabOnRowClick } = useTabContext();
|
|
154
176
|
|
|
155
|
-
function handleRowClick(
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
+
});
|
|
159
186
|
}
|
|
160
187
|
```
|
|
161
188
|
|
|
162
|
-
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## 5) Tab object notes
|
|
192
|
+
|
|
193
|
+
Common fields:
|
|
194
|
+
|
|
195
|
+
- `id`
|
|
196
|
+
- `url`
|
|
197
|
+
- one of `name` / `title`
|
|
163
198
|
|
|
164
|
-
|
|
199
|
+
Optional:
|
|
200
|
+
|
|
201
|
+
- `showAvatar`, `firstName`, `lastName`, `imgSrc` or `img_url`
|
|
202
|
+
- `isExternal`
|
|
203
|
+
- any extra metadata your module uses
|
|
204
|
+
|
|
205
|
+
```js
|
|
206
|
+
{
|
|
207
|
+
id: 100,
|
|
208
|
+
url: "/app/profile/100",
|
|
209
|
+
firstName: "John",
|
|
210
|
+
lastName: "Doe",
|
|
211
|
+
showAvatar: true,
|
|
212
|
+
imgSrc: "https://cdn.example.com/u100.png"
|
|
213
|
+
}
|
|
165
214
|
```
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## 6) Add button callbacks
|
|
219
|
+
|
|
220
|
+
Bind module add callbacks using emitter:
|
|
221
|
+
|
|
222
|
+
```jsx
|
|
223
|
+
import { useCallback, useEffect } from "react";
|
|
166
224
|
import { emitter } from "components/SuperTabs/SuperTabs";
|
|
167
225
|
import { useTabContext } from "components/SuperTabs/TabContext";
|
|
168
226
|
|
|
169
227
|
const { openSuperTabOnRowClick } = useTabContext();
|
|
170
228
|
|
|
171
|
-
const handleTabAddBtn = useCallback(
|
|
229
|
+
const handleTabAddBtn = useCallback(
|
|
230
|
+
(customButtonData) => {
|
|
231
|
+
// customButtonData is passed when using customAddButtons
|
|
172
232
|
openSuperTabOnRowClick({
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
233
|
+
tab: {
|
|
234
|
+
id: 123,
|
|
235
|
+
name: "New Funnel",
|
|
236
|
+
url: "/app/recruiter/f-123",
|
|
237
|
+
},
|
|
238
|
+
isSubTab: true,
|
|
179
239
|
});
|
|
180
|
-
},
|
|
240
|
+
},
|
|
241
|
+
[openSuperTabOnRowClick],
|
|
242
|
+
);
|
|
181
243
|
|
|
182
244
|
useEffect(() => {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
245
|
+
const unsub = emitter.emit("bindCallback", {
|
|
246
|
+
id: 2, // same parent module id from SITE_PAGES
|
|
247
|
+
callback: handleTabAddBtn,
|
|
248
|
+
});
|
|
249
|
+
return unsub;
|
|
188
250
|
}, [handleTabAddBtn]);
|
|
189
251
|
```
|
|
190
252
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
-----
|
|
194
|
-
|
|
195
|
-
#### Advanced
|
|
253
|
+
---
|
|
196
254
|
|
|
197
|
-
|
|
255
|
+
## 7) Close cleanup event
|
|
198
256
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
import { emitter } from 'components/SuperTabs/SuperTabs';
|
|
257
|
+
```jsx
|
|
258
|
+
import { useEffect } from "react";
|
|
259
|
+
import { emitter } from "components/SuperTabs/SuperTabs";
|
|
203
260
|
|
|
204
261
|
useEffect(() => {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
262
|
+
const unsub = emitter.subscribe(
|
|
263
|
+
"superTabClose",
|
|
264
|
+
({ appId, isSubTab, tab }) => {
|
|
265
|
+
if (appId === 2 && isSubTab) {
|
|
266
|
+
// cleanup here
|
|
267
|
+
}
|
|
268
|
+
},
|
|
269
|
+
);
|
|
270
|
+
return unsub;
|
|
212
271
|
}, []);
|
|
213
272
|
```
|
|
214
273
|
|
|
215
|
-
|
|
216
|
-
- If a profile is deleted, the tab will remain open. To address this, we can display a "Page Not Available" message.
|
|
274
|
+
---
|
|
217
275
|
|
|
218
|
-
|
|
219
|
-
import NotAvailable from 'components/SuperTabs/NotAvailable';
|
|
276
|
+
## 8) Not Available page
|
|
220
277
|
|
|
221
|
-
|
|
278
|
+
```jsx
|
|
279
|
+
import NotAvailable from "components/SuperTabs/NotAvailable";
|
|
280
|
+
|
|
281
|
+
<NotAvailable subTabName="Profile" />;
|
|
222
282
|
```
|
|
223
283
|
|
|
224
|
-
|
|
284
|
+
---
|
|
225
285
|
|
|
226
|
-
|
|
227
|
-
import { withKeepAlive } from "components/SuperTabs/KeepAlive";
|
|
286
|
+
## 9) Keep-alive (optional)
|
|
228
287
|
|
|
229
|
-
|
|
230
|
-
export default withKeepAlive(Component, Unique ID);
|
|
288
|
+
Use shared keep-alive utility:
|
|
231
289
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
return <div>Component</div>
|
|
235
|
-
}
|
|
290
|
+
```jsx
|
|
291
|
+
import { withKeepAlive } from "components/KeepAlive";
|
|
236
292
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
Keep Alive Cleanup
|
|
293
|
+
const Component = () => <div>Component</div>;
|
|
294
|
+
export default withKeepAlive(Component, 2); // module id
|
|
240
295
|
```
|
|
241
|
-
// This can be incorporated into a component that is always rendered, such as the header.
|
|
242
296
|
|
|
243
|
-
|
|
244
|
-
import { emitter } from "components/SuperTabs/SuperTabs";
|
|
297
|
+
Cleanup keep-alive cache on parent tab close:
|
|
245
298
|
|
|
246
|
-
|
|
299
|
+
```jsx
|
|
300
|
+
import { useEffect } from "react";
|
|
301
|
+
import { useAliveScope } from "components/KeepAlive";
|
|
302
|
+
import { emitter } from "components/SuperTabs/SuperTabs";
|
|
303
|
+
|
|
304
|
+
const { drop } = useAliveScope();
|
|
247
305
|
|
|
248
306
|
useEffect(() => {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
307
|
+
const unsub = emitter.subscribe("superTabClose", ({ appId, isSubTab }) => {
|
|
308
|
+
if (appId && !isSubTab) {
|
|
309
|
+
drop(appId);
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
return unsub;
|
|
313
|
+
}, [drop]);
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## 10) Styling note
|
|
319
|
+
|
|
320
|
+
Home tab logo can be styled with `.brand-logo`.
|
package/dist/TabContext.js
CHANGED
|
@@ -252,8 +252,8 @@ function TabProvider(_ref9) {
|
|
|
252
252
|
subTabWrapperWidth = _useState12[0],
|
|
253
253
|
setSubTabWrapperWidth = _useState12[1];
|
|
254
254
|
var _useState13 = (0, _react.useState)(function () {
|
|
255
|
-
var _getUserDetails;
|
|
256
|
-
return ((_getUserDetails = getUserDetails()) === null || _getUserDetails === void 0 ? void 0 : _getUserDetails[entityIdentifierKey])
|
|
255
|
+
var _getUserDetails$entit, _getUserDetails;
|
|
256
|
+
return (_getUserDetails$entit = (_getUserDetails = getUserDetails()) === null || _getUserDetails === void 0 ? void 0 : _getUserDetails[entityIdentifierKey]) !== null && _getUserDetails$entit !== void 0 ? _getUserDetails$entit : null;
|
|
257
257
|
}),
|
|
258
258
|
_useState14 = _slicedToArray(_useState13, 2),
|
|
259
259
|
currentEntityId = _useState14[0],
|