refactor: move @theme/hooks to @docusaurus/theme-common (#6289)

This commit is contained in:
Sébastien Lorber 2022-01-07 19:19:35 +01:00 committed by GitHub
parent 024f2bf49b
commit f87a3ead46
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 245 additions and 401 deletions

View file

@ -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';

View file

@ -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';

View file

@ -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;

View file

@ -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';

View file

@ -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';

View file

@ -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>
);
}

View file

@ -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],

View file

@ -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();

View file

@ -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} =

View file

@ -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;

View file

@ -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;

View file

@ -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'];

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;
}

View file

@ -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;

View file

@ -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;

View file

@ -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;
}

View file

@ -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;

View file

@ -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;

View file

@ -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';

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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",

View file

@ -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';

View file

@ -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"

View file

@ -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';

View file

@ -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

View file

@ -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' {

View file

@ -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}>

View file

@ -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,

View file

@ -4,5 +4,5 @@
"module": "esnext",
"jsx": "react-native"
},
"include": ["src/theme/", "src/*.d.ts"]
"include": ["src/theme/", "src/client/", "src/*.d.ts"]
}

View file

@ -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() {