@mattilsynet/design 3.2.9 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/mtds/ai/AGENTS.md +892 -0
- package/mtds/ai/alert.mdx +63 -0
- package/mtds/ai/alert.stories.tsx +128 -0
- package/mtds/ai/analytics.mdx +185 -0
- package/mtds/ai/app.mdx +60 -0
- package/mtds/ai/app.stories.tsx +897 -0
- package/mtds/ai/atlas.mdx +82 -0
- package/mtds/ai/atlas.stories.tsx +424 -0
- package/mtds/ai/avatar.mdx +45 -0
- package/mtds/ai/avatar.stories.tsx +109 -0
- package/mtds/ai/badge.mdx +70 -0
- package/mtds/ai/badge.stories.tsx +122 -0
- package/mtds/ai/breadcrumbs.mdx +36 -0
- package/mtds/ai/breadcrumbs.stories.tsx +158 -0
- package/mtds/ai/button.mdx +179 -0
- package/mtds/ai/button.stories.tsx +440 -0
- package/mtds/ai/card.mdx +51 -0
- package/mtds/ai/card.stories.tsx +469 -0
- package/mtds/ai/chart.mdx +67 -0
- package/mtds/ai/chart.stories.tsx +519 -0
- package/mtds/ai/chip.mdx +71 -0
- package/mtds/ai/chip.stories.tsx +211 -0
- package/mtds/ai/details.mdx +33 -0
- package/mtds/ai/details.stories.tsx +91 -0
- package/mtds/ai/dialog.mdx +38 -0
- package/mtds/ai/dialog.stories.tsx +373 -0
- package/mtds/ai/divider.mdx +19 -0
- package/mtds/ai/divider.stories.tsx +50 -0
- package/mtds/ai/errorsummary.mdx +26 -0
- package/mtds/ai/errorsummary.stories.tsx +137 -0
- package/mtds/ai/field.mdx +86 -0
- package/mtds/ai/field.stories.tsx +863 -0
- package/mtds/ai/fieldset.mdx +126 -0
- package/mtds/ai/fieldset.stories.tsx +298 -0
- package/mtds/ai/fileupload.mdx +16 -0
- package/mtds/ai/fileupload.stories.tsx +126 -0
- package/mtds/ai/helptext.mdx +24 -0
- package/mtds/ai/helptext.stories.tsx +106 -0
- package/mtds/ai/input.mdx +223 -0
- package/mtds/ai/input.stories.tsx +352 -0
- package/mtds/ai/law.mdx +115 -0
- package/mtds/ai/law.stories.tsx +168 -0
- package/mtds/ai/layout.mdx +145 -0
- package/mtds/ai/layout.stories.tsx +443 -0
- package/mtds/ai/link.mdx +45 -0
- package/mtds/ai/link.stories.tsx +44 -0
- package/mtds/ai/logo.mdx +86 -0
- package/mtds/ai/logo.stories.tsx +146 -0
- package/mtds/ai/pagination.mdx +136 -0
- package/mtds/ai/pagination.stories.tsx +404 -0
- package/mtds/ai/popover.mdx +86 -0
- package/mtds/ai/popover.stories.tsx +355 -0
- package/mtds/ai/print.mdx +96 -0
- package/mtds/ai/print.stories.tsx +839 -0
- package/mtds/ai/progress.mdx +41 -0
- package/mtds/ai/progress.stories.tsx +141 -0
- package/mtds/ai/skeleton.mdx +26 -0
- package/mtds/ai/skeleton.stories.tsx +131 -0
- package/mtds/ai/spinner.mdx +26 -0
- package/mtds/ai/spinner.stories.tsx +72 -0
- package/mtds/ai/steps.mdx +37 -0
- package/mtds/ai/steps.stories.tsx +568 -0
- package/mtds/ai/table.mdx +124 -0
- package/mtds/ai/table.stories.tsx +1715 -0
- package/mtds/ai/tabs.mdx +106 -0
- package/mtds/ai/tabs.stories.tsx +159 -0
- package/mtds/ai/tag.mdx +49 -0
- package/mtds/ai/tag.stories.tsx +111 -0
- package/mtds/ai/toast.mdx +67 -0
- package/mtds/ai/toast.stories.tsx +215 -0
- package/mtds/ai/togglegroup.mdx +75 -0
- package/mtds/ai/togglegroup.stories.tsx +96 -0
- package/mtds/ai/tooltip.mdx +32 -0
- package/mtds/ai/tooltip.stories.tsx +34 -0
- package/mtds/ai/typography.mdx +67 -0
- package/mtds/ai/typography.stories.tsx +798 -0
- package/mtds/ai/validation.mdx +19 -0
- package/mtds/ai/validation.stories.tsx +45 -0
- package/mtds/atlas/atlas-element.js +1 -1
- package/mtds/chart/chart-lines.js +19 -19
- package/mtds/chart/chart-lines.js.map +1 -1
- package/mtds/chart/chart.css.js +16 -1
- package/mtds/chart/chart.css.js.map +1 -1
- package/mtds/chart/chart.stories.d.ts +1 -0
- package/mtds/index.iife.js +32 -17
- package/mtds/package.json.js +1 -1
- package/mtds/styles.css +1 -1
- package/mtds/table/table-observer.js +26 -15
- package/mtds/table/table-observer.js.map +1 -1
- package/package.json +4 -2
|
@@ -0,0 +1,892 @@
|
|
|
1
|
+
# @mattilsynet/design — LLM Reference
|
|
2
|
+
|
|
3
|
+
You are a senior frontend engineer who converts Figma sketches/screenshots into production-ready React code, and iterates on existing code, using the `@mattilsynet/design` (mtds) design system. Make no assumptions. Create no new components. This file is your full source of truth.
|
|
4
|
+
|
|
5
|
+
Use **components** from `@mattilsynet/design/react`, **attributes** (`data-*`) for variants and density, and **CSS tokens** (`--mtds-*`) for any custom styling. Never use raw `px`, hex colors, or inline `style` for layout/spacing/color. Avoid creating new class names unless strictly necessary.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 0. Quick start (read first, every time)
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
// In the application entry file, ONCE:
|
|
13
|
+
import '@mattilsynet/design';
|
|
14
|
+
import '@mattilsynet/design/styles.css';
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
// In any file that uses components:
|
|
19
|
+
import { Button, Card, Flex, Grid, Heading /* ... */ } from '@mattilsynet/design/react';
|
|
20
|
+
import { CheckIcon /* ... */ } from '@phosphor-icons/react/ssr';
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The minimal correct page skeleton:
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
<App>
|
|
27
|
+
<App.Header><Logo href="/">App name</Logo></App.Header>
|
|
28
|
+
<App.Toggle />
|
|
29
|
+
<App.Sidebar><App.Sticky as="menu">{/* icon-only buttons */}</App.Sticky></App.Sidebar>
|
|
30
|
+
<App.Main>
|
|
31
|
+
<Flex data-center="2xl" data-gap="6">
|
|
32
|
+
<Card>{/* every leaf node lives inside Card or Group */}</Card>
|
|
33
|
+
</Flex>
|
|
34
|
+
</App.Main>
|
|
35
|
+
</App>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The five rules that catch 80% of mistakes:
|
|
39
|
+
|
|
40
|
+
1. Every leaf node inside `<App.Main>` lives inside a `<Card>` or `<Group>` — never directly in `<App.Main>`.
|
|
41
|
+
2. `data-center` goes on `<Flex>`/`<Grid>` **inside** `<Card>` or `<Group>`. Never on `<App>` or direct children of `<App.Main>`.
|
|
42
|
+
3. No raw `<h1>`–`<h6>`. Use `<Heading as="hN" data-size="…">`.
|
|
43
|
+
4. No raw `px`, `rem`, hex, `rgb()`, named colors, inline `style`, or Tailwind for layout/spacing/color/radius/typography. Use `data-pad` or `data-gap`, and css tokens when absolutely necessary.
|
|
44
|
+
5. Sidebar Buttons are icon-only. Label via `data-tooltip="…"` (and `aria-label` when needed).
|
|
45
|
+
|
|
46
|
+
When in doubt, read `@mattilsynet/design/mtds/ai/<name>.mdx` for the component you're using.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## 1. Mandatory rules (MUST / MUST NOT)
|
|
51
|
+
|
|
52
|
+
1. Import `'@mattilsynet/design'` and `'@mattilsynet/design/styles.css'` once in the entry file.
|
|
53
|
+
2. Do **not** set `data-color-scheme` on `<html>`.
|
|
54
|
+
3. Import React components from `@mattilsynet/design/react`. Import icons from `@phosphor-icons/react/ssr` (ensure `@phosphor-icons/react` is installed).
|
|
55
|
+
4. Do **not** use raw `px`, `rem`, hex, `rgb()`, named colors, inline `style`, or Tailwind utilities for layout, spacing, color, radius, or typography. Use design tokens or component attributes.
|
|
56
|
+
5. Use `<App>` as the page shell (see §6).
|
|
57
|
+
6. Do **not** create a new container component when `Card`, `Group`, `Flex`, or `Grid` already cover the case.
|
|
58
|
+
7. Do **not** write raw `<h1>`–`<h6>`. Use `<Heading as="hN">`.
|
|
59
|
+
8. Read `@mattilsynet/design/mtds/ai/<name>.mdx` (and `<name>.stories.tsx`) before using a component you have not used in this session.
|
|
60
|
+
9. For genuinely custom CSS, use a CSS file or CSS Module that references `--mtds-*` tokens. Never inline `style`.
|
|
61
|
+
10. Verify all requirements against the **§11 checklist** before returning code.
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## 2. Forbidden patterns (read before generating)
|
|
66
|
+
|
|
67
|
+
| Don't | Do |
|
|
68
|
+
|---|---|
|
|
69
|
+
| `style={{…}}` for layout, spacing, or color | Layout component, `data-*` attribute, or token in CSS |
|
|
70
|
+
| `className="p-4 gap-3 text-red-500"` (Tailwind) | Layout components with attributes (or tokens if absolutely necessary) |
|
|
71
|
+
| Hex / `rgb()` / named colors (`#c83719`, `red`) | `var(--mtds-color-…)` under the appropriate `data-color` parent |
|
|
72
|
+
| Raw `px`/`rem` for spacing (`padding: 16px`) | `var(--mtds-4)`, or `data-gap` / `data-pad` on a layout component |
|
|
73
|
+
| `<div style="display:flex">` | `<Flex>` |
|
|
74
|
+
| `<h2>Title</h2>` | `<Heading as="h2">Title</Heading>` |
|
|
75
|
+
| `<small>helper</small>`, `<p style="font-size:1.3em">` | `<Muted>`, `<Ingress>` |
|
|
76
|
+
| Made-up tokens: `var(--mtds-16)`, `var(--mtds-20)` | Snap to the nearest existing token (see §5.1) |
|
|
77
|
+
| Hard-coded palette token: `var(--mtds-color-danger-base-default)` | Set `data-color="danger"` on a parent and use the palette-agnostic token (`var(--mtds-color-base-default)`) |
|
|
78
|
+
| `data-center` on `<App>` or directly on `<App.Main>`'s child | Apply on `<Card>`/`<Group>`, or a `<Flex>`/`<Grid>` inside them |
|
|
79
|
+
| `<aside>` / `<nav>` for primary nav | `<App.Sidebar>` |
|
|
80
|
+
| Custom React state for sidebar open/close | `<App.Toggle>` |
|
|
81
|
+
| Visible text in a sidebar button | Icon only + `data-tooltip="Label"` |
|
|
82
|
+
| `<label>Name<input/></label>` outside `<Field>` | `<Field as="input" label="Name" />` (or compose with `<Field>`+`<Field.Label>`+`<Input>`) |
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## 3. Decision tree (intent → component)
|
|
87
|
+
|
|
88
|
+
### 3.1 Layout — pick the right container
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
Need a page shell? → <App> (see §6)
|
|
92
|
+
Need a content surface? → <Card> (white surface; one related unit)
|
|
93
|
+
Need a region of cards? → <Group> (tinted surface; collection of cards / meta UI)
|
|
94
|
+
Need to lay out children?
|
|
95
|
+
├─ Equal-width / stack → <Grid> (data-items="300" for responsive cols)
|
|
96
|
+
└─ Mixed widths / toolbar → <Flex>
|
|
97
|
+
Need text rhythm? → <Prose> (auto vertical spacing for body text)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
`Card` MAY be nested in `Group`. Avoid `Card` inside `Card`. `Flex`/`Grid` MAY sit between `<App.Main>` and the `Card`/`Group` to apply `data-center` and `data-gap`.
|
|
101
|
+
|
|
102
|
+
### 3.2 Intent → component
|
|
103
|
+
|
|
104
|
+
| You need to… | Use |
|
|
105
|
+
|---|---|
|
|
106
|
+
| Render a heading | `<Heading as="hN" data-size="…">` |
|
|
107
|
+
| Render lead/intro text | `<Ingress>` |
|
|
108
|
+
| Render small/secondary text | `<Muted>` |
|
|
109
|
+
| Render a label + value pair (optionally with icon) | `<Info>` |
|
|
110
|
+
| Trigger an action | `<Button>` (renders `<a>` when `href` is present) |
|
|
111
|
+
| Navigate to another URL inline in text | `<Link>` |
|
|
112
|
+
| Single form field (label + input + validation) | `<Field>` (see §9) |
|
|
113
|
+
| Group several related form fields | `<Fieldset>` (see §9) |
|
|
114
|
+
| Show validation errors at the top of a form | `<Errorsummary>` (single capital R) |
|
|
115
|
+
| Show a modal | `<Dialog>` |
|
|
116
|
+
| Show a non-modal floating overlay anchored to a trigger | `<Popover>` (uses native popover API) |
|
|
117
|
+
| Show a tooltip on an element | `data-tooltip="…"` on the element |
|
|
118
|
+
| Show inline contextual feedback (info/success/warning/danger) | `<Alert>` |
|
|
119
|
+
| Tabular data | `<Table>` |
|
|
120
|
+
| Tabbed content panels | `<Tabs>` |
|
|
121
|
+
| Multi-step progress / timeline | `<Steps>` |
|
|
122
|
+
| Read-only label/keyword | `<Tag>` |
|
|
123
|
+
| Status indicator on top of another element | `<Badge>` |
|
|
124
|
+
| Single-select toggle button group | `<Togglegroup>` (single capital G) |
|
|
125
|
+
| Interactive filter chip | `<Chip>` |
|
|
126
|
+
| Switch palette for a region | `data-color="…"` on any ancestor |
|
|
127
|
+
| Switch density for a region | `data-size="sm│md│lg"` on a layout container |
|
|
128
|
+
|
|
129
|
+
For anything else, find the right component in the **§12 index**.
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## 4. Global cascading attributes
|
|
134
|
+
|
|
135
|
+
These cascade to descendants from any element they're set on.
|
|
136
|
+
|
|
137
|
+
### 4.1 `data-color` — palette
|
|
138
|
+
|
|
139
|
+
| Value | Palette |
|
|
140
|
+
|---|---|
|
|
141
|
+
| `main` | Mattilsynet primary green (default) |
|
|
142
|
+
| `neutral` | Grey |
|
|
143
|
+
| `success` | Green |
|
|
144
|
+
| `info` | Blue |
|
|
145
|
+
| `warning` | Orange |
|
|
146
|
+
| `danger` | Red |
|
|
147
|
+
| `inverted` | Dark green with light text |
|
|
148
|
+
|
|
149
|
+
These seven are the only valid values. All `--mtds-color-*` tokens inside resolve against the nearest ancestor's palette automatically.
|
|
150
|
+
|
|
151
|
+
```tsx
|
|
152
|
+
<section data-color="danger">
|
|
153
|
+
<Heading>Feilseksjon</Heading>
|
|
154
|
+
<Button data-variant="primary">Slett</Button>
|
|
155
|
+
</section>
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### 4.2 `data-size` — density vs. component-local size
|
|
159
|
+
|
|
160
|
+
On layout containers (`Flex`, `Grid`, `Card`, `Group`, `<section>`, etc.), `data-size` controls **UI density** (padding, control heights) for descendants. Values: `sm` (compact), `md` (default), `lg` (spacious).
|
|
161
|
+
|
|
162
|
+
> ⚠ **Name collision.** On `Heading`, `Button`, `Info`, and several other components, `data-size` is a **component-local visual size** (e.g. `2xs`–`2xl` on `Heading`), not density. Always check the component's doc. Do not use `data-size` to change font-size on plain HTML elements — wrap in `<Heading>`, `<Ingress>`, or `<Muted>` instead.
|
|
163
|
+
|
|
164
|
+
### 4.3 `data-tooltip` — tooltip text
|
|
165
|
+
|
|
166
|
+
Place on any element. Tooltip text is automatically exposed to assistive tech. Use `data-tooltip-position="top│right│bottom│left"` to reposition.
|
|
167
|
+
|
|
168
|
+
```tsx
|
|
169
|
+
<Button data-tooltip="Save your changes">Save</Button>
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## 5. Token system
|
|
175
|
+
|
|
176
|
+
Tokens are namespaced `--mtds-*` (system) and `--mtdsc-*` (per-component override). All pixel values are **informative only** — the scale is fluid (resizes with density and root font-size). Always reference the token, never the literal value.
|
|
177
|
+
|
|
178
|
+
### 5.1 Spacing — `--mtds-{n}`
|
|
179
|
+
|
|
180
|
+
Prefer layout primitives (`Flex`, `Grid`, `Card`, `Group`) with `data-gap`, `data-pad`, or `data-items` over raw spacing tokens. Do **not** set `margin` — use `data-gap` or `data-pad`.
|
|
181
|
+
|
|
182
|
+
Valid `n` values: **`0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 18, 22, 26, 30`** (note the gap above 15). At default density, `n × 4 ≈ pixels`. Other indexes (`16`, `17`, `19`, `20`–`21`, `23`–`25`, `27`–`29`) **do not exist**.
|
|
183
|
+
|
|
184
|
+
```css
|
|
185
|
+
/* Do */
|
|
186
|
+
.x { padding: var(--mtds-4); gap: var(--mtds-3); }
|
|
187
|
+
|
|
188
|
+
/* Don't */
|
|
189
|
+
.x { padding: 16px; } /* raw px */
|
|
190
|
+
.x { padding: var(--mtds-16); } /* not in the scale */
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### 5.2 Color — `--mtds-color-{role}-{variant}`
|
|
194
|
+
|
|
195
|
+
Use **palette-agnostic** tokens. They resolve against the nearest ancestor's `data-color`.
|
|
196
|
+
|
|
197
|
+
| Role | Variants |
|
|
198
|
+
|---|---|
|
|
199
|
+
| `background` | `default`, `tinted` |
|
|
200
|
+
| `surface` | `default`, `tinted`, `hover`, `active` |
|
|
201
|
+
| `border` | `subtle`, `default`, `strong` |
|
|
202
|
+
| `text` | `subtle`, `default` |
|
|
203
|
+
| `base` | `default`, `hover`, `active`, `contrast-subtle`, `contrast-default` |
|
|
204
|
+
|
|
205
|
+
Roles are **not interchangeable**: text tokens are for text, surface tokens are for surfaces, etc.
|
|
206
|
+
|
|
207
|
+
```css
|
|
208
|
+
/* Do — works under any data-color */
|
|
209
|
+
.my-very-custom-component {
|
|
210
|
+
background: var(--mtds-color-surface-default);
|
|
211
|
+
color: var(--mtds-color-text-default);
|
|
212
|
+
border: 1px solid var(--mtds-color-border-default);
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Palette-prefixed tokens like `--mtds-color-danger-surface-tinted` exist in the CSS but **must not** be used directly. To get a danger surface, set `data-color="danger"` on a parent and use the agnostic token.
|
|
217
|
+
|
|
218
|
+
### 5.3 Typography
|
|
219
|
+
|
|
220
|
+
Prefer the components (`Heading`, `Ingress`, `Muted`, `Info`) over raw font-size tokens in markup. For custom CSS only:
|
|
221
|
+
|
|
222
|
+
```css
|
|
223
|
+
--mtds-font-family /* "Mattilsynet Sans" */
|
|
224
|
+
--mtds-font-weight-regular /* 400 */
|
|
225
|
+
--mtds-font-weight-medium /* 500 */
|
|
226
|
+
--mtds-font-weight-semibold /* 600 */
|
|
227
|
+
--mtds-font-weight-bold /* 700 */
|
|
228
|
+
--mtds-font-size-muted /* used by <Muted> */
|
|
229
|
+
--mtds-line-height-sm | -md
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Heading sizes (`2xs`–`2xl`) and body sizes (`xs`–`xl`) are exposed via the typography components — use `<Heading data-size="lg">`, not the underlying token.
|
|
233
|
+
|
|
234
|
+
### 5.4 Other tokens
|
|
235
|
+
|
|
236
|
+
```css
|
|
237
|
+
--mtds-border-radius-sm | -md | -lg | -xl | -full
|
|
238
|
+
--mtds-border-width-default | -focus
|
|
239
|
+
--mtds-box-shadow-sm | -md | -lg
|
|
240
|
+
--mtds-icon-size /* = --mtds-6 */
|
|
241
|
+
--mtds-icon-size-sm /* = --mtds-5 */
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### 5.5 Per-component overrides — `--mtdsc-{component}-{property-path}`
|
|
245
|
+
|
|
246
|
+
Each component exposes scoped custom properties. Discover the available ones for any component at the bottom of `mtds/ai/<name>.mdx` (the `<CssVariables component="…" />` block).
|
|
247
|
+
|
|
248
|
+
```css
|
|
249
|
+
.scoped { --mtdsc-button-border-color: var(--mtds-color-border-strong); }
|
|
250
|
+
.wide { --mtdsc-prose-max-width: 60rem; }
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## 6. App layout (page shell)
|
|
256
|
+
|
|
257
|
+
Every Mattilsynet web application/page MUST be built inside the `App` shell. It is the outermost layout that all other content lives within.
|
|
258
|
+
|
|
259
|
+
| Sub-component | Purpose |
|
|
260
|
+
|---|---|
|
|
261
|
+
| `App` | The shell. Supports `data-variant="mobilebar"` (see §6.6) |
|
|
262
|
+
| `App.Header` | Logo + global actions. `<Logo>` MUST be the first child |
|
|
263
|
+
| `App.Toggle` | Expand/minimize sidebar (also opens it as a modal on mobile) |
|
|
264
|
+
| `App.Sidebar` | Primary navigation. Modal on mobile, rail/expanded on desktop |
|
|
265
|
+
| `App.Sticky` | Scroll-direction-aware sticky wrapper for sidebar contents only |
|
|
266
|
+
| `App.Main` | Page content. Every leaf node MUST sit inside a `<Card>` or `<Group>` (see §6.3) |
|
|
267
|
+
| `App.Footer` | Optional footer |
|
|
268
|
+
| `App.Script` | SSR/Next.js only. Render in `<head>` to prevent FOUC (see §6.7) |
|
|
269
|
+
|
|
270
|
+
Required source order: **`App.Header` → `App.Toggle` → `App.Sidebar` → `App.Main` → `App.Footer`**.
|
|
271
|
+
|
|
272
|
+
### 6.1 Canonical composition
|
|
273
|
+
|
|
274
|
+
```tsx
|
|
275
|
+
import { App, Logo, Button, Popover, Avatar, Card } from '@mattilsynet/design/react';
|
|
276
|
+
import { BellIcon, UserIcon, SignOutIcon, GearIcon, SignatureIcon, ListChecksIcon, MagnifyingGlassIcon, PlantIcon } from '@phosphor-icons/react/ssr';
|
|
277
|
+
|
|
278
|
+
<App>
|
|
279
|
+
<App.Header>
|
|
280
|
+
<Logo href="/">
|
|
281
|
+
<PlantIcon weight="fill" />
|
|
282
|
+
Digiplant
|
|
283
|
+
</Logo>
|
|
284
|
+
<Button><BellIcon /></Button>
|
|
285
|
+
<Button aria-label="Meny" popoverTarget="user-menu">
|
|
286
|
+
<Avatar data-size="sm" />
|
|
287
|
+
</Button>
|
|
288
|
+
<Popover as="menu" popover="auto" id="user-menu">
|
|
289
|
+
<li><Button href="/profil"><UserIcon />Profil</Button></li>
|
|
290
|
+
<li><Button href="/innstillinger"><GearIcon />Innstillinger</Button></li>
|
|
291
|
+
<li><Button href="/logout"><SignOutIcon />Logg ut</Button></li>
|
|
292
|
+
</Popover>
|
|
293
|
+
</App.Header>
|
|
294
|
+
|
|
295
|
+
<App.Toggle />
|
|
296
|
+
|
|
297
|
+
<App.Sidebar>
|
|
298
|
+
<App.Sticky as="menu">
|
|
299
|
+
<li><Button href="/soknader" aria-current="page" data-tooltip="Søknader"><SignatureIcon /></Button></li>
|
|
300
|
+
<li><Button href="/behandling" data-tooltip="Behandling"><ListChecksIcon /></Button></li>
|
|
301
|
+
<li><Button href="/sok" data-tooltip="Søk"><MagnifyingGlassIcon /></Button></li>
|
|
302
|
+
</App.Sticky>
|
|
303
|
+
</App.Sidebar>
|
|
304
|
+
|
|
305
|
+
<App.Main>
|
|
306
|
+
<Card>
|
|
307
|
+
<Flex data-center="2xl" data-gap="6">
|
|
308
|
+
{/* page content here — see §6.3 */}
|
|
309
|
+
</Flex>
|
|
310
|
+
</Card>
|
|
311
|
+
</App.Main>
|
|
312
|
+
|
|
313
|
+
<App.Footer>
|
|
314
|
+
<Logo href="/" />
|
|
315
|
+
</App.Footer>
|
|
316
|
+
</App>
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### 6.2 Sidebar buttons MUST be icon-only
|
|
320
|
+
|
|
321
|
+
Every interactive child of `<App.Sidebar>` MUST contain **only an icon** as visible content. The textual label MUST be supplied via `data-tooltip="…"` (and `aria-label` where the icon alone is not self-descriptive).
|
|
322
|
+
|
|
323
|
+
The design system manages how labels appear: when the sidebar is minimized the tooltip is shown on hover; when expanded the design system reveals the label inline automatically. Hard-coding text inside the button breaks both states.
|
|
324
|
+
|
|
325
|
+
`<Button>` elements inside `<App.Sidebar>` are most commonly wrapped in a `<menu>` → `<li>` structure (often via `<App.Sticky as="menu">`) for vertical stacking and good accessibility.
|
|
326
|
+
|
|
327
|
+
```tsx
|
|
328
|
+
// Do — icon-only, label via data-tooltip
|
|
329
|
+
<Button href="/soknader" data-tooltip="Søknader">
|
|
330
|
+
<SignatureIcon />
|
|
331
|
+
</Button>
|
|
332
|
+
|
|
333
|
+
// Don't — visible text inside a sidebar button
|
|
334
|
+
<Button href="/soknader">
|
|
335
|
+
<SignatureIcon />
|
|
336
|
+
Søknader
|
|
337
|
+
</Button>
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### 6.3 `App.Main` content rules (the centering rule)
|
|
341
|
+
|
|
342
|
+
`<App.Main>` is a layout region, not a content surface.
|
|
343
|
+
|
|
344
|
+
- Every **leaf** node (text, heading, paragraph, raw HTML element) inside `<App.Main>` MUST be wrapped in at least one `<Card>` or `<Group>`.
|
|
345
|
+
- Layout primitives (`<Flex>`, `<Grid>`) MAY sit between `<App.Main>` and the `<Card>`/`<Group>` — but if you need to apply `data-center`, you must have a `<Card>` or `<Group>` as parent.
|
|
346
|
+
- Use `<Card>` for a single related content unit on a solid surface (e.g. one inspection, one entity detail page).
|
|
347
|
+
- Use `<Group>` for a collection of cards or meta-UI on a tinted surface.
|
|
348
|
+
|
|
349
|
+
```tsx
|
|
350
|
+
// Do
|
|
351
|
+
<App.Main>
|
|
352
|
+
<Card>
|
|
353
|
+
<Grid data-center="2xl" data-gap="6">
|
|
354
|
+
<Heading as="h1" data-size="xl">Tilsyn 12345</Heading>
|
|
355
|
+
<p>…</p>
|
|
356
|
+
<Group>
|
|
357
|
+
<Card>…</Card>
|
|
358
|
+
<Card>…</Card>
|
|
359
|
+
</Group>
|
|
360
|
+
</Grid>
|
|
361
|
+
</Card>
|
|
362
|
+
</App.Main>
|
|
363
|
+
|
|
364
|
+
// Do multiple Cards in Group in App.Main
|
|
365
|
+
<App.Main>
|
|
366
|
+
<Group>
|
|
367
|
+
<Prose>
|
|
368
|
+
<Heading as="h1" data-size="xl">Liste over tilsyn</Heading>
|
|
369
|
+
<Card>…</Card>
|
|
370
|
+
<Card>…</Card>
|
|
371
|
+
</Prose>
|
|
372
|
+
</Group>
|
|
373
|
+
</App.Main>
|
|
374
|
+
|
|
375
|
+
// Don't — bare content directly in App.Main
|
|
376
|
+
<App.Main>
|
|
377
|
+
<Heading as="h1" data-size="xl">Tilsyn 12345</Heading>
|
|
378
|
+
<p>…</p>
|
|
379
|
+
</App.Main>
|
|
380
|
+
|
|
381
|
+
// Don't — data-center on App or App.Main
|
|
382
|
+
<App data-center="2xl">…</App>
|
|
383
|
+
|
|
384
|
+
// Don't — data-center on direct child of App.Main
|
|
385
|
+
<App><App.Main><Grid data-center="2xl"></Grid></App.Main></App>
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
### 6.4 Expand / minimize sidebar
|
|
389
|
+
|
|
390
|
+
- `<App.Toggle>` renders the standard expand/minimize button.
|
|
391
|
+
- Do **not** manage sidebar visibility with custom React state — always use `<App.Toggle>`.
|
|
392
|
+
|
|
393
|
+
### 6.5 Sticky sidebar content
|
|
394
|
+
|
|
395
|
+
Wrap the contents of `<App.Sidebar>` in `<App.Sticky>` to get scroll-direction-aware sticky behavior (the sidebar stays visible when scrolling up and slides away when scrolling down). `<App.Sticky>` is for sidebar contents — do not use it elsewhere.
|
|
396
|
+
|
|
397
|
+
### 6.6 Mobile bottom bar variant
|
|
398
|
+
|
|
399
|
+
```tsx
|
|
400
|
+
<App data-variant="mobilebar">…</App>
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
- Converts the sidebar into a mobile bottom navigation bar.
|
|
404
|
+
- Use only when there are 5 or fewer top-level navigation items.
|
|
405
|
+
- Headings, dividers, and elements with `data-app-expanded="false"` are auto-hidden in this variant.
|
|
406
|
+
|
|
407
|
+
### 6.7 SSR / Next.js
|
|
408
|
+
|
|
409
|
+
To prevent flash of unstyled content (FOUC) when the persisted expanded/minimized state is restored, render `<App.Script />` inside `<head>`:
|
|
410
|
+
|
|
411
|
+
```tsx
|
|
412
|
+
// app/layout.tsx (Next.js)
|
|
413
|
+
<html>
|
|
414
|
+
<head>
|
|
415
|
+
<App.Script />
|
|
416
|
+
</head>
|
|
417
|
+
<body>{children}</body>
|
|
418
|
+
</html>
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
---
|
|
422
|
+
|
|
423
|
+
## 7. Layout primitives
|
|
424
|
+
|
|
425
|
+
### 7.1 When to use which
|
|
426
|
+
|
|
427
|
+
| Component | Use when | Surface |
|
|
428
|
+
|---|---|---|
|
|
429
|
+
| `Flex` | Children have different widths (button rows, toolbars) | none |
|
|
430
|
+
| `Grid` | Children should be equal-width (card grids) or stack vertically | none |
|
|
431
|
+
| `Card` | Group related content as a single unit | `surface-default` (white) |
|
|
432
|
+
| `Group` | Group several cards or meta-UI as a region | `background-tinted` (translucent grey) |
|
|
433
|
+
|
|
434
|
+
`Card` MAY be nested inside `Group`. Avoid `Card` inside `Card`.
|
|
435
|
+
|
|
436
|
+
### 7.2 Shared attributes — `Flex` and `Grid`
|
|
437
|
+
|
|
438
|
+
| Attribute | Values | Notes |
|
|
439
|
+
|---|---|---|
|
|
440
|
+
| `data-gap`, `data-row-gap`, `data-column-gap` | Same set as §5.1 (`0`–`15`, `18`, `22`, `26`, `30`) | Default `data-gap="3"` |
|
|
441
|
+
| `data-items` | `auto`, `full`, or `25`, `50`, `75`, `100`, `150`, `200`, `250`, `300`, `350`, `400`, `450`, `500` | Min child width in **px** before wrapping. `full` = single column (Flex only). `auto` = intrinsic |
|
|
442
|
+
| `data-align` | `normal`, `stretch`, `start`, `center`, `end` | `align-items` |
|
|
443
|
+
| `data-align-content` | `start`, `center`, `end`, `space-between`, `space-around`, `space-evenly` | `align-content` |
|
|
444
|
+
| `data-justify` | `start`, `center`, `end`, `space-between`, `space-around`, `space-evenly` | `justify-content` |
|
|
445
|
+
| `data-center` | `sm`, `md`, `lg`, `xl`, `2xl` | Max-width + center + side padding. |
|
|
446
|
+
| `data-nowrap` | boolean | Prevent wrapping |
|
|
447
|
+
| `data-fixed` | boolean | Children don't grow (Flex) / `auto-fill` (Grid) |
|
|
448
|
+
|
|
449
|
+
### 7.3 Flex child attributes
|
|
450
|
+
|
|
451
|
+
| Attribute | Values | Notes |
|
|
452
|
+
|---|---|---|
|
|
453
|
+
| `data-self` | Same set as `data-items` | Min width for this child |
|
|
454
|
+
| `data-align-self` | `start`, `center`, `end`, `stretch` | |
|
|
455
|
+
| `data-justify-self` | `start`, `center`, `end` | |
|
|
456
|
+
|
|
457
|
+
### 7.4 `Card` and `Group`
|
|
458
|
+
|
|
459
|
+
| Attribute | Values | Notes |
|
|
460
|
+
|---|---|---|
|
|
461
|
+
| `data-pad` | Single index from §5.1, or `{vertical}-{horizontal}` (e.g. `4-6`) | Inner padding. Default `5`. Pair form is fluid between viewport breakpoints |
|
|
462
|
+
| `data-radius` | `sm`, `md`, `lg`, `xl` | Defaults: Card `lg`, Group `xl` |
|
|
463
|
+
|
|
464
|
+
`Card` only:
|
|
465
|
+
|
|
466
|
+
| Attribute | Values | Notes |
|
|
467
|
+
|---|---|---|
|
|
468
|
+
| `href` | string | Renders Card as `<a>` (whole card becomes a link) |
|
|
469
|
+
| `data-clickdelegatefor` | CSS selector | Click anywhere on the card triggers the matching descendant (e.g. `"#details-link"`) |
|
|
470
|
+
|
|
471
|
+
### 7.5 Quick reference
|
|
472
|
+
|
|
473
|
+
```tsx
|
|
474
|
+
// Page container, centered, max-width 2xl
|
|
475
|
+
<Card>
|
|
476
|
+
<Grid data-center="2xl">
|
|
477
|
+
<Heading as="h1" data-size="xl">Page title</Heading>
|
|
478
|
+
</Grid>
|
|
479
|
+
</Card>
|
|
480
|
+
|
|
481
|
+
// Button row
|
|
482
|
+
<Flex>
|
|
483
|
+
<Button data-variant="primary">Save</Button>
|
|
484
|
+
<Button>Cancel</Button>
|
|
485
|
+
</Flex>
|
|
486
|
+
|
|
487
|
+
// Responsive equal-width card grid
|
|
488
|
+
<Grid data-items="300" data-gap="6">
|
|
489
|
+
<Card>Card 1</Card>
|
|
490
|
+
<Card>Card 2</Card>
|
|
491
|
+
<Card>Card 3</Card>
|
|
492
|
+
</Grid>
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
---
|
|
496
|
+
|
|
497
|
+
## 8. Typography components
|
|
498
|
+
|
|
499
|
+
### 8.1 `Heading`
|
|
500
|
+
|
|
501
|
+
| Attribute | Values | Notes |
|
|
502
|
+
|---|---|---|
|
|
503
|
+
| `as` | `h1`–`h6` | **Semantic** level. Default `h2`. Always set explicitly for accessibility |
|
|
504
|
+
| `data-size` | `2xs`, `xs`, `sm`, `md`, `lg`, `xl`, `2xl` | **Visual** size (independent of `as`) |
|
|
505
|
+
| `data-justify` | `start`, `center`, `end` | Text alignment |
|
|
506
|
+
|
|
507
|
+
`Heading` auto-sizes and aligns any `<svg>` child (no inner `<Flex>` needed).
|
|
508
|
+
|
|
509
|
+
```tsx
|
|
510
|
+
<Heading as="h2" data-size="sm"><CheckIcon /> Approved</Heading>
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
### 8.2 `Prose`
|
|
514
|
+
|
|
515
|
+
Wraps body/editorial text and applies typographic margins to **all direct children**. Tuned for `<p>`, `<ul>`, `<ol>`, `<h1>`–`<h6>`, `<figure>`, and `<Heading>`.
|
|
516
|
+
|
|
517
|
+
- Default max-width: `45rem` (override with `--mtdsc-prose-max-width`).
|
|
518
|
+
- Layout primitives (`<Flex>`, `<Grid>`) inside `<Prose>` will also receive the rhythm margin — keep them outside `<Prose>` if you don't want that.
|
|
519
|
+
|
|
520
|
+
```tsx
|
|
521
|
+
<Prose>
|
|
522
|
+
<Heading as="h2">Section</Heading>
|
|
523
|
+
<p>Body text with automatic rhythm.</p>
|
|
524
|
+
<ul><li>Item</li></ul>
|
|
525
|
+
</Prose>
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
### 8.3 `Ingress` and `Muted`
|
|
529
|
+
|
|
530
|
+
```tsx
|
|
531
|
+
<Ingress>Lead paragraph introducing the topic.</Ingress>
|
|
532
|
+
<Muted>Last updated 06.05.2026</Muted>
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
### 8.4 `Info`
|
|
536
|
+
|
|
537
|
+
Responsive label + value layout. Use for metadata, key/value pairs, or a value next to an indicator icon. Wrap the label in `<strong>`.
|
|
538
|
+
|
|
539
|
+
| Attribute | Values | Notes |
|
|
540
|
+
|---|---|---|
|
|
541
|
+
| `data-variant` | `regular` (default), `circle` | `circle` wraps the icon in a colored circle. Use only when the icon must draw visual attention; show **both** label and value |
|
|
542
|
+
|
|
543
|
+
```tsx
|
|
544
|
+
// Label + value
|
|
545
|
+
<Info><strong>Organization</strong>Fisk AS</Info>
|
|
546
|
+
|
|
547
|
+
// Icon + label + value
|
|
548
|
+
<Info><CheckIcon /><strong>Status</strong>Approved</Info>
|
|
549
|
+
|
|
550
|
+
// Emphasised: icon in colored circle (palette comes from ancestor data-color)
|
|
551
|
+
<Info data-variant="circle">
|
|
552
|
+
<CheckIcon /><strong>Status</strong>Approved
|
|
553
|
+
</Info>
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
To color the circle, set `data-color` on an ancestor (e.g. wrap the `Info` in a `<div data-color="success">`).
|
|
557
|
+
|
|
558
|
+
---
|
|
559
|
+
|
|
560
|
+
## 9. Forms
|
|
561
|
+
|
|
562
|
+
Forms in mtds compose `<Field>` (single input row), `<Fieldset>` (grouped fields), `<Errorsummary>` (top-of-form error list), and `<Validation>` (field-level error message). The exact React export names matter — see §12.
|
|
563
|
+
|
|
564
|
+
### 9.1 `Field` — two usage modes
|
|
565
|
+
|
|
566
|
+
**Mode A (composition):** wrap children manually. Use `<Field.Label>`, `<Field.Description>`, and an `<Input>` (or other input). Use this when you need fine control.
|
|
567
|
+
|
|
568
|
+
```tsx
|
|
569
|
+
<Field>
|
|
570
|
+
<Field.Label>Name</Field.Label>
|
|
571
|
+
<Field.Description>Your full legal name.</Field.Description>
|
|
572
|
+
<Input />
|
|
573
|
+
</Field>
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
**Mode B (polymorphic shorthand):** `<Field as="…" label="…" />` renders the complete row.
|
|
577
|
+
|
|
578
|
+
```tsx
|
|
579
|
+
<Field
|
|
580
|
+
as="input"
|
|
581
|
+
type="text"
|
|
582
|
+
label="Name"
|
|
583
|
+
description="Your full legal name."
|
|
584
|
+
validation="Name is required."
|
|
585
|
+
helpText="Enter the name as it appears on your ID."
|
|
586
|
+
/>
|
|
587
|
+
|
|
588
|
+
<Field as="select" label="Country" options={['Norway', 'Sweden', 'Denmark']} />
|
|
589
|
+
|
|
590
|
+
<Field
|
|
591
|
+
as="textarea"
|
|
592
|
+
label="Comment"
|
|
593
|
+
count={500} /* character counter */
|
|
594
|
+
/>
|
|
595
|
+
|
|
596
|
+
<Field
|
|
597
|
+
as={Field.Suggestion}
|
|
598
|
+
label="Search"
|
|
599
|
+
options={[{ label: 'Oslo', value: 'oslo' }]}
|
|
600
|
+
/>
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
Supported `as` values: `"input"`, `"textarea"`, `"select"`, `Field.Suggestion`.
|
|
604
|
+
|
|
605
|
+
Compound members: `Field.Label`, `Field.Description`, `Field.Suggestion`, `Field.Datalist`, `Field.Option`.
|
|
606
|
+
|
|
607
|
+
### 9.2 `Fieldset` — group related fields
|
|
608
|
+
|
|
609
|
+
Use plain `<legend>` and `<Fieldset.Description>` for grouped options. There is **no** `Fieldset.Legend` component — use the native HTML `<legend>` element when composing manually, or use `<Fieldset.Legend>` only inside the React `<Fieldset>` component (it renders `<legend>`).
|
|
610
|
+
|
|
611
|
+
```tsx
|
|
612
|
+
<Fieldset>
|
|
613
|
+
<Fieldset.Legend>Hva foretrekker du?</Fieldset.Legend>
|
|
614
|
+
<Fieldset.Description>Pick one option.</Fieldset.Description>
|
|
615
|
+
<Field as="input" type="radio" name="pref" label="Alternative 1" />
|
|
616
|
+
<Field as="input" type="radio" name="pref" label="Alternative 2" />
|
|
617
|
+
</Fieldset>
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
### 9.3 `Errorsummary` — top-of-form errors
|
|
621
|
+
|
|
622
|
+
Note the spelling: `Errorsummary` (single capital R). Children are a `<Heading>` + an unordered list of in-page anchors that point to field IDs.
|
|
623
|
+
|
|
624
|
+
```tsx
|
|
625
|
+
import { Errorsummary, Heading } from '@mattilsynet/design/react';
|
|
626
|
+
|
|
627
|
+
<Errorsummary>
|
|
628
|
+
<Heading>For å gå videre må du rette opp følgende feil:</Heading>
|
|
629
|
+
<ul>
|
|
630
|
+
<li><a href="#name">Name is required</a></li>
|
|
631
|
+
<li><a href="#email">Email must be valid</a></li>
|
|
632
|
+
</ul>
|
|
633
|
+
</Errorsummary>
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
Behavior rules:
|
|
637
|
+
- Hide the Errorsummary with `hidden` until validation fails.
|
|
638
|
+
- Anchor links in Errorsummary must match field `id` attributes. On click, scroll the target into view and focus it.
|
|
639
|
+
|
|
640
|
+
### 9.4 `Validation` — field-level error
|
|
641
|
+
|
|
642
|
+
Use `<Validation>` (or `<div data-field="validation">` in plain HTML) as a child of `<Field>` or `<Fieldset>`. In Mode B (`Field as="…"`), pass `validation="…"` instead.
|
|
643
|
+
|
|
644
|
+
### 9.5 `Togglegroup` — single-select toggle button row
|
|
645
|
+
|
|
646
|
+
Note the spelling: `Togglegroup` (single capital G). Compound: `Togglegroup.Item`.
|
|
647
|
+
|
|
648
|
+
```tsx
|
|
649
|
+
<Togglegroup data-toggle-group="View">
|
|
650
|
+
<Togglegroup.Item><input type="radio" name="view" defaultChecked /> List</Togglegroup.Item>
|
|
651
|
+
<Togglegroup.Item><input type="radio" name="view" /> Grid</Togglegroup.Item>
|
|
652
|
+
</Togglegroup>
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
### 9.6 `Fileupload` (experimental)
|
|
656
|
+
|
|
657
|
+
Note the spelling: `Fileupload`. API may change — read `fileupload.mdx` before use.
|
|
658
|
+
|
|
659
|
+
### 9.7 `HelpText`
|
|
660
|
+
|
|
661
|
+
Inline contextual help button (renders as a button that toggles a popover). Prefer passing `helpText="…"` and `helpTextLabel="…"` to `<Field>` over composing `<HelpText>` manually.
|
|
662
|
+
|
|
663
|
+
### 9.8 Worked form example
|
|
664
|
+
|
|
665
|
+
```tsx
|
|
666
|
+
import { useRef, useState } from 'react';
|
|
667
|
+
import { Button, Card, Errorsummary, Field, Fieldset, Flex, Heading } from '@mattilsynet/design/react';
|
|
668
|
+
|
|
669
|
+
function ApplicationForm() {
|
|
670
|
+
const [errors, setErrors] = useState<Record<string, string>>({});
|
|
671
|
+
const summaryRef = useRef<HTMLHeadingElement>(null);
|
|
672
|
+
|
|
673
|
+
const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
|
674
|
+
e.preventDefault();
|
|
675
|
+
const data = new FormData(e.currentTarget);
|
|
676
|
+
const next: Record<string, string> = {};
|
|
677
|
+
if (!data.get('name')) next.name = 'Navn er påkrevd';
|
|
678
|
+
if (!data.get('email')) next.email = 'E-post er påkrevd';
|
|
679
|
+
setErrors(next);
|
|
680
|
+
if (Object.keys(next).length) setTimeout(() => summaryRef.current?.focus());
|
|
681
|
+
};
|
|
682
|
+
|
|
683
|
+
return (
|
|
684
|
+
<Card>
|
|
685
|
+
<form onSubmit={onSubmit}>
|
|
686
|
+
<Flex data-gap="5">
|
|
687
|
+
<Errorsummary hidden={!Object.keys(errors).length}>
|
|
688
|
+
<Heading ref={summaryRef} tabIndex={-1}>
|
|
689
|
+
For å gå videre må du rette opp følgende feil:
|
|
690
|
+
</Heading>
|
|
691
|
+
<ul>
|
|
692
|
+
{errors.name && <li><a href="#name">{errors.name}</a></li>}
|
|
693
|
+
{errors.email && <li><a href="#email">{errors.email}</a></li>}
|
|
694
|
+
</ul>
|
|
695
|
+
</Errorsummary>
|
|
696
|
+
|
|
697
|
+
<Field as="input" id="name" name="name" label="Navn" validation={errors.name} />
|
|
698
|
+
<Field as="input" id="email" name="email" label="E-post" type="email" validation={errors.email} />
|
|
699
|
+
|
|
700
|
+
<Fieldset>
|
|
701
|
+
<Fieldset.Legend>Foretrukket kontakt</Fieldset.Legend>
|
|
702
|
+
<Field as="input" type="radio" name="contact" label="E-post" defaultChecked />
|
|
703
|
+
<Field as="input" type="radio" name="contact" label="Telefon" />
|
|
704
|
+
</Fieldset>
|
|
705
|
+
|
|
706
|
+
<Button type="submit" data-variant="primary">Send inn</Button>
|
|
707
|
+
</Flex>
|
|
708
|
+
</form>
|
|
709
|
+
</Card>
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
---
|
|
715
|
+
|
|
716
|
+
## 10. Worked page example
|
|
717
|
+
|
|
718
|
+
```tsx
|
|
719
|
+
import { Flex, Grid, Card, Heading, Prose, Muted, Button, Tag } from '@mattilsynet/design/react';
|
|
720
|
+
import { WarningIcon } from '@phosphor-icons/react/ssr';
|
|
721
|
+
|
|
722
|
+
export function InspectionsPage() {
|
|
723
|
+
return (
|
|
724
|
+
<App.Main>
|
|
725
|
+
<Group>
|
|
726
|
+
<Prose>
|
|
727
|
+
<Heading as="h1" data-size="xl">Inspections</Heading>
|
|
728
|
+
{/* Compact toolbar — density cascades */}
|
|
729
|
+
<Flex>
|
|
730
|
+
<Button data-variant="primary">New</Button>
|
|
731
|
+
<Button>Export</Button>
|
|
732
|
+
</Flex>
|
|
733
|
+
|
|
734
|
+
<Grid data-items="300" data-gap="6">
|
|
735
|
+
<Card>
|
|
736
|
+
<Prose>
|
|
737
|
+
<Heading as="h2" data-size="sm">Fisk AS</Heading>
|
|
738
|
+
<Tag data-color="success">Approved</Tag>
|
|
739
|
+
<Muted>Inspected 03.05.2026</Muted>
|
|
740
|
+
</Prose>
|
|
741
|
+
</Card>
|
|
742
|
+
|
|
743
|
+
{/* Single attribute switches whole card to red */}
|
|
744
|
+
<Alert data-color="danger">
|
|
745
|
+
<Prose>
|
|
746
|
+
<Heading as="h2" data-size="sm"><WarningIcon /> Kjøtt & Co</Heading>
|
|
747
|
+
<p>Critical violations. Follow up within 14 days.</p>
|
|
748
|
+
</Prose>
|
|
749
|
+
<Button data-variant="primary">Follow up</Button>
|
|
750
|
+
</Alert>
|
|
751
|
+
</Grid>
|
|
752
|
+
</Prose>
|
|
753
|
+
</Group>
|
|
754
|
+
</App.Main>
|
|
755
|
+
);
|
|
756
|
+
}
|
|
757
|
+
```
|
|
758
|
+
|
|
759
|
+
---
|
|
760
|
+
|
|
761
|
+
## 11. Figma → code & pre-return checklist
|
|
762
|
+
|
|
763
|
+
When the input is a Figma frame/sketch, treat the design as a binding spec for *which design system components to use*, not just a pixel target.
|
|
764
|
+
|
|
765
|
+
### 11.1 Figma component names map 1:1 to mtds component names
|
|
766
|
+
|
|
767
|
+
A Figma instance named **Card** → `<Card>`. **Field** → `<Field>`. **Errorsummary** → `<Errorsummary>`. **Info** → `<Info>`. Look up the Figma component name in §12 and use the matching React component. There is no name remapping table — if the names differ, the Figma library is out of date; ask before guessing.
|
|
768
|
+
|
|
769
|
+
### 11.2 Mapping rules
|
|
770
|
+
|
|
771
|
+
- Map every visible Figma component instance to its corresponding component from `@mattilsynet/design/react`. Do **not** substitute custom HTML/CSS to gain layout control.
|
|
772
|
+
- Do **not** reach for raw `<div>`/`<span>` + CSS when a design system component covers the case — even if the component feels harder to make responsive. First try the component plus a wrapper (`Flex`, `Grid`, `Card`) and/or token-based CSS.
|
|
773
|
+
- **Auto-layout:** if children of a Figma auto-layout have equal width or width "fill", prefer `<Grid>` over `<Flex>`.
|
|
774
|
+
- **Metadata** (icon + value, or label + value): MUST use `<Info>` whenever Figma uses an `Info` instance, or whenever the content shape is icon + value or label + value. Custom `<span>`/`<div>` structures for metadata are not allowed unless the user explicitly approves the deviation.
|
|
775
|
+
- **Headings:** any Figma text styled as a heading → `<Heading as="hN" data-size="…">`, never raw `<h1>`–`<h6>` or styled `<div>`.
|
|
776
|
+
- **Lead/intro text** → `<Ingress>`. **Secondary/meta text** → `<Muted>`.
|
|
777
|
+
- **Surface containers:** Figma "Card" → `<Card>`; Figma tinted region grouping cards → `<Group>`.
|
|
778
|
+
- **Buttons, Tags, Chips, Alerts, Tabs, Steps, Dialog, Popover, Table, Field/Fieldset, Errorsummary** → use the matching component from §12. Do not reimplement.
|
|
779
|
+
- **Color regions:** when a Figma frame is in a semantic color (danger/warning/success/info/inverted), set `data-color="…"` on a parent rather than picking palette-prefixed tokens.
|
|
780
|
+
- If you genuinely believe a deviation is required (e.g. the component cannot express the intended behavior), **STOP**. Explain the deviation and the proposed alternative **before** writing code, and wait for explicit approval.
|
|
781
|
+
- If the sketch is unclear (text unreadable, intent unknown, missing state), list assumptions explicitly at the top of your final answer.
|
|
782
|
+
|
|
783
|
+
### 11.3 Pre-return checklist
|
|
784
|
+
|
|
785
|
+
Before sending the final response, verify every item:
|
|
786
|
+
|
|
787
|
+
**Imports & setup**
|
|
788
|
+
- [ ] React components imported from `@mattilsynet/design/react`; icons from `@phosphor-icons/react/ssr`.
|
|
789
|
+
- [ ] Entry file imports `'@mattilsynet/design'` and `'@mattilsynet/design/styles.css'`.
|
|
790
|
+
- [ ] Exact export names spelled correctly: `Errorsummary`, `Togglegroup`, `Fileupload`, `HelpText`.
|
|
791
|
+
|
|
792
|
+
**Spacing, color, sizing**
|
|
793
|
+
- [ ] No raw `px`/`rem` for spacing — only `data-gap`, `data-pad`, or `var(--mtds-{n})` from the §5.1 scale.
|
|
794
|
+
- [ ] No hex/rgb/named colors — only `var(--mtds-color-*)` (palette-agnostic) under the right `data-color` parent.
|
|
795
|
+
- [ ] No inline `style={{…}}` for layout/color/spacing.
|
|
796
|
+
- [ ] No Tailwind utility classes.
|
|
797
|
+
- [ ] All spacing-token indexes used exist (`0`–`15`, `18`, `22`, `26`, `30`).
|
|
798
|
+
- [ ] `data-size` collisions accounted for (density on containers vs. local size on `Heading`/`Button`/`Info`).
|
|
799
|
+
|
|
800
|
+
**Markup**
|
|
801
|
+
- [ ] No raw `<h1>`–`<h6>` — every heading is `<Heading as="hN">`.
|
|
802
|
+
- [ ] No raw `<div style="display:flex">` — use `<Flex>` or `<Grid>`.
|
|
803
|
+
- [ ] Metadata pairs (icon + value / label + value) use `<Info>`, not custom `<span>`/`<div>`.
|
|
804
|
+
|
|
805
|
+
**App shell**
|
|
806
|
+
- [ ] `<App>` wraps the page; source order `Header → Toggle → Sidebar → Main → Footer`.
|
|
807
|
+
- [ ] Every leaf node inside `<App.Main>` is wrapped in `<Card>` or `<Group>`.
|
|
808
|
+
- [ ] `<Logo>` is the first child of `<App.Header>`.
|
|
809
|
+
- [ ] Sidebar buttons are icon-only with `data-tooltip="Label"`.
|
|
810
|
+
- [ ] Sidebar state uses `<App.Toggle>` and `<App.Sidebar>`
|
|
811
|
+
|
|
812
|
+
**Figma audit (when converting from a sketch)**
|
|
813
|
+
- [ ] Every visible Figma component instance is mapped to its `@mattilsynet/design/react` counterpart, or listed as an explicit, approved deviation.
|
|
814
|
+
|
|
815
|
+
---
|
|
816
|
+
|
|
817
|
+
## 12. Component index
|
|
818
|
+
|
|
819
|
+
Each component has full docs at `./ai/<name>.mdx` and examples in `./ai/<name>.stories.tsx`.
|
|
820
|
+
Read the relevant doc before using a component you have not used in this session.
|
|
821
|
+
|
|
822
|
+
### Layout & structure
|
|
823
|
+
|
|
824
|
+
- **`App`** — page shell with `Header`, `Toggle`, `Sidebar`, `Sticky`, `Main`, `Footer`, `Script`. See §6.
|
|
825
|
+
- **`Card`** — solid white surface for one related content unit. `data-pad`, `data-radius`, `href`, `data-clickdelegatefor`. See §7.4.
|
|
826
|
+
- **`Group`** — tinted surface for a region of cards or meta-UI. Same attrs as `Card`. See §7.4.
|
|
827
|
+
- **`Flex`** — flex container for mixed-width children (toolbars, button rows). See §7.2.
|
|
828
|
+
- **`Grid`** — grid container for equal-width columns or vertical stacks. Use `data-items="300"` for responsive. See §7.2.
|
|
829
|
+
- **`Divider`** — visual separator (`<hr>` styling).
|
|
830
|
+
|
|
831
|
+
### Typography
|
|
832
|
+
|
|
833
|
+
- **`Heading`** — `as="h1".."h6"` for semantics, `data-size="2xs".."2xl"` for visual size. See §8.1.
|
|
834
|
+
- **`Prose`** — apply vertical typographic rhythm to direct children (body text, lists, headings). See §8.2.
|
|
835
|
+
- **`Ingress`** — lead/intro paragraph. See §8.3.
|
|
836
|
+
- **`Muted`** — small/secondary text. See §8.3.
|
|
837
|
+
- **`Info`** — label + value pair, optionally with icon. `data-variant="regular│circle"`. See §8.4.
|
|
838
|
+
- **`Link`** — inline anchor styled to mtds.
|
|
839
|
+
|
|
840
|
+
### Forms (see §9)
|
|
841
|
+
|
|
842
|
+
- **`Field`** — single field row. Two modes: composition (`<Field.Label>`+`<Input>`) or polymorphic (`<Field as="input" label="…" />`). Compound: `Label`, `Description`, `Suggestion`, `Datalist`, `Option`.
|
|
843
|
+
- **`Fieldset`** — group related fields. Compound: `Legend`, `Description`.
|
|
844
|
+
- **`Input`** — form control primitive (`<input>`, used inside `<Field>`).
|
|
845
|
+
- **`Validation`** — field-level validation message. Pass via `validation="…"` on `<Field as="…">`, or render directly inside `<Field>`.
|
|
846
|
+
- **`Errorsummary`** — top-of-form error list (note: single capital R). Children: `<Heading>` + `<ul><li><a href="#fieldId">`. See §9.3.
|
|
847
|
+
- **`Togglegroup`** — single-select toggle button row (single capital G). Compound: `Item`. See §9.5.
|
|
848
|
+
- **`Fileupload`** — file upload (experimental). See §9.6.
|
|
849
|
+
- **`HelpText`** — popover-style help button. Prefer `<Field helpText="…" />`. See §9.7.
|
|
850
|
+
|
|
851
|
+
### Actions
|
|
852
|
+
|
|
853
|
+
- **`Button`** — primary action element. Renders `<a>` when `href` is set. Variants: `data-variant="primary│secondary│tertiary"`. Sizes via `data-size` on the button or its container.
|
|
854
|
+
- **`Chip`** — interactive filter chip / single-select toggle.
|
|
855
|
+
|
|
856
|
+
### Feedback & status
|
|
857
|
+
|
|
858
|
+
- **`Alert`** — inline contextual feedback. Color via ancestor `data-color="info│success│warning│danger"`.
|
|
859
|
+
- **`Badge`** — small status indicator overlay (count, dot) on top of another element.
|
|
860
|
+
- **`Progress`** — progress bar.
|
|
861
|
+
- **`Skeleton`** — loading placeholder block.
|
|
862
|
+
- **`Spinner`** — loading spinner.
|
|
863
|
+
- **`Tag`** — read-only label/keyword. Color via `data-color`.
|
|
864
|
+
- **`Toast`** — transient notification (managed via toast helpers).
|
|
865
|
+
|
|
866
|
+
### Navigation
|
|
867
|
+
|
|
868
|
+
- **`Breadcrumbs`** — breadcrumb trail.
|
|
869
|
+
- **`Pagination`** — paged result navigation.
|
|
870
|
+
- **`Steps`** — multi-step progress / timeline. Children are `<li>` with `<mark>`, `<strong>`, optional `<small>`. Set `aria-current="step"` on the active `<li>`. State via `data-state="complete"` on the `<ol>`/`<Steps>`.
|
|
871
|
+
- **`Tabs`** — tabbed content panels. Compound: `List`, `Tab`, `Panel`.
|
|
872
|
+
|
|
873
|
+
### Display
|
|
874
|
+
|
|
875
|
+
- **`Avatar`** — round user avatar.
|
|
876
|
+
- **`Chart`** — chart wrapper.
|
|
877
|
+
- **`Details`** — disclosure widget. Compound: `Summary`.
|
|
878
|
+
- **`Dialog`** — modal dialog using native `<dialog>`. Open with `command="show-modal"` + `commandfor="id"`; close with `command="request-close"`.
|
|
879
|
+
- **`Popover`** — non-modal floating overlay using native popover API. Polymorphic via `as` (commonly `as="menu"`). Anchor with `popoverTarget="id"` on the trigger.
|
|
880
|
+
- **`Table`** — data table. Compound: `Thead`, `Tbody`, `Tr`, `Th`, `Td`, `ThSortable`.
|
|
881
|
+
- **`Tooltip`** — prefer `data-tooltip="…"` on any element instead of a component.
|
|
882
|
+
|
|
883
|
+
### Domain & branding
|
|
884
|
+
|
|
885
|
+
- **`Law`** — Mattilsynet legal-text formatting.
|
|
886
|
+
- **`Logo`** — Mattilsynet (or sub-app) logo. Required as the first child of `<App.Header>`.
|
|
887
|
+
- **`Print`** — print-only utilities and breaks.
|
|
888
|
+
|
|
889
|
+
### Map and Charts
|
|
890
|
+
|
|
891
|
+
- **`Atlas`** — interactive map.
|
|
892
|
+
- **`Chart`** — chart primitives.
|