mirror of
https://github.com/facebook/docusaurus.git
synced 2025-08-06 10:20:09 +02:00
feat: add ability to filter doc sidebar items
This commit is contained in:
parent
a1d333e96b
commit
251223f75e
14 changed files with 276 additions and 20 deletions
|
@ -166,7 +166,7 @@ declare module '@theme/DocSidebar' {
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
readonly path: string;
|
readonly path: string;
|
||||||
readonly sidebar: readonly PropSidebarItem[];
|
readonly sidebar: PropSidebarItem[];
|
||||||
readonly onCollapse: () => void;
|
readonly onCollapse: () => void;
|
||||||
readonly isHidden: boolean;
|
readonly isHidden: boolean;
|
||||||
// MobileSecondaryFilter expects Record<string, unknown>
|
// MobileSecondaryFilter expects Record<string, unknown>
|
||||||
|
@ -198,7 +198,7 @@ declare module '@theme/DocSidebar/Desktop/Content' {
|
||||||
export interface Props {
|
export interface Props {
|
||||||
readonly className?: string;
|
readonly className?: string;
|
||||||
readonly path: string;
|
readonly path: string;
|
||||||
readonly sidebar: readonly PropSidebarItem[];
|
readonly sidebar: PropSidebarItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Content(props: Props): JSX.Element;
|
export default function Content(props: Props): JSX.Element;
|
||||||
|
@ -212,6 +212,10 @@ declare module '@theme/DocSidebar/Desktop/CollapseButton' {
|
||||||
export default function CollapseButton(props: Props): JSX.Element;
|
export default function CollapseButton(props: Props): JSX.Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module '@theme/DocSidebar/Desktop/Filter' {
|
||||||
|
export default function Filter(): JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
declare module '@theme/DocSidebarItem' {
|
declare module '@theme/DocSidebarItem' {
|
||||||
import type {PropSidebarItem} from '@docusaurus/plugin-content-docs';
|
import type {PropSidebarItem} from '@docusaurus/plugin-content-docs';
|
||||||
|
|
||||||
|
@ -941,3 +945,12 @@ declare module '@theme/Seo' {
|
||||||
|
|
||||||
export default function Seo(props: Props): JSX.Element;
|
export default function Seo(props: Props): JSX.Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module '@theme/TextHighlight' {
|
||||||
|
export interface Props {
|
||||||
|
readonly text?: string;
|
||||||
|
readonly highlight?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function TextHighlight(props: Props): JSX.Element;
|
||||||
|
}
|
||||||
|
|
|
@ -8,6 +8,15 @@
|
||||||
import React, {type ReactNode, useState, useCallback} from 'react';
|
import React, {type ReactNode, useState, useCallback} from 'react';
|
||||||
import renderRoutes from '@docusaurus/renderRoutes';
|
import renderRoutes from '@docusaurus/renderRoutes';
|
||||||
import type {PropVersionMetadata} from '@docusaurus/plugin-content-docs';
|
import type {PropVersionMetadata} from '@docusaurus/plugin-content-docs';
|
||||||
|
import {
|
||||||
|
DocsFilterProvider,
|
||||||
|
HtmlClassNameProvider,
|
||||||
|
ThemeClassNames,
|
||||||
|
docVersionSearchTag,
|
||||||
|
DocsSidebarProvider,
|
||||||
|
useDocsSidebar,
|
||||||
|
DocsVersionProvider,
|
||||||
|
} from '@docusaurus/theme-common';
|
||||||
import Layout from '@theme/Layout';
|
import Layout from '@theme/Layout';
|
||||||
import DocSidebar from '@theme/DocSidebar';
|
import DocSidebar from '@theme/DocSidebar';
|
||||||
import NotFound from '@theme/NotFound';
|
import NotFound from '@theme/NotFound';
|
||||||
|
@ -21,15 +30,6 @@ import {translate} from '@docusaurus/Translate';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import styles from './styles.module.css';
|
import styles from './styles.module.css';
|
||||||
|
|
||||||
import {
|
|
||||||
HtmlClassNameProvider,
|
|
||||||
ThemeClassNames,
|
|
||||||
docVersionSearchTag,
|
|
||||||
DocsSidebarProvider,
|
|
||||||
useDocsSidebar,
|
|
||||||
DocsVersionProvider,
|
|
||||||
} from '@docusaurus/theme-common';
|
|
||||||
|
|
||||||
type DocPageContentProps = {
|
type DocPageContentProps = {
|
||||||
readonly currentDocRoute: DocumentRoute;
|
readonly currentDocRoute: DocumentRoute;
|
||||||
readonly versionMetadata: PropVersionMetadata;
|
readonly versionMetadata: PropVersionMetadata;
|
||||||
|
@ -164,12 +164,14 @@ export default function DocPage(props: Props): JSX.Element {
|
||||||
<HtmlClassNameProvider className={versionMetadata.className}>
|
<HtmlClassNameProvider className={versionMetadata.className}>
|
||||||
<DocsVersionProvider version={versionMetadata}>
|
<DocsVersionProvider version={versionMetadata}>
|
||||||
<DocsSidebarProvider sidebar={sidebar ?? null}>
|
<DocsSidebarProvider sidebar={sidebar ?? null}>
|
||||||
<DocPageContent
|
<DocsFilterProvider>
|
||||||
currentDocRoute={currentDocRoute}
|
<DocPageContent
|
||||||
versionMetadata={versionMetadata}
|
currentDocRoute={currentDocRoute}
|
||||||
sidebarName={sidebarName}>
|
versionMetadata={versionMetadata}
|
||||||
{renderRoutes(docRoutes, {versionMetadata})}
|
sidebarName={sidebarName}>
|
||||||
</DocPageContent>
|
{renderRoutes(docRoutes, {versionMetadata})}
|
||||||
|
</DocPageContent>
|
||||||
|
</DocsFilterProvider>
|
||||||
</DocsSidebarProvider>
|
</DocsSidebarProvider>
|
||||||
</DocsVersionProvider>
|
</DocsVersionProvider>
|
||||||
</HtmlClassNameProvider>
|
</HtmlClassNameProvider>
|
||||||
|
|
|
@ -11,6 +11,8 @@ import {
|
||||||
ThemeClassNames,
|
ThemeClassNames,
|
||||||
useAnnouncementBar,
|
useAnnouncementBar,
|
||||||
useScrollPosition,
|
useScrollPosition,
|
||||||
|
useDocsFilter,
|
||||||
|
filterDocsSidebar,
|
||||||
} from '@docusaurus/theme-common';
|
} from '@docusaurus/theme-common';
|
||||||
import DocSidebarItems from '@theme/DocSidebarItems';
|
import DocSidebarItems from '@theme/DocSidebarItems';
|
||||||
import type {Props} from '@theme/DocSidebar/Desktop/Content';
|
import type {Props} from '@theme/DocSidebar/Desktop/Content';
|
||||||
|
@ -38,6 +40,8 @@ export default function DocSidebarDesktopContent({
|
||||||
className,
|
className,
|
||||||
}: Props): JSX.Element {
|
}: Props): JSX.Element {
|
||||||
const showAnnouncementBar = useShowAnnouncementBar();
|
const showAnnouncementBar = useShowAnnouncementBar();
|
||||||
|
const {filterTerm} = useDocsFilter();
|
||||||
|
const filteredSidebar = filterDocsSidebar(sidebar, filterTerm);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav
|
<nav
|
||||||
|
@ -48,7 +52,7 @@ export default function DocSidebarDesktopContent({
|
||||||
className,
|
className,
|
||||||
)}>
|
)}>
|
||||||
<ul className={clsx(ThemeClassNames.docs.docSidebarMenu, 'menu__list')}>
|
<ul className={clsx(ThemeClassNames.docs.docSidebarMenu, 'menu__list')}>
|
||||||
<DocSidebarItems items={sidebar} activePath={path} level={1} />
|
<DocSidebarItems items={filteredSidebar} activePath={path} level={1} />
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
/**
|
||||||
|
* 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 clsx from 'clsx';
|
||||||
|
import {useDocsFilter} from '@docusaurus/theme-common';
|
||||||
|
|
||||||
|
import styles from './styles.module.css';
|
||||||
|
|
||||||
|
function Filter(): JSX.Element {
|
||||||
|
const {setFilterTerm, filterTerm = ''} = useDocsFilter();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.filter}>
|
||||||
|
<input
|
||||||
|
placeholder="Filter by title" // todo: i18n
|
||||||
|
type="text"
|
||||||
|
className={styles.filterInput}
|
||||||
|
onChange={(e) => setFilterTerm(e.target.value)}
|
||||||
|
value={filterTerm}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{filterTerm && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={clsx('clean-btn', styles.clearFilterInputBtn)}
|
||||||
|
onClick={() => setFilterTerm('')}>
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round">
|
||||||
|
<line x1="18" y1="6" x2="6" y2="18" />
|
||||||
|
<line x1="6" y1="6" x2="18" y2="18" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Filter;
|
|
@ -0,0 +1,52 @@
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.filter {
|
||||||
|
--docusaurus-clear-filter-icon: 1rem;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filterInput {
|
||||||
|
appearance: none;
|
||||||
|
width: 100%;
|
||||||
|
border: 1px solid var(--ifm-color-emphasis-300);
|
||||||
|
border-radius: var(--ifm-global-radius);
|
||||||
|
background: var(--docsearch-searchbox-focus-background);
|
||||||
|
color: var(--ifm-color-emphasis-800);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
padding: 0.5rem calc(0.5rem + var(--docusaurus-clear-filter-icon)) 0.5rem
|
||||||
|
0.5rem;
|
||||||
|
transition: border var(--ifm-transition-fast) ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filterInput:focus {
|
||||||
|
border-color: var(--docsearch-primary-color);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clearFilterInputBtn {
|
||||||
|
position: absolute;
|
||||||
|
right: 1rem;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
border-radius: 50%;
|
||||||
|
padding: 0.2rem;
|
||||||
|
color: var(--ifm-color-emphasis-800);
|
||||||
|
transition: background var(--ifm-transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.clearFilterInputBtn:hover {
|
||||||
|
background: var(--ifm-color-emphasis-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.clearFilterInputBtn svg {
|
||||||
|
width: var(--docusaurus-clear-filter-icon);
|
||||||
|
height: var(--docusaurus-clear-filter-icon);
|
||||||
|
display: block;
|
||||||
|
}
|
|
@ -11,6 +11,7 @@ import {useThemeConfig} from '@docusaurus/theme-common';
|
||||||
import Logo from '@theme/Logo';
|
import Logo from '@theme/Logo';
|
||||||
import CollapseButton from '@theme/DocSidebar/Desktop/CollapseButton';
|
import CollapseButton from '@theme/DocSidebar/Desktop/CollapseButton';
|
||||||
import Content from '@theme/DocSidebar/Desktop/Content';
|
import Content from '@theme/DocSidebar/Desktop/Content';
|
||||||
|
import Filter from '@theme/DocSidebar/Desktop/Filter';
|
||||||
import type {Props} from '@theme/DocSidebar/Desktop';
|
import type {Props} from '@theme/DocSidebar/Desktop';
|
||||||
|
|
||||||
import styles from './styles.module.css';
|
import styles from './styles.module.css';
|
||||||
|
@ -19,6 +20,7 @@ function DocSidebarDesktop({path, sidebar, onCollapse, isHidden}: Props) {
|
||||||
const {
|
const {
|
||||||
navbar: {hideOnScroll},
|
navbar: {hideOnScroll},
|
||||||
hideableSidebar,
|
hideableSidebar,
|
||||||
|
filterableSidebar,
|
||||||
} = useThemeConfig();
|
} = useThemeConfig();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -29,6 +31,7 @@ function DocSidebarDesktop({path, sidebar, onCollapse, isHidden}: Props) {
|
||||||
isHidden && styles.sidebarHidden,
|
isHidden && styles.sidebarHidden,
|
||||||
)}>
|
)}>
|
||||||
{hideOnScroll && <Logo tabIndex={-1} className={styles.sidebarLogo} />}
|
{hideOnScroll && <Logo tabIndex={-1} className={styles.sidebarLogo} />}
|
||||||
|
{filterableSidebar && <Filter />}
|
||||||
<Content path={path} sidebar={sidebar} />
|
<Content path={path} sidebar={sidebar} />
|
||||||
{hideableSidebar && <CollapseButton onClick={onCollapse} />}
|
{hideableSidebar && <CollapseButton onClick={onCollapse} />}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -17,11 +17,13 @@ import {
|
||||||
useThemeConfig,
|
useThemeConfig,
|
||||||
useDocSidebarItemsExpandedState,
|
useDocSidebarItemsExpandedState,
|
||||||
isSamePath,
|
isSamePath,
|
||||||
|
useDocsFilter,
|
||||||
} from '@docusaurus/theme-common';
|
} from '@docusaurus/theme-common';
|
||||||
import Link from '@docusaurus/Link';
|
import Link from '@docusaurus/Link';
|
||||||
import isInternalUrl from '@docusaurus/isInternalUrl';
|
import isInternalUrl from '@docusaurus/isInternalUrl';
|
||||||
import {translate} from '@docusaurus/Translate';
|
import {translate} from '@docusaurus/Translate';
|
||||||
import IconExternalLink from '@theme/IconExternalLink';
|
import IconExternalLink from '@theme/IconExternalLink';
|
||||||
|
import TextHighlight from '@theme/TextHighlight';
|
||||||
|
|
||||||
import DocSidebarItems from '@theme/DocSidebarItems';
|
import DocSidebarItems from '@theme/DocSidebarItems';
|
||||||
import type {Props} from '@theme/DocSidebarItem';
|
import type {Props} from '@theme/DocSidebarItem';
|
||||||
|
@ -104,6 +106,7 @@ function DocSidebarItemCategory({
|
||||||
}: Props & {item: PropSidebarItemCategory}) {
|
}: Props & {item: PropSidebarItemCategory}) {
|
||||||
const {items, label, collapsible, className, href} = item;
|
const {items, label, collapsible, className, href} = item;
|
||||||
const hrefWithSSRFallback = useCategoryHrefWithSSRFallback(item);
|
const hrefWithSSRFallback = useCategoryHrefWithSSRFallback(item);
|
||||||
|
const {filterTerm} = useDocsFilter();
|
||||||
|
|
||||||
const isActive = isActiveSidebarItem(item, activePath);
|
const isActive = isActiveSidebarItem(item, activePath);
|
||||||
const isCurrentPage = isSamePath(href, activePath);
|
const isCurrentPage = isSamePath(href, activePath);
|
||||||
|
@ -182,7 +185,7 @@ function DocSidebarItemCategory({
|
||||||
aria-expanded={collapsible ? !collapsed : undefined}
|
aria-expanded={collapsible ? !collapsed : undefined}
|
||||||
href={collapsible ? hrefWithSSRFallback ?? '#' : hrefWithSSRFallback}
|
href={collapsible ? hrefWithSSRFallback ?? '#' : hrefWithSSRFallback}
|
||||||
{...props}>
|
{...props}>
|
||||||
{label}
|
<TextHighlight text={label} highlight={filterTerm} />
|
||||||
</Link>
|
</Link>
|
||||||
{href && collapsible && (
|
{href && collapsible && (
|
||||||
<button
|
<button
|
||||||
|
@ -249,6 +252,7 @@ function DocSidebarItemLink({
|
||||||
}: Props & {item: PropSidebarItemLink}) {
|
}: Props & {item: PropSidebarItemLink}) {
|
||||||
const {href, label, className} = item;
|
const {href, label, className} = item;
|
||||||
const isActive = isActiveSidebarItem(item, activePath);
|
const isActive = isActiveSidebarItem(item, activePath);
|
||||||
|
const {filterTerm} = useDocsFilter();
|
||||||
return (
|
return (
|
||||||
<li
|
<li
|
||||||
className={clsx(
|
className={clsx(
|
||||||
|
@ -269,7 +273,7 @@ function DocSidebarItemLink({
|
||||||
})}
|
})}
|
||||||
{...props}>
|
{...props}>
|
||||||
<span>
|
<span>
|
||||||
{label}
|
<TextHighlight text={label} highlight={filterTerm} />
|
||||||
{!isInternalUrl(href) && <IconExternalLink />}
|
{!isInternalUrl(href) && <IconExternalLink />}
|
||||||
</span>
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
/**
|
||||||
|
* 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 {Props} from '@theme/TextHighlight';
|
||||||
|
|
||||||
|
import styles from './styles.module.css';
|
||||||
|
|
||||||
|
function TextHighlight({text, highlight}: Props): JSX.Element {
|
||||||
|
if (!highlight) {
|
||||||
|
return <>{text}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const highlightedText = text.replace(
|
||||||
|
new RegExp(highlight, 'gi'),
|
||||||
|
(match) => `<mark>${match}</mark>`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={styles.highlightText}
|
||||||
|
dangerouslySetInnerHTML={{__html: highlightedText}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TextHighlight;
|
|
@ -0,0 +1,11 @@
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.highlightText mark {
|
||||||
|
background: none;
|
||||||
|
color: var(--ifm-color-primary);
|
||||||
|
}
|
|
@ -35,6 +35,7 @@ export const DEFAULT_CONFIG = {
|
||||||
items: [],
|
items: [],
|
||||||
},
|
},
|
||||||
hideableSidebar: false,
|
hideableSidebar: false,
|
||||||
|
filterableSidebar: false,
|
||||||
autoCollapseSidebarCategories: false,
|
autoCollapseSidebarCategories: false,
|
||||||
tableOfContents: {
|
tableOfContents: {
|
||||||
minHeadingLevel: 2,
|
minHeadingLevel: 2,
|
||||||
|
@ -343,6 +344,7 @@ export const ThemeConfigSchema = Joi.object({
|
||||||
.default(DEFAULT_CONFIG.prism)
|
.default(DEFAULT_CONFIG.prism)
|
||||||
.unknown(),
|
.unknown(),
|
||||||
hideableSidebar: Joi.bool().default(DEFAULT_CONFIG.hideableSidebar),
|
hideableSidebar: Joi.bool().default(DEFAULT_CONFIG.hideableSidebar),
|
||||||
|
filterableSidebar: Joi.bool().default(DEFAULT_CONFIG.filterableSidebar),
|
||||||
autoCollapseSidebarCategories: Joi.bool().default(
|
autoCollapseSidebarCategories: Joi.bool().default(
|
||||||
DEFAULT_CONFIG.autoCollapseSidebarCategories,
|
DEFAULT_CONFIG.autoCollapseSidebarCategories,
|
||||||
),
|
),
|
||||||
|
|
|
@ -155,6 +155,12 @@ export {
|
||||||
} from './utils/navbarSecondaryMenuUtils';
|
} from './utils/navbarSecondaryMenuUtils';
|
||||||
export type {NavbarSecondaryMenuComponent} from './utils/navbarSecondaryMenuUtils';
|
export type {NavbarSecondaryMenuComponent} from './utils/navbarSecondaryMenuUtils';
|
||||||
|
|
||||||
|
export {
|
||||||
|
DocsFilterProvider,
|
||||||
|
useDocsFilter,
|
||||||
|
filterDocsSidebar,
|
||||||
|
} from './utils/docsFilterUtils';
|
||||||
|
|
||||||
export {default as useHideableNavbar} from './hooks/useHideableNavbar';
|
export {default as useHideableNavbar} from './hooks/useHideableNavbar';
|
||||||
export {
|
export {
|
||||||
default as useKeyboardNavigation,
|
default as useKeyboardNavigation,
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
/**
|
||||||
|
* 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, {type ReactNode, useMemo, useState, useContext} from 'react';
|
||||||
|
import {ReactContextError} from './reactUtils';
|
||||||
|
import type {PropSidebarItem} from '@docusaurus/plugin-content-docs';
|
||||||
|
|
||||||
|
type DocsFilterContextValue = {
|
||||||
|
filterTerm: string | undefined;
|
||||||
|
setFilterTerm: (value: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DocsFilterContext = React.createContext<
|
||||||
|
DocsFilterContextValue | undefined
|
||||||
|
>(undefined);
|
||||||
|
|
||||||
|
function useDocsFilterContextValue(): DocsFilterContextValue {
|
||||||
|
const [filterTerm, setFilterTerm] = useState<string | undefined>(undefined);
|
||||||
|
|
||||||
|
return useMemo(() => ({filterTerm, setFilterTerm}), [filterTerm]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DocsFilterProvider({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: ReactNode;
|
||||||
|
}): JSX.Element {
|
||||||
|
const contextValue = useDocsFilterContextValue();
|
||||||
|
return (
|
||||||
|
<DocsFilterContext.Provider value={contextValue}>
|
||||||
|
{children}
|
||||||
|
</DocsFilterContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useDocsFilter(): DocsFilterContextValue {
|
||||||
|
const context = useContext<DocsFilterContextValue | undefined>(
|
||||||
|
DocsFilterContext,
|
||||||
|
);
|
||||||
|
if (context == null) {
|
||||||
|
throw new ReactContextError('DocsFilterProvider');
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function filterDocsSidebar(
|
||||||
|
sidebar: PropSidebarItem[],
|
||||||
|
filterTerm: string | undefined,
|
||||||
|
): PropSidebarItem[] {
|
||||||
|
if (!filterTerm) {
|
||||||
|
return sidebar;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sidebar.reduce((acc, item) => {
|
||||||
|
if (!('label' in item)) {
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isLabelMatch = new RegExp(filterTerm, 'i').test(item.label);
|
||||||
|
|
||||||
|
if (item.type !== 'category') {
|
||||||
|
return isLabelMatch ? acc.concat(item) : acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
const filteredItems = filterDocsSidebar(item.items, filterTerm);
|
||||||
|
const isCategoryMatch = isLabelMatch || filteredItems.length > 0;
|
||||||
|
const filteredItem = {
|
||||||
|
...item,
|
||||||
|
items: filteredItems, // or it's to worth showing items even they do not meet the filter criteria?
|
||||||
|
collapsed: !isCategoryMatch, // todo: fix bug with auto collapse category feature
|
||||||
|
collapsible: filteredItems.length > 0, // or disable it at all?
|
||||||
|
};
|
||||||
|
|
||||||
|
return isCategoryMatch ? acc.concat(filteredItem) : acc;
|
||||||
|
}, [] as PropSidebarItem[]);
|
||||||
|
}
|
|
@ -117,6 +117,7 @@ export type ThemeConfig = {
|
||||||
prism: PrismConfig;
|
prism: PrismConfig;
|
||||||
footer?: Footer;
|
footer?: Footer;
|
||||||
hideableSidebar: boolean;
|
hideableSidebar: boolean;
|
||||||
|
filterableSidebar: boolean;
|
||||||
autoCollapseSidebarCategories: boolean;
|
autoCollapseSidebarCategories: boolean;
|
||||||
image?: string;
|
image?: string;
|
||||||
metadata: Array<Record<string, string>>;
|
metadata: Array<Record<string, string>>;
|
||||||
|
|
|
@ -339,6 +339,7 @@ const config = {
|
||||||
},
|
},
|
||||||
hideableSidebar: true,
|
hideableSidebar: true,
|
||||||
autoCollapseSidebarCategories: true,
|
autoCollapseSidebarCategories: true,
|
||||||
|
filterableSidebar: true,
|
||||||
colorMode: {
|
colorMode: {
|
||||||
defaultMode: 'light',
|
defaultMode: 'light',
|
||||||
disableSwitch: false,
|
disableSwitch: false,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue