implement TitleFormatterProvider

This commit is contained in:
sebastien 2025-04-11 17:21:19 +02:00
parent f414ebf442
commit 1460e06d21
3 changed files with 79 additions and 18 deletions

View file

@ -43,7 +43,10 @@ export {
export {DEFAULT_SEARCH_TAG} from './utils/searchUtils'; export {DEFAULT_SEARCH_TAG} from './utils/searchUtils';
export {useTitleFormatter} from './utils/titleFormatterUtils'; export {
TitleFormatterProvider,
useTitleFormatter,
} from './utils/titleFormatterUtils';
export {useLocationChange} from './utils/useLocationChange'; export {useLocationChange} from './utils/useLocationChange';

View file

@ -5,12 +5,12 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import {DefaultTitleFormatter} from '../titleFormatterUtils'; import {TitleFormatterFnDefault} from '../titleFormatterUtils';
describe('DefaultTitleFormatter', () => { describe('TitleFormatterFnDefault', () => {
it('works', () => { it('works', () => {
expect( expect(
DefaultTitleFormatter({ TitleFormatterFnDefault({
title: 'a page', title: 'a page',
siteTitle: 'my site', siteTitle: 'my site',
titleDelimiter: '·', titleDelimiter: '·',
@ -20,7 +20,7 @@ describe('DefaultTitleFormatter', () => {
it('ignores empty title', () => { it('ignores empty title', () => {
expect( expect(
DefaultTitleFormatter({ TitleFormatterFnDefault({
title: ' ', title: ' ',
siteTitle: 'my site', siteTitle: 'my site',
titleDelimiter: '·', titleDelimiter: '·',
@ -33,7 +33,7 @@ describe('DefaultTitleFormatter', () => {
// By default it's preferable to avoid duplicate siteTitle // By default it's preferable to avoid duplicate siteTitle
// See also https://github.com/facebook/docusaurus/issues/5878#issuecomment-961505856 // See also https://github.com/facebook/docusaurus/issues/5878#issuecomment-961505856
expect( expect(
DefaultTitleFormatter({ TitleFormatterFnDefault({
title: 'my site', title: 'my site',
siteTitle: 'my site', siteTitle: 'my site',
titleDelimiter: '·', titleDelimiter: '·',

View file

@ -5,21 +5,48 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import {createContext, useContext, useMemo} from 'react';
import type {ReactNode} from 'react';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import {ReactContextError} from './reactUtils';
type TitleFormatterParams = { type TitleFormatterParams = {
title: string | undefined; /**
* The page title to format
* Usually provided through <PageMetadata> component
* But also when using useTitleFormatter().format(title)
*/
title: string;
/**
* The siteConfig.title value
*/
siteTitle: string; siteTitle: string;
/**
* The siteConfig.titleDelimiter value
*/
titleDelimiter: string; titleDelimiter: string;
}; };
type TitleFormatterFn = (params: TitleFormatterParams) => string; /**
* This is the full formatting function
* Can be customized through React context with the provider
*/
export type TitleFormatterFn = (params: TitleFormatterParams) => string;
export const DefaultTitleFormatter: TitleFormatterFn = ({ /**
* The default formatter is provided in params for convenience
*/
export type TitleFormatterFnWithDefaultFormatter = (
params: TitleFormatterParams & {
defaultFormatter: (params: TitleFormatterParams) => string;
},
) => string;
export const TitleFormatterFnDefault: TitleFormatterFn = ({
title, title,
siteTitle, siteTitle,
titleDelimiter, titleDelimiter,
}: TitleFormatterParams): string => { }): string => {
const trimmedTitle = title?.trim(); const trimmedTitle = title?.trim();
if (!trimmedTitle || trimmedTitle === siteTitle) { if (!trimmedTitle || trimmedTitle === siteTitle) {
return siteTitle; return siteTitle;
@ -27,16 +54,47 @@ export const DefaultTitleFormatter: TitleFormatterFn = ({
return `${trimmedTitle} ${titleDelimiter} ${siteTitle}`; return `${trimmedTitle} ${titleDelimiter} ${siteTitle}`;
}; };
type TitleFormatterUtils = {format: (title?: string) => string}; /**
* This is the simpler API exposed to theme/users
*/
type TitleFormatter = {format: (title: string) => string};
const TitleFormatterContext = createContext<TitleFormatter | null>(null);
export function TitleFormatterProvider({
formatter,
children,
}: {
children: ReactNode;
formatter: TitleFormatterFnWithDefaultFormatter;
}): ReactNode {
const {siteConfig} = useDocusaurusContext();
const {title: siteTitle, titleDelimiter} = siteConfig;
const value: TitleFormatter = useMemo(() => {
return {
format: (title: string) =>
formatter({
title,
siteTitle,
titleDelimiter,
defaultFormatter: TitleFormatterFnDefault,
}),
};
}, [formatter, siteTitle, titleDelimiter]);
return (
<TitleFormatterContext.Provider value={value}>
{children}
</TitleFormatterContext.Provider>
);
}
/** /**
* Returns a function to format the page title * Returns a function to format the page title
*/ */
export function useTitleFormatter(): TitleFormatterUtils { export function useTitleFormatter(): TitleFormatter {
const {siteConfig} = useDocusaurusContext(); const value = useContext(TitleFormatterContext);
const formatter = DefaultTitleFormatter; if (value === null) {
const {title: siteTitle, titleDelimiter} = siteConfig; throw new ReactContextError('TitleFormatterProvider');
return { }
format: (title) => formatter({title, siteTitle, titleDelimiter}), return value;
};
} }