From 27f867ff249704fb0cebd1160b7084ca32e4719b Mon Sep 17 00:00:00 2001 From: Ajax Davis Date: Wed, 26 Nov 2025 08:33:03 +1000 Subject: [PATCH] feat(design-system): complete blueprint aesthetic redesign with editorial typography MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace Geist fonts with Space Grotesk + Space Mono via next/font/google - Make dotted borders default for all card variants (default, elevated, outline) - Add blueprint grid backgrounds to homepage sections and playground - Create Section layout component with flexible semantic elements and spacing variants - Create GridContainer component with auto-fit/auto-fill responsive grid support - Add blueprint variants to Tabs and Button components (dotted borders) - Add editorial typography tokens (line-heights, letter-spacing) to Tailwind config - Add spacing tokens for airy editorial layouts - Add shadow utilities for blueprint aesthetic (shadow-blueprint, shadow-blueprint-hover) - Update 59 component tests for new variants and styling - All 429 tests passing, zero hardcoded colors, fully themeable 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- apps/web/src/app/globals.css | 45 ++- apps/web/src/app/layout.tsx | 22 +- apps/web/src/app/page.tsx | 12 +- apps/web/src/app/playground/page.tsx | 11 +- packages/config/tailwind/base.ts | 35 ++ packages/ui/package.json | 8 + packages/ui/src/Button/types.ts | 4 +- packages/ui/src/Button/variants.ts | 13 + packages/ui/src/Card/Card.test.tsx | 6 +- packages/ui/src/Card/types.ts | 2 +- packages/ui/src/Card/variants.ts | 13 +- packages/ui/src/CodeBlock/CodeBlock.test.tsx | 6 +- packages/ui/src/CodeBlock/variants.ts | 12 +- .../src/GridContainer/GridContainer.test.tsx | 317 ++++++++++++++++ .../ui/src/GridContainer/GridContainer.tsx | 62 ++++ packages/ui/src/GridContainer/types.ts | 41 +++ packages/ui/src/GridContainer/variants.ts | 118 ++++++ packages/ui/src/Header/Header.test.tsx | 8 +- packages/ui/src/Header/variants.ts | 8 +- .../ui/src/ProgressBar/ProgressBar.test.tsx | 20 +- packages/ui/src/ProgressBar/ProgressBar.tsx | 2 +- packages/ui/src/ProgressBar/variants.ts | 10 +- packages/ui/src/Section/Section.test.tsx | 345 ++++++++++++++++++ packages/ui/src/Section/Section.tsx | 67 ++++ packages/ui/src/Section/types.ts | 41 +++ packages/ui/src/Section/variants.ts | 49 +++ packages/ui/src/Tabs/Tabs.test.tsx | 16 +- packages/ui/src/Tabs/Tabs.tsx | 15 +- packages/ui/src/Tabs/types.ts | 6 + packages/ui/src/Tabs/variants.ts | 24 +- 30 files changed, 1265 insertions(+), 73 deletions(-) create mode 100644 packages/ui/src/GridContainer/GridContainer.test.tsx create mode 100644 packages/ui/src/GridContainer/GridContainer.tsx create mode 100644 packages/ui/src/GridContainer/types.ts create mode 100644 packages/ui/src/GridContainer/variants.ts create mode 100644 packages/ui/src/Section/Section.test.tsx create mode 100644 packages/ui/src/Section/Section.tsx create mode 100644 packages/ui/src/Section/types.ts create mode 100644 packages/ui/src/Section/variants.ts diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css index 4254162..2098b3a 100644 --- a/apps/web/src/app/globals.css +++ b/apps/web/src/app/globals.css @@ -66,9 +66,32 @@ --radius-xl: 12px; --radius: var(--radius-md); + /* Spacing - Editorial scale for airy layouts */ + --spacing-section: 6rem; /* 96px - Between major sections */ + --spacing-component: 3rem; /* 48px - Between components */ + --spacing-element: 1.5rem; /* 24px - Between related elements */ + --spacing-tight: 0.75rem; /* 12px - Tight groupings */ + --spacing-comfortable: 2rem; /* 32px - Comfortable breathing room */ + /* Typography */ - --font-sans: 'Geist', -apple-system, BlinkMacSystemFont, system-ui, sans-serif; - --font-mono: 'Geist Mono', 'SF Mono', Consolas, monospace; + --font-sans: var(--font-sans), -apple-system, BlinkMacSystemFont, system-ui, sans-serif; + --font-mono: var(--font-mono), 'SF Mono', Consolas, monospace; + + /* Editorial Line Heights - Airy, readable */ + --leading-tight: 1.25; + --leading-snug: 1.375; + --leading-normal: 1.5; + --leading-relaxed: 1.625; + --leading-loose: 1.75; + --leading-editorial: 1.8; /* Extra generous for body copy */ + + /* Letter Spacing - Technical precision */ + --tracking-tighter: -0.05em; + --tracking-tight: -0.025em; + --tracking-normal: 0; + --tracking-wide: 0.025em; + --tracking-wider: 0.05em; + --tracking-widest: 0.1em; } /* Dark mode (opt-in) - Vercel/Cursor/Perplexity aesthetic */ @@ -199,6 +222,11 @@ border-style: dashed; } + .border-dotted-2 { + border-width: 2px; + border-style: dotted; + } + /* Directional dotted borders */ .border-t-dotted { border-top-width: 1px; @@ -220,6 +248,19 @@ border-right-style: dotted; } + /* Shadow utilities - Blueprint aesthetic */ + .shadow-blueprint { + box-shadow: + 0 1px 2px 0 rgb(0 0 0 / 0.03), + 0 0 0 1px rgb(0 0 0 / 0.02); + } + + .shadow-blueprint-hover { + box-shadow: + 0 2px 4px 0 rgb(0 0 0 / 0.05), + 0 0 0 1px rgb(0 0 0 / 0.03); + } + /* Transition presets */ .transition-base { transition-property: background-color, border-color, color, fill, stroke; diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx index 15c805b..0cf440a 100644 --- a/apps/web/src/app/layout.tsx +++ b/apps/web/src/app/layout.tsx @@ -1,7 +1,21 @@ import type { Metadata } from "next"; +import { Space_Grotesk, Space_Mono } from "next/font/google"; import { ThemeProvider } from "../components/providers/ThemeProvider"; import "./globals.css"; +const spaceGrotesk = Space_Grotesk({ + subsets: ["latin"], + variable: "--font-sans", + display: "swap", +}); + +const spaceMono = Space_Mono({ + subsets: ["latin"], + weight: ["400", "700"], + variable: "--font-mono", + display: "swap", +}); + export const metadata: Metadata = { title: "TPMJS - Tool Package Manager for AI Agents", description: @@ -14,8 +28,12 @@ export default function RootLayout({ children: React.ReactNode; }): React.ReactElement { return ( - - + + {/* Hero Section */} -
-
+
+

@@ -97,7 +97,7 @@ export default function HomePage(): React.ReactElement {

{/* Featured Tools Section */} -
+

Featured Tools

@@ -164,14 +164,14 @@ export default function HomePage(): React.ReactElement {
{/* Statistics Section */} -
+

Platform Statistics

- {statistics.map((stat, index) => ( - + {statistics.map((stat) => ( + +
{/* Header */}
-
+
+
{/* Page Title */} @@ -166,7 +167,7 @@ export default function PlaygroundPage() {

Available Icons

- {[ + {([ "check", "x", "chevronDown", @@ -175,12 +176,12 @@ export default function PlaygroundPage() { "github", "sun", "moon", - ].map((icon) => ( + ] as const).map((icon) => (
- + {icon}
))} diff --git a/packages/config/tailwind/base.ts b/packages/config/tailwind/base.ts index cd1f673..2aed1a2 100644 --- a/packages/config/tailwind/base.ts +++ b/packages/config/tailwind/base.ts @@ -2,6 +2,23 @@ import type { Config } from "tailwindcss"; export default { content: [], + safelist: [ + // Custom border utilities + "border-dotted-1", + "border-dotted-2", + "border-dashed-1", + "border-t-dotted", + "border-b-dotted", + "border-l-dotted", + "border-r-dotted", + // Custom shadow utilities + "shadow-blueprint", + "shadow-blueprint-hover", + // Grid backgrounds + "dotted-grid-background", + "blueprint-background", + "grid-background", + ], theme: { extend: { colors: { @@ -94,6 +111,24 @@ export default { mono: "var(--font-mono)", }, + + lineHeight: { + tight: "var(--leading-tight)", + snug: "var(--leading-snug)", + normal: "var(--leading-normal)", + relaxed: "var(--leading-relaxed)", + loose: "var(--leading-loose)", + editorial: "var(--leading-editorial)", + }, + + letterSpacing: { + tighter: "var(--tracking-tighter)", + tight: "var(--tracking-tight)", + normal: "var(--tracking-normal)", + wide: "var(--tracking-wide)", + wider: "var(--tracking-wider)", + widest: "var(--tracking-widest)", + }, keyframes: { "slide-up": { from: { transform: "translateY(10px)", opacity: "0" }, diff --git a/packages/ui/package.json b/packages/ui/package.json index 686ad75..55cfe96 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -46,6 +46,14 @@ "./Header/Header": { "types": "./dist/Header/Header.d.ts", "default": "./dist/Header/Header.js" + }, + "./Section/Section": { + "types": "./dist/Section/Section.d.ts", + "default": "./dist/Section/Section.js" + }, + "./GridContainer/GridContainer": { + "types": "./dist/GridContainer/GridContainer.d.ts", + "default": "./dist/GridContainer/GridContainer.js" } }, "files": [ diff --git a/packages/ui/src/Button/types.ts b/packages/ui/src/Button/types.ts index f26aef8..79da1bf 100644 --- a/packages/ui/src/Button/types.ts +++ b/packages/ui/src/Button/types.ts @@ -12,9 +12,11 @@ export interface ButtonProps extends ButtonHTMLAttributes { | "default" | "destructive" | "outline" + | "outline-dotted" | "secondary" | "ghost" - | "link"; + | "link" + | "blueprint"; /** * Size of the button diff --git a/packages/ui/src/Button/variants.ts b/packages/ui/src/Button/variants.ts index 66454f3..3fa6a7e 100644 --- a/packages/ui/src/Button/variants.ts +++ b/packages/ui/src/Button/variants.ts @@ -41,6 +41,18 @@ export const buttonVariants = createVariants({ "active:bg-accent/80", ].join(" "), + "outline-dotted": [ + "border border-dotted border-border bg-transparent", + "hover:bg-accent hover:text-accent-foreground hover:border-border-strong", + "active:bg-accent/80", + ].join(" "), + + blueprint: [ + "border border-dotted border-border bg-card", + "hover:shadow-blueprint-hover", + "active:bg-card/90", + ].join(" "), + secondary: [ "bg-secondary text-secondary-foreground", "hover:bg-secondary/80", @@ -48,6 +60,7 @@ export const buttonVariants = createVariants({ ].join(" "), ghost: [ + "text-foreground", "hover:bg-accent hover:text-accent-foreground", "active:bg-accent/80", ].join(" "), diff --git a/packages/ui/src/Card/Card.test.tsx b/packages/ui/src/Card/Card.test.tsx index bac358a..599d791 100644 --- a/packages/ui/src/Card/Card.test.tsx +++ b/packages/ui/src/Card/Card.test.tsx @@ -49,7 +49,7 @@ describe("Card", () => { , ); const card = screen.getByTestId("card"); - expect(card.className).toContain("border"); + expect(card.className).toContain("border-dotted"); expect(card.className).toContain("bg-card"); expect(card.className).toContain("shadow-sm"); }); @@ -61,6 +61,7 @@ describe("Card", () => { , ); const card = screen.getByTestId("card"); + expect(card.className).toContain("border-dotted"); expect(card.className).toContain("bg-surface-elevated"); expect(card.className).toContain("shadow-md"); }); @@ -72,7 +73,8 @@ describe("Card", () => { , ); const card = screen.getByTestId("card"); - expect(card.className).toContain("border-2"); + expect(card.className).toContain("border-dotted"); + expect(card.className).toContain("border-2"); expect(card.className).toContain("bg-transparent"); }); diff --git a/packages/ui/src/Card/types.ts b/packages/ui/src/Card/types.ts index d76dce2..d5d41aa 100644 --- a/packages/ui/src/Card/types.ts +++ b/packages/ui/src/Card/types.ts @@ -8,7 +8,7 @@ export interface CardProps extends HTMLAttributes { * Visual variant of the card * @default 'default' */ - variant?: "default" | "elevated" | "outline" | "ghost"; + variant?: "default" | "elevated" | "outline" | "blueprint" | "ghost"; /** * Padding size for the card diff --git a/packages/ui/src/Card/variants.ts b/packages/ui/src/Card/variants.ts index 7207933..115e98b 100644 --- a/packages/ui/src/Card/variants.ts +++ b/packages/ui/src/Card/variants.ts @@ -17,22 +17,29 @@ export const cardVariants = createVariants({ variants: { variant: { default: [ - "border border-border", + "border border-dotted border-border", "bg-card text-card-foreground", "shadow-sm", ].join(" "), elevated: [ - "border border-border", + "border border-dotted border-border", "bg-surface-elevated text-card-foreground", "shadow-md", ].join(" "), outline: [ - "border-2 border-border", + "border-2 border-dotted border-border", "bg-transparent text-foreground", ].join(" "), + blueprint: [ + "border border-dotted border-border", + "bg-card text-card-foreground", + "shadow-blueprint", + "hover:shadow-blueprint-hover", + ].join(" "), + ghost: ["bg-transparent text-foreground"].join(" "), }, diff --git a/packages/ui/src/CodeBlock/CodeBlock.test.tsx b/packages/ui/src/CodeBlock/CodeBlock.test.tsx index 21f4da4..e2697ee 100644 --- a/packages/ui/src/CodeBlock/CodeBlock.test.tsx +++ b/packages/ui/src/CodeBlock/CodeBlock.test.tsx @@ -276,7 +276,7 @@ describe("CodeBlock", () => { ); const codeblock = screen.getByTestId("codeblock"); expect(codeblock.className).toContain("custom-class"); - expect(codeblock.className).toContain("bg-zinc-900"); + expect(codeblock.className).toContain("bg-background"); expect(codeblock.className).toContain("border"); }); }); @@ -302,7 +302,7 @@ describe("CodeBlock", () => { render(); const codeblock = screen.getByTestId("codeblock"); expect(codeblock.className).toContain("relative"); - expect(codeblock.className).toContain("bg-zinc-900"); + expect(codeblock.className).toContain("bg-background"); expect(codeblock.className).toContain("border"); expect(codeblock.className).toContain("overflow-hidden"); }); @@ -313,7 +313,7 @@ describe("CodeBlock", () => { const code = codeblock.querySelector("code"); expect(code?.className).toContain("block"); expect(code?.className).toContain("font-mono"); - expect(code?.className).toContain("text-zinc-300"); + expect(code?.className).toContain("text-foreground-secondary"); expect(code?.className).toContain("overflow-x-auto"); expect(code?.className).toContain("whitespace-pre"); }); diff --git a/packages/ui/src/CodeBlock/variants.ts b/packages/ui/src/CodeBlock/variants.ts index 149d967..c45166c 100644 --- a/packages/ui/src/CodeBlock/variants.ts +++ b/packages/ui/src/CodeBlock/variants.ts @@ -8,9 +8,9 @@ export const codeBlockContainerVariants = createVariants({ // Layout "relative", // Background - "bg-zinc-900", + "bg-background", // Border - "border border-zinc-800 rounded-lg", + "border border-border rounded-lg", // Overflow "overflow-hidden", ].join(" "), @@ -32,7 +32,7 @@ export const codeBlockCodeVariants = createVariants({ // Font "font-mono", // Color - "text-zinc-300", + "text-foreground-secondary", // Overflow "overflow-x-auto", // Whitespace @@ -66,11 +66,11 @@ export const codeBlockCopyButtonVariants = createVariants({ // Size "w-8 h-8", // Background - "bg-zinc-800 hover:bg-zinc-700", + "bg-surface-elevated hover:bg-accent", // Border - "border border-zinc-700 rounded", + "border border-border rounded", // Color - "text-zinc-400 hover:text-zinc-100", + "text-foreground-secondary hover:text-foreground", // Cursor "cursor-pointer", // Transition diff --git a/packages/ui/src/GridContainer/GridContainer.test.tsx b/packages/ui/src/GridContainer/GridContainer.test.tsx new file mode 100644 index 0000000..df79d4e --- /dev/null +++ b/packages/ui/src/GridContainer/GridContainer.test.tsx @@ -0,0 +1,317 @@ +import { render, screen } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; +import { GridContainer } from "./GridContainer"; + +describe("GridContainer", () => { + describe("Rendering", () => { + it("renders a div element", () => { + render(Content); + const grid = screen.getByTestId("grid"); + expect(grid).toBeInTheDocument(); + expect(grid.tagName).toBe("DIV"); + }); + + it("renders content", () => { + render(Test content); + expect(screen.getByText("Test content")).toBeInTheDocument(); + }); + + it("renders multiple children", () => { + render( + +
Child 1
+
Child 2
+
Child 3
+
, + ); + expect(screen.getByText("Child 1")).toBeInTheDocument(); + expect(screen.getByText("Child 2")).toBeInTheDocument(); + expect(screen.getByText("Child 3")).toBeInTheDocument(); + }); + }); + + describe("Column Variants", () => { + it("applies auto columns by default", () => { + render(Content); + const grid = screen.getByTestId("grid"); + expect(grid.className).toContain("grid-cols-[repeat(auto-fit"); + }); + + it("applies single column", () => { + render( + + Content + , + ); + const grid = screen.getByTestId("grid"); + expect(grid.className).toContain("grid-cols-1"); + }); + + it("applies responsive 2 columns", () => { + render( + + Content + , + ); + const grid = screen.getByTestId("grid"); + expect(grid.className).toContain("grid-cols-1"); + expect(grid.className).toContain("md:grid-cols-2"); + }); + + it("applies responsive 3 columns", () => { + render( + + Content + , + ); + const grid = screen.getByTestId("grid"); + expect(grid.className).toContain("lg:grid-cols-3"); + }); + + it("applies responsive 4 columns", () => { + render( + + Content + , + ); + const grid = screen.getByTestId("grid"); + expect(grid.className).toContain("lg:grid-cols-4"); + }); + }); + + describe("Gap Variants", () => { + it("applies no gap", () => { + render( + + Content + , + ); + const grid = screen.getByTestId("grid"); + expect(grid.className).toContain("gap-0"); + }); + + it("applies small gap", () => { + render( + + Content + , + ); + const grid = screen.getByTestId("grid"); + expect(grid.className).toContain("gap-4"); + }); + + it("applies medium gap by default", () => { + render(Content); + const grid = screen.getByTestId("grid"); + expect(grid.className).toContain("gap-6"); + }); + + it("applies large gap", () => { + render( + + Content + , + ); + const grid = screen.getByTestId("grid"); + expect(grid.className).toContain("gap-8"); + }); + + it("applies extra large gap", () => { + render( + + Content + , + ); + const grid = screen.getByTestId("grid"); + expect(grid.className).toContain("gap-12"); + }); + }); + + describe("Responsive Behavior", () => { + it("uses responsive by default", () => { + render( + + Content + , + ); + const grid = screen.getByTestId("grid"); + expect(grid.className).toContain("md:grid-cols"); + }); + + it("applies fixed columns when responsive is false", () => { + render( + + Content + , + ); + const grid = screen.getByTestId("grid"); + // When responsive="fixed", compound variant adds grid-cols-3 + // Base variant still adds responsive classes, but compound variant takes precedence + expect(grid.className).toContain("grid-cols-3"); + }); + }); + + describe("Alignment", () => { + it("applies start alignment", () => { + render( + + Content + , + ); + const grid = screen.getByTestId("grid"); + expect(grid.className).toContain("items-start"); + }); + + it("applies center alignment", () => { + render( + + Content + , + ); + const grid = screen.getByTestId("grid"); + expect(grid.className).toContain("items-center"); + }); + + it("applies end alignment", () => { + render( + + Content + , + ); + const grid = screen.getByTestId("grid"); + expect(grid.className).toContain("items-end"); + }); + + it("applies stretch alignment by default", () => { + render(Content); + const grid = screen.getByTestId("grid"); + expect(grid.className).toContain("items-stretch"); + }); + }); + + describe("Justification", () => { + it("applies start justify by default", () => { + render(Content); + const grid = screen.getByTestId("grid"); + expect(grid.className).toContain("justify-items-start"); + }); + + it("applies center justify", () => { + render( + + Content + , + ); + const grid = screen.getByTestId("grid"); + expect(grid.className).toContain("justify-items-center"); + }); + + it("applies end justify", () => { + render( + + Content + , + ); + const grid = screen.getByTestId("grid"); + expect(grid.className).toContain("justify-items-end"); + }); + }); + + describe("HTML Attributes", () => { + it("passes through id attribute", () => { + render( + + Content + , + ); + const grid = screen.getByTestId("grid"); + expect(grid).toHaveAttribute("id", "grid-id"); + }); + + it("passes through data attributes", () => { + render( + + Content + , + ); + const grid = screen.getByTestId("grid"); + expect(grid).toHaveAttribute("data-custom", "test"); + }); + }); + + describe("Custom className", () => { + it("merges custom className with variant classes", () => { + render( + + Content + , + ); + const grid = screen.getByTestId("grid"); + expect(grid.className).toContain("custom-class"); + expect(grid.className).toContain("grid"); + expect(grid.className).toContain("w-full"); + }); + }); + + describe("Ref Forwarding", () => { + it("forwards ref to div element", () => { + let ref: HTMLDivElement | null = null; + render( + { + ref = el; + }} + > + Content + , + ); + expect(ref).toBeInstanceOf(HTMLDivElement); + }); + }); + + describe("Base Classes", () => { + it("always includes base classes", () => { + render(Content); + const grid = screen.getByTestId("grid"); + expect(grid.className).toContain("grid"); + expect(grid.className).toContain("w-full"); + }); + }); + + describe("Compound Scenarios", () => { + it("works with multiple variants combined", () => { + render( + + Content + , + ); + const grid = screen.getByTestId("grid"); + expect(grid.className).toContain("lg:grid-cols-4"); + expect(grid.className).toContain("gap-8"); + expect(grid.className).toContain("items-center"); + expect(grid.className).toContain("justify-items-center"); + }); + + it("works with fixed columns and custom className", () => { + render( + + Content + , + ); + const grid = screen.getByTestId("grid"); + expect(grid.className).toContain("grid-cols-3"); + expect(grid.className).toContain("my-custom-grid"); + }); + }); +}); diff --git a/packages/ui/src/GridContainer/GridContainer.tsx b/packages/ui/src/GridContainer/GridContainer.tsx new file mode 100644 index 0000000..5ebafd6 --- /dev/null +++ b/packages/ui/src/GridContainer/GridContainer.tsx @@ -0,0 +1,62 @@ +import { cn } from "@tpmjs/utils/cn"; +import { forwardRef } from "react"; +import type { GridContainerProps } from "./types"; +import { gridContainerVariants } from "./variants"; + +/** + * GridContainer component + * + * Responsive grid layout container with flexible column and gap options. + * Built with React and JSX for blueprint/editorial layouts. + * + * @example + * ```tsx + * import { GridContainer } from '@tpmjs/ui/GridContainer/GridContainer'; + * + * function MyComponent() { + * return ( + * + *
Item 1
+ *
Item 2
+ *
Item 3
+ *
+ * ); + * } + * ``` + */ +export const GridContainer = forwardRef( + ( + { + className, + columns = "auto", + gap = "md", + responsive = "responsive", + align = "stretch", + justify = "start", + children, + ...props + }, + ref, + ) => { + return ( +
+ {children} +
+ ); + }, +); + +GridContainer.displayName = "GridContainer"; diff --git a/packages/ui/src/GridContainer/types.ts b/packages/ui/src/GridContainer/types.ts new file mode 100644 index 0000000..01a22a6 --- /dev/null +++ b/packages/ui/src/GridContainer/types.ts @@ -0,0 +1,41 @@ +import type { HTMLAttributes } from "react"; + +/** + * GridContainer component props + */ +export interface GridContainerProps extends HTMLAttributes { + /** + * Number of columns in the grid + * @default 'auto' + */ + columns?: "auto" | 1 | 2 | 3 | 4 | 5 | 6 | 12; + + /** + * Gap between grid items + * @default 'md' + */ + gap?: "none" | "sm" | "md" | "lg" | "xl"; + + /** + * Responsive breakpoint behavior + * @default 'responsive' + */ + responsive?: "responsive" | "fixed"; + + /** + * Align items vertically + * @default 'stretch' + */ + align?: "start" | "center" | "end" | "stretch"; + + /** + * Justify items horizontally + * @default 'start' + */ + justify?: "start" | "center" | "end" | "between" | "around" | "evenly"; +} + +/** + * GridContainer ref type + */ +export type GridContainerRef = HTMLDivElement; diff --git a/packages/ui/src/GridContainer/variants.ts b/packages/ui/src/GridContainer/variants.ts new file mode 100644 index 0000000..f1836c9 --- /dev/null +++ b/packages/ui/src/GridContainer/variants.ts @@ -0,0 +1,118 @@ +import { createVariants } from "../system/variants"; + +/** + * GridContainer variant definitions + */ +export const gridContainerVariants = createVariants({ + base: ["grid w-full"].join(" "), + + variants: { + columns: { + auto: "", + 1: "grid-cols-1", + 2: "grid-cols-1 md:grid-cols-2", + 3: "grid-cols-1 md:grid-cols-2 lg:grid-cols-3", + 4: "grid-cols-1 md:grid-cols-2 lg:grid-cols-4", + 5: "grid-cols-2 md:grid-cols-3 lg:grid-cols-5", + 6: "grid-cols-2 md:grid-cols-3 lg:grid-cols-6", + 12: "grid-cols-4 md:grid-cols-6 lg:grid-cols-12", + }, + + gap: { + none: "gap-0", + sm: "gap-4", + md: "gap-6", + lg: "gap-8", + xl: "gap-12", + }, + + responsive: { + responsive: "", + fixed: "", + }, + + align: { + start: "items-start", + center: "items-center", + end: "items-end", + stretch: "items-stretch", + }, + + justify: { + start: "justify-items-start", + center: "justify-items-center", + end: "justify-items-end", + between: "justify-items-between", + around: "justify-items-around", + evenly: "justify-items-evenly", + }, + }, + + compoundVariants: [ + { + conditions: { + columns: "auto", + responsive: "responsive", + }, + className: "grid-cols-[repeat(auto-fit,minmax(250px,1fr))]", + }, + { + conditions: { + columns: "auto", + responsive: "fixed", + }, + className: "grid-cols-[repeat(auto-fill,minmax(250px,1fr))]", + }, + // Fixed columns don't use responsive breakpoints + { + conditions: { + columns: 2, + responsive: "fixed", + }, + className: "grid-cols-2", + }, + { + conditions: { + columns: 3, + responsive: "fixed", + }, + className: "grid-cols-3", + }, + { + conditions: { + columns: 4, + responsive: "fixed", + }, + className: "grid-cols-4", + }, + { + conditions: { + columns: 5, + responsive: "fixed", + }, + className: "grid-cols-5", + }, + { + conditions: { + columns: 6, + responsive: "fixed", + }, + className: "grid-cols-6", + }, + { + conditions: { + columns: 12, + responsive: "fixed", + }, + className: "grid-cols-12", + }, + ], + + defaultVariants: { + columns: "auto", + gap: "md", + responsive: "responsive", + align: "stretch", + justify: "start", + }, +}); diff --git a/packages/ui/src/Header/Header.test.tsx b/packages/ui/src/Header/Header.test.tsx index 516e675..0ffbe73 100644 --- a/packages/ui/src/Header/Header.test.tsx +++ b/packages/ui/src/Header/Header.test.tsx @@ -39,7 +39,7 @@ describe("Header", () => { expect(titleContainer.className).toContain("flex"); expect(titleContainer.className).toContain("items-center"); expect(titleContainer.className).toContain("font-semibold"); - expect(titleContainer.className).toContain("text-zinc-100"); + expect(titleContainer.className).toContain("text-foreground"); }); it("renders ReactNode as title", () => { @@ -240,7 +240,7 @@ describe("Header", () => { const header = screen.getByTestId("header"); expect(header.className).toContain("custom-class"); expect(header.className).toContain("flex"); - expect(header.className).toContain("bg-black"); + expect(header.className).toContain("bg-surface"); }); }); @@ -267,9 +267,9 @@ describe("Header", () => { expect(header.className).toContain("items-center"); expect(header.className).toContain("justify-between"); expect(header.className).toContain("w-full"); - expect(header.className).toContain("bg-black"); + expect(header.className).toContain("bg-surface"); expect(header.className).toContain("border-b"); - expect(header.className).toContain("border-zinc-800"); + expect(header.className).toContain("border-border"); }); }); diff --git a/packages/ui/src/Header/variants.ts b/packages/ui/src/Header/variants.ts index 5f2915f..4f0dcdf 100644 --- a/packages/ui/src/Header/variants.ts +++ b/packages/ui/src/Header/variants.ts @@ -10,9 +10,9 @@ export const headerVariants = createVariants({ // Width "w-full", // Background - "bg-black", + "bg-surface", // Border - "border-b border-zinc-800", + "border-b border-border", ].join(" "), variants: { @@ -31,7 +31,7 @@ export const headerVariants = createVariants({ defaultVariants: { size: "md", - sticky: false, + sticky: "false", }, }); @@ -45,7 +45,7 @@ export const headerTitleVariants = createVariants({ // Font "font-semibold", // Color - "text-zinc-100", + "text-foreground", ].join(" "), variants: { diff --git a/packages/ui/src/ProgressBar/ProgressBar.test.tsx b/packages/ui/src/ProgressBar/ProgressBar.test.tsx index 8108283..b52fc1a 100644 --- a/packages/ui/src/ProgressBar/ProgressBar.test.tsx +++ b/packages/ui/src/ProgressBar/ProgressBar.test.tsx @@ -108,7 +108,7 @@ describe("ProgressBar", () => { ); const progress = screen.getByTestId("progress"); const fill = progress.querySelector("div"); - expect(fill?.className).toContain("bg-blue-500"); + expect(fill?.className).toContain("bg-primary"); }); it("applies success variant", () => { @@ -117,7 +117,7 @@ describe("ProgressBar", () => { ); const progress = screen.getByTestId("progress"); const fill = progress.querySelector("div"); - expect(fill?.className).toContain("bg-green-500"); + expect(fill?.className).toContain("bg-success"); }); it("applies warning variant", () => { @@ -126,7 +126,7 @@ describe("ProgressBar", () => { ); const progress = screen.getByTestId("progress"); const fill = progress.querySelector("div"); - expect(fill?.className).toContain("bg-yellow-500"); + expect(fill?.className).toContain("bg-warning"); }); it("applies danger variant", () => { @@ -135,14 +135,14 @@ describe("ProgressBar", () => { ); const progress = screen.getByTestId("progress"); const fill = progress.querySelector("div"); - expect(fill?.className).toContain("bg-red-500"); + expect(fill?.className).toContain("bg-error"); }); it("uses primary variant by default", () => { render(); const progress = screen.getByTestId("progress"); const fill = progress.querySelector("div"); - expect(fill?.className).toContain("bg-blue-500"); + expect(fill?.className).toContain("bg-primary"); }); }); @@ -176,7 +176,7 @@ describe("ProgressBar", () => { render(); const label = screen.getByText("50%"); expect(label.className).toContain("text-sm"); - expect(label.className).toContain("text-zinc-400"); + expect(label.className).toContain("text-foreground-secondary"); expect(label.className).toContain("tabular-nums"); }); }); @@ -210,7 +210,7 @@ describe("ProgressBar", () => { ); const progress = screen.getByTestId("progress"); expect(progress.className).toContain("custom-class"); - expect(progress.className).toContain("bg-zinc-800"); + expect(progress.className).toContain("bg-surface"); expect(progress.className).toContain("rounded"); }); }); @@ -237,7 +237,7 @@ describe("ProgressBar", () => { const progress = screen.getByTestId("progress"); expect(progress.className).toContain("relative"); expect(progress.className).toContain("overflow-hidden"); - expect(progress.className).toContain("bg-zinc-800"); + expect(progress.className).toContain("bg-surface"); expect(progress.className).toContain("rounded"); }); @@ -264,7 +264,7 @@ describe("ProgressBar", () => { const progress = screen.getByTestId("progress"); expect(progress.className).toContain("h-3"); const fill = progress.querySelector("div"); - expect(fill?.className).toContain("bg-green-500"); + expect(fill?.className).toContain("bg-success"); expect(fill).toHaveStyle({ width: "80%" }); }); @@ -283,7 +283,7 @@ describe("ProgressBar", () => { const track = wrapper.querySelector('[role="progressbar"]'); expect(track?.className).toContain("my-custom-class"); const fill = track?.querySelector("div"); - expect(fill?.className).toContain("bg-yellow-500"); + expect(fill?.className).toContain("bg-warning"); expect(screen.getByText("45%")).toBeInTheDocument(); }); }); diff --git a/packages/ui/src/ProgressBar/ProgressBar.tsx b/packages/ui/src/ProgressBar/ProgressBar.tsx index 6328841..2cb77bc 100644 --- a/packages/ui/src/ProgressBar/ProgressBar.tsx +++ b/packages/ui/src/ProgressBar/ProgressBar.tsx @@ -93,7 +93,7 @@ export const ProgressBar = forwardRef( }} />
- + {Math.round(clampedValue)}%
diff --git a/packages/ui/src/ProgressBar/variants.ts b/packages/ui/src/ProgressBar/variants.ts index 55eda3b..e2a5f16 100644 --- a/packages/ui/src/ProgressBar/variants.ts +++ b/packages/ui/src/ProgressBar/variants.ts @@ -8,7 +8,7 @@ export const progressBarTrackVariants = createVariants({ // Layout "relative overflow-hidden", // Background - "bg-zinc-800", + "bg-surface", // Border "rounded", ].join(" "), @@ -43,10 +43,10 @@ export const progressBarFillVariants = createVariants({ variants: { variant: { - primary: "bg-blue-500", - success: "bg-green-500", - warning: "bg-yellow-500", - danger: "bg-red-500", + primary: "bg-primary", + success: "bg-success", + warning: "bg-warning", + danger: "bg-error", }, }, diff --git a/packages/ui/src/Section/Section.test.tsx b/packages/ui/src/Section/Section.test.tsx new file mode 100644 index 0000000..bbddb5f --- /dev/null +++ b/packages/ui/src/Section/Section.test.tsx @@ -0,0 +1,345 @@ +import { render, screen } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; +import { Section } from "./Section"; + +describe("Section", () => { + describe("Rendering", () => { + it("renders a section element by default", () => { + render(
Content
); + const section = screen.getByTestId("section"); + expect(section).toBeInTheDocument(); + expect(section.tagName).toBe("SECTION"); + }); + + it("renders content", () => { + render(
Test content
); + expect(screen.getByText("Test content")).toBeInTheDocument(); + }); + + it("renders as different semantic elements", () => { + const { rerender } = render( +
+ Content +
, + ); + expect(screen.getByTestId("element").tagName).toBe("ARTICLE"); + + rerender( +
+ Content +
, + ); + expect(screen.getByTestId("element").tagName).toBe("ASIDE"); + + rerender( +
+ Content +
, + ); + expect(screen.getByTestId("element").tagName).toBe("NAV"); + + rerender( +
+ Content +
, + ); + expect(screen.getByTestId("element").tagName).toBe("DIV"); + }); + }); + + describe("Spacing Variants", () => { + it("applies no spacing", () => { + render( +
+ Content +
, + ); + const section = screen.getByTestId("section"); + expect(section.className).toContain("py-0"); + }); + + it("applies small spacing", () => { + render( +
+ Content +
, + ); + const section = screen.getByTestId("section"); + expect(section.className).toContain("py-8"); + }); + + it("applies medium spacing", () => { + render( +
+ Content +
, + ); + const section = screen.getByTestId("section"); + expect(section.className).toContain("py-16"); + }); + + it("applies large spacing", () => { + render( +
+ Content +
, + ); + const section = screen.getByTestId("section"); + expect(section.className).toContain("py-24"); + }); + + it("applies extra large spacing", () => { + render( +
+ Content +
, + ); + const section = screen.getByTestId("section"); + expect(section.className).toContain("py-32"); + }); + + it("uses md spacing by default", () => { + render(
Content
); + const section = screen.getByTestId("section"); + expect(section.className).toContain("py-16"); + }); + }); + + describe("Background Variants", () => { + it("applies default background", () => { + render( +
+ Content +
, + ); + const section = screen.getByTestId("section"); + expect(section.className).toContain("bg-background"); + }); + + it("applies surface background", () => { + render( +
+ Content +
, + ); + const section = screen.getByTestId("section"); + expect(section.className).toContain("bg-surface"); + }); + + it("applies dotted-grid background", () => { + render( +
+ Content +
, + ); + const section = screen.getByTestId("section"); + expect(section.className).toContain("dotted-grid-background"); + }); + + it("applies blueprint background", () => { + render( +
+ Content +
, + ); + const section = screen.getByTestId("section"); + expect(section.className).toContain("blueprint-background"); + }); + + it("applies grid background", () => { + render( +
+ Content +
, + ); + const section = screen.getByTestId("section"); + expect(section.className).toContain("grid-background"); + }); + }); + + describe("Container Variants", () => { + it("applies no container by default", () => { + render(
Content
); + const section = screen.getByTestId("section"); + expect(section.className).not.toContain("max-w"); + }); + + it("applies small container", () => { + render( +
+ Content +
, + ); + const section = screen.getByTestId("section"); + expect(section.className).toContain("max-w-3xl"); + }); + + it("applies medium container", () => { + render( +
+ Content +
, + ); + const section = screen.getByTestId("section"); + expect(section.className).toContain("max-w-5xl"); + }); + + it("applies large container", () => { + render( +
+ Content +
, + ); + const section = screen.getByTestId("section"); + expect(section.className).toContain("max-w-7xl"); + }); + + it("applies extra large container", () => { + render( +
+ Content +
, + ); + const section = screen.getByTestId("section"); + expect(section.className).toContain("max-w-[90rem]"); + }); + + it("applies full width container", () => { + render( +
+ Content +
, + ); + const section = screen.getByTestId("section"); + expect(section.className).toContain("max-w-full"); + }); + }); + + describe("Centered Option", () => { + it("does not center by default", () => { + render(
Content
); + const section = screen.getByTestId("section"); + expect(section.className).not.toContain("mx-auto"); + }); + + it("centers content when centered is true", () => { + render( +
+ Content +
, + ); + const section = screen.getByTestId("section"); + expect(section.className).toContain("mx-auto"); + }); + }); + + describe("HTML Attributes", () => { + it("passes through id attribute", () => { + render( +
+ Content +
, + ); + const section = screen.getByTestId("section"); + expect(section).toHaveAttribute("id", "section-id"); + }); + + it("passes through data attributes", () => { + render( +
+ Content +
, + ); + const section = screen.getByTestId("section"); + expect(section).toHaveAttribute("data-custom", "test"); + }); + + it("passes through aria attributes", () => { + render( +
+ Content +
, + ); + const section = screen.getByTestId("section"); + expect(section).toHaveAttribute("aria-label", "Main section"); + }); + }); + + describe("Custom className", () => { + it("merges custom className with variant classes", () => { + render( +
+ Content +
, + ); + const section = screen.getByTestId("section"); + expect(section.className).toContain("custom-class"); + expect(section.className).toContain("relative"); + expect(section.className).toContain("w-full"); + }); + }); + + describe("Ref Forwarding", () => { + it("forwards ref to section element", () => { + let ref: HTMLElement | null = null; + render( +
{ + ref = el; + }} + > + Content +
, + ); + expect(ref).toBeInstanceOf(HTMLElement); + expect(ref?.tagName).toBe("SECTION"); + }); + }); + + describe("Base Classes", () => { + it("always includes base classes", () => { + render(
Content
); + const section = screen.getByTestId("section"); + expect(section.className).toContain("relative"); + expect(section.className).toContain("w-full"); + }); + }); + + describe("Compound Scenarios", () => { + it("works with multiple variants combined", () => { + render( +
+ Content +
, + ); + const section = screen.getByTestId("section"); + expect(section.className).toContain("py-24"); + expect(section.className).toContain("blueprint-background"); + expect(section.className).toContain("max-w-[90rem]"); + expect(section.className).toContain("mx-auto"); + }); + + it("works with custom className and variants", () => { + render( +
+ Content +
, + ); + const section = screen.getByTestId("section"); + expect(section.className).toContain("py-16"); + expect(section.className).toContain("bg-surface"); + expect(section.className).toContain("border-t"); + }); + }); +}); diff --git a/packages/ui/src/Section/Section.tsx b/packages/ui/src/Section/Section.tsx new file mode 100644 index 0000000..fb34837 --- /dev/null +++ b/packages/ui/src/Section/Section.tsx @@ -0,0 +1,67 @@ +import { cn } from "@tpmjs/utils/cn"; +import { forwardRef } from "react"; +import type { SectionProps } from "./types"; +import { sectionVariants } from "./variants"; + +/** + * Section component + * + * Semantic section wrapper with built-in spacing, backgrounds, and container options. + * Built with React and JSX for blueprint/editorial layouts. + * + * @example + * ```tsx + * import { Section } from '@tpmjs/ui/Section/Section'; + * + * function MyComponent() { + * return ( + *
+ *

Section Content

+ *
+ * ); + * } + * ``` + */ +export const Section = forwardRef( + ( + { + className, + as = "section", + spacing = "md", + background = "default", + container = "none", + centered = false, + children, + ...props + }, + ref, + ) => { + const Component = as; + + // biome-ignore lint/suspicious/noExplicitAny: Polymorphic component requires any for ref type + return ( + + {children} + + ); + }, +); + +Section.displayName = "Section"; diff --git a/packages/ui/src/Section/types.ts b/packages/ui/src/Section/types.ts new file mode 100644 index 0000000..954c8ff --- /dev/null +++ b/packages/ui/src/Section/types.ts @@ -0,0 +1,41 @@ +import type { HTMLAttributes } from "react"; + +/** + * Section component props + */ +export interface SectionProps extends HTMLAttributes { + /** + * Semantic section element to render + * @default 'section' + */ + as?: "section" | "article" | "aside" | "nav" | "div"; + + /** + * Vertical spacing size + * @default 'md' + */ + spacing?: "none" | "sm" | "md" | "lg" | "xl"; + + /** + * Background variant + * @default 'default' + */ + background?: "default" | "surface" | "dotted-grid" | "blueprint" | "grid"; + + /** + * Maximum width container + * @default 'none' + */ + container?: "none" | "sm" | "md" | "lg" | "xl" | "full"; + + /** + * Center content horizontally + * @default false + */ + centered?: boolean; +} + +/** + * Section ref type + */ +export type SectionRef = HTMLElement; diff --git a/packages/ui/src/Section/variants.ts b/packages/ui/src/Section/variants.ts new file mode 100644 index 0000000..f7a99e1 --- /dev/null +++ b/packages/ui/src/Section/variants.ts @@ -0,0 +1,49 @@ +import { createVariants } from "../system/variants"; + +/** + * Section variant definitions + */ +export const sectionVariants = createVariants({ + base: ["relative w-full"].join(" "), + + variants: { + spacing: { + none: "py-0", + sm: "py-8", + md: "py-16", + lg: "py-24", + xl: "py-32", + }, + + background: { + default: "bg-background", + surface: "bg-surface", + "dotted-grid": "dotted-grid-background", + blueprint: "blueprint-background", + grid: "grid-background", + }, + + container: { + none: "", + sm: "max-w-3xl", + md: "max-w-5xl", + lg: "max-w-7xl", + xl: "max-w-[90rem]", + full: "max-w-full", + }, + + centered: { + true: "mx-auto", + false: "", + }, + }, + + compoundVariants: [], + + defaultVariants: { + spacing: "md", + background: "default", + container: "none", + centered: "false", + }, +}); diff --git a/packages/ui/src/Tabs/Tabs.test.tsx b/packages/ui/src/Tabs/Tabs.test.tsx index 5012a64..f0b11ba 100644 --- a/packages/ui/src/Tabs/Tabs.test.tsx +++ b/packages/ui/src/Tabs/Tabs.test.tsx @@ -101,8 +101,8 @@ describe("Tabs", () => { , ); const activeTab = screen.getByTestId("tab-all"); - expect(activeTab.className).toContain("text-zinc-100"); - expect(activeTab.className).toContain("border-blue-500"); + expect(activeTab.className).toContain("text-foreground"); + expect(activeTab.className).toContain("border-primary"); }); it("applies inactive styling to inactive tabs", () => { @@ -111,7 +111,7 @@ describe("Tabs", () => { , ); const inactiveTab = screen.getByTestId("tab-featured"); - expect(inactiveTab.className).toContain("text-zinc-400"); + expect(inactiveTab.className).toContain("text-foreground-secondary"); expect(inactiveTab.className).toContain("border-transparent"); }); }); @@ -181,7 +181,7 @@ describe("Tabs", () => { , ); const activeBadge = screen.getByText("1234"); - expect(activeBadge.className).toContain("text-zinc-100"); + expect(activeBadge.className).toContain("text-foreground"); }); it("applies inactive styling to count badge on inactive tab", () => { @@ -190,7 +190,7 @@ describe("Tabs", () => { , ); const inactiveBadge = screen.getByText("42"); - expect(inactiveBadge.className).toContain("text-zinc-500"); + expect(inactiveBadge.className).toContain("text-foreground-tertiary"); }); it("handles zero count", () => { @@ -428,10 +428,10 @@ describe("Tabs", () => { expect(tab.className).toContain("text-lg"); expect(tab.className).toContain("px-6"); expect(tab.className).toContain("py-4"); - expect(tab.className).toContain("text-zinc-100"); - expect(tab.className).toContain("border-blue-500"); + expect(tab.className).toContain("text-foreground"); + expect(tab.className).toContain("border-primary"); const badge = screen.getByText("42"); - expect(badge.className).toContain("text-zinc-100"); + expect(badge.className).toContain("text-foreground"); }); it("handles single tab", () => { diff --git a/packages/ui/src/Tabs/Tabs.tsx b/packages/ui/src/Tabs/Tabs.tsx index 306f62e..0ebf1c7 100644 --- a/packages/ui/src/Tabs/Tabs.tsx +++ b/packages/ui/src/Tabs/Tabs.tsx @@ -36,12 +36,24 @@ import { * ``` */ export const Tabs = forwardRef( - ({ className, tabs, activeTab, onTabChange, size = "md", ...props }, ref) => { + ( + { + className, + tabs, + activeTab, + onTabChange, + size = "md", + variant = "default", + ...props + }, + ref, + ) => { return (
( className={tabButtonVariants({ size, active: isActive ? "true" : "false", + variant, })} onClick={() => onTabChange(tab.id)} data-testid={`tab-${tab.id}`} diff --git a/packages/ui/src/Tabs/types.ts b/packages/ui/src/Tabs/types.ts index cdb60d5..ed224ba 100644 --- a/packages/ui/src/Tabs/types.ts +++ b/packages/ui/src/Tabs/types.ts @@ -45,6 +45,12 @@ export interface TabsProps * @default 'md' */ size?: "sm" | "md" | "lg"; + + /** + * Visual variant + * @default 'default' + */ + variant?: "default" | "blueprint"; } /** diff --git a/packages/ui/src/Tabs/variants.ts b/packages/ui/src/Tabs/variants.ts index 316df60..8b41085 100644 --- a/packages/ui/src/Tabs/variants.ts +++ b/packages/ui/src/Tabs/variants.ts @@ -9,8 +9,6 @@ export const tabsContainerVariants = createVariants({ "flex items-center", // Overflow "overflow-x-auto", - // Border - "border-b border-zinc-800", ].join(" "), variants: { @@ -19,12 +17,17 @@ export const tabsContainerVariants = createVariants({ md: "gap-1", lg: "gap-1", }, + variant: { + default: "border-b border-border", + blueprint: "border-b border-dotted border-border", + }, }, compoundVariants: [], defaultVariants: { size: "md", + variant: "default", }, }); @@ -37,8 +40,6 @@ export const tabButtonVariants = createVariants({ "inline-flex items-center gap-2", // Font "font-medium whitespace-nowrap", - // Border - "border-b-2", // Transition "transition-colors duration-200", // Cursor @@ -52,9 +53,13 @@ export const tabButtonVariants = createVariants({ lg: "text-lg px-6 py-4", }, active: { - true: "text-zinc-100 border-blue-500", + true: "text-foreground border-primary", false: - "text-zinc-400 border-transparent hover:text-zinc-300 hover:border-zinc-700", + "text-foreground-secondary border-transparent hover:text-foreground hover:border-border-strong", + }, + variant: { + default: "border-b-2", + blueprint: "border-b-2 border-dotted", }, }, @@ -63,6 +68,7 @@ export const tabButtonVariants = createVariants({ defaultVariants: { size: "md", active: "false", + variant: "default", }, }); @@ -80,15 +86,15 @@ export const tabCountVariants = createVariants({ // Font "text-xs font-medium tabular-nums", // Background - "bg-zinc-800", + "bg-surface-elevated", // Border "rounded-full", ].join(" "), variants: { active: { - true: "text-zinc-100", - false: "text-zinc-500", + true: "text-foreground", + false: "text-foreground-tertiary", }, },