@bydata/react-supertabs 1.2.5 → 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 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bydata/react-supertabs",
3
- "version": "1.2.5",
3
+ "version": "1.2.6",
4
4
  "description": "A customizable React super tabs component.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
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
- ```