@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.
Files changed (314) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/LICENSE +21 -0
  3. package/README.md +101 -0
  4. package/dist/cli.d.ts +31 -0
  5. package/dist/cli.d.ts.map +1 -0
  6. package/dist/cli.js +261 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/components/css/accordion/accordion.d.ts +37 -0
  9. package/dist/components/css/accordion/accordion.d.ts.map +1 -0
  10. package/dist/components/css/accordion/accordion.js +31 -0
  11. package/dist/components/css/accordion/accordion.js.map +1 -0
  12. package/dist/components/css/alert/alert.d.ts +35 -0
  13. package/dist/components/css/alert/alert.d.ts.map +1 -0
  14. package/dist/components/css/alert/alert.js +29 -0
  15. package/dist/components/css/alert/alert.js.map +1 -0
  16. package/dist/components/css/avatar/avatar.d.ts +40 -0
  17. package/dist/components/css/avatar/avatar.d.ts.map +1 -0
  18. package/dist/components/css/avatar/avatar.js +30 -0
  19. package/dist/components/css/avatar/avatar.js.map +1 -0
  20. package/dist/components/css/badge/badge.d.ts +24 -0
  21. package/dist/components/css/badge/badge.d.ts.map +1 -0
  22. package/dist/components/css/badge/badge.js +24 -0
  23. package/dist/components/css/badge/badge.js.map +1 -0
  24. package/dist/components/css/breadcrumbs/breadcrumbs.d.ts +25 -0
  25. package/dist/components/css/breadcrumbs/breadcrumbs.d.ts.map +1 -0
  26. package/dist/components/css/breadcrumbs/breadcrumbs.js +23 -0
  27. package/dist/components/css/breadcrumbs/breadcrumbs.js.map +1 -0
  28. package/dist/components/css/button/button.d.ts +26 -0
  29. package/dist/components/css/button/button.d.ts.map +1 -0
  30. package/dist/components/css/button/button.js +25 -0
  31. package/dist/components/css/button/button.js.map +1 -0
  32. package/dist/components/css/card/card.d.ts +36 -0
  33. package/dist/components/css/card/card.d.ts.map +1 -0
  34. package/dist/components/css/card/card.js +28 -0
  35. package/dist/components/css/card/card.js.map +1 -0
  36. package/dist/components/css/checkbox/checkbox.d.ts +32 -0
  37. package/dist/components/css/checkbox/checkbox.d.ts.map +1 -0
  38. package/dist/components/css/checkbox/checkbox.js +27 -0
  39. package/dist/components/css/checkbox/checkbox.js.map +1 -0
  40. package/dist/components/css/dialog/dialog.d.ts +30 -0
  41. package/dist/components/css/dialog/dialog.d.ts.map +1 -0
  42. package/dist/components/css/dialog/dialog.js +28 -0
  43. package/dist/components/css/dialog/dialog.js.map +1 -0
  44. package/dist/components/css/divider/divider.d.ts +28 -0
  45. package/dist/components/css/divider/divider.d.ts.map +1 -0
  46. package/dist/components/css/divider/divider.js +22 -0
  47. package/dist/components/css/divider/divider.js.map +1 -0
  48. package/dist/components/css/error-text/error-text.d.ts +24 -0
  49. package/dist/components/css/error-text/error-text.d.ts.map +1 -0
  50. package/dist/components/css/error-text/error-text.js +18 -0
  51. package/dist/components/css/error-text/error-text.js.map +1 -0
  52. package/dist/components/css/fieldset/fieldset.d.ts +55 -0
  53. package/dist/components/css/fieldset/fieldset.d.ts.map +1 -0
  54. package/dist/components/css/fieldset/fieldset.js +39 -0
  55. package/dist/components/css/fieldset/fieldset.js.map +1 -0
  56. package/dist/components/css/file-upload/file-upload.d.ts +41 -0
  57. package/dist/components/css/file-upload/file-upload.d.ts.map +1 -0
  58. package/dist/components/css/file-upload/file-upload.js +32 -0
  59. package/dist/components/css/file-upload/file-upload.js.map +1 -0
  60. package/dist/components/css/heading/heading.d.ts +29 -0
  61. package/dist/components/css/heading/heading.d.ts.map +1 -0
  62. package/dist/components/css/heading/heading.js +23 -0
  63. package/dist/components/css/heading/heading.js.map +1 -0
  64. package/dist/components/css/help-text/help-text.d.ts +24 -0
  65. package/dist/components/css/help-text/help-text.d.ts.map +1 -0
  66. package/dist/components/css/help-text/help-text.js +23 -0
  67. package/dist/components/css/help-text/help-text.js.map +1 -0
  68. package/dist/components/css/input/input.d.ts +39 -0
  69. package/dist/components/css/input/input.d.ts.map +1 -0
  70. package/dist/components/css/input/input.js +44 -0
  71. package/dist/components/css/input/input.js.map +1 -0
  72. package/dist/components/css/label/label.d.ts +33 -0
  73. package/dist/components/css/label/label.d.ts.map +1 -0
  74. package/dist/components/css/label/label.js +23 -0
  75. package/dist/components/css/label/label.js.map +1 -0
  76. package/dist/components/css/link/link.d.ts +21 -0
  77. package/dist/components/css/link/link.d.ts.map +1 -0
  78. package/dist/components/css/link/link.js +14 -0
  79. package/dist/components/css/link/link.js.map +1 -0
  80. package/dist/components/css/list/list.d.ts +29 -0
  81. package/dist/components/css/list/list.d.ts.map +1 -0
  82. package/dist/components/css/list/list.js +23 -0
  83. package/dist/components/css/list/list.js.map +1 -0
  84. package/dist/components/css/pagination/pagination.d.ts +27 -0
  85. package/dist/components/css/pagination/pagination.d.ts.map +1 -0
  86. package/dist/components/css/pagination/pagination.js +25 -0
  87. package/dist/components/css/pagination/pagination.js.map +1 -0
  88. package/dist/components/css/paragraph/paragraph.d.ts +36 -0
  89. package/dist/components/css/paragraph/paragraph.d.ts.map +1 -0
  90. package/dist/components/css/paragraph/paragraph.js +26 -0
  91. package/dist/components/css/paragraph/paragraph.js.map +1 -0
  92. package/dist/components/css/radio/radio.d.ts +32 -0
  93. package/dist/components/css/radio/radio.d.ts.map +1 -0
  94. package/dist/components/css/radio/radio.js +27 -0
  95. package/dist/components/css/radio/radio.js.map +1 -0
  96. package/dist/components/css/search/search.d.ts +33 -0
  97. package/dist/components/css/search/search.d.ts.map +1 -0
  98. package/dist/components/css/search/search.js +31 -0
  99. package/dist/components/css/search/search.js.map +1 -0
  100. package/dist/components/css/select/select.d.ts +23 -0
  101. package/dist/components/css/select/select.d.ts.map +1 -0
  102. package/dist/components/css/select/select.js +18 -0
  103. package/dist/components/css/select/select.js.map +1 -0
  104. package/dist/components/css/shared.d.ts +26 -0
  105. package/dist/components/css/shared.d.ts.map +1 -0
  106. package/dist/components/css/shared.js +34 -0
  107. package/dist/components/css/shared.js.map +1 -0
  108. package/dist/components/css/skeleton/skeleton.d.ts +29 -0
  109. package/dist/components/css/skeleton/skeleton.d.ts.map +1 -0
  110. package/dist/components/css/skeleton/skeleton.js +23 -0
  111. package/dist/components/css/skeleton/skeleton.js.map +1 -0
  112. package/dist/components/css/skip-link/skip-link.d.ts +18 -0
  113. package/dist/components/css/skip-link/skip-link.d.ts.map +1 -0
  114. package/dist/components/css/skip-link/skip-link.js +16 -0
  115. package/dist/components/css/skip-link/skip-link.js.map +1 -0
  116. package/dist/components/css/spinner/spinner.d.ts +33 -0
  117. package/dist/components/css/spinner/spinner.d.ts.map +1 -0
  118. package/dist/components/css/spinner/spinner.js +25 -0
  119. package/dist/components/css/spinner/spinner.js.map +1 -0
  120. package/dist/components/css/sr-only/sr-only.d.ts +17 -0
  121. package/dist/components/css/sr-only/sr-only.d.ts.map +1 -0
  122. package/dist/components/css/sr-only/sr-only.js +14 -0
  123. package/dist/components/css/sr-only/sr-only.js.map +1 -0
  124. package/dist/components/css/switch/switch.d.ts +33 -0
  125. package/dist/components/css/switch/switch.d.ts.map +1 -0
  126. package/dist/components/css/switch/switch.js +29 -0
  127. package/dist/components/css/switch/switch.js.map +1 -0
  128. package/dist/components/css/table/table.d.ts +36 -0
  129. package/dist/components/css/table/table.d.ts.map +1 -0
  130. package/dist/components/css/table/table.js +28 -0
  131. package/dist/components/css/table/table.js.map +1 -0
  132. package/dist/components/css/tag/tag.d.ts +35 -0
  133. package/dist/components/css/tag/tag.d.ts.map +1 -0
  134. package/dist/components/css/tag/tag.js +27 -0
  135. package/dist/components/css/tag/tag.js.map +1 -0
  136. package/dist/components/css/textarea/textarea.d.ts +23 -0
  137. package/dist/components/css/textarea/textarea.d.ts.map +1 -0
  138. package/dist/components/css/textarea/textarea.js +18 -0
  139. package/dist/components/css/textarea/textarea.js.map +1 -0
  140. package/dist/components/css/toggle-group/toggle-group.d.ts +31 -0
  141. package/dist/components/css/toggle-group/toggle-group.d.ts.map +1 -0
  142. package/dist/components/css/toggle-group/toggle-group.js +29 -0
  143. package/dist/components/css/toggle-group/toggle-group.js.map +1 -0
  144. package/dist/components/css/tooltip/tooltip.d.ts +30 -0
  145. package/dist/components/css/tooltip/tooltip.d.ts.map +1 -0
  146. package/dist/components/css/tooltip/tooltip.js +28 -0
  147. package/dist/components/css/tooltip/tooltip.js.map +1 -0
  148. package/dist/components/frittstaende/calendar/fs-calendar.d.ts +52 -0
  149. package/dist/components/frittstaende/calendar/fs-calendar.d.ts.map +1 -0
  150. package/dist/components/frittstaende/calendar/fs-calendar.js +462 -0
  151. package/dist/components/frittstaende/calendar/fs-calendar.js.map +1 -0
  152. package/dist/components/frittstaende/date-field/fs-date-field.d.ts +110 -0
  153. package/dist/components/frittstaende/date-field/fs-date-field.d.ts.map +1 -0
  154. package/dist/components/frittstaende/date-field/fs-date-field.js +264 -0
  155. package/dist/components/frittstaende/date-field/fs-date-field.js.map +1 -0
  156. package/dist/components/frittstaende/suggestion/fs-suggestion.d.ts +133 -0
  157. package/dist/components/frittstaende/suggestion/fs-suggestion.d.ts.map +1 -0
  158. package/dist/components/frittstaende/suggestion/fs-suggestion.js +280 -0
  159. package/dist/components/frittstaende/suggestion/fs-suggestion.js.map +1 -0
  160. package/dist/components/frittstaende/toast/fs-toast.d.ts +67 -0
  161. package/dist/components/frittstaende/toast/fs-toast.d.ts.map +1 -0
  162. package/dist/components/frittstaende/toast/fs-toast.js +108 -0
  163. package/dist/components/frittstaende/toast/fs-toast.js.map +1 -0
  164. package/dist/components/ramme/error-summary/fs-error-summary.d.ts +70 -0
  165. package/dist/components/ramme/error-summary/fs-error-summary.d.ts.map +1 -0
  166. package/dist/components/ramme/error-summary/fs-error-summary.js +155 -0
  167. package/dist/components/ramme/error-summary/fs-error-summary.js.map +1 -0
  168. package/dist/components/ramme/field/field-core.d.ts +70 -0
  169. package/dist/components/ramme/field/field-core.d.ts.map +1 -0
  170. package/dist/components/ramme/field/field-core.js +60 -0
  171. package/dist/components/ramme/field/field-core.js.map +1 -0
  172. package/dist/components/ramme/field/fs-field.d.ts +49 -0
  173. package/dist/components/ramme/field/fs-field.d.ts.map +1 -0
  174. package/dist/components/ramme/field/fs-field.js +118 -0
  175. package/dist/components/ramme/field/fs-field.js.map +1 -0
  176. package/dist/components/ramme/popover/fs-popover.d.ts +68 -0
  177. package/dist/components/ramme/popover/fs-popover.d.ts.map +1 -0
  178. package/dist/components/ramme/popover/fs-popover.js +167 -0
  179. package/dist/components/ramme/popover/fs-popover.js.map +1 -0
  180. package/dist/components/ramme/tabs/fs-tabs.d.ts +61 -0
  181. package/dist/components/ramme/tabs/fs-tabs.d.ts.map +1 -0
  182. package/dist/components/ramme/tabs/fs-tabs.js +156 -0
  183. package/dist/components/ramme/tabs/fs-tabs.js.map +1 -0
  184. package/dist/dom.d.ts +33 -0
  185. package/dist/dom.d.ts.map +1 -0
  186. package/dist/dom.js +77 -0
  187. package/dist/dom.js.map +1 -0
  188. package/dist/fs.d.ts +199 -0
  189. package/dist/fs.d.ts.map +1 -0
  190. package/dist/fs.js +128 -0
  191. package/dist/fs.js.map +1 -0
  192. package/dist/index.d.ts +84 -0
  193. package/dist/index.d.ts.map +1 -0
  194. package/dist/index.js +48 -0
  195. package/dist/index.js.map +1 -0
  196. package/dist/jsx/react.d.ts +127 -0
  197. package/dist/jsx/react.d.ts.map +1 -0
  198. package/dist/jsx/react.js +2 -0
  199. package/dist/jsx/react.js.map +1 -0
  200. package/dist/react.d.ts +207 -0
  201. package/dist/react.d.ts.map +1 -0
  202. package/dist/react.js +109 -0
  203. package/dist/react.js.map +1 -0
  204. package/dist/tailwind/preset.d.ts +120 -0
  205. package/dist/tailwind/preset.d.ts.map +1 -0
  206. package/dist/tailwind/preset.js +107 -0
  207. package/dist/tailwind/preset.js.map +1 -0
  208. package/dist/takeover.d.ts +79 -0
  209. package/dist/takeover.d.ts.map +1 -0
  210. package/dist/takeover.js +119 -0
  211. package/dist/takeover.js.map +1 -0
  212. package/dist/tokens/color.d.ts +85 -0
  213. package/dist/tokens/color.d.ts.map +1 -0
  214. package/dist/tokens/color.js +212 -0
  215. package/dist/tokens/color.js.map +1 -0
  216. package/dist/tokens/theme.d.ts +52 -0
  217. package/dist/tokens/theme.d.ts.map +1 -0
  218. package/dist/tokens/theme.js +197 -0
  219. package/dist/tokens/theme.js.map +1 -0
  220. package/dist/tokens/tokens.d.ts +210 -0
  221. package/dist/tokens/tokens.d.ts.map +1 -0
  222. package/dist/tokens/tokens.js +238 -0
  223. package/dist/tokens/tokens.js.map +1 -0
  224. package/package.json +334 -0
  225. package/src/components/css/accordion/accordion.css +74 -0
  226. package/src/components/css/accordion/accordion.ts +50 -0
  227. package/src/components/css/alert/alert.css +50 -0
  228. package/src/components/css/alert/alert.ts +48 -0
  229. package/src/components/css/avatar/avatar.css +64 -0
  230. package/src/components/css/avatar/avatar.ts +57 -0
  231. package/src/components/css/badge/badge.css +42 -0
  232. package/src/components/css/badge/badge.ts +43 -0
  233. package/src/components/css/breadcrumbs/breadcrumbs.css +50 -0
  234. package/src/components/css/breadcrumbs/breadcrumbs.ts +29 -0
  235. package/src/components/css/button/button.css +101 -0
  236. package/src/components/css/button/button.ts +44 -0
  237. package/src/components/css/card/card.css +48 -0
  238. package/src/components/css/card/card.ts +50 -0
  239. package/src/components/css/checkbox/checkbox.css +96 -0
  240. package/src/components/css/checkbox/checkbox.ts +50 -0
  241. package/src/components/css/dialog/dialog.css +50 -0
  242. package/src/components/css/dialog/dialog.ts +36 -0
  243. package/src/components/css/divider/divider.css +18 -0
  244. package/src/components/css/divider/divider.ts +41 -0
  245. package/src/components/css/error-text/error-text.css +21 -0
  246. package/src/components/css/error-text/error-text.ts +37 -0
  247. package/src/components/css/fieldset/fieldset.css +57 -0
  248. package/src/components/css/fieldset/fieldset.ts +87 -0
  249. package/src/components/css/file-upload/file-upload.css +92 -0
  250. package/src/components/css/file-upload/file-upload.ts +65 -0
  251. package/src/components/css/heading/heading.css +33 -0
  252. package/src/components/css/heading/heading.ts +42 -0
  253. package/src/components/css/help-text/help-text.css +24 -0
  254. package/src/components/css/help-text/help-text.ts +42 -0
  255. package/src/components/css/input/input.css +131 -0
  256. package/src/components/css/input/input.ts +76 -0
  257. package/src/components/css/label/label.css +34 -0
  258. package/src/components/css/label/label.ts +51 -0
  259. package/src/components/css/link/link.css +36 -0
  260. package/src/components/css/link/link.ts +29 -0
  261. package/src/components/css/list/list.css +40 -0
  262. package/src/components/css/list/list.ts +42 -0
  263. package/src/components/css/pagination/pagination.css +77 -0
  264. package/src/components/css/pagination/pagination.ts +33 -0
  265. package/src/components/css/paragraph/paragraph.css +31 -0
  266. package/src/components/css/paragraph/paragraph.ts +53 -0
  267. package/src/components/css/radio/radio.css +76 -0
  268. package/src/components/css/radio/radio.ts +50 -0
  269. package/src/components/css/search/search.css +49 -0
  270. package/src/components/css/search/search.ts +52 -0
  271. package/src/components/css/select/select.css +95 -0
  272. package/src/components/css/select/select.ts +40 -0
  273. package/src/components/css/shared.ts +40 -0
  274. package/src/components/css/skeleton/skeleton.css +49 -0
  275. package/src/components/css/skeleton/skeleton.ts +43 -0
  276. package/src/components/css/skip-link/skip-link.css +36 -0
  277. package/src/components/css/skip-link/skip-link.ts +22 -0
  278. package/src/components/css/spinner/spinner.css +48 -0
  279. package/src/components/css/spinner/spinner.ts +48 -0
  280. package/src/components/css/sr-only/sr-only.css +16 -0
  281. package/src/components/css/sr-only/sr-only.ts +18 -0
  282. package/src/components/css/switch/switch.css +101 -0
  283. package/src/components/css/switch/switch.ts +46 -0
  284. package/src/components/css/table/table.css +63 -0
  285. package/src/components/css/table/table.ts +50 -0
  286. package/src/components/css/tag/tag.css +58 -0
  287. package/src/components/css/tag/tag.ts +49 -0
  288. package/src/components/css/textarea/textarea.css +71 -0
  289. package/src/components/css/textarea/textarea.ts +40 -0
  290. package/src/components/css/toggle-group/toggle-group.css +82 -0
  291. package/src/components/css/toggle-group/toggle-group.ts +37 -0
  292. package/src/components/css/tooltip/tooltip.css +47 -0
  293. package/src/components/css/tooltip/tooltip.ts +36 -0
  294. package/src/components/frittstaende/calendar/fs-calendar.ts +525 -0
  295. package/src/components/frittstaende/date-field/date-field.css +54 -0
  296. package/src/components/frittstaende/date-field/fs-date-field.ts +309 -0
  297. package/src/components/frittstaende/suggestion/fs-suggestion.ts +333 -0
  298. package/src/components/frittstaende/suggestion/suggestion.css +63 -0
  299. package/src/components/frittstaende/toast/fs-toast.ts +146 -0
  300. package/src/components/frittstaende/toast/toast.css +71 -0
  301. package/src/components/ramme/error-summary/error-summary.css +45 -0
  302. package/src/components/ramme/error-summary/fs-error-summary.ts +197 -0
  303. package/src/components/ramme/field/field-core.ts +132 -0
  304. package/src/components/ramme/field/field.css +6 -0
  305. package/src/components/ramme/field/fs-field.ts +154 -0
  306. package/src/components/ramme/popover/fs-popover.ts +205 -0
  307. package/src/components/ramme/popover/popover.css +34 -0
  308. package/src/components/ramme/tabs/fs-tabs.ts +182 -0
  309. package/src/components/ramme/tabs/tabs.css +68 -0
  310. package/src/tailwind/tailwind.css +134 -0
  311. package/src/tokens/color.ts +267 -0
  312. package/src/tokens/theme.ts +350 -0
  313. package/src/tokens/tokens.css +214 -0
  314. package/src/tokens/tokens.ts +293 -0
