@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,53 @@
|
|
|
1
|
+
import { attributes, createGuard } from "../shared.js"
|
|
2
|
+
|
|
3
|
+
export const PARAGRAPH_CLASS = "fs-paragraph" as const
|
|
4
|
+
|
|
5
|
+
export const paragraphSizes = ["small", "medium", "large"] as const
|
|
6
|
+
export const paragraphVariants = ["default", "lead"] as const
|
|
7
|
+
|
|
8
|
+
export type ParagraphSize = (typeof paragraphSizes)[number]
|
|
9
|
+
export type ParagraphVariant = (typeof paragraphVariants)[number]
|
|
10
|
+
export type NonDefaultParagraphSize = Exclude<ParagraphSize, "medium">
|
|
11
|
+
export type NonDefaultParagraphVariant = Exclude<ParagraphVariant, "default">
|
|
12
|
+
|
|
13
|
+
export type ParagraphOptions = {
|
|
14
|
+
/** Tekststørrelse. Standard: `medium`. */
|
|
15
|
+
size?: ParagraphSize
|
|
16
|
+
/** `lead` er ingressen rett under overskriften. Standard: `default`. */
|
|
17
|
+
variant?: ParagraphVariant
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type ParagraphAttributes = {
|
|
21
|
+
class: typeof PARAGRAPH_CLASS
|
|
22
|
+
"data-size"?: NonDefaultParagraphSize
|
|
23
|
+
"data-variant"?: NonDefaultParagraphVariant
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Attributtene for et avsnitt.
|
|
28
|
+
*
|
|
29
|
+
* Bredden er begrenset til 70 tegn. Lengre linjer gjør det vanskelig å finne
|
|
30
|
+
* tilbake til starten av neste, og det er den vanligste grunnen til at en
|
|
31
|
+
* tekst er tung å lese selv når skriften er stor nok.
|
|
32
|
+
*
|
|
33
|
+
* ```ts
|
|
34
|
+
* <p {...paragraph({ variant: "lead" })}>Kort om hva siden handler om.</p>
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export const paragraph = Object.assign(
|
|
38
|
+
({
|
|
39
|
+
size = "medium",
|
|
40
|
+
variant = "default",
|
|
41
|
+
}: ParagraphOptions = {}): ParagraphAttributes =>
|
|
42
|
+
attributes({
|
|
43
|
+
class: PARAGRAPH_CLASS,
|
|
44
|
+
"data-size": size === "medium" ? undefined : size,
|
|
45
|
+
"data-variant": variant === "default" ? undefined : variant,
|
|
46
|
+
}),
|
|
47
|
+
{
|
|
48
|
+
sizes: paragraphSizes,
|
|
49
|
+
isSize: createGuard(paragraphSizes),
|
|
50
|
+
variants: paragraphVariants,
|
|
51
|
+
isVariant: createGuard(paragraphVariants),
|
|
52
|
+
},
|
|
53
|
+
)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
@layer fristil {
|
|
2
|
+
.fs-radio {
|
|
3
|
+
box-sizing: border-box;
|
|
4
|
+
appearance: none;
|
|
5
|
+
flex: none;
|
|
6
|
+
display: inline-block;
|
|
7
|
+
inline-size: var(--fs-radio-size, var(--size-5));
|
|
8
|
+
block-size: var(--fs-radio-size, var(--size-5));
|
|
9
|
+
margin: 0;
|
|
10
|
+
background-color: var(--semantic-page-background);
|
|
11
|
+
border: 1px solid var(--semantic-field-border);
|
|
12
|
+
border-radius: 50%;
|
|
13
|
+
cursor: pointer;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.fs-radio:hover:not(:disabled) {
|
|
17
|
+
border-color: var(--semantic-field-border-hover);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.fs-radio:focus-visible {
|
|
21
|
+
outline: 2px solid var(--semantic-interactive-main);
|
|
22
|
+
outline-offset: 2px;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/* Prikken er en gradient og ikke et bilde, så den tar farge fra tokenet
|
|
26
|
+
direkte og trenger ingen egen variant for mørkt tema. */
|
|
27
|
+
.fs-radio:checked {
|
|
28
|
+
background-color: var(--semantic-interactive-main);
|
|
29
|
+
border-color: var(--semantic-interactive-main);
|
|
30
|
+
background-image: radial-gradient(
|
|
31
|
+
circle at center,
|
|
32
|
+
var(--semantic-interactive-contrast) 0 32%,
|
|
33
|
+
transparent 34%
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.fs-radio:disabled {
|
|
38
|
+
background-color: var(--semantic-disabled-background);
|
|
39
|
+
border-color: var(--semantic-disabled-background);
|
|
40
|
+
cursor: not-allowed;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.fs-radio:disabled:checked {
|
|
44
|
+
background-color: var(--semantic-disabled-foreground);
|
|
45
|
+
border-color: var(--semantic-disabled-foreground);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.fs-radio[data-state="invalid"] {
|
|
49
|
+
border-color: var(--semantic-danger-foreground);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.fs-radio[data-state="invalid"]:focus-visible {
|
|
53
|
+
outline-color: var(--semantic-danger-foreground);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.fs-radio[data-state="success"] {
|
|
57
|
+
border-color: var(--semantic-success-foreground);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.fs-radio-row {
|
|
61
|
+
display: flex;
|
|
62
|
+
align-items: flex-start;
|
|
63
|
+
gap: var(--size-2);
|
|
64
|
+
line-height: 1.5;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* Høykontrast: prikken er en gradient, og gradienter fjernes i modusen.
|
|
68
|
+
Nettleserens egen radioknapp tegnes riktig i alle temaer. */
|
|
69
|
+
@media (forced-colors: active) {
|
|
70
|
+
.fs-radio,
|
|
71
|
+
.fs-radio:checked {
|
|
72
|
+
appearance: auto;
|
|
73
|
+
background-image: none;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import {
|
|
2
|
+
attributes,
|
|
3
|
+
type FieldState,
|
|
4
|
+
fieldStates,
|
|
5
|
+
isFieldState,
|
|
6
|
+
type NonDefaultFieldState,
|
|
7
|
+
} from "../shared.js"
|
|
8
|
+
|
|
9
|
+
export const RADIO_CLASS = "fs-radio" as const
|
|
10
|
+
/** Raden som holder knappen og teksten ved siden av hverandre. */
|
|
11
|
+
export const RADIO_ROW_CLASS = "fs-radio-row" as const
|
|
12
|
+
|
|
13
|
+
export type RadioOptions = {
|
|
14
|
+
/** Valideringstilstand. Standard: `default`. */
|
|
15
|
+
state?: FieldState
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type RadioAttributes = {
|
|
19
|
+
class: typeof RADIO_CLASS
|
|
20
|
+
type: "radio"
|
|
21
|
+
"data-state"?: NonDefaultFieldState
|
|
22
|
+
"aria-invalid"?: "true"
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Attributtene for en radioknapp.
|
|
27
|
+
*
|
|
28
|
+
* Alle knappene i en gruppe må ha samme `name`. Det er den koblingen som gjør
|
|
29
|
+
* at bare én kan være valgt, og at piltastene flytter mellom dem. `name`
|
|
30
|
+
* setter du selv, siden bare du vet hva gruppen heter.
|
|
31
|
+
*
|
|
32
|
+
* ```ts
|
|
33
|
+
* <input {...radio()} name="leveringsmate" value="post" />
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export const radio = Object.assign(
|
|
37
|
+
({ state = "default" }: RadioOptions = {}): RadioAttributes =>
|
|
38
|
+
attributes({
|
|
39
|
+
class: RADIO_CLASS,
|
|
40
|
+
type: "radio" as const,
|
|
41
|
+
"data-state": state === "default" ? undefined : state,
|
|
42
|
+
"aria-invalid": state === "invalid" ? ("true" as const) : undefined,
|
|
43
|
+
}),
|
|
44
|
+
{
|
|
45
|
+
/** Klassen på raden rundt knappen og teksten. */
|
|
46
|
+
row: RADIO_ROW_CLASS,
|
|
47
|
+
states: fieldStates,
|
|
48
|
+
isState: isFieldState,
|
|
49
|
+
},
|
|
50
|
+
)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
@import "../input/input.css";
|
|
2
|
+
|
|
3
|
+
@layer fristil {
|
|
4
|
+
/*
|
|
5
|
+
* Begge klassene i selektoren, ikke bare .fs-search.
|
|
6
|
+
*
|
|
7
|
+
* .fs-input setter padding med kortformen, og .fs-search bare
|
|
8
|
+
* padding-inline-start. Med lik spesifisitet avgjør rekkefølgen stilarkene
|
|
9
|
+
* lastes i, og importerer konsumenten input.css sist, forsvinner plassen
|
|
10
|
+
* til ikonet. Med begge klassene vinner regelen uansett rekkefølge.
|
|
11
|
+
*/
|
|
12
|
+
.fs-input.fs-search {
|
|
13
|
+
padding-inline-start: var(--fs-search-icon-space, var(--size-10));
|
|
14
|
+
background-image: var(--semantic-icon-search);
|
|
15
|
+
background-repeat: no-repeat;
|
|
16
|
+
background-position: left var(--size-3) center;
|
|
17
|
+
background-size: var(--fs-search-icon-size, var(--size-5));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.fs-search:dir(rtl) {
|
|
21
|
+
background-position: right var(--size-3) center;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* Nettleserens eget kryss i et type="search"-felt. Safari og Chrome tegner
|
|
25
|
+
det ulikt, og Firefox ikke i det hele tatt. */
|
|
26
|
+
.fs-search::-webkit-search-cancel-button {
|
|
27
|
+
cursor: pointer;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.fs-search-row {
|
|
31
|
+
display: flex;
|
|
32
|
+
align-items: stretch;
|
|
33
|
+
gap: var(--size-2);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.fs-search-row .fs-search {
|
|
37
|
+
flex: 1 1 auto;
|
|
38
|
+
min-inline-size: 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* Høykontrast: forstørrelsesglasset er malt inn i bildet med fast farge, og
|
|
42
|
+
kan bli usynlig. Da er det bedre å ta det bort og gi plassen tilbake. */
|
|
43
|
+
@media (forced-colors: active) {
|
|
44
|
+
.fs-input.fs-search {
|
|
45
|
+
background-image: none;
|
|
46
|
+
padding-inline-start: var(--size-3);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { INPUT_CLASS, input } from "../input/input.js"
|
|
2
|
+
import {
|
|
3
|
+
attributes,
|
|
4
|
+
type FieldState,
|
|
5
|
+
fieldStates,
|
|
6
|
+
isFieldState,
|
|
7
|
+
} from "../shared.js"
|
|
8
|
+
|
|
9
|
+
export const SEARCH_CLASS = "fs-search" as const
|
|
10
|
+
/** Raden med søkefeltet og knappen ved siden av. */
|
|
11
|
+
export const SEARCH_ROW_CLASS = "fs-search-row" as const
|
|
12
|
+
|
|
13
|
+
export type SearchOptions = {
|
|
14
|
+
/** Valideringstilstand. Standard: `default`. */
|
|
15
|
+
state?: FieldState
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type SearchAttributes = {
|
|
19
|
+
class: `${typeof INPUT_CLASS} ${typeof SEARCH_CLASS}`
|
|
20
|
+
type: "search"
|
|
21
|
+
"data-state"?: "invalid" | "success"
|
|
22
|
+
"aria-invalid"?: "true"
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Attributtene for et søkefelt.
|
|
27
|
+
*
|
|
28
|
+
* Feltet er en `fs-input` med plass til forstørrelsesglasset, så begge
|
|
29
|
+
* klassene settes. `type="search"` gir nettleserens eget kryss for å tømme
|
|
30
|
+
* feltet, og på mobil et tastatur med «Søk» i stedet for «Enter».
|
|
31
|
+
*
|
|
32
|
+
* ```ts
|
|
33
|
+
* <input {...search()} name="q" />
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export const search = Object.assign(
|
|
37
|
+
({ state = "default" }: SearchOptions = {}): SearchAttributes => {
|
|
38
|
+
const base = input({ type: "search", state })
|
|
39
|
+
return attributes({
|
|
40
|
+
...base,
|
|
41
|
+
class: `${INPUT_CLASS} ${SEARCH_CLASS}` as const,
|
|
42
|
+
type: "search" as const,
|
|
43
|
+
"data-variant": undefined,
|
|
44
|
+
}) as SearchAttributes
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
/** Klassen på raden med feltet og knappen. */
|
|
48
|
+
row: SEARCH_ROW_CLASS,
|
|
49
|
+
states: fieldStates,
|
|
50
|
+
isState: isFieldState,
|
|
51
|
+
},
|
|
52
|
+
)
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
@layer fristil {
|
|
2
|
+
.fs-select {
|
|
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
|
+
padding: var(--size-2) calc(var(--size-4) + var(--size-2)) var(--size-2)
|
|
11
|
+
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-color: var(--semantic-page-background);
|
|
17
|
+
border: 1px solid var(--semantic-field-border);
|
|
18
|
+
border-radius: var(--fs-select-radius, var(--size-1));
|
|
19
|
+
appearance: none;
|
|
20
|
+
background-image:
|
|
21
|
+
linear-gradient(
|
|
22
|
+
45deg,
|
|
23
|
+
transparent 50%,
|
|
24
|
+
var(--semantic-muted-foreground) 50%
|
|
25
|
+
),
|
|
26
|
+
linear-gradient(
|
|
27
|
+
135deg,
|
|
28
|
+
var(--semantic-muted-foreground) 50%,
|
|
29
|
+
transparent 50%
|
|
30
|
+
);
|
|
31
|
+
background-position:
|
|
32
|
+
calc(100% - 1rem) calc(50% - 0.1rem),
|
|
33
|
+
calc(100% - 0.75rem) calc(50% - 0.1rem);
|
|
34
|
+
background-size:
|
|
35
|
+
0.35rem 0.35rem,
|
|
36
|
+
0.35rem 0.35rem;
|
|
37
|
+
background-repeat: no-repeat;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.fs-select:hover {
|
|
41
|
+
border-color: var(--semantic-field-border-hover);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.fs-select:focus-visible {
|
|
45
|
+
outline: 2px solid var(--semantic-interactive-main);
|
|
46
|
+
outline-offset: 2px;
|
|
47
|
+
border-color: var(--semantic-interactive-main);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.fs-select:disabled,
|
|
51
|
+
.fs-select[aria-disabled="true"] {
|
|
52
|
+
background-color: var(--semantic-disabled-background);
|
|
53
|
+
color: var(--semantic-disabled-foreground);
|
|
54
|
+
border-color: var(--semantic-disabled-background);
|
|
55
|
+
/* Ingen pointer-events: none her. Nettleseren blokkerer allerede et
|
|
56
|
+
deaktivert felt, og pointer-events ville bare hindret markøren i å
|
|
57
|
+
vises. Elementet blir da ikke truffet av musa i det hele tatt. */
|
|
58
|
+
cursor: not-allowed;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* aria-disabled er bare en opplysning til skjermlesere; elementet virker
|
|
62
|
+
fortsatt. Her stopper pointer-events musa, på bekostning av markøren.
|
|
63
|
+
Skal feltet faktisk være av, bruk `disabled` eller `readonly`. */
|
|
64
|
+
.fs-select[aria-disabled="true"] {
|
|
65
|
+
pointer-events: none;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.fs-select[data-state="invalid"] {
|
|
69
|
+
border-color: var(--semantic-danger-foreground);
|
|
70
|
+
background-color: var(--semantic-danger-background);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.fs-select[data-state="invalid"]:focus-visible {
|
|
74
|
+
outline-color: var(--semantic-danger-foreground);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.fs-select[data-state="success"] {
|
|
78
|
+
border-color: var(--semantic-success-foreground);
|
|
79
|
+
background-color: var(--semantic-success-background);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.fs-select[data-state="success"]:focus-visible {
|
|
83
|
+
outline-color: var(--semantic-success-foreground);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/* Høykontrast: pilen er to gradienter, og de fjernes i modusen. Da er
|
|
87
|
+
nettleserens egen pil bedre enn ingen. */
|
|
88
|
+
@media (forced-colors: active) {
|
|
89
|
+
.fs-select {
|
|
90
|
+
appearance: auto;
|
|
91
|
+
background-image: none;
|
|
92
|
+
padding-inline-end: var(--size-3);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -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 SELECT_CLASS = "fs-select" as const
|
|
10
|
+
|
|
11
|
+
export type SelectOptions = {
|
|
12
|
+
/** Valideringstilstand. Standard: `default`. */
|
|
13
|
+
state?: FieldState
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type SelectAttributes = {
|
|
17
|
+
class: typeof SELECT_CLASS
|
|
18
|
+
"data-state"?: NonDefaultFieldState
|
|
19
|
+
"aria-invalid"?: "true"
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Attributtene for en nedtrekksliste.
|
|
24
|
+
*
|
|
25
|
+
* ```ts
|
|
26
|
+
* <select {...select({ state: "invalid" })} />
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export const select = Object.assign(
|
|
30
|
+
({ state = "default" }: SelectOptions = {}): SelectAttributes =>
|
|
31
|
+
attributes({
|
|
32
|
+
class: SELECT_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,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Felles byggeklosser for komponent-API-et.
|
|
3
|
+
*
|
|
4
|
+
* Hver komponent eksporterer én funksjon som tar et valgobjekt og returnerer
|
|
5
|
+
* attributtene du sprer inn i elementet. Formen er lik for alle, slik at du
|
|
6
|
+
* bare trenger å lære den én gang.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** Fjerner attributter uten verdi, så `{...spredning}` ikke setter tomme felt. */
|
|
10
|
+
export function attributes<T extends Record<string, unknown>>(values: T): T {
|
|
11
|
+
const result = {} as T
|
|
12
|
+
for (const [name, value] of Object.entries(values)) {
|
|
13
|
+
if (value !== undefined) {
|
|
14
|
+
;(result as Record<string, unknown>)[name] = value
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return result
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Lager en typevakt for en liste av lovlige strenger. */
|
|
21
|
+
export function createGuard<T extends string>(allowed: readonly T[]) {
|
|
22
|
+
return (value: string): value is T =>
|
|
23
|
+
(allowed as readonly string[]).includes(value)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Valideringstilstand for skjemakontroller.
|
|
28
|
+
*
|
|
29
|
+
* Input, textarea og select hadde hver sin identiske type. Nå er det én, så
|
|
30
|
+
* en verdi kan sendes mellom dem uten konvertering.
|
|
31
|
+
*/
|
|
32
|
+
export const fieldStates = ["default", "invalid", "success"] as const
|
|
33
|
+
export type FieldState = (typeof fieldStates)[number]
|
|
34
|
+
export type NonDefaultFieldState = Exclude<FieldState, "default">
|
|
35
|
+
export const isFieldState = createGuard(fieldStates)
|
|
36
|
+
|
|
37
|
+
/** Hvordan et påkrevd felt markeres i ledeteksten. */
|
|
38
|
+
export const requiredMarkers = ["symbol", "text"] as const
|
|
39
|
+
export type RequiredMarker = (typeof requiredMarkers)[number]
|
|
40
|
+
export const isRequiredMarker = createGuard(requiredMarkers)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
@layer fristil {
|
|
2
|
+
.fs-skeleton {
|
|
3
|
+
box-sizing: border-box;
|
|
4
|
+
display: block;
|
|
5
|
+
inline-size: 100%;
|
|
6
|
+
block-size: var(--fs-skeleton-height, var(--size-4));
|
|
7
|
+
border-radius: var(--fs-skeleton-radius, var(--size-1));
|
|
8
|
+
background: var(--semantic-neutral-background);
|
|
9
|
+
/* Skimmeret er en gradient som beveger seg over flaten, ikke en
|
|
10
|
+
gjennomsiktighet som pulserer. Da blir kontrasten mot sideflaten den
|
|
11
|
+
samme hele veien. */
|
|
12
|
+
background-image: linear-gradient(
|
|
13
|
+
90deg,
|
|
14
|
+
transparent 0%,
|
|
15
|
+
var(--semantic-page-background) 50%,
|
|
16
|
+
transparent 100%
|
|
17
|
+
);
|
|
18
|
+
background-size: 200% 100%;
|
|
19
|
+
background-repeat: no-repeat;
|
|
20
|
+
animation: fs-skeleton-shimmer 1600ms ease-in-out infinite;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.fs-skeleton[data-variant="text"] {
|
|
24
|
+
block-size: var(--font-size-m);
|
|
25
|
+
border-radius: var(--size-0-5);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.fs-skeleton[data-variant="circle"] {
|
|
29
|
+
inline-size: var(--fs-skeleton-height, var(--size-10));
|
|
30
|
+
block-size: var(--fs-skeleton-height, var(--size-10));
|
|
31
|
+
border-radius: 50%;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@keyframes fs-skeleton-shimmer {
|
|
35
|
+
from {
|
|
36
|
+
background-position: 150% 0;
|
|
37
|
+
}
|
|
38
|
+
to {
|
|
39
|
+
background-position: -50% 0;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@media (prefers-reduced-motion: reduce) {
|
|
44
|
+
.fs-skeleton {
|
|
45
|
+
animation: none;
|
|
46
|
+
background-image: none;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { attributes, createGuard } from "../shared.js"
|
|
2
|
+
|
|
3
|
+
export const SKELETON_CLASS = "fs-skeleton" as const
|
|
4
|
+
|
|
5
|
+
export const skeletonVariants = ["block", "text", "circle"] as const
|
|
6
|
+
|
|
7
|
+
export type SkeletonVariant = (typeof skeletonVariants)[number]
|
|
8
|
+
export type NonDefaultSkeletonVariant = Exclude<SkeletonVariant, "block">
|
|
9
|
+
|
|
10
|
+
export type SkeletonOptions = {
|
|
11
|
+
/** Form. Standard: `block`. */
|
|
12
|
+
variant?: SkeletonVariant
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type SkeletonAttributes = {
|
|
16
|
+
class: typeof SKELETON_CLASS
|
|
17
|
+
"data-variant"?: NonDefaultSkeletonVariant
|
|
18
|
+
"aria-hidden": "true"
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Attributtene for en plassholder mens innholdet lastes.
|
|
23
|
+
*
|
|
24
|
+
* Formene er alltid skjult for skjermlesere. De sier ingenting om hva som
|
|
25
|
+
* kommer, og en rekke tomme bokser som leses opp er verre enn stillhet. Si
|
|
26
|
+
* fra om ventingen ett sted i stedet, med `role="status"`.
|
|
27
|
+
*
|
|
28
|
+
* ```ts
|
|
29
|
+
* <div {...skeleton({ variant: "text" })} />
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export const skeleton = Object.assign(
|
|
33
|
+
({ variant = "block" }: SkeletonOptions = {}): SkeletonAttributes =>
|
|
34
|
+
attributes({
|
|
35
|
+
class: SKELETON_CLASS,
|
|
36
|
+
"data-variant": variant === "block" ? undefined : variant,
|
|
37
|
+
"aria-hidden": "true" as const,
|
|
38
|
+
}),
|
|
39
|
+
{
|
|
40
|
+
variants: skeletonVariants,
|
|
41
|
+
isVariant: createGuard(skeletonVariants),
|
|
42
|
+
},
|
|
43
|
+
)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
@layer fristil {
|
|
2
|
+
/* Lenken ligger utenfor skjermen til den får fokus. Den kan ikke skjules
|
|
3
|
+
med display eller visibility, for da kan tastaturet heller ikke nå den,
|
|
4
|
+
og hele poenget forsvinner. */
|
|
5
|
+
.fs-skip-link {
|
|
6
|
+
position: absolute;
|
|
7
|
+
inset-inline-start: var(--size-2);
|
|
8
|
+
inset-block-start: var(--size-2);
|
|
9
|
+
z-index: 100;
|
|
10
|
+
transform: translateY(calc(-100% - var(--size-4)));
|
|
11
|
+
display: inline-block;
|
|
12
|
+
padding: var(--fs-skip-link-padding, var(--size-2) var(--size-4));
|
|
13
|
+
font-family: inherit;
|
|
14
|
+
font-size: var(--font-size-m);
|
|
15
|
+
line-height: 1.5;
|
|
16
|
+
color: var(--semantic-interactive-contrast);
|
|
17
|
+
background: var(--semantic-interactive-main);
|
|
18
|
+
border-radius: var(--fs-skip-link-radius, var(--size-1));
|
|
19
|
+
text-decoration: underline;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/* `:focus` og ikke `:focus-visible`. Nettleseren regner ikke fokus satt
|
|
23
|
+
fra kode som «synlig fokus», og lenken ville da blitt stående skjult for
|
|
24
|
+
den som hopper dit fra et skript. */
|
|
25
|
+
.fs-skip-link:focus {
|
|
26
|
+
transform: translateY(0);
|
|
27
|
+
outline: 2px solid var(--semantic-page-foreground);
|
|
28
|
+
outline-offset: 2px;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
32
|
+
.fs-skip-link {
|
|
33
|
+
transition: transform 120ms ease;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { attributes } from "../shared.js"
|
|
2
|
+
|
|
3
|
+
export const SKIP_LINK_CLASS = "fs-skip-link" as const
|
|
4
|
+
|
|
5
|
+
export type SkipLinkAttributes = {
|
|
6
|
+
class: typeof SKIP_LINK_CLASS
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Attributtene for en hopp-til-innhold-lenke.
|
|
11
|
+
*
|
|
12
|
+
* Lenken skal være det første elementet i `<body>`, og peke på id-en til
|
|
13
|
+
* hovedinnholdet. Den ligger utenfor skjermen til den får fokus, og kan
|
|
14
|
+
* derfor ikke skjules med `display: none`: da kan tastaturet heller ikke nå
|
|
15
|
+
* den, og hele poenget forsvinner.
|
|
16
|
+
*
|
|
17
|
+
* ```ts
|
|
18
|
+
* <a {...skipLink()} href="#hovedinnhold">Hopp til hovedinnhold</a>
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export const skipLink = (): SkipLinkAttributes =>
|
|
22
|
+
attributes({ class: SKIP_LINK_CLASS })
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
@layer fristil {
|
|
2
|
+
.fs-spinner {
|
|
3
|
+
box-sizing: border-box;
|
|
4
|
+
display: inline-block;
|
|
5
|
+
inline-size: var(--fs-spinner-size, var(--size-6));
|
|
6
|
+
block-size: var(--fs-spinner-size, var(--size-6));
|
|
7
|
+
border: var(--fs-spinner-thickness, 2px) solid
|
|
8
|
+
var(--semantic-neutral-background);
|
|
9
|
+
border-block-start-color: var(--semantic-interactive-main);
|
|
10
|
+
border-radius: 50%;
|
|
11
|
+
animation: fs-spinner-rotate 800ms linear infinite;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.fs-spinner[data-size="small"] {
|
|
15
|
+
inline-size: var(--size-4);
|
|
16
|
+
block-size: var(--size-4);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.fs-spinner[data-size="large"] {
|
|
20
|
+
inline-size: var(--size-10);
|
|
21
|
+
block-size: var(--size-10);
|
|
22
|
+
border-width: 3px;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@keyframes fs-spinner-rotate {
|
|
26
|
+
to {
|
|
27
|
+
transform: rotate(360deg);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* Brukeren har bedt om mindre bevegelse. Da snurrer den saktere framfor å
|
|
32
|
+
stå stille: en indikator som ikke beveger seg sier ikke lenger at noe
|
|
33
|
+
pågår. */
|
|
34
|
+
@media (prefers-reduced-motion: reduce) {
|
|
35
|
+
.fs-spinner {
|
|
36
|
+
animation-duration: 2400ms;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* Høykontrast: alle fire kantene tvinges til samme farge, og ringen blir en
|
|
41
|
+
jevn sirkel. Den snurrer fortsatt, men `Highlight` gir tilbake punktet
|
|
42
|
+
som viser at det skjer noe. */
|
|
43
|
+
@media (forced-colors: active) {
|
|
44
|
+
.fs-spinner {
|
|
45
|
+
border-block-start-color: Highlight;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|