diff --git a/contrib/claude/ui.md b/contrib/claude/ui.md index 71bc8fa02..2ca777b82 100644 --- a/contrib/claude/ui.md +++ b/contrib/claude/ui.md @@ -20,7 +20,7 @@ These rules are the **source of truth**. The legacy tree (`Atoms/`, `Molecules/` |------|------------| | Package | **`@probo/ui`** — v2 components under `src/v2`. Apps opt into v2 by importing the v2 theme (see [`v2-tokens.md`](v2-tokens.md)). | | Styling | **Tailwind v4** with the Radix-scale tokens (`bg-sand-3`, `text-sand-12`, `rounded-3`, `text-4`, …). | -| Variants API | **`tailwind-variants`** only — `import { tv } from "tailwind-variants"`. | +| Variants API | **`tailwind-variants/lite`** only — `import { tv } from "tailwind-variants/lite"`. The `/lite` entrypoint ships **without `tailwind-merge`**, which is required: the numbered scales (`text-1…9`, `rounded-1…6`, `shadow-1…6`) collide with the color/utility namespaces and tailwind-merge would silently drop the scale class (e.g. `text-3` next to `text-sand-11`). The legacy v1 kit stays on `tailwind-variants` (with merge). | | Class composition | **Do not use `clsx` or `tailwind-merge`.** All conditional styling goes through `tv` variants and slots. | | Headless primitives | **Base UI** (`@base-ui/react`). We **style** these primitives; we do not re-implement their behavior. | @@ -58,7 +58,7 @@ export function Dialog({ trigger, ref, children }: Props) { ```tsx // Good — thin styling over Base UI; consumers use the lib's open/onOpenChange directly import { Dialog as BaseDialog } from "@base-ui/react/dialog"; -import { tv } from "tailwind-variants"; +import { tv } from "tailwind-variants/lite"; const dialog = tv({ slots: { @@ -111,7 +111,7 @@ For **compound / multi-slot** components, define `tv` in a **dedicated `variants ```tsx // Bad — same file mixes tv() output with ad-hoc Tailwind / clsx on className import { clsx } from "clsx"; -import { tv } from "tailwind-variants"; +import { tv } from "tailwind-variants/lite"; const row = tv({ base: "flex items-center gap-2" }); export function Row({ children }: { children: ReactNode }) { @@ -121,7 +121,7 @@ export function Row({ children }: { children: ReactNode }) { ```tsx // Good — layout and look live in tv -import { tv } from "tailwind-variants"; +import { tv } from "tailwind-variants/lite"; const row = tv({ base: "flex items-center gap-2 rounded-3 border border-sand-6", @@ -133,7 +133,7 @@ export function Row({ children }: { children: ReactNode }) { ```tsx // Good — optional styling toggles use tv variants, not extra className strings -import { tv } from "tailwind-variants"; +import { tv } from "tailwind-variants/lite"; const row = tv({ base: "flex items-center gap-2", @@ -398,7 +398,7 @@ Model regions with `tv` `slots` named after the layout: ```ts // ImageCard/variants.ts -import { tv } from "tailwind-variants"; +import { tv } from "tailwind-variants/lite"; export const imageCard = tv({ slots: { diff --git a/eslint.config.mjs b/eslint.config.mjs index 33d8248fa..784a62744 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -68,6 +68,34 @@ export default defineConfig([ ], }, }, + { + // The v2 kit styles with tailwind-variants/lite (no tailwind-merge): the + // numbered scales (text-1…9, rounded-1…6, shadow-1…6) collide with the + // color/utility namespaces and tailwind-merge would drop the scale class. + files: ["packages/ui/src/v2/**"], + rules: { + "no-restricted-imports": [ + "error", + { + paths: [ + { + name: "tailwind-variants", + message: + "Import from tailwind-variants/lite (no tailwind-merge) in v2.", + }, + { + name: "tailwind-merge", + message: "The v2 kit does not use tailwind-merge.", + }, + { + name: "clsx", + message: "The v2 kit does not use clsx; style via tailwind-variants/lite.", + }, + ], + }, + ], + }, + }, { files: reactDirs, ignores: ["packages/ui/tailwind.config.js"], diff --git a/package-lock.json b/package-lock.json index fb3f95fa5..5dff894a4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18989,29 +18989,22 @@ } }, "node_modules/tailwind-variants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/tailwind-variants/-/tailwind-variants-1.0.0.tgz", - "integrity": "sha512-2WSbv4ulEEyuBKomOunut65D8UZwxrHoRfYnxGcQNnHqlSCp2+B7Yz2W+yrNDrxRodOXtGD/1oCcKGNBnUqMqA==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/tailwind-variants/-/tailwind-variants-3.2.2.tgz", + "integrity": "sha512-Mi4kHeMTLvKlM98XPnK+7HoBPmf4gygdFmqQPaDivc3DpYS6aIY6KiG/PgThrGvii5YZJqRsPz0aPyhoFzmZgg==", "license": "MIT", - "dependencies": { - "tailwind-merge": "3.0.2" - }, "engines": { "node": ">=16.x", "pnpm": ">=7.x" }, "peerDependencies": { + "tailwind-merge": ">=3.0.0", "tailwindcss": "*" - } - }, - "node_modules/tailwind-variants/node_modules/tailwind-merge": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.0.2.tgz", - "integrity": "sha512-l7z+OYZ7mu3DTqrL88RiKrKIqO3NcpEO8V/Od04bNpvk0kiIFndGEoqfuzvj4yuhRkHKjRkII2z+KS2HfPcSxw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/dcastil" + }, + "peerDependenciesMeta": { + "tailwind-merge": { + "optional": true + } } }, "node_modules/tailwindcss": { @@ -21497,7 +21490,7 @@ "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.1", "tailwind-merge": "^3.4.0", - "tailwind-variants": "^1.0.0", + "tailwind-variants": "^3.2.2", "tailwindcss": "^4.3.1", "tw-animate-css": "^1.3.0", "zustand": "^5.0.4" diff --git a/packages/ui/package.json b/packages/ui/package.json index f3eb98f8b..53703fa4b 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -42,7 +42,7 @@ "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.1", "tailwind-merge": "^3.4.0", - "tailwind-variants": "^1.0.0", + "tailwind-variants": "^3.2.2", "tailwindcss": "^4.3.1", "tw-animate-css": "^1.3.0", "zustand": "^5.0.4" diff --git a/packages/ui/src/v2/typography/Text.tsx b/packages/ui/src/v2/typography/Text.tsx index 44b665c46..334c8b4ef 100644 --- a/packages/ui/src/v2/typography/Text.tsx +++ b/packages/ui/src/v2/typography/Text.tsx @@ -13,7 +13,7 @@ // PERFORMANCE OF THIS SOFTWARE. import type { ComponentProps } from "react"; -import type { VariantProps } from "tailwind-variants"; +import type { VariantProps } from "tailwind-variants/lite"; import { text } from "./variants"; diff --git a/packages/ui/src/v2/typography/TextSkeleton.tsx b/packages/ui/src/v2/typography/TextSkeleton.tsx index f00647023..1dab13452 100644 --- a/packages/ui/src/v2/typography/TextSkeleton.tsx +++ b/packages/ui/src/v2/typography/TextSkeleton.tsx @@ -13,7 +13,7 @@ // PERFORMANCE OF THIS SOFTWARE. import type { ComponentProps } from "react"; -import type { VariantProps } from "tailwind-variants"; +import type { VariantProps } from "tailwind-variants/lite"; import { textSkeleton } from "./variants"; diff --git a/packages/ui/src/v2/typography/variants.ts b/packages/ui/src/v2/typography/variants.ts index 18d1e78ac..c2e3d82e7 100644 --- a/packages/ui/src/v2/typography/variants.ts +++ b/packages/ui/src/v2/typography/variants.ts @@ -12,7 +12,7 @@ // OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -import { tv } from "tailwind-variants"; +import { tv } from "tailwind-variants/lite"; // Numbered type scale (text-1 … text-9). Each utility carries its paired // font-size, line-height, and letter-spacing from the v2 theme. Shared between