Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/components-html/src/body/body.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.dsn-body {
background-color: var(--dsn-color-neutral-bg-document);
color: var(--dsn-color-neutral-color-document);
font-family: var(--dsn-text-font-family-default);
font-size: var(--dsn-text-font-size-md);
line-height: var(--dsn-text-line-height-md);
font-weight: var(--dsn-text-font-weight-default);
}
6 changes: 6 additions & 0 deletions packages/components-react/src/Body/Body.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/**
* Body component styles voor React
* Re-exporteert de basis Body stijlen vanuit components-html
*/

@import '../../../components-html/src/body/body.css';
43 changes: 43 additions & 0 deletions packages/components-react/src/Body/Body.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { describe, it, expect } from 'vitest';
import { render, screen } from '@testing-library/react';
import { createRef } from 'react';
import { Body } from './Body';

describe('Body', () => {
it('renders children', () => {
render(
<Body>
<p>Inhoud</p>
</Body>
);
expect(screen.getByText('Inhoud')).toBeInTheDocument();
});

it('renders as a <div> element', () => {
const { container } = render(<Body />);
expect(container.firstChild?.nodeName).toBe('DIV');
});

it('always has base dsn-body class', () => {
const { container } = render(<Body />);
expect(container.firstChild).toHaveClass('dsn-body');
});

it('applies custom className', () => {
const { container } = render(<Body className="custom" />);
expect(container.firstChild).toHaveClass('dsn-body');
expect(container.firstChild).toHaveClass('custom');
});

it('forwards ref to the div element', () => {
const ref = createRef<HTMLDivElement>();
const { container } = render(<Body ref={ref} />);
expect(ref.current).toBe(container.firstChild);
});

it('passes additional HTML attributes', () => {
const { container } = render(<Body data-testid="body" id="root" />);
expect(container.firstChild).toHaveAttribute('data-testid', 'body');
expect(container.firstChild).toHaveAttribute('id', 'root');
});
});
41 changes: 41 additions & 0 deletions packages/components-react/src/Body/Body.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from 'react';
import { classNames } from '@dsn/core';
import './Body.css';

export interface BodyProps extends React.HTMLAttributes<HTMLDivElement> {
/**
* Child-elementen die de document-level stijlen erven
*/
children?: React.ReactNode;
}

/**
* Body component
* Stelt document-level CSS stijlen in via CSS inheritance zodat alle child-elementen
* automatisch de juiste typografie, kleur en achtergrond erven.
*
* @example
* ```tsx
* // In een applicatie: wrap de root-content
* <Body>
* <h1>Mijn pagina</h1>
* <p>Inhoud met geërfde stijlen</p>
* </Body>
*
* // Of pas de CSS class direct toe op het <body> element
* // <body class="dsn-body">
* ```
*/
export const Body = React.forwardRef<HTMLDivElement, BodyProps>(
({ className, children, ...props }, ref) => {
const classes = classNames('dsn-body', className);

return (
<div ref={ref} className={classes} {...props}>
{children}
</div>
);
}
);

Body.displayName = 'Body';
2 changes: 2 additions & 0 deletions packages/components-react/src/Body/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { Body } from './Body';
export type { BodyProps } from './Body';
1 change: 1 addition & 0 deletions packages/components-react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

// Layout Components
export * from './Body';
export * from './Container';
export * from './Grid';
export * from './Stack';
Expand Down
2 changes: 1 addition & 1 deletion packages/storybook/.storybook/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ const preview: Preview = {
// Update body classes for any CSS scoping
const densityClass =
projectType === 'information-dense' ? 'dense' : 'default';
document.body.className = `dsn-theme-${theme} dsn-mode-${mode} dsn-density-${densityClass}`;
document.body.className = `dsn-body dsn-theme-${theme} dsn-mode-${mode} dsn-density-${densityClass}`;
}

return Story();
Expand Down
44 changes: 44 additions & 0 deletions packages/storybook/src/Body.docs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Body

Stelt document-level CSS stijlen in zodat alle child-elementen via cascade de juiste typografie, kleur en achtergrond erven.

## Doel

Body definieert zes CSS properties op document-niveau: achtergrondkleur, tekstkleur, lettertype, lettergrootte, regelafstand en lettergewicht. Componenten die zelf geen specifieke waarden definiëren erven deze defaults automatisch via de CSS cascade.

In een applicatie zet je de `dsn-body` class op het `<body>` element van je HTML, of gebruik je het React `<Body>` component als root-wrapper.

In Storybook is `dsn-body` als global decorator toegepast op alle stories en 'Voorbeeld'-secties, zodat componenten altijd in de juiste omgeving worden getoond.

<!-- VOORBEELD -->

## Use when

