Skip to content
This repository was archived by the owner on Mar 13, 2025. It is now read-only.

Commit 1f9cd29

Browse files
committed
feat: disable/enable sidebar
1 parent d3267e4 commit 1f9cd29

File tree

14 files changed

+214
-57
lines changed

14 files changed

+214
-57
lines changed

.nvmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
10.22.1

README.md

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ This app exports functions to be imported by other microapps.
6464
- `setAppMenu` - sets sidebar menu for the app by app's `path`
6565
- `getAuthUserTokens` - returns a promise which resolves to object with user tokens `{ tokenV3, tokenV2 }`
6666
- `getAuthUserProfile` - returns a promise which resolves to the user profile object
67+
- `disableSidebarForRoute` - disable (remove) sidebar for some route
68+
- `enableSidebarForRoute` - enable sidebar for the route, which was previously disabled
6769

6870
#### How to export
6971

@@ -89,13 +91,15 @@ For example see https://github.com/topcoder-platform/micro-frontends-react-app
8991

9092
2. As `importmaps` only work in browser and don't work in unit test, we have to mock this module in unit tests. For example by creating a file `src/__mocks__/@topcoder/micro-frontends-navbar-app.js` with the content like:
9193
```js
92-
module.exports = {
94+
module.exports = {
9395
login: () => {},
9496
logout: () => {},
9597
setAppMenu: () => {},
9698
getAuthUserTokens: () => new Promise(() => {}),
9799
getAuthUserProfile: () => new Promise(() => {}),
98-
};
100+
disableSidebarForRoute: () => {},
101+
enableSidebarForRoute: () => {},
102+
};
99103
```
100104

101105
##### How to import in Angular app
@@ -112,11 +116,13 @@ For example see https://github.com/topcoder-platform/micro-frontends-angular-app
112116
2. Add type definition in `src/typings.d.ts`:
113117
```js
114118
declare module '@topcoder/micro-frontends-navbar-app' {
115-
export const login: any;
116-
export const logout: any;
117-
export const setAppMenu: any;
118-
export const getAuthUserTokens: any;
119-
export const getAuthUserProfile: any;
119+
export const login: any;
120+
export const logout: any;
121+
export const setAppMenu: any;
122+
export const getAuthUserTokens: any;
123+
export const getAuthUserProfile: any;
124+
export const disableSidebarForRoute: any;
125+
export const enableSidebarForRoute: any;
120126
}
121127
```
122128

src/App.jsx

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,58 @@
11
/**
22
* Main App component
33
*/
4-
import React, { useState, useCallback, useMemo } from "react";
4+
import React, { useState, useCallback, useMemo, useEffect } from "react";
55
import _ from "lodash";
66
import MainMenu from "./components/MainMenu";
77
import NavBar from "./components/NavBar";
8-
import { Router, createHistory, LocationProvider } from "@reach/router";
8+
import { Router } from "@reach/router";
99
import { useSelector } from "react-redux";
10-
11-
// History for location provider
12-
let history = createHistory(window);
10+
import useMatchSomeRoute from "./hooks/useMatchSomeRoute";
1311

1412
const App = () => {
1513
// all menu options
16-
const menu = useSelector((state) => state.menu);
14+
const menu = useSelector((state) => state.menu.categories);
1715
// flat list of all apps (only updated when menu updated in the Redux store)
1816
const apps = useMemo(() => _.flatMap(menu, "apps"), [menu]);
17+
// list of routes where we have to disabled sidebar
18+
const disabledRoutes = useSelector((state) => state.menu.disabledRoutes);
19+
// `true` is sidebar has to be disabled for the current route
20+
const isSideBarDisabled = useMatchSomeRoute(disabledRoutes);
1921
// Left sidebar collapse state
2022
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
2123
// Toggle left sidebar callback
2224
const toggleSidebar = useCallback(() => {
2325
setSidebarCollapsed(!sidebarCollapsed);
2426
}, [sidebarCollapsed, setSidebarCollapsed]);
2527

28+
// set/remove class for the whole page, to know if sidebar is present or no
29+
useEffect(() => {
30+
if (isSideBarDisabled) {
31+
document.body.classList.add("no-sidebar");
32+
} else {
33+
document.body.classList.remove("no-sidebar");
34+
}
35+
}, [isSideBarDisabled]);
36+
2637
return (
27-
<LocationProvider history={history}>
38+
<>
2839
<NavBar />
29-
<div className="main-menu-wrapper">
30-
<Router>
31-
{apps.map((app) => (
32-
<MainMenu
33-
sidebarCollapsed={sidebarCollapsed}
34-
toggleSidebar={toggleSidebar}
35-
key={app.path}
36-
path={app.path + "/*"}
37-
app={app}
38-
/>
39-
))}
40-
</Router>
41-
</div>
42-
</LocationProvider>
40+
{!isSideBarDisabled && (
41+
<div className="main-menu-wrapper">
42+
<Router>
43+
{apps.map((app) => (
44+
<MainMenu
45+
sidebarCollapsed={sidebarCollapsed}
46+
toggleSidebar={toggleSidebar}
47+
key={app.path}
48+
path={app.path + "/*"}
49+
app={app}
50+
/>
51+
))}
52+
</Router>
53+
</div>
54+
)}
55+
</>
4356
);
4457
};
4558

