From a7b18a74548f4b8261675fdc954b74f9496929cc Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 1 Sep 2026 03:15:36 -0300 Subject: [PATCH] feat: add robotd slot filter --- src/components/BehaviorCatalog.tsx | 14 +++++++++++++- src/components/FilterBar.tsx | 17 ++++++++++++++++- src/lib/labels.ts | 18 +++++++++++++++++- 3 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src/components/BehaviorCatalog.tsx b/src/components/BehaviorCatalog.tsx index 5e75744..bdeb049 100644 --- a/src/components/BehaviorCatalog.tsx +++ b/src/components/BehaviorCatalog.tsx @@ -6,6 +6,7 @@ import { BehaviorCard } from "./BehaviorCard"; import type { Behavior } from "@registry/schema/behavior"; import { DuckMark } from "./DuckMark"; import { QuackButton } from "./QuackAction"; +import { formatRobotdSlot } from "@/lib/labels"; interface BehaviorCatalogProps { initialBehaviors: Behavior[]; @@ -16,10 +17,16 @@ export function BehaviorCatalog({ initialBehaviors }: BehaviorCatalogProps) { const [category, setCategory] = useState("all"); const [verification, setVerification] = useState("all"); const [accessory, setAccessory] = useState("all"); + const [slot, setSlot] = useState("all"); + + const slots = useMemo(() => Array.from(new Set(initialBehaviors.map((behavior) => behavior.compatibility.robotd_slot))) + .sort((a, b) => formatRobotdSlot(a).localeCompare(formatRobotdSlot(b))) + .map((id) => ({ id, label: formatRobotdSlot(id) })), [initialBehaviors]); const filteredBehaviors = useMemo(() => initialBehaviors.filter((behavior) => { if (category !== "all" && behavior.category !== category) return false; if (verification !== "all" && behavior.verification.status !== verification) return false; + if (slot !== "all" && behavior.compatibility.robotd_slot !== slot) return false; if (accessory === "none" && behavior.compatibility.accessories_required.length > 0) return false; if (accessory !== "all" && accessory !== "none" && !behavior.compatibility.accessories_required.includes(accessory)) return false; @@ -35,15 +42,17 @@ export function BehaviorCatalog({ initialBehaviors }: BehaviorCatalogProps) { behavior.sources.task_id || "", behavior.category, behavior.verification.status, + behavior.compatibility.robotd_slot, ...behavior.compatibility.accessories_required, ].some((value) => value.toLowerCase().includes(query)); - }), [accessory, category, initialBehaviors, search, verification]); + }), [accessory, category, initialBehaviors, search, slot, verification]); const resetFilters = () => { setSearch(""); setCategory("all"); setVerification("all"); setAccessory("all"); + setSlot("all"); }; return ( @@ -58,6 +67,9 @@ export function BehaviorCatalog({ initialBehaviors }: BehaviorCatalogProps) { setSelectedVerification={setVerification} selectedAccessory={accessory} setSelectedAccessory={setAccessory} + selectedSlot={slot} + setSelectedSlot={setSlot} + slots={slots} totalCount={initialBehaviors.length} filteredCount={filteredBehaviors.length} /> diff --git a/src/components/FilterBar.tsx b/src/components/FilterBar.tsx index f9cf1f7..cb72b4a 100644 --- a/src/components/FilterBar.tsx +++ b/src/components/FilterBar.tsx @@ -11,6 +11,9 @@ interface FilterBarProps { setSelectedVerification: (v: string) => void; selectedAccessory: string; setSelectedAccessory: (a: string) => void; + selectedSlot: string; + setSelectedSlot: (s: string) => void; + slots: Array<{ id: string; label: string }>; totalCount: number; filteredCount: number; } @@ -48,6 +51,9 @@ export function FilterBar({ setSelectedVerification, selectedAccessory, setSelectedAccessory, + selectedSlot, + setSelectedSlot, + slots, totalCount, filteredCount, }: FilterBarProps) { @@ -55,13 +61,15 @@ export function FilterBar({ search.trim() !== "" || selectedCategory !== "all" || selectedVerification !== "all" || - selectedAccessory !== "all"; + selectedAccessory !== "all" || + selectedSlot !== "all"; const clearAllFilters = () => { setSearch(""); setSelectedCategory("all"); setSelectedVerification("all"); setSelectedAccessory("all"); + setSelectedSlot("all"); }; return ( @@ -104,6 +112,13 @@ export function FilterBar({ {accessories.map((item) => )} +
{filteredCount} of {totalCount} {hasActiveFilters && } diff --git a/src/lib/labels.ts b/src/lib/labels.ts index 09f8658..d63bdad 100644 --- a/src/lib/labels.ts +++ b/src/lib/labels.ts @@ -1,4 +1,4 @@ -import type { BehaviorCategory } from "@registry/schema/behavior"; +import type { BehaviorCategory, RobotDSlot } from "@registry/schema/behavior"; const categoryLabels: Record = { locomotion: "Locomotion", @@ -9,6 +9,18 @@ const categoryLabels: Record = { experimental: "Experimental", }; +const robotdSlotLabels: Record = { + walk: "Walk", + stand: "Stand", + sitstand: "Sit ↔ stand", + roulade: "Roulade", + kick_left: "Kick left", + kick_right: "Kick right", + ground_pick: "Ground pick", + roller: "Roller mode", + custom: "Custom", +}; + export function formatCategory(category: BehaviorCategory) { return categoryLabels[category] ?? category; } @@ -21,3 +33,7 @@ export function formatAccessory(accessory: string) { return labels[accessory] ?? accessory.replaceAll("_", " "); } + +export function formatRobotdSlot(slot: RobotDSlot) { + return robotdSlotLabels[slot]; +}