Replace SidebarPinStateContext with versioned provider and hook.
Signed-off-by: Eric Peterson <ericpeterson@spotify.com>
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
@@ -1006,7 +1006,13 @@ export type SidebarPageProps = {
|
||||
};
|
||||
|
||||
// @public
|
||||
export const SidebarPinStateContext: React_2.Context<SidebarPinStateContextType>;
|
||||
export const SidebarPinStateContextProvider: ({
|
||||
children,
|
||||
value,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
value: SidebarPinStateContextType;
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public
|
||||
export type SidebarPinStateContextType = {
|
||||
@@ -1451,6 +1457,9 @@ export class UserIdentity implements IdentityApi {
|
||||
// @public
|
||||
export const useSidebar: () => SidebarContextType;
|
||||
|
||||
// @public
|
||||
export const useSidebarPinState: () => SidebarPinStateContextType;
|
||||
|
||||
// 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)
|
||||
|
||||
@@ -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,
|
||||
SidebarPinStateContextProvider,
|
||||
SidebarSubmenu,
|
||||
SidebarSubmenuItem,
|
||||
} from '.';
|
||||
|
||||
async function renderScalableSidebar() {
|
||||
await renderInTestApp(
|
||||
<SidebarPinStateContext.Provider
|
||||
<SidebarPinStateContextProvider
|
||||
value={{
|
||||
isPinned: false,
|
||||
isMobile: false,
|
||||
@@ -75,7 +75,7 @@ async function renderScalableSidebar() {
|
||||
<SidebarItem icon={CreateComponentIcon} to="create" text="Create..." />
|
||||
<SidebarExpandButton />
|
||||
</Sidebar>
|
||||
</SidebarPinStateContext.Provider>,
|
||||
</SidebarPinStateContextProvider>,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -31,8 +31,9 @@ import {
|
||||
SubmenuOptions,
|
||||
} from './config';
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
import { SidebarPinStateContext, useContent } from './Page';
|
||||
import { useContent } from './Page';
|
||||
import { SidebarContextProvider } from './SidebarContext';
|
||||
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) {
|
||||
@@ -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>
|
||||
|
||||
@@ -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 { SidebarPinStateContextProvider } 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
|
||||
<SidebarPinStateContextProvider
|
||||
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>
|
||||
</SidebarPinStateContextProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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,79 @@
|
||||
/*
|
||||
* 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 } from 'react';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
import { screen, waitFor } from '@testing-library/react';
|
||||
import { renderHook, act } from '@testing-library/react-hooks';
|
||||
import {
|
||||
SidebarPinStateContextProvider,
|
||||
useSidebarPinState,
|
||||
} from './SidebarPinStateContext';
|
||||
|
||||
describe('SidebarContext', () => {
|
||||
describe('SidebarContextProvider', () => {
|
||||
it('should render children', async () => {
|
||||
await renderInTestApp(
|
||||
<SidebarPinStateContextProvider
|
||||
value={{
|
||||
isPinned: true,
|
||||
isMobile: false,
|
||||
toggleSidebarPinState: () => {},
|
||||
}}
|
||||
>
|
||||
Child
|
||||
</SidebarPinStateContextProvider>,
|
||||
);
|
||||
expect(await screen.findByText('Child')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('useSidebar', () => {
|
||||
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 }) => (
|
||||
<SidebarPinStateContextProvider
|
||||
value={{
|
||||
isPinned: actualValue,
|
||||
isMobile: false,
|
||||
toggleSidebarPinState: () => {
|
||||
actualValue = !actualValue;
|
||||
},
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</SidebarPinStateContextProvider>
|
||||
);
|
||||
const { result } = renderHook(() => useSidebarPinState(), { wrapper });
|
||||
|
||||
expect(result.current.isPinned).toBe(true);
|
||||
|
||||
act(() => {
|
||||
result.current.toggleSidebarPinState();
|
||||
});
|
||||
|
||||
waitFor(() => {
|
||||
expect(result.current.isPinned).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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, { ReactNode, useContext } from 'react';
|
||||
|
||||
/**
|
||||
* Type of `SidebarPinStateContext`
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type SidebarPinStateContextType = {
|
||||
isPinned: boolean;
|
||||
toggleSidebarPinState: () => any;
|
||||
isMobile?: boolean;
|
||||
};
|
||||
|
||||
const VersionedSidebarPinStateContext = createVersionedContext<{
|
||||
1: SidebarPinStateContextType;
|
||||
}>('sidebar-pin-state-context');
|
||||
|
||||
/**
|
||||
* Provides state for how the `Sidebar` is rendered
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const SidebarPinStateContextProvider = ({
|
||||
children,
|
||||
value,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
value: SidebarPinStateContextType;
|
||||
}) => (
|
||||
<VersionedSidebarPinStateContext.Provider
|
||||
value={createVersionedValueMap({ 1: value })}
|
||||
>
|
||||
{children}
|
||||
</VersionedSidebarPinStateContext.Provider>
|
||||
);
|
||||
|
||||
/**
|
||||
* Hook to read and update sidebar pin state.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const useSidebarPinState = (): SidebarPinStateContextType => {
|
||||
const versionedSidebarContext = useContext(VersionedSidebarPinStateContext);
|
||||
|
||||
// Invoked from outside a SidebarPinStateContextProvider: default value.
|
||||
if (versionedSidebarContext === undefined) {
|
||||
return {
|
||||
isPinned: true,
|
||||
toggleSidebarPinState: () => {},
|
||||
isMobile: false,
|
||||
};
|
||||
}
|
||||
|
||||
const sidebarContext = versionedSidebarContext.atVersion(1);
|
||||
if (sidebarContext === undefined) {
|
||||
throw new Error('No context found for version 1.');
|
||||
}
|
||||
|
||||
return sidebarContext;
|
||||
};
|
||||
@@ -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,
|
||||
@@ -58,3 +50,8 @@ export { SIDEBAR_INTRO_LOCAL_STORAGE, sidebarConfig } from './config';
|
||||
export type { SidebarOptions, SubmenuOptions } from './config';
|
||||
export { SidebarContextProvider, useSidebar } from './SidebarContext';
|
||||
export type { SidebarContextType } from './SidebarContext';
|
||||
export {
|
||||
SidebarPinStateContextProvider,
|
||||
useSidebarPinState,
|
||||
} from './SidebarPinStateContext';
|
||||
export type { SidebarPinStateContextType } from './SidebarPinStateContext';
|
||||
|
||||
@@ -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 { SidebarPinStateContextProvider } from '@backstage/core-components';
|
||||
|
||||
describe('<UserSettingsPinToggle />', () => {
|
||||
it('toggles the pin sidebar button', async () => {
|
||||
const mockToggleFn = jest.fn();
|
||||
const rendered = await renderWithEffects(
|
||||
wrapInTestApp(
|
||||
<SidebarPinStateContext.Provider
|
||||
<SidebarPinStateContextProvider
|
||||
value={{
|
||||
isPinned: false,
|
||||
isMobile: false,
|
||||
@@ -33,7 +33,7 @@ describe('<UserSettingsPinToggle />', () => {
|
||||
}}
|
||||
>
|
||||
<UserSettingsPinToggle />
|
||||
</SidebarPinStateContext.Provider>,
|
||||
</SidebarPinStateContextProvider>,
|
||||
),
|
||||
);
|
||||
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