Barazo default frontend
barazo.forum
1/**
2 * Breadcrumbs component with JSON-LD structured data.
3 * WCAG 2.2 AA: nav landmark, semantic list, 44px mobile touch target.
4 * Mobile: collapses to single parent back-link.
5 * Desktop: full breadcrumb trail.
6 * @see https://schema.org/BreadcrumbList
7 */
8
9import Link from 'next/link'
10import { CaretLeft } from '@phosphor-icons/react/dist/ssr'
11
12export interface BreadcrumbItem {
13 label: string
14 href?: string
15}
16
17interface BreadcrumbsProps {
18 items: BreadcrumbItem[]
19 /** When provided, JSON-LD uses these instead of `items`. Lets pages keep full path in structured data while showing fewer visual breadcrumbs. */
20 jsonLdItems?: BreadcrumbItem[]
21}
22
23export function Breadcrumbs({ items, jsonLdItems }: BreadcrumbsProps) {
24 if (items.length === 0) return null
25
26 const jsonLdSource = jsonLdItems ?? items
27 const jsonLd = {
28 '@context': 'https://schema.org',
29 '@type': 'BreadcrumbList',
30 itemListElement: jsonLdSource
31 .filter((item) => item.href)
32 .map((item, index) => ({
33 '@type': 'ListItem',
34 position: index + 1,
35 name: item.label,
36 item: `https://barazo.forum${item.href}`,
37 })),
38 }
39
40 // Last item with an href = parent link for mobile back-link
41 const parentItem = [...items].reverse().find((item) => item.href)
42
43 return (
44 <nav aria-label="Breadcrumb" className="mb-4">
45 <script
46 type="application/ld+json"
47 dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
48 />
49
50 {/* Mobile: single parent back-link */}
51 {parentItem && (
52 <Link
53 href={parentItem.href!}
54 className="flex min-h-[44px] items-center gap-1 py-2 text-sm text-muted-foreground transition-colors hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 rounded-sm md:hidden"
55 >
56 <CaretLeft size={14} aria-hidden="true" />
57 {parentItem.label}
58 </Link>
59 )}
60
61 {/* Desktop: full breadcrumb trail */}
62 <ol className="hidden items-center gap-1 text-sm text-muted-foreground md:flex">
63 {items.map((item, index) => {
64 const isLast = index === items.length - 1
65 return (
66 <li key={item.href ?? item.label} className="flex items-center gap-1">
67 {index > 0 && (
68 <span aria-hidden="true" className="text-muted-foreground">
69 /
70 </span>
71 )}
72 {isLast || !item.href ? (
73 <span className={isLast ? 'font-medium text-foreground' : ''}>{item.label}</span>
74 ) : (
75 <Link
76 href={item.href}
77 className="transition-colors hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 rounded-sm px-1"
78 >
79 {item.label}
80 </Link>
81 )}
82 </li>
83 )
84 })}
85 </ol>
86 </nav>
87 )
88}