Added ability to override properties of Sidebar config and Sidebar Submenu config

Signed-off-by: Jonathan Ash <jonathan-ash@users.noreply.github.com>
This commit is contained in:
Jonathan Ash
2022-02-15 10:33:36 +00:00
parent 833bf61327
commit 7741e47eae
9 changed files with 419 additions and 325 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/core-components': patch
---
<Sidebar /> now accepts custom configuration options by supplying props sidebarConfig and submenuConfig
@@ -21,7 +21,14 @@ import classnames from 'classnames';
import React, { useState, useContext, useRef } from 'react';
import Button from '@material-ui/core/Button';
import { sidebarConfig, SidebarContext } from './config';
import {
makeSidebarConfig,
makeSidebarSubmenuConfig,
SidebarConfig,
SidebarContext,
SidebarConfigContext,
SubmenuConfig,
} from './config';
import { BackstageTheme } from '@backstage/theme';
import { SidebarPinStateContext, useContent } from './Page';
import { MobileSidebar } from './MobileSidebar';
@@ -29,52 +36,55 @@ import { MobileSidebar } from './MobileSidebar';
/** @public */
export type SidebarClassKey = 'drawer' | 'drawerOpen';
const useStyles = makeStyles<BackstageTheme>(
theme => ({
drawer: {
display: 'flex',
flexFlow: 'column nowrap',
alignItems: 'flex-start',
position: 'fixed',
left: 0,
top: 0,
bottom: 0,
zIndex: theme.zIndex.appBar,
background: theme.palette.navigation.background,
overflowX: 'hidden',
msOverflowStyle: 'none',
scrollbarWidth: 'none',
width: sidebarConfig.drawerWidthClosed,
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.shortest,
}),
'& > *': {
flexShrink: 0,
},
'&::-webkit-scrollbar': {
display: 'none',
},
const useStyles = ({ sidebarConfig }: { sidebarConfig: SidebarConfig }) =>
makeStyles<BackstageTheme>(
theme => {
return {
drawer: {
display: 'flex',
flexFlow: 'column nowrap',
alignItems: 'flex-start',
position: 'fixed',
left: 0,
top: 0,
bottom: 0,
zIndex: theme.zIndex.appBar,
background: theme.palette.navigation.background,
overflowX: 'hidden',
msOverflowStyle: 'none',
scrollbarWidth: 'none',
width: sidebarConfig.drawerWidthClosed,
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.shortest,
}),
'& > *': {
flexShrink: 0,
},
'&::-webkit-scrollbar': {
display: 'none',
},
},
drawerOpen: {
width: sidebarConfig.drawerWidthOpen,
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.shorter,
}),
},
visuallyHidden: {
top: 0,
position: 'absolute',
zIndex: 1000,
transform: 'translateY(-200%)',
'&:focus': {
transform: 'translateY(5px)',
},
},
};
},
drawerOpen: {
width: sidebarConfig.drawerWidthOpen,
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.shorter,
}),
},
visuallyHidden: {
top: 0,
position: 'absolute',
zIndex: 1000,
transform: 'translateY(-200%)',
'&:focus': {
transform: 'translateY(5px)',
},
},
}),
{ name: 'BackstageSidebar' },
);
{ name: 'BackstageSidebar' },
);
enum State {
Closed,
@@ -84,8 +94,13 @@ enum State {
/** @public */
export type SidebarProps = {
openDelayMs?: number;
closeDelayMs?: number;
sidebarConfig?: Partial<SidebarConfig>;
submenuConfig?: Partial<SubmenuConfig>;
disableExpandOnHover?: boolean;
children?: React.ReactNode;
};
export type DesktopSidebarProps = {
disableExpandOnHover?: boolean;
children?: React.ReactNode;
};
@@ -100,14 +115,14 @@ export type SidebarProps = {
* @returns
* @internal
*/
const DesktopSidebar = (props: SidebarProps) => {
const {
openDelayMs = sidebarConfig.defaultOpenDelayMs,
closeDelayMs = sidebarConfig.defaultCloseDelayMs,
disableExpandOnHover,
children,
} = props;
const classes = useStyles();
const DesktopSidebar = (props: DesktopSidebarProps) => {
const { sidebarConfig } = useContext(SidebarConfigContext);
const { defaultOpenDelayMs: openDelayMs, defaultCloseDelayMs: closeDelayMs } =
sidebarConfig;
const { disableExpandOnHover, children } = props;
const classes = useStyles({ sidebarConfig })();
const isSmallScreen = useMediaQuery<BackstageTheme>(
theme => theme.breakpoints.down('md'),
{ noSsr: true },
@@ -172,12 +187,7 @@ const DesktopSidebar = (props: SidebarProps) => {
return (
<div style={{}}>
<A11ySkipSidebar />
<SidebarContext.Provider
value={{
isOpen,
setOpen,
}}
>
<SidebarContext.Provider value={{ isOpen, setOpen }}>
<div
className={classes.root}
data-testid="sidebar-root"
@@ -205,25 +215,31 @@ const DesktopSidebar = (props: SidebarProps) => {
* @public
*/
export const Sidebar = (props: SidebarProps) => {
const { children, openDelayMs, closeDelayMs, disableExpandOnHover } = props;
const sidebarConfig: SidebarConfig = makeSidebarConfig(
props.sidebarConfig ?? {},
);
const submenuConfig: SubmenuConfig = makeSidebarSubmenuConfig(
props.submenuConfig ?? {},
sidebarConfig,
);
const { children, disableExpandOnHover } = props;
const { isMobile } = useContext(SidebarPinStateContext);
return isMobile ? (
<MobileSidebar>{children}</MobileSidebar>
) : (
<DesktopSidebar
openDelayMs={openDelayMs}
closeDelayMs={closeDelayMs}
disableExpandOnHover={disableExpandOnHover}
>
{children}
</DesktopSidebar>
<SidebarConfigContext.Provider value={{ sidebarConfig, submenuConfig }}>
<DesktopSidebar disableExpandOnHover={disableExpandOnHover}>
{children}
</DesktopSidebar>
</SidebarConfigContext.Provider>
);
};
function A11ySkipSidebar() {
const { sidebarConfig } = useContext(SidebarConfigContext);
const { focusContent, contentRef } = useContent();
const classes = useStyles();
const classes = useStyles({ sidebarConfig })();
if (!contentRef?.current) {
return null;
@@ -23,7 +23,8 @@ import CloseIcon from '@material-ui/icons/Close';
import React, { useContext, useState } from 'react';
import { useLocalStorageValue } from '@react-hookz/web';
import {
sidebarConfig,
SidebarConfigContext,
SidebarConfig,
SidebarContext,
SIDEBAR_INTRO_LOCAL_STORAGE,
} from './config';
@@ -37,51 +38,54 @@ export type SidebarIntroClassKey =
| 'introDismissText'
| 'introDismissIcon';
const useStyles = makeStyles<BackstageTheme>(
theme => ({
introCard: {
color: '#b5b5b5',
// XXX (@koroeskohr): should I be using a Mui theme variable?
fontSize: 12,
width: sidebarConfig.drawerWidthOpen,
marginTop: 18,
marginBottom: 12,
paddingLeft: sidebarConfig.iconPadding,
paddingRight: sidebarConfig.iconPadding,
const useStyles = ({sidebarConfig}: { sidebarConfig: SidebarConfig }) =>
makeStyles<BackstageTheme>(
theme => {
return {
introCard: {
color: '#b5b5b5',
// XXX (@koroeskohr): should I be using a Mui theme variable?
fontSize: 12,
width: sidebarConfig.drawerWidthOpen,
marginTop: 18,
marginBottom: 12,
paddingLeft: sidebarConfig.iconPadding,
paddingRight: sidebarConfig.iconPadding,
},
introDismiss: {
display: 'flex',
justifyContent: 'flex-end',
alignItems: 'center',
marginTop: 12,
},
introDismissLink: {
color: '#dddddd',
display: 'flex',
alignItems: 'center',
marginBottom: 4,
'&:hover': {
color: theme.palette.linkHover,
transition: theme.transitions.create('color', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.shortest,
}),
},
},
introDismissText: {
fontSize: '0.7rem',
fontWeight: 'bold',
textTransform: 'uppercase',
letterSpacing: 1,
},
introDismissIcon: {
width: 18,
height: 18,
marginRight: 12,
},
};
},
introDismiss: {
display: 'flex',
justifyContent: 'flex-end',
alignItems: 'center',
marginTop: 12,
},
introDismissLink: {
color: '#dddddd',
display: 'flex',
alignItems: 'center',
marginBottom: 4,
'&:hover': {
color: theme.palette.linkHover,
transition: theme.transitions.create('color', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.shortest,
}),
},
},
introDismissText: {
fontSize: '0.7rem',
fontWeight: 'bold',
textTransform: 'uppercase',
letterSpacing: 1,
},
introDismissIcon: {
width: 18,
height: 18,
marginRight: 12,
},
}),
{ name: 'BackstageSidebarIntro' },
);
{ name: 'BackstageSidebarIntro' },
);
type IntroCardProps = {
text: string;
@@ -96,7 +100,8 @@ type IntroCardProps = {
*/
export function IntroCard(props: IntroCardProps) {
const classes = useStyles();
const { sidebarConfig } = useContext(SidebarConfigContext);
const classes = useStyles({ sidebarConfig })();
const { text, onClose } = props;
const handleClose = () => onClose();
@@ -47,9 +47,10 @@ import {
useResolvedPath,
} from 'react-router-dom';
import {
sidebarConfig,
SidebarContext,
SidebarConfigContext,
SidebarItemWithSubmenuContext,
SidebarConfig,
} from './config';
import {
SidebarSubmenuItemProps,
@@ -82,128 +83,124 @@ export type SidebarItemClassKey =
| 'arrows'
| 'selected';
const useStyles = makeStyles<BackstageTheme>(
theme => {
const {
selectedIndicatorWidth,
drawerWidthClosed,
drawerWidthOpen,
iconContainerWidth,
} = sidebarConfig;
return {
root: {
color: theme.palette.navigation.color,
display: 'flex',
flexFlow: 'row nowrap',
alignItems: 'center',
height: 48,
cursor: 'pointer',
},
buttonItem: {
background: 'none',
border: 'none',
width: '100%',
margin: 0,
padding: 0,
textAlign: 'inherit',
font: 'inherit',
},
closed: {
width: drawerWidthClosed,
justifyContent: 'center',
},
open: {
[theme.breakpoints.up('sm')]: {
width: drawerWidthOpen,
const useStyles = ({ sidebarConfig }: { sidebarConfig: SidebarConfig }) =>
makeStyles<BackstageTheme>(
theme => {
return {
root: {
color: theme.palette.navigation.color,
display: 'flex',
flexFlow: 'row nowrap',
alignItems: 'center',
height: 48,
cursor: 'pointer',
},
},
highlightable: {
'&:hover': {
buttonItem: {
background: 'none',
border: 'none',
width: '100%',
margin: 0,
padding: 0,
textAlign: 'inherit',
font: 'inherit',
},
closed: {
width: sidebarConfig.drawerWidthClosed,
justifyContent: 'center',
},
open: {
[theme.breakpoints.up('sm')]: {
width: sidebarConfig.drawerWidthOpen,
},
},
highlightable: {
'&:hover': {
background:
theme.palette.navigation.navItem?.hoverBackground ?? '#404040',
},
},
highlighted: {
background:
theme.palette.navigation.navItem?.hoverBackground ?? '#404040',
},
},
highlighted: {
background:
theme.palette.navigation.navItem?.hoverBackground ?? '#404040',
},
label: {
// XXX (@koroeskohr): I can't seem to achieve the desired font-weight from the designs
fontWeight: 'bold',
whiteSpace: 'nowrap',
lineHeight: 'auto',
flex: '3 1 auto',
width: '110px',
overflow: 'hidden',
'text-overflow': 'ellipsis',
},
iconContainer: {
boxSizing: 'border-box',
height: '100%',
width: iconContainerWidth,
marginRight: -theme.spacing(2),
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
},
searchRoot: {
marginBottom: 12,
},
searchField: {
color: '#b5b5b5',
fontWeight: 'bold',
fontSize: theme.typography.fontSize,
},
searchFieldHTMLInput: {
padding: theme.spacing(2, 0, 2),
},
searchContainer: {
width: drawerWidthOpen - iconContainerWidth,
},
secondaryAction: {
width: theme.spacing(6),
textAlign: 'center',
marginRight: theme.spacing(1),
},
closedItemIcon: {
width: '100%',
justifyContent: 'center',
},
submenuArrow: {
display: 'flex',
},
expandButton: {
background: 'none',
border: 'none',
color: theme.palette.navigation.color,
width: '100%',
cursor: 'pointer',
position: 'relative',
height: 48,
},
arrows: {
position: 'absolute',
right: 10,
},
selected: {
'&$root': {
borderLeft: `solid ${selectedIndicatorWidth}px ${theme.palette.navigation.indicator}`,
color: theme.palette.navigation.selectedColor,
label: {
// XXX (@koroeskohr): I can't seem to achieve the desired font-weight from the designs
fontWeight: 'bold',
whiteSpace: 'nowrap',
lineHeight: 'auto',
flex: '3 1 auto',
width: '110px',
overflow: 'hidden',
'text-overflow': 'ellipsis',
},
'&$closed': {
width: drawerWidthClosed,
iconContainer: {
boxSizing: 'border-box',
height: '100%',
width: sidebarConfig.iconContainerWidth,
marginRight: -theme.spacing(2),
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
},
'& $closedItemIcon': {
paddingRight: selectedIndicatorWidth,
searchRoot: {
marginBottom: 12,
},
'& $iconContainer': {
marginLeft: -selectedIndicatorWidth,
searchField: {
color: '#b5b5b5',
fontWeight: 'bold',
fontSize: theme.typography.fontSize,
},
},
};
},
{ name: 'BackstageSidebarItem' },
);
searchFieldHTMLInput: {
padding: theme.spacing(2, 0, 2),
},
searchContainer: {
width:
sidebarConfig.drawerWidthOpen - sidebarConfig.iconContainerWidth,
},
secondaryAction: {
width: theme.spacing(6),
textAlign: 'center',
marginRight: theme.spacing(1),
},
closedItemIcon: {
width: '100%',
justifyContent: 'center',
},
submenuArrow: {
display: 'flex',
},
expandButton: {
background: 'none',
border: 'none',
color: theme.palette.navigation.color,
width: '100%',
cursor: 'pointer',
position: 'relative',
height: 48,
},
arrows: {
position: 'absolute',
right: 10,
},
selected: {
'&$root': {
borderLeft: `solid ${sidebarConfig.selectedIndicatorWidth}px ${theme.palette.navigation.indicator}`,
color: theme.palette.navigation.selectedColor,
},
'&$closed': {
width: sidebarConfig.drawerWidthClosed,
},
'& $closedItemIcon': {
paddingRight: sidebarConfig.selectedIndicatorWidth,
},
'& $iconContainer': {
marginLeft: -sidebarConfig.selectedIndicatorWidth,
},
},
};
},
{ name: 'BackstageSidebarItem' },
);
/**
* Evaluates the routes of the SubmenuItems & nested DropdownItems.
@@ -356,7 +353,8 @@ const SidebarItemBase = forwardRef<any, SidebarItemProps>((props, ref) => {
className,
...navLinkProps
} = props;
const classes = useStyles();
const { sidebarConfig } = useContext(SidebarConfigContext);
const classes = useStyles({ sidebarConfig })();
// XXX (@koroeskohr): unsure this is optimal. But I just really didn't want to have the item component
// depend on the current location, and at least have it being optionally forced to selected.
// Still waiting on a Q answered to fine tune the implementation
@@ -429,7 +427,8 @@ const SidebarItemWithSubmenu = ({
}: SidebarItemBaseProps & {
children: React.ReactElement<SidebarSubmenuProps>;
}) => {
const classes = useStyles();
const { sidebarConfig } = useContext(SidebarConfigContext);
const classes = useStyles({ sidebarConfig })();
const [isHoveredOn, setIsHoveredOn] = useState(false);
const location = useLocation();
const isActive = useLocationMatch(children, location);
@@ -518,8 +517,9 @@ type SidebarSearchFieldProps = {
};
export function SidebarSearchField(props: SidebarSearchFieldProps) {
const { sidebarConfig } = useContext(SidebarConfigContext);
const [input, setInput] = useState('');
const classes = useStyles();
const classes = useStyles({ sidebarConfig })();
const Icon = props.icon ? props.icon : SearchIcon;
const search = () => {
@@ -647,7 +647,8 @@ export const SidebarScrollWrapper = styled('div')(({ theme }) => {
* @public
*/
export const SidebarExpandButton = () => {
const classes = useStyles();
const { sidebarConfig } = useContext(SidebarConfigContext);
const classes = useStyles({ sidebarConfig })();
const { isOpen, setOpen } = useContext(SidebarContext);
const isSmallScreen = useMediaQuery<BackstageTheme>(
theme => theme.breakpoints.down('md'),
@@ -25,11 +25,10 @@ import Typography from '@material-ui/core/Typography';
import CloseIcon from '@material-ui/icons/Close';
import MenuIcon from '@material-ui/icons/Menu';
import { orderBy } from 'lodash';
import React, { createContext, useEffect, useState } from 'react';
import React, { createContext, useEffect, useState, useContext } from 'react';
import { useLocation } from 'react-router';
import { SidebarContext } from '.';
import { sidebarConfig } from './config';
import { SidebarGroup } from './SidebarGroup';
import { SidebarConfigContext, SidebarContext, SidebarConfig } from './config';
/**
* Type of `MobileSidebarContext`
@@ -60,44 +59,47 @@ type OverlayMenuProps = {
children?: React.ReactNode;
};
const useStyles = makeStyles<BackstageTheme>(theme => ({
root: {
position: 'fixed',
backgroundColor: theme.palette.navigation.background,
color: theme.palette.navigation.color,
bottom: 0,
left: 0,
right: 0,
zIndex: theme.zIndex.snackbar,
// SidebarDivider color
borderTop: '1px solid #383838',
},
const useStyles = ({ sidebarConfig }: { sidebarConfig: SidebarConfig }) =>
makeStyles<BackstageTheme>(theme => {
return {
root: {
position: 'fixed',
backgroundColor: theme.palette.navigation.background,
color: theme.palette.navigation.color,
bottom: 0,
left: 0,
right: 0,
zIndex: theme.zIndex.snackbar,
// SidebarDivider color
borderTop: '1px solid #383838',
},
overlay: {
background: theme.palette.navigation.background,
width: '100%',
bottom: `${sidebarConfig.mobileSidebarHeight}px`,
height: `calc(100% - ${sidebarConfig.mobileSidebarHeight}px)`,
flex: '0 1 auto',
overflow: 'auto',
},
overlay: {
background: theme.palette.navigation.background,
width: '100%',
bottom: `${sidebarConfig.mobileSidebarHeight}px`,
height: `calc(100% - ${sidebarConfig.mobileSidebarHeight}px)`,
flex: '0 1 auto',
overflow: 'auto',
},
overlayHeader: {
display: 'flex',
color: theme.palette.bursts.fontColor,
alignItems: 'center',
justifyContent: 'space-between',
padding: theme.spacing(2, 3),
},
overlayHeader: {
display: 'flex',
color: theme.palette.bursts.fontColor,
alignItems: 'center',
justifyContent: 'space-between',
padding: theme.spacing(2, 3),
},
overlayHeaderClose: {
color: theme.palette.bursts.fontColor,
},
overlayHeaderClose: {
color: theme.palette.bursts.fontColor,
},
marginMobileSidebar: {
marginBottom: `${sidebarConfig.mobileSidebarHeight}px`,
},
}));
marginMobileSidebar: {
marginBottom: `${sidebarConfig.mobileSidebarHeight}px`,
},
};
})
const sortSidebarGroupsForPriority = (children: React.ReactElement[]) =>
orderBy(
@@ -114,7 +116,8 @@ const OverlayMenu = ({
open,
onClose,
}: OverlayMenuProps) => {
const classes = useStyles();
const { sidebarConfig } = useContext(SidebarConfigContext);
const classes = useStyles({ sidebarConfig })();
return (
<Drawer
@@ -163,8 +166,9 @@ export const MobileSidebarContext = createContext<MobileSidebarContextType>({
* @public
*/
export const MobileSidebar = (props: MobileSidebarProps) => {
const { sidebarConfig } = useContext(SidebarConfigContext);
const { children } = props;
const classes = useStyles();
const classes = useStyles({ sidebarConfig })();
const location = useLocation();
const [selectedMenuItemIndex, setSelectedMenuItemIndex] =
useState<number>(-1);
@@ -25,39 +25,45 @@ import React, {
useRef,
useState,
} from 'react';
import { sidebarConfig } from './config';
import { SidebarConfigContext } from './config';
import { BackstageTheme } from '@backstage/theme';
import { LocalStorage } from './localStorage';
import useMediaQuery from '@material-ui/core/useMediaQuery';
export type SidebarPageClassKey = 'root';
const useStyles = makeStyles<BackstageTheme, { isPinned: boolean }>(
theme => ({
root: {
width: '100%',
transition: 'padding-left 0.1s ease-out',
isolation: 'isolate',
[theme.breakpoints.up('sm')]: {
paddingLeft: ({ isPinned }) =>
isPinned
? sidebarConfig.drawerWidthOpen
: sidebarConfig.drawerWidthClosed,
},
[theme.breakpoints.down('xs')]: {
paddingBottom: sidebarConfig.mobileSidebarHeight,
},
const useStyles = ({ isPinned }: { isPinned: boolean }) => {
const { sidebarConfig } = useContext(SidebarConfigContext);
return makeStyles<BackstageTheme>(
theme => {
return {
root: {
width: '100%',
transition: 'padding-left 0.1s ease-out',
isolation: 'isolate',
[theme.breakpoints.up('sm')]: {
paddingLeft: () =>
isPinned
? sidebarConfig.drawerWidthOpen
: sidebarConfig.drawerWidthClosed,
},
[theme.breakpoints.down('xs')]: {
paddingBottom: sidebarConfig.mobileSidebarHeight,
},
},
content: {
zIndex: 0,
isolation: 'isolate',
'&:focus': {
outline: 0,
},
},
};
},
content: {
zIndex: 0,
isolation: 'isolate',
'&:focus': {
outline: 0,
},
},
}),
{ name: 'BackstageSidebarPage' },
);
{ name: 'BackstageSidebarPage' },
)();
};
/**
* Type of `SidebarPinStateContext`
@@ -24,7 +24,7 @@ import React, { useContext } from 'react';
import { useLocation } from 'react-router-dom';
import { SidebarPinStateContext } from '.';
import { Link } from '../../components';
import { sidebarConfig } from './config';
import { SidebarConfigContext, SidebarConfig } from './config';
import { MobileSidebarContext } from './MobileSidebar';
/**
@@ -48,23 +48,26 @@ export interface SidebarGroupProps extends BottomNavigationActionProps {
children?: React.ReactNode;
}
const useStyles = makeStyles<BackstageTheme>(theme => ({
root: {
flexGrow: 0,
margin: theme.spacing(0, 2),
color: theme.palette.navigation.color,
},
const useStyles = ({ sidebarConfig }: { sidebarConfig: SidebarConfig }) =>
makeStyles<BackstageTheme>(theme => {
return {
root: {
flexGrow: 0,
margin: theme.spacing(0, 2),
color: theme.palette.navigation.color,
},
selected: {
color: `${theme.palette.navigation.selectedColor}!important`,
borderTop: `solid ${sidebarConfig.selectedIndicatorWidth}px ${theme.palette.navigation.indicator}`,
marginTop: '-1px',
},
selected: {
color: `${theme.palette.navigation.selectedColor}!important`,
borderTop: `solid ${sidebarConfig.selectedIndicatorWidth}px ${theme.palette.navigation.indicator}`,
marginTop: '-1px',
},
label: {
display: 'none',
},
}));
label: {
display: 'none',
},
};
});
/**
* Returns a MUI `BottomNavigationAction`, which is aware of the current location & the selected item in the `BottomNavigation`,
@@ -75,7 +78,8 @@ const useStyles = makeStyles<BackstageTheme>(theme => ({
*/
const MobileSidebarGroup = (props: SidebarGroupProps) => {
const { to, label, icon, value } = props;
const classes = useStyles();
const { sidebarConfig } = useContext(SidebarConfigContext);
const classes = useStyles({ sidebarConfig })();
const location = useLocation();
const { selectedMenuItemIndex, setSelectedMenuItemIndex } =
useContext(MobileSidebarContext);
@@ -19,13 +19,13 @@ import classnames from 'classnames';
import React, { ReactNode, useContext, useEffect, useState } from 'react';
import {
SidebarItemWithSubmenuContext,
sidebarConfig,
SidebarContext,
submenuConfig,
SidebarConfigContext,
SubmenuConfig,
} from './config';
import { BackstageTheme } from '@backstage/theme';
const useStyles = (props: { left: number }) =>
const useStyles = (props: { left: number, submenuConfig: SubmenuConfig }) =>
makeStyles<BackstageTheme>(
theme => ({
root: {
@@ -54,8 +54,8 @@ const useStyles = (props: { left: number }) =>
msOverflowStyle: 'none',
scrollbarWidth: 'none',
cursor: 'default',
width: submenuConfig.drawerWidthClosed,
transitionDelay: `${submenuConfig.defaultOpenDelayMs}ms`,
width: props.submenuConfig.drawerWidthClosed,
transitionDelay: `${props.submenuConfig.defaultOpenDelayMs}ms`,
'& > *': {
flexShrink: 0,
},
@@ -64,7 +64,7 @@ const useStyles = (props: { left: number }) =>
},
},
drawerOpen: {
width: submenuConfig.drawerWidthOpen,
width: props.submenuConfig.drawerWidthOpen,
[theme.breakpoints.down('xs')]: {
width: '100%',
position: 'relative',
@@ -104,10 +104,11 @@ export type SidebarSubmenuProps = {
*/
export const SidebarSubmenu = (props: SidebarSubmenuProps) => {
const { isOpen } = useContext(SidebarContext);
const { sidebarConfig, submenuConfig } = useContext(SidebarConfigContext);
const left = isOpen
? sidebarConfig.drawerWidthOpen
: sidebarConfig.drawerWidthClosed;
const classes = useStyles({ left })();
const classes = useStyles({ left, submenuConfig })();
const { isHoveredOn } = useContext(SidebarItemWithSubmenuContext);
const [isSubmenuOpen, setIsSubmenuOpen] = useState(false);
@@ -20,6 +20,22 @@ const drawerWidthClosed = 72;
const iconPadding = 24;
const userBadgePadding = 18;
export type SidebarConfig = {
drawerWidthClosed: number;
drawerWidthOpen: number;
defaultOpenDelayMs: number;
defaultCloseDelayMs: number;
defaultFadeDuration: number;
logoHeight: number;
iconContainerWidth: number;
iconSize: number;
iconPadding: number;
selectedIndicatorWidth: number;
userBadgePadding: number;
userBadgeDiameter: number;
mobileSidebarHeight: number;
};
export const sidebarConfig = {
drawerWidthClosed,
drawerWidthOpen: 224,
@@ -38,12 +54,38 @@ export const sidebarConfig = {
mobileSidebarHeight: 56,
};
export const makeSidebarConfig = (
customSidebarConfig: Partial<SidebarConfig>,
) => ({
...sidebarConfig,
...customSidebarConfig,
iconContainerWidth: sidebarConfig.drawerWidthClosed,
iconSize: sidebarConfig.drawerWidthClosed - sidebarConfig.iconPadding * 2,
userBadgeDiameter:
sidebarConfig.drawerWidthClosed - sidebarConfig.userBadgePadding * 2,
});
export type SubmenuConfig = {
drawerWidthClosed: number;
drawerWidthOpen: number;
defaultOpenDelayMs: number;
};
export const submenuConfig = {
drawerWidthClosed: 0,
drawerWidthOpen: 202,
defaultOpenDelayMs: sidebarConfig.defaultOpenDelayMs + 200,
};
export const makeSidebarSubmenuConfig = (
customSubmenuConfig: Partial<SubmenuConfig>,
customSidebarConfig: SidebarConfig,
) => ({
...submenuConfig,
...customSubmenuConfig,
defaultOpenDelayMs: customSidebarConfig.defaultOpenDelayMs + 200,
});
export const SIDEBAR_INTRO_LOCAL_STORAGE =
'@backstage/core/sidebar-intro-dismissed';
@@ -63,6 +105,16 @@ export const SidebarContext = createContext<SidebarContextType>({
setOpen: () => {},
});
export type SidebarConfigContextType = {
sidebarConfig: SidebarConfig;
submenuConfig: SubmenuConfig;
};
export const SidebarConfigContext = createContext<SidebarConfigContextType>({
sidebarConfig,
submenuConfig,
});
export type SidebarItemWithSubmenuContextType = {
isHoveredOn: boolean;
setIsHoveredOn: Dispatch<SetStateAction<boolean>>;