diff --git a/CLAUDE.md b/CLAUDE.md index 01d9591..4a9eac9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -94,7 +94,26 @@ Kernregels: - Geen geneste element-namen: `dsn-alert__content__text` ❌ - HTML-toestanden via pseudo-klassen: `.dsn-button:disabled` ✅ -### 4. TypeScript moet volledig schoon zijn +### 4. Token-hiërarchie — altijd op de juiste laag aanpassen + +Tokens zijn gelaagd: `base.json` (gedeelde primitieven) → component-token JSON → CSS custom property → component CSS. Pas altijd aan op de **hoogste laag die de waarde definieert**, zodat de delegatieketen intact blijft. + +```json +// ❌ Omzeilen van de delegatieketen in text-input.json +"padding-block-start": { "value": "{dsn.space.block.md}" } + +// ✅ Aanpassen op de juiste laag — in base.json onder form-control +"padding-block-start": { "value": "{dsn.space.block.md}" } +// text-input.json blijft delegeren naar {dsn.form-control.padding-block-start} +``` + +**Werkwijze bij een token-wijziging:** + +1. Zoek via `Grep` welk token de waarde _uiteindelijk_ definieert (vaak in `base.json` of een theme-bestand) +2. Pas dáár de waarde aan +3. Controleer of de delegatieketen in component-JSONs ongewijzigd blijft + +### 5. TypeScript moet volledig schoon zijn `pnpm --filter storybook exec tsc --noEmit` geeft **0 fouten en 0 warnings**. Nieuwe code mag geen nieuwe fouten of warnings introduceren. diff --git a/README.md b/README.md index d931827..d782261 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ pnpm --filter @dsn/design-tokens watch # Start Storybook in development mode pnpm dev -# Run tests (1234 tests across 61 test suites) +# Run tests (1282 tests across 63 test suites) pnpm test # Run tests in watch mode @@ -201,7 +201,7 @@ All components are fully typed with TypeScript and include comprehensive JSDoc d | **StatusBadge** | Yes | Yes | — | | **Table** | Yes | Yes | — | -**Navigation Components (4)** +**Navigation Components (5)** | Component | HTML/CSS | React | Web Component | | ------------------------ | -------- | ----- | ------------- | @@ -209,6 +209,7 @@ All components are fully typed with TypeScript and include comprehensive JSDoc d | **Menu** | Yes | Yes | — | | **MenuButton** | Yes | Yes | — | | **MenuLink** | Yes | Yes | — | +| **PageHeader** | Yes | Yes | — | **Form Components (25)** @@ -383,7 +384,7 @@ Comprehensive documentation is available in the `/docs` folder: - **Pre-commit hooks** via Husky + lint-staged (ESLint + Prettier) - **Type checking** across all packages (`pnpm type-check`) -- **1248 tests** covering React components, Web Components, and utilities +- **1282 tests** covering React components, Web Components, and utilities - **CI/CD** via GitHub Actions (lint, type-check, test, build) ## Tech Stack diff --git a/docs/03-components.md b/docs/03-components.md index e0ffb42..98cb4d6 100644 --- a/docs/03-components.md +++ b/docs/03-components.md @@ -1,6 +1,6 @@ # Components -**Last Updated:** April 5, 2026 +**Last Updated:** April 6, 2026 Complete component specifications and guidelines for the Design System Starter Kit. @@ -1587,7 +1587,7 @@ const [isOpen, setIsOpen] = React.useState(false); ## Navigation Components -**Status:** Complete (HTML/CSS, React) — 4 components total +**Status:** Complete (HTML/CSS, React) — 5 components total ### Menu @@ -1899,6 +1899,223 @@ const [isOpen, setIsOpen] = React.useState(false); --- +### PageHeader + +**Status:** Complete (HTML/CSS, React) + +**Location:** `packages/components-{html|react}/src/PageHeader/` + +**Tokens:** `tokens/components/page-header.json` + +**Props:** `logoSlot`, `sticky` (`'none'` | `'sticky'` | `'auto-hide'`), `primaryNavigation`, `primaryNavigationLarge`, `secondaryNavigation`, `secondaryNavigationLarge`, `searchSlot`, `onMenuOpen`, `onMenuClose`, `onSearchOpen`, `onSearchClose`, `className` + +**Features:** + +- Mobile-first: hamburgerknop (inline-start) opent een `Drawer`, gecentreerd logo (CSS-grid `1fr auto 1fr`), zoekknop (inline-end) ontvouwt zoekpaneel direct onder de header +- Boven `64em` (~1024px): tweebandig large viewport layout via `display: none` switch + - **Masthead** — neutrale achtergrond met logo (inline-start), servicemenu en inline zoekveld (inline-end) + - **Navigatiebalk** — accent-1 achtergrond met primaire navigatie; MenuLink-items krijgen `min-block-size: 4rem` en `padding-inline: var(--dsn-space-inline-xl)` via token-overschrijving op de container +- `primaryNavigationLarge` / `secondaryNavigationLarge` — aparte slots voor large viewport; valt terug op de mobile variant wanneer weggelaten +- `sticky='sticky'`: `position: sticky; inset-block-start: 0` +- `sticky='auto-hide'`: sticky + verbergt bij scroll-down via JS `scroll`-eventlistener (`data-hidden` attribuut), CSS-transitie animeert de beweging +- Focus management: openen zoekpaneel → focus naar ``; sluiten → focus terug naar zoekknop +- Unieke IDs via `useId()` voor `aria-controls` (zoekpaneel) en `aria-labelledby` (nav-elementen) +- De `Drawer` is altijd in de DOM (niet `hidden`) zodat focus management correct werkt + +**CSS-klassen:** + +| Klasse | Element | Beschrijving | +| --------------------------------- | ---------- | ----------------------------------------------------- | +| `dsn-page-header` | `
` | Basiscomponent | +| `dsn-page-header--sticky` | `
` | Sticky gedrag: `position: sticky` | +| `dsn-page-header--auto-hide` | `
` | Auto-hide sticky: CSS-transitie op `data-hidden` | +| `dsn-page-header__small-layout` | `
` | Zichtbaar op small viewport (`< 64em`) | +| `dsn-page-header__large-layout` | `
` | Zichtbaar op large viewport (`≥ 64em`) | +| `dsn-page-header__inner` | `
` | CSS-grid (`1fr auto 1fr`) voor gecentreerd logo | +| `dsn-page-header__start` | `
` | Inline-start slot (hamburgerknop) | +| `dsn-page-header__logo` | `
` | Logo-slot; `max-block-size` op directe child | +| `dsn-page-header__end` | `
` | Inline-end slot (zoekknop) | +| `dsn-page-header__search-panel` | `
` | Zoekpaneel (small viewport); verborgen via `[hidden]` | +| `dsn-page-header__search-inner` | `
` | Flex-container: zoekveld + zoekknop | +| `dsn-page-header__masthead` | `
` | Bovenste band large viewport (neutrale achtergrond) | +| `dsn-page-header__masthead-inner` | `
` | Flex-container: logo ↔ secondary-nav | +| `dsn-page-header__secondary-nav` | `
` | Servicemenu + zoekveld naast elkaar (inline-end) | +| `dsn-page-header__searchbox` | `
` | Inline zoekveld + zoekknop in masthead | +| `dsn-page-header__navbar` | `
` | Onderste band large viewport (accent-1 achtergrond) | + +**Design tokens:** + +| Token | Waarde | Beschrijving | +| ------------------------------------------------- | ------------------------------------ | ------------------------------------------- | +| `--dsn-page-header-background-color` | `{dsn.color.neutral.bg-document}` | Achtergrondkleur header | +| `--dsn-page-header-border-block-end-width` | `{dsn.border.width.thick}` | Breedte onderkantrand (4px, small viewport) | +| `--dsn-page-header-border-block-end-color` | `{dsn.color.accent-1.color-default}` | Kleur onderkantrand (merkkleur) | +| `--dsn-page-header-padding-block` | `{dsn.space.block.md}` | Verticale padding mobile binnenbalk | +| `--dsn-page-header-padding-inline` | `{dsn.space.inline.xl}` | Horizontale padding mobile binnenbalk | +| `--dsn-page-header-z-index` | `300` | Z-index sticky — onder backdrop (400) | +| `--dsn-page-header-logo-max-block-size` | `2rem` | Maximale hoogte logo (32px) | +| `--dsn-page-header-search-panel-background-color` | `{dsn.color.accent-1.bg-default}` | Achtergrond zoekpaneel (small viewport) | +| `--dsn-page-header-search-panel-padding-block` | `{dsn.space.block.md}` | Verticale padding zoekpaneel | +| `--dsn-page-header-search-panel-padding-inline` | `{dsn.space.inline.xl}` | Horizontale padding zoekpaneel | +| `--dsn-page-header-masthead-background-color` | `{dsn.color.neutral.bg-document}` | Masthead achtergrond (large viewport) | +| `--dsn-page-header-masthead-padding-block` | `{dsn.space.block.xl}` | Verticale padding masthead | +| `--dsn-page-header-masthead-padding-inline` | `{dsn.space.inline.xl}` | Horizontale padding masthead | +| `--dsn-page-header-navbar-background-color` | `{dsn.color.accent-1.bg-default}` | Navigatiebalk achtergrond | +| `--dsn-page-header-navbar-padding-inline` | `{dsn.space.inline.xl}` | Horizontale padding navigatiebalk | +| `--dsn-page-header-secondary-nav-gap` | `{dsn.space.column.3xl}` | Gap servicemenu ↔ zoekveld in masthead | + +**Usage:** + +```html + +
+
+
+
+ +
+ +
+ +
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+
+ +
+
+
+``` + +```tsx +// React + + + + Naam organisatie — terug naar homepage + + + } + primaryNavigation={ + + + Home + + + } + primaryNavigationLarge={ + + + Home + + + } + secondaryNavigation={ + + + Contact + + + } + secondaryNavigationLarge={ + + + Contact + + + } + searchSlot={ + <> + + + + } + sticky="auto-hide" +/> +``` + +**Tests:** React (38 tests) + +--- + ## Branding Components **Status:** Complete (HTML/CSS, React) — 1 component total @@ -2294,15 +2511,15 @@ defineButton('my-custom-button'); ## Component Statistics -**Total Components:** 48 +**Total Components:** 49 **Implementations:** -- **HTML/CSS:** 48 components -- **React:** 48 components (1248 tests total, 62 test suites) +- **HTML/CSS:** 49 components +- **React:** 49 components (1282 tests total, 63 test suites) - **Web Component:** 7 components (Button, Heading, Icon, Link, OrderedList, Paragraph, UnorderedList) -**Test Coverage:** 1248 tests across 62 test suites +**Test Coverage:** 1282 tests across 63 test suites --- diff --git a/docs/changelog.md b/docs/changelog.md index dd3a33c..40c0266 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -6,6 +6,41 @@ All notable changes to this project are documented in this file. --- +## Version 5.21.1 (April 6, 2026) + +### Fix: PageHeader large viewport verfijningen (PR #146) + +#### Fixed + +- **Zoekknop padding** in de masthead (large viewport): `SearchInput` wrapper krijgt `flex: 1` + `max-inline-size: none` zodat de Zoeken-knop zijn natuurlijke breedte en padding behoudt — zelfde patroon als het mobile zoekpaneel +- **Token gecorrigeerd**: `--dsn-page-header-padding-inline` van `{dsn.space.row.md}` naar `{dsn.space.inline.xl}` — nu consistent met masthead en navbar padding +- **Docs gecorrigeerd**: `--dsn-page-header-search-panel-background-color` was gedocumenteerd als `{dsn.color.neutral.bg-subtle}`, is `{dsn.color.accent-1.bg-default}` + +#### Changed + +- **Navbar MenuLink-hoogte en padding**: `min-block-size: 4rem` en `padding-inline: var(--dsn-space-inline-xl)` via token-overschrijving op `.dsn-page-header__navbar`; de `calc()`-compensatie voor de current-indicator werkt automatisch mee +- **Storybook controls opgeschoond**: circulaire `DocsPage`-import verwijderd (loste crash op), `argTypes` geherstructureerd in drie categorieën: **Gedrag** / **Slots** / **Events** + +--- + +## Version 5.21.0 (April 6, 2026) + +### PageHeader large viewport layout (PR #142) + +#### Added + +- **Large viewport layout** (≥ 64em) voor `PageHeader` — tweebandig ontwerp boven `64em` via `display: none` switch: + - **Masthead** (`dsn-page-header__masthead`) — neutrale achtergrond met logo (inline-start), servicemenu en inline zoekveld (inline-end) + - **Navigatiebalk** (`dsn-page-header__navbar`) — accent-1 achtergrond met primaire navigatie +- `primaryNavigationLarge` prop — aparte navigatie-inhoud voor de navigatiebalk op large viewport; valt terug op `primaryNavigation` wanneer weggelaten +- `secondaryNavigationLarge` prop — aparte servicemenu-inhoud voor de masthead; valt terug op `secondaryNavigation` wanneer weggelaten +- `searchSlot` prop — inline zoekveld (SearchInput + zoekknop) in de masthead rechts van het servicemenu +- CSS-klassen: `dsn-page-header__large-layout`, `dsn-page-header__small-layout`, `dsn-page-header__masthead`, `dsn-page-header__masthead-inner`, `dsn-page-header__secondary-nav`, `dsn-page-header__searchbox`, `dsn-page-header__navbar` +- 6 nieuwe design tokens: `masthead-background-color`, `masthead-padding-block`, `masthead-padding-inline`, `navbar-background-color`, `navbar-padding-inline`, `secondary-nav-gap` +- 38 React tests + +--- + ## Version 5.20.0 (April 5, 2026) ### Logo component (issue #126) diff --git a/packages/components-html/src/page-header/page-header.css b/packages/components-html/src/page-header/page-header.css index d154276..e3816cc 100644 --- a/packages/components-html/src/page-header/page-header.css +++ b/packages/components-html/src/page-header/page-header.css @@ -97,6 +97,19 @@ gap: var(--dsn-space-inline-md); } +/* SearchInput wrapper vult beschikbare ruimte — button behoudt zijn natuurlijke + breedte en padding. Zelfde patroon als dsn-page-header__search-inner. */ +.dsn-page-header__searchbox .dsn-search-input-wrapper { + flex: 1; + min-inline-size: 0; + max-inline-size: none; +} + +.dsn-page-header__searchbox .dsn-text-input { + max-inline-size: none; + inline-size: 100%; +} + /* ============================================================================= Navbar (large viewport) — accent-1 achtergrond, primaire navigatie ============================================================================= */ @@ -104,6 +117,12 @@ .dsn-page-header__navbar { background-color: var(--dsn-page-header-navbar-background-color); padding-inline: var(--dsn-page-header-navbar-padding-inline); + + /* MenuLink-items in de navbar krijgen een grotere min-block-size en ruimere + padding-inline. Door de tokens te overschrijven op de container werkt de + calc()-compensatie voor de current-indicator automatisch mee. */ + --dsn-menu-item-min-block-size: 4rem; + --dsn-menu-item-padding-inline: var(--dsn-space-inline-xl); } /* ============================================================================= diff --git a/packages/design-tokens/src/tokens/components/page-header.json b/packages/design-tokens/src/tokens/components/page-header.json index 1bb4da8..f15bced 100644 --- a/packages/design-tokens/src/tokens/components/page-header.json +++ b/packages/design-tokens/src/tokens/components/page-header.json @@ -22,9 +22,9 @@ "comment": "Verticale padding van de header-binnenbalk" }, "padding-inline": { - "value": "{dsn.space.row.md}", + "value": "{dsn.space.inline.xl}", "type": "spacing", - "comment": "Horizontale padding van de header-binnenbalk" + "comment": "Horizontale padding van de header-binnenbalk — zelfde schaal als masthead en navbar" }, "z-index": { "value": "300", diff --git a/packages/storybook/src/PageHeader.docs.md b/packages/storybook/src/PageHeader.docs.md index 226827b..e9e5fdc 100644 --- a/packages/storybook/src/PageHeader.docs.md +++ b/packages/storybook/src/PageHeader.docs.md @@ -197,7 +197,7 @@ Het zoekpaneel verschijnt direct onder de header-binnenbalk. Het paneel bevat ee | `--dsn-page-header-padding-inline` | `{dsn.space.inline.xl}` | Horizontale padding binnenbalk | | `--dsn-page-header-z-index` | `300` | Z-index voor sticky — onder backdrop (400) | | `--dsn-page-header-logo-max-block-size` | `2rem` | Maximale hoogte logo (32px) | -| `--dsn-page-header-search-panel-background-color` | `{dsn.color.neutral.bg-subtle}` | Achtergrond zoekpaneel (small) | +| `--dsn-page-header-search-panel-background-color` | `{dsn.color.accent-1.bg-default}` | Achtergrond zoekpaneel (small) | | `--dsn-page-header-search-panel-padding-block` | `{dsn.space.block.md}` | Verticale padding zoekpaneel (small) | | `--dsn-page-header-search-panel-padding-inline` | `{dsn.space.inline.xl}` | Horizontale padding zoekpaneel (small) | | `--dsn-page-header-masthead-background-color` | `{dsn.color.neutral.bg-document}` | Masthead achtergrond (large) | diff --git a/packages/storybook/src/PageHeader.stories.tsx b/packages/storybook/src/PageHeader.stories.tsx index a861e5d..3c36448 100644 --- a/packages/storybook/src/PageHeader.stories.tsx +++ b/packages/storybook/src/PageHeader.stories.tsx @@ -8,7 +8,6 @@ import { PageHeader, SearchInput, } from '@dsn/components-react'; -import DocsPage from './PageHeader.docs.mdx'; // ============================================================================= // META @@ -190,7 +189,6 @@ const meta: Meta = { title: 'Components/PageHeader', component: PageHeader, parameters: { - docs: { page: DocsPage }, layout: 'fullscreen', dsn: { // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -231,9 +229,53 @@ const meta: Meta = { }, }, argTypes: { + // ── Gedrag ────────────────────────────────────────────────────────────── sticky: { control: 'select', options: ['none', 'sticky', 'auto-hide'], + table: { category: 'Gedrag' }, + }, + // ── Slots — ReactNode, niet bewerkbaar via controls ────────────────────── + logoSlot: { + control: false, + table: { category: 'Slots' }, + }, + searchSlot: { + control: false, + table: { category: 'Slots' }, + }, + primaryNavigation: { + control: false, + table: { category: 'Slots' }, + }, + primaryNavigationLarge: { + control: false, + table: { category: 'Slots' }, + }, + secondaryNavigation: { + control: false, + table: { category: 'Slots' }, + }, + secondaryNavigationLarge: { + control: false, + table: { category: 'Slots' }, + }, + // ── Events ─────────────────────────────────────────────────────────────── + onMenuOpen: { + control: false, + table: { category: 'Events' }, + }, + onMenuClose: { + control: false, + table: { category: 'Events' }, + }, + onSearchOpen: { + control: false, + table: { category: 'Events' }, + }, + onSearchClose: { + control: false, + table: { category: 'Events' }, }, }, args: {