Files
lotteryAdmin/src/components/admin/admin-subnav.tsx

129 lines
3.1 KiB
TypeScript

"use client";
import Link from "next/link";
import type { ReactNode } from "react";
import { cn } from "@/lib/utils";
export function adminSubnavItemClassName(active: boolean, disabled = false): string {
if (disabled) {
return "border-b-2 border-transparent px-4 py-3 text-sm font-medium text-muted-foreground/45 cursor-not-allowed";
}
return cn(
"relative -mb-px shrink-0 border-b-2 px-4 py-3 text-sm font-medium transition-colors",
active
? "border-primary text-primary"
: "border-transparent text-muted-foreground hover:border-border/80 hover:text-foreground",
);
}
type AdminSubnavProps = {
children: ReactNode;
className?: string;
/** Accessible name for the tab list */
"aria-label": string;
};
/** Underline-style horizontal tab bar used across admin page headers. */
export function AdminSubnav({ children, className, "aria-label": ariaLabel }: AdminSubnavProps) {
return (
<nav
aria-label={ariaLabel}
className={cn(
"flex w-full flex-wrap items-end gap-1 border-b border-border/60 px-1",
className,
)}
>
{children}
</nav>
);
}
type AdminSubnavLinkProps = {
href: string;
active: boolean;
children: ReactNode;
className?: string;
disabled?: boolean;
disabledTitle?: string;
};
export function AdminSubnavLink({
href,
active,
children,
className,
disabled = false,
disabledTitle,
}: AdminSubnavLinkProps) {
if (disabled) {
return (
<span className={cn(adminSubnavItemClassName(false, true), className)} title={disabledTitle}>
{children}
</span>
);
}
return (
<Link href={href} className={cn(adminSubnavItemClassName(active), className)}>
{children}
</Link>
);
}
type AdminSubnavButtonProps = {
active: boolean;
onClick: () => void;
children: ReactNode;
className?: string;
count?: number;
disabled?: boolean;
};
export function AdminSubnavButton({
active,
onClick,
children,
className,
count,
disabled = false,
}: AdminSubnavButtonProps) {
return (
<button
type="button"
disabled={disabled}
onClick={onClick}
className={cn(adminSubnavItemClassName(active, disabled), className)}
>
{children}
{count !== undefined ? (
<span
className={cn(
"ml-1.5 inline-flex min-w-[1.25rem] items-center justify-center rounded-full px-1.5 py-0.5 text-[10px] font-medium tabular-nums",
active ? "bg-primary/10 text-primary" : "bg-muted text-muted-foreground",
)}
>
{count}
</span>
) : null}
</button>
);
}
type AdminSubnavBarProps = {
children: ReactNode;
trailing?: ReactNode;
className?: string;
};
/** Wrapper for subnav plus optional trailing controls (site selector, back link, etc.). */
export function AdminSubnavBar({ children, trailing, className }: AdminSubnavBarProps) {
return (
<div className={cn("flex w-full flex-wrap items-end justify-between gap-3", className)}>
{children}
{trailing ? <div className="shrink-0 pb-1">{trailing}</div> : null}
</div>
);
}