fix(ui): center vertical toolbar dividers

The shadcn separator primitive ships `data-vertical:self-stretch` so a bare
vertical divider fills its row, but every call site overrides the height with
`h-5`. A definite cross size makes `align-self: stretch` behave as
`flex-start`, so the dividers rendered flush with the top of their flex line
instead of centered: 0px above and 18px below in the dashboard header, 0px
above and 12px below in the models table toolbar

Routes the three vertical dividers through a ToolbarSeparator that pairs the
fixed height with a same-variant `data-vertical:self-center`. Matching the
variant is what matters; tailwind-merge then drops the conflicting class
outright, whereas a plain `self-center` ties on specificity (the variant is
defined with `:where()`) and loses on utility order. The CLI-managed primitive
is left untouched
This commit is contained in:
Yuneng Jiang 2026-07-25 18:41:06 -07:00
parent 215f05588d
commit 086cbb2d85
No known key found for this signature in database
6 changed files with 71 additions and 5 deletions

View file

@ -200,6 +200,15 @@ describe("AllModelsTable", () => {
expect(screen.getByText("+2 more")).toBeInTheDocument();
});
it("renders the toolbar divider centered rather than stretched to the top of the row", () => {
const { container } = render(<AllModelsTable {...baseProps} />);
const separators = container.querySelectorAll('[data-slot="separator"][data-orientation="vertical"]');
expect(separators).toHaveLength(1);
expect(separators[0].className).not.toMatch(/self-stretch/);
expect(separators[0].className).toContain("data-vertical:self-center");
});
describe("pause / resume", () => {
it("renders the toggle on for an active DB model and off for a blocked one", () => {
const { rerender } = render(<AllModelsTable {...baseProps} />);

View file

@ -14,7 +14,7 @@ import {
import { SearchSelect } from "@/components/shared/SearchSelect";
import { Button } from "@/components/ui/button";
import { Select, SelectContent, SelectItem, SelectTrigger } from "@/components/ui/select";
import { Separator } from "@/components/ui/separator";
import { ToolbarSeparator } from "@/components/shared/ToolbarSeparator";
import { cn } from "@/lib/cva.config";
import {
@ -242,7 +242,7 @@ export function AllModelsTable({
</SelectContent>
</Select>
<Separator orientation="vertical" className="mx-0.5 h-5" />
<ToolbarSeparator className="mx-0.5" />
<Button
variant="outline"

View file

@ -54,4 +54,13 @@ describe("DashboardHeader breadcrumb", () => {
expect(screen.getByText("Logs")).toBeInTheDocument();
expect(screen.queryByText("Observability")).not.toBeInTheDocument();
});
it("renders the tools divider centered rather than stretched to the top of the row", () => {
const { container } = render(<DashboardHeader page="logs" />);
const separators = container.querySelectorAll('[data-slot="separator"][data-orientation="vertical"]');
expect(separators).toHaveLength(1);
expect(separators[0].className).not.toMatch(/self-stretch/);
expect(separators[0].className).toContain("data-vertical:self-center");
});
});

View file

@ -8,7 +8,7 @@ import {
BreadcrumbPage,
BreadcrumbSeparator,
} from "@/components/ui/breadcrumb";
import { Separator } from "@/components/ui/separator";
import { ToolbarSeparator } from "@/components/shared/ToolbarSeparator";
import { getBreadcrumb } from "@/components/leftnav";
import { BlogDropdown } from "@/components/Navbar/BlogDropdown/BlogDropdown";
import { CommunityEngagementButtons } from "@/components/Navbar/CommunityEngagementButtons/CommunityEngagementButtons";
@ -58,7 +58,7 @@ export function DashboardHeader({ page }: DashboardHeaderProps) {
{showWorkerSwitch && (
<>
<WorkerDropdown onWorkerSwitch={handleWorkerSwitch} />
<Separator orientation="vertical" className="mx-1.5 h-5" />
<ToolbarSeparator />
</>
)}
<Button
@ -72,7 +72,7 @@ export function DashboardHeader({ page }: DashboardHeaderProps) {
</Button>
<BlogDropdown />
{!hideCommunityLinks && <CommunityEngagementButtons />}
<Separator orientation="vertical" className="mx-1.5 h-5" />
<ToolbarSeparator />
<NotificationsBell />
</div>
</header>

View file

@ -0,0 +1,36 @@
import { describe, expect, it } from "vitest";
import { render } from "@testing-library/react";
import { ToolbarSeparator } from "./ToolbarSeparator";
function renderSeparator(className?: string): HTMLElement {
const { container } = render(<ToolbarSeparator className={className} />);
const separator = container.querySelector('[data-slot="separator"]');
if (!(separator instanceof HTMLElement)) {
throw new Error("ToolbarSeparator did not render a separator element");
}
return separator;
}
describe("ToolbarSeparator", () => {
it("drops the primitive's self-stretch so a fixed-height divider stays vertically centered", () => {
const separator = renderSeparator();
expect(separator.className).not.toMatch(/self-stretch/);
expect(separator.className).toContain("data-vertical:self-center");
});
it("stays vertical and keeps its fixed height", () => {
const separator = renderSeparator();
expect(separator).toHaveAttribute("data-orientation", "vertical");
expect(separator.className).toContain("h-5");
});
it("lets callers override spacing without resurrecting self-stretch", () => {
const separator = renderSeparator("mx-0.5");
expect(separator.className).toContain("mx-0.5");
expect(separator.className).not.toMatch(/mx-1\.5/);
expect(separator.className).not.toMatch(/self-stretch/);
});
});

View file

@ -0,0 +1,12 @@
"use client";
import { Separator } from "@/components/ui/separator";
import { cn } from "@/lib/cva.config";
interface ToolbarSeparatorProps {
className?: string;
}
export function ToolbarSeparator({ className }: ToolbarSeparatorProps) {
return <Separator orientation="vertical" className={cn("mx-1.5 h-5 data-vertical:self-center", className)} />;
}