@@ -0,0 +1,50 @@
1
+ import { attributes, createGuard } from "../shared.js"
2
+
3
+ export const ACCORDION_CLASS = "fs-accordion" as const
4
+ /** Innholdet som vises når panelet er åpent. */
5
+ export const ACCORDION_CONTENT_CLASS = "fs-accordion__content" as const
6
+
7
+ export const accordionVariants = ["default", "plain"] as const
8
+
9
+ export type AccordionVariant = (typeof accordionVariants)[number]
10
+ export type NonDefaultAccordionVariant = Exclude<AccordionVariant, "default">
11
+
12
+ export type AccordionOptions = {
13
+ /** Egen ramme, eller bare en strek mellom panelene. Standard: `default`. */
14
+ variant?: AccordionVariant
15
+ }
16
+
17
+ export type AccordionAttributes = {
18
+ class: typeof ACCORDION_CLASS
19
+ "data-variant"?: NonDefaultAccordionVariant
20
+ }
21
+
22
+ /**
23
+ * Attributtene for et panel som kan foldes ut.
24
+ *
25
+ * Klassen hører på et `<details>` med et `<summary>` først. Nettleseren tar
26
+ * seg av åpning, lukking, tastatur og at skjermlesere melder «utvidet» eller
27
+ * «sammenfoldet». Bygger du det samme av knapper og `aria-expanded`, må alt
28
+ * det skrives på nytt, og nettleserens søk i siden finner ikke lenger tekst
29
+ * inne i et lukket panel.
30
+ *
31
+ * ```ts
32
+ * <details {...accordion()}>
33
+ * <summary>Hvem kan søke?</summary>
34
+ * <div class={accordion.content}>…</div>
35
+ * </details>
36
+ * ```
37
+ */
38
+ export const accordion = Object.assign(
39
+ ({ variant = "default" }: AccordionOptions = {}): AccordionAttributes =>
40
+ attributes({
41
+ class: ACCORDION_CLASS,
42
+ "data-variant": variant === "default" ? undefined : variant,
43
+ }),
44
+ {
45
+ /** Klassen på innholdet under overskriften. */
46
+ content: ACCORDION_CONTENT_CLASS,
47
+ variants: accordionVariants,
48
+ isVariant: createGuard(accordionVariants),
49
+ },
50
+ )
@@ -0,0 +1,50 @@
1
+ @layer fristil {
2
+ .fs-alert {
3
+ box-sizing: border-box;
4
+ display: flex;
5
+ flex-direction: column;
6
+ gap: var(--size-1);
7
+ padding: var(--fs-alert-padding, var(--size-3) var(--size-4));
8
+ font-family: inherit;
9
+ font-size: var(--font-size-m);
10
+ line-height: 1.5;
11
+ color: var(--semantic-page-foreground);
12
+ background: var(--semantic-neutral-background);
13
+ border-radius: var(--fs-alert-radius, var(--size-1));
14
+ /* Fargen ligger i en strek på kanten framfor i hele flaten, så teksten
15
+ beholder kontrasten mot sideflaten i begge temaer. */
16
+ border-inline-start: var(--fs-alert-accent, var(--size-1)) solid
17
+ var(--semantic-neutral-foreground);
18
+ }
19
+
20
+ .fs-alert__title {
21
+ margin: 0;
22
+ font-size: var(--font-size-m);
23
+ font-weight: 600;
24
+ line-height: 1.5;
25
+ }
26
+
27
+ .fs-alert p {
28
+ margin: 0;
29
+ }
30
+
31
+ .fs-alert[data-color="success"] {
32
+ background: var(--semantic-success-background);
33
+ border-inline-start-color: var(--semantic-success-foreground);
34
+ }
35
+
36
+ .fs-alert[data-color="warning"] {
37
+ background: var(--semantic-warning-background);
38
+ border-inline-start-color: var(--semantic-warning-foreground);
39
+ }
40
+
41
+ .fs-alert[data-color="danger"] {
42
+ background: var(--semantic-danger-background);
43
+ border-inline-start-color: var(--semantic-danger-foreground);
44
+ }
45
+
46
+ .fs-alert[data-color="info"] {
47
+ background: var(--semantic-interactive-background);
48
+ border-inline-start-color: var(--semantic-interactive-main);
49
+ }
50
+ }
@@ -0,0 +1,48 @@
1
+ import { attributes, createGuard } from "../shared.js"
2
+
3
+ export const ALERT_CLASS = "fs-alert" as const
4
+ /** Overskriften inne i meldingen. */
5
+ export const ALERT_TITLE_CLASS = "fs-alert__title" as const
6
+
7
+ export const alertColors = ["info", "success", "warning", "danger"] as const
8
+
9
+ export type AlertColor = (typeof alertColors)[number]
10
+ export type NonNeutralAlertColor = AlertColor
11
+
12
+ export type AlertOptions = {
13
+ /** Hva meldingen betyr. Utelatt gir den nøytrale. */
14
+ color?: AlertColor
15
+ }
16
+
17
+ export type AlertAttributes = {
18
+ class: typeof ALERT_CLASS
19
+ "data-color"?: AlertColor
20
+ }
21
+
22
+ /**
23
+ * Attributtene for en melding som gjelder hele siden eller et helt skjema.
24
+ *
25
+ * Rollen settes ikke her, fordi den avhenger av når meldingen kommer.
26
+ * Står den i markupen fra start, skal den ikke ha noen rolle. Dukker den opp
27
+ * mens siden står stille, trenger den `role="status"`, eller `role="alert"`
28
+ * hvis brukeren må stoppe opp.
29
+ *
30
+ * ```ts
31
+ * <div {...alert({ color: "warning" })}>
32
+ * <p {...alert.title}>Søknaden er ikke sendt</p>
33
+ * </div>
34
+ * ```
35
+ */
36
+ export const alert = Object.assign(
37
+ ({ color }: AlertOptions = {}): AlertAttributes =>
38
+ attributes({
39
+ class: ALERT_CLASS,
40
+ "data-color": color,
41
+ }),
42
+ {
43
+ /** Klassen på overskriften inne i meldingen. */
44
+ title: ALERT_TITLE_CLASS,
45
+ colors: alertColors,
46
+ isColor: createGuard(alertColors),
47
+ },
48
+ )
@@ -0,0 +1,64 @@
1
+ @layer fristil {
2
+ .fs-avatar {
3
+ box-sizing: border-box;
4
+ display: inline-flex;
5
+ align-items: center;
6
+ justify-content: center;
7
+ inline-size: var(--fs-avatar-size, var(--size-10));
8
+ block-size: var(--fs-avatar-size, var(--size-10));
9
+ overflow: hidden;
10
+ font-family: inherit;
11
+ font-size: var(--font-size-s);
12
+ font-weight: 600;
13
+ line-height: 1;
14
+ color: var(--semantic-interactive-foreground);
15
+ background: var(--semantic-interactive-background);
16
+ border-radius: 50%;
17
+ /* Bildet kan være hva som helst. Ringen holder formen tydelig også mot en
18
+ lys flate i bildet. */
19
+ box-shadow: inset 0 0 0 1px var(--semantic-divider-30);
20
+ }
21
+
22
+ .fs-avatar img {
23
+ inline-size: 100%;
24
+ block-size: 100%;
25
+ object-fit: cover;
26
+ }
27
+
28
+ .fs-avatar[data-size="small"] {
29
+ inline-size: var(--size-6);
30
+ block-size: var(--size-6);
31
+ font-size: var(--font-size-xs);
32
+ }
33
+
34
+ .fs-avatar[data-size="large"] {
35
+ inline-size: var(--size-16);
36
+ block-size: var(--size-16);
37
+ font-size: var(--font-size-l);
38
+ }
39
+
40
+ .fs-avatar[data-variant="square"] {
41
+ border-radius: var(--fs-avatar-radius, var(--size-1));
42
+ }
43
+
44
+ /* Flere profilbilder som overlapper. Rekkefølgen snus i CSS, så det
45
+ første bildet ligger øverst uten at markupen må skrives baklengs. */
46
+ .fs-avatar-stack {
47
+ display: inline-flex;
48
+ flex-direction: row-reverse;
49
+ justify-content: flex-end;
50
+ margin: 0;
51
+ padding: 0;
52
+ list-style: none;
53
+ }
54
+
55
+ .fs-avatar-stack > * + * {
56
+ margin-inline-end: var(--fs-avatar-stack-overlap, calc(var(--size-2) * -1));
57
+ }
58
+
59
+ .fs-avatar-stack .fs-avatar {
60
+ box-shadow:
61
+ inset 0 0 0 1px var(--semantic-divider-30),
62
+ 0 0 0 2px var(--semantic-page-background);
63
+ }
64
+ }
@@ -0,0 +1,57 @@
1
+ import { attributes, createGuard } from "../shared.js"
2
+
3
+ export const AVATAR_CLASS = "fs-avatar" as const
4
+ /** Flere profilbilder som overlapper. */
5
+ export const AVATAR_STACK_CLASS = "fs-avatar-stack" as const
6
+
7
+ export const avatarSizes = ["small", "medium", "large"] as const
8
+ export const avatarVariants = ["circle", "square"] as const
9
+
10
+ export type AvatarSize = (typeof avatarSizes)[number]
11
+ export type AvatarVariant = (typeof avatarVariants)[number]
12
+ export type NonDefaultAvatarSize = Exclude<AvatarSize, "medium">
13
+ export type NonDefaultAvatarVariant = Exclude<AvatarVariant, "circle">
14
+
15
+ export type AvatarOptions = {
16
+ /** Størrelse. Standard: `medium`. */
17
+ size?: AvatarSize
18
+ /** Rund eller avrundet firkant. Standard: `circle`. */
19
+ variant?: AvatarVariant
20
+ }
21
+
22
+ export type AvatarAttributes = {
23
+ class: typeof AVATAR_CLASS
24
+ "data-size"?: NonDefaultAvatarSize
25
+ "data-variant"?: NonDefaultAvatarVariant
26
+ }
27
+
28
+ /**
29
+ * Attributtene for et profilbilde eller initialer.
30
+ *
31
+ * Står navnet allerede ved siden av, er bildet pynt: sett `aria-hidden="true"`
32
+ * på det, ellers leses navnet opp to ganger. Står bildet alene, må det ha en
33
+ * tekst som sier hvem det er.
34
+ *
35
+ * ```ts
36
+ * <span {...avatar({ size: "small" })} aria-hidden="true">ON</span>
37
+ * ```
38
+ */
39
+ export const avatar = Object.assign(
40
+ ({
41
+ size = "medium",
42
+ variant = "circle",
43
+ }: AvatarOptions = {}): AvatarAttributes =>
44
+ attributes({
45
+ class: AVATAR_CLASS,
46
+ "data-size": size === "medium" ? undefined : size,
47
+ "data-variant": variant === "circle" ? undefined : variant,
48
+ }),
49
+ {
50
+ /** Klassen på lista med profilbilder som overlapper. */
51
+ stack: AVATAR_STACK_CLASS,
52
+ sizes: avatarSizes,
53
+ isSize: createGuard(avatarSizes),
54
+ variants: avatarVariants,
55
+ isVariant: createGuard(avatarVariants),
56
+ },
57
+ )
@@ -0,0 +1,42 @@
1
+ @layer fristil {
2
+ .fs-badge {
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: inline-flex;
9
+ align-items: center;
10
+ gap: var(--size-1);
11
+ padding: var(--fs-badge-padding, var(--size-0-5) var(--size-2));
12
+ font-family: inherit;
13
+ font-size: var(--font-size-xs);
14
+ font-weight: 600;
15
+ line-height: 1.5;
16
+ border-radius: var(--fs-badge-radius, var(--size-4));
17
+ white-space: nowrap;
18
+ user-select: none;
19
+ background: var(--semantic-interactive-background);
20
+ color: var(--semantic-interactive-main);
21
+ }
22
+
23
+ .fs-badge[data-color="success"] {
24
+ background: var(--semantic-success-background);
25
+ color: var(--semantic-success-foreground);
26
+ }
27
+
28
+ .fs-badge[data-color="warning"] {
29
+ background: var(--semantic-warning-background);
30
+ color: var(--semantic-warning-foreground);
31
+ }
32
+
33
+ .fs-badge[data-color="danger"] {
34
+ background: var(--semantic-danger-background);
35
+ color: var(--semantic-danger-foreground);
36
+ }
37
+
38
+ .fs-badge[data-color="neutral"] {
39
+ background: var(--semantic-neutral-background);
40
+ color: var(--semantic-neutral-foreground);
41
+ }
42
+ }
@@ -0,0 +1,43 @@
1
+ import { attributes, createGuard } from "../shared.js"
2
+
3
+ export const BADGE_CLASS = "fs-badge" as const
4
+
5
+ export const badgeColors = [
6
+ "info",
7
+ "success",
8
+ "warning",
9
+ "danger",
10
+ "neutral",
11
+ ] as const
12
+
13
+ export type BadgeColor = (typeof badgeColors)[number]
14
+ export type NonInfoBadgeColor = Exclude<BadgeColor, "info">
15
+
16
+ export type BadgeOptions = {
17
+ /** Hva tilstanden betyr. Standard: `info`. */
18
+ color?: BadgeColor
19
+ }
20
+
21
+ export type BadgeAttributes = {
22
+ class: typeof BADGE_CLASS
23
+ "data-color"?: NonInfoBadgeColor
24
+ }
25
+
26
+ /**
27
+ * Attributtene for et statusmerke.
28
+ *
29
+ * ```ts
30
+ * <span {...badge({ color: "success" })}>Innvilget</span>
31
+ * ```
32
+ */
33
+ export const badge = Object.assign(
34
+ ({ color = "info" }: BadgeOptions = {}): BadgeAttributes =>
35
+ attributes({
36
+ class: BADGE_CLASS,
37
+ "data-color": color === "info" ? undefined : color,
38
+ }),
39
+ {
40
+ colors: badgeColors,
41
+ isColor: createGuard(badgeColors),
42
+ },
43
+ )
@@ -0,0 +1,50 @@
1
+ @layer fristil {
2
+ .fs-breadcrumbs {
3
+ box-sizing: border-box;
4
+ display: flex;
5
+ flex-wrap: wrap;
6
+ align-items: center;
7
+ gap: var(--fs-breadcrumbs-gap, var(--size-2));
8
+ margin: 0;
9
+ padding: 0;
10
+ list-style: none;
11
+ font-family: inherit;
12
+ font-size: var(--font-size-s);
13
+ line-height: 1.5;
14
+ color: var(--semantic-muted-foreground);
15
+ }
16
+
17
+ .fs-breadcrumbs li {
18
+ display: flex;
19
+ align-items: center;
20
+ gap: var(--fs-breadcrumbs-gap, var(--size-2));
21
+ }
22
+
23
+ /* Skilletegnet er dekorasjon, og legges derfor inn med CSS. Sto det i
24
+ markupen, ville skjermlesere lest «skråstrek» mellom hvert steg. */
25
+ .fs-breadcrumbs li + li::before {
26
+ content: "/";
27
+ color: var(--semantic-muted-foreground);
28
+ }
29
+
30
+ .fs-breadcrumbs a {
31
+ color: var(--semantic-interactive-main);
32
+ text-decoration: underline;
33
+ text-underline-offset: 0.14em;
34
+ }
35
+
36
+ .fs-breadcrumbs a:hover {
37
+ color: var(--semantic-interactive-foreground);
38
+ }
39
+
40
+ .fs-breadcrumbs a:focus-visible {
41
+ outline: 2px solid var(--semantic-interactive-main);
42
+ outline-offset: 2px;
43
+ }
44
+
45
+ .fs-breadcrumbs [aria-current="page"] {
46
+ color: var(--semantic-page-foreground);
47
+ font-weight: 600;
48
+ text-decoration: none;
49
+ }
50
+ }
@@ -0,0 +1,29 @@
1
+ import { attributes } from "../shared.js"
2
+
3
+ export const BREADCRUMBS_CLASS = "fs-breadcrumbs" as const
4
+
5
+ export type BreadcrumbsAttributes = {
6
+ class: typeof BREADCRUMBS_CLASS
7
+ }
8
+
9
+ /**
10
+ * Attributtene for en brødsmulesti.
11
+ *
12
+ * Klassen hører på en `<ol>` inne i en `<nav>` med en tekst som sier hva
13
+ * navigasjonen er, for eksempel `aria-label="Du er her"`. Rekkefølgen betyr
14
+ * noe, så listen må være nummerert selv om tallene ikke vises.
15
+ *
16
+ * Skilletegnet mellom stegene legges inn med CSS. Sto det i markupen, ville
17
+ * skjermlesere lest «skråstrek» mellom hvert steg.
18
+ *
19
+ * ```ts
20
+ * <nav aria-label="Du er her">
21
+ * <ol {...breadcrumbs()}>
22
+ * <li><a href="/">Forsiden</a></li>
23
+ * <li><a href="/saker" aria-current="page">Mine saker</a></li>
24
+ * </ol>
25
+ * </nav>
26
+ * ```
27
+ */
28
+ export const breadcrumbs = (): BreadcrumbsAttributes =>
29
+ attributes({ class: BREADCRUMBS_CLASS })
@@ -0,0 +1,101 @@
1
+ @layer fristil {
2
+ .fs-button {
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: inline-flex;
9
+ align-items: center;
10
+ gap: var(--size-2);
11
+ padding: var(--fs-button-padding, var(--size-2) var(--size-4));
12
+ font-family: inherit;
13
+ font-size: var(--font-size-m);
14
+ font-weight: 500;
15
+ line-height: 1.5;
16
+ border: 1px solid var(--semantic-interactive-main);
17
+ border-radius: var(--fs-button-radius, var(--size-1));
18
+ background: var(--semantic-interactive-main);
19
+ color: var(--semantic-interactive-contrast);
20
+ cursor: pointer;
21
+ text-decoration: none;
22
+ user-select: none;
23
+
24
+ /* Teksten får brekke. Med `white-space: nowrap` sprengte en knapp med en
25
+ lang norsk ledetekst boksen sin på en telefon, og da ruller hele siden
26
+ sidelengs. Knappen skal aldri bli bredere enn plassen den får.
27
+
28
+ `normal` står eksplisitt fordi `white-space` arves: en konsument med
29
+ `nowrap` på noe rundt ville ellers fått problemet tilbake. Målt i
30
+ Chromium holder `text-wrap: balance` alene, siden den setter
31
+ `text-wrap-mode: wrap` og dermed slår den arvede verdien, men den
32
+ eksplisitte linja gjelder også der `text-wrap` ikke er støttet. */
33
+ white-space: normal;
34
+ max-inline-size: 100%;
35
+ text-align: center;
36
+ text-wrap: balance;
37
+ }
38
+
39
+ .fs-button:hover {
40
+ background: var(--semantic-interactive-foreground);
41
+ border-color: var(--semantic-interactive-foreground);
42
+ }
43
+
44
+ .fs-button:focus-visible {
45
+ outline: 2px solid var(--semantic-interactive-main);
46
+ outline-offset: 2px;
47
+ }
48
+
49
+ .fs-button:disabled,
50
+ .fs-button[aria-disabled="true"] {
51
+ background: var(--semantic-disabled-background);
52
+ color: var(--semantic-disabled-foreground);
53
+ border-color: var(--semantic-disabled-background);
54
+ /* Ingen pointer-events: none her. Nettleseren blokkerer allerede et
55
+ deaktivert felt, og pointer-events ville bare hindret markøren i å
56
+ vises. Elementet blir da ikke truffet av musa i det hele tatt. */
57
+ cursor: not-allowed;
58
+ }
59
+
60
+ /* aria-disabled er bare en opplysning til skjermlesere; elementet virker
61
+ fortsatt. Her stopper pointer-events musa, på bekostning av markøren.
62
+ Skal feltet faktisk være av, bruk `disabled` eller `readonly`. */
63
+ .fs-button[aria-disabled="true"] {
64
+ pointer-events: none;
65
+ }
66
+
67
+ /* Sekundær: omriss, gjennomsiktig bakgrunn */
68
+ .fs-button[data-variant="secondary"] {
69
+ background: transparent;
70
+ color: var(--semantic-interactive-main);
71
+ border-color: var(--semantic-interactive-main);
72
+ }
73
+
74
+ .fs-button[data-variant="secondary"]:hover {
75
+ background: var(--semantic-interactive-background);
76
+ }
77
+
78
+ /* Ghost: ingen kant, ingen bakgrunn */
79
+ .fs-button[data-variant="ghost"] {
80
+ background: transparent;
81
+ color: var(--semantic-interactive-main);
82
+ border-color: transparent;
83
+ }
84
+
85
+ .fs-button[data-variant="ghost"]:hover {
86
+ background: var(--semantic-interactive-background);
87
+ }
88
+
89
+ /* Danger */
90
+ .fs-button[data-variant="danger"] {
91
+ background: var(--semantic-danger-background);
92
+ color: var(--semantic-danger-foreground);
93
+ border-color: var(--semantic-danger-foreground);
94
+ }
95
+
96
+ .fs-button[data-variant="danger"]:hover {
97
+ background: var(--semantic-danger-main);
98
+ border-color: var(--semantic-danger-main);
99
+ color: var(--semantic-danger-contrast);
100
+ }
101
+ }
@@ -0,0 +1,44 @@
1
+ import { attributes, createGuard } from "../shared.js"
2
+
3
+ export const BUTTON_CLASS = "fs-button" as const
4
+
5
+ export const buttonVariants = [
6
+ "primary",
7
+ "secondary",
8
+ "ghost",
9
+ "danger",
10
+ ] as const
11
+
12
+ export type ButtonVariant = (typeof buttonVariants)[number]
13
+ export type NonPrimaryButtonVariant = Exclude<ButtonVariant, "primary">
14
+
15
+ export type ButtonOptions = {
16
+ /** Hvor tung handlingen er. Standard: `primary`. */
17
+ variant?: ButtonVariant
18
+ }
19
+
20
+ export type ButtonAttributes = {
21
+ class: typeof BUTTON_CLASS
22
+ "data-variant"?: NonPrimaryButtonVariant
23
+ }
24
+
25
+ /**
26
+ * Attributtene for en knapp.
27
+ *
28
+ * ```ts
29
+ * <button {...button({ variant: "secondary" })}>Lagre utkast</button>
30
+ * ```
31
+ */
32
+ export const button = Object.assign(
33
+ ({ variant = "primary" }: ButtonOptions = {}): ButtonAttributes =>
34
+ attributes({
35
+ class: BUTTON_CLASS,
36
+ "data-variant": variant === "primary" ? undefined : variant,
37
+ }),
38
+ {
39
+ /** De lovlige variantene, for oppslag og forgrening. */
40
+ variants: buttonVariants,
41
+ /** Sjekker om en streng utenfra er en gyldig variant. */
42
+ isVariant: createGuard(buttonVariants),
43
+ },
44
+ )
@@ -0,0 +1,48 @@
1
+ @layer fristil {
2
+ .fs-card {
3
+ box-sizing: border-box;
4
+ display: flex;
5
+ flex-direction: column;
6
+ gap: var(--size-2);
7
+ padding: var(--fs-card-padding, var(--size-4));
8
+ font-family: inherit;
9
+ color: var(--semantic-page-foreground);
10
+ background: var(--semantic-page-background);
11
+ border: 1px solid var(--semantic-divider-30);
12
+ border-radius: var(--fs-card-radius, var(--size-2));
13
+ }
14
+
15
+ .fs-card__title {
16
+ margin: 0;
17
+ font-size: var(--font-size-l);
18
+ font-weight: 600;
19
+ line-height: 1.4;
20
+ }
21
+
22
+ .fs-card p {
23
+ margin: 0;
24
+ line-height: 1.6;
25
+ }
26
+
27
+ .fs-card[data-variant="filled"] {
28
+ background: var(--semantic-neutral-background);
29
+ border-color: transparent;
30
+ }
31
+
32
+ /* Hele kortet er klikkbart. Elementet er da en <a> eller en <button>, og
33
+ fokusmarkeringen kommer derfra, ikke herfra. */
34
+ .fs-card[data-interactive] {
35
+ text-align: start;
36
+ text-decoration: none;
37
+ cursor: pointer;
38
+ }
39
+
40
+ .fs-card[data-interactive]:hover {
41
+ border-color: var(--semantic-interactive-main);
42
+ }
43
+
44
+ .fs-card[data-interactive]:focus-visible {
45
+ outline: 2px solid var(--semantic-interactive-main);
46
+ outline-offset: 2px;
47
+ }
48
+ }
@@ -0,0 +1,50 @@
1
+ import { attributes, createGuard } from "../shared.js"
2
+
3
+ export const CARD_CLASS = "fs-card" as const
4
+ /** Overskriften inne i kortet. */
5
+ export const CARD_TITLE_CLASS = "fs-card__title" as const
6
+
7
+ export const cardVariants = ["outline", "filled"] as const
8
+
9
+ export type CardVariant = (typeof cardVariants)[number]
10
+ export type NonDefaultCardVariant = Exclude<CardVariant, "outline">
11
+
12
+ export type CardOptions = {
13
+ /** Flate eller omriss. Standard: `outline`. */
14
+ variant?: CardVariant
15
+ /** Hele kortet er klikkbart. Bruk bare på `<a>` eller `<button>`. */
16
+ interactive?: boolean
17
+ }
18
+
19
+ export type CardAttributes = {
20
+ class: typeof CARD_CLASS
21
+ "data-variant"?: NonDefaultCardVariant
22
+ "data-interactive"?: ""
23
+ }
24
+
25
+ /**
26
+ * Attributtene for et kort.
27
+ *
28
+ * `interactive` gir hover og fokusmarkering, men gjør ingenting klikkbart av
29
+ * seg selv. Skal hele kortet kunne trykkes på, må elementet være en `<a>`
30
+ * eller en `<button>`. Et `<div>` med en klikklytter kan hverken nås med
31
+ * tastatur eller meldes som noe å trykke på.
32
+ *
33
+ * ```ts
34
+ * <a {...card({ interactive: true })} href="/sak/481">…</a>
35
+ * ```
36
+ */
37
+ export const card = Object.assign(
38
+ ({ variant = "outline", interactive }: CardOptions = {}): CardAttributes =>
39
+ attributes({
40
+ class: CARD_CLASS,
41
+ "data-variant": variant === "outline" ? undefined : variant,
42
+ "data-interactive": interactive ? ("" as const) : undefined,
43
+ }),
44
+ {
45
+ /** Klassen på overskriften inne i kortet. */
46
+ title: CARD_TITLE_CLASS,
47
+ variants: cardVariants,
48
+ isVariant: createGuard(cardVariants),
49
+ },
50
+ )