60 lines
1.5 KiB
TypeScript
60 lines
1.5 KiB
TypeScript
"use client";
|
||
|
||
import type { ReactNode } from "react";
|
||
|
||
export type AdminSegmentOption<T extends string = string> = {
|
||
key: T;
|
||
label: string;
|
||
icon?: ReactNode;
|
||
count?: number | string;
|
||
disabled?: boolean;
|
||
};
|
||
|
||
/** 分段筛选:可带 count / icon;选中态走 --nav-active-* */
|
||
export default function AdminSegmented<T extends string = string>({
|
||
options,
|
||
value,
|
||
onChange,
|
||
ariaLabel,
|
||
size = "md",
|
||
role = "tablist",
|
||
}: {
|
||
options: AdminSegmentOption<T>[];
|
||
value: T;
|
||
onChange: (key: T) => void;
|
||
ariaLabel: string;
|
||
size?: "md" | "sm";
|
||
role?: "tablist" | "group";
|
||
}) {
|
||
const useTabs = role === "tablist";
|
||
return (
|
||
<div
|
||
className="flex items-center gap-1.5 flex-wrap"
|
||
role={role}
|
||
aria-label={ariaLabel}
|
||
>
|
||
{options.map((opt) => {
|
||
const active = value === opt.key;
|
||
return (
|
||
<button
|
||
key={opt.key}
|
||
type="button"
|
||
role={useTabs ? "tab" : undefined}
|
||
aria-selected={useTabs ? active : undefined}
|
||
aria-pressed={!useTabs ? active : undefined}
|
||
disabled={opt.disabled}
|
||
onClick={() => {
|
||
if (!active) onChange(opt.key);
|
||
}}
|
||
className={`admin-seg ${size === "sm" ? "admin-seg-sm" : ""}`.trim()}
|
||
>
|
||
{opt.icon}
|
||
{opt.label}
|
||
{opt.count != null && <span className="admin-seg-count">{opt.count}</span>}
|
||
</button>
|
||
);
|
||
})}
|
||
</div>
|
||
);
|
||
}
|