Move 'isMobile' in Context
Optimize generated docs by labeling components with scope Signed-off-by: Philipp Hugenroth <philipph@spotify.com>
This commit is contained in:
@@ -14,7 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Content, Header, Lifecycle, Page } from '@backstage/core-components';
|
||||
import {
|
||||
Content,
|
||||
Header,
|
||||
Lifecycle,
|
||||
Page,
|
||||
SidebarStateContext,
|
||||
} from '@backstage/core-components';
|
||||
import { CatalogResultListItem } from '@backstage/plugin-catalog';
|
||||
import {
|
||||
DefaultResultListItem,
|
||||
@@ -25,16 +31,8 @@ import {
|
||||
SearchType,
|
||||
} from '@backstage/plugin-search';
|
||||
import { DocsResultListItem } from '@backstage/plugin-techdocs';
|
||||
import {
|
||||
Grid,
|
||||
List,
|
||||
makeStyles,
|
||||
Paper,
|
||||
Theme,
|
||||
useMediaQuery,
|
||||
} from '@material-ui/core';
|
||||
import React from 'react';
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
import { Grid, List, makeStyles, Paper, Theme } from '@material-ui/core';
|
||||
import React, { useContext } from 'react';
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => ({
|
||||
bar: {
|
||||
@@ -52,15 +50,11 @@ const useStyles = makeStyles((theme: Theme) => ({
|
||||
|
||||
const SearchPage = () => {
|
||||
const classes = useStyles();
|
||||
const isMobileScreen = useMediaQuery<BackstageTheme>(theme =>
|
||||
theme.breakpoints.down('xs'),
|
||||
);
|
||||
const { isMobile } = useContext(SidebarStateContext);
|
||||
|
||||
return (
|
||||
<Page themeId="home">
|
||||
{!isMobileScreen && (
|
||||
<Header title="Search" subtitle={<Lifecycle alpha />} />
|
||||
)}
|
||||
{!isMobile && <Header title="Search" subtitle={<Lifecycle alpha />} />}
|
||||
<Content>
|
||||
<Grid container direction="row">
|
||||
<Grid item xs={12}>
|
||||
@@ -68,7 +62,7 @@ const SearchPage = () => {
|
||||
<SearchBar debounceTime={100} />
|
||||
</Paper>
|
||||
</Grid>
|
||||
{!isMobileScreen && (
|
||||
{!isMobile && (
|
||||
<Grid item xs={3}>
|
||||
<Paper className={classes.filters}>
|
||||
<SearchType
|
||||
|
||||
@@ -106,7 +106,7 @@ export type BottomLinkProps = {
|
||||
// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// @public (undocumented)
|
||||
export function Breadcrumbs(props: Props_20): JSX.Element;
|
||||
export function Breadcrumbs(props: Props_19): JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export type BreadcrumbsClickableTextClassKey = 'root';
|
||||
@@ -701,12 +701,10 @@ export function MissingAnnotationEmptyState(props: Props_3): JSX.Element;
|
||||
// @public (undocumented)
|
||||
export type MissingAnnotationEmptyStateClassKey = 'code';
|
||||
|
||||
// Warning: (ae-missing-release-tag) "MobileSidebar" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const MobileSidebar: ({
|
||||
children,
|
||||
}: React_2.PropsWithChildren<{}>) => JSX.Element | null;
|
||||
export const MobileSidebar: (
|
||||
props: React_2.PropsWithChildren<{}>,
|
||||
) => JSX.Element | null;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "OAuthRequestDialog" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
@@ -843,16 +841,10 @@ export type SelectClassKey =
|
||||
// @public (undocumented)
|
||||
export type SelectInputBaseClassKey = 'root' | 'input';
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-missing-release-tag) "Sidebar" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const Sidebar: ({
|
||||
children,
|
||||
openDelayMs,
|
||||
closeDelayMs,
|
||||
disableExpandOnHover,
|
||||
}: React_2.PropsWithChildren<Props_17>) => JSX.Element;
|
||||
export const Sidebar: (
|
||||
props: React_2.PropsWithChildren<SidebarProps>,
|
||||
) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "SIDEBAR_INTRO_LOCAL_STORAGE" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
@@ -860,8 +852,6 @@ export const Sidebar: ({
|
||||
export const SIDEBAR_INTRO_LOCAL_STORAGE =
|
||||
'@backstage/core/sidebar-intro-dismissed';
|
||||
|
||||
// Warning: (ae-missing-release-tag) "SidebarClassKey" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export type SidebarClassKey = 'drawer' | 'drawerOpen';
|
||||
|
||||
@@ -1174,19 +1164,11 @@ export type SidebarDividerClassKey = 'root';
|
||||
// @public
|
||||
export const SidebarExpandButton: () => JSX.Element | null;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "SidebarGroup" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const SidebarGroup: ({
|
||||
children,
|
||||
to,
|
||||
label,
|
||||
icon,
|
||||
value,
|
||||
}: React_2.PropsWithChildren<SidebarGroupProps>) => JSX.Element;
|
||||
export const SidebarGroup: (
|
||||
props: React_2.PropsWithChildren<SidebarGroupProps>,
|
||||
) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "SidebarGroupProps" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export interface SidebarGroupProps extends BottomNavigationActionProps {
|
||||
// (undocumented)
|
||||
@@ -1243,17 +1225,11 @@ export function SidebarPage(props: PropsWithChildren<{}>): JSX.Element;
|
||||
// @public (undocumented)
|
||||
export type SidebarPageClassKey = 'root';
|
||||
|
||||
// Warning: (ae-missing-release-tag) "SidebarPinStateContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const SidebarPinStateContext: React_2.Context<SidebarPinStateContextType>;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "SidebarPinStateContextType" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export type SidebarPinStateContextType = {
|
||||
isPinned: boolean;
|
||||
toggleSidebarPinState: () => any;
|
||||
export type SidebarProps = {
|
||||
openDelayMs?: number;
|
||||
closeDelayMs?: number;
|
||||
disableExpandOnHover?: boolean;
|
||||
};
|
||||
|
||||
// Warning: (ae-missing-release-tag) "SidebarScrollWrapper" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
@@ -2079,6 +2055,16 @@ export const SidebarSpacer: React_2.ComponentType<
|
||||
// @public (undocumented)
|
||||
export type SidebarSpacerClassKey = 'root';
|
||||
|
||||
// @public (undocumented)
|
||||
export const SidebarStateContext: React_2.Context<SidebarStateContextType>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type SidebarStateContextType = {
|
||||
isPinned: boolean;
|
||||
toggleSidebarPinState: () => any;
|
||||
isMobile: boolean;
|
||||
};
|
||||
|
||||
// @public
|
||||
export const SidebarSubmenu: (props: SidebarSubmenuProps) => JSX.Element;
|
||||
|
||||
@@ -2111,7 +2097,7 @@ export type SidebarSubmenuProps = {
|
||||
// Warning: (ae-missing-release-tag) "SignInPage" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export function SignInPage(props: Props_18): JSX.Element;
|
||||
export function SignInPage(props: Props_17): JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "SignInPageClassKey" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
@@ -2282,7 +2268,7 @@ export type TabBarClassKey = 'indicator' | 'flexContainer' | 'root';
|
||||
// Warning: (ae-missing-release-tag) "TabbedCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export function TabbedCard(props: PropsWithChildren<Props_19>): JSX.Element;
|
||||
export function TabbedCard(props: PropsWithChildren<Props_18>): JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "TabbedCardClassKey" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
|
||||
@@ -26,12 +26,16 @@ import { Sidebar } from './Bar';
|
||||
import { SidebarItem, SidebarSearchField, SidebarExpandButton } from './Items';
|
||||
import { SidebarSubmenuItem } from './SidebarSubmenuItem';
|
||||
import { SidebarSubmenu } from './SidebarSubmenu';
|
||||
import { SidebarPinStateContext } from '.';
|
||||
import { SidebarStateContext } from '.';
|
||||
|
||||
async function renderScalableSidebar() {
|
||||
await renderInTestApp(
|
||||
<SidebarPinStateContext.Provider
|
||||
value={{ isPinned: false, toggleSidebarPinState: () => {} }}
|
||||
<SidebarStateContext.Provider
|
||||
value={{
|
||||
isPinned: false,
|
||||
isMobile: false,
|
||||
toggleSidebarPinState: () => {},
|
||||
}}
|
||||
>
|
||||
<Sidebar>
|
||||
<SidebarSearchField onSearch={() => {}} to="/search" />
|
||||
@@ -58,7 +62,7 @@ async function renderScalableSidebar() {
|
||||
<SidebarItem icon={CreateComponentIcon} to="create" text="Create..." />
|
||||
<SidebarExpandButton />
|
||||
</Sidebar>
|
||||
</SidebarPinStateContext.Provider>,
|
||||
</SidebarStateContext.Provider>,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,9 +20,10 @@ import classnames from 'classnames';
|
||||
import React, { useState, useContext, PropsWithChildren, useRef } from 'react';
|
||||
import { sidebarConfig, SidebarContext } from './config';
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
import { SidebarPinStateContext } from './Page';
|
||||
import { SidebarStateContext } from './Page';
|
||||
import { MobileSidebar } from './MobileSidebar';
|
||||
|
||||
/** @public */
|
||||
export type SidebarClassKey = 'drawer' | 'drawerOpen';
|
||||
|
||||
const useStyles = makeStyles<BackstageTheme>(
|
||||
@@ -69,7 +70,8 @@ enum State {
|
||||
Open,
|
||||
}
|
||||
|
||||
type Props = {
|
||||
/** @public */
|
||||
export type SidebarProps = {
|
||||
openDelayMs?: number;
|
||||
closeDelayMs?: number;
|
||||
disableExpandOnHover?: boolean;
|
||||
@@ -80,7 +82,7 @@ const DesktopSidebar = ({
|
||||
closeDelayMs = sidebarConfig.defaultCloseDelayMs,
|
||||
disableExpandOnHover,
|
||||
children,
|
||||
}: PropsWithChildren<Props>) => {
|
||||
}: PropsWithChildren<SidebarProps>) => {
|
||||
const classes = useStyles();
|
||||
const isSmallScreen = useMediaQuery<BackstageTheme>(
|
||||
theme => theme.breakpoints.down('md'),
|
||||
@@ -88,9 +90,7 @@ const DesktopSidebar = ({
|
||||
);
|
||||
const [state, setState] = useState(State.Closed);
|
||||
const hoverTimerRef = useRef<number>();
|
||||
const { isPinned, toggleSidebarPinState } = useContext(
|
||||
SidebarPinStateContext,
|
||||
);
|
||||
const { isPinned, toggleSidebarPinState } = useContext(SidebarStateContext);
|
||||
|
||||
const handleOpen = () => {
|
||||
if (isPinned || disableExpandOnHover) {
|
||||
@@ -163,18 +163,12 @@ const DesktopSidebar = ({
|
||||
);
|
||||
};
|
||||
|
||||
export const Sidebar = ({
|
||||
children,
|
||||
openDelayMs,
|
||||
closeDelayMs,
|
||||
disableExpandOnHover,
|
||||
}: React.PropsWithChildren<Props>) => {
|
||||
const isMobileScreen = useMediaQuery<BackstageTheme>(
|
||||
theme => theme.breakpoints.down('xs'),
|
||||
{ noSsr: true },
|
||||
);
|
||||
/** @public */
|
||||
export const Sidebar = (props: React.PropsWithChildren<SidebarProps>) => {
|
||||
const { children, openDelayMs, closeDelayMs, disableExpandOnHover } = props;
|
||||
const { isMobile } = useContext(SidebarStateContext);
|
||||
|
||||
return isMobileScreen ? (
|
||||
return isMobile ? (
|
||||
<MobileSidebar>{children}</MobileSidebar>
|
||||
) : (
|
||||
<DesktopSidebar
|
||||
|
||||
@@ -39,6 +39,7 @@ import {
|
||||
NavLinkProps,
|
||||
useLocation,
|
||||
useResolvedPath,
|
||||
resolvePath,
|
||||
} from 'react-router-dom';
|
||||
import {
|
||||
sidebarConfig,
|
||||
@@ -52,6 +53,8 @@ import {
|
||||
} from '.';
|
||||
import DoubleArrowLeft from './icons/DoubleArrowLeft';
|
||||
import DoubleArrowRight from './icons/DoubleArrowRight';
|
||||
import { isLocationMatch } from './utils';
|
||||
import { Location } from 'history';
|
||||
|
||||
export type SidebarItemClassKey =
|
||||
| 'root'
|
||||
@@ -140,7 +143,7 @@ const useStyles = makeStyles<BackstageTheme>(
|
||||
fontSize: theme.typography.fontSize,
|
||||
},
|
||||
searchFieldHTMLInput: {
|
||||
padding: `${theme.spacing(2)} 0 ${theme.spacing(2)}`,
|
||||
padding: theme.spacing(2, 0, 2),
|
||||
},
|
||||
searchContainer: {
|
||||
width: drawerWidthOpen - iconContainerWidth,
|
||||
@@ -219,7 +222,7 @@ const useStyles = makeStyles<BackstageTheme>(
|
||||
fontSize: theme.typography.fontSize,
|
||||
},
|
||||
searchFieldHTMLInput: {
|
||||
padding: `${theme.spacing(2)} 0 ${theme.spacing(2)}`,
|
||||
padding: theme.spacing(2, 0, 2),
|
||||
},
|
||||
searchContainer: {
|
||||
width: drawerWidthOpen - iconContainerWidth,
|
||||
@@ -237,7 +240,7 @@ const useStyles = makeStyles<BackstageTheme>(
|
||||
|
||||
const useLocationMatch = (
|
||||
submenu: React.ReactElement<SidebarSubmenuProps>,
|
||||
locationPathname: string,
|
||||
location: Location,
|
||||
): boolean =>
|
||||
// Evaluates the routes of the SubmenuItems & nested DropdownItems.
|
||||
// The reeveluation is only triggered, if the `locationPathname` changes, as `useElementFilter` uses memorization
|
||||
@@ -257,93 +260,21 @@ const useLocationMatch = (
|
||||
if (dropdownItems?.length) {
|
||||
dropdownItems.forEach(
|
||||
({ to: _to }) =>
|
||||
(active = active || locationPathname.includes(_to)),
|
||||
(active =
|
||||
active || isLocationMatch(location, resolvePath(_to))),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (to) {
|
||||
active = locationPathname.includes(to);
|
||||
active = isLocationMatch(location, resolvePath(to));
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
return active;
|
||||
},
|
||||
[locationPathname],
|
||||
[location.pathname],
|
||||
);
|
||||
/*
|
||||
function isSidebarItemWithSubmenuActive(
|
||||
submenu: ReactNode,
|
||||
currentLocation: Location,
|
||||
) {
|
||||
// Item is active if any of submenu items have active paths
|
||||
const toPathnames: string[] = [];
|
||||
let isActive = false;
|
||||
let submenuItems: ReactNode;
|
||||
Children.forEach(submenu, element => {
|
||||
if (!React.isValidElement(element)) return;
|
||||
submenuItems = element.props.children;
|
||||
});
|
||||
Children.forEach(submenuItems, element => {
|
||||
if (!React.isValidElement(element)) return;
|
||||
if (element.props.dropdownItems) {
|
||||
element.props.dropdownItems.map((item: { to: string }) =>
|
||||
toPathnames.push(item.to),
|
||||
);
|
||||
} else if (element.props.to) {
|
||||
toPathnames.push(element.props.to);
|
||||
}
|
||||
});
|
||||
isActive = toPathnames.some(to => {
|
||||
const toLocation = resolvePath(to);
|
||||
return isLocationMatch(currentLocation, toLocation);
|
||||
});
|
||||
return isActive;
|
||||
}
|
||||
|
||||
const SidebarItemWithSubmenu = ({
|
||||
text,
|
||||
hasNotifications = false,
|
||||
icon: Icon,
|
||||
children,
|
||||
}: PropsWithChildren<SidebarItemWithSubmenuProps>) => {
|
||||
const classes = useStyles();
|
||||
const [isHoveredOn, setIsHoveredOn] = useState(false);
|
||||
const currentLocation = useLocation();
|
||||
const isActive = isSidebarItemWithSubmenuActive(children, currentLocation);
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
setIsHoveredOn(true);
|
||||
};
|
||||
const handleMouseLeave = () => {
|
||||
setIsHoveredOn(false);
|
||||
};
|
||||
|
||||
const { isOpen } = useContext(SidebarContext);
|
||||
const itemIcon = (
|
||||
<Badge
|
||||
color="secondary"
|
||||
variant="dot"
|
||||
overlap="circular"
|
||||
className={isOpen ? '' : classes.closedItemIcon}
|
||||
invisible={!hasNotifications}
|
||||
>
|
||||
<Icon fontSize="small" />
|
||||
</Badge>
|
||||
);
|
||||
const openContent = (
|
||||
<>
|
||||
<div data-testid="login-button" className={classes.iconContainer}>
|
||||
{itemIcon}
|
||||
</div>
|
||||
{text && (
|
||||
<Typography variant="subtitle2" className={classes.label}>
|
||||
{text}
|
||||
</Typography>
|
||||
)}
|
||||
<div className={classes.secondaryAction}>{}</div>
|
||||
</>
|
||||
);*/
|
||||
|
||||
type SidebarItemBaseProps = {
|
||||
icon: IconComponent;
|
||||
@@ -522,8 +453,8 @@ const SidebarItemWithSubmenu = ({
|
||||
}) => {
|
||||
const classes = useStyles();
|
||||
const [isHoveredOn, setIsHoveredOn] = useState(false);
|
||||
const { pathname: locationPathname } = useLocation();
|
||||
const isActive = useLocationMatch(children, locationPathname);
|
||||
const location = useLocation();
|
||||
const isActive = useLocationMatch(children, location);
|
||||
const isSmallScreen = useMediaQuery<BackstageTheme>((theme: BackstageTheme) =>
|
||||
theme.breakpoints.down('sm'),
|
||||
);
|
||||
|
||||
@@ -20,6 +20,7 @@ import BottomNavigation from '@material-ui/core/BottomNavigation';
|
||||
import Box from '@material-ui/core/Box';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import Drawer from '@material-ui/core/Drawer';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import CloseIcon from '@material-ui/icons/Close';
|
||||
import MenuIcon from '@material-ui/icons/Menu';
|
||||
@@ -43,7 +44,7 @@ const useStyles = makeStyles<BackstageTheme>(theme => ({
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
zIndex: 1000,
|
||||
zIndex: theme.zIndex.snackbar,
|
||||
// SidebarDivider color
|
||||
borderTop: '1px solid #383838',
|
||||
},
|
||||
@@ -51,11 +52,10 @@ const useStyles = makeStyles<BackstageTheme>(theme => ({
|
||||
overlay: {
|
||||
background: theme.palette.navigation.background,
|
||||
width: '100%',
|
||||
flex: '0 1 auto',
|
||||
bottom: `${sidebarConfig.mobileSidebarHeight}px`,
|
||||
height: `calc(100% - ${sidebarConfig.mobileSidebarHeight}px)`,
|
||||
flex: '0 1 auto',
|
||||
overflow: 'auto',
|
||||
position: 'fixed',
|
||||
zIndex: 500,
|
||||
},
|
||||
|
||||
overlayHeader: {
|
||||
@@ -63,7 +63,7 @@ const useStyles = makeStyles<BackstageTheme>(theme => ({
|
||||
color: theme.palette.bursts.fontColor,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
padding: `${theme.spacing(2)}px ${theme.spacing(3)}px`,
|
||||
padding: theme.spacing(2, 3),
|
||||
},
|
||||
|
||||
overlayHeaderClose: {
|
||||
@@ -81,12 +81,22 @@ const sortSidebarGroupsForPriority = (children: React.ReactElement[]) =>
|
||||
const OverlayMenu = ({
|
||||
children,
|
||||
label = 'Menu',
|
||||
open,
|
||||
onClose,
|
||||
}: React.PropsWithChildren<{ label?: string; onClose: () => void }>) => {
|
||||
}: React.PropsWithChildren<{
|
||||
label?: string;
|
||||
onClose: () => void;
|
||||
open: boolean;
|
||||
}>) => {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<Box className={classes.overlay}>
|
||||
<Drawer
|
||||
anchor="bottom"
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
classes={{ paperAnchorBottom: classes.overlay }}
|
||||
>
|
||||
<Box className={classes.overlayHeader}>
|
||||
<Typography variant="h3">{label}</Typography>
|
||||
<IconButton
|
||||
@@ -97,7 +107,7 @@ const OverlayMenu = ({
|
||||
</IconButton>
|
||||
</Box>
|
||||
<Box>{children}</Box>
|
||||
</Box>
|
||||
</Drawer>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -106,7 +116,9 @@ export const MobileSidebarContext = createContext<MobileSidebarContextType>({
|
||||
setSelectedMenuItemIndex: () => {},
|
||||
});
|
||||
|
||||
export const MobileSidebar = ({ children }: React.PropsWithChildren<{}>) => {
|
||||
/** @public */
|
||||
export const MobileSidebar = (props: React.PropsWithChildren<{}>) => {
|
||||
const { children } = props;
|
||||
const classes = useStyles();
|
||||
const location = useLocation();
|
||||
const [selectedMenuItemIndex, setSelectedMenuItemIndex] =
|
||||
@@ -146,12 +158,19 @@ export const MobileSidebar = ({ children }: React.PropsWithChildren<{}>) => {
|
||||
<MobileSidebarContext.Provider
|
||||
value={{ selectedMenuItemIndex, setSelectedMenuItemIndex }}
|
||||
>
|
||||
{shouldShowGroupChildren && (
|
||||
<OverlayMenu
|
||||
{...sidebarGroups[selectedMenuItemIndex].props}
|
||||
onClose={() => setSelectedMenuItemIndex(-1)}
|
||||
/>
|
||||
)}
|
||||
<OverlayMenu
|
||||
label={
|
||||
sidebarGroups[selectedMenuItemIndex]
|
||||
? (sidebarGroups[selectedMenuItemIndex].props.label as string)
|
||||
: ''
|
||||
}
|
||||
open={shouldShowGroupChildren}
|
||||
onClose={() => setSelectedMenuItemIndex(-1)}
|
||||
>
|
||||
{sidebarGroups[selectedMenuItemIndex] &&
|
||||
(sidebarGroups[selectedMenuItemIndex].props
|
||||
.children as React.ReactChildren)}
|
||||
</OverlayMenu>
|
||||
<BottomNavigation
|
||||
className={classes.root}
|
||||
data-testid="mobile-sidebar-root"
|
||||
|
||||
@@ -24,6 +24,7 @@ import React, {
|
||||
import { sidebarConfig } from './config';
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
import { LocalStorage } from './localStorage';
|
||||
import useMediaQuery from '@material-ui/core/useMediaQuery';
|
||||
|
||||
export type SidebarPageClassKey = 'root';
|
||||
|
||||
@@ -46,17 +47,19 @@ const useStyles = makeStyles<BackstageTheme, { isPinned: boolean }>(
|
||||
{ name: 'BackstageSidebarPage' },
|
||||
);
|
||||
|
||||
export type SidebarPinStateContextType = {
|
||||
/** @public */
|
||||
export type SidebarStateContextType = {
|
||||
isPinned: boolean;
|
||||
toggleSidebarPinState: () => any;
|
||||
isMobile: boolean;
|
||||
};
|
||||
|
||||
export const SidebarPinStateContext = createContext<SidebarPinStateContextType>(
|
||||
{
|
||||
isPinned: true,
|
||||
toggleSidebarPinState: () => {},
|
||||
},
|
||||
);
|
||||
/** @public */
|
||||
export const SidebarStateContext = createContext<SidebarStateContextType>({
|
||||
isPinned: true,
|
||||
toggleSidebarPinState: () => {},
|
||||
isMobile: false,
|
||||
});
|
||||
|
||||
export function SidebarPage(props: PropsWithChildren<{}>) {
|
||||
const [isPinned, setIsPinned] = useState(() =>
|
||||
@@ -67,17 +70,23 @@ export function SidebarPage(props: PropsWithChildren<{}>) {
|
||||
LocalStorage.setSidebarPinState(isPinned);
|
||||
}, [isPinned]);
|
||||
|
||||
const isMobile = useMediaQuery<BackstageTheme>(
|
||||
theme => theme.breakpoints.down('xs'),
|
||||
{ noSsr: true },
|
||||
);
|
||||
|
||||
const toggleSidebarPinState = () => setIsPinned(!isPinned);
|
||||
|
||||
const classes = useStyles({ isPinned });
|
||||
return (
|
||||
<SidebarPinStateContext.Provider
|
||||
<SidebarStateContext.Provider
|
||||
value={{
|
||||
isPinned,
|
||||
toggleSidebarPinState,
|
||||
isMobile,
|
||||
}}
|
||||
>
|
||||
<div className={classes.root}>{props.children}</div>
|
||||
</SidebarPinStateContext.Provider>
|
||||
</SidebarStateContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -108,6 +108,5 @@ export const SampleScalableSidebar = () => (
|
||||
<SidebarSpace />
|
||||
<SidebarExpandButton />
|
||||
</Sidebar>
|
||||
Test
|
||||
</SidebarPage>
|
||||
);
|
||||
|
||||
@@ -20,13 +20,14 @@ import BottomNavigationAction, {
|
||||
BottomNavigationActionProps,
|
||||
} from '@material-ui/core/BottomNavigationAction';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import useMediaQuery from '@material-ui/core/useMediaQuery';
|
||||
import React, { useContext } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { SidebarStateContext } from '.';
|
||||
import { Link } from '../../components';
|
||||
import { sidebarConfig } from './config';
|
||||
import { MobileSidebarContext } from './MobileSidebar';
|
||||
|
||||
/** @public */
|
||||
export interface SidebarGroupProps extends BottomNavigationActionProps {
|
||||
to?: string;
|
||||
priority?: number;
|
||||
@@ -35,7 +36,7 @@ export interface SidebarGroupProps extends BottomNavigationActionProps {
|
||||
const useStyles = makeStyles<BackstageTheme>(theme => ({
|
||||
root: {
|
||||
flexGrow: 0,
|
||||
margin: `0 ${theme.spacing(2)}px`,
|
||||
margin: theme.spacing(0, 2),
|
||||
color: theme.palette.navigation.color,
|
||||
},
|
||||
|
||||
@@ -50,7 +51,8 @@ const useStyles = makeStyles<BackstageTheme>(theme => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const MobileSidebarGroup = ({ to, label, icon, value }: SidebarGroupProps) => {
|
||||
const MobileSidebarGroup = (props: SidebarGroupProps) => {
|
||||
const { to, label, icon, value } = props;
|
||||
const classes = useStyles();
|
||||
const location = useLocation();
|
||||
const { selectedMenuItemIndex, setSelectedMenuItemIndex } =
|
||||
@@ -86,18 +88,14 @@ const MobileSidebarGroup = ({ to, label, icon, value }: SidebarGroupProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const SidebarGroup = ({
|
||||
children,
|
||||
to,
|
||||
label,
|
||||
icon,
|
||||
value,
|
||||
}: React.PropsWithChildren<SidebarGroupProps>) => {
|
||||
const isMobileScreen = useMediaQuery<BackstageTheme>(theme =>
|
||||
theme.breakpoints.down('xs'),
|
||||
);
|
||||
/** @public */
|
||||
export const SidebarGroup = (
|
||||
props: React.PropsWithChildren<SidebarGroupProps>,
|
||||
) => {
|
||||
const { children, to, label, icon, value } = props;
|
||||
const { isMobile } = useContext(SidebarStateContext);
|
||||
|
||||
return isMobileScreen ? (
|
||||
return isMobile ? (
|
||||
<MobileSidebarGroup to={to} label={label} icon={icon} value={value} />
|
||||
) : (
|
||||
<>{children}</>
|
||||
|
||||
@@ -25,9 +25,15 @@ export type {
|
||||
SidebarSubmenuItemProps,
|
||||
SidebarSubmenuItemDropdownItem,
|
||||
} from './SidebarSubmenuItem';
|
||||
export type { SidebarClassKey } from './Bar';
|
||||
export { SidebarPage, SidebarPinStateContext } from './Page';
|
||||
export type { SidebarPinStateContextType, SidebarPageClassKey } from './Page';
|
||||
export type { SidebarClassKey, SidebarProps } from './Bar';
|
||||
export {
|
||||
SidebarPage,
|
||||
SidebarStateContext as SidebarStateContext,
|
||||
} from './Page';
|
||||
export type {
|
||||
SidebarStateContextType as SidebarStateContextType,
|
||||
SidebarPageClassKey,
|
||||
} from './Page';
|
||||
export {
|
||||
SidebarDivider,
|
||||
SidebarItem,
|
||||
|
||||
@@ -13,23 +13,20 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { InfoCard } from '@backstage/core-components';
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
import { List, useMediaQuery } from '@material-ui/core';
|
||||
import React from 'react';
|
||||
import { InfoCard, SidebarStateContext } from '@backstage/core-components';
|
||||
import { List } from '@material-ui/core';
|
||||
import React, { useContext } from 'react';
|
||||
import { UserSettingsPinToggle } from './UserSettingsPinToggle';
|
||||
import { UserSettingsThemeToggle } from './UserSettingsThemeToggle';
|
||||
|
||||
export const UserSettingsAppearanceCard = () => {
|
||||
const isMobileScreen = useMediaQuery<BackstageTheme>(theme =>
|
||||
theme.breakpoints.down('xs'),
|
||||
);
|
||||
const isMobile = useContext(SidebarStateContext);
|
||||
|
||||
return (
|
||||
<InfoCard title="Appearance" variant="gridItem">
|
||||
<List dense>
|
||||
<UserSettingsThemeToggle />
|
||||
{!isMobileScreen && <UserSettingsPinToggle />}
|
||||
{!isMobile && <UserSettingsPinToggle />}
|
||||
</List>
|
||||
</InfoCard>
|
||||
);
|
||||
|
||||
@@ -18,18 +18,22 @@ import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils';
|
||||
import { fireEvent } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { UserSettingsPinToggle } from './UserSettingsPinToggle';
|
||||
import { SidebarPinStateContext } from '@backstage/core-components';
|
||||
import { SidebarStateContext } from '@backstage/core-components';
|
||||
|
||||
describe('<UserSettingsPinToggle />', () => {
|
||||
it('toggles the pin sidebar button', async () => {
|
||||
const mockToggleFn = jest.fn();
|
||||
const rendered = await renderWithEffects(
|
||||
wrapInTestApp(
|
||||
<SidebarPinStateContext.Provider
|
||||
value={{ isPinned: false, toggleSidebarPinState: mockToggleFn }}
|
||||
<SidebarStateContext.Provider
|
||||
value={{
|
||||
isPinned: false,
|
||||
isMobile: false,
|
||||
toggleSidebarPinState: mockToggleFn,
|
||||
}}
|
||||
>
|
||||
<UserSettingsPinToggle />
|
||||
</SidebarPinStateContext.Provider>,
|
||||
</SidebarStateContext.Provider>,
|
||||
),
|
||||
);
|
||||
expect(rendered.getByText('Pin Sidebar')).toBeInTheDocument();
|
||||
|
||||
@@ -22,12 +22,10 @@ import {
|
||||
Switch,
|
||||
Tooltip,
|
||||
} from '@material-ui/core';
|
||||
import { SidebarPinStateContext } from '@backstage/core-components';
|
||||
import { SidebarStateContext } from '@backstage/core-components';
|
||||
|
||||
export const UserSettingsPinToggle = () => {
|
||||
const { isPinned, toggleSidebarPinState } = useContext(
|
||||
SidebarPinStateContext,
|
||||
);
|
||||
const { isPinned, toggleSidebarPinState } = useContext(SidebarStateContext);
|
||||
|
||||
return (
|
||||
<ListItem>
|
||||
|
||||
@@ -14,10 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Header, Page, TabbedLayout } from '@backstage/core-components';
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
import { useMediaQuery } from '@material-ui/core';
|
||||
import React from 'react';
|
||||
import {
|
||||
Header,
|
||||
Page,
|
||||
SidebarStateContext,
|
||||
TabbedLayout,
|
||||
} from '@backstage/core-components';
|
||||
import React, { useContext } from 'react';
|
||||
import { UserSettingsAuthProviders } from './AuthProviders';
|
||||
import { UserSettingsFeatureFlags } from './FeatureFlags';
|
||||
import { UserSettingsGeneral } from './General';
|
||||
@@ -27,13 +30,11 @@ type Props = {
|
||||
};
|
||||
|
||||
export const SettingsPage = ({ providerSettings }: Props) => {
|
||||
const isMobileScreen = useMediaQuery<BackstageTheme>(theme =>
|
||||
theme.breakpoints.down('xs'),
|
||||
);
|
||||
const { isMobile } = useContext(SidebarStateContext);
|
||||
|
||||
return (
|
||||
<Page themeId="home">
|
||||
{!isMobileScreen && <Header title="Settings" />}
|
||||
{!isMobile && <Header title="Settings" />}
|
||||
<TabbedLayout>
|
||||
<TabbedLayout.Route path="general" title="General">
|
||||
<UserSettingsGeneral />
|
||||
|
||||
Reference in New Issue
Block a user