diff --git a/packages/docusaurus-theme-classic/src/theme-classic.d.ts b/packages/docusaurus-theme-classic/src/theme-classic.d.ts index 933533dfdca6..553c04d69647 100644 --- a/packages/docusaurus-theme-classic/src/theme-classic.d.ts +++ b/packages/docusaurus-theme-classic/src/theme-classic.d.ts @@ -215,86 +215,6 @@ declare module '@theme/Heading' { export default function Heading(props: Props): JSX.Element; } -declare module '@theme/hooks/useHideableNavbar' { - export type useHideableNavbarReturns = { - readonly navbarRef: (node: HTMLElement | null) => void; - readonly isNavbarVisible: boolean; - }; - - const useHideableNavbar: (hideOnScroll: boolean) => useHideableNavbarReturns; - export default useHideableNavbar; -} - -declare module '@theme/hooks/useLockBodyScroll' { - const useLockBodyScroll: (lock?: boolean) => void; - export default useLockBodyScroll; -} - -declare module '@theme/hooks/usePrismTheme' { - import type defaultTheme from 'prism-react-renderer/themes/palenight'; - - const usePrismTheme: () => typeof defaultTheme; - export default usePrismTheme; -} - -declare module '@theme/hooks/useTabGroupChoice' { - export type useTabGroupChoiceReturns = { - readonly tabGroupChoices: {readonly [groupId: string]: string}; - readonly setTabGroupChoices: (groupId: string, newChoice: string) => void; - }; - - const useTabGroupChoice: () => useTabGroupChoiceReturns; - export default useTabGroupChoice; -} - -declare module '@theme/hooks/useTheme' { - export type useThemeReturns = { - readonly isDarkTheme: boolean; - readonly setLightTheme: () => void; - readonly setDarkTheme: () => void; - }; - - const useTheme: () => useThemeReturns; - export default useTheme; -} - -declare module '@theme/hooks/useThemeContext' { - export type ThemeContextProps = { - isDarkTheme: boolean; - setLightTheme: () => void; - setDarkTheme: () => void; - }; - - export default function useThemeContext(): ThemeContextProps; -} - -declare module '@theme/hooks/useUserPreferencesContext' { - export type UserPreferencesContextProps = { - tabGroupChoices: {readonly [groupId: string]: string}; - setTabGroupChoices: (groupId: string, newChoice: string) => void; - }; - - export default function useUserPreferencesContext(): UserPreferencesContextProps; -} - -declare module '@theme/hooks/useWindowSize' { - export const windowSizes: { - desktop: 'desktop'; - mobile: 'mobile'; - ssr: 'ssr'; - }; - - export type WindowSize = keyof typeof windowSizes; - - export default function useWindowSize(): WindowSize; -} - -declare module '@theme/hooks/useKeyboardNavigation' { - const useKeyboardNavigation: () => void; - - export default useKeyboardNavigation; -} - declare module '@theme/Layout' { import type {ReactNode} from 'react'; @@ -314,8 +234,7 @@ declare module '@theme/Layout' { }; } - const Layout: (props: Props) => JSX.Element; - export default Layout; + export default function Layout(props: Props): JSX.Element; } declare module '@theme/LayoutHead' { @@ -323,8 +242,17 @@ declare module '@theme/LayoutHead' { export interface Props extends Omit {} - const LayoutHead: (props: Props) => JSX.Element; - export default LayoutHead; + export default function LayoutHead(props: Props): JSX.Element; +} + +declare module '@theme/LayoutProviders' { + import type {ReactNode} from 'react'; + + export interface Props { + readonly children: ReactNode; + } + + export default function LayoutProviders(props: Props): JSX.Element; } declare module '@theme/SearchMetadata' { @@ -621,17 +549,6 @@ declare module '@theme/Details' { export default Details; } -declare module '@theme/ThemeProvider' { - import type {ReactNode} from 'react'; - - export interface Props { - readonly children: ReactNode; - } - - const ThemeProvider: (props: Props) => JSX.Element; - export default ThemeProvider; -} - declare module '@theme/TOCItems' { import type {TOCItem} from '@docusaurus/types'; @@ -712,46 +629,6 @@ declare module '@theme/Toggle' { export default Toggle; } -declare module '@theme/UserPreferencesProvider' { - import type {ReactNode} from 'react'; - - export interface Props { - readonly children: ReactNode; - } - - const UserPreferencesProvider: (props: Props) => JSX.Element; - export default UserPreferencesProvider; -} - -declare module '@theme/LayoutProviders' { - import type {ReactNode} from 'react'; - - export interface Props { - readonly children: ReactNode; - } - - const LayoutProviders: (props: Props) => JSX.Element; - export default LayoutProviders; -} - -declare module '@theme/ThemeContext' { - import type {Context} from 'react'; - import type {ThemeContextProps} from '@theme/hooks/useThemeContext'; - - const ThemeContext: Context; - export default ThemeContext; -} - -declare module '@theme/UserPreferencesContext' { - import type {Context} from 'react'; - import type {UserPreferencesContextProps} from '@theme/hooks/useUserPreferencesContext'; - - const UserPreferencesContext: Context< - UserPreferencesContextProps | undefined - >; - export default UserPreferencesContext; -} - declare module '@theme/Logo' { import type {ComponentProps} from 'react'; diff --git a/packages/docusaurus-theme-classic/src/theme/CodeBlock/index.tsx b/packages/docusaurus-theme-classic/src/theme/CodeBlock/index.tsx index de19668d9799..0640022dc7ad 100644 --- a/packages/docusaurus-theme-classic/src/theme/CodeBlock/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/CodeBlock/index.tsx @@ -16,8 +16,8 @@ import { parseLanguage, parseLines, ThemeClassNames, + usePrismTheme, } from '@docusaurus/theme-common'; -import usePrismTheme from '@theme/hooks/usePrismTheme'; import type {Props} from '@theme/CodeBlock'; import styles from './styles.module.css'; diff --git a/packages/docusaurus-theme-classic/src/theme/DocItem/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocItem/index.tsx index f5b1f9ff2364..0df290a46a7d 100644 --- a/packages/docusaurus-theme-classic/src/theme/DocItem/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/DocItem/index.tsx @@ -7,7 +7,6 @@ import React from 'react'; import clsx from 'clsx'; -import useWindowSize from '@theme/hooks/useWindowSize'; import DocPaginator from '@theme/DocPaginator'; import DocVersionBanner from '@theme/DocVersionBanner'; import DocVersionBadge from '@theme/DocVersionBadge'; @@ -18,7 +17,7 @@ import TOC from '@theme/TOC'; import TOCCollapsible from '@theme/TOCCollapsible'; import Heading from '@theme/Heading'; import styles from './styles.module.css'; -import {ThemeClassNames} from '@docusaurus/theme-common'; +import {ThemeClassNames, useWindowSize} from '@docusaurus/theme-common'; export default function DocItem(props: Props): JSX.Element { const {content: DocContent} = props; diff --git a/packages/docusaurus-theme-classic/src/theme/DocSidebar/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocSidebar/index.tsx index e5282350c12d..77f6e0aae312 100644 --- a/packages/docusaurus-theme-classic/src/theme/DocSidebar/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/DocSidebar/index.tsx @@ -14,8 +14,8 @@ import { type MobileSecondaryMenuComponent, ThemeClassNames, useScrollPosition, + useWindowSize, } from '@docusaurus/theme-common'; -import useWindowSize from '@theme/hooks/useWindowSize'; import Logo from '@theme/Logo'; import IconArrow from '@theme/IconArrow'; import {translate} from '@docusaurus/Translate'; diff --git a/packages/docusaurus-theme-classic/src/theme/Layout/index.tsx b/packages/docusaurus-theme-classic/src/theme/Layout/index.tsx index 1f0e9e5e6388..2353118cea8d 100644 --- a/packages/docusaurus-theme-classic/src/theme/Layout/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/Layout/index.tsx @@ -15,8 +15,7 @@ import Footer from '@theme/Footer'; import LayoutProviders from '@theme/LayoutProviders'; import LayoutHead from '@theme/LayoutHead'; import type {Props} from '@theme/Layout'; -import useKeyboardNavigation from '@theme/hooks/useKeyboardNavigation'; -import {ThemeClassNames} from '@docusaurus/theme-common'; +import {ThemeClassNames, useKeyboardNavigation} from '@docusaurus/theme-common'; import ErrorPageContent from '@theme/ErrorPageContent'; import './styles.css'; diff --git a/packages/docusaurus-theme-classic/src/theme/LayoutProviders/index.tsx b/packages/docusaurus-theme-classic/src/theme/LayoutProviders/index.tsx index 34cb7a2f157d..5d0d2bf859d0 100644 --- a/packages/docusaurus-theme-classic/src/theme/LayoutProviders/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/LayoutProviders/index.tsx @@ -6,9 +6,9 @@ */ import React from 'react'; -import ThemeProvider from '@theme/ThemeProvider'; -import UserPreferencesProvider from '@theme/UserPreferencesProvider'; import { + ColorModeProvider, + TabGroupChoiceProvider, AnnouncementBarProvider, DocsPreferredVersionContextProvider, MobileSecondaryMenuProvider, @@ -18,9 +18,9 @@ import type {Props} from '@theme/LayoutProviders'; export default function LayoutProviders({children}: Props): JSX.Element { return ( - + - + @@ -28,8 +28,8 @@ export default function LayoutProviders({children}: Props): JSX.Element { - + - + ); } diff --git a/packages/docusaurus-theme-classic/src/theme/Navbar/index.tsx b/packages/docusaurus-theme-classic/src/theme/Navbar/index.tsx index 19e90b57e2b9..136f08a27f47 100644 --- a/packages/docusaurus-theme-classic/src/theme/Navbar/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/Navbar/index.tsx @@ -10,16 +10,16 @@ import clsx from 'clsx'; import Translate from '@docusaurus/Translate'; import SearchBar from '@theme/SearchBar'; import Toggle from '@theme/Toggle'; -import useThemeContext from '@theme/hooks/useThemeContext'; import { useThemeConfig, useMobileSecondaryMenuRenderer, usePrevious, useHistoryPopHandler, + useHideableNavbar, + useLockBodyScroll, + useWindowSize, + useColorMode, } from '@docusaurus/theme-common'; -import useHideableNavbar from '@theme/hooks/useHideableNavbar'; -import useLockBodyScroll from '@theme/hooks/useLockBodyScroll'; -import useWindowSize from '@theme/hooks/useWindowSize'; import {useActivePlugin} from '@docusaurus/plugin-content-docs/client'; import NavbarItem, {type Props as NavbarItemConfig} from '@theme/NavbarItem'; import Logo from '@theme/Logo'; @@ -88,7 +88,7 @@ function useColorModeToggle() { const { colorMode: {disableSwitch}, } = useThemeConfig(); - const {isDarkTheme, setLightTheme, setDarkTheme} = useThemeContext(); + const {isDarkTheme, setLightTheme, setDarkTheme} = useColorMode(); const toggle = useCallback( (e) => (e.target.checked ? setDarkTheme() : setLightTheme()), [setLightTheme, setDarkTheme], diff --git a/packages/docusaurus-theme-classic/src/theme/Tabs/__tests__/index.test.tsx b/packages/docusaurus-theme-classic/src/theme/Tabs/__tests__/index.test.tsx index 6bcf0e7b211c..c65e4687ea3b 100644 --- a/packages/docusaurus-theme-classic/src/theme/Tabs/__tests__/index.test.tsx +++ b/packages/docusaurus-theme-classic/src/theme/Tabs/__tests__/index.test.tsx @@ -9,8 +9,10 @@ import React from 'react'; import renderer from 'react-test-renderer'; import Tabs from '../index'; import TabItem from '../../TabItem'; -import UserPreferencesProvider from '@theme/UserPreferencesProvider'; -import {ScrollControllerProvider} from '@docusaurus/theme-common'; +import { + TabGroupChoiceProvider, + ScrollControllerProvider, +} from '@docusaurus/theme-common'; describe('Tabs', () => { test('Should reject bad Tabs child', () => { @@ -57,7 +59,7 @@ describe('Tabs', () => { expect(() => { renderer.create( - + Tab 1 Tab 2 @@ -102,7 +104,7 @@ describe('Tabs', () => { Tab 2 - + , ); }).not.toThrow(); // TODO Better Jest infrastructure to mock the Layout @@ -113,7 +115,7 @@ describe('Tabs', () => { const tabs = ['Apple', 'Banana', 'Carrot']; renderer.create( - + ({label: t, value: idx}))} defaultValue={0}> @@ -121,7 +123,7 @@ describe('Tabs', () => { {t} ))} - + , ); }).not.toThrow(); diff --git a/packages/docusaurus-theme-classic/src/theme/Tabs/index.tsx b/packages/docusaurus-theme-classic/src/theme/Tabs/index.tsx index 144aa5e3966c..9fe416477d25 100644 --- a/packages/docusaurus-theme-classic/src/theme/Tabs/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/Tabs/index.tsx @@ -13,8 +13,11 @@ import React, { type ReactElement, } from 'react'; import useIsBrowser from '@docusaurus/useIsBrowser'; -import useUserPreferencesContext from '@theme/hooks/useUserPreferencesContext'; -import {useScrollPositionBlocker, duplicates} from '@docusaurus/theme-common'; +import { + useScrollPositionBlocker, + duplicates, + useTabGroupChoice, +} from '@docusaurus/theme-common'; import type {Props} from '@theme/Tabs'; import type {Props as TabItemProps} from '@theme/TabItem'; @@ -83,7 +86,7 @@ function TabsComponent(props: Props): JSX.Element { ); } - const {tabGroupChoices, setTabGroupChoices} = useUserPreferencesContext(); + const {tabGroupChoices, setTabGroupChoices} = useTabGroupChoice(); const [selectedValue, setSelectedValue] = useState(defaultValue); const tabRefs: (HTMLLIElement | null)[] = []; const {blockElementScrollPositionUntilNextRender} = diff --git a/packages/docusaurus-theme-classic/src/theme/ThemeContext.ts b/packages/docusaurus-theme-classic/src/theme/ThemeContext.ts deleted file mode 100644 index f5e0f05bc165..000000000000 --- a/packages/docusaurus-theme-classic/src/theme/ThemeContext.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import React from 'react'; -import type {ThemeContextProps} from '@theme/hooks/useThemeContext'; - -const ThemeContext = React.createContext( - undefined, -); - -export default ThemeContext; diff --git a/packages/docusaurus-theme-classic/src/theme/ThemeProvider/index.tsx b/packages/docusaurus-theme-classic/src/theme/ThemeProvider/index.tsx deleted file mode 100644 index ad5f6e81bc35..000000000000 --- a/packages/docusaurus-theme-classic/src/theme/ThemeProvider/index.tsx +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import React, {useMemo} from 'react'; - -import useTheme from '@theme/hooks/useTheme'; -import ThemeContext from '@theme/ThemeContext'; -import type {Props} from '@theme/ThemeProvider'; - -function ThemeProvider(props: Props): JSX.Element { - const {isDarkTheme, setLightTheme, setDarkTheme} = useTheme(); - const contextValue = useMemo( - () => ({isDarkTheme, setLightTheme, setDarkTheme}), - [isDarkTheme, setLightTheme, setDarkTheme], - ); - - return ( - - {props.children} - - ); -} - -export default ThemeProvider; diff --git a/packages/docusaurus-theme-classic/src/theme/ThemedImage/index.tsx b/packages/docusaurus-theme-classic/src/theme/ThemedImage/index.tsx index 4e16a12893b0..179b4f0e3706 100644 --- a/packages/docusaurus-theme-classic/src/theme/ThemedImage/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/ThemedImage/index.tsx @@ -9,14 +9,14 @@ import React from 'react'; import clsx from 'clsx'; import useIsBrowser from '@docusaurus/useIsBrowser'; -import useThemeContext from '@theme/hooks/useThemeContext'; +import {useColorMode} from '@docusaurus/theme-common'; import type {Props} from '@theme/ThemedImage'; import styles from './styles.module.css'; function ThemedImage(props: Props): JSX.Element { const isBrowser = useIsBrowser(); - const {isDarkTheme} = useThemeContext(); + const {isDarkTheme} = useColorMode(); const {sources, className, alt = '', ...propsRest} = props; type SourceName = keyof Props['sources']; diff --git a/packages/docusaurus-theme-classic/src/theme/UserPreferencesProvider/index.tsx b/packages/docusaurus-theme-classic/src/theme/UserPreferencesProvider/index.tsx deleted file mode 100644 index 3c9f2556efd9..000000000000 --- a/packages/docusaurus-theme-classic/src/theme/UserPreferencesProvider/index.tsx +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import React, {useMemo} from 'react'; - -import useTabGroupChoice from '@theme/hooks/useTabGroupChoice'; -import UserPreferencesContext from '@theme/UserPreferencesContext'; -import type {Props} from '@theme/UserPreferencesProvider'; - -function UserPreferencesProvider(props: Props): JSX.Element { - const {tabGroupChoices, setTabGroupChoices} = useTabGroupChoice(); - const contextValue = useMemo( - () => ({ - tabGroupChoices, - setTabGroupChoices, - }), - [tabGroupChoices, setTabGroupChoices], - ); - return ( - - {props.children} - - ); -} - -export default UserPreferencesProvider; diff --git a/packages/docusaurus-theme-classic/src/theme/hooks/useTabGroupChoice.ts b/packages/docusaurus-theme-classic/src/theme/hooks/useTabGroupChoice.ts deleted file mode 100644 index b1014cc3f62d..000000000000 --- a/packages/docusaurus-theme-classic/src/theme/hooks/useTabGroupChoice.ts +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import {useState, useCallback, useEffect} from 'react'; -import type {useTabGroupChoiceReturns} from '@theme/hooks/useTabGroupChoice'; -import {createStorageSlot, listStorageKeys} from '@docusaurus/theme-common'; - -const TAB_CHOICE_PREFIX = 'docusaurus.tab.'; - -const useTabGroupChoice = (): useTabGroupChoiceReturns => { - const [tabGroupChoices, setChoices] = useState<{ - readonly [groupId: string]: string; - }>({}); - const setChoiceSyncWithLocalStorage = useCallback((groupId, newChoice) => { - createStorageSlot(`${TAB_CHOICE_PREFIX}${groupId}`).set(newChoice); - }, []); - - useEffect(() => { - try { - const localStorageChoices: Record = {}; - listStorageKeys().forEach((storageKey) => { - if (storageKey.startsWith(TAB_CHOICE_PREFIX)) { - const groupId = storageKey.substring(TAB_CHOICE_PREFIX.length); - localStorageChoices[groupId] = createStorageSlot(storageKey).get()!; - } - }); - setChoices(localStorageChoices); - } catch (err) { - console.error(err); - } - }, []); - - return { - tabGroupChoices, - setTabGroupChoices: (groupId: string, newChoice: string) => { - setChoices((oldChoices) => ({...oldChoices, [groupId]: newChoice})); - setChoiceSyncWithLocalStorage(groupId, newChoice); - }, - }; -}; - -export default useTabGroupChoice; diff --git a/packages/docusaurus-theme-classic/src/theme/hooks/useThemeContext.ts b/packages/docusaurus-theme-classic/src/theme/hooks/useThemeContext.ts deleted file mode 100644 index 737d310d3b6d..000000000000 --- a/packages/docusaurus-theme-classic/src/theme/hooks/useThemeContext.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import {useContext} from 'react'; - -import ThemeContext from '@theme/ThemeContext'; -import type {ThemeContextProps} from '@theme/hooks/useThemeContext'; - -function useThemeContext(): ThemeContextProps { - const context = useContext(ThemeContext); - if (context == null) { - throw new Error( - '"useThemeContext" is used outside of "Layout" component. Please see https://docusaurus.io/docs/api/themes/configuration#usethemecontext.', - ); - } - return context; -} - -export default useThemeContext; diff --git a/packages/docusaurus-theme-classic/src/theme/hooks/useUserPreferencesContext.ts b/packages/docusaurus-theme-classic/src/theme/hooks/useUserPreferencesContext.ts deleted file mode 100644 index 91b8c05efa39..000000000000 --- a/packages/docusaurus-theme-classic/src/theme/hooks/useUserPreferencesContext.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import {useContext} from 'react'; - -import UserPreferencesContext from '@theme/UserPreferencesContext'; -import type {UserPreferencesContextProps} from '@theme/hooks/useUserPreferencesContext'; - -function useUserPreferencesContext(): UserPreferencesContextProps { - const context = useContext( - UserPreferencesContext, - ); - if (context == null) { - throw new Error( - '"useUserPreferencesContext" is used outside of "Layout" component.', - ); - } - return context; -} - -export default useUserPreferencesContext; diff --git a/packages/docusaurus-theme-classic/src/theme/hooks/styles.css b/packages/docusaurus-theme-common/src/hooks/styles.css similarity index 100% rename from packages/docusaurus-theme-classic/src/theme/hooks/styles.css rename to packages/docusaurus-theme-common/src/hooks/styles.css diff --git a/packages/docusaurus-theme-classic/src/theme/hooks/useHideableNavbar.ts b/packages/docusaurus-theme-common/src/hooks/useHideableNavbar.ts similarity index 82% rename from packages/docusaurus-theme-classic/src/theme/hooks/useHideableNavbar.ts rename to packages/docusaurus-theme-common/src/hooks/useHideableNavbar.ts index 40a9da1ae93d..f83486f2c1b6 100644 --- a/packages/docusaurus-theme-classic/src/theme/hooks/useHideableNavbar.ts +++ b/packages/docusaurus-theme-common/src/hooks/useHideableNavbar.ts @@ -6,10 +6,17 @@ */ import {useState, useCallback, useRef} from 'react'; -import {useLocationChange, useScrollPosition} from '@docusaurus/theme-common'; -import type {useHideableNavbarReturns} from '@theme/hooks/useHideableNavbar'; +import {useLocationChange} from '../utils/useLocationChange'; +import {useScrollPosition} from '../utils/scrollUtils'; -const useHideableNavbar = (hideOnScroll: boolean): useHideableNavbarReturns => { +type UseHideableNavbarReturns = { + readonly navbarRef: (node: HTMLElement | null) => void; + readonly isNavbarVisible: boolean; +}; + +export default function useHideableNavbar( + hideOnScroll: boolean, +): UseHideableNavbarReturns { const [isNavbarVisible, setIsNavbarVisible] = useState(hideOnScroll); const isFocusedAnchor = useRef(false); const navbarHeight = useRef(0); @@ -67,6 +74,4 @@ const useHideableNavbar = (hideOnScroll: boolean): useHideableNavbarReturns => { navbarRef, isNavbarVisible, }; -}; - -export default useHideableNavbar; +} diff --git a/packages/docusaurus-theme-classic/src/theme/hooks/useKeyboardNavigation.ts b/packages/docusaurus-theme-common/src/hooks/useKeyboardNavigation.ts similarity index 93% rename from packages/docusaurus-theme-classic/src/theme/hooks/useKeyboardNavigation.ts rename to packages/docusaurus-theme-common/src/hooks/useKeyboardNavigation.ts index acc5545622aa..80fe791f107d 100644 --- a/packages/docusaurus-theme-classic/src/theme/hooks/useKeyboardNavigation.ts +++ b/packages/docusaurus-theme-common/src/hooks/useKeyboardNavigation.ts @@ -11,7 +11,7 @@ import './styles.css'; // This hook detect keyboard focus indicator to not show outline for mouse users // Inspired by https://hackernoon.com/removing-that-ugly-focus-ring-and-keeping-it-too-6c8727fefcd2 -function useKeyboardNavigation(): void { +export default function useKeyboardNavigation(): void { useEffect(() => { const keyboardFocusedClassName = 'navigation-with-keyboard'; @@ -35,5 +35,3 @@ function useKeyboardNavigation(): void { }; }, []); } - -export default useKeyboardNavigation; diff --git a/packages/docusaurus-theme-classic/src/theme/hooks/useLockBodyScroll.ts b/packages/docusaurus-theme-common/src/hooks/useLockBodyScroll.ts similarity index 81% rename from packages/docusaurus-theme-classic/src/theme/hooks/useLockBodyScroll.ts rename to packages/docusaurus-theme-common/src/hooks/useLockBodyScroll.ts index 4fe49ecd45d4..2ac498d69e5f 100644 --- a/packages/docusaurus-theme-classic/src/theme/hooks/useLockBodyScroll.ts +++ b/packages/docusaurus-theme-common/src/hooks/useLockBodyScroll.ts @@ -7,7 +7,7 @@ import {useEffect} from 'react'; -function useLockBodyScroll(lock: boolean = true): void { +export default function useLockBodyScroll(lock: boolean = true): void { useEffect(() => { document.body.style.overflow = lock ? 'hidden' : 'visible'; @@ -16,5 +16,3 @@ function useLockBodyScroll(lock: boolean = true): void { }; }, [lock]); } - -export default useLockBodyScroll; diff --git a/packages/docusaurus-theme-classic/src/theme/hooks/usePrismTheme.ts b/packages/docusaurus-theme-common/src/hooks/usePrismTheme.ts similarity index 66% rename from packages/docusaurus-theme-classic/src/theme/hooks/usePrismTheme.ts rename to packages/docusaurus-theme-common/src/hooks/usePrismTheme.ts index 36d80a05387f..88b7c1298149 100644 --- a/packages/docusaurus-theme-classic/src/theme/hooks/usePrismTheme.ts +++ b/packages/docusaurus-theme-common/src/hooks/usePrismTheme.ts @@ -6,17 +6,15 @@ */ import defaultTheme from 'prism-react-renderer/themes/palenight'; -import useThemeContext from '@theme/hooks/useThemeContext'; -import {useThemeConfig} from '@docusaurus/theme-common'; +import {useColorMode} from '../utils/colorModeUtils'; +import {useThemeConfig} from '../utils/useThemeConfig'; -const usePrismTheme = (): typeof defaultTheme => { +export default function usePrismTheme(): typeof defaultTheme { const {prism} = useThemeConfig(); - const {isDarkTheme} = useThemeContext(); + const {isDarkTheme} = useColorMode(); const lightModeTheme = prism.theme || defaultTheme; const darkModeTheme = prism.darkTheme || lightModeTheme; const prismTheme = isDarkTheme ? darkModeTheme : lightModeTheme; return prismTheme; -}; - -export default usePrismTheme; +} diff --git a/packages/docusaurus-theme-search-algolia/src/theme/hooks/useSearchQuery.ts b/packages/docusaurus-theme-common/src/hooks/useSearchPage.ts similarity index 86% rename from packages/docusaurus-theme-search-algolia/src/theme/hooks/useSearchQuery.ts rename to packages/docusaurus-theme-common/src/hooks/useSearchPage.ts index 435bf349d507..62271f89a04f 100644 --- a/packages/docusaurus-theme-search-algolia/src/theme/hooks/useSearchQuery.ts +++ b/packages/docusaurus-theme-common/src/hooks/useSearchPage.ts @@ -8,11 +8,16 @@ import {useHistory} from '@docusaurus/router'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import {useCallback, useEffect, useState} from 'react'; -import type {SearchQuery} from '@theme/hooks/useSearchQuery'; const SEARCH_PARAM_QUERY = 'q'; -function useSearchQuery(): SearchQuery { +interface UseSearchPageReturn { + searchQuery: string; + setSearchQuery: (newSearchQuery: string) => void; + generateSearchPageLink: (targetSearchQuery: string) => string; +} + +export default function useSearchPage(): UseSearchPageReturn { const history = useHistory(); const { siteConfig: {baseUrl}, @@ -59,5 +64,3 @@ function useSearchQuery(): SearchQuery { generateSearchPageLink, }; } - -export default useSearchQuery; diff --git a/packages/docusaurus-theme-classic/src/theme/hooks/useWindowSize.ts b/packages/docusaurus-theme-common/src/hooks/useWindowSize.ts similarity index 93% rename from packages/docusaurus-theme-classic/src/theme/hooks/useWindowSize.ts rename to packages/docusaurus-theme-common/src/hooks/useWindowSize.ts index 84e60b168b58..0b6e944392a1 100644 --- a/packages/docusaurus-theme-classic/src/theme/hooks/useWindowSize.ts +++ b/packages/docusaurus-theme-common/src/hooks/useWindowSize.ts @@ -8,7 +8,6 @@ import {useEffect, useState} from 'react'; import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment'; -import type {WindowSize} from '@theme/hooks/useWindowSize'; const windowSizes = { desktop: 'desktop', @@ -21,6 +20,8 @@ const windowSizes = { ssr: 'ssr', } as const; +type WindowSize = keyof typeof windowSizes; + const DesktopThresholdWidth = 996; function getWindowSize() { @@ -38,7 +39,7 @@ const DevSimulateSSR = process.env.NODE_ENV === 'development' && true; // This hook returns an enum value on purpose! // We don't want it to return the actual width value, for resize perf reasons // We only want to re-render once a breakpoint is crossed -function useWindowSize(): WindowSize { +export default function useWindowSize(): WindowSize { const [windowSize, setWindowSize] = useState(() => { if (DevSimulateSSR) { return 'ssr'; @@ -65,5 +66,3 @@ function useWindowSize(): WindowSize { return windowSize; } - -export default useWindowSize; diff --git a/packages/docusaurus-theme-common/src/index.ts b/packages/docusaurus-theme-common/src/index.ts index 06d80707c351..5c622ec99969 100644 --- a/packages/docusaurus-theme-common/src/index.ts +++ b/packages/docusaurus-theme-common/src/index.ts @@ -114,3 +114,16 @@ export { } from './utils/reactUtils'; export {isRegexpStringMatch} from './utils/regexpUtils'; + +export {useColorMode, ColorModeProvider} from './utils/colorModeUtils'; +export { + useTabGroupChoice, + TabGroupChoiceProvider, +} from './utils/tabGroupChoiceUtils'; + +export {default as useHideableNavbar} from './hooks/useHideableNavbar'; +export {default as useKeyboardNavigation} from './hooks/useKeyboardNavigation'; +export {default as usePrismTheme} from './hooks/usePrismTheme'; +export {default as useLockBodyScroll} from './hooks/useLockBodyScroll'; +export {default as useWindowSize} from './hooks/useWindowSize'; +export {default as useSearchPage} from './hooks/useSearchPage'; diff --git a/packages/docusaurus-theme-classic/src/theme/hooks/useTheme.ts b/packages/docusaurus-theme-common/src/utils/colorModeUtils.tsx similarity index 61% rename from packages/docusaurus-theme-classic/src/theme/hooks/useTheme.ts rename to packages/docusaurus-theme-common/src/utils/colorModeUtils.tsx index 3a3437ccff2f..ee0ebba24938 100644 --- a/packages/docusaurus-theme-classic/src/theme/hooks/useTheme.ts +++ b/packages/docusaurus-theme-common/src/utils/colorModeUtils.tsx @@ -5,11 +5,24 @@ * LICENSE file in the root directory of this source tree. */ -import {useState, useCallback, useEffect} from 'react'; +import type {ReactNode} from 'react'; +import React, { + useState, + useCallback, + useEffect, + useContext, + useMemo, +} from 'react'; import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment'; -import type {useThemeReturns} from '@theme/hooks/useTheme'; -import {useThemeConfig, createStorageSlot} from '@docusaurus/theme-common'; +import {createStorageSlot} from './storageUtils'; +import {useThemeConfig} from './useThemeConfig'; + +type ColorModeContextValue = { + readonly isDarkTheme: boolean; + readonly setLightTheme: () => void; + readonly setDarkTheme: () => void; +}; const ThemeStorage = createStorageSlot('theme'); @@ -35,7 +48,7 @@ const storeTheme = (newTheme: Themes) => { createStorageSlot('theme').set(coerceToTheme(newTheme)); }; -const useTheme = (): useThemeReturns => { +function useColorModeContextValue(): ColorModeContextValue { const { colorMode: {defaultMode, disableSwitch, respectPrefersColorScheme}, } = useThemeConfig(); @@ -86,6 +99,37 @@ const useTheme = (): useThemeReturns => { setLightTheme, setDarkTheme, }; -}; - -export default useTheme; +} + +const ColorModeContext = React.createContext( + undefined, +); + +export function ColorModeProvider({ + children, +}: { + children: ReactNode; +}): JSX.Element { + const {isDarkTheme, setLightTheme, setDarkTheme} = useColorModeContextValue(); + const contextValue = useMemo( + () => ({isDarkTheme, setLightTheme, setDarkTheme}), + [isDarkTheme, setLightTheme, setDarkTheme], + ); + return ( + + {children} + + ); +} + +export function useColorMode(): ColorModeContextValue { + const context = useContext( + ColorModeContext, + ); + if (context == null) { + throw new Error( + '"useColorMode()" is used outside of "Layout" component. Please see https://docusaurus.io/docs/api/themes/configuration#use-color-mode.', + ); + } + return context; +} diff --git a/packages/docusaurus-theme-common/src/utils/tabGroupChoiceUtils.tsx b/packages/docusaurus-theme-common/src/utils/tabGroupChoiceUtils.tsx new file mode 100644 index 000000000000..e11df2de181c --- /dev/null +++ b/packages/docusaurus-theme-common/src/utils/tabGroupChoiceUtils.tsx @@ -0,0 +1,90 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React, { + useState, + useCallback, + useEffect, + createContext, + useMemo, + useContext, + type ReactNode, +} from 'react'; +import {createStorageSlot, listStorageKeys} from './storageUtils'; + +const TAB_CHOICE_PREFIX = 'docusaurus.tab.'; + +type TabGroupChoiceContextValue = { + readonly tabGroupChoices: {readonly [groupId: string]: string}; + readonly setTabGroupChoices: (groupId: string, newChoice: string) => void; +}; + +const TabGroupChoiceContext = createContext< + TabGroupChoiceContextValue | undefined +>(undefined); + +function useTabGroupChoiceContextValue(): TabGroupChoiceContextValue { + const [tabGroupChoices, setChoices] = useState<{ + readonly [groupId: string]: string; + }>({}); + const setChoiceSyncWithLocalStorage = useCallback((groupId, newChoice) => { + createStorageSlot(`${TAB_CHOICE_PREFIX}${groupId}`).set(newChoice); + }, []); + + useEffect(() => { + try { + const localStorageChoices: Record = {}; + listStorageKeys().forEach((storageKey) => { + if (storageKey.startsWith(TAB_CHOICE_PREFIX)) { + const groupId = storageKey.substring(TAB_CHOICE_PREFIX.length); + localStorageChoices[groupId] = createStorageSlot(storageKey).get()!; + } + }); + setChoices(localStorageChoices); + } catch (err) { + console.error(err); + } + }, []); + + return { + tabGroupChoices, + setTabGroupChoices: (groupId: string, newChoice: string) => { + setChoices((oldChoices) => ({...oldChoices, [groupId]: newChoice})); + setChoiceSyncWithLocalStorage(groupId, newChoice); + }, + }; +} + +export function TabGroupChoiceProvider({ + children, +}: { + children: ReactNode; +}): JSX.Element { + const {tabGroupChoices, setTabGroupChoices} = useTabGroupChoiceContextValue(); + const contextValue = useMemo( + () => ({ + tabGroupChoices, + setTabGroupChoices, + }), + [tabGroupChoices, setTabGroupChoices], + ); + return ( + + {children} + + ); +} + +export function useTabGroupChoice(): TabGroupChoiceContextValue { + const context = useContext(TabGroupChoiceContext); + if (context == null) { + throw new Error( + '"useUserPreferencesContext" is used outside of "Layout" component.', + ); + } + return context; +} diff --git a/packages/docusaurus-theme-live-codeblock/package.json b/packages/docusaurus-theme-live-codeblock/package.json index d85634bb5924..204d4645d3e9 100644 --- a/packages/docusaurus-theme-live-codeblock/package.json +++ b/packages/docusaurus-theme-live-codeblock/package.json @@ -18,6 +18,7 @@ "license": "MIT", "dependencies": { "@docusaurus/core": "2.0.0-beta.14", + "@docusaurus/theme-common": "2.0.0-beta.14", "@docusaurus/theme-translations": "2.0.0-beta.14", "@docusaurus/utils": "2.0.0-beta.14", "@docusaurus/utils-validation": "2.0.0-beta.14", diff --git a/packages/docusaurus-theme-live-codeblock/src/theme/Playground/index.js b/packages/docusaurus-theme-live-codeblock/src/theme/Playground/index.js index 4826a8868d64..04e8bcdecdb9 100644 --- a/packages/docusaurus-theme-live-codeblock/src/theme/Playground/index.js +++ b/packages/docusaurus-theme-live-codeblock/src/theme/Playground/index.js @@ -11,7 +11,7 @@ import clsx from 'clsx'; import Translate from '@docusaurus/Translate'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import BrowserOnly from '@docusaurus/BrowserOnly'; -import usePrismTheme from '@theme/hooks/usePrismTheme'; +import {usePrismTheme} from '@docusaurus/theme-common'; import styles from './styles.module.css'; import useIsBrowser from '@docusaurus/useIsBrowser'; diff --git a/packages/docusaurus-theme-search-algolia/package.json b/packages/docusaurus-theme-search-algolia/package.json index cda043d49a7d..9838fa006985 100644 --- a/packages/docusaurus-theme-search-algolia/package.json +++ b/packages/docusaurus-theme-search-algolia/package.json @@ -3,6 +3,10 @@ "version": "2.0.0-beta.14", "description": "Algolia search component for Docusaurus.", "main": "lib/index.js", + "exports": { + "./client": "./lib/client/index.js", + ".": "./lib/index.js" + }, "types": "src/theme-search-algolia.d.ts", "publishConfig": { "access": "public" diff --git a/packages/docusaurus-theme-classic/src/theme/UserPreferencesContext.ts b/packages/docusaurus-theme-search-algolia/src/client/index.ts similarity index 58% rename from packages/docusaurus-theme-classic/src/theme/UserPreferencesContext.ts rename to packages/docusaurus-theme-search-algolia/src/client/index.ts index c9c8252f5dc3..a2b338bf27f4 100644 --- a/packages/docusaurus-theme-classic/src/theme/UserPreferencesContext.ts +++ b/packages/docusaurus-theme-search-algolia/src/client/index.ts @@ -5,8 +5,4 @@ * LICENSE file in the root directory of this source tree. */ -import {createContext} from 'react'; - -const UserPreferencesContext = createContext(undefined); - -export default UserPreferencesContext; +export {useAlgoliaContextualFacetFilters} from './useAlgoliaContextualFacetFilters'; diff --git a/packages/docusaurus-theme-search-algolia/src/theme/hooks/useAlgoliaContextualFacetFilters.ts b/packages/docusaurus-theme-search-algolia/src/client/useAlgoliaContextualFacetFilters.ts similarity index 74% rename from packages/docusaurus-theme-search-algolia/src/theme/hooks/useAlgoliaContextualFacetFilters.ts rename to packages/docusaurus-theme-search-algolia/src/client/useAlgoliaContextualFacetFilters.ts index a1ca093d0ab9..10a13cf8c123 100644 --- a/packages/docusaurus-theme-search-algolia/src/theme/hooks/useAlgoliaContextualFacetFilters.ts +++ b/packages/docusaurus-theme-search-algolia/src/client/useAlgoliaContextualFacetFilters.ts @@ -5,11 +5,10 @@ * LICENSE file in the root directory of this source tree. */ -import type {useAlgoliaContextualFacetFiltersReturns} from '@theme/hooks/useAlgoliaContextualFacetFilters'; import {useContextualSearchFilters} from '@docusaurus/theme-common'; // Translate search-engine agnostic search filters to Algolia search filters -export default function useAlgoliaContextualFacetFilters(): useAlgoliaContextualFacetFiltersReturns { +export function useAlgoliaContextualFacetFilters() { const {locale, tags} = useContextualSearchFilters(); // seems safe to convert locale->language, see AlgoliaSearchMetadata comment diff --git a/packages/docusaurus-theme-search-algolia/src/theme-search-algolia.d.ts b/packages/docusaurus-theme-search-algolia/src/theme-search-algolia.d.ts index 005a6946706e..df7e0cb45d2f 100644 --- a/packages/docusaurus-theme-search-algolia/src/theme-search-algolia.d.ts +++ b/packages/docusaurus-theme-search-algolia/src/theme-search-algolia.d.ts @@ -5,24 +5,8 @@ * LICENSE file in the root directory of this source tree. */ -declare module '@docusaurus/theme-search-algolia' { - export type Options = never; -} - -declare module '@theme/hooks/useSearchQuery' { - export interface SearchQuery { - searchQuery: string; - setSearchQuery: (newSearchQuery: string) => void; - generateSearchPageLink: (targetSearchQuery: string) => string; - } - - export default function useSearchQuery(): SearchQuery; -} - -declare module '@theme/hooks/useAlgoliaContextualFacetFilters' { - export type useAlgoliaContextualFacetFiltersReturns = [string, string[]]; - - export default function useAlgoliaContextualFacetFilters(): useAlgoliaContextualFacetFiltersReturns; +declare module '@docusaurus/theme-search-algolia/client' { + export function useAlgoliaContextualFacetFilters(): [string, string[]]; } declare module '@theme/SearchPage' { diff --git a/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.tsx b/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.tsx index f2ed7b9a3653..8c1ed4b86d97 100644 --- a/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.tsx +++ b/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.tsx @@ -13,10 +13,9 @@ import {useHistory} from '@docusaurus/router'; import {useBaseUrlUtils} from '@docusaurus/useBaseUrl'; import Link from '@docusaurus/Link'; import Head from '@docusaurus/Head'; -import useSearchQuery from '@theme/hooks/useSearchQuery'; -import {isRegexpStringMatch} from '@docusaurus/theme-common'; +import {isRegexpStringMatch, useSearchPage} from '@docusaurus/theme-common'; import {DocSearchButton, useDocSearchKeyboardEvents} from '@docsearch/react'; -import useAlgoliaContextualFacetFilters from '@theme/hooks/useAlgoliaContextualFacetFilters'; +import {useAlgoliaContextualFacetFilters} from '@docusaurus/theme-search-algolia/client'; import {translate} from '@docusaurus/Translate'; import styles from './styles.module.css'; @@ -56,7 +55,7 @@ type ResultsFooterProps = { }; function ResultsFooter({state, onClose}: ResultsFooterProps) { - const {generateSearchPageLink} = useSearchQuery(); + const {generateSearchPageLink} = useSearchPage(); return ( diff --git a/packages/docusaurus-theme-search-algolia/src/theme/SearchPage/index.tsx b/packages/docusaurus-theme-search-algolia/src/theme/SearchPage/index.tsx index cc983b265fd1..50be44a8b21d 100644 --- a/packages/docusaurus-theme-search-algolia/src/theme/SearchPage/index.tsx +++ b/packages/docusaurus-theme-search-algolia/src/theme/SearchPage/index.tsx @@ -22,10 +22,10 @@ import { usePluralForm, isRegexpStringMatch, useDynamicCallback, + useSearchPage, } from '@docusaurus/theme-common'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import {useAllDocsData} from '@docusaurus/plugin-content-docs/client'; -import useSearchQuery from '@theme/hooks/useSearchQuery'; import Layout from '@theme/Layout'; import Translate, {translate} from '@docusaurus/Translate'; import styles from './styles.module.css'; @@ -162,7 +162,7 @@ function SearchPage(): JSX.Element { const documentsFoundPlural = useDocumentsFoundPlural(); const docsSearchVersionsHelpers = useDocsSearchVersionsHelpers(); - const {searchQuery, setSearchQuery} = useSearchQuery(); + const {searchQuery, setSearchQuery} = useSearchPage(); const initialSearchResultState: ResultDispatcherState = { items: [], query: null, diff --git a/packages/docusaurus-theme-search-algolia/tsconfig.browser.json b/packages/docusaurus-theme-search-algolia/tsconfig.browser.json index 3af8257cdf8d..0ea16454ec40 100644 --- a/packages/docusaurus-theme-search-algolia/tsconfig.browser.json +++ b/packages/docusaurus-theme-search-algolia/tsconfig.browser.json @@ -4,5 +4,5 @@ "module": "esnext", "jsx": "react-native" }, - "include": ["src/theme/", "src/*.d.ts"] + "include": ["src/theme/", "src/client/", "src/*.d.ts"] } diff --git a/website/docs/api/themes/theme-configuration.md b/website/docs/api/themes/theme-configuration.md index 2a667b872b21..16da0cf3612e 100644 --- a/website/docs/api/themes/theme-configuration.md +++ b/website/docs/api/themes/theme-configuration.md @@ -905,20 +905,20 @@ module.exports = { ## Hooks {#hooks} -### `useThemeContext` {#usethemecontext} +### `useColorMode` {#use-color-mode} -React hook to access theme context. This context contains functions for setting light and dark mode and exposes boolean variable, indicating which mode is currently in use. +A React hook to access the color context. This context contains functions for setting light and dark mode and exposes boolean variable, indicating which mode is currently in use. Usage example: ```jsx import React from 'react'; // highlight-next-line -import useThemeContext from '@theme/hooks/useThemeContext'; +import {useColorMode} from '@docusaurus/theme-common'; const Example = () => { // highlight-next-line - const {isDarkTheme, setLightTheme, setDarkTheme} = useThemeContext(); + const {isDarkTheme, setLightTheme, setDarkTheme} = useColorMode(); return

Dark mode is now {isDarkTheme ? 'on' : 'off'}

; }; @@ -926,7 +926,7 @@ const Example = () => { :::note -The component calling `useThemeContext` must be a child of the `Layout` component. +The component calling `useColorMode` must be a child of the `Layout` component. ```jsx function ExamplePage() {