- Boost text contrast in both themes - Strengthen border visibility (subtle borders now visible) - Convert 39 files from hardcoded dark:/light: to CSS vars - Tertiary text now more readable on both backgrounds
110 lines
3.5 KiB
TypeScript
110 lines
3.5 KiB
TypeScript
import { ReactNode } from 'react';
|
|
import { Link } from 'react-router-dom';
|
|
import { ArrowLeft, type LucideIcon } from 'lucide-react';
|
|
import { Breadcrumbs } from '../ui/Breadcrumbs';
|
|
|
|
interface PageHeaderProps {
|
|
title: string;
|
|
description?: string;
|
|
icon?: LucideIcon;
|
|
iconColor?: string;
|
|
backLink?: string;
|
|
backLabel?: string;
|
|
actions?: ReactNode;
|
|
showBreadcrumbs?: boolean;
|
|
className?: string;
|
|
}
|
|
|
|
/**
|
|
* Consistent page header component with title, description, breadcrumbs, and actions
|
|
*/
|
|
export function PageHeader({
|
|
title,
|
|
description,
|
|
icon: Icon,
|
|
iconColor = 'text-[var(--color-primary)]',
|
|
backLink,
|
|
backLabel = 'Back',
|
|
actions,
|
|
showBreadcrumbs = true,
|
|
className = ''
|
|
}: PageHeaderProps) {
|
|
return (
|
|
<div className={`mb-6 ${className}`}>
|
|
{/* Breadcrumbs */}
|
|
{showBreadcrumbs && <Breadcrumbs />}
|
|
|
|
{/* Back Link */}
|
|
{backLink && (
|
|
<Link
|
|
to={backLink}
|
|
className="inline-flex items-center gap-1.5 text-sm text-[var(--color-text-tertiary)] hover:text-slate-700 dark:text-[var(--color-text-tertiary)] dark:hover:text-slate-200 mb-3 group"
|
|
>
|
|
<ArrowLeft size={16} className="group-hover:-translate-x-0.5 transition-transform" />
|
|
{backLabel}
|
|
</Link>
|
|
)}
|
|
|
|
{/* Title Row */}
|
|
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
|
<div className="flex items-center gap-3">
|
|
{Icon && (
|
|
<div className={`p-2 rounded-xl bg-slate-100 dark:bg-slate-800 ${iconColor}`}>
|
|
<Icon size={24} />
|
|
</div>
|
|
)}
|
|
<div>
|
|
<h1 className="text-xl sm:text-2xl font-bold text-[var(--color-text-primary)]">
|
|
{title}
|
|
</h1>
|
|
{description && (
|
|
<p className="text-sm text-[var(--color-text-tertiary)] mt-0.5">
|
|
{description}
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Actions */}
|
|
{actions && (
|
|
<div className="flex items-center gap-2 flex-shrink-0">
|
|
{actions}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Common action button styles for PageHeader
|
|
*/
|
|
export function PageHeaderButton({
|
|
children,
|
|
variant = 'primary',
|
|
onClick,
|
|
disabled,
|
|
className = ''
|
|
}: {
|
|
children: ReactNode;
|
|
variant?: 'primary' | 'secondary' | 'ghost';
|
|
onClick?: () => void;
|
|
disabled?: boolean;
|
|
className?: string;
|
|
}) {
|
|
const variants = {
|
|
primary: 'bg-[var(--color-primary)] hover:bg-[var(--color-primary-hover)] text-white shadow-sm',
|
|
secondary: 'bg-[var(--color-bg-tertiary)] hover:bg-slate-200 dark:hover:bg-slate-600 text-[var(--color-text-primary)]',
|
|
ghost: 'hover:bg-slate-100 dark:hover:bg-slate-700 text-[var(--color-text-secondary)]'
|
|
};
|
|
|
|
return (
|
|
<button
|
|
onClick={onClick}
|
|
disabled={disabled}
|
|
className={`inline-flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-colors disabled:opacity-50 ${variants[variant]} ${className}`}
|
|
>
|
|
{children}
|
|
</button>
|
|
);
|
|
}
|