Browse Source
Minimal pill-style tab bar for switching between primary and fallback model parameter views inside the InlineParameterPanel. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>handoff-20260429-1057
2 changed files with 113 additions and 0 deletions
@ -0,0 +1,46 @@ |
|||||
|
import React from "react"; |
||||
|
|
||||
|
interface SettingsTabBarProps { |
||||
|
activeTab: "primary" | "fallback"; |
||||
|
onTabChange: (tab: "primary" | "fallback") => void; |
||||
|
primaryLabel: string; |
||||
|
fallbackLabel: string; |
||||
|
} |
||||
|
|
||||
|
export function SettingsTabBar({ |
||||
|
activeTab, |
||||
|
onTabChange, |
||||
|
primaryLabel, |
||||
|
fallbackLabel, |
||||
|
}: SettingsTabBarProps) { |
||||
|
return ( |
||||
|
<div className="flex gap-1 mb-2"> |
||||
|
<button |
||||
|
type="button" |
||||
|
className={`text-[10px] px-2 py-0.5 rounded transition-colors ${ |
||||
|
activeTab === "primary" |
||||
|
? "bg-neutral-700 text-neutral-200" |
||||
|
: "bg-transparent text-neutral-500 hover:text-neutral-400" |
||||
|
}`}
|
||||
|
onClick={() => { |
||||
|
if (activeTab !== "primary") onTabChange("primary"); |
||||
|
}} |
||||
|
> |
||||
|
{primaryLabel} |
||||
|
</button> |
||||
|
<button |
||||
|
type="button" |
||||
|
className={`text-[10px] px-2 py-0.5 rounded transition-colors ${ |
||||
|
activeTab === "fallback" |
||||
|
? "bg-neutral-700 text-neutral-200" |
||||
|
: "bg-transparent text-neutral-500 hover:text-neutral-400" |
||||
|
}`}
|
||||
|
onClick={() => { |
||||
|
if (activeTab !== "fallback") onTabChange("fallback"); |
||||
|
}} |
||||
|
> |
||||
|
{fallbackLabel} |
||||
|
</button> |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
@ -0,0 +1,67 @@ |
|||||
|
import { describe, it, expect, vi } from "vitest"; |
||||
|
import { render, screen, fireEvent } from "@testing-library/react"; |
||||
|
import { SettingsTabBar } from "../SettingsTabBar"; |
||||
|
|
||||
|
describe("SettingsTabBar", () => { |
||||
|
it("renders both tab labels", () => { |
||||
|
render( |
||||
|
<SettingsTabBar |
||||
|
activeTab="primary" |
||||
|
onTabChange={vi.fn()} |
||||
|
primaryLabel="Nano Banana" |
||||
|
fallbackLabel="Flux Dev" |
||||
|
/> |
||||
|
); |
||||
|
|
||||
|
expect(screen.getByText("Nano Banana")).toBeInTheDocument(); |
||||
|
expect(screen.getByText("Flux Dev")).toBeInTheDocument(); |
||||
|
}); |
||||
|
|
||||
|
it("highlights active tab", () => { |
||||
|
render( |
||||
|
<SettingsTabBar |
||||
|
activeTab="primary" |
||||
|
onTabChange={vi.fn()} |
||||
|
primaryLabel="Nano Banana" |
||||
|
fallbackLabel="Flux Dev" |
||||
|
/> |
||||
|
); |
||||
|
|
||||
|
const primaryTab = screen.getByText("Nano Banana"); |
||||
|
const fallbackTab = screen.getByText("Flux Dev"); |
||||
|
|
||||
|
// Active tab should have bg-neutral-700 class
|
||||
|
expect(primaryTab.className).toContain("bg-neutral-700"); |
||||
|
expect(fallbackTab.className).not.toContain("bg-neutral-700"); |
||||
|
}); |
||||
|
|
||||
|
it("calls onTabChange when clicking inactive tab", () => { |
||||
|
const onTabChange = vi.fn(); |
||||
|
render( |
||||
|
<SettingsTabBar |
||||
|
activeTab="primary" |
||||
|
onTabChange={onTabChange} |
||||
|
primaryLabel="Nano Banana" |
||||
|
fallbackLabel="Flux Dev" |
||||
|
/> |
||||
|
); |
||||
|
|
||||
|
fireEvent.click(screen.getByText("Flux Dev")); |
||||
|
expect(onTabChange).toHaveBeenCalledWith("fallback"); |
||||
|
}); |
||||
|
|
||||
|
it("does not call onTabChange when clicking active tab", () => { |
||||
|
const onTabChange = vi.fn(); |
||||
|
render( |
||||
|
<SettingsTabBar |
||||
|
activeTab="primary" |
||||
|
onTabChange={onTabChange} |
||||
|
primaryLabel="Nano Banana" |
||||
|
fallbackLabel="Flux Dev" |
||||
|
/> |
||||
|
); |
||||
|
|
||||
|
fireEvent.click(screen.getByText("Nano Banana")); |
||||
|
expect(onTabChange).not.toHaveBeenCalled(); |
||||
|
}); |
||||
|
}); |
||||
Loading…
Reference in new issue