├── .gitattributes ├── .gitignore ├── README.md ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── src ├── App.test.tsx ├── App.tsx ├── assets │ ├── images │ │ └── typescript-logo-240.png │ └── index.ts ├── components │ ├── common │ │ ├── Sidebar.tsx │ │ ├── SidebarItem.tsx │ │ ├── SidebarItemCollapse.tsx │ │ └── Topbar.tsx │ └── layout │ │ ├── MainLayout.tsx │ │ └── PageWrapper.tsx ├── configs │ ├── colorConfigs.ts │ └── sizeConfigs.ts ├── index.tsx ├── pages │ ├── changelog │ │ └── ChangelogPage.tsx │ ├── component │ │ ├── AlertPage.tsx │ │ ├── ButtonPage.tsx │ │ └── ComponentPageLayout.tsx │ ├── dashboard │ │ ├── AnalyticsPage.tsx │ │ ├── DashboardIndex.tsx │ │ ├── DashboardPageLayout.tsx │ │ ├── DefaultPage.tsx │ │ └── SaasPage.tsx │ ├── documentation │ │ └── DocumentationPage.tsx │ ├── home │ │ └── HomePage.tsx │ └── installation │ │ └── InstallationPage.tsx ├── react-app-env.d.ts ├── redux │ ├── features │ │ └── appStateSlice.ts │ └── store.ts ├── reportWebVitals.ts ├── routes │ ├── appRoutes.tsx │ ├── config.ts │ └── index.tsx └── setupTests.ts ├── tsconfig.json └── yarn.lock /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | !["React sidebar with dropdown menu"](https://user-images.githubusercontent.com/67447840/200093301-c5560000-3d09-4f21-b94d-50d2071d09dd.png "React sidebar with dropdown menu") 2 | 3 | # Video tutorial 4 | 5 | https://youtu.be/XwnZLgIfIvg 6 | 7 | # Reference 8 | 9 | - Create react app:https://create-react-app.dev/ 10 | - Material-UI: https://mui.com/ 11 | - React-router: https://reactrouter.com/ 12 | 13 | # Preview 14 | 15 | !["React sidebar with dropdown menu"](https://user-images.githubusercontent.com/67447840/200093500-325d52c0-365e-4bef-9f63-3be736c917cb.gif "React sidebar with dropdown menu") -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-sidebar-with-dropdown", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@emotion/react": "^11.10.5", 7 | "@emotion/styled": "^11.10.5", 8 | "@mui/icons-material": "^5.10.9", 9 | "@mui/material": "^5.10.12", 10 | "@reduxjs/toolkit": "^1.8.6", 11 | "@testing-library/jest-dom": "^5.14.1", 12 | "@testing-library/react": "^13.0.0", 13 | "@testing-library/user-event": "^13.2.1", 14 | "@types/jest": "^27.0.1", 15 | "@types/node": "^16.7.13", 16 | "@types/react": "^18.0.0", 17 | "@types/react-dom": "^18.0.0", 18 | "react": "^18.2.0", 19 | "react-dom": "^18.2.0", 20 | "react-redux": "^8.0.4", 21 | "react-router-dom": "^6.4.3", 22 | "react-scripts": "5.0.1", 23 | "typescript": "^4.4.2", 24 | "web-vitals": "^2.1.0" 25 | }, 26 | "scripts": { 27 | "start": "react-scripts start", 28 | "build": "react-scripts build", 29 | "test": "react-scripts test", 30 | "eject": "react-scripts eject" 31 | }, 32 | "eslintConfig": { 33 | "extends": [ 34 | "react-app", 35 | "react-app/jest" 36 | ] 37 | }, 38 | "browserslist": { 39 | "production": [ 40 | ">0.2%", 41 | "not dead", 42 | "not op_mini all" 43 | ], 44 | "development": [ 45 | "last 1 chrome version", 46 | "last 1 firefox version", 47 | "last 1 safari version" 48 | ] 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trananhtuat/react-sidebar-with-dropdown/5331d1cd9d33c06c6b5d92bc3ac19a0b288c1157/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | 16 | 25 | 26 | 27 | React App 28 | 29 | 30 | 31 | 32 |
33 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trananhtuat/react-sidebar-with-dropdown/5331d1cd9d33c06c6b5d92bc3ac19a0b288c1157/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trananhtuat/react-sidebar-with-dropdown/5331d1cd9d33c06c6b5d92bc3ac19a0b288c1157/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render, screen } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | render(); 7 | const linkElement = screen.getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { BrowserRouter, Route, Routes } from "react-router-dom"; 2 | import MainLayout from "./components/layout/MainLayout"; 3 | import { routes } from "./routes"; 4 | 5 | function App() { 6 | return ( 7 | 8 | 9 | }> 10 | {routes} 11 | 12 | 13 | 14 | ); 15 | } 16 | 17 | export default App; 18 | -------------------------------------------------------------------------------- /src/assets/images/typescript-logo-240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trananhtuat/react-sidebar-with-dropdown/5331d1cd9d33c06c6b5d92bc3ac19a0b288c1157/src/assets/images/typescript-logo-240.png -------------------------------------------------------------------------------- /src/assets/index.ts: -------------------------------------------------------------------------------- 1 | const assets = { 2 | images: { 3 | logo: require("./images/typescript-logo-240.png") 4 | } 5 | }; 6 | 7 | export default assets; -------------------------------------------------------------------------------- /src/components/common/Sidebar.tsx: -------------------------------------------------------------------------------- 1 | import { Avatar, Drawer, List, Stack, Toolbar } from "@mui/material"; 2 | import assets from "../../assets"; 3 | import colorConfigs from "../../configs/colorConfigs"; 4 | import sizeConfigs from "../../configs/sizeConfigs"; 5 | import appRoutes from "../../routes/appRoutes"; 6 | import SidebarItem from "./SidebarItem"; 7 | import SidebarItemCollapse from "./SidebarItemCollapse"; 8 | 9 | const Sidebar = () => { 10 | return ( 11 | 25 | 26 | 27 | 32 | 33 | 34 | 35 | {appRoutes.map((route, index) => ( 36 | route.sidebarProps ? ( 37 | route.child ? ( 38 | 39 | ) : ( 40 | 41 | ) 42 | ) : null 43 | ))} 44 | 45 | 46 | ); 47 | }; 48 | 49 | export default Sidebar; -------------------------------------------------------------------------------- /src/components/common/SidebarItem.tsx: -------------------------------------------------------------------------------- 1 | import { ListItemButton, ListItemIcon } from "@mui/material"; 2 | import { useSelector } from "react-redux"; 3 | import { Link } from "react-router-dom"; 4 | import colorConfigs from "../../configs/colorConfigs"; 5 | import { RootState } from "../../redux/store"; 6 | import { RouteType } from "../../routes/config"; 7 | 8 | type Props = { 9 | item: RouteType; 10 | }; 11 | 12 | const SidebarItem = ({ item }: Props) => { 13 | const { appState } = useSelector((state: RootState) => state.appState); 14 | 15 | return ( 16 | item.sidebarProps && item.path ? ( 17 | 29 | 32 | {item.sidebarProps.icon && item.sidebarProps.icon} 33 | 34 | {item.sidebarProps.displayText} 35 | 36 | ) : null 37 | ); 38 | }; 39 | 40 | export default SidebarItem; -------------------------------------------------------------------------------- /src/components/common/SidebarItemCollapse.tsx: -------------------------------------------------------------------------------- 1 | import { Collapse, List, ListItemButton, ListItemIcon, ListItemText, Typography } from "@mui/material"; 2 | import { useEffect, useState } from "react"; 3 | import colorConfigs from "../../configs/colorConfigs"; 4 | import { RouteType } from "../../routes/config"; 5 | import ExpandLessOutlinedIcon from '@mui/icons-material/ExpandLessOutlined'; 6 | import ExpandMoreOutlinedIcon from '@mui/icons-material/ExpandMoreOutlined'; 7 | import SidebarItem from "./SidebarItem"; 8 | import { useSelector } from "react-redux"; 9 | import { RootState } from "../../redux/store"; 10 | 11 | type Props = { 12 | item: RouteType; 13 | }; 14 | 15 | const SidebarItemCollapse = ({ item }: Props) => { 16 | const [open, setOpen] = useState(false); 17 | 18 | const { appState } = useSelector((state: RootState) => state.appState); 19 | 20 | useEffect(() => { 21 | if (appState.includes(item.state)) { 22 | setOpen(true); 23 | } 24 | }, [appState, item]); 25 | 26 | return ( 27 | item.sidebarProps ? ( 28 | <> 29 | setOpen(!open)} 31 | sx={{ 32 | "&: hover": { 33 | backgroundColor: colorConfigs.sidebar.hoverBg 34 | }, 35 | paddingY: "12px", 36 | paddingX: "24px" 37 | }} 38 | > 39 | 42 | {item.sidebarProps.icon && item.sidebarProps.icon} 43 | 44 | 48 | {item.sidebarProps.displayText} 49 | 50 | } 51 | /> 52 | {open ? : } 53 | 54 | 55 | 56 | {item.child?.map((route, index) => ( 57 | route.sidebarProps ? ( 58 | route.child ? ( 59 | 60 | ) : ( 61 | 62 | ) 63 | ) : null 64 | ))} 65 | 66 | 67 | 68 | ) : null 69 | ); 70 | }; 71 | 72 | export default SidebarItemCollapse; -------------------------------------------------------------------------------- /src/components/common/Topbar.tsx: -------------------------------------------------------------------------------- 1 | import { AppBar, Toolbar, Typography } from "@mui/material"; 2 | import colorConfigs from "../../configs/colorConfigs"; 3 | import sizeConfigs from "../../configs/sizeConfigs"; 4 | 5 | const Topbar = () => { 6 | return ( 7 | 17 | 18 | 19 | React sidebar with dropdown 20 | 21 | 22 | 23 | ); 24 | }; 25 | 26 | export default Topbar; -------------------------------------------------------------------------------- /src/components/layout/MainLayout.tsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from "react-router-dom"; 2 | import { Box, Toolbar } from "@mui/material"; 3 | import colorConfigs from "../../configs/colorConfigs"; 4 | import sizeConfigs from "../../configs/sizeConfigs"; 5 | import Sidebar from "../common/Sidebar"; 6 | import Topbar from "../common/Topbar"; 7 | 8 | const MainLayout = () => { 9 | return ( 10 | 11 | 12 | 19 | 20 | 21 | 31 | 32 | 33 | 34 | 35 | ); 36 | }; 37 | 38 | export default MainLayout; -------------------------------------------------------------------------------- /src/components/layout/PageWrapper.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode, useEffect } from "react"; 2 | import { useDispatch } from "react-redux"; 3 | import { setAppState } from "../../redux/features/appStateSlice"; 4 | 5 | type Props = { 6 | state?: string, 7 | children: ReactNode; 8 | }; 9 | 10 | const PageWrapper = (props: Props) => { 11 | const dispatch = useDispatch(); 12 | 13 | useEffect(() => { 14 | if (props.state) { 15 | dispatch(setAppState(props.state)); 16 | } 17 | }, [dispatch, props]); 18 | 19 | return ( 20 | <>{props.children} 21 | ); 22 | }; 23 | 24 | export default PageWrapper; -------------------------------------------------------------------------------- /src/configs/colorConfigs.ts: -------------------------------------------------------------------------------- 1 | import { colors } from "@mui/material"; 2 | 3 | const colorConfigs = { 4 | sidebar: { 5 | bg: "#233044", 6 | color: "#eeeeee", 7 | hoverBg: "#1e293a", 8 | activeBg: "#1e253a" 9 | }, 10 | topbar: { 11 | bg: "#fff", 12 | color: "#000" 13 | }, 14 | mainBg: colors.grey["100"] 15 | }; 16 | 17 | export default colorConfigs; -------------------------------------------------------------------------------- /src/configs/sizeConfigs.ts: -------------------------------------------------------------------------------- 1 | const sizeConfigs = { 2 | sidebar: { 3 | width: "300px" 4 | } 5 | }; 6 | 7 | export default sizeConfigs; -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import { CssBaseline } from '@mui/material'; 2 | import React from 'react'; 3 | import ReactDOM from 'react-dom/client'; 4 | import { Provider } from 'react-redux'; 5 | import App from './App'; 6 | import { store } from './redux/store'; 7 | import reportWebVitals from './reportWebVitals'; 8 | 9 | const root = ReactDOM.createRoot( 10 | document.getElementById('root') as HTMLElement 11 | ); 12 | root.render( 13 | 14 | 15 | 16 | 17 | 18 | 19 | ); 20 | 21 | // If you want to start measuring performance in your app, pass a function 22 | // to log results (for example: reportWebVitals(console.log)) 23 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 24 | reportWebVitals(); 25 | -------------------------------------------------------------------------------- /src/pages/changelog/ChangelogPage.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | type Props = {}; 4 | 5 | const ChangelogPage = (props: Props) => { 6 | return ( 7 |
ChangelogPage
8 | ); 9 | }; 10 | 11 | export default ChangelogPage; -------------------------------------------------------------------------------- /src/pages/component/AlertPage.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | type Props = {}; 4 | 5 | const AlertPage = (props: Props) => { 6 | return ( 7 |
AlertPage
8 | ); 9 | }; 10 | 11 | export default AlertPage; -------------------------------------------------------------------------------- /src/pages/component/ButtonPage.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | type Props = {}; 4 | 5 | const ButtonPage = (props: Props) => { 6 | return ( 7 |
ButtonPage
8 | ); 9 | }; 10 | 11 | export default ButtonPage; -------------------------------------------------------------------------------- /src/pages/component/ComponentPageLayout.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Outlet } from 'react-router-dom'; 3 | 4 | type Props = {}; 5 | 6 | const ComponentPageLayout = (props: Props) => { 7 | return ( 8 | <> 9 | ); 10 | }; 11 | 12 | export default ComponentPageLayout; -------------------------------------------------------------------------------- /src/pages/dashboard/AnalyticsPage.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | type Props = {}; 4 | 5 | const AnalyticsPage = (props: Props) => { 6 | return ( 7 |
AnalyticsPage
8 | ); 9 | }; 10 | 11 | export default AnalyticsPage; -------------------------------------------------------------------------------- /src/pages/dashboard/DashboardIndex.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | type Props = {}; 4 | 5 | const DashboardIndex = (props: Props) => { 6 | return ( 7 |
DashboardIndex
8 | ); 9 | }; 10 | 11 | export default DashboardIndex; -------------------------------------------------------------------------------- /src/pages/dashboard/DashboardPageLayout.tsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from "react-router-dom"; 2 | 3 | const DashboardPageLayout = () => { 4 | return ( 5 | <> 6 | ); 7 | }; 8 | 9 | export default DashboardPageLayout; -------------------------------------------------------------------------------- /src/pages/dashboard/DefaultPage.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | type Props = {}; 4 | 5 | const DefaultPage = (props: Props) => { 6 | return ( 7 |
DefaultPage
8 | ); 9 | }; 10 | 11 | export default DefaultPage; -------------------------------------------------------------------------------- /src/pages/dashboard/SaasPage.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | type Props = {}; 4 | 5 | const SaasPage = (props: Props) => { 6 | return ( 7 |
SaasPage
8 | ); 9 | }; 10 | 11 | export default SaasPage; -------------------------------------------------------------------------------- /src/pages/documentation/DocumentationPage.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | type Props = {}; 4 | 5 | const DocumentationPage = (props: Props) => { 6 | return ( 7 |
DocumentationPage
8 | ); 9 | }; 10 | 11 | export default DocumentationPage; -------------------------------------------------------------------------------- /src/pages/home/HomePage.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | type Props = {}; 4 | 5 | const HomePage = (props: Props) => { 6 | return ( 7 |
HomePage
8 | ); 9 | }; 10 | 11 | export default HomePage; -------------------------------------------------------------------------------- /src/pages/installation/InstallationPage.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | type Props = {}; 4 | 5 | const InstallationPage = (props: Props) => { 6 | return ( 7 |
InstallationPage
8 | ); 9 | }; 10 | 11 | export default InstallationPage; -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/redux/features/appStateSlice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, PayloadAction } from "@reduxjs/toolkit"; 2 | 3 | type appState = { 4 | appState: string; 5 | }; 6 | 7 | const initialState: appState = { 8 | appState: "" 9 | }; 10 | 11 | export const appStateSlice = createSlice({ 12 | name: "appState", 13 | initialState, 14 | reducers: { 15 | setAppState: (state, action: PayloadAction) => { 16 | state.appState = action.payload; 17 | } 18 | } 19 | }); 20 | 21 | export const { 22 | setAppState 23 | } = appStateSlice.actions; 24 | 25 | export default appStateSlice.reducer; -------------------------------------------------------------------------------- /src/redux/store.ts: -------------------------------------------------------------------------------- 1 | import { configureStore } from "@reduxjs/toolkit"; 2 | import appStateSlice from "./features/appStateSlice"; 3 | 4 | export const store = configureStore({ 5 | reducer: { 6 | appState: appStateSlice 7 | } 8 | }); 9 | 10 | export type RootState = ReturnType; -------------------------------------------------------------------------------- /src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from 'web-vitals'; 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry); 7 | getFID(onPerfEntry); 8 | getFCP(onPerfEntry); 9 | getLCP(onPerfEntry); 10 | getTTFB(onPerfEntry); 11 | }); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /src/routes/appRoutes.tsx: -------------------------------------------------------------------------------- 1 | import DashboardPageLayout from "../pages/dashboard/DashboardPageLayout"; 2 | import HomePage from "../pages/home/HomePage"; 3 | import { RouteType } from "./config"; 4 | import DefaultPage from "../pages/dashboard/DefaultPage"; 5 | import DashboardIndex from "../pages/dashboard/DashboardIndex"; 6 | import ChangelogPage from "../pages/changelog/ChangelogPage"; 7 | import AnalyticsPage from "../pages/dashboard/AnalyticsPage"; 8 | import SaasPage from "../pages/dashboard/SaasPage"; 9 | import ComponentPageLayout from "../pages/component/ComponentPageLayout"; 10 | import DashboardOutlinedIcon from '@mui/icons-material/DashboardOutlined'; 11 | import AppsOutlinedIcon from '@mui/icons-material/AppsOutlined'; 12 | import ArticleOutlinedIcon from '@mui/icons-material/ArticleOutlined'; 13 | import FormatListBulletedOutlinedIcon from '@mui/icons-material/FormatListBulletedOutlined'; 14 | import FileDownloadOutlinedIcon from '@mui/icons-material/FileDownloadOutlined'; 15 | import AlertPage from "../pages/component/AlertPage"; 16 | import ButtonPage from "../pages/component/ButtonPage"; 17 | import InstallationPage from "../pages/installation/InstallationPage"; 18 | import DocumentationPage from "../pages/documentation/DocumentationPage"; 19 | 20 | const appRoutes: RouteType[] = [ 21 | { 22 | index: true, 23 | element: , 24 | state: "home" 25 | }, 26 | { 27 | path: "/installation", 28 | element: , 29 | state: "installation", 30 | sidebarProps: { 31 | displayText: "Installation", 32 | icon: 33 | } 34 | }, 35 | { 36 | path: "/dashboard", 37 | element: , 38 | state: "dashboard", 39 | sidebarProps: { 40 | displayText: "Dashboard", 41 | icon: 42 | }, 43 | child: [ 44 | { 45 | index: true, 46 | element: , 47 | state: "dashboard.index" 48 | }, 49 | { 50 | path: "/dashboard/default", 51 | element: , 52 | state: "dashboard.default", 53 | sidebarProps: { 54 | displayText: "Default" 55 | }, 56 | }, 57 | { 58 | path: "/dashboard/analytics", 59 | element: , 60 | state: "dashboard.analytics", 61 | sidebarProps: { 62 | displayText: "Analytic" 63 | } 64 | }, 65 | { 66 | path: "/dashboard/saas", 67 | element: , 68 | state: "dashboard.saas", 69 | sidebarProps: { 70 | displayText: "Saas" 71 | } 72 | } 73 | ] 74 | }, 75 | { 76 | path: "/component", 77 | element: , 78 | state: "component", 79 | sidebarProps: { 80 | displayText: "Components", 81 | icon: 82 | }, 83 | child: [ 84 | { 85 | path: "/component/alert", 86 | element: , 87 | state: "component.alert", 88 | sidebarProps: { 89 | displayText: "Alert" 90 | }, 91 | }, 92 | { 93 | path: "/component/button", 94 | element: , 95 | state: "component.button", 96 | sidebarProps: { 97 | displayText: "Button" 98 | } 99 | } 100 | ] 101 | }, 102 | { 103 | path: "/documentation", 104 | element: , 105 | state: "documentation", 106 | sidebarProps: { 107 | displayText: "Documentation", 108 | icon: 109 | } 110 | }, 111 | { 112 | path: "/changelog", 113 | element: , 114 | state: "changelog", 115 | sidebarProps: { 116 | displayText: "Changelog", 117 | icon: 118 | } 119 | } 120 | ]; 121 | 122 | export default appRoutes; -------------------------------------------------------------------------------- /src/routes/config.ts: -------------------------------------------------------------------------------- 1 | import { ReactNode } from "react"; 2 | 3 | export type RouteType = { 4 | element: ReactNode, 5 | state: string, 6 | index?: boolean, 7 | path?: string, 8 | child?: RouteType[], 9 | sidebarProps?: { 10 | displayText: string, 11 | icon?: ReactNode; 12 | }; 13 | }; -------------------------------------------------------------------------------- /src/routes/index.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from "react"; 2 | import { Route } from "react-router-dom"; 3 | import PageWrapper from "../components/layout/PageWrapper"; 4 | import appRoutes from "./appRoutes"; 5 | import { RouteType } from "./config"; 6 | 7 | const generateRoute = (routes: RouteType[]): ReactNode => { 8 | return routes.map((route, index) => ( 9 | route.index ? ( 10 | 14 | {route.element} 15 | } 16 | key={index} 17 | /> 18 | ) : ( 19 | 23 | {route.element} 24 | 25 | } 26 | key={index} 27 | > 28 | {route.child && ( 29 | generateRoute(route.child) 30 | )} 31 | 32 | ) 33 | )); 34 | }; 35 | 36 | export const routes: ReactNode = generateRoute(appRoutes); -------------------------------------------------------------------------------- /src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | --------------------------------------------------------------------------------