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:
@@ -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>>;
|
||||
|
||||
Reference in New Issue
Block a user