From 02338a65157fe16d6e9c8710cd5e91aafeae1a4f Mon Sep 17 00:00:00 2001 From: Philipp Hugenroth Date: Wed, 29 Dec 2021 18:05:17 +0100 Subject: [PATCH] Improve documentation - Fix tests Signed-off-by: Philipp Hugenroth --- packages/core-components/api-report.md | 37 +++++++----- .../core-components/src/layout/Page/Page.tsx | 2 +- .../src/layout/Sidebar/Bar.test.tsx | 26 +++++---- .../src/layout/Sidebar/Bar.tsx | 34 ++++++++--- .../src/layout/Sidebar/Items.tsx | 29 +++++++++- .../src/layout/Sidebar/MobileSidebar.test.tsx | 57 +++++++++++-------- .../src/layout/Sidebar/MobileSidebar.tsx | 27 +++++++-- .../src/layout/Sidebar/Page.tsx | 12 +++- .../src/layout/Sidebar/SidebarGroup.test.tsx | 38 ++++++++----- .../src/layout/Sidebar/SidebarGroup.tsx | 29 +++++++++- .../src/layout/Sidebar/SidebarSubmenuItem.tsx | 1 - .../src/layout/Sidebar/config.ts | 6 ++ .../src/layout/Sidebar/index.ts | 3 +- .../src/overridableComponents.ts | 4 ++ .../General/UserSettingsAppearanceCard.tsx | 2 +- 15 files changed, 221 insertions(+), 86 deletions(-) diff --git a/packages/core-components/api-report.md b/packages/core-components/api-report.md index 04ca94bdcb..2f4a1fb107 100644 --- a/packages/core-components/api-report.md +++ b/packages/core-components/api-report.md @@ -700,11 +700,20 @@ export function MissingAnnotationEmptyState(props: Props_3): JSX.Element; // @public (undocumented) export type MissingAnnotationEmptyStateClassKey = 'code'; -// @public (undocumented) +// @public export const MobileSidebar: ( props: React_2.PropsWithChildren<{}>, ) => JSX.Element | null; +// @public +export const MobileSidebarContext: React_2.Context; + +// @public +export type MobileSidebarContextType = { + selectedMenuItemIndex: number; + setSelectedMenuItemIndex: React_2.Dispatch>; +}; + // Warning: (ae-missing-release-tag) "OAuthRequestDialog" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -844,7 +853,7 @@ export type SelectItem = { value: string | number; }; -// @public (undocumented) +// @public export const Sidebar: ( props: React_2.PropsWithChildren, ) => JSX.Element; @@ -879,12 +888,12 @@ export const sidebarConfig: { // Warning: (ae-missing-release-tag) "SidebarContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // -// @public (undocumented) +// @public export const SidebarContext: Context; // Warning: (ae-missing-release-tag) "SidebarContextType" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // -// @public (undocumented) +// @public export type SidebarContextType = { isOpen: boolean; setOpen?: (open: boolean) => void; @@ -1167,16 +1176,14 @@ export type SidebarDividerClassKey = 'root'; // @public export const SidebarExpandButton: () => JSX.Element | null; -// @public (undocumented) +// @public export const SidebarGroup: ( props: React_2.PropsWithChildren, ) => JSX.Element; -// @public (undocumented) +// @public export interface SidebarGroupProps extends BottomNavigationActionProps { - // (undocumented) priority?: number; - // (undocumented) to?: string; } @@ -1196,19 +1203,19 @@ export type SidebarIntroClassKey = // Warning: (ae-forgotten-export) The symbol "SidebarItemProps" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "SidebarItem" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // -// @public (undocumented) +// @public export const SidebarItem: React_2.ForwardRefExoticComponent< SidebarItemProps & React_2.RefAttributes >; -// Warning: (ae-missing-release-tag) "SidebarItemClassKey" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export type SidebarItemClassKey = | 'root' | 'buttonItem' | 'closed' | 'open' + | 'highlightable' + | 'highlighted' | 'label' | 'iconContainer' | 'searchRoot' @@ -1216,6 +1223,10 @@ export type SidebarItemClassKey = | 'searchFieldHTMLInput' | 'searchContainer' | 'secondaryAction' + | 'closedItemIcon' + | 'submenuArrow' + | 'expandButton' + | 'arrows' | 'selected'; // Warning: (ae-missing-release-tag) "SidebarPage" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -2058,10 +2069,10 @@ export const SidebarSpacer: React_2.ComponentType< // @public (undocumented) export type SidebarSpacerClassKey = 'root'; -// @public (undocumented) +// @public export const SidebarStateContext: React_2.Context; -// @public (undocumented) +// @public export type SidebarStateContextType = { isPinned: boolean; toggleSidebarPinState: () => any; diff --git a/packages/core-components/src/layout/Page/Page.tsx b/packages/core-components/src/layout/Page/Page.tsx index a1efe4dda9..b013affee3 100644 --- a/packages/core-components/src/layout/Page/Page.tsx +++ b/packages/core-components/src/layout/Page/Page.tsx @@ -14,9 +14,9 @@ * limitations under the License. */ +import React, { PropsWithChildren } from 'react'; import { BackstageTheme } from '@backstage/theme'; import { makeStyles, ThemeProvider } from '@material-ui/core/styles'; -import React, { PropsWithChildren } from 'react'; export type PageClassKey = 'root'; diff --git a/packages/core-components/src/layout/Sidebar/Bar.test.tsx b/packages/core-components/src/layout/Sidebar/Bar.test.tsx index acfd949ac5..0e0a7d5aa2 100644 --- a/packages/core-components/src/layout/Sidebar/Bar.test.tsx +++ b/packages/core-components/src/layout/Sidebar/Bar.test.tsx @@ -14,19 +14,23 @@ * limitations under the License. */ -import React from 'react'; import { renderInTestApp } from '@backstage/test-utils'; +import AcUnitIcon from '@material-ui/icons/AcUnit'; +import CreateComponentIcon from '@material-ui/icons/AddCircleOutline'; +import BuildRoundedIcon from '@material-ui/icons/BuildRounded'; +import MenuBookIcon from '@material-ui/icons/MenuBook'; import { screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import BuildRoundedIcon from '@material-ui/icons/BuildRounded'; -import CreateComponentIcon from '@material-ui/icons/AddCircleOutline'; -import MenuBookIcon from '@material-ui/icons/MenuBook'; -import AcUnitIcon from '@material-ui/icons/AcUnit'; -import { Sidebar } from './Bar'; -import { SidebarItem, SidebarSearchField, SidebarExpandButton } from './Items'; -import { SidebarSubmenuItem } from './SidebarSubmenuItem'; -import { SidebarSubmenu } from './SidebarSubmenu'; -import { SidebarStateContext } from '.'; +import React from 'react'; +import { + Sidebar, + SidebarExpandButton, + SidebarItem, + SidebarSearchField, + SidebarStateContext, + SidebarSubmenu, + SidebarSubmenuItem, +} from '.'; async function renderScalableSidebar() { await renderInTestApp( @@ -37,7 +41,7 @@ async function renderScalableSidebar() { toggleSidebarPinState: () => {}, }} > - + {}} to="/search" /> {}} text="Catalog"> diff --git a/packages/core-components/src/layout/Sidebar/Bar.tsx b/packages/core-components/src/layout/Sidebar/Bar.tsx index 51d4268d05..c3eddfe218 100644 --- a/packages/core-components/src/layout/Sidebar/Bar.tsx +++ b/packages/core-components/src/layout/Sidebar/Bar.tsx @@ -36,7 +36,7 @@ const useStyles = makeStyles( left: 0, top: 0, bottom: 0, - zIndex: 1000, + zIndex: theme.zIndex.appBar, background: theme.palette.navigation.background, overflowX: 'hidden', msOverflowStyle: 'none', @@ -77,12 +77,23 @@ export type SidebarProps = { disableExpandOnHover?: boolean; }; -const DesktopSidebar = ({ - openDelayMs = sidebarConfig.defaultOpenDelayMs, - closeDelayMs = sidebarConfig.defaultCloseDelayMs, - disableExpandOnHover, - children, -}: PropsWithChildren) => { +/** + * Places the Sidebar & wraps the children providing context weather the `Sidebar` is open or not. + * + * Handles & delays hover events for expanding the `Sidebar` + * + * @param props `disableExpandOnHover` disables the default hover behaviour; + * `openDelayMs` & `closeDelayMs` set delay until sidebar will open/close on hover + * @returns + * @internal + */ +const DesktopSidebar = (props: PropsWithChildren) => { + const { + openDelayMs = sidebarConfig.defaultOpenDelayMs, + closeDelayMs = sidebarConfig.defaultCloseDelayMs, + disableExpandOnHover, + children, + } = props; const classes = useStyles(); const isSmallScreen = useMediaQuery( theme => theme.breakpoints.down('md'), @@ -130,6 +141,9 @@ const DesktopSidebar = ({ const isOpen = (state === State.Open && !isSmallScreen) || isPinned; + /** + * Close/Open Sidebar directily without delays. Also toggles `SidebarPinState` to avoid hidden content behind Sidebar. + */ const setOpen = (open: boolean) => { if (open) { setState(State.Open); @@ -163,7 +177,11 @@ const DesktopSidebar = ({ ); }; -/** @public */ +/** + * Passing children into the desktop or mobile sidebar depending on the context + * + * @public + */ export const Sidebar = (props: React.PropsWithChildren) => { const { children, openDelayMs, closeDelayMs, disableExpandOnHover } = props; const { isMobile } = useContext(SidebarStateContext); diff --git a/packages/core-components/src/layout/Sidebar/Items.tsx b/packages/core-components/src/layout/Sidebar/Items.tsx index 7e95ecb92b..f1b517cbce 100644 --- a/packages/core-components/src/layout/Sidebar/Items.tsx +++ b/packages/core-components/src/layout/Sidebar/Items.tsx @@ -37,9 +37,9 @@ import React, { import { Link, NavLinkProps, + resolvePath, useLocation, useResolvedPath, - resolvePath, } from 'react-router-dom'; import { sidebarConfig, @@ -56,11 +56,14 @@ import DoubleArrowRight from './icons/DoubleArrowRight'; import { isLocationMatch } from './utils'; import { Location } from 'history'; +/** @public */ export type SidebarItemClassKey = | 'root' | 'buttonItem' | 'closed' | 'open' + | 'highlightable' + | 'highlighted' | 'label' | 'iconContainer' | 'searchRoot' @@ -68,6 +71,10 @@ export type SidebarItemClassKey = | 'searchFieldHTMLInput' | 'searchContainer' | 'secondaryAction' + | 'closedItemIcon' + | 'submenuArrow' + | 'expandButton' + | 'arrows' | 'selected'; const useStyles = makeStyles( @@ -238,12 +245,18 @@ const useStyles = makeStyles( { name: 'BackstageSidebarItem' }, ); +/** + * Evaluates the routes of the SubmenuItems & nested DropdownItems. + * The reeveluation is only triggered, if the `locationPathname` changes, as `useElementFilter` uses memorization. + * + * @param submenu SidebarSubmenu component + * @param location Location + * @returns boolean + */ const useLocationMatch = ( submenu: React.ReactElement, location: Location, ): boolean => - // Evaluates the routes of the SubmenuItems & nested DropdownItems. - // The reeveluation is only triggered, if the `locationPathname` changes, as `useElementFilter` uses memorization useElementFilter( submenu.props.children, elements => { @@ -367,6 +380,9 @@ export const WorkaroundNavLink = React.forwardRef< ); }); +/** + * Common component used by SidebarItem & SidebarItemWithSubmenu + */ const SidebarItemBase = forwardRef((props, ref) => { const { icon: Icon, @@ -507,6 +523,11 @@ const SidebarItemWithSubmenu = ({ ); }; +/** + * Creates a `SidebarItem` + * + * If children contain a `SidebarSubmenu` component the `SidebarItem` will have a expandable submenu + */ export const SidebarItem = forwardRef((props, ref) => { // Filter children for SidebarSubmenu components const [submenu] = useElementFilter(props.children, elements => @@ -655,6 +676,8 @@ export const SidebarScrollWrapper = styled('div')(({ theme }) => { * A button which allows you to expand the sidebar when clicked. * Use optionally to replace sidebar's expand-on-hover feature with expand-on-click. * + * If you are using this you might want to set the `disableExpandOnHover` of the `Sidebar` to `true`. + * * @public */ export const SidebarExpandButton = () => { diff --git a/packages/core-components/src/layout/Sidebar/MobileSidebar.test.tsx b/packages/core-components/src/layout/Sidebar/MobileSidebar.test.tsx index 6f6463a3dc..8dd06b6e2b 100644 --- a/packages/core-components/src/layout/Sidebar/MobileSidebar.test.tsx +++ b/packages/core-components/src/layout/Sidebar/MobileSidebar.test.tsx @@ -21,32 +21,39 @@ import LayersIcon from '@material-ui/icons/Layers'; import LibraryBooks from '@material-ui/icons/LibraryBooks'; import { fireEvent } from '@testing-library/react'; import React from 'react'; -import { Sidebar } from './Bar'; -import { SidebarItem } from './Items'; -import { MobileSidebar } from './MobileSidebar'; -import { SidebarGroup } from './SidebarGroup'; +import { + MobileSidebar, + Sidebar, + SidebarGroup, + SidebarItem, + SidebarPage, +} from '.'; const MobileSidebarWithGroups = () => ( - -

