Files
klz-cables.com/components/content/Breadcrumbs.tsx
2025-12-29 18:18:48 +01:00

243 lines
6.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React from 'react';
import Link from 'next/link';
import { cn } from '../../lib/utils';
import { Container } from '../ui/Container';
interface BreadcrumbItem {
label: string;
href?: string;
icon?: React.ReactNode;
}
interface BreadcrumbsProps {
items: BreadcrumbItem[];
separator?: React.ReactNode;
className?: string;
showHome?: boolean;
homeLabel?: string;
homeHref?: string;
collapseMobile?: boolean;
}
export const Breadcrumbs: React.FC<BreadcrumbsProps> = ({
items,
separator = '/',
className = '',
showHome = true,
homeLabel = 'Home',
homeHref = '/',
collapseMobile = true,
}) => {
// Generate schema.org structured data
const generateSchema = () => {
const breadcrumbs = [
...(showHome ? [{ label: homeLabel, href: homeHref }] : []),
...items,
].map((item, index) => ({
'@type': 'ListItem',
position: index + 1,
name: item.label,
...(item.href && { item: item.href }),
}));
return {
'@context': 'https://schema.org',
'@type': 'BreadcrumbList',
itemListElement: breadcrumbs,
};
};
// Render individual breadcrumb item
const renderItem = (item: BreadcrumbItem, index: number, isLast: boolean) => {
const isHome = showHome && index === 0 && item.href === homeHref;
const content = (
<span className="flex items-center gap-2">
{isHome && (
<span className="inline-flex items-center justify-center w-4 h-4">
<svg
className="w-4 h-4"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z" />
</svg>
</span>
)}
{item.icon && <span className="inline-flex items-center">{item.icon}</span>}
<span className={cn(
'transition-colors duration-200',
isLast ? 'font-semibold text-gray-900' : 'text-gray-600 hover:text-gray-900'
)}>
{item.label}
</span>
</span>
);
if (!isLast && item.href) {
return (
<Link
href={item.href}
className={cn(
'inline-flex items-center gap-2',
'transition-colors duration-200',
'hover:text-primary'
)}
>
{content}
</Link>
);
}
return content;
};
const allItems = [
...(showHome ? [{ label: homeLabel, href: homeHref }] : []),
...items,
];
// Schema.org JSON-LD
const schema = generateSchema();
return (
<>
{/* Schema.org structured data */}
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
/>
{/* Breadcrumbs navigation */}
<nav
aria-label="Breadcrumb"
className={cn('w-full bg-gray-50 py-3', className)}
>
<Container maxWidth="6xl" padding="md">
<ol className={cn(
'flex items-center flex-wrap gap-2 text-sm',
// Mobile: show only current and parent, hide others
collapseMobile && 'max-w-full overflow-hidden'
)}>
{allItems.map((item, index) => {
const isLast = index === allItems.length - 1;
const isFirst = index === 0;
return (
<li
key={index}
className={cn(
'flex items-center gap-2',
// On mobile, hide intermediate items but keep structure
collapseMobile && !isFirst && !isLast && 'hidden sm:flex'
)}
>
{renderItem(item, index, isLast)}
{!isLast && (
<span className={cn(
'text-gray-400 select-none',
// Use different separator for mobile vs desktop
'hidden sm:inline'
)}>
{separator}
</span>
)}
</li>
);
})}
</ol>
</Container>
</nav>
</>
);
};
// Sub-components for variations
export const BreadcrumbsCompact: React.FC<Omit<BreadcrumbsProps, 'collapseMobile'>> = ({
items,
separator = '',
className = '',
showHome = true,
homeLabel = 'Home',
homeHref = '/',
}) => {
const allItems = [
...(showHome ? [{ label: homeLabel, href: homeHref }] : []),
...items,
];
return (
<nav aria-label="Breadcrumb" className={cn('w-full', className)}>
<Container maxWidth="6xl" padding="md">
<ol className="flex items-center gap-2 text-xs md:text-sm">
{allItems.map((item, index) => {
const isLast = index === allItems.length - 1;
return (
<li key={index} className="flex items-center gap-2">
{item.href && !isLast ? (
<Link
href={item.href}
className="text-gray-500 hover:text-gray-900 transition-colors"
>
{item.label}
</Link>
) : (
<span className={cn(
isLast ? 'font-medium text-gray-900' : 'text-gray-500'
)}>
{item.label}
</span>
)}
{!isLast && (
<span className="text-gray-400">{separator}</span>
)}
</li>
);
})}
</ol>
</Container>
</nav>
);
};
export const BreadcrumbsSimple: React.FC<{
items: BreadcrumbItem[];
className?: string;
}> = ({ items, className = '' }) => {
return (
<nav aria-label="Breadcrumb" className={cn('w-full', className)}>
<ol className="flex items-center gap-2 text-sm">
{items.map((item, index) => {
const isLast = index === items.length - 1;
return (
<li key={index} className="flex items-center gap-2">
{item.href && !isLast ? (
<Link
href={item.href}
className="text-blue-600 hover:text-blue-800 hover:underline transition-colors"
>
{item.label}
</Link>
) : (
<span className={cn(
isLast ? 'font-semibold text-gray-900' : 'text-gray-600'
)}>
{item.label}
</span>
)}
{!isLast && <span className="text-gray-400">/</span>}
</li>
);
})}
</ol>
</nav>
);
};
export default Breadcrumbs;