Merge pull request #11629 from backstage/cc/version-sidebar-contexts
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
---
|
||||
'@backstage/create-app': patch
|
||||
---
|
||||
|
||||
Use of `SidebarContext` has been deprecated and will be removed in a future release. Instead, `useSidebarOpenState()` should be used to consume the context and `<SidebarOpenStateProvider>` should be used to provide it.
|
||||
|
||||
To prepare your app, update `packages/app/src/components/Root/Root.tsx` as follows:
|
||||
|
||||
```diff
|
||||
import {
|
||||
Sidebar,
|
||||
sidebarConfig,
|
||||
- SidebarContext
|
||||
SidebarDivider,
|
||||
// ...
|
||||
SidebarSpace,
|
||||
+ useSidebarOpenState,
|
||||
} from '@backstage/core-components';
|
||||
|
||||
// ...
|
||||
|
||||
const SidebarLogo = () => {
|
||||
const classes = useSidebarLogoStyles();
|
||||
- const { isOpen } = useContext(SidebarContext);
|
||||
+ const { isOpen } = useSidebarOpenState();
|
||||
|
||||
// ...
|
||||
};
|
||||
```
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/plugin-techdocs': patch
|
||||
'@backstage/plugin-user-settings': patch
|
||||
'@techdocs/cli': patch
|
||||
---
|
||||
|
||||
Updated sidebar-related logic to use `<SidebarPinStateProvider>` + `useSidebarPinState()` and/or `<SidebarOpenStateProvider>` + `useSidebarOpenState()` from `@backstage/core-components`.
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/core-components': patch
|
||||
---
|
||||
|
||||
The `SidebarPinStateContext` and `SidebarContext` have been deprecated and will be removed in a future release. Instead, use `<SidebarPinStateProvider>` + `useSidebarPinState()` and/or `<SidebarOpenStateProvider>` + `useSidebarOpenState()`.
|
||||
|
||||
This was done to ensure that sidebar state can be shared successfully across components exported by different packages, regardless of what version of this package is resolved and installed for each individual package.
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useContext, PropsWithChildren } from 'react';
|
||||
import React, { PropsWithChildren } from 'react';
|
||||
import { Link, makeStyles } from '@material-ui/core';
|
||||
import HomeIcon from '@material-ui/icons/Home';
|
||||
import ExtensionIcon from '@material-ui/icons/Extension';
|
||||
@@ -39,13 +39,13 @@ import { Shortcuts } from '@backstage/plugin-shortcuts';
|
||||
import {
|
||||
Sidebar,
|
||||
sidebarConfig,
|
||||
SidebarContext,
|
||||
SidebarDivider,
|
||||
SidebarGroup,
|
||||
SidebarItem,
|
||||
SidebarPage,
|
||||
SidebarScrollWrapper,
|
||||
SidebarSpace,
|
||||
useSidebarOpenState,
|
||||
} from '@backstage/core-components';
|
||||
import { MyGroupsSidebarItem } from '@backstage/plugin-org';
|
||||
import GroupIcon from '@material-ui/icons/People';
|
||||
@@ -68,7 +68,7 @@ const useSidebarLogoStyles = makeStyles({
|
||||
|
||||
const SidebarLogo = () => {
|
||||
const classes = useSidebarLogoStyles();
|
||||
const { isOpen } = useContext(SidebarContext);
|
||||
const { isOpen } = useSidebarOpenState();
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
Header,
|
||||
Lifecycle,
|
||||
Page,
|
||||
SidebarPinStateContext,
|
||||
useSidebarPinState,
|
||||
} from '@backstage/core-components';
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
import { CatalogSearchResultListItem } from '@backstage/plugin-catalog';
|
||||
@@ -40,7 +40,7 @@ import {
|
||||
import { useSearch } from '@backstage/plugin-search-react';
|
||||
import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs';
|
||||
import { Grid, List, makeStyles, Paper, Theme } from '@material-ui/core';
|
||||
import React, { useContext } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => ({
|
||||
bar: {
|
||||
@@ -59,7 +59,7 @@ const useStyles = makeStyles((theme: Theme) => ({
|
||||
|
||||
const SearchPage = () => {
|
||||
const classes = useStyles();
|
||||
const { isMobile } = useContext(SidebarPinStateContext);
|
||||
const { isMobile } = useSidebarPinState();
|
||||
const { types } = useSearch();
|
||||
const catalogApi = useApi(catalogApiRef);
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ import { CardHeaderProps } from '@material-ui/core/CardHeader';
|
||||
import { Column } from '@material-table/core';
|
||||
import { ComponentClass } from 'react';
|
||||
import { ComponentProps } from 'react';
|
||||
import { Context } from 'react';
|
||||
import { default as CSS_2 } from 'csstype';
|
||||
import { CSSProperties } from 'react';
|
||||
import { ElementType } from 'react';
|
||||
@@ -903,14 +902,10 @@ export const sidebarConfig: {
|
||||
mobileSidebarHeight: number;
|
||||
};
|
||||
|
||||
// Warning: (ae-missing-release-tag) "SidebarContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public
|
||||
export const SidebarContext: Context<SidebarContextType>;
|
||||
// @public @deprecated
|
||||
export const SidebarContext: React_2.Context<SidebarContextType>;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "SidebarContextType" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public
|
||||
// @public @deprecated
|
||||
export type SidebarContextType = {
|
||||
isOpen: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
@@ -983,6 +978,21 @@ export type SidebarItemClassKey =
|
||||
| 'arrows'
|
||||
| 'selected';
|
||||
|
||||
// @public
|
||||
export type SidebarOpenState = {
|
||||
isOpen: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
};
|
||||
|
||||
// @public
|
||||
export const SidebarOpenStateProvider: ({
|
||||
children,
|
||||
value,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
value: SidebarOpenState;
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export type SidebarOptions = {
|
||||
drawerWidthClosed?: number;
|
||||
@@ -1005,15 +1015,31 @@ export type SidebarPageProps = {
|
||||
};
|
||||
|
||||
// @public
|
||||
export type SidebarPinState = {
|
||||
isPinned: boolean;
|
||||
toggleSidebarPinState: () => any;
|
||||
isMobile?: boolean;
|
||||
};
|
||||
|
||||
// @public @deprecated
|
||||
export const SidebarPinStateContext: React_2.Context<SidebarPinStateContextType>;
|
||||
|
||||
// @public
|
||||
// @public @deprecated
|
||||
export type SidebarPinStateContextType = {
|
||||
isPinned: boolean;
|
||||
toggleSidebarPinState: () => any;
|
||||
isMobile?: boolean;
|
||||
};
|
||||
|
||||
// @public
|
||||
export const SidebarPinStateProvider: ({
|
||||
children,
|
||||
value,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
value: SidebarPinStateContextType;
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export type SidebarProps = {
|
||||
openDelayMs?: number;
|
||||
@@ -1447,6 +1473,12 @@ export class UserIdentity implements IdentityApi {
|
||||
signOut(): Promise<void>;
|
||||
}
|
||||
|
||||
// @public
|
||||
export const useSidebarOpenState: () => SidebarOpenState;
|
||||
|
||||
// @public
|
||||
export const useSidebarPinState: () => SidebarPinState;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "useSupportConfig" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
"@backstage/core-plugin-api": "^1.0.2",
|
||||
"@backstage/errors": "^1.0.0",
|
||||
"@backstage/theme": "^0.2.15",
|
||||
"@backstage/version-bridge": "^1.0.1",
|
||||
"@material-table/core": "^3.1.0",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useContext } from 'react';
|
||||
import React from 'react';
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
import { makeStyles, ThemeProvider } from '@material-ui/core/styles';
|
||||
import { SidebarPinStateContext } from '../Sidebar/Page';
|
||||
import { useSidebarPinState } from '../Sidebar/SidebarPinStateContext';
|
||||
|
||||
export type PageClassKey = 'root';
|
||||
|
||||
@@ -43,7 +43,7 @@ type Props = {
|
||||
|
||||
export function Page(props: Props) {
|
||||
const { themeId, children } = props;
|
||||
const { isMobile } = useContext(SidebarPinStateContext);
|
||||
const { isMobile } = useSidebarPinState();
|
||||
const classes = useStyles({ isMobile });
|
||||
return (
|
||||
<ThemeProvider
|
||||
|
||||
@@ -27,14 +27,14 @@ import {
|
||||
SidebarExpandButton,
|
||||
SidebarItem,
|
||||
SidebarSearchField,
|
||||
SidebarPinStateContext,
|
||||
SidebarPinStateProvider,
|
||||
SidebarSubmenu,
|
||||
SidebarSubmenuItem,
|
||||
} from '.';
|
||||
|
||||
async function renderScalableSidebar() {
|
||||
await renderInTestApp(
|
||||
<SidebarPinStateContext.Provider
|
||||
<SidebarPinStateProvider
|
||||
value={{
|
||||
isPinned: false,
|
||||
isMobile: false,
|
||||
@@ -75,7 +75,7 @@ async function renderScalableSidebar() {
|
||||
<SidebarItem icon={CreateComponentIcon} to="create" text="Create..." />
|
||||
<SidebarExpandButton />
|
||||
</Sidebar>
|
||||
</SidebarPinStateContext.Provider>,
|
||||
</SidebarPinStateProvider>,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,14 +25,15 @@ import {
|
||||
makeSidebarConfig,
|
||||
makeSidebarSubmenuConfig,
|
||||
SidebarConfig,
|
||||
SidebarContext,
|
||||
SidebarConfigContext,
|
||||
SubmenuConfig,
|
||||
SidebarOptions,
|
||||
SubmenuOptions,
|
||||
} from './config';
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
import { SidebarPinStateContext, useContent } from './Page';
|
||||
import { useContent } from './Page';
|
||||
import { SidebarOpenStateProvider } from './SidebarOpenStateContext';
|
||||
import { useSidebarPinState } from './SidebarPinStateContext';
|
||||
import { MobileSidebar } from './MobileSidebar';
|
||||
|
||||
/** @public */
|
||||
@@ -133,9 +134,7 @@ const DesktopSidebar = (props: DesktopSidebarProps) => {
|
||||
);
|
||||
const [state, setState] = useState(State.Closed);
|
||||
const hoverTimerRef = useRef<number>();
|
||||
const { isPinned, toggleSidebarPinState } = useContext(
|
||||
SidebarPinStateContext,
|
||||
);
|
||||
const { isPinned, toggleSidebarPinState } = useSidebarPinState();
|
||||
|
||||
const handleOpen = () => {
|
||||
if (isPinned || disableExpandOnHover) {
|
||||
@@ -191,7 +190,7 @@ const DesktopSidebar = (props: DesktopSidebarProps) => {
|
||||
return (
|
||||
<nav style={{}} aria-label="sidebar nav">
|
||||
<A11ySkipSidebar />
|
||||
<SidebarContext.Provider value={{ isOpen, setOpen }}>
|
||||
<SidebarOpenStateProvider value={{ isOpen, setOpen }}>
|
||||
<div
|
||||
className={classes.root}
|
||||
data-testid="sidebar-root"
|
||||
@@ -208,7 +207,7 @@ const DesktopSidebar = (props: DesktopSidebarProps) => {
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</SidebarContext.Provider>
|
||||
</SidebarOpenStateProvider>
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
@@ -226,7 +225,7 @@ export const Sidebar = (props: SidebarProps) => {
|
||||
props.submenuOptions ?? {},
|
||||
);
|
||||
const { children, disableExpandOnHover, openDelayMs, closeDelayMs } = props;
|
||||
const { isMobile } = useContext(SidebarPinStateContext);
|
||||
const { isMobile } = useSidebarPinState();
|
||||
|
||||
return isMobile ? (
|
||||
<MobileSidebar>{children}</MobileSidebar>
|
||||
|
||||
@@ -25,10 +25,10 @@ import { useLocalStorageValue } from '@react-hookz/web';
|
||||
import {
|
||||
SidebarConfigContext,
|
||||
SidebarConfig,
|
||||
SidebarContext,
|
||||
SIDEBAR_INTRO_LOCAL_STORAGE,
|
||||
} from './config';
|
||||
import { SidebarDivider } from './Items';
|
||||
import { useSidebarOpenState } from './SidebarOpenStateContext';
|
||||
|
||||
/** @public */
|
||||
export type SidebarIntroClassKey =
|
||||
@@ -151,7 +151,7 @@ const recentlyViewedIntroText =
|
||||
'And your recently viewed plugins will pop up here!';
|
||||
|
||||
export function SidebarIntro(_props: {}) {
|
||||
const { isOpen } = useContext(SidebarContext);
|
||||
const { isOpen } = useSidebarOpenState();
|
||||
const defaultValue = {
|
||||
starredItemsDismissed: false,
|
||||
recentlyViewedItemsDismissed: false,
|
||||
|
||||
@@ -48,7 +48,6 @@ import {
|
||||
useResolvedPath,
|
||||
} from 'react-router-dom';
|
||||
import {
|
||||
SidebarContext,
|
||||
SidebarConfigContext,
|
||||
SidebarItemWithSubmenuContext,
|
||||
SidebarConfig,
|
||||
@@ -62,6 +61,7 @@ import DoubleArrowLeft from './icons/DoubleArrowLeft';
|
||||
import DoubleArrowRight from './icons/DoubleArrowRight';
|
||||
import { isLocationMatch } from './utils';
|
||||
import { Location } from 'history';
|
||||
import { useSidebarOpenState } from './SidebarOpenStateContext';
|
||||
|
||||
/** @public */
|
||||
export type SidebarItemClassKey =
|
||||
@@ -369,7 +369,7 @@ const SidebarItemBase = forwardRef<any, SidebarItemProps>((props, ref) => {
|
||||
// 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
|
||||
const { isOpen } = useContext(SidebarContext);
|
||||
const { isOpen } = useSidebarOpenState();
|
||||
|
||||
const divStyle =
|
||||
!isOpen && hasSubmenu ? { display: 'flex', marginLeft: '24px' } : {};
|
||||
@@ -671,7 +671,7 @@ export const SidebarScrollWrapper = styled('div')(({ theme }) => {
|
||||
export const SidebarExpandButton = () => {
|
||||
const { sidebarConfig } = useContext(SidebarConfigContext);
|
||||
const classes = useMemoStyles(sidebarConfig);
|
||||
const { isOpen, setOpen } = useContext(SidebarContext);
|
||||
const { isOpen, setOpen } = useSidebarOpenState();
|
||||
const isSmallScreen = useMediaQuery<BackstageTheme>(
|
||||
theme => theme.breakpoints.down('md'),
|
||||
{ noSsr: true },
|
||||
|
||||
@@ -27,8 +27,9 @@ import MenuIcon from '@material-ui/icons/Menu';
|
||||
import { orderBy } from 'lodash';
|
||||
import React, { createContext, useEffect, useState, useContext } from 'react';
|
||||
import { useLocation } from 'react-router';
|
||||
import { SidebarOpenStateProvider } from './SidebarOpenStateContext';
|
||||
import { SidebarGroup } from './SidebarGroup';
|
||||
import { SidebarConfigContext, SidebarContext, SidebarConfig } from './config';
|
||||
import { SidebarConfigContext, SidebarConfig } from './config';
|
||||
|
||||
/**
|
||||
* Type of `MobileSidebarContext`
|
||||
@@ -207,7 +208,7 @@ export const MobileSidebar = (props: MobileSidebarProps) => {
|
||||
!sidebarGroups[selectedMenuItemIndex].props.to;
|
||||
|
||||
return (
|
||||
<SidebarContext.Provider value={{ isOpen: true, setOpen: () => {} }}>
|
||||
<SidebarOpenStateProvider value={{ isOpen: true, setOpen: () => {} }}>
|
||||
<MobileSidebarContext.Provider
|
||||
value={{ selectedMenuItemIndex, setSelectedMenuItemIndex }}
|
||||
>
|
||||
@@ -231,6 +232,6 @@ export const MobileSidebar = (props: MobileSidebarProps) => {
|
||||
{sidebarGroups}
|
||||
</BottomNavigation>
|
||||
</MobileSidebarContext.Provider>
|
||||
</SidebarContext.Provider>
|
||||
</SidebarOpenStateProvider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -29,6 +29,7 @@ import { SidebarConfigContext, SidebarConfig } from './config';
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
import { LocalStorage } from './localStorage';
|
||||
import useMediaQuery from '@material-ui/core/useMediaQuery';
|
||||
import { SidebarPinStateProvider } from './SidebarPinStateContext';
|
||||
|
||||
export type SidebarPageClassKey = 'root';
|
||||
|
||||
@@ -62,17 +63,6 @@ const useStyles = makeStyles<
|
||||
{ name: 'BackstageSidebarPage' },
|
||||
);
|
||||
|
||||
/**
|
||||
* Type of `SidebarPinStateContext`
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type SidebarPinStateContextType = {
|
||||
isPinned: boolean;
|
||||
toggleSidebarPinState: () => any;
|
||||
isMobile?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Props for SidebarPage
|
||||
*
|
||||
@@ -82,19 +72,6 @@ export type SidebarPageProps = {
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
/**
|
||||
* Contains the state on how the `Sidebar` is rendered
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const SidebarPinStateContext = createContext<SidebarPinStateContextType>(
|
||||
{
|
||||
isPinned: true,
|
||||
toggleSidebarPinState: () => {},
|
||||
isMobile: false,
|
||||
},
|
||||
);
|
||||
|
||||
type PageContextType = {
|
||||
content: {
|
||||
contentRef?: React.MutableRefObject<HTMLElement | null>;
|
||||
@@ -137,7 +114,7 @@ export function SidebarPage(props: SidebarPageProps) {
|
||||
const classes = useStyles({ isPinned, sidebarConfig });
|
||||
|
||||
return (
|
||||
<SidebarPinStateContext.Provider
|
||||
<SidebarPinStateProvider
|
||||
value={{
|
||||
isPinned,
|
||||
toggleSidebarPinState,
|
||||
@@ -147,7 +124,7 @@ export function SidebarPage(props: SidebarPageProps) {
|
||||
<PageContext.Provider value={pageContext}>
|
||||
<div className={classes.root}>{props.children}</div>
|
||||
</PageContext.Provider>
|
||||
</SidebarPinStateContext.Provider>
|
||||
</SidebarPinStateProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ import BottomNavigationAction, {
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import React, { useContext } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { SidebarPinStateContext } from '.';
|
||||
import { useSidebarPinState } from '.';
|
||||
import { Link } from '../../components';
|
||||
import { SidebarConfigContext, SidebarConfig } from './config';
|
||||
import { MobileSidebarContext } from './MobileSidebar';
|
||||
@@ -122,7 +122,7 @@ const MobileSidebarGroup = (props: SidebarGroupProps) => {
|
||||
*/
|
||||
export const SidebarGroup = (props: SidebarGroupProps) => {
|
||||
const { children, to, label, icon, value } = props;
|
||||
const { isMobile } = useContext(SidebarPinStateContext);
|
||||
const { isMobile } = useSidebarPinState();
|
||||
|
||||
return isMobile ? (
|
||||
<MobileSidebarGroup to={to} label={label} icon={icon} value={value} />
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React, { ReactNode, useContext } from 'react';
|
||||
import { renderWithEffects } from '@backstage/test-utils';
|
||||
import { waitFor } from '@testing-library/react';
|
||||
import { renderHook, act } from '@testing-library/react-hooks';
|
||||
import {
|
||||
LegacySidebarContext,
|
||||
SidebarOpenStateProvider,
|
||||
useSidebarOpenState,
|
||||
} from './SidebarOpenStateContext';
|
||||
|
||||
describe('SidebarOpenStateContext', () => {
|
||||
describe('SidebarOpenStateProvider', () => {
|
||||
it('should render children', async () => {
|
||||
const { findByText } = await renderWithEffects(
|
||||
<SidebarOpenStateProvider value={{ isOpen: false, setOpen: () => {} }}>
|
||||
Child
|
||||
</SidebarOpenStateProvider>,
|
||||
);
|
||||
expect(await findByText('Child')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should provide the legacy context as well, for now', async () => {
|
||||
const LegacyContextSpy = () => {
|
||||
const { isOpen } = useContext(LegacySidebarContext);
|
||||
return <>{String(isOpen)}</>;
|
||||
};
|
||||
|
||||
const { findByText } = await renderWithEffects(
|
||||
<SidebarOpenStateProvider
|
||||
value={{
|
||||
isOpen: true,
|
||||
setOpen: () => {},
|
||||
}}
|
||||
>
|
||||
<LegacyContextSpy />
|
||||
</SidebarOpenStateProvider>,
|
||||
);
|
||||
|
||||
expect(await findByText('true')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('useSidebarOpenState', () => {
|
||||
it('can be invoked within legacy context', () => {
|
||||
const wrapper = ({ children }: { children: ReactNode }) => (
|
||||
<SidebarOpenStateProvider
|
||||
value={{
|
||||
isOpen: true,
|
||||
setOpen: () => {},
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</SidebarOpenStateProvider>
|
||||
);
|
||||
|
||||
const { result } = renderHook(() => useSidebarOpenState(), {
|
||||
wrapper,
|
||||
});
|
||||
|
||||
expect(result.current.isOpen).toBe(true);
|
||||
expect(typeof result.current.setOpen).toBe('function');
|
||||
});
|
||||
|
||||
it('does not need to be invoked within provider', () => {
|
||||
const { result } = renderHook(() => useSidebarOpenState());
|
||||
expect(result.current.isOpen).toBe(false);
|
||||
expect(typeof result.current.setOpen).toBe('function');
|
||||
});
|
||||
|
||||
it('should read and update state', async () => {
|
||||
let actualValue = true;
|
||||
const wrapper = ({ children }: { children: ReactNode }) => (
|
||||
<SidebarOpenStateProvider
|
||||
value={{
|
||||
isOpen: actualValue,
|
||||
setOpen: value => {
|
||||
actualValue = value;
|
||||
},
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</SidebarOpenStateProvider>
|
||||
);
|
||||
const { result, rerender } = renderHook(() => useSidebarOpenState(), {
|
||||
wrapper,
|
||||
});
|
||||
|
||||
expect(result.current.isOpen).toBe(true);
|
||||
|
||||
act(() => {
|
||||
result.current.setOpen(false);
|
||||
rerender();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isOpen).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { createContext, ReactNode, useContext } from 'react';
|
||||
import {
|
||||
createVersionedContext,
|
||||
createVersionedValueMap,
|
||||
} from '@backstage/version-bridge';
|
||||
|
||||
/**
|
||||
* Types for the `SidebarContext`
|
||||
*
|
||||
* @public @deprecated
|
||||
* Use `SidebarOpenState` instead.
|
||||
*/
|
||||
export type SidebarContextType = {
|
||||
isOpen: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* The open state of the sidebar.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type SidebarOpenState = {
|
||||
/**
|
||||
* Whether or not the sidebar is open and full-width. When `false`, the
|
||||
* sidebar is "closed" and typically only shows icons with no text.
|
||||
*/
|
||||
isOpen: boolean;
|
||||
|
||||
/**
|
||||
* A function to set whether or not the sidebar is open. Pass `true` to open
|
||||
* the sidebar. Pass `false` to close it.
|
||||
*/
|
||||
setOpen: (open: boolean) => void;
|
||||
};
|
||||
|
||||
const defaultSidebarOpenStateContext = {
|
||||
isOpen: false,
|
||||
setOpen: () => {},
|
||||
};
|
||||
|
||||
/**
|
||||
* Context whether the `Sidebar` is open
|
||||
*
|
||||
* @public @deprecated
|
||||
* Use `<SidebarContextProvider>` + `useSidebar()` instead.
|
||||
*/
|
||||
export const LegacySidebarContext = createContext<SidebarContextType>(
|
||||
defaultSidebarOpenStateContext,
|
||||
);
|
||||
|
||||
const VersionedSidebarContext = createVersionedContext<{
|
||||
1: SidebarOpenState;
|
||||
}>('sidebar-open-state-context');
|
||||
|
||||
/**
|
||||
* Provides context for reading and updating sidebar state.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const SidebarOpenStateProvider = ({
|
||||
children,
|
||||
value,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
value: SidebarOpenState;
|
||||
}) => (
|
||||
<LegacySidebarContext.Provider value={value}>
|
||||
<VersionedSidebarContext.Provider
|
||||
value={createVersionedValueMap({ 1: value })}
|
||||
>
|
||||
{children}
|
||||
</VersionedSidebarContext.Provider>
|
||||
</LegacySidebarContext.Provider>
|
||||
);
|
||||
|
||||
/**
|
||||
* Hook to read and update the sidebar's open state, which controls whether or
|
||||
* not the sidebar is open and full-width, or closed and only displaying icons.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const useSidebarOpenState = (): SidebarOpenState => {
|
||||
const versionedOpenStateContext = useContext(VersionedSidebarContext);
|
||||
const legacyOpenStateContext = useContext(LegacySidebarContext);
|
||||
|
||||
// Invoked from outside a SidebarOpenStateProvider: check for the legacy
|
||||
// context's value, but otherwise return the default.
|
||||
if (versionedOpenStateContext === undefined) {
|
||||
return legacyOpenStateContext || defaultSidebarOpenStateContext;
|
||||
}
|
||||
|
||||
const openStateContext = versionedOpenStateContext.atVersion(1);
|
||||
if (openStateContext === undefined) {
|
||||
throw new Error('No context found for version 1.');
|
||||
}
|
||||
|
||||
return openStateContext;
|
||||
};
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React, { ReactNode, useContext } from 'react';
|
||||
import { renderWithEffects } from '@backstage/test-utils';
|
||||
import { waitFor } from '@testing-library/react';
|
||||
import { renderHook, act } from '@testing-library/react-hooks';
|
||||
import {
|
||||
LegacySidebarPinStateContext,
|
||||
SidebarPinStateProvider,
|
||||
useSidebarPinState,
|
||||
} from './SidebarPinStateContext';
|
||||
|
||||
describe('SidebarPinStateContext', () => {
|
||||
describe('SidebarPinStateProvider', () => {
|
||||
it('should render children', async () => {
|
||||
const { findByText } = await renderWithEffects(
|
||||
<SidebarPinStateProvider
|
||||
value={{
|
||||
isPinned: true,
|
||||
isMobile: false,
|
||||
toggleSidebarPinState: () => {},
|
||||
}}
|
||||
>
|
||||
Child
|
||||
</SidebarPinStateProvider>,
|
||||
);
|
||||
expect(await findByText('Child')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should provide the legacy context as well, for now', async () => {
|
||||
const LegacyContextSpy = () => {
|
||||
const { isMobile } = useContext(LegacySidebarPinStateContext);
|
||||
return <>{String(isMobile)}</>;
|
||||
};
|
||||
|
||||
const { findByText } = await renderWithEffects(
|
||||
<SidebarPinStateProvider
|
||||
value={{
|
||||
isPinned: true,
|
||||
isMobile: true,
|
||||
toggleSidebarPinState: () => {},
|
||||
}}
|
||||
>
|
||||
<LegacyContextSpy />
|
||||
</SidebarPinStateProvider>,
|
||||
);
|
||||
|
||||
expect(await findByText('true')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('useSidebarPinState', () => {
|
||||
it('can be invoked within legacy context', () => {
|
||||
const wrapper = ({ children }: { children: ReactNode }) => (
|
||||
<LegacySidebarPinStateContext.Provider
|
||||
value={{
|
||||
isPinned: true,
|
||||
isMobile: true,
|
||||
toggleSidebarPinState: () => {},
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</LegacySidebarPinStateContext.Provider>
|
||||
);
|
||||
|
||||
const { result } = renderHook(() => useSidebarPinState(), {
|
||||
wrapper,
|
||||
});
|
||||
|
||||
expect(result.current.isPinned).toBe(true);
|
||||
expect(result.current.isMobile).toBe(true);
|
||||
expect(typeof result.current.toggleSidebarPinState).toBe('function');
|
||||
});
|
||||
|
||||
it('does not need to be invoked within provider', () => {
|
||||
const { result } = renderHook(() => useSidebarPinState());
|
||||
expect(result.current.isPinned).toBe(true);
|
||||
expect(result.current.isMobile).toBe(false);
|
||||
expect(typeof result.current.toggleSidebarPinState).toBe('function');
|
||||
});
|
||||
|
||||
it('should read and update state', async () => {
|
||||
let actualValue = true;
|
||||
const wrapper = ({ children }: { children: ReactNode }) => (
|
||||
<SidebarPinStateProvider
|
||||
value={{
|
||||
isPinned: actualValue,
|
||||
isMobile: false,
|
||||
toggleSidebarPinState: () => {
|
||||
actualValue = !actualValue;
|
||||
},
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</SidebarPinStateProvider>
|
||||
);
|
||||
const { result, rerender } = renderHook(() => useSidebarPinState(), {
|
||||
wrapper,
|
||||
});
|
||||
|
||||
expect(result.current.isPinned).toBe(true);
|
||||
|
||||
act(() => {
|
||||
result.current.toggleSidebarPinState();
|
||||
rerender();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isPinned).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {
|
||||
createVersionedContext,
|
||||
createVersionedValueMap,
|
||||
} from '@backstage/version-bridge';
|
||||
import React, { createContext, ReactNode, useContext } from 'react';
|
||||
|
||||
/**
|
||||
* Type of `SidebarPinStateContext`
|
||||
*
|
||||
* @public @deprecated
|
||||
* Use `SidebarPinState` instead.
|
||||
*/
|
||||
export type SidebarPinStateContextType = {
|
||||
isPinned: boolean;
|
||||
toggleSidebarPinState: () => any;
|
||||
isMobile?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* The pin state of the sidebar.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type SidebarPinState = {
|
||||
/**
|
||||
* Whether or not the sidebar is pinned to the `open` state. When `isPinned`
|
||||
* is `false`, the sidebar opens and closes on hover. When `true`, the
|
||||
* sidebar is permanently opened, regardless of user interaction.
|
||||
*/
|
||||
isPinned: boolean;
|
||||
|
||||
/**
|
||||
* A function to toggle the pin state of the sidebar.
|
||||
*/
|
||||
toggleSidebarPinState: () => any;
|
||||
|
||||
/**
|
||||
* Whether or not the sidebar is or should be rendered in a mobile-optimized
|
||||
* way.
|
||||
*/
|
||||
isMobile?: boolean;
|
||||
};
|
||||
|
||||
const defaultSidebarPinStateContext = {
|
||||
isPinned: true,
|
||||
toggleSidebarPinState: () => {},
|
||||
isMobile: false,
|
||||
};
|
||||
|
||||
/**
|
||||
* Contains the state on how the `Sidebar` is rendered
|
||||
*
|
||||
* @public @deprecated
|
||||
* Use `<SidebarPinStateContextProvider>` + `useSidebarPinState()` instead.
|
||||
*/
|
||||
export const LegacySidebarPinStateContext =
|
||||
createContext<SidebarPinStateContextType>(defaultSidebarPinStateContext);
|
||||
|
||||
const VersionedSidebarPinStateContext = createVersionedContext<{
|
||||
1: SidebarPinState;
|
||||
}>('sidebar-pin-state-context');
|
||||
|
||||
/**
|
||||
* Provides state for how the `Sidebar` is rendered
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const SidebarPinStateProvider = ({
|
||||
children,
|
||||
value,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
value: SidebarPinStateContextType;
|
||||
}) => (
|
||||
<LegacySidebarPinStateContext.Provider value={value}>
|
||||
<VersionedSidebarPinStateContext.Provider
|
||||
value={createVersionedValueMap({ 1: value })}
|
||||
>
|
||||
{children}
|
||||
</VersionedSidebarPinStateContext.Provider>
|
||||
</LegacySidebarPinStateContext.Provider>
|
||||
);
|
||||
|
||||
/**
|
||||
* Hook to read and update sidebar pin state, which controls whether or not the
|
||||
* sidebar is pinned open.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const useSidebarPinState = (): SidebarPinState => {
|
||||
const versionedPinStateContext = useContext(VersionedSidebarPinStateContext);
|
||||
const legacyPinStateContext = useContext(LegacySidebarPinStateContext);
|
||||
|
||||
// Invoked from outside a SidebarPinStateProvider: check for the legacy
|
||||
// context's value, but otherwise return the default.
|
||||
if (versionedPinStateContext === undefined) {
|
||||
return legacyPinStateContext || defaultSidebarPinStateContext;
|
||||
}
|
||||
|
||||
const pinStateContext = versionedPinStateContext.atVersion(1);
|
||||
if (pinStateContext === undefined) {
|
||||
throw new Error('No context found for version 1.');
|
||||
}
|
||||
|
||||
return pinStateContext;
|
||||
};
|
||||
@@ -19,10 +19,10 @@ import classnames from 'classnames';
|
||||
import React, { ReactNode, useContext, useEffect, useState } from 'react';
|
||||
import {
|
||||
SidebarItemWithSubmenuContext,
|
||||
SidebarContext,
|
||||
SidebarConfigContext,
|
||||
SubmenuConfig,
|
||||
} from './config';
|
||||
import { useSidebarOpenState } from './SidebarOpenStateContext';
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
|
||||
const useStyles = makeStyles<
|
||||
@@ -105,7 +105,7 @@ export type SidebarSubmenuProps = {
|
||||
* @public
|
||||
*/
|
||||
export const SidebarSubmenu = (props: SidebarSubmenuProps) => {
|
||||
const { isOpen } = useContext(SidebarContext);
|
||||
const { isOpen } = useSidebarOpenState();
|
||||
const { sidebarConfig, submenuConfig } = useContext(SidebarConfigContext);
|
||||
const left = isOpen
|
||||
? sidebarConfig.drawerWidthOpen
|
||||
|
||||
@@ -101,22 +101,6 @@ export const makeSidebarSubmenuConfig = (
|
||||
export const SIDEBAR_INTRO_LOCAL_STORAGE =
|
||||
'@backstage/core/sidebar-intro-dismissed';
|
||||
|
||||
/**
|
||||
* Types for the `SidebarContext`
|
||||
*/
|
||||
export type SidebarContextType = {
|
||||
isOpen: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* Context whether the `Sidebar` is open
|
||||
*/
|
||||
export const SidebarContext = createContext<SidebarContextType>({
|
||||
isOpen: false,
|
||||
setOpen: () => {},
|
||||
});
|
||||
|
||||
export type SidebarConfigContextType = {
|
||||
sidebarConfig: SidebarConfig;
|
||||
submenuConfig: SubmenuConfig;
|
||||
|
||||
@@ -27,16 +27,8 @@ export type {
|
||||
SidebarSubmenuItemDropdownItem,
|
||||
} from './SidebarSubmenuItem';
|
||||
export type { SidebarClassKey, SidebarProps } from './Bar';
|
||||
export {
|
||||
SidebarPage,
|
||||
SidebarPinStateContext as SidebarPinStateContext,
|
||||
useContent,
|
||||
} from './Page';
|
||||
export type {
|
||||
SidebarPinStateContextType as SidebarPinStateContextType,
|
||||
SidebarPageClassKey,
|
||||
SidebarPageProps,
|
||||
} from './Page';
|
||||
export { SidebarPage, useContent } from './Page';
|
||||
export type { SidebarPageClassKey, SidebarPageProps } from './Page';
|
||||
export {
|
||||
SidebarDivider,
|
||||
SidebarItem,
|
||||
@@ -54,13 +46,23 @@ export type {
|
||||
} from './Items';
|
||||
export { IntroCard, SidebarIntro } from './Intro';
|
||||
export type { SidebarIntroClassKey } from './Intro';
|
||||
export { SIDEBAR_INTRO_LOCAL_STORAGE, sidebarConfig } from './config';
|
||||
export type { SidebarOptions, SubmenuOptions } from './config';
|
||||
export {
|
||||
SIDEBAR_INTRO_LOCAL_STORAGE,
|
||||
SidebarContext,
|
||||
sidebarConfig,
|
||||
} from './config';
|
||||
LegacySidebarContext as SidebarContext,
|
||||
SidebarOpenStateProvider,
|
||||
useSidebarOpenState,
|
||||
} from './SidebarOpenStateContext';
|
||||
export type {
|
||||
SidebarContextType,
|
||||
SidebarOptions,
|
||||
SubmenuOptions,
|
||||
} from './config';
|
||||
SidebarOpenState,
|
||||
} from './SidebarOpenStateContext';
|
||||
export {
|
||||
LegacySidebarPinStateContext as SidebarPinStateContext,
|
||||
SidebarPinStateProvider,
|
||||
useSidebarPinState,
|
||||
} from './SidebarPinStateContext';
|
||||
export type {
|
||||
SidebarPinStateContextType,
|
||||
SidebarPinState,
|
||||
} from './SidebarPinStateContext';
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useContext, PropsWithChildren } from 'react';
|
||||
import React, { PropsWithChildren } from 'react';
|
||||
import { Link, makeStyles } from '@material-ui/core';
|
||||
import HomeIcon from '@material-ui/icons/Home';
|
||||
import ExtensionIcon from '@material-ui/icons/Extension';
|
||||
@@ -32,13 +32,13 @@ import { SidebarSearchModal } from '@backstage/plugin-search';
|
||||
import {
|
||||
Sidebar,
|
||||
sidebarConfig,
|
||||
SidebarContext,
|
||||
SidebarDivider,
|
||||
SidebarGroup,
|
||||
SidebarItem,
|
||||
SidebarPage,
|
||||
SidebarScrollWrapper,
|
||||
SidebarSpace,
|
||||
useSidebarOpenState,
|
||||
} from '@backstage/core-components';
|
||||
import MenuIcon from '@material-ui/icons/Menu';
|
||||
import SearchIcon from '@material-ui/icons/Search';
|
||||
@@ -60,7 +60,7 @@ const useSidebarLogoStyles = makeStyles({
|
||||
|
||||
const SidebarLogo = () => {
|
||||
const classes = useSidebarLogoStyles();
|
||||
const { isOpen } = useContext(SidebarContext);
|
||||
const { isOpen } = useSidebarOpenState();
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { PropsWithChildren, useContext } from 'react';
|
||||
import React, { PropsWithChildren } from 'react';
|
||||
|
||||
import { Link, makeStyles } from '@material-ui/core';
|
||||
import LibraryBooks from '@material-ui/icons/LibraryBooks';
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
SidebarPage,
|
||||
sidebarConfig,
|
||||
SidebarDivider,
|
||||
SidebarContext,
|
||||
useSidebarOpenState,
|
||||
} from '@backstage/core-components';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
|
||||
@@ -48,7 +48,7 @@ const useSidebarLogoStyles = makeStyles({
|
||||
|
||||
const SidebarLogo = () => {
|
||||
const classes = useSidebarLogoStyles();
|
||||
const { isOpen } = useContext(SidebarContext);
|
||||
const { isOpen } = useSidebarOpenState();
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
|
||||
@@ -20,7 +20,7 @@ import { ShortcutItem } from './ShortcutItem';
|
||||
import { Shortcut } from './types';
|
||||
import { LocalStoredShortcuts } from './api';
|
||||
import { MockStorageApi, renderInTestApp } from '@backstage/test-utils';
|
||||
import { SidebarContext } from '@backstage/core-components';
|
||||
import { SidebarOpenStateProvider } from '@backstage/core-components';
|
||||
|
||||
describe('ShortcutItem', () => {
|
||||
const shortcut: Shortcut = {
|
||||
@@ -32,9 +32,9 @@ describe('ShortcutItem', () => {
|
||||
|
||||
it('displays the shortcut', async () => {
|
||||
await renderInTestApp(
|
||||
<SidebarContext.Provider value={{ isOpen: true, setOpen: _open => {} }}>
|
||||
<SidebarOpenStateProvider value={{ isOpen: true, setOpen: _open => {} }}>
|
||||
<ShortcutItem api={api} shortcut={shortcut} />
|
||||
</SidebarContext.Provider>,
|
||||
</SidebarOpenStateProvider>,
|
||||
);
|
||||
expect(screen.getByText('ST')).toBeInTheDocument();
|
||||
expect(screen.getByText('some title')).toBeInTheDocument();
|
||||
|
||||
@@ -24,12 +24,12 @@ import { screen, waitFor } from '@testing-library/react';
|
||||
import { Shortcuts } from './Shortcuts';
|
||||
import { LocalStoredShortcuts, shortcutsApiRef } from './api';
|
||||
|
||||
import { SidebarContext } from '@backstage/core-components';
|
||||
import { SidebarOpenStateProvider } from '@backstage/core-components';
|
||||
|
||||
describe('Shortcuts', () => {
|
||||
it('displays an add button', async () => {
|
||||
await renderInTestApp(
|
||||
<SidebarContext.Provider value={{ isOpen: true, setOpen: _open => {} }}>
|
||||
<SidebarOpenStateProvider value={{ isOpen: true, setOpen: _open => {} }}>
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[
|
||||
@@ -40,7 +40,7 @@ describe('Shortcuts', () => {
|
||||
>
|
||||
<Shortcuts />
|
||||
</TestApiProvider>
|
||||
</SidebarContext.Provider>,
|
||||
</SidebarOpenStateProvider>,
|
||||
);
|
||||
await waitFor(() => !screen.queryByTestId('progress'));
|
||||
expect(screen.getByText('Add Shortcuts')).toBeInTheDocument();
|
||||
|
||||
@@ -14,11 +14,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useCallback, useContext, useMemo } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import { useTheme } from '@material-ui/core';
|
||||
|
||||
import { SidebarPinStateContext } from '@backstage/core-components';
|
||||
import { useSidebarPinState } from '@backstage/core-components';
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
|
||||
import { Transformer } from '../transformer';
|
||||
@@ -27,7 +27,7 @@ import { rules } from './rules';
|
||||
/**
|
||||
* Sidebar pinned state to be used in computing style injections.
|
||||
*/
|
||||
const useSidebar = () => useContext(SidebarPinStateContext);
|
||||
const useSidebar = () => useSidebarPinState();
|
||||
|
||||
/**
|
||||
* Process all rules and concatenate their definitions into a single style.
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { InfoCard, SidebarPinStateContext } from '@backstage/core-components';
|
||||
import { InfoCard, useSidebarPinState } from '@backstage/core-components';
|
||||
import { List } from '@material-ui/core';
|
||||
import React, { useContext } from 'react';
|
||||
import React from 'react';
|
||||
import { UserSettingsPinToggle } from './UserSettingsPinToggle';
|
||||
import { UserSettingsThemeToggle } from './UserSettingsThemeToggle';
|
||||
|
||||
export const UserSettingsAppearanceCard = () => {
|
||||
const { isMobile } = useContext(SidebarPinStateContext);
|
||||
const { isMobile } = useSidebarPinState();
|
||||
|
||||
return (
|
||||
<InfoCard title="Appearance" variant="gridItem">
|
||||
|
||||
@@ -18,14 +18,14 @@ 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 { SidebarPinStateProvider } from '@backstage/core-components';
|
||||
|
||||
describe('<UserSettingsPinToggle />', () => {
|
||||
it('toggles the pin sidebar button', async () => {
|
||||
const mockToggleFn = jest.fn();
|
||||
const rendered = await renderWithEffects(
|
||||
wrapInTestApp(
|
||||
<SidebarPinStateContext.Provider
|
||||
<SidebarPinStateProvider
|
||||
value={{
|
||||
isPinned: false,
|
||||
isMobile: false,
|
||||
@@ -33,7 +33,7 @@ describe('<UserSettingsPinToggle />', () => {
|
||||
}}
|
||||
>
|
||||
<UserSettingsPinToggle />
|
||||
</SidebarPinStateContext.Provider>,
|
||||
</SidebarPinStateProvider>,
|
||||
),
|
||||
);
|
||||
expect(rendered.getByText('Pin Sidebar')).toBeInTheDocument();
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useContext } from 'react';
|
||||
import React from 'react';
|
||||
import {
|
||||
ListItem,
|
||||
ListItemSecondaryAction,
|
||||
@@ -22,12 +22,10 @@ import {
|
||||
Switch,
|
||||
Tooltip,
|
||||
} from '@material-ui/core';
|
||||
import { SidebarPinStateContext } from '@backstage/core-components';
|
||||
import { useSidebarPinState } from '@backstage/core-components';
|
||||
|
||||
export const UserSettingsPinToggle = () => {
|
||||
const { isPinned, toggleSidebarPinState } = useContext(
|
||||
SidebarPinStateContext,
|
||||
);
|
||||
const { isPinned, toggleSidebarPinState } = useSidebarPinState();
|
||||
|
||||
return (
|
||||
<ListItem>
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
import {
|
||||
Header,
|
||||
Page,
|
||||
SidebarPinStateContext,
|
||||
TabbedLayout,
|
||||
useSidebarPinState,
|
||||
} from '@backstage/core-components';
|
||||
import React, { useContext } from 'react';
|
||||
import React from 'react';
|
||||
import { useOutlet } from 'react-router';
|
||||
import { useElementFilter } from '@backstage/core-plugin-api';
|
||||
import { UserSettingsAuthProviders } from './AuthProviders';
|
||||
@@ -33,7 +33,7 @@ type Props = {
|
||||
};
|
||||
|
||||
export const SettingsPage = ({ providerSettings }: Props) => {
|
||||
const { isMobile } = useContext(SidebarPinStateContext);
|
||||
const { isMobile } = useSidebarPinState();
|
||||
const outlet = useOutlet();
|
||||
|
||||
const tabs = useElementFilter(outlet, elements =>
|
||||
|
||||
Reference in New Issue
Block a user