src/actions/menu.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,30 @@ export default {
1313
type: ACTIONS.MENU.SET_APP_MENU,
1414
payload: { path, menuOptions },
1515
}),
16+
17+
/**
18+
* Disable sidebar for route.
19+
*
20+
* See how we can define route here https://reach.tech/router/api/Match.
21+
*
22+
* @param {String} route route path
23+
*
24+
* @returns {{ type: String, payload: any }} action object
25+
*/
26+
disableSidebarForRoute: (route) => ({
27+
type: ACTIONS.MENU.DISABLE_SIDEBAR_FOR_ROUTE,
28+
payload: route,
29+
}),
30+
31+
/**
32+
* Enable sidebar for route.
33+
*
34+
* @param {String} route route path
35+
*
36+
* @returns {{ type: String, payload: any }} action object
37+
*/
38+
enableSidebarForRoute: (route) => ({
39+
type: ACTIONS.MENU.ENABLE_SIDEBAR_FOR_ROUTE,
40+
payload: route,
41+
}),
1642
};

src/components/AllAppsMenu/index.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import "./styles.css";
1212
import { useSelector } from "react-redux";
1313

1414
const AllAppsMenu = () => {
15-
const menu = useSelector((state) => state.menu);
15+
const menu = useSelector((state) => state.menu.categories);
1616
const [isOpenMenu, setIsOpenMenu] = useState(false);
1717

1818
const closeMenu = useCallback(() => {

src/components/MainMenu/styles.css

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,24 @@
1+
/* this style controls the sidebar,
2+
but it also has to set the margin for the main content of the main frame */
3+
#single-spa-main {
4+
margin-left: 0;
5+
}
6+
7+
.main-menu-wrapper {
8+
background-color: #fff;
9+
}
10+
111
.main-menu-header {
212
margin: 22px 0 20px 21px;
313
}
14+
415
.menu-toggle-icon {
516
width: 10px;
617
height: 10px;
718
margin-left: 3px;
819
cursor: pointer;
920
}
1021

11-
1222
.main-menu-mobile {
1323
background-color: #fff;
1424
left: 0;
@@ -32,8 +42,9 @@
3242
}
3343

3444
.main-menu {
35-
width: 270px;
45+
width: var(--sideBarWidth);
3646
}
47+
3748
.main-menu-collapsed {
3849
width: auto;
3950
}
@@ -95,3 +106,23 @@
95106
transform: rotate(180deg);
96107
}
97108
}
109+
110+
@media (min-width: 1024px) {
111+
/* this style controls the sidebar,
112+
but it also has to set the margin for the main content of the main frame */
113+
#single-spa-main {
114+
margin-left: var(--sideBarWidth);
115+
}
116+
117+
/* if sidebar is disabled, then remove margin for the main content */
118+
body.no-sidebar #single-spa-main {
119+
margin-left: 0;
120+
}
121+
122+
.main-menu-wrapper {
123+
left: 0;
124+
height: calc(100vh - var(--navbarHeight));
125+
position: absolute;
126+
top: var(--navbarHeight);
127+
}
128+
}

src/components/NavBar/index.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import NotificationsMenu from "../NotificationsMenu";
1717

1818
const NavBar = () => {
1919
// all menu options
20-
const menu = useSelector((state) => state.menu);
20+
const menu = useSelector((state) => state.menu.categories);
2121
// flat list of all apps
2222
const apps = useMemo(() => _.flatMap(menu, "apps"), [menu]);
2323
// Active app

src/constants/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,7 @@ export const ACTIONS = {
1313
},
1414
MENU: {
1515
SET_APP_MENU: "SET_APP_MENU",
16+
DISABLE_SIDEBAR_FOR_ROUTE: "DISABLE_SIDEBAR_FOR_ROUTE",
17+
ENABLE_SIDEBAR_FOR_ROUTE: "ENABLE_SIDEBAR_FOR_ROUTE",
1618
},
1719
};