- Je de juiste document-level stijlen wilt instellen voor een pagina of applicatie.
- Je in Storybook een geïsoleerde preview wilt wrappen met de juiste omgevingsstijlen.

## Don't use when

- Je de stijlen voor een specifieke sectie wilt overschrijven — gebruik dan component-specifieke klassen of CSS custom properties.
- Je een ander thema of een andere modus per sectie wilt toepassen — dit is een document-level wrapper, geen theming-component.

## Best practices

- **Pas `dsn-body` toe op `<body>`**: dit is de bedoelde use case. De CSS cascade werkt dan door voor de hele pagina.
- **Gebruik `<Body>` als React-wrapper** wanneer je geen controle hebt over het `<body>` element (bijv. in geïsoleerde previews of micro-frontends).
- **Voeg geen extra stijlen toe aan `dsn-body`**: de class dient uitsluitend als cascade-startpunt. Paginaspecifieke stijlen horen in layout-componenten.

## Accessibility

Body heeft geen directe invloed op toegankelijkheid. De tokens die het instelt — met name kleur en achtergrond — zijn afgestemd op een voldoende contrastverhouding volgens WCAG 2.1 AA.

## Design tokens

| CSS property | Token | Beschrijving |
| ------------------ | ------------------------------------ | --------------------------------- |
| `background-color` | `--dsn-color-neutral-bg-document` | Achtergrondkleur van het document |
| `color` | `--dsn-color-neutral-color-document` | Standaard tekstkleur |
| `font-family` | `--dsn-text-font-family-default` | Standaard lettertype |
| `font-size` | `--dsn-text-font-size-md` | Standaard lettergrootte |
| `line-height` | `--dsn-text-line-height-md` | Standaard regelafstand |
| `font-weight` | `--dsn-text-font-weight-default` | Standaard lettergewicht |
28 changes: 28 additions & 0 deletions packages/storybook/src/Body.docs.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Meta, Story, Markdown } from '@storybook/blocks';
import * as BodyStories from './Body.stories';
import docs from './Body.docs.md?raw';
import { PreviewFrame, CodeTabs } from './components';

export const [intro, rest] = docs.split('<!-- VOORBEELD -->');

<Meta of={BodyStories} />

<Markdown>{intro}</Markdown>

## Voorbeeld

<PreviewFrame>
<Story of={BodyStories.Default} />
</PreviewFrame>

<CodeTabs
of={BodyStories.Default}
html={`<body class="dsn-body">
<!-- paginainhoud -->
</body>`}
react={`<Body>
{/* paginainhoud */}
</Body>`}
/>

<Markdown>{rest}</Markdown>
39 changes: 39 additions & 0 deletions packages/storybook/src/Body.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { Meta, StoryObj } from '@storybook/react';
import {
Body,
type BodyProps,
Heading,
Paragraph,
} from '@dsn/components-react';
import DocsPage from './Body.docs.mdx';

const meta: Meta<typeof Body> = {
title: 'Foundations/Body',
component: Body,
parameters: {
docs: { page: DocsPage },
dsn: {
htmlTemplate: () =>
`<body class="dsn-body">\n <!-- paginainhoud -->\n</body>`,
},
},
argTypes: {
children: { control: false },
},
};

export default meta;
type Story = StoryObj<typeof Body>;

export const Default: Story = {
render: (args: BodyProps) => (
<Body {...args}>
<Heading level={1}>Paginatitel</Heading>
<Paragraph>
Dit is een voorbeeldparagraaf. De typografie, kleur en achtergrond zijn
ingesteld door het Body component en worden via CSS cascade overgenomen
door alle child-elementen die zelf geen specifieke waarden definiëren.
</Paragraph>
</Body>
),
};
7 changes: 4 additions & 3 deletions packages/storybook/src/Introduction.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,11 @@ function App() {

## Componenten overzicht

**42 componenten totaal** — alle beschikbaar als HTML/CSS én React.
**43 componenten totaal** — alle beschikbaar als HTML/CSS én React.

### Layout Components (3)
### Layout Components (4)

- **Body** — Document-level cascade basisstijlen (typografie, kleur, achtergrond) — zet `dsn-body` op `<body>`
- **Container** — Centrerende wrapper met max-width en padding-inline voor pagina-layout
- **Grid** — 12-koloms CSS Grid container met gutter, margin en optionele max-width (`contained`)
- **Stack** — Verticale stapeling met consistente row-spacing (9 space-varianten)
Expand Down Expand Up @@ -145,4 +146,4 @@ MIT License — zie LICENSE bestand voor details.

---

**Versie:** 5.6.0 | **Laatste update:** 12 maart 2026 | **Auteur:** Jeffrey Lauwers
**Versie:** 5.6.0 | **Laatste update:** 13 maart 2026 | **Auteur:** Jeffrey Lauwers
Loading