@mattilsynet/design 3.2.8 → 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.
Files changed (123) hide show
  1. package/mtds/ai/AGENTS.md +892 -0
  2. package/mtds/ai/alert.mdx +63 -0
  3. package/mtds/ai/alert.stories.tsx +128 -0
  4. package/mtds/ai/analytics.mdx +185 -0
  5. package/mtds/ai/app.mdx +60 -0
  6. package/mtds/ai/app.stories.tsx +897 -0
  7. package/mtds/ai/atlas.mdx +82 -0
  8. package/mtds/ai/atlas.stories.tsx +424 -0
  9. package/mtds/ai/avatar.mdx +45 -0
  10. package/mtds/ai/avatar.stories.tsx +109 -0
  11. package/mtds/ai/badge.mdx +70 -0
  12. package/mtds/ai/badge.stories.tsx +122 -0
  13. package/mtds/ai/breadcrumbs.mdx +36 -0
  14. package/mtds/ai/breadcrumbs.stories.tsx +158 -0
  15. package/mtds/ai/button.mdx +179 -0
  16. package/mtds/ai/button.stories.tsx +440 -0
  17. package/mtds/ai/card.mdx +51 -0
  18. package/mtds/ai/card.stories.tsx +469 -0
  19. package/mtds/ai/chart.mdx +67 -0
  20. package/mtds/ai/chart.stories.tsx +519 -0
  21. package/mtds/ai/chip.mdx +71 -0
  22. package/mtds/ai/chip.stories.tsx +211 -0
  23. package/mtds/ai/details.mdx +33 -0
  24. package/mtds/ai/details.stories.tsx +91 -0
  25. package/mtds/ai/dialog.mdx +38 -0
  26. package/mtds/ai/dialog.stories.tsx +373 -0
  27. package/mtds/ai/divider.mdx +19 -0
  28. package/mtds/ai/divider.stories.tsx +50 -0
  29. package/mtds/ai/errorsummary.mdx +26 -0
  30. package/mtds/ai/errorsummary.stories.tsx +137 -0
  31. package/mtds/ai/field.mdx +86 -0
  32. package/mtds/ai/field.stories.tsx +863 -0
  33. package/mtds/ai/fieldset.mdx +126 -0
  34. package/mtds/ai/fieldset.stories.tsx +298 -0
  35. package/mtds/ai/fileupload.mdx +16 -0
  36. package/mtds/ai/fileupload.stories.tsx +126 -0
  37. package/mtds/ai/helptext.mdx +24 -0
  38. package/mtds/ai/helptext.stories.tsx +106 -0
  39. package/mtds/ai/input.mdx +223 -0
  40. package/mtds/ai/input.stories.tsx +352 -0
  41. package/mtds/ai/law.mdx +115 -0
  42. package/mtds/ai/law.stories.tsx +168 -0
  43. package/mtds/ai/layout.mdx +145 -0
  44. package/mtds/ai/layout.stories.tsx +443 -0
  45. package/mtds/ai/link.mdx +45 -0
  46. package/mtds/ai/link.stories.tsx +44 -0
  47. package/mtds/ai/logo.mdx +86 -0
  48. package/mtds/ai/logo.stories.tsx +146 -0
  49. package/mtds/ai/pagination.mdx +136 -0
  50. package/mtds/ai/pagination.stories.tsx +404 -0
  51. package/mtds/ai/popover.mdx +86 -0
  52. package/mtds/ai/popover.stories.tsx +355 -0
  53. package/mtds/ai/print.mdx +96 -0
  54. package/mtds/ai/print.stories.tsx +839 -0
  55. package/mtds/ai/progress.mdx +41 -0
  56. package/mtds/ai/progress.stories.tsx +141 -0
  57. package/mtds/ai/skeleton.mdx +26 -0
  58. package/mtds/ai/skeleton.stories.tsx +131 -0
  59. package/mtds/ai/spinner.mdx +26 -0
  60. package/mtds/ai/spinner.stories.tsx +72 -0
  61. package/mtds/ai/steps.mdx +37 -0
  62. package/mtds/ai/steps.stories.tsx +568 -0
  63. package/mtds/ai/table.mdx +124 -0
  64. package/mtds/ai/table.stories.tsx +1715 -0
  65. package/mtds/ai/tabs.mdx +106 -0
  66. package/mtds/ai/tabs.stories.tsx +159 -0
  67. package/mtds/ai/tag.mdx +49 -0
  68. package/mtds/ai/tag.stories.tsx +111 -0
  69. package/mtds/ai/toast.mdx +67 -0
  70. package/mtds/ai/toast.stories.tsx +215 -0
  71. package/mtds/ai/togglegroup.mdx +75 -0
  72. package/mtds/ai/togglegroup.stories.tsx +96 -0
  73. package/mtds/ai/tooltip.mdx +32 -0
  74. package/mtds/ai/tooltip.stories.tsx +34 -0
  75. package/mtds/ai/typography.mdx +67 -0
  76. package/mtds/ai/typography.stories.tsx +798 -0
  77. package/mtds/ai/validation.mdx +19 -0
  78. package/mtds/ai/validation.stories.tsx +45 -0
  79. package/mtds/app/app-observer.js +1 -1
  80. package/mtds/app/app-toggle.js +26 -10
  81. package/mtds/app/app-toggle.js.map +1 -1
  82. package/mtds/app/app-toggle2.js +10 -26
  83. package/mtds/app/app-toggle2.js.map +1 -1
  84. package/mtds/app/app.js +1 -1
  85. package/mtds/atlas/atlas-element.js +1 -1
  86. package/mtds/breadcrumbs/breadcrumbs.d.ts +1 -1
  87. package/mtds/breadcrumbs/breadcrumbs.js.map +1 -1
  88. package/mtds/chart/chart-lines.js +19 -19
  89. package/mtds/chart/chart-lines.js.map +1 -1
  90. package/mtds/chart/chart.css.js +16 -1
  91. package/mtds/chart/chart.css.js.map +1 -1
  92. package/mtds/chart/chart.stories.d.ts +1 -0
  93. package/mtds/deprecations.js +6 -5
  94. package/mtds/deprecations.js.map +1 -1
  95. package/mtds/errorsummary/errorsummary.d.ts +1 -1
  96. package/mtds/errorsummary/errorsummary.js.map +1 -1
  97. package/mtds/field/field.d.ts +1 -1
  98. package/mtds/field/field.js.map +1 -1
  99. package/mtds/index.d.ts +1 -2
  100. package/mtds/index.iife.js +29 -18
  101. package/mtds/index.js +32 -37
  102. package/mtds/index.js.map +1 -1
  103. package/mtds/package.json.js +1 -1
  104. package/mtds/pagination/pagination.d.ts +1 -1
  105. package/mtds/pagination/pagination.js +9 -8
  106. package/mtds/pagination/pagination.js.map +1 -1
  107. package/mtds/styles.css +1 -1
  108. package/mtds/styles.json +23 -23
  109. package/mtds/styles.module.css.js +33 -33
  110. package/mtds/table/table-observer.js +26 -15
  111. package/mtds/table/table-observer.js.map +1 -1
  112. package/mtds/tabs/tabs.d.ts +1 -1
  113. package/mtds/tabs/tabs.js.map +1 -1
  114. package/mtds/tailwind.css +0 -1
  115. package/mtds/tooltip/tooltip-element.js +7 -6
  116. package/mtds/tooltip/tooltip-element.js.map +1 -1
  117. package/package.json +8 -6
  118. package/mtds/external/@u-elements/u-datalist/dist/u-datalist.js +0 -238
  119. package/mtds/external/@u-elements/u-datalist/dist/u-datalist.js.map +0 -1
  120. package/mtds/external/@u-elements/u-details/dist/u-details.js +0 -101
  121. package/mtds/external/@u-elements/u-details/dist/u-details.js.map +0 -1
  122. package/mtds/external/@u-elements/u-tabs/dist/u-tabs.js +0 -235
  123. package/mtds/external/@u-elements/u-tabs/dist/u-tabs.js.map +0 -1
@@ -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" />