feat(design-system): layout & typography primitives [Phase 1b]#620
feat(design-system): layout & typography primitives [Phase 1b]#620barach6662001-bit merged 1 commit intomainfrom
Conversation
Add six tokenised primitives that replace ad-hoc divs and inline
typography across the app:
Layout Box · Stack · Cluster · Container
Typography Heading · Text
Every visual decision (gap, padding, font-size, weight, color, tracking)
flows through CSS variables generated from the Phase 1a TS tokens — no
hard-coded values anywhere in this PR.
Implementation notes
- CSS Modules for the few component classes; data-attribute selectors
for variant flags (no className concatenation, no string-typed unions
leaking into CSS).
- Polymorphic 'as' prop on every primitive; refs forwarded.
- Defaults are semantically correct: Heading level=N renders <hN>, Text
defaults to <p>, Container defaults to <div size="xl"> with responsive
side padding (16/24/32 px).
- Fluid typography is inherited from tokens (clamp()), so Heading/Text
scale automatically across breakpoints.
Public surface
import { Box, Stack, Cluster, Container, Heading, Text }
from '@/design-system';
Tests
- 36 new unit tests (Vitest + Testing Library); 134/134 green overall.
- Lint clean, tsc clean, vite build OK.
No existing component is modified; this PR is additive only.
Phase 1c will start migrating real pages onto these primitives.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 76e71d5026
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| .text[data-tone='danger'] { --ds-text-color: var(--danger); } | ||
| .text[data-tone='accent'] { --ds-text-color: var(--accent); } |
There was a problem hiding this comment.
Map danger/accent tones to existing token variables
The new tone mappings use var(--danger) and var(--accent), but those CSS variables are not defined in the design-token output (only tokens like --error and --accent-* exist). When Text is rendered with tone="danger" or tone="accent", --ds-text-color resolves to an undefined variable and the color declaration becomes invalid at compute time, so text falls back/inherits instead of applying the requested semantic tone.
Useful? React with 👍 / 👎.
| bold: 'var(--font-weight-bold)', | ||
| }; | ||
|
|
||
| export interface TextProps extends HTMLAttributes<HTMLElement> { |
There was a problem hiding this comment.
Allow label attributes when Text renders as label
Text explicitly supports as="label", but its props extend HTMLAttributes<HTMLElement>, which excludes label-specific fields such as htmlFor. In strict TypeScript usage, <Text as="label" htmlFor="field" /> fails type-checking (TS2322), which blocks accessible label wiring even though the component API advertises label rendering.
Useful? React with 👍 / 👎.
…[Phase 1f] (#625) Finishes the DashboardV2 row-card migration. Both remaining sibling panels (OperationsTimeline in row 4, UpcomingPanel in row 5) now use the design-system <Card> primitive instead of the local `s.card` wrapper. Combined with Phase 1d (WarehouseSnapshot) and Phase 1e (AlertsPanel), every dashboard surface in this row is now driven by the same primitive. Wrapper migration (DashboardV2.tsx) ----------------------------------- - <div className={s.card}><OperationsTimeline …/></div> → <Card>…</Card> - <div className={s.card}><UpcomingPanel …/></div> → <Card>…</Card> Both sites are pure wrapper swaps. <Card> defaults (variant=subtle, radius=xl=16px, padding=5=20px, bordered=true) are an exact drop-in for the legacy `.card` rule, so the visual contract (background, border, radius, padding) is preserved. The internal list/item content of each panel is untouched. CSS dead-code removal (DashboardV2.module.css) ---------------------------------------------- With both consumers gone, these rules become unreferenced and are deleted (≈39 lines): - .card (background, border, radius, padding, transition) - .card:hover (subtle background lift) - .card::before (decorative top hairline) - .cardHeader (flex justify-between row, never used in DashboardV2.tsx) - .cardLink, .cardLink:hover (eyebrow link button, never used in DashboardV2.tsx) A short tombstone comment is left in place so future readers see why the section is empty and where to look (Card primitive path). Out of scope ------------ - No route changes - No API changes - No business-logic changes - No refactor of inner panel markup - No new dependencies - No Card highlight prop introduction - No new tests (spec: only update if coverage exists; none did) Verification (local) -------------------- - frontend tsc -b: clean - frontend lint: 0 errors (warning count unchanged) - vitest design-system + dashboard subset: 67/67 passing - Full test + build run delegated to CI. Phase 1 status after this PR ---------------------------- - ✅ 1a — TS tokens (#616) - ✅ 1b — Layout + Typography primitives (#620) - ✅ 1c — PageHeader migration (#621) - ✅ 1d — Surface + Card primitives + first dashboard adoption (#622) - ✅ 1e — AlertsPanel adoption (#624) - ✅ 1f — OperationsTimeline + UpcomingPanel adoption (this PR) Co-authored-by: replit-agent <agent@replit.local>
Phase 1b — Layout & Typography Primitives
Шість токенізованих примітивів, що замінять випадкові
div-и та inline-типографіку у застосунку.BoxHeadingStackTextClusterContainerЧому
Phase 1a дав нам TS source-of-truth для токенів + згенерований
tokens.css. Але без примітивів кожен новий екран знов плодив<div style={{ display: 'flex', gap: 16 }}>і<h2 style={{ fontSize: 24 }}>. Цей PR закриває нижній шар: вся візуальна семантика — тільки через ці компоненти, всі значення — тільки через CSS-змінні з токенів.Дизайн API (короткий зріз)
Архітектурні рішення
asна кожному примітиві + forwardRef.style={{ '--ds-stack-gap': 'var(--space-4)' }}. Це SSR-safe, легко переоприділяється, дешево.Heading level={N}рендерить<hN>,Text→<p>,Container→<div>з адаптивним side-padding (16/24/32 px на 0/600/1024+).clamp()), тож масштабується між брейкпоінтами без додаткової логіки.Що доступно публічно
Перевірки
eslint— 0 errors (warnings успадковані з main)tsc -b— cleanvitest— 134 / 134 passed (із них 36 нових)vite build— OKЩо далі (Phase 1c)
Перевести
AppHeader,Sidebarта одну еталонну сторінку (Dashboard) на ці примітиви. Жодного нового токена — лише міграція візуальних значень.