Board chips reserve future models; firmware uses searchable cards with detail pane and optional meta.json media/docs. Co-authored-by: Cursor <cursoragent@cursor.com>
74 lines
2.3 KiB
TypeScript
74 lines
2.3 KiB
TypeScript
"use client";
|
|
|
|
import { useTranslations } from "next-intl";
|
|
import { BOARDS } from "@/lib/boards";
|
|
import { cn } from "@/lib/utils";
|
|
import { Badge } from "@/components/ui/badge";
|
|
|
|
export function BoardSelector({
|
|
value,
|
|
onChange,
|
|
}: {
|
|
value: string;
|
|
onChange: (boardId: string) => void;
|
|
}) {
|
|
const t = useTranslations("boards");
|
|
|
|
return (
|
|
<div>
|
|
<div className="mb-3 flex items-baseline justify-between gap-3">
|
|
<h2 className="m-0 font-[family-name:var(--font-display)] text-[1.05rem] font-bold">
|
|
{t("title")}
|
|
</h2>
|
|
<p className="m-0 text-xs text-[var(--muted)]">{t("hint")}</p>
|
|
</div>
|
|
<div
|
|
className="grid grid-cols-2 gap-2 sm:grid-cols-4"
|
|
role="radiogroup"
|
|
aria-label={t("title")}
|
|
>
|
|
{BOARDS.map((board) => {
|
|
const selected = board.id === value;
|
|
const disabled = !board.available;
|
|
return (
|
|
<button
|
|
key={board.id}
|
|
type="button"
|
|
role="radio"
|
|
aria-checked={selected}
|
|
aria-disabled={disabled}
|
|
disabled={disabled}
|
|
title={disabled ? t("soon") : board.displayName}
|
|
onClick={() => {
|
|
if (!disabled) onChange(board.id);
|
|
}}
|
|
className={cn(
|
|
"relative flex flex-col items-start gap-1 rounded-[12px] border px-3 py-3 text-left transition-all",
|
|
disabled &&
|
|
"cursor-not-allowed opacity-40 grayscale-[0.4]",
|
|
!disabled &&
|
|
!selected &&
|
|
"border-[var(--line)] bg-[rgba(0,0,0,0.2)] hover:border-[rgba(240,197,10,0.35)]",
|
|
selected &&
|
|
"border-[var(--accent)] bg-[rgba(240,197,10,0.1)] shadow-[inset_0_0_0_1px_rgba(240,197,10,0.25)]"
|
|
)}
|
|
>
|
|
<span className="text-[0.8rem] font-semibold leading-tight">
|
|
{t(`items.${board.labelKey}`)}
|
|
</span>
|
|
<span className="text-[0.7rem] text-[var(--muted)]">
|
|
{board.chip}
|
|
</span>
|
|
{disabled && (
|
|
<Badge variant="muted" className="mt-1 absolute top-2 right-2">
|
|
{t("soon")}
|
|
</Badge>
|
|
)}
|
|
</button>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|