diff --git a/packages/gitbook/src/components/SpaceLayout/SpaceLayout.tsx b/packages/gitbook/src/components/SpaceLayout/SpaceLayout.tsx index 556cb32dc..e9076e4ad 100644 --- a/packages/gitbook/src/components/SpaceLayout/SpaceLayout.tsx +++ b/packages/gitbook/src/components/SpaceLayout/SpaceLayout.tsx @@ -5,15 +5,13 @@ import React from 'react'; import { Footer } from '@/components/Footer'; import { Header, HeaderLogo } from '@/components/Header'; import { SearchButton, SearchModal } from '@/components/Search'; -import { TableOfContents } from '@/components/TableOfContents'; +import { TOCScrollContent, TableOfContents } from '@/components/TableOfContents'; import { CONTAINER_STYLE } from '@/components/layout'; import { getSpaceLanguage } from '@/intl/server'; import { t } from '@/intl/translate'; +import type { VisitorAuthClaims } from '@/lib/adaptive'; import { tcls } from '@/lib/tailwind'; -import { MobileMenuSheet } from '@/components/MobileMenu'; -import { TOCScrollContent } from '@/components/TableOfContents/TOCScrollContent'; -import type { VisitorAuthClaims } from '@/lib/adaptive'; import { GITBOOK_API_PUBLIC_URL, GITBOOK_APP_URL } from '@v2/lib/env'; import { Announcement } from '../Announcement'; import { SpacesDropdown } from '../Header/SpacesDropdown'; @@ -68,27 +66,6 @@ export function SpaceLayout(props: { >
- - - ) : null - } - context={context} - /> -
) } - innerHeader={ - // displays the search button and/or the space dropdown in the ToC according to the header/variant settings. E.g if there is no header, the search button will be displayed in the ToC. - <> - {!withTopHeader && ( -
- - - - {t( - getSpaceLanguage(customization), - customization.aiSearch.enabled - ? 'search_or_ask' - : 'search' - )} - ... - - - -
- )} - {!withTopHeader && withSections && sections && ( - - )} - {isMultiVariants && ( - + + {!withTopHeader && ( +
+ + + + {t( + getSpaceLanguage(customization), + customization.aiSearch.enabled + ? 'search_or_ask' + : 'search' + )} + ... + + + +
)} - /> - )} - - } - /> + {!withTopHeader && withSections && sections && ( + + )} + {isMultiVariants && ( + + )} + + ) : null + } + /> +
{children}
diff --git a/packages/gitbook/src/components/TableOfContents/TOCScrollContent.tsx b/packages/gitbook/src/components/TableOfContents/TOCScrollContent.tsx index aeddd1f56..a30bf24b3 100644 --- a/packages/gitbook/src/components/TableOfContents/TOCScrollContent.tsx +++ b/packages/gitbook/src/components/TableOfContents/TOCScrollContent.tsx @@ -40,8 +40,8 @@ export function TOCScrollContent(props: { {children}; + } return ( <> @@ -65,7 +72,7 @@ export function TableOfContents(props: { )} > {header && header} - + {children} diff --git a/packages/gitbook/src/components/TableOfContents/index.ts b/packages/gitbook/src/components/TableOfContents/index.ts index 6eeff9269..d52040bae 100644 --- a/packages/gitbook/src/components/TableOfContents/index.ts +++ b/packages/gitbook/src/components/TableOfContents/index.ts @@ -2,3 +2,4 @@ export { TableOfContents } from './TableOfContents'; export { PagesList } from './PagesList'; export { TOCScrollContainer } from './TOCScroller'; export { Trademark } from './Trademark'; +export { TOCScrollContent } from './TOCScrollContent'; diff --git a/packages/gitbook/src/hooks/useIsMobile.ts b/packages/gitbook/src/hooks/useIsMobile.ts new file mode 100644 index 000000000..f2625c997 --- /dev/null +++ b/packages/gitbook/src/hooks/useIsMobile.ts @@ -0,0 +1,61 @@ +import { useSyncExternalStore } from 'react'; + +/** + * Hook to check if the screen is mobile + * @default breakpoint => 1024 + */ +export function useIsMobile(breakpoint = 1024) { + const store = getMediaQueryStore(breakpoint); + + return useSyncExternalStore( + (cb) => { + store.subscribers.add(cb); + return () => store.subscribers.delete(cb); + }, + () => store.isMatch, + () => false + ); +} + +type MediaQueryStore = { + /** Latest match result */ + isMatch: boolean; + /** The native MediaQueryList object */ + mediaQueryList: MediaQueryList; + /** React subscribers that need re-rendering on change */ + subscribers: Set<() => void>; +}; + +const mediaQueryStores: Record = {}; + +/** + * getMediaQueryStore("(max-width: 1024px)") + * Returns a singleton store for that query, + * creating it (and its listener) the first time. + */ +function getMediaQueryStore(breakpoint: number): MediaQueryStore { + // If the store already exists, return it + if (mediaQueryStores[breakpoint]) return mediaQueryStores[breakpoint]; + + const queryString = `(max-width: ${breakpoint - 0.1}px)`; + const mqList = + typeof window !== 'undefined' ? window.matchMedia(queryString) : ({} as MediaQueryList); + + const store: MediaQueryStore = { + isMatch: typeof window !== 'undefined' ? mqList.matches : false, + mediaQueryList: mqList, + subscribers: new Set(), + }; + + const update = () => { + store.isMatch = mqList.matches; + store.subscribers.forEach((cb) => cb()); + }; + + if (mqList.addEventListener) mqList.addEventListener('change', update); + // For Safari < 14 + else if (mqList.addListener) mqList.addListener(update); + + mediaQueryStores[breakpoint] = store; + return store; +}