Skip to content

refactor(page-header): migrate to design-system primitives [Phase 1c]#621

Merged
barach6662001-bit merged 1 commit intomainfrom
replit/phase-1c-migrate-shell
Apr 24, 2026
Merged

refactor(page-header): migrate to design-system primitives [Phase 1c]#621
barach6662001-bit merged 1 commit intomainfrom
replit/phase-1c-migrate-shell

Conversation

@barach6662001-bit
Copy link
Copy Markdown
Owner

Phase 1c — Перший консумер примітивів: PageHeader

Перша реальна міграція компонента на нові примітиви Phase 1b. PageHeader.tsx тепер zero-CSS — вся розкладка, типографіка та відступи йдуть через Stack/Cluster/Heading/Text.

Чому саме PageHeader

  • Використовується в 20+ сторінках, тож будь-яка регресія спливе одразу.
  • Public API крихітний (title, subtitle, actions, breadcrumbs) — контракт легко зберегти буква в букву.
  • Стара реалізація — підручниковий анти-паттерн: незрозумілі імена класів (text22, flex_between), магічні пікселі, і #fff градієнт на заголовку, який був візуально зламаний на новій світлій темі.

Зміни (3 файли, +57 / -54)

Файл Дія
PageHeader.tsx Перепис на примітивах
PageHeader.module.css Видалено повністю
__tests__/PageHeader.test.tsx +3 семантичних тести

До/після (суть)

Було:

<div>
  <div className={s.flex_between}>
    <h1 className={s.text22}>{title}</h1>     {/* font-size: 26px; gradient #fff → broken on light */}
    <p className={s.text13}>{subtitle}</p>     {/* font-size: 13px; */}
  </div>
</div>

Стало:

<Stack as="header" gap="3" style={{ marginBottom: 'var(--space-6)' }}>
  {breadcrumbs}
  <Cluster align="start" justify="between" style={{ paddingBottom: 'var(--space-5)', borderBottom: '1px solid var(--border)' }}>
    <Stack gap="1">
      <Heading level={1}>{title}</Heading>
      {subtitle && <Text size="sm" tone="tertiary">{subtitle}</Text>}
    </Stack>
    {actions && <Cluster gap="2" align="center">{actions}</Cluster>}
  </Cluster>
</Stack>

Що покращилось семантично

  • Заголовок тепер флюїдний через токени: clamp(24px, …, 32px) — масштабується між брейкпоінтами автоматично.
  • Підпис автоматично адаптується до light/dark через семантичні токени (--text-tertiary).
  • Обгортка тепер реальний <header> (було <div>).
  • Прибрано #fff-градієнт, що ламав світлу тему.
  • Жодних magic numbers, жодних інлайн-розмірів — все через CSS-змінні з токенів.

Перевірки

  • eslint — 0 errors (warnings успадковані)
  • tsc -b — clean
  • vitest137 / 137 passed (3 нових тести на семантику + старі)
  • vite build — OK

API не змінився

Усі 20+ консумерів продовжують працювати без змін: той самий імпорт, ті самі пропси. Безпечно мерджити.

Що далі

Phase 1d — мігрувати ще одну точку дотику (можливо, шапку AppLayout або Sidebar), або перейти до Phase 2 (нові primitives: Card, Surface, Field).

  First real consumer of the Phase 1b primitives. PageHeader.tsx is now
  zero-CSS — the entire layout, typography and spacing flow through
  `Stack`, `Cluster`, `Heading` and `Text` (which themselves consume
  the Phase 1a tokens).

  Why this component first
  - Used by ~20 pages, so any visual regression would surface immediately.
  - Public API is tiny (`title`, `subtitle`, `actions`, `breadcrumbs`),
    so the contract is easy to preserve verbatim.
  - Old implementation was the textbook anti-pattern: cryptic class names
    (`text22`, `flex_between`), hard-coded pixel sizes, and a #fff
    gradient on the title that was visually broken on the new light theme.

  Changes
  - Re-author PageHeader.tsx using primitives only.
  - Delete PageHeader.module.css entirely (no replacement file).
  - Title is now a fluid `Heading level={1}` (clamp(24px, …, 32px)) — no
    more theme-broken gradient.
  - Subtitle is `Text size="sm" tone="tertiary"` — adapts to dark/light
    semantic colors automatically.
  - Wrapper is a semantic `<header>` (was a generic `<div>`).
  - Header divider (border-bottom) and the 24 px / 20 px rhythm are
    expressed via inline token vars — no magic numbers, no module CSS.

  Public API and visual rhythm preserved. Three new tests cover the new
  semantic guarantees:
    • title is queryable via `getByRole('heading', { level: 1 })`
    • actions slot is rendered
    • the wrapper is a real `<header>`

  Verification
  - 137 / 137 tests green (3 new on PageHeader, 36 from Phase 1b).
  - Lint clean, tsc clean, vite build OK.

  No other file is modified. All 20+ consumer pages keep the same import
  and the same prop contract.
@barach6662001-bit barach6662001-bit merged commit b79d5fa into main Apr 24, 2026
3 checks passed
@barach6662001-bit barach6662001-bit deleted the replit/phase-1c-migrate-shell branch April 24, 2026 10:21
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