@bydata/react-supertabs 1.1.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/README.md +257 -0
- package/dist/ClickOutsideListener.js +72 -0
- package/dist/Constants.js +20 -0
- package/dist/NotAvailable.js +29 -0
- package/dist/NotAvailable.scss +30 -0
- package/dist/Readme.md +258 -0
- package/dist/SuperLink.js +36 -0
- package/dist/SuperTabs.js +1196 -0
- package/dist/SuperTabs.scss +892 -0
- package/dist/TabContext.js +1895 -0
- package/dist/TabList.js +234 -0
- package/dist/TabList.scss +274 -0
- package/dist/TabStack.js +98 -0
- package/dist/Utils.js +336 -0
- package/dist/eventEmitter.js +116 -0
- package/dist/images/Icon-open-arrow-top.svg +3 -0
- package/dist/images/icon-chevron-right.svg +1 -0
- package/dist/images/icon-close-lite.svg +3 -0
- package/dist/images/icon-search.svg +10 -0
- package/dist/images/icon-stack.svg +6 -0
- package/dist/index.js +20 -0
- package/dist/usePrevious.js +37 -0
- package/package.json +33 -0
package/README.md
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
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 and usage
|
|
13
|
+
------
|
|
14
|
+
|
|
15
|
+
#### Basic
|
|
16
|
+
|
|
17
|
+
>**Import TabProvider and wrap it around the main wrapper**
|
|
18
|
+
|
|
19
|
+
- This is needed to access useTabContext.
|
|
20
|
+
- Make sure to pass `SITE_PREFIX` and `SITE_PAGES` (From Navigation.js)
|
|
21
|
+
- `homeUrl` is optional and defaults to "/"
|
|
22
|
+
- `persistTabsAfterLogin` is optional and defaults to "false"
|
|
23
|
+
- Used to preserve tab state upon login.
|
|
24
|
+
- `entityIdentifierKey` is optional and defaults to 'organization_id',
|
|
25
|
+
- Used to identify the organization / client id
|
|
26
|
+
- `preventHomeRedirect` is optional and defaults to false,
|
|
27
|
+
- This will open a menu on click of home button instead of redirecting
|
|
28
|
+
- `handleOpenTab` async function to open the tab in backend
|
|
29
|
+
- expects the following keys in return
|
|
30
|
+
- { status: 1, tabId, children: undefined }
|
|
31
|
+
- `handleCloseTab` async function to open the tab in backend
|
|
32
|
+
- expects the following keys in return
|
|
33
|
+
- { status: 1, current_tab_id: 123 }
|
|
34
|
+
- `handleReorderTabs` async function to open the tab in backend
|
|
35
|
+
- { status: 1 }
|
|
36
|
+
- `getTabs` async function to open the tab in backend async () => { },
|
|
37
|
+
- expects the following keys in return
|
|
38
|
+
- { status: 1, tabs: [], current_tab: {} || null, hasNoTabs }
|
|
39
|
+
- hasNoTabs = user for whom there are no tabs open
|
|
40
|
+
- `getUserDetails` function which return the user details from user token
|
|
41
|
+
- `hasPrivilege` function which checks if the user has the privilege passed in function param
|
|
42
|
+
- `alertService` An object containing different methods for displaying toast alerts.
|
|
43
|
+
- can call success, warning, error, info, alert methods.
|
|
44
|
+
- super-notifier - NPM package can be used here
|
|
45
|
+
- https://www.npmjs.com/package/super-notifier
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
Eg.
|
|
49
|
+
const SITE_PREFIX = 'teamLink_'
|
|
50
|
+
const API_BASE_URL = 'https://api.teamlink.com/data_stream'
|
|
51
|
+
const SITE_PAGES = [{
|
|
52
|
+
title: "Organization",
|
|
53
|
+
url: "/app/organization",
|
|
54
|
+
privilege: "ORGANIZATION",
|
|
55
|
+
id: 1,
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
title: "Recruiter",
|
|
59
|
+
url: "/app/recruiter/f-home",
|
|
60
|
+
privilege: "RECRUITER",
|
|
61
|
+
showAddButton: true,
|
|
62
|
+
customAddButtons: [
|
|
63
|
+
{
|
|
64
|
+
name: "Data Grid",
|
|
65
|
+
id: 1,
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: "Trend Master",
|
|
69
|
+
id: 2,
|
|
70
|
+
}
|
|
71
|
+
],
|
|
72
|
+
containsSubTabs: true,
|
|
73
|
+
id: 2,
|
|
74
|
+
}]
|
|
75
|
+
|
|
76
|
+
// showAddButton - can be added if the module needs plus button.
|
|
77
|
+
// containsSubTabs - can be added if the tab includes sub tabs.
|
|
78
|
+
// customAddButtons - These buttons will be added in the sub tab list. Clicking on the button will trigger the binded callback with object as param.
|
|
79
|
+
|
|
80
|
+
import { TabProvider } from 'components/SuperTabs/TabContext';
|
|
81
|
+
<TabProvider
|
|
82
|
+
SITE_PREFIX={SITE_PREFIX}
|
|
83
|
+
SITE_PAGES={SITE_PAGES}
|
|
84
|
+
homeUrl="/app"
|
|
85
|
+
getTabs={getTabs}
|
|
86
|
+
handleOpenTab={handleOpenTab}
|
|
87
|
+
handleCloseTab={handleCloseTab}
|
|
88
|
+
handleReorderTabs={handleReorderTabs}
|
|
89
|
+
alertService={alertService}
|
|
90
|
+
>
|
|
91
|
+
<Main />
|
|
92
|
+
</TabProvider>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
>**Render the component within the App's header**
|
|
96
|
+
```
|
|
97
|
+
import { useTabContext } from "components/SuperTabs/TabContext";
|
|
98
|
+
|
|
99
|
+
<header>
|
|
100
|
+
<SuperTabs />
|
|
101
|
+
</header>
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
>**Wrap the hyperlink as follows:**
|
|
105
|
+
|
|
106
|
+
Each tab should contain all the necessary information to be stored.
|
|
107
|
+
|
|
108
|
+
Mandatory Fields:
|
|
109
|
+
- `id`
|
|
110
|
+
- `name`
|
|
111
|
+
- `url`
|
|
112
|
+
|
|
113
|
+
For Avatar:
|
|
114
|
+
- `showAvatar` must be set to `true`
|
|
115
|
+
- The following fields must be provided:
|
|
116
|
+
- `firstName`
|
|
117
|
+
- `lastName`
|
|
118
|
+
- `imgSrc`
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
// Tab object Examples
|
|
122
|
+
|
|
123
|
+
{
|
|
124
|
+
id: 1,
|
|
125
|
+
name: 'title',
|
|
126
|
+
url: '/recruiter/f-1`
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
{
|
|
130
|
+
id: 100,
|
|
131
|
+
url: '/profile/100`
|
|
132
|
+
firstName: 'John',
|
|
133
|
+
lastName: 'Doe',
|
|
134
|
+
showAvatar: true,
|
|
135
|
+
imgSrc: img_url,
|
|
136
|
+
department,
|
|
137
|
+
designation,
|
|
138
|
+
department_id,
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
```
|
|
142
|
+
import SuperLink from "components/SuperTabs/SuperLink";
|
|
143
|
+
<SuperLink tab={page} isSubTab={false}>
|
|
144
|
+
{page.title}
|
|
145
|
+
</SuperLink>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
OR
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
import { useTabContext } from "components/SuperTabs/TabContext";
|
|
152
|
+
const { openSuperTabOnRowClick } = useTabContext();
|
|
153
|
+
|
|
154
|
+
function handleRowClick(e) {
|
|
155
|
+
const tab = { id: e.id, name: e.name, url: `/recruiter/f-${e.id}` };
|
|
156
|
+
// isSubTab defaults to true
|
|
157
|
+
openSuperTabOnRowClick({ tab, isSubTab });
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
>**Bind the callback for the plus button to each module during the initial render.**
|
|
162
|
+
|
|
163
|
+
If a module requires a plus button for adding a new tab, you can include the following:
|
|
164
|
+
```
|
|
165
|
+
import { emitter } from "components/SuperTabs/SuperTabs";
|
|
166
|
+
import { useTabContext } from "components/SuperTabs/TabContext";
|
|
167
|
+
|
|
168
|
+
const { openSuperTabOnRowClick } = useTabContext();
|
|
169
|
+
|
|
170
|
+
const handleTabAddBtn = useCallback(() => {
|
|
171
|
+
openSuperTabOnRowClick({
|
|
172
|
+
tab: {
|
|
173
|
+
id: 123,
|
|
174
|
+
name: `New Funnel`,
|
|
175
|
+
url: `/recruiter/f-${123}`,
|
|
176
|
+
},
|
|
177
|
+
isSubTab: true,
|
|
178
|
+
});
|
|
179
|
+
}, [openSuperTabOnRowClick]);
|
|
180
|
+
|
|
181
|
+
useEffect(() => {
|
|
182
|
+
const unsub = emitter.emit("bindCallback", {
|
|
183
|
+
id: 2, // Same id provided for this module in navigation.js
|
|
184
|
+
callback: handleTabAddBtn,
|
|
185
|
+
});
|
|
186
|
+
return unsub;
|
|
187
|
+
}, [handleTabAddBtn]);
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
> Note: The logo of the home tab can be updated by targeting the `brand-logo` class.
|
|
191
|
+
|
|
192
|
+
-----
|
|
193
|
+
|
|
194
|
+
#### Advanced
|
|
195
|
+
|
|
196
|
+
>**Cleanup**
|
|
197
|
+
|
|
198
|
+
When a tab is closed, it may be necessary to clean up some data. The `superTabClose` event can be utilized for this purpose.
|
|
199
|
+
|
|
200
|
+
```
|
|
201
|
+
import { emitter } from 'components/SuperTabs/SuperTabs';
|
|
202
|
+
|
|
203
|
+
useEffect(() => {
|
|
204
|
+
const unsub = emitter.subscribe('superTabClose', ({ appId, isSubTab, tab }) => {
|
|
205
|
+
// Same id provided for this module in navigation.js
|
|
206
|
+
if (appId === 2 && isSubTab) {
|
|
207
|
+
// handle clean up here
|
|
208
|
+
}
|
|
209
|
+
})
|
|
210
|
+
return unsub;
|
|
211
|
+
}, []);
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
>**Not Available page**
|
|
215
|
+
- If a profile is deleted, the tab will remain open. To address this, we can display a "Page Not Available" message.
|
|
216
|
+
|
|
217
|
+
```
|
|
218
|
+
import NotAvailable from 'components/SuperTabs/NotAvailable';
|
|
219
|
+
|
|
220
|
+
<NotAvailable subTabName='Profile' />
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
>**Keep Alive (Experimental)**
|
|
224
|
+
|
|
225
|
+
```
|
|
226
|
+
import { withKeepAlive } from "components/SuperTabs/KeepAlive";
|
|
227
|
+
|
|
228
|
+
// Same id provided for this module in navigation.js
|
|
229
|
+
export default withKeepAlive(Component, Unique ID);
|
|
230
|
+
|
|
231
|
+
Eg.
|
|
232
|
+
const Component = () => {
|
|
233
|
+
return <div>Component</div>
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export default withKeepAlive(Component, 2);
|
|
237
|
+
```
|
|
238
|
+
Keep Alive Cleanup
|
|
239
|
+
```
|
|
240
|
+
// This can be incorporated into a component that is always rendered, such as the header.
|
|
241
|
+
|
|
242
|
+
import { useAliveScope } from "components/SuperTabs/KeepAlive";
|
|
243
|
+
import { emitter } from "components/SuperTabs/SuperTabs";
|
|
244
|
+
|
|
245
|
+
const { drop } = useAliveScope();
|
|
246
|
+
|
|
247
|
+
useEffect(() => {
|
|
248
|
+
const unsub = emitter.subscribe('superTabClose', (e) => {
|
|
249
|
+
const { appId, isSubTab } = e;
|
|
250
|
+
if (appId && !isSubTab) {
|
|
251
|
+
// drop the cache of the tab
|
|
252
|
+
drop(appId);
|
|
253
|
+
}
|
|
254
|
+
})
|
|
255
|
+
return unsub;
|
|
256
|
+
}, []);
|
|
257
|
+
```
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports["default"] = void 0;
|
|
8
|
+
var _react = _interopRequireDefault(require("react"));
|
|
9
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default": e }; }
|
|
10
|
+
function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
|
|
11
|
+
function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }
|
|
12
|
+
function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; }
|
|
13
|
+
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
|
|
14
|
+
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
15
|
+
function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); }
|
|
16
|
+
function _possibleConstructorReturn(t, e) { if (e && ("object" == _typeof(e) || "function" == typeof e)) return e; if (void 0 !== e) throw new TypeError("Derived constructors may only return object or undefined"); return _assertThisInitialized(t); }
|
|
17
|
+
function _assertThisInitialized(e) { if (void 0 === e) throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); return e; }
|
|
18
|
+
function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }
|
|
19
|
+
function _getPrototypeOf(t) { return _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function (t) { return t.__proto__ || Object.getPrototypeOf(t); }, _getPrototypeOf(t); }
|
|
20
|
+
function _inherits(t, e) { if ("function" != typeof e && null !== e) throw new TypeError("Super expression must either be null or a function"); t.prototype = Object.create(e && e.prototype, { constructor: { value: t, writable: !0, configurable: !0 } }), Object.defineProperty(t, "prototype", { writable: !1 }), e && _setPrototypeOf(t, e); }
|
|
21
|
+
function _setPrototypeOf(t, e) { return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) { return t.__proto__ = e, t; }, _setPrototypeOf(t, e); }
|
|
22
|
+
var ClickOutsideListener = exports["default"] = /*#__PURE__*/function (_React$Component) {
|
|
23
|
+
function ClickOutsideListener(props) {
|
|
24
|
+
var _this;
|
|
25
|
+
_classCallCheck(this, ClickOutsideListener);
|
|
26
|
+
_this = _callSuper(this, ClickOutsideListener, [props]);
|
|
27
|
+
_this.domRef = /*#__PURE__*/_react["default"].createRef();
|
|
28
|
+
_this.listener = _this.listener.bind(_this);
|
|
29
|
+
return _this;
|
|
30
|
+
}
|
|
31
|
+
_inherits(ClickOutsideListener, _React$Component);
|
|
32
|
+
return _createClass(ClickOutsideListener, [{
|
|
33
|
+
key: "componentDidMount",
|
|
34
|
+
value: function componentDidMount() {
|
|
35
|
+
document.addEventListener('click', this.listener, true);
|
|
36
|
+
}
|
|
37
|
+
}, {
|
|
38
|
+
key: "componentWillUnmount",
|
|
39
|
+
value: function componentWillUnmount() {
|
|
40
|
+
document.removeEventListener('click', this.listener, true);
|
|
41
|
+
}
|
|
42
|
+
}, {
|
|
43
|
+
key: "isClickedOutside",
|
|
44
|
+
value: function isClickedOutside(e) {
|
|
45
|
+
// e.stopPropagation();
|
|
46
|
+
// If the event target doesn't match bail - analysis view - save and filter panel fix
|
|
47
|
+
// if(e.target.matches('button') || e.target.matches('svg') || e.target.matches('rect') || e.target.matches('polyline') || e.target.matches('span')) return false;
|
|
48
|
+
// console.log(e.target);
|
|
49
|
+
// console.log(this.domRef.current);
|
|
50
|
+
// console.log(this.domRef.current.contains(e.target));
|
|
51
|
+
|
|
52
|
+
if (this.domRef.current && !this.domRef.current.contains(e.target)) {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}, {
|
|
58
|
+
key: "listener",
|
|
59
|
+
value: function listener(e) {
|
|
60
|
+
if (this.isClickedOutside(e)) {
|
|
61
|
+
this.props.onOutsideClick(e);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}, {
|
|
65
|
+
key: "render",
|
|
66
|
+
value: function render() {
|
|
67
|
+
return /*#__PURE__*/_react["default"].createElement("div", {
|
|
68
|
+
ref: this.domRef
|
|
69
|
+
}, this.props.children);
|
|
70
|
+
}
|
|
71
|
+
}]);
|
|
72
|
+
}(_react["default"].Component);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.transitionDurationExtended = exports.transitionDuration = exports.fontStyle = exports.ToolbarButtonWidth = exports.SubTabWrapperPadding = exports.SubTabPadding = exports.SubTabGap = exports.StackButtonWidth = exports.MarginLeft = exports.MainTabGap = exports.HomeTabWidth = exports.CloseButtonWidth = exports.AvatarSize = void 0;
|
|
7
|
+
var transitionDuration = exports.transitionDuration = 300;
|
|
8
|
+
var transitionDurationExtended = exports.transitionDurationExtended = 800;
|
|
9
|
+
var HomeTabWidth = exports.HomeTabWidth = 36;
|
|
10
|
+
var MainTabGap = exports.MainTabGap = 2;
|
|
11
|
+
var SubTabGap = exports.SubTabGap = 6;
|
|
12
|
+
var MarginLeft = exports.MarginLeft = 5;
|
|
13
|
+
var SubTabPadding = exports.SubTabPadding = 20;
|
|
14
|
+
var SubTabWrapperPadding = exports.SubTabWrapperPadding = MarginLeft * 2;
|
|
15
|
+
var StackButtonWidth = exports.StackButtonWidth = 32 + MarginLeft;
|
|
16
|
+
var AvatarSize = exports.AvatarSize = 20 + SubTabGap;
|
|
17
|
+
var ToolbarButtonWidth = exports.ToolbarButtonWidth = 24;
|
|
18
|
+
var CloseButtonWidth = exports.CloseButtonWidth = 20; // 24-4px is for the right padding adjustment
|
|
19
|
+
|
|
20
|
+
var fontStyle = exports.fontStyle = '600 12px "Open Sans", "Poppins", sans-serif'; // "same as supertabs.scss global font style"
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports["default"] = void 0;
|
|
7
|
+
var _TabContext = require("./TabContext");
|
|
8
|
+
var _Utils = require("./Utils");
|
|
9
|
+
require("./NotAvailable.scss");
|
|
10
|
+
var NotAvailable = function NotAvailable(_ref) {
|
|
11
|
+
var title = _ref.title,
|
|
12
|
+
description = _ref.description,
|
|
13
|
+
_ref$subTabName = _ref.subTabName,
|
|
14
|
+
subTabName = _ref$subTabName === void 0 ? 'Page' : _ref$subTabName;
|
|
15
|
+
var _useTabContext = (0, _TabContext.useTabContext)(),
|
|
16
|
+
activeSubTab = _useTabContext.activeSubTab;
|
|
17
|
+
var heading = title !== null && title !== void 0 ? title : (0, _Utils.getTitle)(activeSubTab);
|
|
18
|
+
var subHeading = description !== null && description !== void 0 ? description : "".concat(subTabName, " is not available");
|
|
19
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
20
|
+
className: "not-availble-wrapper"
|
|
21
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
22
|
+
className: "not-available-content"
|
|
23
|
+
}, /*#__PURE__*/React.createElement("h3", {
|
|
24
|
+
className: "title"
|
|
25
|
+
}, heading.replace('Loading...', 'Untitled')), /*#__PURE__*/React.createElement("p", {
|
|
26
|
+
className: "description"
|
|
27
|
+
}, subHeading)));
|
|
28
|
+
};
|
|
29
|
+
var _default = exports["default"] = NotAvailable;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
.not-availble-wrapper {
|
|
2
|
+
display: grid;
|
|
3
|
+
place-items: center;
|
|
4
|
+
height: 100%;
|
|
5
|
+
|
|
6
|
+
.not-available-content {
|
|
7
|
+
display: grid;
|
|
8
|
+
justify-items: center;
|
|
9
|
+
gap: 20px;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.title,
|
|
13
|
+
.description {
|
|
14
|
+
font-family: 'Open sans', sans-serif;
|
|
15
|
+
margin: 0;
|
|
16
|
+
color: var(--tab-text-normal);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.title {
|
|
20
|
+
font-size: 16px;
|
|
21
|
+
font-weight: 600;
|
|
22
|
+
line-height: 1.5;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.description {
|
|
26
|
+
font-size: 14px;
|
|
27
|
+
font-weight: 400;
|
|
28
|
+
line-height: 1;
|
|
29
|
+
}
|
|
30
|
+
}
|
package/dist/Readme.md
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
|
|
2
|
+
# SuperTabs
|
|
3
|
+
|
|
4
|
+
**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.
|
|
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
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
Eg.
|
|
50
|
+
const SITE_PREFIX = 'teamLink_'
|
|
51
|
+
const API_BASE_URL = 'https://api.teamlink.com/data_stream'
|
|
52
|
+
const SITE_PAGES = [{
|
|
53
|
+
title: "Organization",
|
|
54
|
+
url: "/app/organization",
|
|
55
|
+
privilege: "ORGANIZATION",
|
|
56
|
+
id: 1,
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
title: "Recruiter",
|
|
60
|
+
url: "/app/recruiter/f-home",
|
|
61
|
+
privilege: "RECRUITER",
|
|
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
|
+
}]
|
|
76
|
+
|
|
77
|
+
// showAddButton - can be added if the module needs plus button.
|
|
78
|
+
// containsSubTabs - can be added if the tab includes sub tabs.
|
|
79
|
+
// customAddButtons - These buttons will be added in the sub tab list. Clicking on the button will trigger the binded callback with object as param.
|
|
80
|
+
|
|
81
|
+
import { TabProvider } from 'components/SuperTabs/TabContext';
|
|
82
|
+
<TabProvider
|
|
83
|
+
SITE_PREFIX={SITE_PREFIX}
|
|
84
|
+
SITE_PAGES={SITE_PAGES}
|
|
85
|
+
homeUrl="/app"
|
|
86
|
+
getTabs={getTabs}
|
|
87
|
+
handleOpenTab={handleOpenTab}
|
|
88
|
+
handleCloseTab={handleCloseTab}
|
|
89
|
+
handleReorderTabs={handleReorderTabs}
|
|
90
|
+
alertService={alertService}
|
|
91
|
+
>
|
|
92
|
+
<Main />
|
|
93
|
+
</TabProvider>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
>**Render the component within the App's header**
|
|
97
|
+
```
|
|
98
|
+
import { useTabContext } from "components/SuperTabs/TabContext";
|
|
99
|
+
|
|
100
|
+
<header>
|
|
101
|
+
<SuperTabs />
|
|
102
|
+
</header>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
>**Wrap the hyperlink as follows:**
|
|
106
|
+
|
|
107
|
+
Each tab should contain all the necessary information to be stored.
|
|
108
|
+
|
|
109
|
+
Mandatory Fields:
|
|
110
|
+
- `id`
|
|
111
|
+
- `name`
|
|
112
|
+
- `url`
|
|
113
|
+
|
|
114
|
+
For Avatar:
|
|
115
|
+
- `showAvatar` must be set to `true`
|
|
116
|
+
- The following fields must be provided:
|
|
117
|
+
- `firstName`
|
|
118
|
+
- `lastName`
|
|
119
|
+
- `imgSrc`
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
// Tab object Examples
|
|
123
|
+
|
|
124
|
+
{
|
|
125
|
+
id: 1,
|
|
126
|
+
name: 'title',
|
|
127
|
+
url: '/recruiter/f-1`
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
{
|
|
131
|
+
id: 100,
|
|
132
|
+
url: '/profile/100`
|
|
133
|
+
firstName: 'John',
|
|
134
|
+
lastName: 'Doe',
|
|
135
|
+
showAvatar: true,
|
|
136
|
+
imgSrc: img_url,
|
|
137
|
+
department,
|
|
138
|
+
designation,
|
|
139
|
+
department_id,
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
```
|
|
143
|
+
import SuperLink from "components/SuperTabs/SuperLink";
|
|
144
|
+
<SuperLink tab={page} isSubTab={false}>
|
|
145
|
+
{page.title}
|
|
146
|
+
</SuperLink>
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
OR
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
import { useTabContext } from "components/SuperTabs/TabContext";
|
|
153
|
+
const { openSuperTabOnRowClick } = useTabContext();
|
|
154
|
+
|
|
155
|
+
function handleRowClick(e) {
|
|
156
|
+
const tab = { id: e.id, name: e.name, url: `/recruiter/f-${e.id}` };
|
|
157
|
+
// isSubTab defaults to true
|
|
158
|
+
openSuperTabOnRowClick({ tab, isSubTab });
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
>**Bind the callback for the plus button to each module during the initial render.**
|
|
163
|
+
|
|
164
|
+
If a module requires a plus button for adding a new tab, you can include the following:
|
|
165
|
+
```
|
|
166
|
+
import { emitter } from "components/SuperTabs/SuperTabs";
|
|
167
|
+
import { useTabContext } from "components/SuperTabs/TabContext";
|
|
168
|
+
|
|
169
|
+
const { openSuperTabOnRowClick } = useTabContext();
|
|
170
|
+
|
|
171
|
+
const handleTabAddBtn = useCallback(() => {
|
|
172
|
+
openSuperTabOnRowClick({
|
|
173
|
+
tab: {
|
|
174
|
+
id: 123,
|
|
175
|
+
name: `New Funnel`,
|
|
176
|
+
url: `/recruiter/f-${123}`,
|
|
177
|
+
},
|
|
178
|
+
isSubTab: true,
|
|
179
|
+
});
|
|
180
|
+
}, [openSuperTabOnRowClick]);
|
|
181
|
+
|
|
182
|
+
useEffect(() => {
|
|
183
|
+
const unsub = emitter.emit("bindCallback", {
|
|
184
|
+
id: 2, // Same id provided for this module in navigation.js
|
|
185
|
+
callback: handleTabAddBtn,
|
|
186
|
+
});
|
|
187
|
+
return unsub;
|
|
188
|
+
}, [handleTabAddBtn]);
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
> Note: The logo of the home tab can be updated by targeting the `brand-logo` class.
|
|
192
|
+
|
|
193
|
+
-----
|
|
194
|
+
|
|
195
|
+
#### Advanced
|
|
196
|
+
|
|
197
|
+
>**Cleanup**
|
|
198
|
+
|
|
199
|
+
When a tab is closed, it may be necessary to clean up some data. The `superTabClose` event can be utilized for this purpose.
|
|
200
|
+
|
|
201
|
+
```
|
|
202
|
+
import { emitter } from 'components/SuperTabs/SuperTabs';
|
|
203
|
+
|
|
204
|
+
useEffect(() => {
|
|
205
|
+
const unsub = emitter.subscribe('superTabClose', ({ appId, isSubTab, tab }) => {
|
|
206
|
+
// Same id provided for this module in navigation.js
|
|
207
|
+
if (appId === 2 && isSubTab) {
|
|
208
|
+
// handle clean up here
|
|
209
|
+
}
|
|
210
|
+
})
|
|
211
|
+
return unsub;
|
|
212
|
+
}, []);
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
>**Not Available page**
|
|
216
|
+
- If a profile is deleted, the tab will remain open. To address this, we can display a "Page Not Available" message.
|
|
217
|
+
|
|
218
|
+
```
|
|
219
|
+
import NotAvailable from 'components/SuperTabs/NotAvailable';
|
|
220
|
+
|
|
221
|
+
<NotAvailable subTabName='Profile' />
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
>**Keep Alive (Experimental)**
|
|
225
|
+
|
|
226
|
+
```
|
|
227
|
+
import { withKeepAlive } from "components/SuperTabs/KeepAlive";
|
|
228
|
+
|
|
229
|
+
// Same id provided for this module in navigation.js
|
|
230
|
+
export default withKeepAlive(Component, Unique ID);
|
|
231
|
+
|
|
232
|
+
Eg.
|
|
233
|
+
const Component = () => {
|
|
234
|
+
return <div>Component</div>
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export default withKeepAlive(Component, 2);
|
|
238
|
+
```
|
|
239
|
+
Keep Alive Cleanup
|
|
240
|
+
```
|
|
241
|
+
// This can be incorporated into a component that is always rendered, such as the header.
|
|
242
|
+
|
|
243
|
+
import { useAliveScope } from "components/SuperTabs/KeepAlive";
|
|
244
|
+
import { emitter } from "components/SuperTabs/SuperTabs";
|
|
245
|
+
|
|
246
|
+
const { drop } = useAliveScope();
|
|
247
|
+
|
|
248
|
+
useEffect(() => {
|
|
249
|
+
const unsub = emitter.subscribe('superTabClose', (e) => {
|
|
250
|
+
const { appId, isSubTab } = e;
|
|
251
|
+
if (appId && !isSubTab) {
|
|
252
|
+
// drop the cache of the tab
|
|
253
|
+
drop(appId);
|
|
254
|
+
}
|
|
255
|
+
})
|
|
256
|
+
return unsub;
|
|
257
|
+
}, []);
|
|
258
|
+
```
|