Files
dotcom-arcade/frontend/src/components/common/Breadcrumbs.tsx
T
Darkraise 3e35799de8 fix: standardize imports, tighten types, and align
mock data with actual API contracts

- Normalize all `cn` imports to `@/lib/utils` across
  30+ UI and component files
- Replace relative imports with `@/` alias in auth,
  roles, users, stats, and layout components
- Remove redundant `as` type assertions in playlist
  routes (Zod parse already returns typed data)
- Replace `any` with proper types in FallbackCache,
  mock handlers, and validateIcons
- Use `type` import for type-only imports
- Add type-safe `.filter(Boolean)` with type guard
  in Sidebar nav item arrays
- Update MSW mock data to match actual API shape:
  numeric IDs, `role` field, HTTP-only cookie auth,
  paginated response format, and settings schema
- Add JSDoc to AuthTokens clarifying cookie-based
  token storage
- Replace console.* calls with logger in icon
  validation
- Ignore SQLite WAL files in .gitignore
2026-02-10 20:42:56 +07:00

120 lines
3.4 KiB
TypeScript

import { Link, useNavigate } from 'react-router-dom';
import { ChevronRight, Home, ArrowLeft } from 'lucide-react';
import { cn } from '@/lib/utils';
export interface BreadcrumbContext {
label: string;
href: string;
}
export interface PlayerBreadcrumbContext {
breadcrumbContext?: BreadcrumbContext;
gameTitle: string;
gameDetailHref: string;
shareToken: string | null;
sharedPlaylistTitle: string | null;
sharedPlaylistHref: string | null;
}
export interface BreadcrumbItem {
label: string;
href?: string;
active?: boolean;
}
interface BreadcrumbsProps {
items: BreadcrumbItem[];
showHome?: boolean;
homeLabel?: string;
homeHref?: string;
showBackButton?: boolean;
className?: string;
}
export function Breadcrumbs({
items,
showHome = true,
homeLabel = 'Home',
homeHref = '/',
showBackButton = true,
className,
}: BreadcrumbsProps) {
const navigate = useNavigate();
return (
<nav
aria-label="Breadcrumb"
className={cn('flex items-center gap-2 text-sm rounded-lg bg-muted/50 px-3 py-2', className)}
>
{showBackButton ? (
<>
<button
onClick={() => navigate(-1)}
className="rounded-md p-1.5 text-muted-foreground hover:text-foreground hover:bg-accent transition-colors"
aria-label="Go back"
>
<ArrowLeft size={16} />
</button>
<div className="w-px h-5 bg-border" aria-hidden="true" />
</>
) : null}
<ol className="flex items-center gap-1 flex-wrap">
{showHome ? (
<>
<li>
<Link
to={homeHref}
className="flex items-center gap-1 rounded-md px-2 py-1 text-muted-foreground hover:text-foreground hover:bg-accent transition-colors"
aria-label={homeLabel}
>
<Home size={14} />
<span className="hidden sm:inline">{homeLabel}</span>
</Link>
</li>
{items.length > 0 ? (
<li aria-hidden="true" className="flex items-center">
<ChevronRight size={14} className="text-muted-foreground" />
</li>
) : null}
</>
) : null}
{items.map((item, index) => {
const isLast = index === items.length - 1;
const isActive = item.active ?? isLast;
return (
<li key={item.href ?? item.label} className="flex items-center gap-1">
{item.href && !isActive ? (
<Link
to={item.href}
className="rounded-md px-2 py-1 text-muted-foreground hover:text-foreground hover:bg-accent transition-colors max-w-[200px] truncate"
title={item.label}
>
{item.label}
</Link>
) : (
<span
className={cn(
'rounded-md px-2 py-1 max-w-[200px] truncate',
isActive ? 'text-foreground font-medium' : 'text-muted-foreground'
)}
aria-current={isActive ? 'page' : undefined}
title={item.label}
>
{item.label}
</span>
)}
{!isLast ? (
<ChevronRight size={14} className="text-muted-foreground" aria-hidden="true" />
) : null}
</li>
);
})}
</ol>
</nav>
);
}