Breadcrumbs (#296)

* add breadcrumbs

* rm space

* move breadcrumbs to ui
This commit is contained in:
tmshv authored and GitHub committed 2026-04-25 03:15:25 +03:00
1 parent ad9695127a
commit 98dcaaa237
11 files changed
+92 -12

No files matched your search

+1 -2
View File
@@ -5,12 +5,11 @@ import s from "./app.module.css"
import { Wrapper } from "../Wrapper"
import { Footer } from "../Footer"
import { Content } from "../Content"
import { Breadcrumbs } from "../Breadcrumbs"
import type { BreadcrumbPart } from "@/types"
import { usePathname } from "next/navigation"
import { useMobile } from "@hudozka/hooks"
import { Navigation } from "@/components/Navigation"
import { Box, Panel, Title } from "@hudozka/ui"
import { Box, Breadcrumbs, Panel, Title } from "@hudozka/ui"
import { AccessibilityButton } from "../AccessibilityButton"
import { Copyright } from "@/components/Copyright"
import { yearRange } from "@/const"
+42 -4
View File
@@ -2,7 +2,43 @@ import { pb } from "./pb"
import { createPage, createHomeCards, createMenu, createFeedPages } from "./factory"
import type { PbPage, PbImage, PbFile, PbTag, PbHomeData, PbMenuData } from "./types"
import type { DocV1Block } from "./doc"
import type { MenuItem, Page, PageCardDto, FeedPage } from "@/types"
import type { BreadcrumbPart, MenuItem, Page, PageCardDto, FeedPage } from "@/types"
import { menu } from "@/const"
const MAX_PARENT_DEPTH = 10
function getParentId(parent: string | string[] | undefined): string | undefined {
if (!parent) return undefined
if (Array.isArray(parent)) return parent[0]
return parent
}
async function fetchParentChain(startId: string | undefined): Promise<PbPage[]> {
if (!startId) return []
const chain: PbPage[] = []
const visited = new Set<string>()
let currentId: string | undefined = startId
while (currentId && chain.length < MAX_PARENT_DEPTH) {
if (visited.has(currentId)) break
visited.add(currentId)
try {
const parent = await pb.collection("pages").getOne<PbPage>(currentId)
chain.unshift(parent)
currentId = getParentId(parent.parent)
} catch {
break
}
}
return chain
}
function buildBreadcrumb(record: PbPage, parents: PbPage[]): BreadcrumbPart[] {
if (parents.length === 0) return []
const home: BreadcrumbPart = { name: menu[0].name, href: "/" }
const ancestors: BreadcrumbPart[] = parents.map(p => ({ name: p.title, href: p.slug }))
const current: BreadcrumbPart = { name: record.title, href: record.slug }
return [home, ...ancestors, current]
}
function buildIdFilter(ids: string[]): string {
return ids.map(id => `id="${id}"`).join(" || ")
@@ -90,12 +126,13 @@ export async function getPageBySlug(slug: string): Promise<Page | null> {
refs.imageIds.add(record.cover)
}
// Round 2: fetch images, files, tags, card-grid pages in parallel
const [images, files, tags, cardGridPages] = await Promise.all([
// Round 2: fetch images, files, tags, card-grid pages, parent chain in parallel
const [images, files, tags, cardGridPages, parentChain] = await Promise.all([
fetchImagesByIds([...refs.imageIds]),
fetchFilesByIds([...refs.fileIds]),
fetchTagsByIds(record.tags),
fetchPagesByIds([...refs.pageIds]),
fetchParentChain(getParentId(record.parent)),
])
// Round 3: fetch cover images for card-grid pages
@@ -112,7 +149,8 @@ export async function getPageBySlug(slug: string): Promise<Page | null> {
// Merge all images for card-grid covers
const allCardGridImages = new Map([...images, ...cardGridImages])
return createPage(record, images, files, tags, cardGridPages, allCardGridImages)
const breadcrumb = buildBreadcrumb(record, parentChain)
return createPage(record, images, files, tags, cardGridPages, allCardGridImages, breadcrumb)
} catch (error) {
console.error(`Failed to fetch page: ${error}`)
return null
+3 -1
View File
@@ -1,4 +1,4 @@
import type { MenuItem, Page, Tag, PageCardDto, Pic, Token, FeedPage } from "@/types"
import type { MenuItem, Page, Tag, PageCardDto, Pic, Token, FeedPage, BreadcrumbPart } from "@/types"
import type { PbPage, PbImage, PbFile, PbTag, PbHomeData, PbMenuData } from "./types"
import type { DocV1Block } from "./doc"
import { typograf, markdownToHtml } from "@hudozka/text"
@@ -159,6 +159,7 @@ export function createPage(
tags: PbTag[],
cardGridPages: Map<string, PbPage>,
cardGridImages: Map<string, PbImage>,
breadcrumb: BreadcrumbPart[] = [],
): Page {
const cover = getCoverPic(record.cover, images)
@@ -190,6 +191,7 @@ export function createPage(
...tokens,
],
tags: pageTags,
breadcrumb,
featured: false,
}
}
+1
View File
@@ -36,6 +36,7 @@ export type PbPage = PbRecord & {
doc: DocV1
tags: string[]
draft: boolean
parent?: string | string[]
}
export type PbTag = PbRecord & {
+1
View File
@@ -9633,6 +9633,7 @@
"version": "0.0.0",
"dependencies": {
"@hudozka/hooks": "*",
"@hudozka/utils": "*",
"classnames": "^2.5.0"
},
"peerDependencies": {
+2 -1
View File
@@ -11,7 +11,8 @@
},
"dependencies": {
"classnames": "^2.5.0",
"@hudozka/hooks": "*"
"@hudozka/hooks": "*",
"@hudozka/utils": "*"
},
"peerDependencies": {
"react": ">=19",
@@ -1,12 +1,16 @@
import s from "./styles.module.css"
import type { BreadcrumbPart } from "@/types"
import { Button } from "@hudozka/ui"
import { Button } from "../Button"
import { insertBetween } from "@hudozka/utils"
export type Crumb = {
name: string
href: string
}
export type BreadcrumbsProps = {
style?: React.CSSProperties
items: BreadcrumbPart[]
items: Crumb[]
path: string
}
@@ -37,6 +41,6 @@ export const Breadcrumbs: React.FC<BreadcrumbsProps> = props => {
</li>
)
})}
</nav >
</nav>
)
}
@@ -11,3 +11,7 @@
.breadcrumbs li {
margin-bottom: var(--size-xs);
}
.breadcrumbs a {
text-decoration: none;
}
+1
View File
@@ -1,4 +1,5 @@
export * from "./Box"
export * from "./Breadcrumbs"
export * from "./Button"
export * from "./Panel"
export * from "./Title"
@@ -0,0 +1,29 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_1125843985")
// add field
collection.fields.addAt(5, new Field({
"cascadeDelete": false,
"collectionId": "pbc_1125843985",
"help": "",
"hidden": false,
"id": "relation1032740943",
"maxSelect": 0,
"minSelect": 0,
"name": "parent",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_1125843985")
// remove field
collection.fields.removeById("relation1032740943")
return app.save(collection)
})