Skip to content

Commit 93ff38c

Browse files
Merge pull request #84 from jeffreylauwers/feature/body-component
feat(Body): implementeer Body component met cascade basisstijlen
2 parents 3fb34bb + 34fbcdb commit 93ff38c

File tree

11 files changed

+217
-4
lines changed

11 files changed

+217
-4
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.dsn-body {
2+
background-color: var(--dsn-color-neutral-bg-document);
3+
color: var(--dsn-color-neutral-color-document);
4+
font-family: var(--dsn-text-font-family-default);
5+
font-size: var(--dsn-text-font-size-md);
6+
line-height: var(--dsn-text-line-height-md);
7+
font-weight: var(--dsn-text-font-weight-default);
8+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/**
2+
* Body component styles voor React
3+
* Re-exporteert de basis Body stijlen vanuit components-html
4+
*/
5+
6+
@import '../../../components-html/src/body/body.css';
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { render, screen } from '@testing-library/react';
3+
import { createRef } from 'react';
4+
import { Body } from './Body';
5+
6+
describe('Body', () => {
7+
it('renders children', () => {
8+
render(
9+
<Body>
10+
<p>Inhoud</p>
11+
</Body>
12+
);
13+
expect(screen.getByText('Inhoud')).toBeInTheDocument();
14+
});
15+
16+
it('renders as a <div> element', () => {
17+
const { container } = render(<Body />);
18+
expect(container.firstChild?.nodeName).toBe('DIV');
19+
});
20+
21+
it('always has base dsn-body class', () => {
22+
const { container } = render(<Body />);
23+
expect(container.firstChild).toHaveClass('dsn-body');
24+
});
25+
26+
it('applies custom className', () => {
27+
const { container } = render(<Body className="custom" />);
28+
expect(container.firstChild).toHaveClass('dsn-body');
29+
expect(container.firstChild).toHaveClass('custom');
30+
});
31+
32+
it('forwards ref to the div element', () => {
33+
const ref = createRef<HTMLDivElement>();
34+
const { container } = render(<Body ref={ref} />);
35+
expect(ref.current).toBe(container.firstChild);
36+
});
37+
38+
it('passes additional HTML attributes', () => {
39+
const { container } = render(<Body data-testid="body" id="root" />);
40+
expect(container.firstChild).toHaveAttribute('data-testid', 'body');
41+
expect(container.firstChild).toHaveAttribute('id', 'root');
42+
});
43+
});
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import React from 'react';
2+
import { classNames } from '@dsn/core';
3+
import './Body.css';
4+
5+
export interface BodyProps extends React.HTMLAttributes<HTMLDivElement> {
6+
/**
7+
* Child-elementen die de document-level stijlen erven
8+
*/
9+
children?: React.ReactNode;
10+
}
11+
12+
/**
13+
* Body component
14+
* Stelt document-level CSS stijlen in via CSS inheritance zodat alle child-elementen
15+
* automatisch de juiste typografie, kleur en achtergrond erven.
16+
*
17+
* @example
18+
* ```tsx
19+
* // In een applicatie: wrap de root-content
20+
* <Body>
21+
* <h1>Mijn pagina</h1>
22+
* <p>Inhoud met geërfde stijlen</p>
23+
* </Body>
24+
*
25+
* // Of pas de CSS class direct toe op het <body> element
26+
* // <body class="dsn-body">
27+
* ```
28+
*/
29+
export const Body = React.forwardRef<HTMLDivElement, BodyProps>(
30+
({ className, children, ...props }, ref) => {
31+
const classes = classNames('dsn-body', className);
32+
33+
return (
34+
<div ref={ref} className={classes} {...props}>
35+
{children}
36+
</div>
37+
);
38+
}
39+
);
40+
41+
Body.displayName = 'Body';
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { Body } from './Body';
2+
export type { BodyProps } from './Body';

packages/components-react/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
*/
77

88
// Layout Components
9+
export * from './Body';
910
export * from './Container';
1011
export * from './Grid';
1112
export * from './Stack';

packages/storybook/.storybook/preview.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ const preview: Preview = {
157157
// Update body classes for any CSS scoping
158158
const densityClass =
159159
projectType === 'information-dense' ? 'dense' : 'default';
160-
document.body.className = `dsn-theme-${theme} dsn-mode-${mode} dsn-density-${densityClass}`;
160+
document.body.className = `dsn-body dsn-theme-${theme} dsn-mode-${mode} dsn-density-${densityClass}`;
161161
}
162162

163163
return Story();
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Body
2+
3+
Stelt document-level CSS stijlen in zodat alle child-elementen via cascade de juiste typografie, kleur en achtergrond erven.
4+
5+
## Doel
6+
7+
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.
8+
9+
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.
10+
11+
In Storybook is `dsn-body` als global decorator toegepast op alle stories en 'Voorbeeld'-secties, zodat componenten altijd in de juiste omgeving worden getoond.
12+
13+
<!-- VOORBEELD -->
14+
15+
## Use when
16+
17+
- Je de juiste document-level stijlen wilt instellen voor een pagina of applicatie.
18+
- Je in Storybook een geïsoleerde preview wilt wrappen met de juiste omgevingsstijlen.
19+
20+
## Don't use when
21+
22+
- Je de stijlen voor een specifieke sectie wilt overschrijven — gebruik dan component-specifieke klassen of CSS custom properties.
23+
- Je een ander thema of een andere modus per sectie wilt toepassen — dit is een document-level wrapper, geen theming-component.
24+
25+
## Best practices
26+
27+
- **Pas `dsn-body` toe op `<body>`**: dit is de bedoelde use case. De CSS cascade werkt dan door voor de hele pagina.
28+
- **Gebruik `<Body>` als React-wrapper** wanneer je geen controle hebt over het `<body>` element (bijv. in geïsoleerde previews of micro-frontends).
29+
- **Voeg geen extra stijlen toe aan `dsn-body`**: de class dient uitsluitend als cascade-startpunt. Paginaspecifieke stijlen horen in layout-componenten.
30+
31+
## Accessibility
32+
33+
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.
34+
35+
## Design tokens
36+
37+
| CSS property | Token | Beschrijving |
38+
| ------------------ | ------------------------------------ | --------------------------------- |
39+
| `background-color` | `--dsn-color-neutral-bg-document` | Achtergrondkleur van het document |
40+
| `color` | `--dsn-color-neutral-color-document` | Standaard tekstkleur |
41+
| `font-family` | `--dsn-text-font-family-default` | Standaard lettertype |
42+
| `font-size` | `--dsn-text-font-size-md` | Standaard lettergrootte |
43+
| `line-height` | `--dsn-text-line-height-md` | Standaard regelafstand |
44+
| `font-weight` | `--dsn-text-font-weight-default` | Standaard lettergewicht |
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { Meta, Story, Markdown } from '@storybook/blocks';
2+
import * as BodyStories from './Body.stories';
3+
import docs from './Body.docs.md?raw';
4+
import { PreviewFrame, CodeTabs } from './components';
5+
6+
export const [intro, rest] = docs.split('<!-- VOORBEELD -->');
7+
8+
<Meta of={BodyStories} />
9+
10+
<Markdown>{intro}</Markdown>
11+
12+
## Voorbeeld
13+
14+
<PreviewFrame>
15+
<Story of={BodyStories.Default} />
16+
</PreviewFrame>
17+
18+
<CodeTabs
19+
of={BodyStories.Default}
20+
html={`<body class="dsn-body">
21+
<!-- paginainhoud -->
22+
</body>`}
23+
react={`<Body>
24+
{/* paginainhoud */}
25+
</Body>`}
26+
/>
27+
28+
<Markdown>{rest}</Markdown>
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import type { Meta, StoryObj } from '@storybook/react';
2+
import {
3+
Body,
4+
type BodyProps,
5+
Heading,
6+
Paragraph,
7+
} from '@dsn/components-react';
8+
import DocsPage from './Body.docs.mdx';
9+
10+
const meta: Meta<typeof Body> = {
11+
title: 'Foundations/Body',
12+
component: Body,
13+
parameters: {
14+
docs: { page: DocsPage },
15+
dsn: {
16+
htmlTemplate: () =>
17+
`<body class="dsn-body">\n <!-- paginainhoud -->\n</body>`,
18+
},
19+
},
20+
argTypes: {
21+
children: { control: false },
22+
},
23+
};
24+
25+
export default meta;
26+
type Story = StoryObj<typeof Body>;
27+
28+
export const Default: Story = {
29+
render: (args: BodyProps) => (
30+
<Body {...args}>
31+
<Heading level={1}>Paginatitel</Heading>
32+
<Paragraph>
33+
Dit is een voorbeeldparagraaf. De typografie, kleur en achtergrond zijn
34+
ingesteld door het Body component en worden via CSS cascade overgenomen
35+
door alle child-elementen die zelf geen specifieke waarden definiëren.
36+
</Paragraph>
37+
</Body>
38+
),
39+
};

0 commit comments

Comments
 (0)