mirror of
https://github.com/facebook/docusaurus.git
synced 2025-07-23 11:38:48 +02:00
feat(theme): ability to use <DocCardList> without items prop, on any doc page (#8008)
This commit is contained in:
parent
ff8ef774d6
commit
c811d6249e
9 changed files with 191 additions and 67 deletions
|
@ -28,7 +28,10 @@ export {createStorageSlot, listStorageKeys} from './utils/storageUtils';
|
|||
|
||||
export {useContextualSearchFilters} from './utils/searchUtils';
|
||||
|
||||
export {useCurrentSidebarCategory} from './utils/docsUtils';
|
||||
export {
|
||||
useCurrentSidebarCategory,
|
||||
filterDocCardListItems,
|
||||
} from './utils/docsUtils';
|
||||
|
||||
export {usePluralForm} from './utils/usePluralForm';
|
||||
|
||||
|
|
|
@ -441,26 +441,87 @@ describe('useCurrentSidebarCategory', () => {
|
|||
</DocsSidebarProvider>
|
||||
),
|
||||
}).result.current;
|
||||
it('works', () => {
|
||||
const category: PropSidebarItemCategory = {
|
||||
type: 'category',
|
||||
label: 'Category',
|
||||
|
||||
it('works for sidebar category', () => {
|
||||
const category: PropSidebarItemCategory = testCategory({
|
||||
href: '/cat',
|
||||
collapsible: true,
|
||||
collapsed: false,
|
||||
items: [
|
||||
{type: 'link', href: '/cat/foo', label: 'Foo'},
|
||||
{type: 'link', href: '/cat/bar', label: 'Bar'},
|
||||
{type: 'link', href: '/baz', label: 'Baz'},
|
||||
],
|
||||
};
|
||||
const mockUseCurrentSidebarCategory = createUseCurrentSidebarCategoryMock([
|
||||
{type: 'link', href: '/cat/fake', label: 'Fake'},
|
||||
});
|
||||
const sidebar: PropSidebar = [
|
||||
testLink(),
|
||||
testLink(),
|
||||
category,
|
||||
]);
|
||||
testCategory(),
|
||||
];
|
||||
|
||||
const mockUseCurrentSidebarCategory =
|
||||
createUseCurrentSidebarCategoryMock(sidebar);
|
||||
|
||||
expect(mockUseCurrentSidebarCategory('/cat')).toEqual(category);
|
||||
});
|
||||
|
||||
it('works for nested sidebar category', () => {
|
||||
const category2: PropSidebarItemCategory = testCategory({
|
||||
href: '/cat2',
|
||||
});
|
||||
const category1: PropSidebarItemCategory = testCategory({
|
||||
href: '/cat1',
|
||||
items: [testLink(), testLink(), category2, testCategory()],
|
||||
});
|
||||
const sidebar: PropSidebar = [
|
||||
testLink(),
|
||||
testLink(),
|
||||
category1,
|
||||
testCategory(),
|
||||
];
|
||||
|
||||
const mockUseCurrentSidebarCategory =
|
||||
createUseCurrentSidebarCategoryMock(sidebar);
|
||||
|
||||
expect(mockUseCurrentSidebarCategory('/cat2')).toEqual(category2);
|
||||
});
|
||||
|
||||
it('works for category link item', () => {
|
||||
const link = testLink({href: '/my/link/path'});
|
||||
const category: PropSidebarItemCategory = testCategory({
|
||||
href: '/cat1',
|
||||
items: [testLink(), testLink(), link, testCategory()],
|
||||
});
|
||||
const sidebar: PropSidebar = [
|
||||
testLink(),
|
||||
testLink(),
|
||||
category,
|
||||
testCategory(),
|
||||
];
|
||||
|
||||
const mockUseCurrentSidebarCategory =
|
||||
createUseCurrentSidebarCategoryMock(sidebar);
|
||||
|
||||
expect(mockUseCurrentSidebarCategory('/my/link/path')).toEqual(category);
|
||||
});
|
||||
|
||||
it('works for nested category link item', () => {
|
||||
const link = testLink({href: '/my/link/path'});
|
||||
const category2: PropSidebarItemCategory = testCategory({
|
||||
href: '/cat2',
|
||||
items: [testLink(), testLink(), link, testCategory()],
|
||||
});
|
||||
const category1: PropSidebarItemCategory = testCategory({
|
||||
href: '/cat1',
|
||||
items: [testLink(), testLink(), category2, testCategory()],
|
||||
});
|
||||
const sidebar: PropSidebar = [
|
||||
testLink(),
|
||||
testLink(),
|
||||
category1,
|
||||
testCategory(),
|
||||
];
|
||||
|
||||
const mockUseCurrentSidebarCategory =
|
||||
createUseCurrentSidebarCategoryMock(sidebar);
|
||||
|
||||
expect(mockUseCurrentSidebarCategory('/my/link/path')).toEqual(category2);
|
||||
});
|
||||
|
||||
it('throws for non-category index page', () => {
|
||||
const category: PropSidebarItemCategory = {
|
||||
type: 'category',
|
||||
|
|
|
@ -110,15 +110,18 @@ export function useCurrentSidebarCategory(): PropSidebarItemCategory {
|
|||
if (!sidebar) {
|
||||
throw new Error('Unexpected: cant find current sidebar in context');
|
||||
}
|
||||
const category = findSidebarCategory(sidebar.items, (item) =>
|
||||
isSamePath(item.href, pathname),
|
||||
);
|
||||
if (!category) {
|
||||
const categoryBreadcrumbs = getSidebarBreadcrumbs({
|
||||
sidebarItems: sidebar.items,
|
||||
pathname,
|
||||
onlyCategories: true,
|
||||
});
|
||||
const deepestCategory = categoryBreadcrumbs.slice(-1)[0];
|
||||
if (!deepestCategory) {
|
||||
throw new Error(
|
||||
`${pathname} is not associated with a category. useCurrentSidebarCategory() should only be used on category index pages.`,
|
||||
);
|
||||
}
|
||||
return category;
|
||||
return deepestCategory;
|
||||
}
|
||||
|
||||
const isActive = (testedPath: string | undefined, activePath: string) =>
|
||||
|
@ -149,6 +152,55 @@ export function isActiveSidebarItem(
|
|||
return false;
|
||||
}
|
||||
|
||||
function getSidebarBreadcrumbs(param: {
|
||||
sidebarItems: PropSidebar;
|
||||
pathname: string;
|
||||
onlyCategories: true;
|
||||
}): PropSidebarItemCategory[];
|
||||
|
||||
function getSidebarBreadcrumbs(param: {
|
||||
sidebarItems: PropSidebar;
|
||||
pathname: string;
|
||||
}): PropSidebarBreadcrumbsItem[];
|
||||
|
||||
/**
|
||||
* Get the sidebar the breadcrumbs for a given pathname
|
||||
* Ordered from top to bottom
|
||||
*/
|
||||
function getSidebarBreadcrumbs({
|
||||
sidebarItems,
|
||||
pathname,
|
||||
onlyCategories = false,
|
||||
}: {
|
||||
sidebarItems: PropSidebar;
|
||||
pathname: string;
|
||||
onlyCategories?: boolean;
|
||||
}): PropSidebarBreadcrumbsItem[] {
|
||||
const breadcrumbs: PropSidebarBreadcrumbsItem[] = [];
|
||||
|
||||
function extract(items: PropSidebarItem[]) {
|
||||
for (const item of items) {
|
||||
if (
|
||||
(item.type === 'category' &&
|
||||
(isSamePath(item.href, pathname) || extract(item.items))) ||
|
||||
(item.type === 'link' && isSamePath(item.href, pathname))
|
||||
) {
|
||||
const filtered = onlyCategories && item.type !== 'category';
|
||||
if (!filtered) {
|
||||
breadcrumbs.unshift(item);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
extract(sidebarItems);
|
||||
|
||||
return breadcrumbs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the breadcrumbs of the current doc page, based on its sidebar location.
|
||||
* Returns `null` if there's no sidebar or breadcrumbs are disabled.
|
||||
|
@ -157,31 +209,10 @@ export function useSidebarBreadcrumbs(): PropSidebarBreadcrumbsItem[] | null {
|
|||
const sidebar = useDocsSidebar();
|
||||
const {pathname} = useLocation();
|
||||
const breadcrumbsOption = useActivePlugin()?.pluginData.breadcrumbs;
|
||||
|
||||
if (breadcrumbsOption === false || !sidebar) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const breadcrumbs: PropSidebarBreadcrumbsItem[] = [];
|
||||
|
||||
function extract(items: PropSidebar) {
|
||||
for (const item of items) {
|
||||
if (
|
||||
(item.type === 'category' &&
|
||||
(isSamePath(item.href, pathname) || extract(item.items))) ||
|
||||
(item.type === 'link' && isSamePath(item.href, pathname))
|
||||
) {
|
||||
breadcrumbs.push(item);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
extract(sidebar.items);
|
||||
|
||||
return breadcrumbs.reverse();
|
||||
return getSidebarBreadcrumbs({sidebarItems: sidebar.items, pathname});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -330,3 +361,18 @@ export function useDocRootMetadata({route}: DocRootProps): null | {
|
|||
sidebarItems,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter categories that don't have a link.
|
||||
* @param items
|
||||
*/
|
||||
export function filterDocCardListItems(
|
||||
items: PropSidebarItem[],
|
||||
): PropSidebarItem[] {
|
||||
return items.filter((item) => {
|
||||
if (item.type === 'category') {
|
||||
return !!findFirstCategoryLink(item);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue