Skip to content

Commit 4269d50

Browse files
ericyangpanclaude
andcommitted
refactor: simplify Breadcrumb with CSS sticky positioning
Remove client-side JavaScript scroll handling in favor of pure CSS sticky positioning for better performance and simplicity. - Remove useState and useEffect hooks for scroll detection - Replace dynamic sticky behavior with CSS position: sticky - Eliminate client-side header height calculations - Convert from 'use client' to server component compatible code 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent bb3d56a commit 4269d50

File tree

1 file changed

+31
-68
lines changed

1 file changed

+31
-68
lines changed
Lines changed: 31 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
'use client'
2-
3-
import { useEffect, useRef, useState } from 'react'
41
import { JsonLd } from '@/components/JsonLd'
52
import { Link } from '@/i18n/navigation'
63
import { SITE_CONFIG } from '@/lib/metadata/config'
@@ -14,32 +11,9 @@ export interface BreadcrumbItem {
1411
* Renders a breadcrumb navigation bar and injects a Schema.org BreadcrumbList via JsonLd.
1512
* - Visual trail is rendered from provided items; the last item is shown as plain text.
1613
* - Structured data includes "Home" at the first position for better SEO.
17-
* - Sticky behavior is enabled when scrolling.
14+
* - Sticky behavior is enabled when scrolling using CSS position: sticky.
1815
*/
1916
export function Breadcrumb({ items }: { items: BreadcrumbItem[] }) {
20-
const breadcrumbRef = useRef<HTMLDivElement>(null)
21-
const [isBreadcrumbFixed, setIsBreadcrumbFixed] = useState(false)
22-
const [headerHeight, setHeaderHeight] = useState(0)
23-
24-
useEffect(() => {
25-
const handleScroll = () => {
26-
if (!breadcrumbRef.current) return
27-
const header = document.querySelector('header')
28-
const currentHeaderHeight = header?.offsetHeight || 0
29-
setHeaderHeight(currentHeaderHeight)
30-
const breadcrumbTop = breadcrumbRef.current.offsetTop
31-
setIsBreadcrumbFixed(window.scrollY > breadcrumbTop - currentHeaderHeight)
32-
}
33-
34-
window.addEventListener('scroll', handleScroll)
35-
window.addEventListener('resize', handleScroll)
36-
handleScroll()
37-
38-
return () => {
39-
window.removeEventListener('scroll', handleScroll)
40-
window.removeEventListener('resize', handleScroll)
41-
}
42-
}, [])
4317
// Normalize href to ensure it starts with '/' (unless it's already an absolute URL)
4418
const normalizeHref = (href: string): string => {
4519
// If it's already an absolute URL or starts with '/', return as is
@@ -74,51 +48,40 @@ export function Breadcrumb({ items }: { items: BreadcrumbItem[] }) {
7448
itemListElement: schemaItems,
7549
} as const
7650

77-
const BreadcrumbContent = () => (
78-
<section
79-
className="py-[var(--spacing-sm)] bg-[var(--color-hover)] border-b border-[var(--color-border)]"
80-
data-breadcrumb
81-
>
82-
<div className="max-w-8xl mx-auto px-[var(--spacing-md)]">
83-
<nav className="flex items-center gap-[var(--spacing-xs)] text-sm pl-[var(--spacing-xs)]">
84-
{items.map((item, index) => {
85-
const isLast = index === items.length - 1
86-
const normalizedHref = normalizeHref(item.href)
87-
return (
88-
<span
89-
key={`${item.href}-${index}`}
90-
className="inline-flex items-center gap-[var(--spacing-xs)]"
91-
>
92-
{isLast ? (
93-
<span className="text-[var(--color-text)] font-medium">{item.name}</span>
94-
) : (
95-
<Link
96-
href={normalizedHref}
97-
className="text-[var(--color-text-secondary)] hover:text-[var(--color-text)] transition-colors"
98-
>
99-
{item.name}
100-
</Link>
101-
)}
102-
{!isLast && <span className="text-[var(--color-text-muted)]">/</span>}
103-
</span>
104-
)
105-
})}
106-
</nav>
107-
</div>
108-
</section>
109-
)
110-
11151
return (
11252
<>
11353
<JsonLd data={breadcrumbListSchema} />
114-
{isBreadcrumbFixed && (
115-
<div className="fixed left-0 right-0 z-40 shadow-sm" style={{ top: `${headerHeight}px` }}>
116-
<BreadcrumbContent />
54+
<section
55+
className="sticky top-[4rem] z-40 py-[var(--spacing-sm)] bg-[var(--color-hover)] border-b border-[var(--color-border)] shadow-sm"
56+
data-breadcrumb
57+
>
58+
<div className="max-w-8xl mx-auto px-[var(--spacing-md)]">
59+
<nav className="flex items-center gap-[var(--spacing-xs)] text-sm pl-[var(--spacing-xs)]">
60+
{items.map((item, index) => {
61+
const isLast = index === items.length - 1
62+
const normalizedHref = normalizeHref(item.href)
63+
return (
64+
<span
65+
key={`${item.href}-${index}`}
66+
className="inline-flex items-center gap-[var(--spacing-xs)]"
67+
>
68+
{isLast ? (
69+
<span className="text-[var(--color-text)] font-medium">{item.name}</span>
70+
) : (
71+
<Link
72+
href={normalizedHref}
73+
className="text-[var(--color-text-secondary)] hover:text-[var(--color-text)] transition-colors"
74+
>
75+
{item.name}
76+
</Link>
77+
)}
78+
{!isLast && <span className="text-[var(--color-text-muted)]">/</span>}
79+
</span>
80+
)
81+
})}
82+
</nav>
11783
</div>
118-
)}
119-
<div ref={breadcrumbRef} className={isBreadcrumbFixed ? 'invisible' : ''}>
120-
<BreadcrumbContent />
121-
</div>
84+
</section>
12285
</>
12386
)
12487
}

0 commit comments

Comments
 (0)