src/global.css

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
:root {
22
--navbarHeight: 60px;
3+
--sideBarWidth: 270px;
34
}
45

56
/* This global style is necessary to completely hide the iframe injected into
@@ -8,16 +9,3 @@
89
border: none;
910
display: none;
1011
}
11-
12-
.main-menu-wrapper {
13-
background-color: #fff;
14-
}
15-
16-
@media (min-width: 1024px) {
17-
.main-menu-wrapper {
18-
left: 0;
19-
height: calc(100vh - var(--navbarHeight));
20-
position: absolute;
21-
top: var(--navbarHeight);
22-
}
23-
}

src/hooks/useMatchSomeRoute.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { useLocation, matchPath } from "@reach/router";
2+
import _ from "lodash";
3+
4+
/**
5+
* Check if any of the passed paths match the current route.
6+
*
7+
* @param {string[]} paths paths of the routes
8+
*
9+
* @returns {{ uri: string, path: string, params: {} }} matched route params
10+
*/
11+
const useMatchSomeRoute = (paths) => {
12+
const location = useLocation();
13+
14+
return _.find(paths, (path) => {
15+
const result = matchPath(path, location.pathname);
16+
17+
return result
18+
? {
19+
params: result.params,
20+
uri: result.uri,
21+
path,
22+
}
23+
: null;
24+
});
25+
};
26+
27+
export default useMatchSomeRoute;

src/reducers/menu.js

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@ import _ from "lodash";
55
import { ACTIONS, APP_CATEGORIES } from "../constants";
66

77
/**
8-
* Default Apps Menu structure.
8+
* Menu State Initial Structure
99
*/
10-
const initialState = APP_CATEGORIES;
10+
const initialState = {
11+
categories: APP_CATEGORIES, // Default Apps Menu structure.
12+
disabledRoutes: [],
13+
};
1114

1215
/**
1316
* Find indexes of the category and app in the menu structure by the app's path.
@@ -78,19 +81,57 @@ const menuReducer = (state = initialState, action) => {
7881
switch (action.type) {
7982
case ACTIONS.MENU.SET_APP_MENU: {
8083
const { path, menuOptions } = action.payload;
81-
const { categoryIndex, appIndex } = findIndexByPath(state, path);
84+
const { categoryIndex, appIndex } = findIndexByPath(
85+
state.categories,
86+
path
87+
);
8288

8389
// if we cannot find the app, just log error and don't try to update it
8490
if (categoryIndex === -1 || appIndex === -1) {
8591
console.error(`App is not found by path "${path}".`);
8692
return state;
8793
}
8894

89-
return updateApp(state, categoryIndex, appIndex, (app) => ({
90-
...app,
91-
menu: menuOptions,
92-
}));
95+
return {
96+
...state,
97+
categories: updateApp(
98+
state.categories,
99+
categoryIndex,
100+
appIndex,
101+
(app) => ({
102+
...app,
103+
menu: menuOptions,
104+
})
105+
),
106+
};
107+
}
108+
109+
case ACTIONS.MENU.DISABLE_SIDEBAR_FOR_ROUTE: {
110+
// if route is already disabled, don't do anything
111+
if (state.disabledRoutes.indexOf(action.payload) > -1) {
112+
return state;
113+
}
114+
115+
return {
116+
...state,
117+
// add route to the disabled list
118+
disabledRoutes: [...state.disabledRoutes, action.payload],
119+
};
93120
}
121+
122+
case ACTIONS.MENU.ENABLE_SIDEBAR_FOR_ROUTE: {
123+
// if route is not disabled, don't do anything
124+
if (state.disabledRoutes.indexOf(action.payload) === -1) {
125+
return state;
126+
}
127+
128+
return {
129+
...state,
130+
// remove the route from the disabled list
131+
disabledRoutes: _.without(state.disabledRoutes, action.payload),
132+
};
133+
}
134+
94135
default:
95136
return state;
96137
}

src/root.component.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,17 @@ import React from "react";
55
import App from "./App";
66
import { Provider } from "react-redux";
77
import store from "./store";
8+
import { createHistory, LocationProvider } from "@reach/router";
9+
10+
// History for location provider
11+
const history = createHistory(window);
812

913
export default function Root() {
1014
return (
11-
<Provider store={store}>
12-
<App />
13-
</Provider>
15+
<LocationProvider history={history}>
16+
<Provider store={store}>
17+
<App />
18+
</Provider>
19+
</LocationProvider>
1420
);
1521
}

0 commit comments

Comments
 (0)