Header

- } label="Menu"> - - - - -
Content
-
More Content
- } label="Create" to="#" /> -
Footer
-
+ + +

Header

+ } label="Menu"> + + + + +
Content
+
More Content
+ } label="Create" to="#" /> +
Footer
+
+
); const MobileSidebarWithoutGroups = () => ( - - - - - + + + + + + + ); describe('', () => { @@ -56,9 +63,11 @@ describe('', () => { it('should render MobileSidebar on smaller screens', async () => { const { getByTestId } = await renderInTestApp( - - - , + + + + + , ); expect(getByTestId('mobile-sidebar-root')).toBeVisible(); }); diff --git a/packages/core-components/src/layout/Sidebar/MobileSidebar.tsx b/packages/core-components/src/layout/Sidebar/MobileSidebar.tsx index 158b20e6be..af66dcc322 100644 --- a/packages/core-components/src/layout/Sidebar/MobileSidebar.tsx +++ b/packages/core-components/src/layout/Sidebar/MobileSidebar.tsx @@ -31,7 +31,12 @@ import { SidebarContext } from '.'; import { sidebarConfig } from './config'; import { SidebarGroup } from './SidebarGroup'; -type MobileSidebarContextType = { +/** + * Type of `MobileSidebarContext` + * + * @public + */ +export type MobileSidebarContextType = { selectedMenuItemIndex: number; setSelectedMenuItemIndex: React.Dispatch>; }; @@ -111,12 +116,25 @@ const OverlayMenu = ({ ); }; +/** + * Context on which `SidebarGroup` is currently selected + * + * @public + */ export const MobileSidebarContext = createContext({ selectedMenuItemIndex: -1, setSelectedMenuItemIndex: () => {}, }); -/** @public */ +/** + * A navigation component for mobile screens, which sticks to the bottom. + * + * It alternates the normal sidebar by grouping the `SidebarItems` based on provided `SidebarGroups` + * either rendering them as a link or an overlay menu. + * If no `SidebarGroups` are provided the sidebar content is wrapped in an default overlay menu. + * + * @public + */ export const MobileSidebar = (props: React.PropsWithChildren<{}>) => { const { children } = props; const classes = useStyles(); @@ -160,9 +178,8 @@ export const MobileSidebar = (props: React.PropsWithChildren<{}>) => { > setSelectedMenuItemIndex(-1)} diff --git a/packages/core-components/src/layout/Sidebar/Page.tsx b/packages/core-components/src/layout/Sidebar/Page.tsx index 26b3426eb0..2bc4ed195b 100644 --- a/packages/core-components/src/layout/Sidebar/Page.tsx +++ b/packages/core-components/src/layout/Sidebar/Page.tsx @@ -47,14 +47,22 @@ const useStyles = makeStyles( { name: 'BackstageSidebarPage' }, ); -/** @public */ +/** + * Type of `SidebarStateContext` + * + * @public + */ export type SidebarStateContextType = { isPinned: boolean; toggleSidebarPinState: () => any; isMobile: boolean; }; -/** @public */ +/** + * Contains the state on how the `Sidebar` is rendered + * + * @public + */ export const SidebarStateContext = createContext({ isPinned: true, toggleSidebarPinState: () => {}, diff --git a/packages/core-components/src/layout/Sidebar/SidebarGroup.test.tsx b/packages/core-components/src/layout/Sidebar/SidebarGroup.test.tsx index 664495067c..e4d7e21c50 100644 --- a/packages/core-components/src/layout/Sidebar/SidebarGroup.test.tsx +++ b/packages/core-components/src/layout/Sidebar/SidebarGroup.test.tsx @@ -20,32 +20,42 @@ import LayersIcon from '@material-ui/icons/Layers'; import LibraryBooks from '@material-ui/icons/LibraryBooks'; import { fireEvent } from '@testing-library/react'; import React from 'react'; -import { SidebarItem } from './Items'; -import { MobileSidebarContext } from './MobileSidebar'; -import { SidebarGroup } from './SidebarGroup'; +import { + MobileSidebarContext, + SidebarGroup, + SidebarItem, + SidebarPage, +} from '.'; const SidebarGroupWithItems = () => ( - } label="Menu"> - - - - + + } label="Menu"> + + + + + ); describe('', () => { - it('should render Items in BottomNavigationAciton on small screens', async () => { + it('should render Items in BottomNavigationAction on small screens', async () => { mockBreakpoint({ matches: true }); - const { findByRole, container } = await renderInTestApp( + const { getByRole, getAllByRole } = await renderInTestApp( , ); - expect(container.childNodes.length).toBe(1); - expect(await findByRole('button')).toBeVisible(); + expect(getAllByRole('button').length).toEqual(1); + expect(getByRole('button')).toBeVisible(); }); it('should render Items without wrapper on bigger screens', async () => { mockBreakpoint({ matches: false }); - const { container } = await renderInTestApp(); - expect(container.childNodes.length).toBe(3); + const { getByRole, queryByRole } = await renderInTestApp( + , + ); + expect(queryByRole('button')).not.toBeInTheDocument(); + expect(getByRole('link', { name: 'Home' })).toBeVisible(); + expect(getByRole('link', { name: 'Explore' })).toBeVisible(); + expect(getByRole('link', { name: 'Docs' })).toBeVisible(); }); it('should trigger update of MobileSidebarContext', async () => { diff --git a/packages/core-components/src/layout/Sidebar/SidebarGroup.tsx b/packages/core-components/src/layout/Sidebar/SidebarGroup.tsx index 1a43f703bf..f80af385c8 100644 --- a/packages/core-components/src/layout/Sidebar/SidebarGroup.tsx +++ b/packages/core-components/src/layout/Sidebar/SidebarGroup.tsx @@ -27,9 +27,20 @@ import { Link } from '../../components'; import { sidebarConfig } from './config'; import { MobileSidebarContext } from './MobileSidebar'; -/** @public */ +/** + * Props for the `SidebarGroup` + * + * @public + */ export interface SidebarGroupProps extends BottomNavigationActionProps { + /** + * If the `SidebarGroup` should be a `Link`, `to` should be a pathname to that location + */ to?: string; + /** + * If the `SidebarGroups` should be in a different order than in the normal `Sidebar`, you can provide + * each `SidebarGroup` it's own priority to reorder them. + */ priority?: number; } @@ -51,6 +62,13 @@ const useStyles = makeStyles(theme => ({ }, })); +/** + * Returns a MUI `BottomNavigationAction`, which is aware of the current location & the selected item in the `BottomNavigation`, + * such that it will highlight a `MobileSidebarGroup` either on location change or if the selected item changes. + * + * @param props `to`: pathname of link; `value`: index of the selected item + * @internal + */ const MobileSidebarGroup = (props: SidebarGroupProps) => { const { to, label, icon, value } = props; const classes = useStyles(); @@ -88,7 +106,14 @@ const MobileSidebarGroup = (props: SidebarGroupProps) => { ); }; -/** @public */ +/** + * Groups items of the `Sidebar` together. + * + * On bigger screens, this won't have any effect at the moment. + * On small screens, it will add an action to the bottom navigation - either triggering an overlay menu or acting as a link + * + * @public + */ export const SidebarGroup = ( props: React.PropsWithChildren, ) => { diff --git a/packages/core-components/src/layout/Sidebar/SidebarSubmenuItem.tsx b/packages/core-components/src/layout/Sidebar/SidebarSubmenuItem.tsx index 47aba5e18e..1ede5f887e 100644 --- a/packages/core-components/src/layout/Sidebar/SidebarSubmenuItem.tsx +++ b/packages/core-components/src/layout/Sidebar/SidebarSubmenuItem.tsx @@ -122,7 +122,6 @@ export const SidebarSubmenuItem = (props: SidebarSubmenuItemProps) => { const { title, to, icon: Icon, dropdownItems } = props; const classes = useStyles(); const { setIsHoveredOn } = useContext(SidebarItemWithSubmenuContext); - const closeSubmenu = () => { setIsHoveredOn(false); }; diff --git a/packages/core-components/src/layout/Sidebar/config.ts b/packages/core-components/src/layout/Sidebar/config.ts index a26a3dbc0d..0ef5282997 100644 --- a/packages/core-components/src/layout/Sidebar/config.ts +++ b/packages/core-components/src/layout/Sidebar/config.ts @@ -58,11 +58,17 @@ export const submenuConfig = { 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 wether the `Sidebar` is open + */ export const SidebarContext = createContext({ isOpen: false, }); diff --git a/packages/core-components/src/layout/Sidebar/index.ts b/packages/core-components/src/layout/Sidebar/index.ts index 4a1e1c0474..a9d31f465b 100644 --- a/packages/core-components/src/layout/Sidebar/index.ts +++ b/packages/core-components/src/layout/Sidebar/index.ts @@ -15,7 +15,8 @@ */ export { Sidebar } from './Bar'; -export { MobileSidebar } from './MobileSidebar'; +export { MobileSidebar, MobileSidebarContext } from './MobileSidebar'; +export type { MobileSidebarContextType } from './MobileSidebar'; export { SidebarGroup } from './SidebarGroup'; export type { SidebarGroupProps } from './SidebarGroup'; export { SidebarSubmenuItem } from './SidebarSubmenuItem'; diff --git a/packages/core-components/src/overridableComponents.ts b/packages/core-components/src/overridableComponents.ts index 8044da4c81..c53c47da1b 100644 --- a/packages/core-components/src/overridableComponents.ts +++ b/packages/core-components/src/overridableComponents.ts @@ -87,6 +87,8 @@ import { SidebarSpacerClassKey, SidebarDividerClassKey, SidebarIntroClassKey, + SidebarItemClassKey, + SidebarPageClassKey, CustomProviderClassKey, SignInPageClassKey, TabbedCardClassKey, @@ -162,6 +164,8 @@ type BackstageComponentsNameToClassKey = { BackstageSidebarSpacer: SidebarSpacerClassKey; BackstageSidebarDivider: SidebarDividerClassKey; BackstageSidebarIntro: SidebarIntroClassKey; + BackstageSidebarItem: SidebarItemClassKey; + BackstageSidebarPage: SidebarPageClassKey; BackstageCustomProvider: CustomProviderClassKey; BackstageSignInPage: SignInPageClassKey; BackstageTabbedCard: TabbedCardClassKey; diff --git a/plugins/user-settings/src/components/General/UserSettingsAppearanceCard.tsx b/plugins/user-settings/src/components/General/UserSettingsAppearanceCard.tsx index 915edc94cb..946d22a9f0 100644 --- a/plugins/user-settings/src/components/General/UserSettingsAppearanceCard.tsx +++ b/plugins/user-settings/src/components/General/UserSettingsAppearanceCard.tsx @@ -20,7 +20,7 @@ import { UserSettingsPinToggle } from './UserSettingsPinToggle'; import { UserSettingsThemeToggle } from './UserSettingsThemeToggle'; export const UserSettingsAppearanceCard = () => { - const isMobile = useContext(SidebarStateContext); + const { isMobile } = useContext(SidebarStateContext); return (