@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,373 @@
|
|
|
1
|
+
import { FunnelIcon } from "@phosphor-icons/react";
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
3
|
+
import { useEffect, useRef, useState } from "react";
|
|
4
|
+
import { Button, Dialog, Field, Flex, Heading, Prose } from "../react";
|
|
5
|
+
import styles from "../styles.module.css";
|
|
6
|
+
|
|
7
|
+
const meta = {
|
|
8
|
+
title: "Designsystem/Dialog",
|
|
9
|
+
} satisfies Meta;
|
|
10
|
+
|
|
11
|
+
export default meta;
|
|
12
|
+
type Story = StoryObj<typeof meta>;
|
|
13
|
+
|
|
14
|
+
const LOREM_IPSUM = (
|
|
15
|
+
<>
|
|
16
|
+
<p>
|
|
17
|
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse
|
|
18
|
+
malesuada eget risus nec viverra. Nam dapibus nec arcu in tristique. Fusce
|
|
19
|
+
varius urna odio, vel bibendum odio imperdiet eget. Aliquam consectetur
|
|
20
|
+
arcu mi, quis elementum mi convallis a. Sed venenatis nec enim vel
|
|
21
|
+
molestie. Vestibulum nec auctor ligula. Nunc id sollicitudin ligula. Fusce
|
|
22
|
+
interdum quam posuere augue fringilla, dignissim convallis ex suscipit.
|
|
23
|
+
</p>
|
|
24
|
+
<p>
|
|
25
|
+
Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere
|
|
26
|
+
cubilia curae; Aliquam commodo bibendum risus non luctus. Etiam molestie
|
|
27
|
+
lectus commodo quam ornare posuere. Vestibulum aliquam viverra ligula non
|
|
28
|
+
ultricies. Pellentesque eu bibendum nibh, vel vestibulum tortor. Duis
|
|
29
|
+
rutrum metus sed dictum sagittis. Vivamus in arcu sed neque cursus
|
|
30
|
+
condimentum. Praesent vel turpis malesuada, ullamcorper justo et,
|
|
31
|
+
facilisis neque. In ex enim, semper sed sapien sit amet, mollis laoreet
|
|
32
|
+
mi. Fusce vitae bibendum nulla, in condimentum tortor.
|
|
33
|
+
</p>
|
|
34
|
+
</>
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
export const Default: Story = {
|
|
38
|
+
render: () => (
|
|
39
|
+
<>
|
|
40
|
+
<button
|
|
41
|
+
className={styles.button}
|
|
42
|
+
command="show-modal"
|
|
43
|
+
commandfor="my-dialog"
|
|
44
|
+
type="button"
|
|
45
|
+
>
|
|
46
|
+
Open
|
|
47
|
+
</button>
|
|
48
|
+
<dialog className={styles.dialog} id="my-dialog">
|
|
49
|
+
<button
|
|
50
|
+
aria-label="Lukk"
|
|
51
|
+
command="request-close"
|
|
52
|
+
commandfor="my-dialog"
|
|
53
|
+
type="button"
|
|
54
|
+
></button>
|
|
55
|
+
<div className={styles.prose}>
|
|
56
|
+
<h2 className={styles.heading}>Eksempeldialog</h2>
|
|
57
|
+
{LOREM_IPSUM}
|
|
58
|
+
</div>
|
|
59
|
+
</dialog>
|
|
60
|
+
</>
|
|
61
|
+
),
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const React: Story = {
|
|
65
|
+
render: function Render() {
|
|
66
|
+
return (
|
|
67
|
+
<>
|
|
68
|
+
<Button command="show-modal" commandfor="my-react-dialog">
|
|
69
|
+
Open
|
|
70
|
+
</Button>
|
|
71
|
+
<Dialog id="my-react-dialog">
|
|
72
|
+
<Button
|
|
73
|
+
aria-label="Lukk"
|
|
74
|
+
command="close"
|
|
75
|
+
commandfor="my-react-dialog"
|
|
76
|
+
/>
|
|
77
|
+
<Prose>
|
|
78
|
+
<Heading>Eksempeldialog</Heading>
|
|
79
|
+
{LOREM_IPSUM}
|
|
80
|
+
</Prose>
|
|
81
|
+
</Dialog>
|
|
82
|
+
</>
|
|
83
|
+
);
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export const ReactWithState: Story = {
|
|
88
|
+
render: function Render() {
|
|
89
|
+
const dialogRef = useRef<HTMLDialogElement>(null);
|
|
90
|
+
const [open, setOpen] = useState(false);
|
|
91
|
+
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
dialogRef.current?.[open ? "showModal" : "close"]();
|
|
94
|
+
}, [open]);
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<>
|
|
98
|
+
<Button onClick={() => setOpen(true)}>Open</Button>
|
|
99
|
+
<Dialog ref={dialogRef} onClose={() => setOpen(false)}>
|
|
100
|
+
<Button
|
|
101
|
+
aria-label="Lukk"
|
|
102
|
+
command="close"
|
|
103
|
+
onClick={() => setOpen(false)}
|
|
104
|
+
/>
|
|
105
|
+
<Prose>
|
|
106
|
+
<Heading>Eksempeldialog</Heading>
|
|
107
|
+
{LOREM_IPSUM}
|
|
108
|
+
</Prose>
|
|
109
|
+
</Dialog>
|
|
110
|
+
</>
|
|
111
|
+
);
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export const WithClose: Story = {
|
|
116
|
+
render: () => (
|
|
117
|
+
<>
|
|
118
|
+
<button
|
|
119
|
+
className={styles.button}
|
|
120
|
+
command="show-modal"
|
|
121
|
+
commandfor="my-close-dialog"
|
|
122
|
+
type="button"
|
|
123
|
+
>
|
|
124
|
+
Open
|
|
125
|
+
</button>
|
|
126
|
+
<dialog className={styles.dialog} id="my-close-dialog">
|
|
127
|
+
<button
|
|
128
|
+
type="button"
|
|
129
|
+
command="close"
|
|
130
|
+
commandfor="my-close-dialog"
|
|
131
|
+
aria-label="Lukk"
|
|
132
|
+
></button>
|
|
133
|
+
<div className={styles.prose}>
|
|
134
|
+
<h2 className={styles.heading}>Eksempeldialog</h2>
|
|
135
|
+
<p>Dialog content here</p>
|
|
136
|
+
<div className={styles.flex}>
|
|
137
|
+
<button
|
|
138
|
+
type="button"
|
|
139
|
+
data-variant="primary"
|
|
140
|
+
className={styles.button}
|
|
141
|
+
>
|
|
142
|
+
Lagre
|
|
143
|
+
</button>
|
|
144
|
+
<button
|
|
145
|
+
className={styles.button}
|
|
146
|
+
commandfor="my-close-dialog"
|
|
147
|
+
command="close"
|
|
148
|
+
type="button"
|
|
149
|
+
>
|
|
150
|
+
Avbryt
|
|
151
|
+
</button>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
</dialog>
|
|
155
|
+
</>
|
|
156
|
+
),
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
export const WithBackdropClose: Story = {
|
|
160
|
+
render: () => (
|
|
161
|
+
<>
|
|
162
|
+
<button
|
|
163
|
+
className={styles.button}
|
|
164
|
+
command="show-modal"
|
|
165
|
+
commandfor="my-backdrop-dialog"
|
|
166
|
+
type="button"
|
|
167
|
+
>
|
|
168
|
+
Open
|
|
169
|
+
</button>
|
|
170
|
+
<dialog id="my-backdrop-dialog" className={styles.dialog} closedby="any">
|
|
171
|
+
<h2 className={styles.heading}>Eksempeldialog</h2>
|
|
172
|
+
Klikk på utsiden for å lukke
|
|
173
|
+
</dialog>
|
|
174
|
+
</>
|
|
175
|
+
),
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
export const WithStickyFooter: Story = {
|
|
179
|
+
render: () => (
|
|
180
|
+
<>
|
|
181
|
+
<button
|
|
182
|
+
className={styles.button}
|
|
183
|
+
type="button"
|
|
184
|
+
command="show-modal"
|
|
185
|
+
commandfor="my-footer-dialog"
|
|
186
|
+
>
|
|
187
|
+
Open
|
|
188
|
+
</button>
|
|
189
|
+
<dialog className={styles.dialog} id="my-footer-dialog">
|
|
190
|
+
<form className={styles.prose} method="dialog" id="my-dialog-form">
|
|
191
|
+
<h2 className={styles.heading}>Vilkår</h2>
|
|
192
|
+
{LOREM_IPSUM}
|
|
193
|
+
{LOREM_IPSUM}
|
|
194
|
+
{LOREM_IPSUM}
|
|
195
|
+
{LOREM_IPSUM}
|
|
196
|
+
{LOREM_IPSUM}
|
|
197
|
+
</form>
|
|
198
|
+
<footer className={styles.flex}>
|
|
199
|
+
<button
|
|
200
|
+
form="my-dialog-form"
|
|
201
|
+
type="submit"
|
|
202
|
+
className={styles.button}
|
|
203
|
+
data-variant="primary"
|
|
204
|
+
>
|
|
205
|
+
Godta vilkår
|
|
206
|
+
</button>
|
|
207
|
+
<button
|
|
208
|
+
type="button"
|
|
209
|
+
command="close"
|
|
210
|
+
commandfor="my-footer-dialog"
|
|
211
|
+
className={styles.button}
|
|
212
|
+
>
|
|
213
|
+
Avbryt
|
|
214
|
+
</button>
|
|
215
|
+
</footer>
|
|
216
|
+
</dialog>
|
|
217
|
+
</>
|
|
218
|
+
),
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
export const WithoutBackdrop: Story = {
|
|
222
|
+
render: function Render() {
|
|
223
|
+
const [open, setOpen] = useState(false);
|
|
224
|
+
|
|
225
|
+
return (
|
|
226
|
+
<>
|
|
227
|
+
<button
|
|
228
|
+
className={styles.button}
|
|
229
|
+
type="button"
|
|
230
|
+
onClick={() => setOpen(true)}
|
|
231
|
+
>
|
|
232
|
+
Open
|
|
233
|
+
</button>
|
|
234
|
+
<dialog
|
|
235
|
+
className={styles.dialog}
|
|
236
|
+
onClose={() => setOpen(false)}
|
|
237
|
+
id="my-nonmodal-dialog"
|
|
238
|
+
open={open}
|
|
239
|
+
>
|
|
240
|
+
<button
|
|
241
|
+
type="button"
|
|
242
|
+
aria-label="Lukk"
|
|
243
|
+
command="close"
|
|
244
|
+
commandfor="my-nonmodal-dialog"
|
|
245
|
+
/>
|
|
246
|
+
<div className={styles.prose}>
|
|
247
|
+
<h2 className={styles.heading}>Eksempeldialog</h2>
|
|
248
|
+
<p>Uten backdrop</p>
|
|
249
|
+
</div>
|
|
250
|
+
</dialog>
|
|
251
|
+
</>
|
|
252
|
+
);
|
|
253
|
+
},
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
export const VariantDrawer: Story = {
|
|
257
|
+
render: function Render() {
|
|
258
|
+
return (
|
|
259
|
+
<>
|
|
260
|
+
<button
|
|
261
|
+
className={styles.button}
|
|
262
|
+
type="button"
|
|
263
|
+
command="show-modal"
|
|
264
|
+
commandfor="my-drawer"
|
|
265
|
+
>
|
|
266
|
+
Open
|
|
267
|
+
</button>
|
|
268
|
+
<dialog
|
|
269
|
+
className={styles.dialog}
|
|
270
|
+
data-placement="right"
|
|
271
|
+
id="my-drawer"
|
|
272
|
+
closedby="any"
|
|
273
|
+
>
|
|
274
|
+
<button
|
|
275
|
+
type="button"
|
|
276
|
+
aria-label="Lukk"
|
|
277
|
+
command="close"
|
|
278
|
+
commandfor="my-drawer"
|
|
279
|
+
/>
|
|
280
|
+
<div className={styles.prose}>
|
|
281
|
+
<h2 className={styles.heading}>Eksempelskuff med backdrop</h2>
|
|
282
|
+
<p>Kan også brukes uten backdrop</p>
|
|
283
|
+
</div>
|
|
284
|
+
</dialog>
|
|
285
|
+
</>
|
|
286
|
+
);
|
|
287
|
+
},
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
const options = Array.from({ length: 25 }, (_, i) => ({
|
|
291
|
+
label: `Alternativ ${i + 1}`,
|
|
292
|
+
value: `${i}`,
|
|
293
|
+
}));
|
|
294
|
+
|
|
295
|
+
export const VariantDrawerWithoutBackdrop: Story = {
|
|
296
|
+
render: function Render() {
|
|
297
|
+
const [open, setOpen] = useState(false);
|
|
298
|
+
|
|
299
|
+
return (
|
|
300
|
+
<>
|
|
301
|
+
<Button data-variant="secondary" onClick={() => setOpen(true)}>
|
|
302
|
+
<FunnelIcon />
|
|
303
|
+
Filtrer
|
|
304
|
+
</Button>
|
|
305
|
+
<Dialog
|
|
306
|
+
id="my-filter-drawer"
|
|
307
|
+
data-placement="right"
|
|
308
|
+
open={open}
|
|
309
|
+
onClose={() => setOpen(false)}
|
|
310
|
+
>
|
|
311
|
+
<Button command="close" commandfor="my-filter-drawer" />
|
|
312
|
+
<form action="#">
|
|
313
|
+
<Prose>
|
|
314
|
+
<Heading>
|
|
315
|
+
<FunnelIcon /> Filtrer
|
|
316
|
+
</Heading>
|
|
317
|
+
<Field as={Field.Suggestion} label="Filter 1" options={options} />
|
|
318
|
+
<Field as={Field.Suggestion} label="Filter 2" options={options} />
|
|
319
|
+
<Field as={Field.Suggestion} label="Filter 3" options={options} />
|
|
320
|
+
<Field as={Field.Suggestion} label="Filter 4" options={options} />
|
|
321
|
+
<Field as={Field.Suggestion} label="Filter 5" options={options} />
|
|
322
|
+
<Field as={Field.Suggestion} label="Filter 6" options={options} />
|
|
323
|
+
<Field as={Field.Suggestion} label="Filter 7" options={options} />
|
|
324
|
+
<Field as={Field.Suggestion} label="Filter 8" options={options} />
|
|
325
|
+
<Field as={Field.Suggestion} label="Filter 9" options={options} />
|
|
326
|
+
</Prose>
|
|
327
|
+
</form>
|
|
328
|
+
<Flex as="footer">
|
|
329
|
+
<Button data-variant="primary" type="submit" form="my-filter-form">
|
|
330
|
+
Bruk
|
|
331
|
+
</Button>
|
|
332
|
+
<Button type="reset" form="my-filter-form">
|
|
333
|
+
Tøm filter
|
|
334
|
+
</Button>
|
|
335
|
+
</Flex>
|
|
336
|
+
</Dialog>
|
|
337
|
+
</>
|
|
338
|
+
);
|
|
339
|
+
},
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
export const DeprecatedDefault: Story = {
|
|
343
|
+
tags: ["!dev"],
|
|
344
|
+
render: function Render() {
|
|
345
|
+
const [open, setOpen] = useState(false);
|
|
346
|
+
|
|
347
|
+
return (
|
|
348
|
+
<>
|
|
349
|
+
<button
|
|
350
|
+
className={styles.button}
|
|
351
|
+
onClick={() => setOpen(true)}
|
|
352
|
+
type="button"
|
|
353
|
+
>
|
|
354
|
+
Open
|
|
355
|
+
</button>
|
|
356
|
+
<dialog
|
|
357
|
+
className={styles.dialog}
|
|
358
|
+
data-closedby="any"
|
|
359
|
+
data-modal="true"
|
|
360
|
+
data-variant="drawer"
|
|
361
|
+
onClose={() => setOpen(false)}
|
|
362
|
+
open={open}
|
|
363
|
+
>
|
|
364
|
+
<button aria-label="Lukk" data-command="close" type="button"></button>
|
|
365
|
+
<div className={styles.prose}>
|
|
366
|
+
<h2 className={styles.heading}>Eksempeldialog</h2>
|
|
367
|
+
{LOREM_IPSUM}
|
|
368
|
+
</div>
|
|
369
|
+
</dialog>
|
|
370
|
+
</>
|
|
371
|
+
);
|
|
372
|
+
},
|
|
373
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Meta, Canvas } from '@storybook/addon-docs/blocks';
|
|
2
|
+
import { CssVariables } from '../../.storybook/blocks';
|
|
3
|
+
import * as stories from './divider.stories';
|
|
4
|
+
|
|
5
|
+
<Meta of={stories} />
|
|
6
|
+
|
|
7
|
+
# Divider
|
|
8
|
+
> Divider er en horisontal linje som skaper et visuelt skille mellom innhold.
|
|
9
|
+
|
|
10
|
+
## Kode
|
|
11
|
+
- `<hr />` får automatisk skillelinjestil
|
|
12
|
+
- Bruk klassen `divider` om du mot formodning trenger skillelinjestil på et annet element
|
|
13
|
+
<Canvas of={stories.Default} />
|
|
14
|
+
|
|
15
|
+
## Gap
|
|
16
|
+
- Bruk `data-gap="0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|18|22|26|30"` (`6` er default)
|
|
17
|
+
<Canvas of={stories.Gap} />
|
|
18
|
+
|
|
19
|
+
<CssVariables component="divider" />
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { Divider } from "../react";
|
|
3
|
+
import styles from "../styles.module.css";
|
|
4
|
+
|
|
5
|
+
const gaps = [
|
|
6
|
+
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 18, 22, 26, 30,
|
|
7
|
+
];
|
|
8
|
+
const meta = {
|
|
9
|
+
title: "Designsystem/Divider",
|
|
10
|
+
parameters: {
|
|
11
|
+
layout: "padded",
|
|
12
|
+
},
|
|
13
|
+
} satisfies Meta;
|
|
14
|
+
|
|
15
|
+
export default meta;
|
|
16
|
+
type Story = StoryObj<typeof meta>;
|
|
17
|
+
|
|
18
|
+
export const Default: Story = {
|
|
19
|
+
render: () => (
|
|
20
|
+
<>
|
|
21
|
+
Innhold før
|
|
22
|
+
<hr />
|
|
23
|
+
Innhold etter
|
|
24
|
+
</>
|
|
25
|
+
),
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const React: Story = {
|
|
29
|
+
render: () => (
|
|
30
|
+
<>
|
|
31
|
+
Innhold før
|
|
32
|
+
<Divider />
|
|
33
|
+
Innhold etter
|
|
34
|
+
</>
|
|
35
|
+
),
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const Gap: Story = {
|
|
39
|
+
render: () => (
|
|
40
|
+
<div className={styles.grid} data-items="150">
|
|
41
|
+
{gaps.map((gap) => (
|
|
42
|
+
<div key={gap} className={styles.card}>
|
|
43
|
+
data-gap="{gap}"
|
|
44
|
+
<hr data-gap={gap} />
|
|
45
|
+
data-gap="{gap}"
|
|
46
|
+
</div>
|
|
47
|
+
))}
|
|
48
|
+
</div>
|
|
49
|
+
),
|
|
50
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Meta, Canvas } from '@storybook/addon-docs/blocks';
|
|
2
|
+
import { CssVariables } from '../../.storybook/blocks';
|
|
3
|
+
import { Button } from '../react';
|
|
4
|
+
import * as stories from './errorsummary.stories';
|
|
5
|
+
|
|
6
|
+
<Meta of={stories} />
|
|
7
|
+
|
|
8
|
+
# Error summary
|
|
9
|
+
> ErrorSummary er en oppsummering av feil. Den gir brukeren oversikt over feil eller mangler som må rettes for å komme videre.
|
|
10
|
+
|
|
11
|
+
## Kode
|
|
12
|
+
- Bruk klassen `errorsummary` på `<ds-errorsummary>`
|
|
13
|
+
- Hver lenke skal være et anker som peker til `id` til relevante skjemafelt
|
|
14
|
+
- Legg `tabindex="-1"` på overskriften slik at den kan gå fokus
|
|
15
|
+
- Hvis `errorsummary` er synlig ved pageload: legg `autofocus` på overskriften
|
|
16
|
+
- Hvis `errorsummary` blir ved submit av skjema: flytt fokus til overskriften med `.focus()`
|
|
17
|
+
- **Merk:** Alle skjemafelt med feil skal ha en tilhørende [Validation](?path=/docs/designsystem-validation--docs). Errorsummary sin jobb er å gi en enkel oversikt og lenking alle feil.
|
|
18
|
+
- **Mønster:** [Se mønster for skjemafeilemeldinger](https://www.designsystemet.no/no/patterns/errors)
|
|
19
|
+
|
|
20
|
+
<Canvas of={stories.Default} />
|
|
21
|
+
|
|
22
|
+
## Komplett eksempel
|
|
23
|
+
- **Merk:** React kan ikke vise og flytte fokus i samme operasjon på grunn av asynkron uttegning. Bruk `setTimeout(() => { din-fokus-her })` for å flytte fokus etter at feilmeldingen er synlig.
|
|
24
|
+
<Canvas of={stories.WithForm} />
|
|
25
|
+
|
|
26
|
+
<CssVariables component="errorsummary" />
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
|
+
import { Errorsummary, Heading } from "../react";
|
|
4
|
+
import styles from "../styles.module.css";
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
title: "Designsystem/Error summary",
|
|
8
|
+
decorators: [
|
|
9
|
+
function Decorate(Story) {
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
const handleClick = (event: Event) => {
|
|
12
|
+
const anchor = (event.target as Element)?.closest?.('a[href^="#"]');
|
|
13
|
+
const input = document.getElementById(
|
|
14
|
+
(anchor as HTMLAnchorElement)?.hash.slice(1),
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
if (input) {
|
|
18
|
+
event.preventDefault();
|
|
19
|
+
input.scrollIntoView({ behavior: "smooth" });
|
|
20
|
+
input.focus();
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
document.addEventListener("click", handleClick);
|
|
25
|
+
return () => document.removeEventListener("click", handleClick);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const klass = styles.errorsummary.split(" ")[0];
|
|
29
|
+
|
|
30
|
+
// Disable autofocus in storybook
|
|
31
|
+
return (
|
|
32
|
+
<>
|
|
33
|
+
<style>{`.${klass},.${klass}::after{animation:none}`}</style>
|
|
34
|
+
<Story />
|
|
35
|
+
</>
|
|
36
|
+
);
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
} satisfies Meta;
|
|
40
|
+
|
|
41
|
+
export default meta;
|
|
42
|
+
type Story = StoryObj<typeof meta>;
|
|
43
|
+
|
|
44
|
+
export const Default: Story = {
|
|
45
|
+
render: () => (
|
|
46
|
+
<ds-error-summary className={styles.errorsummary}>
|
|
47
|
+
<h2 tabIndex={-1}>For å gå videre må du rette opp følgende feil:</h2>
|
|
48
|
+
<ul>
|
|
49
|
+
<li>
|
|
50
|
+
<a href="#my-input-1">Fødselsdato kan ikke være etter år 2005</a>
|
|
51
|
+
</li>
|
|
52
|
+
<li>
|
|
53
|
+
<a href="#my-input-2">Telefonnummer kan kun inneholde siffer</a>
|
|
54
|
+
</li>
|
|
55
|
+
<li>
|
|
56
|
+
<a href="#my-input-3">E-post må være gyldig</a>
|
|
57
|
+
</li>
|
|
58
|
+
</ul>
|
|
59
|
+
</ds-error-summary>
|
|
60
|
+
),
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const React: Story = {
|
|
64
|
+
render: () => (
|
|
65
|
+
<Errorsummary>
|
|
66
|
+
<Heading>For å gå videre må du rette opp følgende feil:</Heading>
|
|
67
|
+
<ul>
|
|
68
|
+
<li>
|
|
69
|
+
<a href="#my-input-1">Fødselsdato kan ikke være etter år 2005</a>
|
|
70
|
+
</li>
|
|
71
|
+
<li>
|
|
72
|
+
<a href="#my-input-2">Telefonnummer kan kun inneholde siffer</a>
|
|
73
|
+
</li>
|
|
74
|
+
<li>
|
|
75
|
+
<a href="#my-input-3">E-post må være gyldig</a>
|
|
76
|
+
</li>
|
|
77
|
+
</ul>
|
|
78
|
+
</Errorsummary>
|
|
79
|
+
),
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export const WithForm: Story = {
|
|
83
|
+
render: function Render() {
|
|
84
|
+
const [showErrorSummary, setShowErrorSummary] = useState(false);
|
|
85
|
+
const [value, setValue] = useState("");
|
|
86
|
+
|
|
87
|
+
const errorTitle = useRef<HTMLDivElement>(null);
|
|
88
|
+
const handleSubmit = (event: React.FormEvent) => {
|
|
89
|
+
const isValid = value === "jeg tester";
|
|
90
|
+
|
|
91
|
+
event.preventDefault();
|
|
92
|
+
setShowErrorSummary(!isValid);
|
|
93
|
+
|
|
94
|
+
if (isValid) alert("Du fikk det til!");
|
|
95
|
+
else setTimeout(() => errorTitle.current?.focus()); // Move focus on next render
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<form className={styles.grid} data-gap="4" onSubmit={handleSubmit}>
|
|
100
|
+
<ds-error-summary
|
|
101
|
+
className={styles.errorsummary}
|
|
102
|
+
hidden={!showErrorSummary}
|
|
103
|
+
>
|
|
104
|
+
<h2 tabIndex={-1} ref={errorTitle}>
|
|
105
|
+
For å gå videre må du rette opp følgende feil:
|
|
106
|
+
</h2>
|
|
107
|
+
<ul>
|
|
108
|
+
<li>
|
|
109
|
+
<a href="#my-input">Du må skrive teksten "jeg tester"</a>
|
|
110
|
+
</li>
|
|
111
|
+
</ul>
|
|
112
|
+
</ds-error-summary>
|
|
113
|
+
<ds-field className={styles.field}>
|
|
114
|
+
<label>
|
|
115
|
+
Skriv teksten <code className={styles.tag}>jeg tester</code>:
|
|
116
|
+
</label>
|
|
117
|
+
<input
|
|
118
|
+
className={styles.input}
|
|
119
|
+
id="my-input"
|
|
120
|
+
onChange={({ target }) => setValue(target.value)}
|
|
121
|
+
value={value}
|
|
122
|
+
/>
|
|
123
|
+
</ds-field>
|
|
124
|
+
<div className={styles.flex} data-justify="end">
|
|
125
|
+
<button
|
|
126
|
+
type="submit"
|
|
127
|
+
className={styles.button}
|
|
128
|
+
data-variant="primary"
|
|
129
|
+
data-arrow
|
|
130
|
+
>
|
|
131
|
+
Send inn
|
|
132
|
+
</button>
|
|
133
|
+
</div>
|
|
134
|
+
</form>
|
|
135
|
+
);
|
|
136
|
+
},
|
|
137
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { ArgTypes, Meta, Canvas } from '@storybook/addon-docs/blocks';
|
|
2
|
+
import { CssVariables, JumpTo } from '../../.storybook/blocks';
|
|
3
|
+
import * as stories from './field.stories';
|
|
4
|
+
import { Details } from '../react';
|
|
5
|
+
|
|
6
|
+
<Meta of={stories} />
|
|
7
|
+
|
|
8
|
+
# Field
|
|
9
|
+
> Field er et hjelpemiddel for å automatisk koble et felt sammen med Label, Field. Description, ValidationMessage og Field.Counter. Field kan brukes sammen med Input, Textarea og Select.
|
|
10
|
+
|
|
11
|
+
<JumpTo />
|
|
12
|
+
|
|
13
|
+
## Kode
|
|
14
|
+
- Legg klassen `field` på `<ds-field>` rundt ledetekst og skjemafelt
|
|
15
|
+
- Field beriker indre skjemaelementer med f.eks automatisk sammenkobling av `<label>` og `<input>`, beskrivelser (`<p data-field="validation">`) og valideringer `validation data-field="validation"`, samt `textarea` skalering, tegn teller, `suggestion` posisjonering etc.
|
|
16
|
+
- **Flere inputs for én label?** Field skal kun inneholde ét skjemaelementet, men du kan knytte flere sammen (f.eks fornavn + etternavn osv.) med [Fieldset](/?path=/story/designsystem-fieldset--with-texts)
|
|
17
|
+
- **Mønster:** [Se mønster for merking av obligatoriske felter](https://www.designsystemet.no/no/patterns/required-and-optional-fields)
|
|
18
|
+
|
|
19
|
+
<Details>
|
|
20
|
+
<Details.Summary>Se egne parameter for React</Details.Summary>
|
|
21
|
+
<div>
|
|
22
|
+
I React kan du velge å bruke `<Field>` med `<label>`, `<Input>` osv. for full kontroll, eller med `as="input"`/`as="select"` og props for automatisk oppsett av elementer:
|
|
23
|
+
<ArgTypes of={stories} />
|
|
24
|
+
</div>
|
|
25
|
+
</Details>
|
|
26
|
+
|
|
27
|
+
<Canvas of={stories.Default} />
|
|
28
|
+
|
|
29
|
+
## Obligatorisk felt
|
|
30
|
+
- Legg `required` eller `aria-required` på skjemaelementet
|
|
31
|
+
- Da får du automatisk teksten `Må fylles ut` i `<label>`
|
|
32
|
+
- Teksten `Må fylles ut` oversettes automatisk til `Required` dersom du setter `<html lang="en">`. Du kan også selv endre tekst med CSS custom property: `--mtds-text-required: "Må fylles ut";`
|
|
33
|
+
- **Merk:** Dersom du ønsker å skru denne [lovpålagte merkingen av obligatoriske felter](https://www.designsystemet.no/monstre/obligatoriske-og-valgfrie-felt), kan du legge `data-required="hidden"` på hvilket som helst element (f.eks. `<html data-required="hidden">` for å skru av for hele dokumentet)
|
|
34
|
+
<Canvas of={stories.Required} />
|
|
35
|
+
|
|
36
|
+
## Radio / Check / Switch
|
|
37
|
+
- Ved bruk av `<input type="checkbox | radio"` vil `<label>`, `<p data-field="description">` og `class="styles.validation" data-field="validation"` legge seg etter input
|
|
38
|
+
<Canvas of={stories.Toggles} />
|
|
39
|
+
|
|
40
|
+
## Delvis valgt checkbox
|
|
41
|
+
- Bruk `indeterminate` ([via javascript](https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/indeterminate)) på `<input type="checkbox">` for delvis valgt checkbox
|
|
42
|
+
<Canvas of={stories.Indeterminate} />
|
|
43
|
+
|
|
44
|
+
## Feilmelding
|
|
45
|
+
- Legg klassen `validation` på et element for å tilknytte feilmelding
|
|
46
|
+
<Canvas of={stories.WithValidation} />
|
|
47
|
+
|
|
48
|
+
## Innebygget skjemavalidering
|
|
49
|
+
- Nettleserens innebyggede valideringsmeldinger skjules pga. begrenset styling og universell utforming
|
|
50
|
+
- Du kan allikevel få [nettleserens innebygge skjemavalidering](https://developer.mozilla.org/en-US/docs/Learn_web_development/Extensions/Forms/Form_validation#using_built-in_form_validation) til å automatisk skjule og vise [validation-komponentet](?path=/docs/designsystem-validation--docs) ved å legge `data-validation="form"` på `form`
|
|
51
|
+
- **Merk:** `ds-suggestion` krever `input aria-required="true"` (ikke `input required`) siden suggestion ikke er et native skjemaelement
|
|
52
|
+
<Canvas of={stories.WithValidationForm} />
|
|
53
|
+
|
|
54
|
+
## Prefiks og suffiks
|
|
55
|
+
- Legg klassen `affixes` på en `<div>` rundt `input`
|
|
56
|
+
- Legg elementer før eller etter `input` for å legge til ikon eller tekst
|
|
57
|
+
- **Merk:** for god tilgjengelighet, beskriv ønsket innhold i `<p data-field="description">` eller `<label>`, f.eks.: "Pris i NOK per måned"
|
|
58
|
+
<Canvas of={stories.WithAffixes} />
|
|
59
|
+
|
|
60
|
+
## Antall tegn
|
|
61
|
+
- Legg klassen `validation` og attributt `data-field="counter"` på typisk en `<p>` etter `input`
|
|
62
|
+
- Du kan endre teksten med `data-over="%d characters too many"` og `data-under="%d characters remaining"`
|
|
63
|
+
<Canvas of={stories.WithCharacterCount} />
|
|
64
|
+
|
|
65
|
+
## Med suggestion (Combobox)
|
|
66
|
+
- Legg klassen `suggestion` på `<ds-suggestion>` rundt `<input>` og `<datalist>` med `<option>`s for å lage forslag
|
|
67
|
+
- Legg en `<select hidden name="NAME-HERE">` i `<ds-suggestion>` for å få med verdien i skjemaet
|
|
68
|
+
- Legg en `<del hidden aria-label=""></del>` etter input for å få en tøm-knapp
|
|
69
|
+
- Bruk `data-nofilter` på `<u-datalist>` for å skru av filtrering
|
|
70
|
+
- Trenger du egne hjelpetekster for skjemleser? Se attributter [for ds-suggestion](https://u-elements.github.io/u-elements/elements/u-combobox#attributes-and-props) og for [for u-datalist]https://u-elements.github.io/u-elements/elements/u-datalist#attributes-and-props)
|
|
71
|
+
- **Merk:** Suggestion må alltid ha en tilknyttet `<label>`, men denne kan skjules med `hidden`
|
|
72
|
+
- **React?** Bruk `<Field.Suggestion>`, `<Field.Datalist>` og `<Field.Option>` eller<br />`<Field as={Field.Suggestion}>` ([se eksempel](?path=/story/designsystem-field--react-with-suggestion))
|
|
73
|
+
<Details>
|
|
74
|
+
<Details.Summary>Hva er `ds-suggestion`?</Details.Summary>
|
|
75
|
+
|
|
76
|
+
ds-suggestion bygger på [u-combobox](https://u-elements.github.io/u-elements/elements/u-combobox),
|
|
77
|
+
som sikrer universell utforming. Dette lastes automatisk når du bruker `@mattilsynet/design`.
|
|
78
|
+
Du kan også hente inn tilhørende HTMLElement interface ved behov:
|
|
79
|
+
```
|
|
80
|
+
import { DSSuggestionElement } from '@mattilsynet/design'
|
|
81
|
+
```
|
|
82
|
+
</Details>
|
|
83
|
+
|
|
84
|
+
<Canvas of={stories.WithSuggestion} />
|
|
85
|
+
|
|
86
|
+
<CssVariables component="field" />
|