mirror of
https://github.com/facebook/docusaurus.git
synced 2025-07-22 19:17:46 +02:00
refactor: move @theme/hooks to @docusaurus/theme-common (#6289)
This commit is contained in:
parent
024f2bf49b
commit
f87a3ead46
36 changed files with 245 additions and 401 deletions
|
@ -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<LayoutProps, 'children'> {}
|
||||
|
||||
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<ThemeContextProps | undefined>;
|
||||
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';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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 (
|
||||
<ThemeProvider>
|
||||
<ColorModeProvider>
|
||||
<AnnouncementBarProvider>
|
||||
<UserPreferencesProvider>
|
||||
<TabGroupChoiceProvider>
|
||||
<ScrollControllerProvider>
|
||||
<DocsPreferredVersionContextProvider>
|
||||
<MobileSecondaryMenuProvider>
|
||||
|
@ -28,8 +28,8 @@ export default function LayoutProviders({children}: Props): JSX.Element {
|
|||
</MobileSecondaryMenuProvider>
|
||||
</DocsPreferredVersionContextProvider>
|
||||
</ScrollControllerProvider>
|
||||
</UserPreferencesProvider>
|
||||
</TabGroupChoiceProvider>
|
||||
</AnnouncementBarProvider>
|
||||
</ThemeProvider>
|
||||
</ColorModeProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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(
|
||||
<ScrollControllerProvider>
|
||||
<UserPreferencesProvider>
|
||||
<TabGroupChoiceProvider>
|
||||
<Tabs>
|
||||
<TabItem value="v1">Tab 1</TabItem>
|
||||
<TabItem value="v2">Tab 2</TabItem>
|
||||
|
@ -102,7 +104,7 @@ describe('Tabs', () => {
|
|||
Tab 2
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
</UserPreferencesProvider>
|
||||
</TabGroupChoiceProvider>
|
||||
</ScrollControllerProvider>,
|
||||
);
|
||||
}).not.toThrow(); // TODO Better Jest infrastructure to mock the Layout
|
||||
|
@ -113,7 +115,7 @@ describe('Tabs', () => {
|
|||
const tabs = ['Apple', 'Banana', 'Carrot'];
|
||||
renderer.create(
|
||||
<ScrollControllerProvider>
|
||||
<UserPreferencesProvider>
|
||||
<TabGroupChoiceProvider>
|
||||
<Tabs
|
||||
values={tabs.map((t, idx) => ({label: t, value: idx}))}
|
||||
defaultValue={0}>
|
||||
|
@ -121,7 +123,7 @@ describe('Tabs', () => {
|
|||
<TabItem value={idx}>{t}</TabItem>
|
||||
))}
|
||||
</Tabs>
|
||||
</UserPreferencesProvider>
|
||||
</TabGroupChoiceProvider>
|
||||
</ScrollControllerProvider>,
|
||||
);
|
||||
}).not.toThrow();
|
||||
|
|
|
@ -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} =
|
||||
|
|
|
@ -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<ThemeContextProps | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
export default ThemeContext;
|
|
@ -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 (
|
||||
<ThemeContext.Provider value={contextValue}>
|
||||
{props.children}
|
||||
</ThemeContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export default ThemeProvider;
|
|
@ -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'];
|
||||
|
|
|
@ -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 (
|
||||
<UserPreferencesContext.Provider value={contextValue}>
|
||||
{props.children}
|
||||
</UserPreferencesContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export default UserPreferencesProvider;
|
|
@ -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<string, string> = {};
|
||||
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;
|
|
@ -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<ThemeContextProps | undefined>(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;
|
|
@ -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<UserPreferencesContextProps | undefined>(
|
||||
UserPreferencesContext,
|
||||
);
|
||||
if (context == null) {
|
||||
throw new Error(
|
||||
'"useUserPreferencesContext" is used outside of "Layout" component.',
|
||||
);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
export default useUserPreferencesContext;
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
|
@ -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<WindowSize>(() => {
|
||||
if (DevSimulateSSR) {
|
||||
return 'ssr';
|
||||
|
@ -65,5 +66,3 @@ function useWindowSize(): WindowSize {
|
|||
|
||||
return windowSize;
|
||||
}
|
||||
|
||||
export default useWindowSize;
|
|
@ -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';
|
||||
|
|
|
@ -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<ColorModeContextValue | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
export function ColorModeProvider({
|
||||
children,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
}): JSX.Element {
|
||||
const {isDarkTheme, setLightTheme, setDarkTheme} = useColorModeContextValue();
|
||||
const contextValue = useMemo(
|
||||
() => ({isDarkTheme, setLightTheme, setDarkTheme}),
|
||||
[isDarkTheme, setLightTheme, setDarkTheme],
|
||||
);
|
||||
return (
|
||||
<ColorModeContext.Provider value={contextValue}>
|
||||
{children}
|
||||
</ColorModeContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useColorMode(): ColorModeContextValue {
|
||||
const context = useContext<ColorModeContextValue | undefined>(
|
||||
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;
|
||||
}
|
|
@ -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<string, string> = {};
|
||||
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 (
|
||||
<TabGroupChoiceContext.Provider value={contextValue}>
|
||||
{children}
|
||||
</TabGroupChoiceContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useTabGroupChoice(): TabGroupChoiceContextValue {
|
||||
const context = useContext(TabGroupChoiceContext);
|
||||
if (context == null) {
|
||||
throw new Error(
|
||||
'"useUserPreferencesContext" is used outside of "Layout" component.',
|
||||
);
|
||||
}
|
||||
return context;
|
||||
}
|
|
@ -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",
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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';
|
|
@ -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
|
|
@ -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' {
|
||||
|
|
|
@ -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 (
|
||||
<Link to={generateSearchPageLink(state.query)} onClick={onClose}>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -4,5 +4,5 @@
|
|||
"module": "esnext",
|
||||
"jsx": "react-native"
|
||||
},
|
||||
"include": ["src/theme/", "src/*.d.ts"]
|
||||
"include": ["src/theme/", "src/client/", "src/*.d.ts"]
|
||||
}
|
||||
|
|
|
@ -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 <h1>Dark mode is now {isDarkTheme ? 'on' : 'off'}</h1>;
|
||||
};
|
||||
|
@ -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() {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue