From 5cf5c84bd1e3fac89beb2bac705d8563a0b4a046 Mon Sep 17 00:00:00 2001 From: Mathieu Chambaud Date: Tue, 10 Nov 2026 10:01:48 +0100 Subject: [PATCH 1/4] feat!: move the design system to the v3 visual language Re-point the tokens at the Cloud UI v3 and Dashboard UI v3 designs: true greys, ink primary action, Geist Mono for numbers, 6/8/12/16 radii, one easing curve and a 2px focus outline. Restyle the base components and add TopBar, PageHeader, SidebarBrand, SidebarPanel, SegmentedControl and CodeBlock. AppHeader is deprecated. BREAKING CHANGE: token values, radii, the primary button and the sl-num and sl-label utilities change; see Migrating from 0.3 in the README. --- README.md | 34 ++- scripts/verify-ds.mjs | 5 +- scripts/verify-ds.test.mjs | 5 +- src/components/lumyx/app-header.tsx | 46 +---- src/components/lumyx/event-list.tsx | 4 +- src/components/lumyx/feedback.tsx | 80 ++++--- src/components/lumyx/index.ts | 2 + src/components/lumyx/metric.tsx | 52 +++-- src/components/lumyx/page-body.tsx | 4 +- src/components/lumyx/peer-card.tsx | 10 +- src/components/lumyx/quality.tsx | 6 +- src/components/lumyx/room-card.tsx | 12 +- src/components/lumyx/sidebar-brand.tsx | 45 ++++ src/components/lumyx/sidebar-nav.tsx | 62 ++++-- src/components/lumyx/sparkline.tsx | 24 ++- src/components/lumyx/status-dot.tsx | 14 +- src/components/lumyx/status-strip.tsx | 4 +- src/components/lumyx/time-series-chart.tsx | 12 +- src/components/lumyx/top-bar.tsx | 85 ++++++++ src/components/lumyx/video-tile.tsx | 4 +- src/components/ui/badge.tsx | 30 ++- src/components/ui/button.tsx | 27 +-- src/components/ui/card.tsx | 21 +- src/components/ui/dialog.tsx | 4 +- src/components/ui/dropdown-menu.tsx | 4 +- src/components/ui/input.tsx | 10 +- src/components/ui/progress.tsx | 28 ++- src/components/ui/segmented-control.tsx | 42 ++++ src/components/ui/select.tsx | 10 +- src/components/ui/switch.tsx | 4 +- src/components/ui/table.tsx | 17 +- src/components/ui/tabs.tsx | 67 ++++-- src/index.ts | 1 + src/lib/utils.ts | 2 +- src/styles.css | 229 +++++++++++++-------- 35 files changed, 690 insertions(+), 316 deletions(-) create mode 100644 src/components/lumyx/sidebar-brand.tsx create mode 100644 src/components/lumyx/top-bar.tsx create mode 100644 src/components/ui/segmented-control.tsx diff --git a/README.md b/README.md index 61c3305..2d708a5 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,8 @@ The Lumyx design system — React components and Tailwind CSS v4 tokens, built o ```bash bun add @lumyx/ui -# until the first npm release: -bun add github:lumyx-dev/ui +# until the first npm release, pin a tag — never the moving default branch: +bun add github:lumyx-dev/ui#v0.4.0 ``` Peer dependencies: `react@^19` and `react-dom@^19`. The package does not depend on any @@ -41,6 +41,9 @@ Import the tokens and point Tailwind at the component sources so their classes a Adjust the `@source` path to where `node_modules` sits relative to the CSS file. +Numbers and identifiers use Geist Mono through `--font-mono` (`sl-num`). With Next.js, load +both families with `next/font` and expose them as `--font-geist-sans` and `--font-geist-mono`. + ## Usage ```tsx @@ -56,6 +59,33 @@ export function Example() { } ``` +## Migrating from 0.3 + +0.4 moves the system to the v3 visual language (Cloud UI v3, Dashboard UI v3). Token and +class names are unchanged, their values are not: + +- **Palette.** True greys (`#fafafa` page, `#eaeaea` card stroke, `#0a0a0a` ink); semantic inks + are `#16a34a` / `#d97706` / `#e5484d`. New tokens: `bg-ink` / `text-on-ink`, `bg-chrome`, + `border-stroke-hover`, `border-chrome-border`, `bg-accent-subtle`, `bg-accent-soft`, + `border-{ok,warn,danger,info}-border`. +- **Radii.** `xs` 6 · `sm` 8 · `md` 12 · `lg` 16 (were 8 · 12 · 14 · 18). +- **Type.** Body is 14/20; `text-24`, `text-30`, `text-36` join the scale; line heights sit on a + 4px grid. `sl-num` now switches to Geist Mono (use `sl-tnum` for tabular sans); + `sl-label` is 12px sentence case, no longer uppercase. +- **Motion.** One curve, `cubic-bezier(0.32,0.72,0,1)`; 300 / 400 / 500 / 600ms are allowed. + Focus is a 2px accent outline offset by 2px. +- **Button.** `primary` is ink. The former indigo primary is `accent`. New `danger-outline`. +- **Badge.** Pills by default; `shape="chip"` for mono machine values. +- **Card.** Flat at rest, `interactive` lifts on hover; header and content pad 16px. +- **Tabs.** `TabsList` is segmented by default; pass `variant="underline"` for the old look. + `SegmentedControl` covers toggles that are not navigation. +- **Metrics.** `MetricCard` is a card of its own (`bare` drops the frame) with `sub`, + `subState` and an inline 64×20 sparkline; `MetricGrid` is a gapped responsive grid. +- **Chrome.** `TopBar` (row 1) and `PageHeader` (inside `PageBody`) replace `AppHeader`, + which still renders but is deprecated. `SidebarBrand` and `SidebarPanel` build the + sidebar head and foot; `SidebarNav` items take `count` and `dot`. +- **Feedback.** `AlertBanner` takes `icon`, `action` and `onDismiss`; `CodeBlock` is new. + ## Routing `SidebarNav`, `AppHeader` breadcrumbs and `RoomCard` render internal links. By default they are diff --git a/scripts/verify-ds.mjs b/scripts/verify-ds.mjs index fdfb321..204fb82 100644 --- a/scripts/verify-ds.mjs +++ b/scripts/verify-ds.mjs @@ -153,8 +153,9 @@ scanClasses( new RegExp(`(? DURATIONS.has(m[1])); if (failures.length) { diff --git a/scripts/verify-ds.test.mjs b/scripts/verify-ds.test.mjs index b38e9be..af0d364 100644 --- a/scripts/verify-ds.test.mjs +++ b/scripts/verify-ds.test.mjs @@ -91,12 +91,13 @@ describe('verify-ds regle 8 — couleurs par token', () => { }); describe('verify-ds regle 9 — durees de motion', () => { - test('accepte 120 / 180 / 260', () => { + test('accepte 120 / 180 / 260 et 300 / 400 / 500 / 600', () => { expect(runWith('export const A = () =>

;\n').code).toBe(0); + expect(runWith('export const A = () =>

;\n').code).toBe(0); }); test('refuse une duree hors systeme', () => { - const { code, output } = runWith('export const A = () =>

;\n'); + const { code, output } = runWith('export const A = () =>

;\n'); expect(code).toBe(1); expect(output).toContain('motion-duration'); }); diff --git a/src/components/lumyx/app-header.tsx b/src/components/lumyx/app-header.tsx index 34e1b53..d8ed315 100644 --- a/src/components/lumyx/app-header.tsx +++ b/src/components/lumyx/app-header.tsx @@ -1,14 +1,13 @@ "use client"; import * as React from "react"; -import { useLumyxLink } from "../../lib/link"; -import { cn } from "../../lib/utils"; -import { SidebarTrigger } from "../ui/sidebar"; +import { TopBar, type Crumb } from "./top-bar"; -export type Crumb = { href?: string; label: string }; +export type { Crumb } from "./top-bar"; /** - * Ligne 1 de la grille du SidebarInset. Rendu par la page, pas par le chrome : - * le titre vit avec les donnees qui le remplissent. + * @deprecated since 0.4.0 — compose `TopBar` (row 1) with `PageHeader` (inside `PageBody`). + * Kept so 0.3 call sites still render: the title becomes the last breadcrumb, `meta` and + * `actions` sit on the right of the bar. */ export function AppHeader({ breadcrumb, @@ -23,35 +22,12 @@ export function AppHeader({ actions?: React.ReactNode; className?: string; }) { - const { Link } = useLumyxLink(); + const crumbs: Crumb[] = [...(breadcrumb ?? []), { label: typeof title === "string" ? title : "" }].filter((c) => c.label); return ( -

-
-
- -
- {breadcrumb?.length ? ( - - ) : null} -

{title}

- {meta ?
{meta}
: null} -
-
- {actions ?
{actions}
: null} -
-
+ + {typeof title !== "string" ? {title} : null} + {meta ? {meta} : null} + {actions} + ); } diff --git a/src/components/lumyx/event-list.tsx b/src/components/lumyx/event-list.tsx index b6fea6b..f9efc1a 100644 --- a/src/components/lumyx/event-list.tsx +++ b/src/components/lumyx/event-list.tsx @@ -8,9 +8,9 @@ export function EventList({ events, className }: { events: LumyxEvent[]; classNa return (