Skip to content

feat(design-system): layout & typography primitives [Phase 1b]#620

Merged
barach6662001-bit merged 1 commit intomainfrom
replit/phase-1b-layout-typography
Apr 24, 2026
Merged

feat(design-system): layout & typography primitives [Phase 1b]#620
barach6662001-bit merged 1 commit intomainfrom
replit/phase-1b-layout-typography

Conversation

@barach6662001-bit
Copy link
Copy Markdown
Owner

Phase 1b — Layout & Typography Primitives

Шість токенізованих примітивів, що замінять випадкові div-и та inline-типографіку у застосунку.

Layout Typography
Box Heading
Stack Text
Cluster
Container

Чому

Phase 1a дав нам TS source-of-truth для токенів + згенерований tokens.css. Але без примітивів кожен новий екран знов плодив <div style={{ display: 'flex', gap: 16 }}> і <h2 style={{ fontSize: 24 }}>. Цей PR закриває нижній шар: вся візуальна семантика — тільки через ці компоненти, всі значення — тільки через CSS-змінні з токенів.

Дизайн API (короткий зріз)

import { Stack, Cluster, Container, Heading, Text, Box } from '@/design-system';

<Container size="lg">
  <Stack gap="6">
    <Heading level={1}>Дашборд</Heading>
    <Text size="md" tone="secondary">Огляд за сезон 2026</Text>

    <Cluster gap="3" justify="between">
      <Heading level={3} as="h2">KPI</Heading>
      <Cluster gap="2"><Button/><Button/></Cluster>
    </Cluster>

    <Box p="4" /* …дочірні компоненти */ />
  </Stack>
</Container>

Архітектурні рішення

  • Polymorphic as на кожному примітиві + forwardRef.
  • CSS Modules + data-attribute селектори для варіантів — нуль конкатенації className, нуль string-юніонів, що протікають у CSS.
  • Inline CSS-змінні для токенозалежних значень: 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+).
  • Fluid типографіка успадковується з токенів (clamp()), тож масштабується між брейкпоінтами без додаткової логіки.
  • Zero-deps — лише React. Жоден існуючий компонент не змінено.

Що доступно публічно

import { Box, Stack, Cluster, Container, Heading, Text, space, cx } from '@/design-system';

Перевірки

  • eslint — 0 errors (warnings успадковані з main)
  • tsc -b — clean
  • vitest134 / 134 passed (із них 36 нових)
  • vite build — OK

Що далі (Phase 1c)

Перевести AppHeader, Sidebar та одну еталонну сторінку (Dashboard) на ці примітиви. Жодного нового токена — лише міграція візуальних значень.

Це additive-PR: жодного існуючого файлу не змінено, безпечно мерджити одразу.

  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.
@barach6662001-bit barach6662001-bit merged commit d1bc68f into main Apr 24, 2026
3 checks passed
@barach6662001-bit barach6662001-bit deleted the replit/phase-1b-layout-typography branch April 24, 2026 10:12
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +19 to +20
.text[data-tone='danger'] { --ds-text-color: var(--danger); }
.text[data-tone='accent'] { --ds-text-color: var(--accent); }
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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> {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants