Add multi-board selector and scalable firmware catalog UI.
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>
This commit is contained in:
73
components/board-selector.tsx
Normal file
73
components/board-selector.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
"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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user