diff --git a/.nvmrc b/.nvmrc
new file mode 100644
index 0000000..c2f6421
--- /dev/null
+++ b/.nvmrc
@@ -0,0 +1 @@
+10.22.1
diff --git a/README.md b/README.md
index 413234e..3ec4783 100644
--- a/README.md
+++ b/README.md
@@ -64,6 +64,8 @@ This app exports functions to be imported by other microapps.
- `setAppMenu` - sets sidebar menu for the app by app's `path`
- `getAuthUserTokens` - returns a promise which resolves to object with user tokens `{ tokenV3, tokenV2 }`
- `getAuthUserProfile` - returns a promise which resolves to the user profile object
+- `disableSidebarForRoute` - disable (remove) sidebar for some route
+- `enableSidebarForRoute` - enable sidebar for the route, which was previously disabled
#### How to export
@@ -89,13 +91,15 @@ For example see https://github.com/topcoder-platform/micro-frontends-react-app
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:
```js
- module.exports = {
+ module.exports = {
login: () => {},
logout: () => {},
setAppMenu: () => {},
getAuthUserTokens: () => new Promise(() => {}),
getAuthUserProfile: () => new Promise(() => {}),
- };
+ disableSidebarForRoute: () => {},
+ enableSidebarForRoute: () => {},
+ };
```
##### How to import in Angular app
@@ -112,11 +116,13 @@ For example see https://github.com/topcoder-platform/micro-frontends-angular-app
2. Add type definition in `src/typings.d.ts`:
```js
declare module '@topcoder/micro-frontends-navbar-app' {
- export const login: any;
- export const logout: any;
- export const setAppMenu: any;
- export const getAuthUserTokens: any;
- export const getAuthUserProfile: any;
+ export const login: any;
+ export const logout: any;
+ export const setAppMenu: any;
+ export const getAuthUserTokens: any;
+ export const getAuthUserProfile: any;
+ export const disableSidebarForRoute: any;
+ export const enableSidebarForRoute: any;
}
```
diff --git a/src/App.jsx b/src/App.jsx
index 91f41fb..58aa4dc 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -1,21 +1,23 @@
/**
* Main App component
*/
-import React, { useState, useCallback, useMemo } from "react";
+import React, { useState, useCallback, useMemo, useEffect } from "react";
import _ from "lodash";
import MainMenu from "./components/MainMenu";
import NavBar from "./components/NavBar";
-import { Router, createHistory, LocationProvider } from "@reach/router";
+import { Router } from "@reach/router";
import { useSelector } from "react-redux";
-
-// History for location provider
-let history = createHistory(window);
+import useMatchSomeRoute from "./hooks/useMatchSomeRoute";
const App = () => {
// all menu options
- const menu = useSelector((state) => state.menu);
+ const menu = useSelector((state) => state.menu.categories);
// flat list of all apps (only updated when menu updated in the Redux store)
const apps = useMemo(() => _.flatMap(menu, "apps"), [menu]);
+ // list of routes where we have to disabled sidebar
+ const disabledRoutes = useSelector((state) => state.menu.disabledRoutes);
+ // `true` is sidebar has to be disabled for the current route
+ const isSideBarDisabled = useMatchSomeRoute(disabledRoutes);
// Left sidebar collapse state
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
// Toggle left sidebar callback
@@ -23,23 +25,34 @@ const App = () => {
setSidebarCollapsed(!sidebarCollapsed);
}, [sidebarCollapsed, setSidebarCollapsed]);
+ // set/remove class for the whole page, to know if sidebar is present or no
+ useEffect(() => {
+ if (isSideBarDisabled) {
+ document.body.classList.add("no-sidebar");
+ } else {
+ document.body.classList.remove("no-sidebar");
+ }
+ }, [isSideBarDisabled]);
+
return (
-
+ <>
-
-
- {apps.map((app) => (
-
- ))}
-
-
-
+ {!isSideBarDisabled && (
+
+
+ {apps.map((app) => (
+
+ ))}
+
+
+ )}
+ >
);
};
diff --git a/src/actions/menu.js b/src/actions/menu.js
index e1aab9e..835c9b7 100644
--- a/src/actions/menu.js
+++ b/src/actions/menu.js
@@ -13,4 +13,30 @@ export default {
type: ACTIONS.MENU.SET_APP_MENU,
payload: { path, menuOptions },
}),
+
+ /**
+ * Disable sidebar for route.
+ *
+ * See how we can define route here https://reach.tech/router/api/Match.
+ *
+ * @param {String} route route path
+ *
+ * @returns {{ type: String, payload: any }} action object
+ */
+ disableSidebarForRoute: (route) => ({
+ type: ACTIONS.MENU.DISABLE_SIDEBAR_FOR_ROUTE,
+ payload: route,
+ }),
+
+ /**
+ * Enable sidebar for route.
+ *
+ * @param {String} route route path
+ *
+ * @returns {{ type: String, payload: any }} action object
+ */
+ enableSidebarForRoute: (route) => ({
+ type: ACTIONS.MENU.ENABLE_SIDEBAR_FOR_ROUTE,
+ payload: route,
+ }),
};
diff --git a/src/components/AllAppsMenu/index.jsx b/src/components/AllAppsMenu/index.jsx
index 5765b0e..171b5f9 100644
--- a/src/components/AllAppsMenu/index.jsx
+++ b/src/components/AllAppsMenu/index.jsx
@@ -12,7 +12,7 @@ import "./styles.css";
import { useSelector } from "react-redux";
const AllAppsMenu = () => {
- const menu = useSelector((state) => state.menu);
+ const menu = useSelector((state) => state.menu.categories);
const [isOpenMenu, setIsOpenMenu] = useState(false);
const closeMenu = useCallback(() => {
diff --git a/src/components/MainMenu/styles.css b/src/components/MainMenu/styles.css
index b8fa15b..083dd85 100644
--- a/src/components/MainMenu/styles.css
+++ b/src/components/MainMenu/styles.css
@@ -1,6 +1,17 @@
+/* this style controls the sidebar,
+ but it also has to set the margin for the main content of the main frame */
+#single-spa-main {
+ margin-left: 0;
+}
+
+.main-menu-wrapper {
+ background-color: #fff;
+}
+
.main-menu-header {
margin: 22px 0 20px 21px;
}
+
.menu-toggle-icon {
width: 10px;
height: 10px;
@@ -8,7 +19,6 @@
cursor: pointer;
}
-
.main-menu-mobile {
background-color: #fff;
left: 0;
@@ -32,8 +42,9 @@
}
.main-menu {
- width: 270px;
+ width: var(--sideBarWidth);
}
+
.main-menu-collapsed {
width: auto;
}
@@ -95,3 +106,23 @@
transform: rotate(180deg);
}
}
+
+@media (min-width: 1024px) {
+ /* this style controls the sidebar,
+ but it also has to set the margin for the main content of the main frame */
+ #single-spa-main {
+ margin-left: var(--sideBarWidth);
+ }
+
+ /* if sidebar is disabled, then remove margin for the main content */
+ body.no-sidebar #single-spa-main {
+ margin-left: 0;
+ }
+
+ .main-menu-wrapper {
+ left: 0;
+ height: calc(100vh - var(--navbarHeight));
+ position: absolute;
+ top: var(--navbarHeight);
+ }
+}
diff --git a/src/components/NavBar/index.jsx b/src/components/NavBar/index.jsx
index 0a37a8e..a2276ef 100644
--- a/src/components/NavBar/index.jsx
+++ b/src/components/NavBar/index.jsx
@@ -17,7 +17,7 @@ import NotificationsMenu from "../NotificationsMenu";
const NavBar = () => {
// all menu options
- const menu = useSelector((state) => state.menu);
+ const menu = useSelector((state) => state.menu.categories);
// flat list of all apps
const apps = useMemo(() => _.flatMap(menu, "apps"), [menu]);
// Active app
diff --git a/src/constants/index.js b/src/constants/index.js
index bfafa76..7df810f 100644
--- a/src/constants/index.js
+++ b/src/constants/index.js
@@ -13,5 +13,7 @@ export const ACTIONS = {
},
MENU: {
SET_APP_MENU: "SET_APP_MENU",
+ DISABLE_SIDEBAR_FOR_ROUTE: "DISABLE_SIDEBAR_FOR_ROUTE",
+ ENABLE_SIDEBAR_FOR_ROUTE: "ENABLE_SIDEBAR_FOR_ROUTE",
},
};
diff --git a/src/global.css b/src/global.css
index 3c2634a..6bc313a 100644
--- a/src/global.css
+++ b/src/global.css
@@ -1,5 +1,6 @@
:root {
--navbarHeight: 60px;
+ --sideBarWidth: 270px;
}
/* This global style is necessary to completely hide the iframe injected into
@@ -8,16 +9,3 @@
border: none;
display: none;
}
-
-.main-menu-wrapper {
- background-color: #fff;
-}
-
-@media (min-width: 1024px) {
- .main-menu-wrapper {
- left: 0;
- height: calc(100vh - var(--navbarHeight));
- position: absolute;
- top: var(--navbarHeight);
- }
-}
diff --git a/src/hooks/useMatchSomeRoute.js b/src/hooks/useMatchSomeRoute.js
new file mode 100644
index 0000000..6c403bc
--- /dev/null
+++ b/src/hooks/useMatchSomeRoute.js
@@ -0,0 +1,27 @@
+import { useLocation, matchPath } from "@reach/router";
+import _ from "lodash";
+
+/**
+ * Check if any of the passed paths match the current route.
+ *
+ * @param {string[]} paths paths of the routes
+ *
+ * @returns {{ uri: string, path: string, params: {} }} matched route params
+ */
+const useMatchSomeRoute = (paths) => {
+ const location = useLocation();
+
+ return _.find(paths, (path) => {
+ const result = matchPath(path, location.pathname);
+
+ return result
+ ? {
+ params: result.params,
+ uri: result.uri,
+ path,
+ }
+ : null;
+ });
+};
+
+export default useMatchSomeRoute;
diff --git a/src/reducers/menu.js b/src/reducers/menu.js
index cc031db..5338139 100644
--- a/src/reducers/menu.js
+++ b/src/reducers/menu.js
@@ -5,9 +5,12 @@ import _ from "lodash";
import { ACTIONS, APP_CATEGORIES } from "../constants";
/**
- * Default Apps Menu structure.
+ * Menu State Initial Structure
*/
-const initialState = APP_CATEGORIES;
+const initialState = {
+ categories: APP_CATEGORIES, // Default Apps Menu structure.
+ disabledRoutes: [],
+};
/**
* Find indexes of the category and app in the menu structure by the app's path.
@@ -78,7 +81,10 @@ const menuReducer = (state = initialState, action) => {
switch (action.type) {
case ACTIONS.MENU.SET_APP_MENU: {
const { path, menuOptions } = action.payload;
- const { categoryIndex, appIndex } = findIndexByPath(state, path);
+ const { categoryIndex, appIndex } = findIndexByPath(
+ state.categories,
+ path
+ );
// if we cannot find the app, just log error and don't try to update it
if (categoryIndex === -1 || appIndex === -1) {
@@ -86,11 +92,46 @@ const menuReducer = (state = initialState, action) => {
return state;
}
- return updateApp(state, categoryIndex, appIndex, (app) => ({
- ...app,
- menu: menuOptions,
- }));
+ return {
+ ...state,
+ categories: updateApp(
+ state.categories,
+ categoryIndex,
+ appIndex,
+ (app) => ({
+ ...app,
+ menu: menuOptions,
+ })
+ ),
+ };
+ }
+
+ case ACTIONS.MENU.DISABLE_SIDEBAR_FOR_ROUTE: {
+ // if route is already disabled, don't do anything
+ if (state.disabledRoutes.indexOf(action.payload) > -1) {
+ return state;
+ }
+
+ return {
+ ...state,
+ // add route to the disabled list
+ disabledRoutes: [...state.disabledRoutes, action.payload],
+ };
}
+
+ case ACTIONS.MENU.ENABLE_SIDEBAR_FOR_ROUTE: {
+ // if route is not disabled, don't do anything
+ if (state.disabledRoutes.indexOf(action.payload) === -1) {
+ return state;
+ }
+
+ return {
+ ...state,
+ // remove the route from the disabled list
+ disabledRoutes: _.without(state.disabledRoutes, action.payload),
+ };
+ }
+
default:
return state;
}
diff --git a/src/root.component.js b/src/root.component.js
index 8fe4157..ff4e98f 100644
--- a/src/root.component.js
+++ b/src/root.component.js
@@ -5,11 +5,17 @@ import React from "react";
import App from "./App";
import { Provider } from "react-redux";
import store from "./store";
+import { createHistory, LocationProvider } from "@reach/router";
+
+// History for location provider
+const history = createHistory(window);
export default function Root() {
return (
-
-
-
+
+
+
+
+
);
}
diff --git a/src/topcoder-micro-frontends-navbar-app.js b/src/topcoder-micro-frontends-navbar-app.js
index 10e4be4..67ecfcf 100644
--- a/src/topcoder-micro-frontends-navbar-app.js
+++ b/src/topcoder-micro-frontends-navbar-app.js
@@ -12,6 +12,8 @@ import Root from "./root.component";
import "./global.css?modules=false";
import {
setAppMenu,
+ disableSidebarForRoute,
+ enableSidebarForRoute,
getAuthUserTokens,
getAuthUserProfile,
} from "./utils/exports";
@@ -31,4 +33,12 @@ const lifecycles = singleSpaReact({
export const { bootstrap, mount, unmount } = lifecycles;
// list everything we want to export for other microapps here
-export { login, logout, setAppMenu, getAuthUserTokens, getAuthUserProfile };
+export {
+ login,
+ logout,
+ setAppMenu,
+ getAuthUserTokens,
+ getAuthUserProfile,
+ disableSidebarForRoute,
+ enableSidebarForRoute,
+};
diff --git a/src/utils/exports.js b/src/utils/exports.js
index 0f4c3fd..ec22d26 100644
--- a/src/utils/exports.js
+++ b/src/utils/exports.js
@@ -9,9 +9,15 @@ import store from "../store";
import menuActions from "../actions/menu";
// bind all the actions for exporting here
-export const { setAppMenu } = bindActionCreators(
+export const {
+ setAppMenu,
+ disableSidebarForRoute,
+ enableSidebarForRoute,
+} = bindActionCreators(
{
setAppMenu: menuActions.setAppMenu,
+ disableSidebarForRoute: menuActions.disableSidebarForRoute,
+ enableSidebarForRoute: menuActions.enableSidebarForRoute,
},
store.dispatch
);