Browse Source

feat: add SettingsTabBar component for primary/fallback settings tabs

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
shrimbly 3 months ago
parent
commit
a2b36bb1f3
  1. 46
      src/components/nodes/SettingsTabBar.tsx
  2. 67
      src/components/nodes/__tests__/SettingsTabBar.test.tsx

46
src/components/nodes/SettingsTabBar.tsx

@ -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>
);
}

67
src/components/nodes/__tests__/SettingsTabBar.test.tsx

@ -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…
Cancel
Save