@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,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
+ )