migration wip

This commit is contained in:
2025-12-29 18:18:48 +01:00
parent 292975299d
commit f86785bfb0
182 changed files with 30131 additions and 9321 deletions

View File

@@ -0,0 +1,243 @@
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;