-
-| Prop Name | Description | Type | Required | Default |
-| ---------------- | ------------------------------------------------------------------------------------------------------------------- | ------------------ | -------- | ----------------------------- |
-| avatar | MUI Avatar that displays | `Avatar` | yes | |
-| classes | Style overrides | `UserMenuClasses` | no | |
-| menu | Custom MUI Menu displayed when Avatar is clicked | Material-UI `Menu` | no | |
-| menuGroups | Groups of menu items that display | `UserMenuGroups[]` | no | |
-| menuSubtitle | Subtitle shown when menu is open | `string` | no | |
-| menuTitle | Title shown when menu is open | `string` | no | |
-| MenuProps | Property overrides for the MUI Menu | `MenuProps` | no | |
-| onClose | Function called when the menu is closed | `Function` | no | |
-| onOpen | Function called when the menu is opened | `Function` | no | |
-| useBottomSheetAt | Window pixel width at which the responsive bottom sheet menu is triggered (set to 0 to disable responsive behavior) | `number` | no | `theme.breakpoints.values.sm` |
-
-
-
-Any other props supplied will be provided to the root element (`div`).
+### User Menu API
+
+
+
| Attribute | Description | Type | Required | Default |
-| ----------- | ----------------------------------- | ------------- | -------- | ------- |
+| :---------- | :---------------------------------- | :------------ | :------- | :------ |
| chevron | Show chevron icon to the right | `boolean` | no | false |
| divider | Show a divider line below the item | `boolean` | no | true |
| icon | A component to render for the icon | `JSX.Element` | no | |
@@ -126,4 +132,4 @@ The `menuGroups` prop of the `` includes many properties from the [`
+
diff --git a/docs/src/componentDocs/UserMenu/markdown/UserMenuExamples.mdx b/docs/src/componentDocs/UserMenu/markdown/UserMenuExamples.mdx
new file mode 100644
index 000000000..985e3ba59
--- /dev/null
+++ b/docs/src/componentDocs/UserMenu/markdown/UserMenuExamples.mdx
@@ -0,0 +1,37 @@
+import {
+ UserMenu,
+ UserMenuAlternativeAvatarFormats,
+ UserMenuWithMenuHeader,
+ UserMenuWithCustomHeader,
+ UserMenuWithBottomSheet,
+} from '../examples';
+
+## User Menu
+
+The `
` is a combination of an avatar and a [MUI Menu](https://mui.com/api/menu/) that is used to hold user account-related information and actions. It is typically located in the top corner of your application within a toolbar.
+
+
+
+## Alternative Avatar Formats
+
+The User Menu supports multiple avatar formats (text, icon, image).
+
+
+
+## Adding a Menu Header
+
+You can add a header to the top of the menu by passing `menuTitle` and `menuSubtitle` props. The avatar will also appear in the menu header.
+
+
+
+## Customizing the Menu
+
+If you want to supply your own custom menu, you can pass in your own menu element via the `menu` prop.
+
+
+
+## Using a Bottom Sheet
+
+On larger screens the User Menu opens as a dropdown, but on smaller screens it opens as a bottomsheet. You can customize the point where this transition occurs via the `useBottomSheetAt` prop (default of 600px).
+
+
diff --git a/docs/src/componentDocs/UserMenu/playground/PlaygroundPage.tsx b/docs/src/componentDocs/UserMenu/playground/PlaygroundPage.tsx
new file mode 100644
index 000000000..ea2337fa7
--- /dev/null
+++ b/docs/src/componentDocs/UserMenu/playground/PlaygroundPage.tsx
@@ -0,0 +1,17 @@
+import React from 'react';
+import { Box } from '@mui/material';
+import UserMenuPlayground from './PropsPlayground';
+import { PreviewComponent } from './PreviewComponent';
+
+export const UserMenuPlaygroundComponent = (): JSX.Element => (
+
+
+
+
+);
diff --git a/docs/src/componentDocs/UserMenu/playground/PreviewComponent.tsx b/docs/src/componentDocs/UserMenu/playground/PreviewComponent.tsx
new file mode 100644
index 000000000..490fa95aa
--- /dev/null
+++ b/docs/src/componentDocs/UserMenu/playground/PreviewComponent.tsx
@@ -0,0 +1,90 @@
+import React from 'react';
+import { RootState } from '../../../redux/store';
+import { useAppSelector } from '../../../redux/hooks';
+import { createProps, hideDefaultPropsFromSnippet, removeEmptyLines } from '../../../shared/utilities';
+import { PropsType } from '../../../__types__';
+import PreviewComponentWithCode from '../../../shared/PreviewComponentWithCode';
+import { UserMenu } from '@brightlayer-ui/react-components/core/UserMenu';
+import Avatar from '@mui/material/Avatar';
+import Email from '@mui/icons-material/Email';
+import ExitToApp from '@mui/icons-material/ExitToApp';
+import Settings from '@mui/icons-material/Settings';
+import Trex from '../images/trex.png';
+
+export const PreviewComponent = (): JSX.Element => {
+ const userMenuJson = useAppSelector((state: RootState) => state.componentsPropsState.userMenuComponent);
+
+ const userMenuProps = createProps(userMenuJson.props as PropsType[]);
+ const userMenuOtherProps = createProps(userMenuJson.otherProps as PropsType[]);
+
+ const toggleDefaultProp = (propName: string, currentValue: any): string =>
+ hideDefaultPropsFromSnippet(userMenuJson, propName, currentValue, 'props');
+
+ const toggleAvatarSection = (showAvatarImage: boolean): JSX.Element =>
+ showAvatarImage ? : AV;
+
+ const toggleAvatarSnippet = (showAvatarImageSnippet: boolean): string =>
+ showAvatarImageSnippet
+ ? `avatar={}`
+ : `avatar={AV}`;
+
+ const generateCodeSnippet = (): string => {
+ const jsx = `,
+ },
+ {
+ title: "Contact Us",
+ icon: ,
+ },
+ {
+ title: "Log Out",
+ icon: ,
+ },
+ ],
+ },
+ ]}
+/>`;
+ return removeEmptyLines(jsx);
+ };
+
+ return (
+ ,
+ },
+ {
+ title: 'Contact Us',
+ icon: ,
+ },
+ {
+ title: 'Log Out',
+ icon: ,
+ },
+ ],
+ },
+ ]}
+ menuTitle={userMenuProps.menuTitle}
+ menuSubtitle={userMenuProps.menuSubtitle}
+ useBottomSheetAt={userMenuProps.useBottomSheetAt}
+ />
+ }
+ code={generateCodeSnippet()}
+ />
+ );
+};
diff --git a/docs/src/componentDocs/UserMenu/playground/PropsPlayground.tsx b/docs/src/componentDocs/UserMenu/playground/PropsPlayground.tsx
new file mode 100644
index 000000000..106ca0bf5
--- /dev/null
+++ b/docs/src/componentDocs/UserMenu/playground/PropsPlayground.tsx
@@ -0,0 +1,12 @@
+import * as React from 'react';
+import { RootState } from '../../../redux/store';
+import { useAppSelector } from '../../../redux/hooks';
+import PlaygroundDrawer from '../../../shared/PlaygroundDrawer';
+
+const PropsPlayground = (): JSX.Element => {
+ const userMenuJson = useAppSelector((state: RootState) => state.componentsPropsState.userMenuComponent);
+
+ return ;
+};
+
+export default PropsPlayground;
diff --git a/docs/src/componentDocs/UserMenu/playground/UserMenuConfig.tsx b/docs/src/componentDocs/UserMenu/playground/UserMenuConfig.tsx
new file mode 100644
index 000000000..dbb6111c4
--- /dev/null
+++ b/docs/src/componentDocs/UserMenu/playground/UserMenuConfig.tsx
@@ -0,0 +1,51 @@
+import { ComponentType } from '../../../__types__';
+
+export const userMenuConfig: ComponentType = {
+ componentName: 'User Menu',
+ props: [
+ {
+ propName: 'menuSubtitle',
+ inputType: 'string',
+ inputValue: 'Menu Subtitle',
+ propType: 'string',
+ helperText: 'Subtitle shown when menu is open',
+ required: false,
+ },
+ {
+ propName: 'menuTitle',
+ inputType: 'string',
+ inputValue: 'Menu Title',
+ propType: 'string',
+ helperText: 'Title shown when menu is open',
+ required: false,
+ },
+ {
+ propName: 'useBottomSheetAt',
+ inputType: 'number',
+ inputValue: 600,
+ propType: 'number',
+ helperText:
+ 'Window pixel width at which the responsive bottom sheet menu is triggered (set to 0 to disable responsive behavior)',
+ required: false,
+ defaultValue: 600,
+ rangeData: {
+ min: 0,
+ max: 1000,
+ step: 50,
+ },
+ },
+ ],
+ otherProps: [
+ {
+ propName: 'showAvatarImage',
+ inputType: 'boolean',
+ inputValue: false,
+ propType: 'boolean',
+ helperText: 'Show / hide image for avatar',
+ required: false,
+ label: 'Use Image For Avatar',
+ },
+ ],
+};
+
+export default userMenuConfig;
diff --git a/docs/src/componentDocs/UserMenu/playground/index.tsx b/docs/src/componentDocs/UserMenu/playground/index.tsx
new file mode 100644
index 000000000..234b17b3c
--- /dev/null
+++ b/docs/src/componentDocs/UserMenu/playground/index.tsx
@@ -0,0 +1,2 @@
+export * from './PlaygroundPage';
+export * from './PreviewComponent';
diff --git a/docs/src/contexts/drawerContextProvider.tsx b/docs/src/contexts/drawerContextProvider.tsx
new file mode 100644
index 000000000..b0e934a43
--- /dev/null
+++ b/docs/src/contexts/drawerContextProvider.tsx
@@ -0,0 +1,16 @@
+import { createContext, useContext } from 'react';
+
+type DrawerContextType = {
+ drawerOpen: boolean;
+ setDrawerOpen: (open: boolean) => void;
+};
+
+export const DrawerContext = createContext(null);
+
+export const useDrawer = (): DrawerContextType => {
+ const context = useContext(DrawerContext);
+ if (context === null) {
+ throw new Error('useDrawer must be used within a DrawerContextProvider');
+ }
+ return context;
+};
diff --git a/docs/src/ga.js b/docs/src/ga.js
new file mode 100644
index 000000000..5e8618362
--- /dev/null
+++ b/docs/src/ga.js
@@ -0,0 +1 @@
+export const gaID = 'undefined';
diff --git a/docs/src/index.css b/docs/src/index.css
new file mode 100644
index 000000000..e0b9b68b0
--- /dev/null
+++ b/docs/src/index.css
@@ -0,0 +1,35 @@
+@import 'prismjs/themes/prism-tomorrow.css';
+@import 'prismjs/plugins/line-numbers/prism-line-numbers.css';
+@import 'prismjs/plugins/line-highlight/prism-line-highlight.css';
+
+html,
+body,
+#root {
+ height: 100%;
+ margin: 0;
+ padding: 0;
+}
+.drawerBodyStyle {
+ flex: 1 1 auto;
+}
+.line-highlight {
+ margin-top: 20px;
+}
+a,
+a:not(.MuiTab-root):visited {
+ color: #007bc1;
+ font-weight: 400;
+}
+
+/* HIDE INCREMENT/DECREMENT BUTTONS ON NUMBER INPUTS */
+/* Chrome, Safari, Edge, Opera */
+input::-webkit-outer-spin-button,
+input::-webkit-inner-spin-button {
+ -webkit-appearance: none;
+ margin: 0;
+}
+
+/* Firefox */
+input[type='number'] {
+ -moz-appearance: textfield;
+}
diff --git a/docs/src/index.tsx b/docs/src/index.tsx
new file mode 100644
index 000000000..4fb88e621
--- /dev/null
+++ b/docs/src/index.tsx
@@ -0,0 +1,69 @@
+/**
+ Copyright (c) 2021-present, Eaton
+
+ All rights reserved.
+
+ This code is licensed under the BSD-3 license found in the LICENSE file in the root directory of this source tree and at https://opensource.org/licenses/BSD-3-Clause.
+ **/
+import 'react-app-polyfill/stable';
+import React from 'react';
+import ReactDOMClient from 'react-dom/client';
+import { Provider } from 'react-redux';
+import { BrowserRouter } from 'react-router-dom';
+import { createTheme, ThemeProvider, StyledEngineProvider } from '@mui/material/styles';
+import CssBaseline from '@mui/material/CssBaseline';
+import * as BLUIThemes from '@brightlayer-ui/react-themes';
+import '@brightlayer-ui/react-themes/open-sans';
+import '@fontsource/roboto';
+import '@fontsource/roboto-mono';
+import { App } from './App';
+import reportWebVitals from './reportWebVitals';
+import './index.css';
+import { store } from './redux/store';
+import { MDXProvider } from '@mdx-js/react';
+import { componentsMap } from './__configuration__/markdownMapping';
+import { GoogleAnalyticsWrapper } from './router/GoogleAnalyticsWrapper';
+
+// prismJs
+import 'prismjs/components/prism-jsx.js';
+import 'prismjs/plugins/line-numbers/prism-line-numbers.js';
+import 'prismjs/plugins/line-highlight/prism-line-highlight.js';
+
+// google analytics
+import ReactGA from 'react-ga';
+import { gaID } from './ga.js';
+import { ScrollToTop } from './router/ScrollToTop';
+if (gaID) {
+ ReactGA.initialize(gaID);
+}
+
+// Brightlayer UI Icon font
+require('@brightlayer-ui/icons/iconfont/BrightlayerUIIcons.css');
+const container = document.getElementById('root');
+
+if (!container) throw new Error('Root Element was not found in the DOM');
+
+const root = ReactDOMClient.createRoot(container);
+const basename = process.env.PUBLIC_URL || '/';
+
+root.render(
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+
+// If you want to start measuring performance in your app, pass a function
+// to log results (for example: reportWebVitals(console.log))
+// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
+reportWebVitals();
diff --git a/docs/src/pages/componentPreviewPage.tsx b/docs/src/pages/componentPreviewPage.tsx
new file mode 100644
index 000000000..8b903087a
--- /dev/null
+++ b/docs/src/pages/componentPreviewPage.tsx
@@ -0,0 +1,49 @@
+import React, { HTMLAttributes } from 'react';
+import { AppBar, IconButton, Toolbar, Typography, useTheme, useMediaQuery, Box } from '@mui/material';
+import Menu from '@mui/icons-material/Menu';
+import { ComponentPreviewTabs } from '../shared';
+import { toggleDrawer } from '../redux/appState';
+import { useAppDispatch } from '../redux/hooks';
+
+export type ComponentPreviewPageProps = HTMLAttributes & {
+ title: string;
+};
+export const ComponentPreviewPage: React.FC = (props): JSX.Element => {
+ const { title } = props;
+ const dispatch = useAppDispatch();
+ const theme = useTheme();
+ const lgUp = useMediaQuery(theme.breakpoints.up('lg'));
+
+ return (
+
+
+
+ {lgUp ? null : (
+ {
+ dispatch(toggleDrawer());
+ }}
+ edge={'start'}
+ sx={{ mr: 3 }}
+ size="large"
+ >
+
+
+ )}
+
+ {title}
+
+
+
+
+
+ );
+};
diff --git a/docs/src/pages/index.tsx b/docs/src/pages/index.tsx
new file mode 100644
index 000000000..9559c819f
--- /dev/null
+++ b/docs/src/pages/index.tsx
@@ -0,0 +1 @@
+export * from './componentPreviewPage';
diff --git a/docs/src/react-app-env.d.ts b/docs/src/react-app-env.d.ts
new file mode 100644
index 000000000..6431bc5fc
--- /dev/null
+++ b/docs/src/react-app-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/docs/src/redux/appState.tsx b/docs/src/redux/appState.tsx
new file mode 100644
index 000000000..f4106c3ff
--- /dev/null
+++ b/docs/src/redux/appState.tsx
@@ -0,0 +1,28 @@
+import { createSlice } from '@reduxjs/toolkit';
+
+type AppState = {
+ drawerOpen: boolean;
+};
+
+const initialState: AppState = {
+ drawerOpen: false,
+};
+
+export const appStateSlice = createSlice({
+ name: 'appState',
+ initialState: initialState,
+ reducers: {
+ closeDrawer: (state) => ({
+ ...state,
+ drawerOpen: false,
+ }),
+ toggleDrawer: (state) => ({
+ ...state,
+ drawerOpen: !state.drawerOpen,
+ }),
+ },
+});
+
+export const { closeDrawer, toggleDrawer } = appStateSlice.actions;
+
+export default appStateSlice.reducer;
diff --git a/docs/src/redux/componentsPropsState.tsx b/docs/src/redux/componentsPropsState.tsx
new file mode 100644
index 000000000..8b5572546
--- /dev/null
+++ b/docs/src/redux/componentsPropsState.tsx
@@ -0,0 +1,112 @@
+import { createSlice, PayloadAction } from '@reduxjs/toolkit';
+import appBarConfig from '../componentDocs/AppBar/playground/AppBarConfig';
+import channelValueConfig from '../componentDocs/ChannelValue/playground/ChannelValueConfig';
+import drawerConfig from '../componentDocs/Drawer/playground/DrawerConfig';
+import drawerHeaderConfig from '../componentDocs/DrawerHeader/playground/DrawerHeaderConfig';
+import drawerSubheaderConfig from '../componentDocs/DrawerSubheader/playground/DrawerSubheaderConfig';
+import drawerFooterConfig from '../componentDocs/DrawerFooter/playground/DrawerFooterConfig';
+import drawerNavGroupConfig from '../componentDocs/DrawerNavGroup/playground/DrawerNavGroupConfig';
+import drawerNavItemConfig from '../componentDocs/DrawerNavItem/playground/DrawerNavItemConfig';
+import drawerRailItemConfig from '../componentDocs/DrawerRailItem/playground/DrawerRailItemConfig';
+import emptyStateConfig from '../componentDocs/EmptyState/playground/EmptyStateConfig';
+import heroConfig from '../componentDocs/Hero/playground/HeroConfig';
+import infoListItemConfig from '../componentDocs/InfoListItem/playground/InfoListItemConfig';
+import listItemTagConfig from '../componentDocs/ListItemTag/playground/ListItemTagConfig';
+import spacerConfig from '../componentDocs/Spacer/playground/SpacerConfig';
+import scoreCardConfig from '../componentDocs/ScoreCard/playground/ScoreCardConfig';
+import threeLinerConfig from '../componentDocs/ThreeLiner/playground/ThreeLinerConfig';
+import toolbarMenuConfig from '../componentDocs/ToolbarMenu/playground/ToolbarMenuConfig';
+import userMenuConfig from '../componentDocs/UserMenu/playground/UserMenuConfig';
+import { getComponentState } from '../shared/utilities';
+import { PayloadType, ComponentType } from '../__types__';
+
+type ComponentState = {
+ appBarComponent: ComponentType;
+ channelValueComponent: ComponentType;
+ drawerComponent: ComponentType;
+ drawerHeaderComponent: ComponentType;
+ drawerSubheaderComponent: ComponentType;
+ drawerFooterComponent: ComponentType;
+ drawerNavGroupComponent: ComponentType;
+ drawerNavItemComponent: ComponentType;
+ drawerRailItemComponent: ComponentType;
+ emptyStateComponent: ComponentType;
+ heroComponent: ComponentType;
+ infoListItemComponent: ComponentType;
+ listItemTagComponent: ComponentType;
+ spacerComponent: ComponentType;
+ scoreCardComponent: ComponentType;
+ threeLinerComponent: ComponentType;
+ toolbarMenuComponent: ComponentType;
+ userMenuComponent: ComponentType;
+};
+
+const initialState: ComponentState = {
+ appBarComponent: appBarConfig,
+ channelValueComponent: channelValueConfig,
+ drawerComponent: drawerConfig,
+ drawerHeaderComponent: drawerHeaderConfig,
+ drawerSubheaderComponent: drawerSubheaderConfig,
+ drawerFooterComponent: drawerFooterConfig,
+ drawerNavGroupComponent: drawerNavGroupConfig,
+ drawerNavItemComponent: drawerNavItemConfig,
+ drawerRailItemComponent: drawerRailItemConfig,
+ emptyStateComponent: emptyStateConfig,
+ heroComponent: heroConfig,
+ infoListItemComponent: infoListItemConfig,
+ listItemTagComponent: listItemTagConfig,
+ spacerComponent: spacerConfig,
+ scoreCardComponent: scoreCardConfig,
+ threeLinerComponent: threeLinerConfig,
+ toolbarMenuComponent: toolbarMenuConfig,
+ userMenuComponent: userMenuConfig,
+};
+
+export const componentPropsStateSlice = createSlice({
+ name: 'componentsPropsState',
+ initialState: initialState,
+ reducers: {
+ updateProp: (state, action: PayloadAction) => {
+ const newArray = getComponentState(action.payload.componentName, state);
+ const updatedKnob = newArray?.props?.filter((prop) => prop.propName === action.payload.propName);
+ if (updatedKnob) {
+ updatedKnob[0].inputValue = action.payload.propValue;
+ }
+ },
+ updateSharedProp: (state, action: PayloadAction) => {
+ const newArray = getComponentState(action.payload.componentName, state);
+ const updatedKnob = newArray?.sharedProps?.filter((prop) => prop.propName === action.payload.propName);
+ if (updatedKnob) {
+ updatedKnob[0].inputValue = action.payload.propValue;
+ }
+ },
+ updateOtherProp: (state, action: PayloadAction) => {
+ const newArray = getComponentState(action.payload.componentName, state);
+ const updatedKnob = newArray?.otherProps?.filter((prop) => prop.propName === action.payload.propName);
+ if (updatedKnob) {
+ updatedKnob[0].inputValue = action.payload.propValue;
+ }
+ },
+ updateOtherComponentProp: (state, action: PayloadAction) => {
+ const newArray = getComponentState(action.payload.componentName, state);
+ const updatedKnob = newArray?.otherComponentProps?.childComponentProps?.filter(
+ (prop) => prop.propName === action.payload.propName
+ );
+ if (updatedKnob) {
+ updatedKnob[0].inputValue = action.payload.propValue;
+ }
+ },
+ updateComponentProp: (state, action: PayloadAction) => {
+ const newArray = getComponentState(action.payload.componentName, state);
+ const updatedKnob = newArray?.props?.filter((prop) => prop.propName === action.payload.propName);
+ if (updatedKnob) {
+ updatedKnob[0].inputValue = action.payload.propValue;
+ }
+ },
+ },
+});
+
+export const { updateProp, updateSharedProp, updateOtherProp, updateComponentProp, updateOtherComponentProp } =
+ componentPropsStateSlice.actions;
+
+export default componentPropsStateSlice.reducer;
diff --git a/docs/src/redux/hooks.ts b/docs/src/redux/hooks.ts
new file mode 100644
index 000000000..313e2dbb1
--- /dev/null
+++ b/docs/src/redux/hooks.ts
@@ -0,0 +1,5 @@
+import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
+import type { RootState, AppDispatch } from './store';
+
+export const useAppDispatch: () => AppDispatch = useDispatch;
+export const useAppSelector: TypedUseSelectorHook = useSelector;
diff --git a/docs/src/redux/store.tsx b/docs/src/redux/store.tsx
new file mode 100644
index 000000000..734ff4c6e
--- /dev/null
+++ b/docs/src/redux/store.tsx
@@ -0,0 +1,15 @@
+import { configureStore } from '@reduxjs/toolkit';
+import appStateReducer from './appState';
+import componentsPropsStateReducer from './componentsPropsState';
+
+export const store = configureStore({
+ reducer: {
+ appState: appStateReducer,
+ componentsPropsState: componentsPropsStateReducer,
+ },
+});
+
+// Infer the `RootState` and `AppDispatch` types from the store itself
+export type RootState = ReturnType;
+// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
+export type AppDispatch = typeof store.dispatch;
diff --git a/docs/src/reportWebVitals.ts b/docs/src/reportWebVitals.ts
new file mode 100644
index 000000000..886866863
--- /dev/null
+++ b/docs/src/reportWebVitals.ts
@@ -0,0 +1,16 @@
+/* eslint-disable */
+import { ReportHandler } from 'web-vitals';
+
+const reportWebVitals = (onPerfEntry?: ReportHandler) => {
+ if (onPerfEntry && onPerfEntry instanceof Function) {
+ void import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
+ getCLS(onPerfEntry);
+ getFID(onPerfEntry);
+ getFCP(onPerfEntry);
+ getLCP(onPerfEntry);
+ getTTFB(onPerfEntry);
+ });
+ }
+};
+
+export default reportWebVitals;
diff --git a/docs/src/router/GoogleAnalyticsWrapper.tsx b/docs/src/router/GoogleAnalyticsWrapper.tsx
new file mode 100644
index 000000000..2bb23354a
--- /dev/null
+++ b/docs/src/router/GoogleAnalyticsWrapper.tsx
@@ -0,0 +1,12 @@
+import React, { useEffect } from 'react';
+import { useLocation } from 'react-router-dom';
+import ReactGA from 'react-ga';
+
+export const GoogleAnalyticsWrapper: React.FC = () => {
+ const location = useLocation();
+ useEffect(() => {
+ ReactGA.pageview(window.location.pathname + window.location.search);
+ }, [location.pathname, location.search]);
+
+ return null;
+};
diff --git a/docs/src/router/ScrollToTop.tsx b/docs/src/router/ScrollToTop.tsx
new file mode 100644
index 000000000..04506d3eb
--- /dev/null
+++ b/docs/src/router/ScrollToTop.tsx
@@ -0,0 +1,24 @@
+import { useEffect } from 'react';
+import { useLocation } from 'react-router';
+
+export const ScrollToTop = (): any => {
+ const { pathname, hash } = useLocation();
+ useEffect(() => {
+ // if an anchor link is present, scroll to the anchor link;
+ // else scroll the page to the top
+
+ if (hash) {
+ const id = hash.replace('#', '');
+ const headline = document.getElementById(id);
+ if (headline) {
+ window.scrollTo(0, headline.offsetTop);
+ } else {
+ window.scrollTo(0, 0);
+ }
+ } else {
+ window.scrollTo(0, 0);
+ }
+ }, [pathname, hash]);
+
+ return null;
+};
diff --git a/docs/src/router/drawer.tsx b/docs/src/router/drawer.tsx
new file mode 100644
index 000000000..80f02e344
--- /dev/null
+++ b/docs/src/router/drawer.tsx
@@ -0,0 +1,141 @@
+import React, { useCallback } from 'react';
+import { useMediaQuery, useTheme } from '@mui/material';
+import { useNavigate, useLocation } from 'react-router';
+import { Drawer, DrawerBody, DrawerHeader, DrawerNavGroup, NavItem } from '@brightlayer-ui/react-components';
+import { pageDefinitions, RouteConfig } from '../__configuration__/navigationMenu/navigation';
+import Box from '@mui/material/Box';
+import Chip from '@mui/material/Chip';
+import Typography from '@mui/material/Typography';
+import { DRAWER_WIDTH } from '../shared';
+import AvatarSvg from '../assets/react_logo.svg';
+
+import { useAppDispatch, useAppSelector } from '../redux/hooks';
+import { RootState } from '../redux/store';
+import { closeDrawer, toggleDrawer } from '../redux/appState';
+
+const backgroundImage = require('../assets/cubes_tile.png');
+const linearGradientOverlayImage = `linear-gradient(to right, rgba(0, 123, 193, 1) 22.4%, rgba(0, 123, 193, 0.2) 100%), url(${backgroundImage})`;
+
+const tabs = ['examples', 'api-docs', 'playground'];
+
+const convertNavItems = (
+ navData: RouteConfig[],
+ parentUrl: string,
+ depth: number,
+ handleNavigate: (u: string) => void,
+ dispatch: any
+): NavItem[] => {
+ const convertedItems: NavItem[] = [];
+ for (let i = 0; i < navData.length; i++) {
+ const item = navData[i];
+ const fullURL = `${parentUrl}${item.path || ''}`;
+ convertedItems.push({
+ title: item.title,
+ icon: depth === 0 ? item.icon : undefined,
+ itemID: fullURL.replace(/\/$/, ''),
+ hidePadding: depth > 0 ? false : true,
+ onClick: item.element
+ ? (): void => {
+ handleNavigate(fullURL);
+ dispatch(toggleDrawer());
+ }
+ : undefined,
+ items: item.pages
+ ? convertNavItems(item.pages, `${parentUrl}${item.path || ''}`, depth + 1, handleNavigate, dispatch)
+ : undefined,
+ });
+ }
+ return convertedItems;
+};
+
+export const NavigationDrawer: React.FC = () => {
+ const drawerOpen = useAppSelector((state: RootState) => state.appState.drawerOpen);
+ const theme = useTheme();
+ const lgDown = useMediaQuery(theme.breakpoints.down('lg'));
+ const dispatch = useAppDispatch();
+ const location = useLocation();
+ const navigate = useNavigate();
+ const activeItem = location.pathname.replace(/\/(examples|api-docs|playground)/, '');
+
+ const handleNavigate = useCallback(
+ (id: string): void => {
+ const tabName = tabs.includes(location.pathname.split('/')[4])
+ ? location.pathname.split('/')[4]
+ : location.pathname.split('/')[3];
+ navigate(`${id}${tabName || ''}`);
+ dispatch(toggleDrawer());
+ },
+ [location.pathname, dispatch, navigate]
+ );
+
+ return (
+ {
+ dispatch(closeDrawer());
+ },
+ }}
+ >
+
+ Brightlayer User Interface
+
+
+ Developer Docs
+
+ }
+ label={
+
+ REACT
+
+ }
+ variant={'filled'}
+ size={'small'}
+ />
+
+
+ }
+ />
+