From 863e41c47f63981ab910d44ba419c42f923f1f51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89mile=20R=C3=A9?= Date: Wed, 24 Jun 2026 11:50:39 +0200 Subject: [PATCH] Add v2 Text typography primitive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement the first v2 typography component from the Probo Radix UI Figma: a Text primitive rendering a single span with size, weight, align, color, and highContrast variants (color x highContrast resolves to the sand/gold/red/green/amber/sky text step 11 or 12), plus a paired TextSkeleton. Styling is tailwind-variants only, with the scale shared through a variants module per the kit guides. Stand up a theme-isolated v2 Storybook (.storybook-v2) loading the v2 theme with a light/dark toggle, since v1 and v2 cannot share one Tailwind build; the v1 build now excludes src/v2. Signed-off-by: Émile Ré --- packages/ui/.storybook-v2/main.ts | 27 +++++ packages/ui/.storybook-v2/preview.css | 18 +++ packages/ui/.storybook-v2/preview.tsx | 59 ++++++++++ packages/ui/.storybook/main.ts | 6 +- packages/ui/package.json | 2 + .../ui/src/v2/typography/Text.stories.tsx | 111 ++++++++++++++++++ packages/ui/src/v2/typography/Text.tsx | 34 ++++++ .../ui/src/v2/typography/TextSkeleton.tsx | 32 +++++ packages/ui/src/v2/typography/variants.ts | 91 ++++++++++++++ packages/ui/tsconfig.app.json | 2 +- 10 files changed, 380 insertions(+), 2 deletions(-) create mode 100644 packages/ui/.storybook-v2/main.ts create mode 100644 packages/ui/.storybook-v2/preview.css create mode 100644 packages/ui/.storybook-v2/preview.tsx create mode 100644 packages/ui/src/v2/typography/Text.stories.tsx create mode 100644 packages/ui/src/v2/typography/Text.tsx create mode 100644 packages/ui/src/v2/typography/TextSkeleton.tsx create mode 100644 packages/ui/src/v2/typography/variants.ts diff --git a/packages/ui/.storybook-v2/main.ts b/packages/ui/.storybook-v2/main.ts new file mode 100644 index 000000000..6c380384c --- /dev/null +++ b/packages/ui/.storybook-v2/main.ts @@ -0,0 +1,27 @@ +// Copyright (c) 2025-2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import type { StorybookConfig } from "@storybook/react-vite"; + +// The v2 kit is theme-isolated from v1 (v2 wipes Tailwind's palette), so it has +// its own Storybook build scanning only src/v2 and loading the v2 theme. +const config: StorybookConfig = { + stories: ["../src/v2/**/*.stories.@(js|jsx|mjs|ts|tsx)"], + addons: [import.meta.resolve("@chromatic-com/storybook")], + framework: { + name: "@storybook/react-vite", + options: {}, + }, +}; +export default config; diff --git a/packages/ui/.storybook-v2/preview.css b/packages/ui/.storybook-v2/preview.css new file mode 100644 index 000000000..e140bbb25 --- /dev/null +++ b/packages/ui/.storybook-v2/preview.css @@ -0,0 +1,18 @@ +/* Copyright (c) 2026 Probo Inc . + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +@import "tailwindcss"; +@import "../src/v2/theme.css"; +@source "../src/v2"; diff --git a/packages/ui/.storybook-v2/preview.tsx b/packages/ui/.storybook-v2/preview.tsx new file mode 100644 index 000000000..8b187cdc9 --- /dev/null +++ b/packages/ui/.storybook-v2/preview.tsx @@ -0,0 +1,59 @@ +// Copyright (c) 2025-2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import type { Preview } from "@storybook/react"; +import { useEffect } from "react"; + +import "./preview.css"; + +const preview: Preview = { + parameters: { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + }, + }, + globalTypes: { + theme: { + description: "v2 color theme", + defaultValue: "light", + toolbar: { + title: "Theme", + icon: "circlehollow", + items: [ + { value: "light", title: "Light" }, + { value: "dark", title: "Dark" }, + ], + dynamicTitle: true, + }, + }, + }, + decorators: [ + (Story, context) => { + const isDark = context.globals.theme === "dark"; + useEffect(() => { + document.documentElement.classList.toggle("dark", isDark); + }, [isDark]); + return ( +
+ +
+ ); + }, + ], +}; + +export default preview; diff --git a/packages/ui/.storybook/main.ts b/packages/ui/.storybook/main.ts index 015f0d7df..e0ff054b9 100644 --- a/packages/ui/.storybook/main.ts +++ b/packages/ui/.storybook/main.ts @@ -15,7 +15,11 @@ import type { StorybookConfig } from "@storybook/react-vite"; const config: StorybookConfig = { - stories: ["../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"], + // v2 components have their own theme-isolated Storybook (.storybook-v2). + stories: [ + "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)", + "!../src/v2/**", + ], addons: [ import.meta.resolve("@chromatic-com/storybook"), import.meta.resolve("@storybook/addon-vitest"), diff --git a/packages/ui/package.json b/packages/ui/package.json index 2d5a2f335..f3eb98f8b 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -7,6 +7,8 @@ "main": "src/index.ts", "scripts": { "dev": "storybook dev -p 6006 --no-open", + "dev:v2": "storybook dev -p 6007 -c .storybook-v2 --no-open", + "build:v2": "storybook build -c .storybook-v2", "check": "tsc --noEmit -p tsconfig.app.json", "icons": "bun run src/Atoms/Icons/generator.ts" }, diff --git a/packages/ui/src/v2/typography/Text.stories.tsx b/packages/ui/src/v2/typography/Text.stories.tsx new file mode 100644 index 000000000..edb365506 --- /dev/null +++ b/packages/ui/src/v2/typography/Text.stories.tsx @@ -0,0 +1,111 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import type { Meta, StoryObj } from "@storybook/react"; + +import { Text } from "./Text"; +import { TextSkeleton } from "./TextSkeleton"; + +const sample = "The quick brown fox jumps over the lazy dog."; + +const sizes = [1, 2, 3, 4, 5, 6, 7, 8, 9] as const; +const weights = ["light", "regular", "medium", "bold"] as const; +const colors = ["neutral", "gold", "red", "green", "amber", "sky"] as const; + +export default { + title: "v2/Typography/Text", + component: Text, + args: { + children: sample, + size: 3, + weight: "regular", + color: "neutral", + highContrast: false, + }, +} satisfies Meta; + +type Story = StoryObj; + +export const Playground: Story = {}; + +export const Sizes: Story = { + render: () => ( +
+ {sizes.map(size => ( + + {size} + {" — "} + {sample} + + ))} +
+ ), +}; + +export const Weights: Story = { + render: () => ( +
+ {weights.map(weight => ( + + {weight} + {" — "} + {sample} + + ))} +
+ ), +}; + +export const Align: Story = { + render: () => ( +
+ Left-aligned + Center-aligned + Right-aligned +
+ ), +}; + +export const Colors: Story = { + render: () => ( +
+ {colors.map(color => ( + + {color} + {" — "} + {sample} + + ))} +
+ ), +}; + +export const HighContrast: Story = { + render: () => ( +
+ Low contrast (step 11) + High contrast (step 12) +
+ ), +}; + +export const Skeleton: Story = { + render: () => ( +
+ + + +
+ ), +}; diff --git a/packages/ui/src/v2/typography/Text.tsx b/packages/ui/src/v2/typography/Text.tsx new file mode 100644 index 000000000..44b665c46 --- /dev/null +++ b/packages/ui/src/v2/typography/Text.tsx @@ -0,0 +1,34 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import type { ComponentProps } from "react"; +import type { VariantProps } from "tailwind-variants"; + +import { text } from "./variants"; + +export type TextProps = ComponentProps<"span"> & VariantProps; + +// Foundational text primitive (Radix "Text"). Renders a single ; reach +// for size/weight/align/color/highContrast variants rather than raw type +// utilities. See contrib/claude/ui.md (typography). +export function Text(props: TextProps) { + const { size, weight, align, color, highContrast, className, ...rest } = props; + + return ( + + ); +} diff --git a/packages/ui/src/v2/typography/TextSkeleton.tsx b/packages/ui/src/v2/typography/TextSkeleton.tsx new file mode 100644 index 000000000..f00647023 --- /dev/null +++ b/packages/ui/src/v2/typography/TextSkeleton.tsx @@ -0,0 +1,32 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import type { ComponentProps } from "react"; +import type { VariantProps } from "tailwind-variants"; + +import { textSkeleton } from "./variants"; + +export type TextSkeletonProps = Omit, "children"> & VariantProps; + +// Loading placeholder paired with Text: a pulse block matching a line of text +// at the given size. Defaults to full width; constrain with a width class. +export function TextSkeleton(props: TextSkeletonProps) { + const { size, className, ...rest } = props; + + return ( + + {"\u00A0"} + + ); +} diff --git a/packages/ui/src/v2/typography/variants.ts b/packages/ui/src/v2/typography/variants.ts new file mode 100644 index 000000000..18d1e78ac --- /dev/null +++ b/packages/ui/src/v2/typography/variants.ts @@ -0,0 +1,91 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import { tv } from "tailwind-variants"; + +// 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 +// Text and its skeleton so the placeholder matches the real line height. +const size = { + 1: "text-1", + 2: "text-2", + 3: "text-3", + 4: "text-4", + 5: "text-5", + 6: "text-6", + 7: "text-7", + 8: "text-8", + 9: "text-9", +} as const; + +export const text = tv({ + variants: { + size, + weight: { + light: "font-light", + regular: "font-normal", + medium: "font-medium", + bold: "font-bold", + }, + align: { + left: "text-left", + center: "text-center", + right: "text-right", + }, + // Hue only; the resolved text step comes from the color × highContrast + // compound variants below (step 11 low-contrast, step 12 high-contrast). + color: { + neutral: "", + gold: "", + red: "", + green: "", + amber: "", + sky: "", + }, + highContrast: { + true: "", + false: "", + }, + }, + compoundVariants: [ + { color: "neutral", highContrast: false, class: "text-sand-11" }, + { color: "neutral", highContrast: true, class: "text-sand-12" }, + { color: "gold", highContrast: false, class: "text-gold-11" }, + { color: "gold", highContrast: true, class: "text-gold-12" }, + { color: "red", highContrast: false, class: "text-red-11" }, + { color: "red", highContrast: true, class: "text-red-12" }, + { color: "green", highContrast: false, class: "text-green-11" }, + { color: "green", highContrast: true, class: "text-green-12" }, + { color: "amber", highContrast: false, class: "text-amber-11" }, + { color: "amber", highContrast: true, class: "text-amber-12" }, + { color: "sky", highContrast: false, class: "text-sky-11" }, + { color: "sky", highContrast: true, class: "text-sky-12" }, + ], + defaultVariants: { + size: 3, + weight: "regular", + color: "neutral", + highContrast: false, + }, +}); + +export const textSkeleton = tv({ + base: "inline-block w-full animate-pulse select-none rounded-2 bg-sand-3 text-transparent", + variants: { + size, + }, + defaultVariants: { + size: 3, + }, +}); diff --git a/packages/ui/tsconfig.app.json b/packages/ui/tsconfig.app.json index adb2a99ad..c4f2921f2 100644 --- a/packages/ui/tsconfig.app.json +++ b/packages/ui/tsconfig.app.json @@ -23,5 +23,5 @@ "noFallthroughCasesInSwitch": true, "noUncheckedSideEffectImports": true }, - "include": ["src", ".storybook/**/*"], + "include": ["src", ".storybook/**/*", ".storybook-v2/**/*"], }