feat(ui): Select primitive; restyle ProfileSelector on tokens

- New Select primitive in @trails-cool/ui: token-styled native <select>
  (appearance-none + overlaid chevron for a consistent closed control),
  sm/md sizes, sibling of Input. Unit-tested + added to /dev/ui.
- ProfileSelector uses Select (size sm) with token label; the profile
  label now hides on small screens. Completes the topbar's migration
  onto the design system.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Ullrich Schäfer 2026-07-16 00:12:23 +02:00
parent bf73feb75a
commit a56445daf3
No known key found for this signature in database
GPG key ID: A32FF691A0F752D9
5 changed files with 122 additions and 4 deletions

View file

@ -0,0 +1,52 @@
// @vitest-environment jsdom
import { describe, it, expect, afterEach, vi } from "vitest";
import { render, screen, cleanup, fireEvent } from "@testing-library/react";
import { Select } from "./Select.tsx";
afterEach(cleanup);
function options() {
return (
<>
<option value="fastbike">Fast bike</option>
<option value="trekking">Trekking</option>
</>
);
}
describe("Select", () => {
it("renders options and reflects the value", () => {
render(
<Select value="trekking" onChange={() => {}} aria-label="Profile">
{options()}
</Select>,
);
const select = screen.getByRole("combobox", { name: "Profile" });
expect((select as HTMLSelectElement).value).toBe("trekking");
expect(select.className).toContain("border-border");
});
it("fires onChange with the selected value", () => {
const onChange = vi.fn();
render(
<Select value="fastbike" onChange={onChange} aria-label="Profile">
{options()}
</Select>,
);
fireEvent.change(screen.getByRole("combobox", { name: "Profile" }), {
target: { value: "trekking" },
});
expect(onChange).toHaveBeenCalledTimes(1);
});
it("applies the compact size classes", () => {
render(
<Select size="sm" aria-label="Profile">
{options()}
</Select>,
);
expect(screen.getByRole("combobox", { name: "Profile" }).className).toContain(
"h-7",
);
});
});