feat(design-system): add Surface and Card primitives + first dashboard adoption [Phase 1d]#622
Merged
barach6662001-bit merged 1 commit intomainfrom Apr 24, 2026
Merged
Conversation
…d adoption [Phase 1d]
Establishes the dashboard "surface system" — the second layer of the
design system, sitting on top of the Phase 1b layout primitives. With
this PR, the typical "panel" / "card" pattern (background, border,
radius, padding, elevation) becomes a single, token-driven component
instead of a bespoke CSS module per consumer.
What ships
- `Surface`: the primitive container.
- `variant`: 'subtle' (--card-bg) | 'raised' (--bg-elevated) | 'flat'
- `bordered`: boolean → 1 px --border outline
- `radius`: 'sm' | 'md' | 'lg' | 'xl' | '2xl' (token-mapped)
- `elevation`: 0..4 → --shadow-0..4
- `interactive`: hover/focus states via :hover / :focus-visible (the
only place CSS modules are needed; rest of the styling is inline-
token).
- Polymorphic `as`, forwardRef, full padding pass-through (p / px /
py / pt / pr / pb / pl) — same idiom as Box.
- `Card`: pre-configured Surface (subtle, xl, p=5, no shadow, bordered)
with internal vertical flex + token gap. Sub-components compose
naturally inside.
- `CardHeader` / `CardTitle` / `CardDescription` / `CardContent` /
`CardFooter`: thin compositions over the existing primitives —
Stack, Cluster, Heading, Text. No duplication, no parallel CSS,
no new tokens.
Migration (proof-of-adoption)
- `DashboardV2.tsx` "Warehouse snapshot" tile: replaces the local
`<div className={s.card}>` wrapper with `<Card>`.
- Visual hierarchy preserved: section label still renders above the
card; padding stays at 20 px; same radius (16 / xl); same hairline
border; same subtle bg.
- Token drift is intentional: the legacy class used off-token values
(.02 bg, .06 border, 22 px horizontal padding); the new Card uses
the canonical `--card-bg` / `--border` / `--space-5`. Visually
indistinguishable, but now governed by the design system.
- The other two cards in the same row (OperationsTimeline, Upcoming)
intentionally stay on `s.card` — single-site adoption keeps blast
radius minimal. Future PRs migrate the rest.
Out of scope (explicit non-goals for this PR)
- AppLayout / Sidebar are NOT migrated.
- No new tokens added.
- No new dependencies.
- No public route or API call changed; no business logic touched.
Verification
- 162 / 162 tests green (25 new: 14 on Surface, 11 on Card / Card.*).
- `tsc -b` clean.
- `eslint` 0 errors (only inherited warnings).
- `vite build` OK; PWA bundle size unchanged (+0.07 KB).
What this unlocks
- `AlertsPanel` and the remaining dashboard tiles can now drop their
per-component card CSS modules and adopt `<Card>` in trivial PRs.
- Sidebar panels and form containers can adopt `<Surface>` directly,
parameterised by variant/elevation.
- Storybook stories (Phase 2) get a single `Card` story instead of
N near-duplicate panel stories.
This was referenced Apr 24, 2026
barach6662001-bit
added a commit
that referenced
this pull request
Apr 24, 2026
…[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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 1d — Поверхнева система:
Surface+CardЦе другий шар design-system над примітивами Phase 1b. Зараз кожен «панельний» вигляд (фон + бордер + радіус + падінг + тінь) — це власний CSS-модуль. Тепер це один токенізований компонент.
Що додано
Surface— базовий контейнерvariant:'subtle'(--card-bg) |'raised'(--bg-elevated) |'flat'bordered:boolean— 1 px--borderradius:'sm' | 'md' | 'lg' | 'xl' | '2xl'(мапиться на--radius-*)elevation:0..4→--shadow-0..4interactive: hover/focus стани через:hover/:focus-visible(єдине місце, де знадобився CSS-модуль; решта стилізації — інлайн через токени)as,forwardRef, повний padding pass-through (p / px / py / pt / pr / pb / pl) — той самий ідіом, що вBoxCard— пресетSurfacesubtle,xl,p=5, без тіні, з бордером, внутрішній вертикальний flex з токен-gapCardHeader / CardTitle / CardDescription / CardContent / CardFooterТонкі обгортки над уже існуючими примітивами (Stack, Cluster, Heading, Text). Жодного дублювання, жодних паралельних CSS, жодних нових токенів.
Міграція (proof-of-adoption)
Один сайт у
DashboardV2.tsx— обгортка тайлу «Залишки на складах»:<div className={s.card}>→<Card>.Cardвикористовує канонічні--card-bg / --border / --space-5. Візуально нерозрізнимо, але керується системою.OperationsTimeline,Upcoming) навмисно залишаються на старомуs.card— мінімальний радіус впливу. Майбутні PR мігрують решту.Поза скоупом (явні non-goals)
AppLayout/Sidebar— НЕ мігруємоПеревірки
vitesttsc -beslintvite buildЩо це розблоковує
AlertsPanelта решта дашборд-тайлів зможуть викинути власні card-CSS і прийняти<Card>тривіальними PR-ами<Surface>напряму, параметризовано черезvariant/elevationCardstory замість N майже-однакових panel-сторі