brackt/app/components/league/settings/SettingsNavigation.tsx
chrisp 3273cf1bcb
All checks were successful
🚀 Deploy / ʦ🔍 Typecheck & Lint (push) Successful in 1m17s
🚀 Deploy / 🐳 Build (push) Successful in 1m9s
🚀 Deploy / 🧪 Test (push) Successful in 3m17s
🚀 Deploy / 🚀 Deploy (push) Successful in 14s
claude/settings-menu-urls-kbp6x8 (#116)
Co-authored-by: Claude <noreply@anthropic.com>
Reviewed-on: #116
2026-06-30 16:01:36 +00:00

170 lines
4.8 KiB
TypeScript

import type { ComponentType } from "react";
import type { LucideProps } from "lucide-react";
import { ArrowLeft } from "lucide-react";
import { Link } from "react-router";
import { cn } from "~/lib/utils";
type SettingsNavSection = {
id: string;
label: string;
};
export type SettingsGridSection = SettingsNavSection & {
icon: ComponentType<LucideProps>;
subtitle: string;
isDanger?: boolean;
};
export function SettingsMobileGridNav({
sections,
onSectionChange,
buildHref,
}: {
sections: readonly SettingsGridSection[];
onSectionChange?: (sectionId: string) => void;
buildHref?: (sectionId: string) => string;
}) {
const cardClassName = (section: SettingsGridSection) =>
cn(
"flex cursor-pointer flex-col gap-3 rounded-xl border bg-card p-4 text-left transition-colors hover:border-primary/40 active:scale-[0.98]",
section.isDanger && "border-destructive/40"
);
const cardInner = (section: SettingsGridSection) => (
<>
<div
className={cn(
"flex h-10 w-10 items-center justify-center rounded-lg",
section.isDanger
? "bg-destructive/15 text-destructive"
: "bg-primary/20 text-primary"
)}
>
<section.icon className="h-5 w-5" />
</div>
<div>
<p className="text-sm font-semibold leading-tight">{section.label}</p>
<p className="mt-0.5 text-xs text-muted-foreground">{section.subtitle}</p>
</div>
</>
);
return (
<div className="mb-5 lg:hidden">
<div className="mb-3">
<p className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
Manage
</p>
</div>
<div className="grid grid-cols-2 gap-3">
{sections.map((section) =>
buildHref ? (
<Link key={section.id} to={buildHref(section.id)} className={cardClassName(section)}>
{cardInner(section)}
</Link>
) : (
<button
key={section.id}
type="button"
onClick={() => onSectionChange?.(section.id)}
className={cardClassName(section)}
>
{cardInner(section)}
</button>
)
)}
</div>
</div>
);
}
export function SettingsMobileSectionPill({
onShowGrid,
backHref,
}: {
onShowGrid?: () => void;
backHref?: string;
}) {
const className =
"flex cursor-pointer items-center gap-1.5 rounded-full border bg-card px-3 py-1.5 text-sm font-medium hover:bg-muted";
const content = (
<>
<ArrowLeft className="h-3.5 w-3.5" />
Back to all settings
</>
);
return (
<div className="mb-5 lg:hidden">
{backHref ? (
<Link to={backHref} className={className}>
{content}
</Link>
) : (
<button type="button" onClick={onShowGrid} className={className}>
{content}
</button>
)}
</div>
);
}
export function SettingsDesktopNav({
sections,
activeSection,
onSectionChange,
buildHref,
navLabel = "League settings",
}: {
sections: readonly SettingsGridSection[];
activeSection: string;
onSectionChange?: (sectionId: string) => void;
buildHref?: (sectionId: string) => string;
navLabel?: string;
}) {
const itemClassName = (section: SettingsGridSection) =>
cn(
"flex w-full cursor-pointer items-center gap-2.5 rounded-md px-3 py-2 text-left text-sm font-medium text-muted-foreground hover:bg-muted hover:text-foreground",
activeSection === section.id && "bg-muted text-foreground"
);
const itemInner = (section: SettingsGridSection) => (
<>
<section.icon className={cn("h-4 w-4 shrink-0", section.isDanger && "text-destructive")} aria-hidden="true" />
{section.label}
</>
);
return (
<aside className="hidden lg:block">
<div className="sticky top-6 rounded-xl border bg-card p-3">
<p className="px-3 pb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground">
Manage
</p>
<nav aria-label={navLabel} className="space-y-1">
{sections.map((section) =>
buildHref ? (
<Link
key={section.id}
to={buildHref(section.id)}
aria-current={activeSection === section.id ? "page" : undefined}
className={itemClassName(section)}
>
{itemInner(section)}
</Link>
) : (
<button
key={section.id}
type="button"
onClick={() => onSectionChange?.(section.id)}
aria-current={activeSection === section.id ? "page" : undefined}
className={itemClassName(section)}
>
{itemInner(section)}
</button>
)
)}
</nav>
</div>
</aside>
);
}