@fristil/designsystem 0.2.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/CHANGELOG.md +85 -0
- package/LICENSE +21 -0
- package/README.md +101 -0
- package/dist/cli.d.ts +31 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +261 -0
- package/dist/cli.js.map +1 -0
- package/dist/components/css/accordion/accordion.d.ts +37 -0
- package/dist/components/css/accordion/accordion.d.ts.map +1 -0
- package/dist/components/css/accordion/accordion.js +31 -0
- package/dist/components/css/accordion/accordion.js.map +1 -0
- package/dist/components/css/alert/alert.d.ts +35 -0
- package/dist/components/css/alert/alert.d.ts.map +1 -0
- package/dist/components/css/alert/alert.js +29 -0
- package/dist/components/css/alert/alert.js.map +1 -0
- package/dist/components/css/avatar/avatar.d.ts +40 -0
- package/dist/components/css/avatar/avatar.d.ts.map +1 -0
- package/dist/components/css/avatar/avatar.js +30 -0
- package/dist/components/css/avatar/avatar.js.map +1 -0
- package/dist/components/css/badge/badge.d.ts +24 -0
- package/dist/components/css/badge/badge.d.ts.map +1 -0
- package/dist/components/css/badge/badge.js +24 -0
- package/dist/components/css/badge/badge.js.map +1 -0
- package/dist/components/css/breadcrumbs/breadcrumbs.d.ts +25 -0
- package/dist/components/css/breadcrumbs/breadcrumbs.d.ts.map +1 -0
- package/dist/components/css/breadcrumbs/breadcrumbs.js +23 -0
- package/dist/components/css/breadcrumbs/breadcrumbs.js.map +1 -0
- package/dist/components/css/button/button.d.ts +26 -0
- package/dist/components/css/button/button.d.ts.map +1 -0
- package/dist/components/css/button/button.js +25 -0
- package/dist/components/css/button/button.js.map +1 -0
- package/dist/components/css/card/card.d.ts +36 -0
- package/dist/components/css/card/card.d.ts.map +1 -0
- package/dist/components/css/card/card.js +28 -0
- package/dist/components/css/card/card.js.map +1 -0
- package/dist/components/css/checkbox/checkbox.d.ts +32 -0
- package/dist/components/css/checkbox/checkbox.d.ts.map +1 -0
- package/dist/components/css/checkbox/checkbox.js +27 -0
- package/dist/components/css/checkbox/checkbox.js.map +1 -0
- package/dist/components/css/dialog/dialog.d.ts +30 -0
- package/dist/components/css/dialog/dialog.d.ts.map +1 -0
- package/dist/components/css/dialog/dialog.js +28 -0
- package/dist/components/css/dialog/dialog.js.map +1 -0
- package/dist/components/css/divider/divider.d.ts +28 -0
- package/dist/components/css/divider/divider.d.ts.map +1 -0
- package/dist/components/css/divider/divider.js +22 -0
- package/dist/components/css/divider/divider.js.map +1 -0
- package/dist/components/css/error-text/error-text.d.ts +24 -0
- package/dist/components/css/error-text/error-text.d.ts.map +1 -0
- package/dist/components/css/error-text/error-text.js +18 -0
- package/dist/components/css/error-text/error-text.js.map +1 -0
- package/dist/components/css/fieldset/fieldset.d.ts +55 -0
- package/dist/components/css/fieldset/fieldset.d.ts.map +1 -0
- package/dist/components/css/fieldset/fieldset.js +39 -0
- package/dist/components/css/fieldset/fieldset.js.map +1 -0
- package/dist/components/css/file-upload/file-upload.d.ts +41 -0
- package/dist/components/css/file-upload/file-upload.d.ts.map +1 -0
- package/dist/components/css/file-upload/file-upload.js +32 -0
- package/dist/components/css/file-upload/file-upload.js.map +1 -0
- package/dist/components/css/heading/heading.d.ts +29 -0
- package/dist/components/css/heading/heading.d.ts.map +1 -0
- package/dist/components/css/heading/heading.js +23 -0
- package/dist/components/css/heading/heading.js.map +1 -0
- package/dist/components/css/help-text/help-text.d.ts +24 -0
- package/dist/components/css/help-text/help-text.d.ts.map +1 -0
- package/dist/components/css/help-text/help-text.js +23 -0
- package/dist/components/css/help-text/help-text.js.map +1 -0
- package/dist/components/css/input/input.d.ts +39 -0
- package/dist/components/css/input/input.d.ts.map +1 -0
- package/dist/components/css/input/input.js +44 -0
- package/dist/components/css/input/input.js.map +1 -0
- package/dist/components/css/label/label.d.ts +33 -0
- package/dist/components/css/label/label.d.ts.map +1 -0
- package/dist/components/css/label/label.js +23 -0
- package/dist/components/css/label/label.js.map +1 -0
- package/dist/components/css/link/link.d.ts +21 -0
- package/dist/components/css/link/link.d.ts.map +1 -0
- package/dist/components/css/link/link.js +14 -0
- package/dist/components/css/link/link.js.map +1 -0
- package/dist/components/css/list/list.d.ts +29 -0
- package/dist/components/css/list/list.d.ts.map +1 -0
- package/dist/components/css/list/list.js +23 -0
- package/dist/components/css/list/list.js.map +1 -0
- package/dist/components/css/pagination/pagination.d.ts +27 -0
- package/dist/components/css/pagination/pagination.d.ts.map +1 -0
- package/dist/components/css/pagination/pagination.js +25 -0
- package/dist/components/css/pagination/pagination.js.map +1 -0
- package/dist/components/css/paragraph/paragraph.d.ts +36 -0
- package/dist/components/css/paragraph/paragraph.d.ts.map +1 -0
- package/dist/components/css/paragraph/paragraph.js +26 -0
- package/dist/components/css/paragraph/paragraph.js.map +1 -0
- package/dist/components/css/radio/radio.d.ts +32 -0
- package/dist/components/css/radio/radio.d.ts.map +1 -0
- package/dist/components/css/radio/radio.js +27 -0
- package/dist/components/css/radio/radio.js.map +1 -0
- package/dist/components/css/search/search.d.ts +33 -0
- package/dist/components/css/search/search.d.ts.map +1 -0
- package/dist/components/css/search/search.js +31 -0
- package/dist/components/css/search/search.js.map +1 -0
- package/dist/components/css/select/select.d.ts +23 -0
- package/dist/components/css/select/select.d.ts.map +1 -0
- package/dist/components/css/select/select.js +18 -0
- package/dist/components/css/select/select.js.map +1 -0
- package/dist/components/css/shared.d.ts +26 -0
- package/dist/components/css/shared.d.ts.map +1 -0
- package/dist/components/css/shared.js +34 -0
- package/dist/components/css/shared.js.map +1 -0
- package/dist/components/css/skeleton/skeleton.d.ts +29 -0
- package/dist/components/css/skeleton/skeleton.d.ts.map +1 -0
- package/dist/components/css/skeleton/skeleton.js +23 -0
- package/dist/components/css/skeleton/skeleton.js.map +1 -0
- package/dist/components/css/skip-link/skip-link.d.ts +18 -0
- package/dist/components/css/skip-link/skip-link.d.ts.map +1 -0
- package/dist/components/css/skip-link/skip-link.js +16 -0
- package/dist/components/css/skip-link/skip-link.js.map +1 -0
- package/dist/components/css/spinner/spinner.d.ts +33 -0
- package/dist/components/css/spinner/spinner.d.ts.map +1 -0
- package/dist/components/css/spinner/spinner.js +25 -0
- package/dist/components/css/spinner/spinner.js.map +1 -0
- package/dist/components/css/sr-only/sr-only.d.ts +17 -0
- package/dist/components/css/sr-only/sr-only.d.ts.map +1 -0
- package/dist/components/css/sr-only/sr-only.js +14 -0
- package/dist/components/css/sr-only/sr-only.js.map +1 -0
- package/dist/components/css/switch/switch.d.ts +33 -0
- package/dist/components/css/switch/switch.d.ts.map +1 -0
- package/dist/components/css/switch/switch.js +29 -0
- package/dist/components/css/switch/switch.js.map +1 -0
- package/dist/components/css/table/table.d.ts +36 -0
- package/dist/components/css/table/table.d.ts.map +1 -0
- package/dist/components/css/table/table.js +28 -0
- package/dist/components/css/table/table.js.map +1 -0
- package/dist/components/css/tag/tag.d.ts +35 -0
- package/dist/components/css/tag/tag.d.ts.map +1 -0
- package/dist/components/css/tag/tag.js +27 -0
- package/dist/components/css/tag/tag.js.map +1 -0
- package/dist/components/css/textarea/textarea.d.ts +23 -0
- package/dist/components/css/textarea/textarea.d.ts.map +1 -0
- package/dist/components/css/textarea/textarea.js +18 -0
- package/dist/components/css/textarea/textarea.js.map +1 -0
- package/dist/components/css/toggle-group/toggle-group.d.ts +31 -0
- package/dist/components/css/toggle-group/toggle-group.d.ts.map +1 -0
- package/dist/components/css/toggle-group/toggle-group.js +29 -0
- package/dist/components/css/toggle-group/toggle-group.js.map +1 -0
- package/dist/components/css/tooltip/tooltip.d.ts +30 -0
- package/dist/components/css/tooltip/tooltip.d.ts.map +1 -0
- package/dist/components/css/tooltip/tooltip.js +28 -0
- package/dist/components/css/tooltip/tooltip.js.map +1 -0
- package/dist/components/frittstaende/calendar/fs-calendar.d.ts +52 -0
- package/dist/components/frittstaende/calendar/fs-calendar.d.ts.map +1 -0
- package/dist/components/frittstaende/calendar/fs-calendar.js +462 -0
- package/dist/components/frittstaende/calendar/fs-calendar.js.map +1 -0
- package/dist/components/frittstaende/date-field/fs-date-field.d.ts +110 -0
- package/dist/components/frittstaende/date-field/fs-date-field.d.ts.map +1 -0
- package/dist/components/frittstaende/date-field/fs-date-field.js +264 -0
- package/dist/components/frittstaende/date-field/fs-date-field.js.map +1 -0
- package/dist/components/frittstaende/suggestion/fs-suggestion.d.ts +133 -0
- package/dist/components/frittstaende/suggestion/fs-suggestion.d.ts.map +1 -0
- package/dist/components/frittstaende/suggestion/fs-suggestion.js +280 -0
- package/dist/components/frittstaende/suggestion/fs-suggestion.js.map +1 -0
- package/dist/components/frittstaende/toast/fs-toast.d.ts +67 -0
- package/dist/components/frittstaende/toast/fs-toast.d.ts.map +1 -0
- package/dist/components/frittstaende/toast/fs-toast.js +108 -0
- package/dist/components/frittstaende/toast/fs-toast.js.map +1 -0
- package/dist/components/ramme/error-summary/fs-error-summary.d.ts +70 -0
- package/dist/components/ramme/error-summary/fs-error-summary.d.ts.map +1 -0
- package/dist/components/ramme/error-summary/fs-error-summary.js +155 -0
- package/dist/components/ramme/error-summary/fs-error-summary.js.map +1 -0
- package/dist/components/ramme/field/field-core.d.ts +70 -0
- package/dist/components/ramme/field/field-core.d.ts.map +1 -0
- package/dist/components/ramme/field/field-core.js +60 -0
- package/dist/components/ramme/field/field-core.js.map +1 -0
- package/dist/components/ramme/field/fs-field.d.ts +49 -0
- package/dist/components/ramme/field/fs-field.d.ts.map +1 -0
- package/dist/components/ramme/field/fs-field.js +118 -0
- package/dist/components/ramme/field/fs-field.js.map +1 -0
- package/dist/components/ramme/popover/fs-popover.d.ts +68 -0
- package/dist/components/ramme/popover/fs-popover.d.ts.map +1 -0
- package/dist/components/ramme/popover/fs-popover.js +167 -0
- package/dist/components/ramme/popover/fs-popover.js.map +1 -0
- package/dist/components/ramme/tabs/fs-tabs.d.ts +61 -0
- package/dist/components/ramme/tabs/fs-tabs.d.ts.map +1 -0
- package/dist/components/ramme/tabs/fs-tabs.js +156 -0
- package/dist/components/ramme/tabs/fs-tabs.js.map +1 -0
- package/dist/dom.d.ts +33 -0
- package/dist/dom.d.ts.map +1 -0
- package/dist/dom.js +77 -0
- package/dist/dom.js.map +1 -0
- package/dist/fs.d.ts +199 -0
- package/dist/fs.d.ts.map +1 -0
- package/dist/fs.js +128 -0
- package/dist/fs.js.map +1 -0
- package/dist/index.d.ts +84 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +48 -0
- package/dist/index.js.map +1 -0
- package/dist/jsx/react.d.ts +127 -0
- package/dist/jsx/react.d.ts.map +1 -0
- package/dist/jsx/react.js +2 -0
- package/dist/jsx/react.js.map +1 -0
- package/dist/react.d.ts +207 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +109 -0
- package/dist/react.js.map +1 -0
- package/dist/tailwind/preset.d.ts +120 -0
- package/dist/tailwind/preset.d.ts.map +1 -0
- package/dist/tailwind/preset.js +107 -0
- package/dist/tailwind/preset.js.map +1 -0
- package/dist/takeover.d.ts +79 -0
- package/dist/takeover.d.ts.map +1 -0
- package/dist/takeover.js +119 -0
- package/dist/takeover.js.map +1 -0
- package/dist/tokens/color.d.ts +85 -0
- package/dist/tokens/color.d.ts.map +1 -0
- package/dist/tokens/color.js +212 -0
- package/dist/tokens/color.js.map +1 -0
- package/dist/tokens/theme.d.ts +52 -0
- package/dist/tokens/theme.d.ts.map +1 -0
- package/dist/tokens/theme.js +197 -0
- package/dist/tokens/theme.js.map +1 -0
- package/dist/tokens/tokens.d.ts +210 -0
- package/dist/tokens/tokens.d.ts.map +1 -0
- package/dist/tokens/tokens.js +238 -0
- package/dist/tokens/tokens.js.map +1 -0
- package/package.json +334 -0
- package/src/components/css/accordion/accordion.css +74 -0
- package/src/components/css/accordion/accordion.ts +50 -0
- package/src/components/css/alert/alert.css +50 -0
- package/src/components/css/alert/alert.ts +48 -0
- package/src/components/css/avatar/avatar.css +64 -0
- package/src/components/css/avatar/avatar.ts +57 -0
- package/src/components/css/badge/badge.css +42 -0
- package/src/components/css/badge/badge.ts +43 -0
- package/src/components/css/breadcrumbs/breadcrumbs.css +50 -0
- package/src/components/css/breadcrumbs/breadcrumbs.ts +29 -0
- package/src/components/css/button/button.css +101 -0
- package/src/components/css/button/button.ts +44 -0
- package/src/components/css/card/card.css +48 -0
- package/src/components/css/card/card.ts +50 -0
- package/src/components/css/checkbox/checkbox.css +96 -0
- package/src/components/css/checkbox/checkbox.ts +50 -0
- package/src/components/css/dialog/dialog.css +50 -0
- package/src/components/css/dialog/dialog.ts +36 -0
- package/src/components/css/divider/divider.css +18 -0
- package/src/components/css/divider/divider.ts +41 -0
- package/src/components/css/error-text/error-text.css +21 -0
- package/src/components/css/error-text/error-text.ts +37 -0
- package/src/components/css/fieldset/fieldset.css +57 -0
- package/src/components/css/fieldset/fieldset.ts +87 -0
- package/src/components/css/file-upload/file-upload.css +92 -0
- package/src/components/css/file-upload/file-upload.ts +65 -0
- package/src/components/css/heading/heading.css +33 -0
- package/src/components/css/heading/heading.ts +42 -0
- package/src/components/css/help-text/help-text.css +24 -0
- package/src/components/css/help-text/help-text.ts +42 -0
- package/src/components/css/input/input.css +131 -0
- package/src/components/css/input/input.ts +76 -0
- package/src/components/css/label/label.css +34 -0
- package/src/components/css/label/label.ts +51 -0
- package/src/components/css/link/link.css +36 -0
- package/src/components/css/link/link.ts +29 -0
- package/src/components/css/list/list.css +40 -0
- package/src/components/css/list/list.ts +42 -0
- package/src/components/css/pagination/pagination.css +77 -0
- package/src/components/css/pagination/pagination.ts +33 -0
- package/src/components/css/paragraph/paragraph.css +31 -0
- package/src/components/css/paragraph/paragraph.ts +53 -0
- package/src/components/css/radio/radio.css +76 -0
- package/src/components/css/radio/radio.ts +50 -0
- package/src/components/css/search/search.css +49 -0
- package/src/components/css/search/search.ts +52 -0
- package/src/components/css/select/select.css +95 -0
- package/src/components/css/select/select.ts +40 -0
- package/src/components/css/shared.ts +40 -0
- package/src/components/css/skeleton/skeleton.css +49 -0
- package/src/components/css/skeleton/skeleton.ts +43 -0
- package/src/components/css/skip-link/skip-link.css +36 -0
- package/src/components/css/skip-link/skip-link.ts +22 -0
- package/src/components/css/spinner/spinner.css +48 -0
- package/src/components/css/spinner/spinner.ts +48 -0
- package/src/components/css/sr-only/sr-only.css +16 -0
- package/src/components/css/sr-only/sr-only.ts +18 -0
- package/src/components/css/switch/switch.css +101 -0
- package/src/components/css/switch/switch.ts +46 -0
- package/src/components/css/table/table.css +63 -0
- package/src/components/css/table/table.ts +50 -0
- package/src/components/css/tag/tag.css +58 -0
- package/src/components/css/tag/tag.ts +49 -0
- package/src/components/css/textarea/textarea.css +71 -0
- package/src/components/css/textarea/textarea.ts +40 -0
- package/src/components/css/toggle-group/toggle-group.css +82 -0
- package/src/components/css/toggle-group/toggle-group.ts +37 -0
- package/src/components/css/tooltip/tooltip.css +47 -0
- package/src/components/css/tooltip/tooltip.ts +36 -0
- package/src/components/frittstaende/calendar/fs-calendar.ts +525 -0
- package/src/components/frittstaende/date-field/date-field.css +54 -0
- package/src/components/frittstaende/date-field/fs-date-field.ts +309 -0
- package/src/components/frittstaende/suggestion/fs-suggestion.ts +333 -0
- package/src/components/frittstaende/suggestion/suggestion.css +63 -0
- package/src/components/frittstaende/toast/fs-toast.ts +146 -0
- package/src/components/frittstaende/toast/toast.css +71 -0
- package/src/components/ramme/error-summary/error-summary.css +45 -0
- package/src/components/ramme/error-summary/fs-error-summary.ts +197 -0
- package/src/components/ramme/field/field-core.ts +132 -0
- package/src/components/ramme/field/field.css +6 -0
- package/src/components/ramme/field/fs-field.ts +154 -0
- package/src/components/ramme/popover/fs-popover.ts +205 -0
- package/src/components/ramme/popover/popover.css +34 -0
- package/src/components/ramme/tabs/fs-tabs.ts +182 -0
- package/src/components/ramme/tabs/tabs.css +68 -0
- package/src/tailwind/tailwind.css +134 -0
- package/src/tokens/color.ts +267 -0
- package/src/tokens/theme.ts +350 -0
- package/src/tokens/tokens.css +214 -0
- package/src/tokens/tokens.ts +293 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { attributes, createGuard } from "../shared.js"
|
|
2
|
+
|
|
3
|
+
export const SPINNER_CLASS = "fs-spinner" as const
|
|
4
|
+
|
|
5
|
+
export const spinnerSizes = ["small", "medium", "large"] as const
|
|
6
|
+
|
|
7
|
+
export type SpinnerSize = (typeof spinnerSizes)[number]
|
|
8
|
+
export type NonDefaultSpinnerSize = Exclude<SpinnerSize, "medium">
|
|
9
|
+
|
|
10
|
+
export type SpinnerOptions = {
|
|
11
|
+
/** Størrelse. Standard: `medium`. */
|
|
12
|
+
size?: SpinnerSize
|
|
13
|
+
/** Hva som lastes. Blir `aria-label`, så ventingen kan leses opp. */
|
|
14
|
+
label?: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type SpinnerAttributes = {
|
|
18
|
+
class: typeof SPINNER_CLASS
|
|
19
|
+
"data-size"?: NonDefaultSpinnerSize
|
|
20
|
+
role?: "status"
|
|
21
|
+
"aria-label"?: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Attributtene for en venteindikator.
|
|
26
|
+
*
|
|
27
|
+
* Uten `label` er ringen bare en form, og skjermlesere melder ingenting. Med
|
|
28
|
+
* `label` blir den en `role="status"` som sier hva som pågår. Har du allerede
|
|
29
|
+
* en synlig tekst ved siden av, la den bære beskjeden og dropp `label`, så
|
|
30
|
+
* det samme ikke leses opp to ganger.
|
|
31
|
+
*
|
|
32
|
+
* ```ts
|
|
33
|
+
* <span {...spinner({ label: "Laster søknader" })} />
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export const spinner = Object.assign(
|
|
37
|
+
({ size = "medium", label }: SpinnerOptions = {}): SpinnerAttributes =>
|
|
38
|
+
attributes({
|
|
39
|
+
class: SPINNER_CLASS,
|
|
40
|
+
"data-size": size === "medium" ? undefined : size,
|
|
41
|
+
role: label ? ("status" as const) : undefined,
|
|
42
|
+
"aria-label": label,
|
|
43
|
+
}),
|
|
44
|
+
{
|
|
45
|
+
sizes: spinnerSizes,
|
|
46
|
+
isSize: createGuard(spinnerSizes),
|
|
47
|
+
},
|
|
48
|
+
)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
@layer fristil {
|
|
2
|
+
/* Tekst som bare skjermlesere skal få. `display: none` og
|
|
3
|
+
`visibility: hidden` fjerner elementet også for dem, så innholdet må bli
|
|
4
|
+
stående i dokumentet og krympes bort visuelt i stedet. */
|
|
5
|
+
.fs-sr-only {
|
|
6
|
+
position: absolute;
|
|
7
|
+
width: 1px;
|
|
8
|
+
height: 1px;
|
|
9
|
+
padding: 0;
|
|
10
|
+
margin: -1px;
|
|
11
|
+
overflow: hidden;
|
|
12
|
+
clip-path: inset(50%);
|
|
13
|
+
white-space: nowrap;
|
|
14
|
+
border-width: 0;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const SR_ONLY_CLASS = "fs-sr-only" as const
|
|
2
|
+
|
|
3
|
+
export type SrOnlyAttributes = {
|
|
4
|
+
class: typeof SR_ONLY_CLASS
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Attributtene for tekst som bare skjermlesere skal få.
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* <span {...srOnly()}>Åpner i ny fane</span>
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* Bruk den når en ledetekst eller forklaring trengs for den som hører siden,
|
|
15
|
+
* men ville vært støy for den som ser den. Skal innholdet bort for alle, hører
|
|
16
|
+
* `hidden` hjemme i stedet.
|
|
17
|
+
*/
|
|
18
|
+
export const srOnly = (): SrOnlyAttributes => ({ class: SR_ONLY_CLASS })
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
@layer fristil {
|
|
2
|
+
.fs-switch {
|
|
3
|
+
box-sizing: border-box;
|
|
4
|
+
appearance: none;
|
|
5
|
+
flex: none;
|
|
6
|
+
display: inline-block;
|
|
7
|
+
inline-size: var(--fs-switch-width, var(--size-10));
|
|
8
|
+
block-size: var(--fs-switch-height, var(--size-6));
|
|
9
|
+
margin: 0;
|
|
10
|
+
border: 1px solid var(--semantic-field-border);
|
|
11
|
+
border-radius: var(--size-4);
|
|
12
|
+
background-color: var(--semantic-neutral-background);
|
|
13
|
+
/* Knappen er en gradient, ikke et pseudoelement: Firefox rendrer ikke
|
|
14
|
+
::before og ::after på et <input>. */
|
|
15
|
+
background-image: radial-gradient(
|
|
16
|
+
circle at center,
|
|
17
|
+
var(--semantic-page-foreground) 0 34%,
|
|
18
|
+
transparent 36%
|
|
19
|
+
);
|
|
20
|
+
background-repeat: no-repeat;
|
|
21
|
+
background-size: var(--fs-switch-height, var(--size-6)) 100%;
|
|
22
|
+
/* background-position har ingen logisk variant, så begge retningene
|
|
23
|
+
skrives for hånd. Av er på starten, på er på slutten. */
|
|
24
|
+
background-position: left center;
|
|
25
|
+
cursor: pointer;
|
|
26
|
+
transition:
|
|
27
|
+
background-color 120ms ease,
|
|
28
|
+
background-position 120ms ease;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.fs-switch:hover:not(:disabled) {
|
|
32
|
+
border-color: var(--semantic-field-border-hover);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.fs-switch:focus-visible {
|
|
36
|
+
outline: 2px solid var(--semantic-interactive-main);
|
|
37
|
+
outline-offset: 2px;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.fs-switch:dir(rtl) {
|
|
41
|
+
background-position: right center;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.fs-switch:checked {
|
|
45
|
+
background-color: var(--semantic-interactive-main);
|
|
46
|
+
border-color: var(--semantic-interactive-main);
|
|
47
|
+
background-position: right center;
|
|
48
|
+
background-image: radial-gradient(
|
|
49
|
+
circle at center,
|
|
50
|
+
var(--semantic-interactive-contrast) 0 34%,
|
|
51
|
+
transparent 36%
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.fs-switch:checked:dir(rtl) {
|
|
56
|
+
background-position: left center;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.fs-switch:disabled {
|
|
60
|
+
background-color: var(--semantic-disabled-background);
|
|
61
|
+
border-color: var(--semantic-disabled-background);
|
|
62
|
+
background-image: radial-gradient(
|
|
63
|
+
circle at center,
|
|
64
|
+
var(--semantic-disabled-foreground) 0 34%,
|
|
65
|
+
transparent 36%
|
|
66
|
+
);
|
|
67
|
+
cursor: not-allowed;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/* Brukeren har bedt om mindre bevegelse. Bryteren skal fortsatt bytte
|
|
71
|
+
tilstand, den skal bare ikke gli. */
|
|
72
|
+
@media (prefers-reduced-motion: reduce) {
|
|
73
|
+
.fs-switch {
|
|
74
|
+
transition: none;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.fs-switch-row {
|
|
79
|
+
display: flex;
|
|
80
|
+
align-items: center;
|
|
81
|
+
gap: var(--size-2);
|
|
82
|
+
line-height: 1.5;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/*
|
|
86
|
+
* Høykontrast: knappen er en gradient, og forsvinner i modusen. Uten den er
|
|
87
|
+
* av og på helt like. Her faller vi tilbake til en vanlig avkryssingsboks.
|
|
88
|
+
* Bryteren mister formen sin, men `role="switch"` står fortsatt, så den
|
|
89
|
+
* leses opp som på og av, og tilstanden er synlig igjen.
|
|
90
|
+
*/
|
|
91
|
+
@media (forced-colors: active) {
|
|
92
|
+
.fs-switch,
|
|
93
|
+
.fs-switch:checked {
|
|
94
|
+
appearance: auto;
|
|
95
|
+
inline-size: auto;
|
|
96
|
+
block-size: auto;
|
|
97
|
+
background-image: none;
|
|
98
|
+
border: 0;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { attributes } from "../shared.js"
|
|
2
|
+
|
|
3
|
+
export const SWITCH_CLASS = "fs-switch" as const
|
|
4
|
+
/** Raden som holder bryteren og teksten ved siden av hverandre. */
|
|
5
|
+
export const SWITCH_ROW_CLASS = "fs-switch-row" as const
|
|
6
|
+
|
|
7
|
+
export type SwitchOptions = {
|
|
8
|
+
/** Kontrollen er slått av. */
|
|
9
|
+
disabled?: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type SwitchAttributes = {
|
|
13
|
+
class: typeof SWITCH_CLASS
|
|
14
|
+
type: "checkbox"
|
|
15
|
+
role: "switch"
|
|
16
|
+
disabled?: true
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Attributtene for en av- og på-bryter.
|
|
21
|
+
*
|
|
22
|
+
* Bryteren er en avkryssingsboks med `role="switch"`. Da leser skjermlesere
|
|
23
|
+
* den opp som «på» og «av» framfor «avkrysset», mens tastaturet og
|
|
24
|
+
* `FormData` virker som for en vanlig boks.
|
|
25
|
+
*
|
|
26
|
+
* Bryteren har ingen valideringstilstand. Et valg som er på eller av kan ikke
|
|
27
|
+
* være ugyldig i seg selv; er det påkrevd å slå den på, hører kravet hjemme i
|
|
28
|
+
* en avkryssingsboks med `required`.
|
|
29
|
+
*
|
|
30
|
+
* ```ts
|
|
31
|
+
* <input {...switchControl()} />
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export const switchControl = Object.assign(
|
|
35
|
+
({ disabled }: SwitchOptions = {}): SwitchAttributes =>
|
|
36
|
+
attributes({
|
|
37
|
+
class: SWITCH_CLASS,
|
|
38
|
+
type: "checkbox" as const,
|
|
39
|
+
role: "switch" as const,
|
|
40
|
+
disabled: disabled ? (true as const) : undefined,
|
|
41
|
+
}),
|
|
42
|
+
{
|
|
43
|
+
/** Klassen på raden rundt bryteren og teksten. */
|
|
44
|
+
row: SWITCH_ROW_CLASS,
|
|
45
|
+
},
|
|
46
|
+
)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
@layer fristil {
|
|
2
|
+
.fs-table {
|
|
3
|
+
box-sizing: border-box;
|
|
4
|
+
inline-size: 100%;
|
|
5
|
+
border-collapse: collapse;
|
|
6
|
+
font-family: inherit;
|
|
7
|
+
font-size: var(--font-size-m);
|
|
8
|
+
line-height: 1.5;
|
|
9
|
+
color: var(--semantic-page-foreground);
|
|
10
|
+
/* Tall som står under hverandre skal ha samme bredde per siffer, ellers
|
|
11
|
+
kan ikke kolonnen leses nedover. */
|
|
12
|
+
font-variant-numeric: tabular-nums;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.fs-table caption {
|
|
16
|
+
text-align: start;
|
|
17
|
+
font-weight: 600;
|
|
18
|
+
padding-block-end: var(--size-2);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.fs-table th,
|
|
22
|
+
.fs-table td {
|
|
23
|
+
padding: var(--fs-table-cell-padding, var(--size-2) var(--size-3));
|
|
24
|
+
text-align: start;
|
|
25
|
+
vertical-align: top;
|
|
26
|
+
border-block-end: 1px solid var(--semantic-divider-30);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.fs-table thead th {
|
|
30
|
+
font-weight: 600;
|
|
31
|
+
border-block-end: 2px solid var(--semantic-divider-100);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.fs-table tbody tr:last-child th,
|
|
35
|
+
.fs-table tbody tr:last-child td {
|
|
36
|
+
border-block-end: 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.fs-table[data-variant="striped"] tbody tr:nth-child(even) {
|
|
40
|
+
background: var(--semantic-neutral-background);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.fs-table[data-hoverable] tbody tr:hover {
|
|
44
|
+
background: var(--semantic-interactive-background);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.fs-table td[data-align="end"],
|
|
48
|
+
.fs-table th[data-align="end"] {
|
|
49
|
+
text-align: end;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* En tabell som er bredere enn skjermen må kunne rulles, og det området må
|
|
53
|
+
kunne nås med tastatur. Derfor tabindex="0" på beholderen. */
|
|
54
|
+
.fs-table-scroll {
|
|
55
|
+
overflow-x: auto;
|
|
56
|
+
max-inline-size: 100%;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.fs-table-scroll:focus-visible {
|
|
60
|
+
outline: 2px solid var(--semantic-interactive-main);
|
|
61
|
+
outline-offset: 2px;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { attributes, createGuard } from "../shared.js"
|
|
2
|
+
|
|
3
|
+
export const TABLE_CLASS = "fs-table" as const
|
|
4
|
+
/** Beholderen som lar en bred tabell rulles sidelengs. */
|
|
5
|
+
export const TABLE_SCROLL_CLASS = "fs-table-scroll" as const
|
|
6
|
+
|
|
7
|
+
export const tableVariants = ["default", "striped"] as const
|
|
8
|
+
|
|
9
|
+
export type TableVariant = (typeof tableVariants)[number]
|
|
10
|
+
export type NonDefaultTableVariant = Exclude<TableVariant, "default">
|
|
11
|
+
|
|
12
|
+
export type TableOptions = {
|
|
13
|
+
/** Annenhver rad i dempet flate. Standard: `default`. */
|
|
14
|
+
variant?: TableVariant
|
|
15
|
+
/** Marker raden under musa. Bruk bare når rader kan trykkes på. */
|
|
16
|
+
hoverable?: boolean
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type TableAttributes = {
|
|
20
|
+
class: typeof TABLE_CLASS
|
|
21
|
+
"data-variant"?: NonDefaultTableVariant
|
|
22
|
+
"data-hoverable"?: ""
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Attributtene for en tabell.
|
|
27
|
+
*
|
|
28
|
+
* Klassen hører på et ekte `<table>` med `<thead>` og `<th>`. Det er
|
|
29
|
+
* overskriftscellene som gjør at skjermlesere kan si «Beløp, 1 240 kroner»
|
|
30
|
+
* når brukeren står i en celle. Bygger du tabellen av `<div>`-er, finnes ikke
|
|
31
|
+
* den koblingen.
|
|
32
|
+
*
|
|
33
|
+
* ```ts
|
|
34
|
+
* <table {...table({ variant: "striped" })}>…</table>
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export const table = Object.assign(
|
|
38
|
+
({ variant = "default", hoverable }: TableOptions = {}): TableAttributes =>
|
|
39
|
+
attributes({
|
|
40
|
+
class: TABLE_CLASS,
|
|
41
|
+
"data-variant": variant === "default" ? undefined : variant,
|
|
42
|
+
"data-hoverable": hoverable ? ("" as const) : undefined,
|
|
43
|
+
}),
|
|
44
|
+
{
|
|
45
|
+
/** Klassen på beholderen rundt en bred tabell. */
|
|
46
|
+
scroll: TABLE_SCROLL_CLASS,
|
|
47
|
+
variants: tableVariants,
|
|
48
|
+
isVariant: createGuard(tableVariants),
|
|
49
|
+
},
|
|
50
|
+
)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
@layer fristil {
|
|
2
|
+
.fs-tag {
|
|
3
|
+
box-sizing: border-box;
|
|
4
|
+
display: inline-flex;
|
|
5
|
+
align-items: center;
|
|
6
|
+
gap: var(--size-1);
|
|
7
|
+
padding: var(--fs-tag-padding, var(--size-0-5) var(--size-2));
|
|
8
|
+
font-family: inherit;
|
|
9
|
+
font-size: var(--font-size-s);
|
|
10
|
+
line-height: 1.4;
|
|
11
|
+
color: var(--semantic-page-foreground);
|
|
12
|
+
background: transparent;
|
|
13
|
+
border: 1px solid var(--semantic-divider-30);
|
|
14
|
+
border-radius: var(--fs-tag-radius, var(--size-1));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.fs-tag[data-variant="filled"] {
|
|
18
|
+
background: var(--semantic-neutral-background);
|
|
19
|
+
border-color: transparent;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/* Merkelappen kan velges av og på, og er da en ekte knapp. */
|
|
23
|
+
.fs-tag[data-selectable] {
|
|
24
|
+
cursor: pointer;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.fs-tag[data-selectable]:hover {
|
|
28
|
+
border-color: var(--semantic-interactive-main);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.fs-tag[data-selectable]:focus-visible {
|
|
32
|
+
outline: 2px solid var(--semantic-interactive-main);
|
|
33
|
+
outline-offset: 2px;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.fs-tag[aria-pressed="true"] {
|
|
37
|
+
background: var(--semantic-interactive-main);
|
|
38
|
+
border-color: var(--semantic-interactive-main);
|
|
39
|
+
color: var(--semantic-interactive-contrast);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.fs-tag:disabled,
|
|
43
|
+
.fs-tag[aria-disabled="true"] {
|
|
44
|
+
background: var(--semantic-disabled-background);
|
|
45
|
+
border-color: var(--semantic-disabled-background);
|
|
46
|
+
color: var(--semantic-disabled-foreground);
|
|
47
|
+
cursor: not-allowed;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* Høykontrast: den valgte lappen skilles bare med farge. Systemfargene
|
|
51
|
+
slipper gjennom der våre egne ikke gjør det. */
|
|
52
|
+
@media (forced-colors: active) {
|
|
53
|
+
.fs-tag[aria-pressed="true"] {
|
|
54
|
+
background-color: Highlight;
|
|
55
|
+
color: HighlightText;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { attributes, createGuard } from "../shared.js"
|
|
2
|
+
|
|
3
|
+
export const TAG_CLASS = "fs-tag" as const
|
|
4
|
+
|
|
5
|
+
export const tagVariants = ["outline", "filled"] as const
|
|
6
|
+
|
|
7
|
+
export type TagVariant = (typeof tagVariants)[number]
|
|
8
|
+
export type NonDefaultTagVariant = Exclude<TagVariant, "outline">
|
|
9
|
+
|
|
10
|
+
export type TagOptions = {
|
|
11
|
+
/** Omriss eller dempet flate. Standard: `outline`. */
|
|
12
|
+
variant?: TagVariant
|
|
13
|
+
/** Merkelappen kan slås av og på. Bruk bare på en `<button>`. */
|
|
14
|
+
selectable?: boolean
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type TagAttributes = {
|
|
18
|
+
class: typeof TAG_CLASS
|
|
19
|
+
"data-variant"?: NonDefaultTagVariant
|
|
20
|
+
"data-selectable"?: ""
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Attributtene for en merkelapp.
|
|
25
|
+
*
|
|
26
|
+
* En merkelapp merker innhold: emneord på en artikkel, valgte filtre over en
|
|
27
|
+
* liste. Sier den hvilken tilstand noe er i, er [Badge](../badge/badge.js)
|
|
28
|
+
* riktig i stedet.
|
|
29
|
+
*
|
|
30
|
+
* `selectable` gir hover og fokusmarkering, men gjør ingenting trykkbart.
|
|
31
|
+
* Elementet må være en `<button>`, og du setter `aria-pressed` selv, siden
|
|
32
|
+
* bare du vet om lappen er valgt.
|
|
33
|
+
*
|
|
34
|
+
* ```ts
|
|
35
|
+
* <button {...tag({ selectable: true })} aria-pressed="true">Bostøtte</button>
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export const tag = Object.assign(
|
|
39
|
+
({ variant = "outline", selectable }: TagOptions = {}): TagAttributes =>
|
|
40
|
+
attributes({
|
|
41
|
+
class: TAG_CLASS,
|
|
42
|
+
"data-variant": variant === "outline" ? undefined : variant,
|
|
43
|
+
"data-selectable": selectable ? ("" as const) : undefined,
|
|
44
|
+
}),
|
|
45
|
+
{
|
|
46
|
+
variants: tagVariants,
|
|
47
|
+
isVariant: createGuard(tagVariants),
|
|
48
|
+
},
|
|
49
|
+
)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
@layer fristil {
|
|
2
|
+
.fs-textarea {
|
|
3
|
+
/* Designsystemet kan ikke regne med at konsumenten har en CSS-reset.
|
|
4
|
+
Uten border-box legges padding og ramme utenpå width, og feltet
|
|
5
|
+
blir bredere enn boksen det ligger i. */
|
|
6
|
+
box-sizing: border-box;
|
|
7
|
+
|
|
8
|
+
display: block;
|
|
9
|
+
width: 100%;
|
|
10
|
+
min-height: var(--fs-textarea-min-height, calc(var(--size-4) * 5));
|
|
11
|
+
padding: var(--fs-textarea-padding, var(--size-2) var(--size-3));
|
|
12
|
+
font-family: inherit;
|
|
13
|
+
font-size: var(--font-size-m);
|
|
14
|
+
line-height: 1.5;
|
|
15
|
+
color: var(--semantic-page-foreground);
|
|
16
|
+
background: var(--semantic-page-background);
|
|
17
|
+
border: 1px solid var(--semantic-field-border);
|
|
18
|
+
border-radius: var(--fs-textarea-radius, var(--size-1));
|
|
19
|
+
resize: vertical;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.fs-textarea::placeholder {
|
|
23
|
+
color: var(--semantic-muted-foreground);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.fs-textarea:hover {
|
|
27
|
+
border-color: var(--semantic-field-border-hover);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.fs-textarea:focus-visible {
|
|
31
|
+
outline: 2px solid var(--semantic-interactive-main);
|
|
32
|
+
outline-offset: 2px;
|
|
33
|
+
border-color: var(--semantic-interactive-main);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.fs-textarea:disabled,
|
|
37
|
+
.fs-textarea[aria-disabled="true"] {
|
|
38
|
+
background: var(--semantic-disabled-background);
|
|
39
|
+
color: var(--semantic-disabled-foreground);
|
|
40
|
+
border-color: var(--semantic-disabled-background);
|
|
41
|
+
/* Ingen pointer-events: none her. Nettleseren blokkerer allerede et
|
|
42
|
+
deaktivert felt, og pointer-events ville bare hindret markøren i å
|
|
43
|
+
vises. Elementet blir da ikke truffet av musa i det hele tatt. */
|
|
44
|
+
cursor: not-allowed;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* aria-disabled er bare en opplysning til skjermlesere; elementet virker
|
|
48
|
+
fortsatt. Her stopper pointer-events musa, på bekostning av markøren.
|
|
49
|
+
Skal feltet faktisk være av, bruk `disabled` eller `readonly`. */
|
|
50
|
+
.fs-textarea[aria-disabled="true"] {
|
|
51
|
+
pointer-events: none;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.fs-textarea[data-state="invalid"] {
|
|
55
|
+
border-color: var(--semantic-danger-foreground);
|
|
56
|
+
background: var(--semantic-danger-background);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.fs-textarea[data-state="invalid"]:focus-visible {
|
|
60
|
+
outline-color: var(--semantic-danger-foreground);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.fs-textarea[data-state="success"] {
|
|
64
|
+
border-color: var(--semantic-success-foreground);
|
|
65
|
+
background: var(--semantic-success-background);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.fs-textarea[data-state="success"]:focus-visible {
|
|
69
|
+
outline-color: var(--semantic-success-foreground);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import {
|
|
2
|
+
attributes,
|
|
3
|
+
type FieldState,
|
|
4
|
+
fieldStates,
|
|
5
|
+
isFieldState,
|
|
6
|
+
type NonDefaultFieldState,
|
|
7
|
+
} from "../shared.js"
|
|
8
|
+
|
|
9
|
+
export const TEXTAREA_CLASS = "fs-textarea" as const
|
|
10
|
+
|
|
11
|
+
export type TextareaOptions = {
|
|
12
|
+
/** Valideringstilstand. Standard: `default`. */
|
|
13
|
+
state?: FieldState
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type TextareaAttributes = {
|
|
17
|
+
class: typeof TEXTAREA_CLASS
|
|
18
|
+
"data-state"?: NonDefaultFieldState
|
|
19
|
+
"aria-invalid"?: "true"
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Attributtene for et flerlinjet tekstfelt.
|
|
24
|
+
*
|
|
25
|
+
* ```ts
|
|
26
|
+
* <textarea {...textarea({ state: "invalid" })} />
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export const textarea = Object.assign(
|
|
30
|
+
({ state = "default" }: TextareaOptions = {}): TextareaAttributes =>
|
|
31
|
+
attributes({
|
|
32
|
+
class: TEXTAREA_CLASS,
|
|
33
|
+
"data-state": state === "default" ? undefined : state,
|
|
34
|
+
"aria-invalid": state === "invalid" ? ("true" as const) : undefined,
|
|
35
|
+
}),
|
|
36
|
+
{
|
|
37
|
+
states: fieldStates,
|
|
38
|
+
isState: isFieldState,
|
|
39
|
+
},
|
|
40
|
+
)
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
@layer fristil {
|
|
2
|
+
.fs-toggle-group {
|
|
3
|
+
box-sizing: border-box;
|
|
4
|
+
display: inline-flex;
|
|
5
|
+
flex-wrap: wrap;
|
|
6
|
+
gap: 0;
|
|
7
|
+
margin: 0;
|
|
8
|
+
padding: 0;
|
|
9
|
+
border: 1px solid var(--semantic-field-border);
|
|
10
|
+
border-radius: var(--fs-toggle-group-radius, var(--size-1));
|
|
11
|
+
overflow: hidden;
|
|
12
|
+
min-inline-size: 0;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.fs-toggle-group__option {
|
|
16
|
+
display: inline-flex;
|
|
17
|
+
align-items: center;
|
|
18
|
+
justify-content: center;
|
|
19
|
+
/* 40 piksler høy, som knappene, så en rad av dem står i linje. */
|
|
20
|
+
min-block-size: var(--fs-toggle-group-height, var(--size-10));
|
|
21
|
+
padding: var(--fs-toggle-group-padding, 0 var(--size-4));
|
|
22
|
+
font-family: inherit;
|
|
23
|
+
font-size: var(--font-size-m);
|
|
24
|
+
line-height: 1.5;
|
|
25
|
+
color: var(--semantic-interactive-main);
|
|
26
|
+
background: var(--semantic-page-background);
|
|
27
|
+
cursor: pointer;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.fs-toggle-group__option + .fs-toggle-group__option {
|
|
31
|
+
border-inline-start: 1px solid var(--semantic-field-border);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* Radioknappen ligger inni ledeteksten og er krympet bort visuelt. Den blir
|
|
35
|
+
stående i tastaturrekkefølgen, så piltastene og skjermleseren virker som
|
|
36
|
+
i en vanlig radiogruppe. */
|
|
37
|
+
.fs-toggle-group__option input {
|
|
38
|
+
position: absolute;
|
|
39
|
+
inline-size: 1px;
|
|
40
|
+
block-size: 1px;
|
|
41
|
+
margin: -1px;
|
|
42
|
+
padding: 0;
|
|
43
|
+
overflow: hidden;
|
|
44
|
+
clip-path: inset(50%);
|
|
45
|
+
white-space: nowrap;
|
|
46
|
+
border-width: 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.fs-toggle-group__option:hover {
|
|
50
|
+
background: var(--semantic-interactive-background);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.fs-toggle-group__option:has(input:focus-visible) {
|
|
54
|
+
outline: 2px solid var(--semantic-interactive-main);
|
|
55
|
+
outline-offset: -2px;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.fs-toggle-group__option:has(input:checked) {
|
|
59
|
+
color: var(--semantic-interactive-contrast);
|
|
60
|
+
background: var(--semantic-interactive-main);
|
|
61
|
+
font-weight: 600;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.fs-toggle-group__option:has(input:disabled) {
|
|
65
|
+
color: var(--semantic-disabled-foreground);
|
|
66
|
+
background: var(--semantic-disabled-background);
|
|
67
|
+
cursor: not-allowed;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/*
|
|
71
|
+
* Høykontrast: det valgte alternativet skilles bare med bakgrunnsfarge, og
|
|
72
|
+
* egne farger overstyres i modusen. Systemfargene slipper gjennom, og
|
|
73
|
+
* `Highlight` med `HighlightText` er nettopp paret operativsystemet bruker
|
|
74
|
+
* til å vise noe som er valgt.
|
|
75
|
+
*/
|
|
76
|
+
@media (forced-colors: active) {
|
|
77
|
+
.fs-toggle-group__option:has(input:checked) {
|
|
78
|
+
background-color: Highlight;
|
|
79
|
+
color: HighlightText;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { attributes } from "../shared.js"
|
|
2
|
+
|
|
3
|
+
export const TOGGLE_GROUP_CLASS = "fs-toggle-group" as const
|
|
4
|
+
/** Ledeteksten rundt hvert alternativ. */
|
|
5
|
+
export const TOGGLE_GROUP_OPTION_CLASS = "fs-toggle-group__option" as const
|
|
6
|
+
|
|
7
|
+
export type ToggleGroupAttributes = {
|
|
8
|
+
class: typeof TOGGLE_GROUP_CLASS
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Attributtene for en gruppe der ett alternativ er valgt om gangen.
|
|
13
|
+
*
|
|
14
|
+
* Gruppen er radioknapper i forkledning. Hvert alternativ er en `<label>` med
|
|
15
|
+
* en `<input type="radio">` inni, krympet bort visuelt. Da beholder du
|
|
16
|
+
* piltastene, `FormData` og at skjermleseren sier «2 av 3» uten at noe av det
|
|
17
|
+
* må skrives på nytt.
|
|
18
|
+
*
|
|
19
|
+
* Er valgene handlinger framfor en innstilling, som «fet, kursiv,
|
|
20
|
+
* understreket», hører knapper med `aria-pressed` hjemme i stedet.
|
|
21
|
+
*
|
|
22
|
+
* ```ts
|
|
23
|
+
* <fieldset {...toggleGroup()}>
|
|
24
|
+
* <legend class="fs-sr-only">Visning</legend>
|
|
25
|
+
* <label class={toggleGroup.option}>
|
|
26
|
+
* <input type="radio" name="visning" value="liste" checked /> Liste
|
|
27
|
+
* </label>
|
|
28
|
+
* </fieldset>
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export const toggleGroup = Object.assign(
|
|
32
|
+
(): ToggleGroupAttributes => attributes({ class: TOGGLE_GROUP_CLASS }),
|
|
33
|
+
{
|
|
34
|
+
/** Klassen på ledeteksten rundt hvert alternativ. */
|
|
35
|
+
option: TOGGLE_GROUP_OPTION_CLASS,
|
|
36
|
+
},
|
|
37
|
+
)
|