Compare commits

...
Author SHA1 Message Date
Halvor Haugan 2b714ee0af docs(core): update hideLabel prop description for consistency across components 2026-10-20 11:23:31 +02:00
dependabot[bot] a2d0ffda0a ⬆️ Bump figlet from 1.11.3 to 1.12.0 (#5404)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-19 10:40:46 +02:00
dependabot[bot] 45aa8baf10 ⬆️ Bump tsc-alias from 1.9.1 to 1.9.7 (#5407)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-19 10:38:19 +02:00
dependabot[bot] 3f7b72369e ⬆️ Bump react-hook-form from 7.89.0 to 7.90.0 (#5409)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-19 10:30:25 +02:00
Halvor Haugan 8009289ca4 feat(website): add desc on ToggleGroup icon example 2026-10-16 10:34:43 +02:00
Ken ee50050d27 feat: Added custom link for navno tema header 2026-10-15 15:20:15 +02:00
Halvor Haugan 4e1cfddd84 feat(Combobox2): open on arrow up/down on trigger 2026-10-15 13:17:36 +02:00
Halvor Haugan 643ab79c8d chore(AI): update copilot-instructions.md and code-review skill (regarding tests) 2026-10-15 13:08:15 +02:00
Halvor Haugan d134397fd0 feat(core): mark Badge and Lookup from /PREVIEW as deprecated (#5401) 2026-10-15 11:44:32 +02:00
Halvor Haugan d8e62db7f1 fix(Listbox): fix issue where options were not announced by VO in Safari (#5399) 2026-10-14 15:34:57 +02:00
dependabot[bot] 0cf700322c ⬆️ Bump react-hook-form from 7.86.0 to 7.89.0 (#5395)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-14 13:21:32 +02:00
dependabot[bot] 2c468cc8c7 ⬆️ Bump the storybook group with 8 updates (#5393)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-14 13:21:12 +02:00
Halvor Haugan d722f23a5e fix(Combobox2): fix issue with VO+Safari when selecting option 2026-10-14 13:15:45 +02:00
github-actions[bot] 317940ec17 Version Packages (#5387)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-10-14 12:46:56 +02:00
fb0330cf9d refactor(Badge): Removed built inn a11y handling trough aria-label, labelledby and aria-hidden (#5397)
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
Co-authored-by: Halvor Haugan <halvor.haugan@nav.no>
2026-10-14 10:37:44 +02:00
Halvor Haugan b9eb6568ad feat(website): add data-text-prose on DoDont 2026-10-13 14:21:17 +02:00
cc379525c0 feat(sanity): Store list of last published documents to keep track of postings (#5371)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
2026-10-13 12:50:12 +02:00
KenandHalvor Haugan 6bb7becad7 fix(forms): Properly forward disabled/readonly-states, force disabled state when loading true (#5328)
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
2026-10-13 09:13:19 +02:00
dependabot[bot] 8a513c96cc ⬆️ Bump json5 from 2.2.1 to 2.2.3 (#5396)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-12 15:25:57 +02:00
Ken b4e5a3b8bd fix: all groups unique keys on dependabot.yml 2026-10-12 11:09:25 +02:00
dependabot[bot]andKen 64dd1a529b ⬆️ Bump next from 16.3.6 to 16.3.8 in /aksel.nav.no/website in the npm_and_yarn group across 1 directory (#5390)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Ken <ken.aleksander@gmail.com>
2026-10-12 11:02:37 +02:00
3f9daed6a0 ⬆️ Bump simple-git from 3.36.0 to 4.0.1 (#5380)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
Co-authored-by: Halvor Haugan <halvor.haugan@nav.no>
2026-10-12 10:13:58 +02:00
dependabot[bot] a53637c2d7 ⬆️ Bump http-cache-semantics from 4.2.0 to 4.3.0 (#5389)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-12 09:44:28 +02:00
Ken c9e48dfbd0 feat(website): Add error-pages to website, fix logging (#5321) 2026-10-12 09:38:45 +02:00
dependabot[bot] ef2e1f88e3 ⬆️ Bump js-yaml from 5.4.2 to 5.4.3 (#5378)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-12 09:22:37 +02:00
Halvor Haugan 05e88b37b8 fix(Search): icon more visible in forced-colors mode (#5386) 2026-10-09 16:48:25 +02:00
Halvor Haugan 8ec9677d20 fix(Combobox): misc fixes 2026-10-09 16:36:45 +02:00
Halvor Haugan 2b6c530fef fix(Listbox): forced-colors adjustment 2026-10-09 16:33:25 +02:00
github-actions[bot] f784aac8bb Version Packages (#5384)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-10-09 14:02:16 +02:00
Halvor Haugan 1490249f0c feat(Listbox): no looping (#5383) 2026-10-09 12:29:14 +02:00
Ken 31c4296568 fix(core): Expandable elements now respect media print and auto-opens (#5363) 2026-10-09 12:26:45 +02:00
Halvor Haugan af64a52f83 chore(deps): bump js-yaml from 4.3.1 to 4.3.2 2026-10-08 21:04:27 +02:00
Halvor Haugan 0d6db896c0 chore(mcp): bump @modelcontextprotocol/inspector to 2.9.0 2026-10-08 20:56:15 +02:00
dependabot[bot]andHalvor Haugan 91b4d8827e ⬆️ Bump sharp from 0.35.4 to 0.35.5 (#5374)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Halvor Haugan <halvor.haugan@nav.no>
2026-10-08 20:49:07 +02:00
dependabot[bot] ea9ff5e63d ⬆️ Bump @modelcontextprotocol/sdk from 1.30.0 to 1.31.0 (#5373)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-08 20:47:17 +02:00
dependabot[bot] 3ab3498ddb ⬆️ Bump fast-copy from 4.0.4 to 4.1.1 (#5381)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-08 20:35:32 +02:00
dependabot[bot] 40e1717fe9 ⬆️ Bump proxy-addr from 2.0.7 to 2.0.8 (#5377)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-08 20:34:29 +02:00
dependabot[bot] 06e3234ecd ⬆️ Bump compression from 1.8.1 to 1.8.2 (#5376)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-08 20:34:15 +02:00
dependabot[bot] 43f7fc70f0 ⬆️ Bump source-map-js from 1.2.1 to 1.2.2 (#5375)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-08 20:33:51 +02:00
github-actions[bot] 2994c8d5ac Version Packages (#5361)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-10-08 19:03:01 +02:00
Ken 1bca520e92 examples(radiocard): Added missing compositions (#5369) 2026-10-08 12:26:24 +02:00
Ken c5dd3097e5 feat(icons): Added new icon MenuHamburgerMagnifyingGlass (#5368) 2026-10-07 15:00:59 +02:00
Ken a8cca91bcd feat(website): Adjust DS-page template 2026-10-07 11:48:12 +02:00
Ken bdddbd75d1 fix(website): Forvalter-field now has updated spacing 2026-10-06 13:35:35 +02:00
Ken 5e778e84a2 fix(sanity): Egnet til field no longer required 2026-10-06 13:32:23 +02:00
111 changed files with 2739 additions and 1152 deletions

No files matched your search

+5
View File
@@ -0,0 +1,5 @@
---
"@navikt/ds-react": patch
---
Lookup, Badge: Mark as deprecated when imported from @navikt/ds-react/PREVIEW
-5
View File
@@ -1,5 +0,0 @@
---
"@navikt/aksel": patch
---
CLI: Update clipboardy to v5.3.2
-5
View File
@@ -1,5 +0,0 @@
---
"@navikt/aksel": patch
---
CLI: Update figlet to 1.11.3
+3 -2
View File
@@ -10,8 +10,8 @@
## Behavior
- Only respond in same language as the input. Prefer english always.
- Use caveman-skill unless asked not to
- Only respond in same language as the input. Prefer English always.
- Use caveman-skill unless asked not to.
- No apologies, no "I think", no change summaries.
- Answer-first. 0-2 sentences max.
- No thinking aloud; do next edit. Explain only when asked or non-obvious.
@@ -88,3 +88,4 @@ Path-specific rules load automatically from `.github/instructions/` (see each `a
- `node:` imports for Node builtins. Never import from `esm/`/`cjs/` output.
- Preserve JSDoc on public props/components when changing public APIs.
- Reuse helpers: `cl`, `composeEventHandlers`, `useId`, `omit`, `useClientLayoutEffect`, `useEventListener`.
- No tests for trivial logic. Only tests for complex logic that has a higher risk of breaking.
+12
View File
@@ -15,6 +15,12 @@ updates:
registries:
- npm-github
groups:
next:
applies-to: version-updates
patterns:
- "@next/eslint-plugin-next"
- "@next/bundle-analyzer"
- "next"
sanity:
applies-to: version-updates
patterns:
@@ -43,6 +49,12 @@ updates:
- "vitest"
- "@vitest/*"
# Security updates ignore version-update groups, so mirror them to keep lockstep packages together.
next-security:
applies-to: security-updates
patterns:
- "@next/eslint-plugin-next"
- "@next/bundle-analyzer"
- "next"
sanity-security:
applies-to: security-updates
patterns:
+1 -1
View File
@@ -69,7 +69,7 @@ Skip anything ESLint, Stylelint, Biome, Prettier or TypeScript already catches.
### Tests and docs (🟡)
- A behavior change has a story `play` test or Vitest test proving the user-facing contract. Queries use role and accessible name.
- Complex logic with a higher risk of breaking has a story `play` test or Vitest test proving the user-facing contract. Queries use role and accessible name.
- Tests are deterministic: no dependence on timezone, current time or randomness.
- Edge cases that are easy to miss: controlled vs. uncontrolled, `ref`/`className`/`...rest` forwarding, `as` prop, empty and very long content.
- Website examples (`aksel.nav.no/website/pages/eksempler/`) are updated when usage changes.
+10
View File
@@ -1,5 +1,15 @@
# @navikt/aksel-icons
## 8.19.2
## 8.19.1
## 8.19.0
### Minor Changes
- Icons: Added new icon MenuHamburgerMagnifyingGlass ([#5368](https://github.com/navikt/aksel/pull/5368))
## 8.18.0
## 8.17.2
@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.5 3.25C17.9518 3.25 20.75 6.04822 20.75 9.5C20.75 11.6221 19.6911 13.4953 18.0742 14.625L20.667 19.6562C20.8567 20.0244 20.7119 20.4773 20.3438 20.667C19.9756 20.8567 19.5227 20.7119 19.333 20.3438L16.751 15.3301C16.0524 15.6 15.2938 15.75 14.5 15.75C11.0482 15.75 8.25 12.9518 8.25 9.5C8.25 6.04822 11.0482 3.25 14.5 3.25ZM15.8994 17.25C15.993 17.25 16.0792 17.3026 16.1221 17.3857L16.6377 18.3857C16.723 18.552 16.6021 18.75 16.415 18.75H3.5C3.08579 18.75 2.75 18.4142 2.75 18C2.75 17.5858 3.08579 17.25 3.5 17.25H15.8994ZM14.5 4.75C11.8766 4.75 9.75 6.87665 9.75 9.5C9.75 12.1234 11.8766 14.25 14.5 14.25C17.1234 14.25 19.25 12.1234 19.25 9.5C19.25 6.87665 17.1234 4.75 14.5 4.75ZM6.75195 11.25C6.86836 11.25 6.96896 11.3306 6.99805 11.4434C7.08182 11.7679 7.1863 12.084 7.30957 12.3906C7.37768 12.56 7.2558 12.75 7.07324 12.75H3.5C3.08579 12.75 2.75 12.4142 2.75 12C2.75 11.5858 3.08579 11.25 3.5 11.25H6.75195ZM7.55664 5.25C7.75557 5.25 7.8765 5.47207 7.77734 5.64453C7.60333 5.94718 7.4499 6.26318 7.31738 6.58984C7.27831 6.68596 7.18482 6.75 7.08105 6.75H3.5C3.08579 6.75 2.75 6.41421 2.75 6C2.75 5.58579 3.08579 5.25 3.5 5.25H7.55664Z" fill="#202733"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1,12 @@
name: MenuHamburgerMagnifyingGlass
category: Interface
sub_category: Menu
keywords:
- Menu
- meny
- hamburger
- search
- søk
- forstørrelsesglass
variant: Stroke
created_at: 07.10.2026
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/aksel-icons",
"version": "8.18.0",
"version": "8.19.2",
"author": "Aksel | Nav",
"homepage": "https://aksel.nav.no/ikoner",
"repository": {
@@ -82,12 +82,12 @@
"copyfiles": "^2.4.1",
"fast-glob": "3.3.3",
"hast-util-select": "6.0.4",
"js-yaml": "^5.4.2",
"js-yaml": "^5.4.3",
"lodash": "4.18.1",
"prettier": "3.9.6",
"react": "19.3.0",
"rehype-parse": "9.0.1",
"tsc-alias": "1.9.1",
"tsc-alias": "1.9.7",
"tsx": "^4.23.15",
"typescript": "6.0.3",
"unified": "11.0.5",
+6
View File
@@ -1,5 +1,11 @@
# @navikt/aksel-stylelint
## 8.19.2
## 8.19.1
## 8.19.0
## 8.18.0
## 8.17.2
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/aksel-stylelint",
"version": "8.18.0",
"version": "8.19.2",
"type": "module",
"engines": {
"node": ">=18.12.0"
@@ -43,8 +43,8 @@
"dev": "yarn watch:lint"
},
"devDependencies": {
"@navikt/ds-css": "^8.18.0",
"@navikt/ds-tokens": "^8.18.0",
"@navikt/ds-css": "^8.19.2",
"@navikt/ds-tokens": "^8.19.2",
"concurrently": "10.0.4",
"postcss-selector-parser": "^7.1.6",
"postcss-value-parser": "^4.2.0",
+12
View File
@@ -1,5 +1,17 @@
# @navikt/aksel
## 8.19.2
## 8.19.1
## 8.19.0
### Patch Changes
- CLI: Update clipboardy to v5.3.2 ([#5357](https://github.com/navikt/aksel/pull/5357))
- CLI: Update figlet to 1.11.3 ([#5360](https://github.com/navikt/aksel/pull/5360))
## 8.18.0
### Patch Changes
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/aksel",
"version": "8.18.0",
"version": "8.19.2",
"description": "Aksel command line interface. Codemods and other utilities for Aksel users.",
"author": "Aksel | Nav designsystem team",
"license": "MIT",
@@ -46,7 +46,7 @@
"commander": "15.0.0",
"enquirer": "2.4.1",
"fast-glob": "3.3.3",
"figlet": "1.11.3",
"figlet": "1.12.0",
"is-git-clean": "1.1.0",
"jscodeshift": "17.4.0",
"lodash": "4.18.1"
+18
View File
@@ -1,5 +1,23 @@
# @navikt/ds-css
## 8.19.2
### Patch Changes
- Search: Icon more visible in forced-colors mode ([#5386](https://github.com/navikt/aksel/pull/5386))
## 8.19.1
### Patch Changes
- Accordion: `@media print` now auto-opens every Accordion ([#5363](https://github.com/navikt/aksel/pull/5363))
- ExpansionCard: `@media print` now auto-opens every ExpansionCard ([#5363](https://github.com/navikt/aksel/pull/5363))
- ReadMore: `@media print` now auto-opens every ReadMore ([#5363](https://github.com/navikt/aksel/pull/5363))
## 8.19.0
## 8.18.0
### Patch Changes
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-css",
"version": "8.18.0",
"version": "8.19.2",
"description": "CSS for Nav Designsystem",
"author": "Aksel | Nav designsystem team",
"keywords": [
@@ -32,7 +32,7 @@
"css:get-version": "node config/get-version.js"
},
"devDependencies": {
"@navikt/ds-tokens": "^8.18.0",
"@navikt/ds-tokens": "^8.19.2",
"browserslist": "^4.28.7",
"esbuild": "^0.28.2",
"fast-glob": "3.3.3",
+17
View File
@@ -123,6 +123,11 @@
.aksel-accordion--indent > .aksel-accordion__item > .aksel-accordion__content {
box-shadow: -2px 0 0 0 var(--ax-border-neutral-subtleA);
@media print {
border-left: 1px solid black;
margin-left: calc(var(--ax-space-24) - 2px);
}
}
.aksel-accordion__item {
@@ -163,4 +168,16 @@
border-bottom: 1px solid var(--ax-border-neutral-subtleA);
}
}
@media print {
& > .aksel-accordion__content {
visibility: visible;
/* Override grid layout for print. Grid causes element to stretch unexpectedly across printed pages in FF */
display: block;
margin-block: var(--ax-space-8);
margin-block-end: var(--ax-space-24);
opacity: 1;
}
}
}
+15
View File
@@ -19,6 +19,10 @@
box-shadow: 0 0 0 1px var(--ax-border-default);
border-color: var(--ax-border-default);
}
@media print {
border-color: black;
}
}
/* ------------------------ ExpansionCard Small-size ------------------------ */
@@ -166,6 +170,17 @@
opacity: 1;
}
}
@media print {
/* Override grid layout for print. Grid causes element to stretch unexpectedly across printed pages in FF */
display: block;
visibility: visible;
padding-block: var(--__axc-expansioncard-padding-block);
& .aksel-expansioncard__content-inner {
opacity: 1;
}
}
}
.aksel-expansioncard__content-inner {
+11 -4
View File
@@ -35,7 +35,7 @@
color: var(--ax-text-neutral);
width: 100%;
min-height: 3rem;
display: inline-block;
display: block;
position: relative;
padding: var(--ax-space-8);
padding-right: var(--ax-space-40);
@@ -81,10 +81,11 @@
.aksel-combobox2__field-chevron {
position: absolute;
font-size: 1.5rem;
right: var(--ax-space-8);
top: 0;
margin-top: 0.75rem; /* We don't use dynamic centering because we don't want it to move if height changes */
font-size: 1.5rem;
pointer-events: none;
margin-top: 12px; /* We don't use dynamic centering because we don't want it to move if height changes */
color: var(--ax-text-neutral);
@media (forced-colors: active) {
@@ -92,7 +93,7 @@
}
.aksel-combobox2[data-size="small"] & {
margin-top: 4px;
margin-top: 0.25rem;
}
}
@@ -236,6 +237,12 @@
.aksel-combobox2__option:is([aria-selected="true"], [aria-selected="true"][data-virtual-focus="true"])
.aksel-listbox__option-inner {
background-color: var(--ax-bg-moderate-pressedA);
@media (forced-colors: active) {
background-color: SelectedItem;
color: SelectedItemText;
forced-color-adjust: none;
}
}
.aksel-combobox2__option-checkmark {
+4
View File
@@ -81,6 +81,10 @@
pointer-events: none;
font-size: 1.5rem;
@media (forced-colors: active) {
color: CanvasText;
}
.aksel-search--disabled & {
opacity: var(--ax-opacity-disabled);
}
+4
View File
@@ -49,5 +49,9 @@
outline: 2px solid var(--ax-border-focus);
outline-offset: -2px;
background-color: var(--ax-bg-neutral-moderate-hoverA);
@media (forced-colors: active) {
outline-color: Highlight;
}
}
}
+4
View File
@@ -86,6 +86,10 @@
.aksel-read-more[data-state="closed"] & {
display: none;
@media print {
display: block;
}
}
}
+38
View File
@@ -1,5 +1,43 @@
# @navikt/ds-react
## 8.19.2
### Patch Changes
- TextField: Now inherits `readOnly` from Fieldset. No longer gets read-only styling when `disabled` and `readOnly`. ([#5328](https://github.com/navikt/aksel/pull/5328))
- Textarea: Now inherits `readOnly` from Fieldset. No longer gets read-only styling when `disabled` and `readOnly`. ([#5328](https://github.com/navikt/aksel/pull/5328))
- Fieldset: Nested Fieldsets now inherits the parent's `disabled` state. ([#5328](https://github.com/navikt/aksel/pull/5328))
- Button: `loading={true}` and `disabled={false}` now disable the element. ([#5328](https://github.com/navikt/aksel/pull/5328))
- Switch: `loading={true}` and `disabled={false}` now disable the element. ([#5328](https://github.com/navikt/aksel/pull/5328))
- Search.Button: `loading={true}` and `disabled={false}` now disable SearchButton. ([#5328](https://github.com/navikt/aksel/pull/5328))
- Badge: No longer sets aria-hidden internally automatically. ([#5397](https://github.com/navikt/aksel/pull/5397))
- Updated dependencies []:
- @navikt/ds-tokens@8.19.2
- @navikt/aksel-icons@8.19.2
## 8.19.1
### Patch Changes
- Updated dependencies []:
- @navikt/ds-tokens@8.19.1
- @navikt/aksel-icons@8.19.1
## 8.19.0
### Patch Changes
- Updated dependencies [[`c5dd309`](https://github.com/navikt/aksel/commit/c5dd3097e51ef2a9f79964248398bd158660bd3b)]:
- @navikt/aksel-icons@8.19.0
- @navikt/ds-tokens@8.19.0
## 8.18.0
### Minor Changes
+12 -12
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-react",
"version": "8.18.0",
"version": "8.19.2",
"description": "React components from the Norwegian Labour and Welfare Administration.",
"author": "Aksel, a team part of the Norwegian Labour and Welfare Administration.",
"license": "MIT",
@@ -68,22 +68,22 @@
},
"./PREVIEW/Lookup": {
"import": {
"types": "./esm/lookup/index.d.ts",
"default": "./esm/lookup/index.js"
"types": "./esm/lookup/index-deprecated.d.ts",
"default": "./esm/lookup/index-deprecated.js"
},
"require": {
"types": "./cjs/lookup/index.d.ts",
"default": "./cjs/lookup/index.js"
"types": "./cjs/lookup/index-deprecated.d.ts",
"default": "./cjs/lookup/index-deprecated.js"
}
},
"./PREVIEW/Badge": {
"import": {
"types": "./esm/badge/index.d.ts",
"default": "./esm/badge/index.js"
"types": "./esm/badge/index-deprecated.d.ts",
"default": "./esm/badge/index-deprecated.js"
},
"require": {
"types": "./cjs/badge/index.d.ts",
"default": "./cjs/badge/index.js"
"types": "./cjs/badge/index-deprecated.d.ts",
"default": "./cjs/badge/index-deprecated.js"
}
},
"./PREVIEW/RadioCard": {
@@ -778,8 +778,8 @@
"dependencies": {
"@floating-ui/react": "0.27.20",
"@floating-ui/react-dom": "^2.1.9",
"@navikt/aksel-icons": "^8.18.0",
"@navikt/ds-tokens": "^8.18.0",
"@navikt/aksel-icons": "^8.19.2",
"@navikt/ds-tokens": "^8.19.2",
"date-fns": "^4.0.0",
"react-day-picker": "10.0.1"
},
@@ -801,7 +801,7 @@
"react-dom": "19.3.0",
"rimraf": "6.1.3",
"swr": "^2.4.2",
"tsc-alias": "1.9.1",
"tsc-alias": "1.9.7",
"tsx": "^4.23.15",
"typescript": "6.0.3",
"vitest": "4.1.11"
+10 -10
View File
@@ -19,12 +19,12 @@ export default meta;
type Story = StoryObj<typeof Badge>;
const statuses: { color: AkselColor; label: string }[] = [
{ color: "danger", label: "Avslått" },
{ color: "success", label: "Innvilget" },
{ color: "info", label: "Under behandling" },
{ color: "warning", label: "Mangler dokumentasjon" },
{ color: "neutral", label: "Ikke startet" },
const statuses: AkselColor[] = [
"danger",
"success",
"info",
"warning",
"neutral",
];
export const Default: Story = {
@@ -37,8 +37,8 @@ export const Default: Story = {
export const Dot: Story = {
render: () => (
<HStack gap="space-16" align="center">
{statuses.map(({ color, label }) => (
<Badge key={color} data-color={color} aria-label={label} />
{statuses.map((color) => (
<Badge key={color} data-color={color} />
))}
</HStack>
),
@@ -58,13 +58,13 @@ export const Count: Story = {
export const Anchored: Story = {
render: () => (
<HStack gap="space-32" align="center">
<Badge data-color="danger" count={42}>
<Badge data-color="danger" count={42} aria-hidden>
<Button
icon={<InboxIcon aria-hidden />}
aria-label="Innboks, 42 nye meldinger"
/>
</Badge>
<Badge data-color="danger" aria-label="Nytt varsel">
<Badge data-color="danger" aria-hidden>
<Button icon={<InboxIcon aria-hidden />} aria-label="Innboks" />
</Badge>
</HStack>
+12 -18
View File
@@ -35,9 +35,8 @@ interface BadgeProps extends HTMLAttributes<HTMLSpanElement> {
* Use standalone for inline indicators/counts, or pass the element you want to
* anchor it to as `children` to pin the badge to one of its corners.
*
* Accessibility: A badge with no label is treated as decorative when it is a
* dot or anchored to another element. When anchoring to an interactive element,
* fold the state into that element's accessible name
* When anchoring to an interactive element,
* set `aria-hidden` on the badge and fold the count into that element's accessible name
* (e.g. `aria-label="Inbox, 42 new messages"`).
*
* @see [📝 Documentation](https://aksel.nav.no/komponenter/core/badge)
@@ -46,11 +45,11 @@ interface BadgeProps extends HTMLAttributes<HTMLSpanElement> {
* @example
* ```jsx
* // Standalone
* <Badge data-color="success" aria-label="Active" />
* <Badge data-color="success" />
* <Badge data-color="danger" count={42} />
*
* // Anchored to an element
* <Badge data-color="danger" count={42}>
* <Badge data-color="danger" count={42} aria-hidden>
* <Button icon={<InboxIcon />} aria-label="Inbox, 42 new messages" />
* </Badge>
* ```
@@ -61,10 +60,6 @@ const Badge = forwardRef<HTMLSpanElement, BadgeProps>(
children,
className,
"data-color": color = "danger",
"aria-label": ariaLabel,
"aria-labelledby": ariaLabelledby,
role,
"aria-hidden": ariaHidden,
count,
maxCount = 99,
...rest
@@ -75,10 +70,6 @@ const Badge = forwardRef<HTMLSpanElement, BadgeProps>(
count == null ? undefined : normalizeCount(count, 0);
const normalizedMaxCount = normalizeCount(maxCount, 99);
const isDot = normalizedCount == null;
const hasLabel =
Boolean(ariaLabel?.trim()) || Boolean(ariaLabelledby?.trim());
const isDecorative =
role == null && !hasLabel && (isDot || children != null);
const badge = (
<BodyShort
@@ -89,10 +80,6 @@ const Badge = forwardRef<HTMLSpanElement, BadgeProps>(
weight="semibold"
data-color={color}
data-dot={isDot || undefined}
aria-label={ariaLabel}
aria-labelledby={ariaLabelledby}
role={role}
aria-hidden={ariaHidden ?? (isDecorative || undefined)}
className={cl("aksel-badge", className)}
>
{isDot
@@ -125,4 +112,11 @@ export namespace Badge {
export type Props = BadgeProps;
}
export { Badge };
/** @deprecated Badge is no longer in preview. Import it from `@navikt/ds-react` instead. */
const BadgeDeprecated = Badge;
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace BadgeDeprecated {
export type Props = BadgeProps;
}
export { Badge, BadgeDeprecated };
@@ -0,0 +1,2 @@
"use client";
export { BadgeDeprecated as Badge } from "./Badge";
+2 -2
View File
@@ -105,9 +105,9 @@ export const Button: OverridableComponent<ButtonProps, HTMLButtonElement> =
className={cl(className, "aksel-button", `aksel-button--${size}`, {
"aksel-button--loading": loading,
"aksel-button--icon-only": !!icon && !children,
"aksel-button--disabled": disabled ?? loading,
"aksel-button--disabled": disabled || loading,
})}
disabled={(disabled ?? loading) ? true : undefined}
disabled={disabled || loading ? true : undefined}
>
{icon && iconPosition === "left" && (
<span className="aksel-button__icon">{icon}</span>
@@ -86,7 +86,9 @@ const AutoSuggest = forwardRef<HTMLInputElement, AutoSuggestProps>(
<Floating>
<Listbox setVirtuallyFocusedOptionId={setVirtuallyFocusedOptionId}>
<Floating.Anchor>
<Listbox.InputSlot>
<Listbox.InputSlot
virtuallyFocusedOptionId={virtuallyFocusedOptionId}
>
<Search
label="Tabellsøk"
variant="simple"
@@ -107,6 +107,18 @@ function DisabledFormElements() {
<TextField label="Second field" />
</Fieldset>
<Fieldset legend="Nested Fieldset (outer disabled)" disabled>
<Fieldset legend="Inner fieldset">
<TextField label="Nested field" />
<Switch>Nested switch</Switch>
</Fieldset>
</Fieldset>
<Fieldset legend="Fieldset (readOnly)" readOnly>
<TextField label="TextField" defaultValue="Read-only value" />
<Textarea label="Textarea" defaultValue="Read-only value" />
</Fieldset>
<FileUpload.Dropzone
label="FileUpload Dropzone"
onSelect={console.log}
@@ -19,7 +19,7 @@ export interface CheckboxProps
*/
children: React.ReactNode;
/**
* Visually hides label and description, and makes it viewable for screen-readers only.
* Hides the label and description visually while keeping them available to screen readers.
*/
hideLabel?: boolean;
/**
@@ -60,8 +60,7 @@ export interface ComboboxProps
*/
filteredOptions?: string[] | ComboboxOption[];
/**
* Optionally hide the label visually.
* Not recommended, but can be considered for e.g. search fields in the top menu.
* Hides the label and description visually while keeping them available to screen readers.
*/
hideLabel?: boolean;
/**
@@ -137,7 +137,12 @@ export const Readonly = () => <BasicCombobox readOnly />;
export const Disabled = () => <BasicCombobox disabled />;
export const ErrorStory = () => <BasicCombobox error="Du må velge et land." />;
export const ErrorStory = () => (
<VStack gap="space-16">
<BasicCombobox error="Du må velge et land." defaultSelectedOptions={[]} />
<BasicCombobox error="Ugyldig land." />
</VStack>
);
ErrorStory.storyName = "Error";
export const DefaultOpen = () => <BasicCombobox defaultOpen />;
@@ -462,33 +467,50 @@ export const Testing = () => {
);
};
function WithValidation() {
const [error, setError] = useState("Du må velge et land");
return (
<BasicCombobox
options={nordicCountries}
defaultSelectedOptions={[]}
error={error}
onToggleOption={(option) =>
setError(option.label === "Island" ? "" : "Du må velge Island")
}
/>
);
}
export const ScreenreaderTesting = () => (
<div>
<h2>Med grupper</h2>
<BasicCombobox options={groupedCountries} defaultSelectedOptions={[]} />
<h2>Uten grupper</h2>
<BasicCombobox options={nordicCountries} defaultSelectedOptions={[]} />
<h2>Label skjult visuelt</h2>
<BasicCombobox options={nordicCountries} hideLabel />
<h2>Med feilmelding</h2>
<WithValidation />
<h2>Readonly med verdi</h2>
<BasicCombobox readOnly />
<h2>Readonly uten verdi</h2>
<BasicCombobox readOnly defaultSelectedOptions={[]} />
<h2>Disabled</h2>
<BasicCombobox disabled />
</div>
);
/* TODO:
- Mobil: Vurder å ikke fokusere søkefelt automatisk.
- Mobil: Vurder å ikke ha virtuelt fokus i det hele tatt hvis man ikke har tastatur
Kan kanskje bruke media query, ev. ikke gi noe fokus før man begynner å trykke (som dagens CB. Sjekk hvordan SR takler at ingenting har virtuelt fokus.)
Sjekk om det er mulig å flytte virtuelt fokus uten tastatur (typ med skjermleser).
- Vurder om fokus skal låses til søkefelt (mest aktuelt ved multiselect).
- Åpne på pil ned (og ev. opp)?
- Vurder funksjoner fra gamle CB (ikke brukt: dropp, brukt lite: muliggjør med komposisjon, brukt mye: bygg inn støtte)
- allowNewValues (er dette ofte egentlig Autocomplete?)
- isLoading
- maxSelected
- Skal den hete noe annet enn Combobox?
- Navngivning: Vurder å dele opp i separate komponenter for single- og multiselect.
- Hvordan unngå CSS-konflikt med dagens CB?
- Vurder om Label og Description (og error?) skal være sub-komponenter eller ikke.
- Følge Combobox-pattern (mer)? Kan ikke følge det slavisk uansett.
Pil opp og ned velger
Ikke loop
- A11y-sjekk (skjermleser, zoom, høykontrast...)
- Beslutningsloggen?
- Test med skjermleser
Utfordringer med komposisjon:
- Vanskelig å bruke
- Utfordrende for oss å endre (ref FormSummary)
- Context er litt magisk/uoversiktlig
- Description: Hvordan skal root/trigger vite at aria-describedby skal settes?
Forslag: Tilby enkeltkomponent for de vanligste tilfellene, men også subkomponentene for fleksibilitet.
Kan ev. ha slot/render-props for enkelte ting.
Kan ev. bruke children for å kunne bytte ut/skreddersy innholdet i popup.
*/
*/
@@ -7,7 +7,7 @@ import {
} from "../root/ComboboxRoot";
interface ComboboxFieldProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
children?: React.ReactNode;
children?: string;
hasError?: boolean;
}
@@ -16,6 +16,8 @@ const ComboboxField = forwardRef<HTMLButtonElement, ComboboxFieldProps>(
const { options, selectedOptions, size } = useComboboxRootContext();
const labels = getLabels(selectedOptions, options);
const label =
children ?? (labels.length > 1 ? `${labels.length} valgt` : labels[0]);
return (
<div className="aksel-combobox2__field-container">
@@ -28,10 +30,10 @@ const ComboboxField = forwardRef<HTMLButtonElement, ComboboxFieldProps>(
"aksel-body-short",
`aksel-body-short--${size ?? "medium"}`,
)}
aria-label={label} // Without this, VO on Safari will announce the old label when focusing this button right after selecting a new option
{...rest}
>
{children ||
(labels.length > 1 ? `${labels.length} valgt` : labels[0])}
{label}
</button>
<ChevronDownIcon
className="aksel-combobox2__field-chevron"
@@ -11,11 +11,12 @@ type ComboboxFilterProps = Pick<
export const ComboboxFilter = (props: ComboboxFilterProps) => {
const { size } = useComboboxRootContext();
const { filterString, setFilterString } = useComboboxPopupContext();
const { filterString, setFilterString, virtuallyFocusedOptionValue } =
useComboboxPopupContext();
return (
<div className="aksel-combobox2__filter">
<Listbox.InputSlot>
<Listbox.InputSlot virtuallyFocusedOptionId={virtuallyFocusedOptionValue}>
<Search
value={filterString}
onChange={setFilterString}
@@ -21,7 +21,6 @@ export interface ComboboxPopupProps {
children: React.ReactNode;
}
// TODO: Bør den hete Dropdown eller Menu? (Og Overlay -> Popup?)
export const ComboboxPopup = ({ children }: ComboboxPopupProps) => {
const [virtuallyFocusedOptionValue, setVirtuallyFocusedOptionValue] =
useState("");
@@ -41,10 +40,10 @@ export const ComboboxPopup = ({ children }: ComboboxPopupProps) => {
if (canHover) {
setVirtuallyFocusedOptionValue(selectedOptionElm.dataset.id || "");
}
setTimeout(
() => selectedOptionElm.scrollIntoView({ block: "nearest" }),
0,
);
const scrollToSelectedOption = () =>
selectedOptionElm.scrollIntoView({ block: "nearest" });
setTimeout(scrollToSelectedOption, 0); // For when opened in Floating
queueMicrotask(scrollToSelectedOption); // For when opened in Dialog
} else {
const firstOptionElm =
ref.current?.querySelector<HTMLElement>('[role="option"]');
@@ -28,13 +28,13 @@ const ComboboxTrigger = ({
// Prevents "flash of focus" on trigger before focus is moved to input
if (!readOnly && !rootContext.open) event.preventDefault();
}}
/*onKeyDown={composeEventHandlers(onKeyDown, (event) => {
if (event.key === "ArrowDown") {
context.onOpenChange(true);
onKeyDown={(event) => {
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
rootContext.setOpen(true);
// Stop keydown from scrolling window
event.preventDefault();
}
})}*/
}}
data-readonly={readOnly}
aria-readonly={readOnly} // TODO: Vurder
disabled={rootContext.disabled}
@@ -61,6 +61,7 @@ export const Fieldset = forwardRef<HTMLFieldSetElement, FieldsetProps>(
readOnly,
inputDescriptionId,
readOnlyIconNeedsTitle,
disabled,
} = useFieldset(props, legendId);
const fieldset = useContext(FieldsetContext);
@@ -86,7 +87,7 @@ export const Fieldset = forwardRef<HTMLFieldSetElement, FieldsetProps>(
[fieldset?.errorId ?? ""]: !!fieldset?.error,
}),
size,
disabled: props.disabled ?? false,
disabled,
readOnly,
}}
>
@@ -15,6 +15,7 @@ export const useFieldset = (props: FieldsetProps, legendId: string) => {
return {
...formField,
disabled: formField.inputProps.disabled ?? false,
readOnlyIconNeedsTitle: checkboxGroupContext || radioGroupContext,
inputProps: {
...(checkboxGroupContext || radioGroupContext
@@ -39,7 +39,7 @@ export interface SearchProps
*/
label: React.ReactNode;
/**
* Shows label and description for screenreaders only.
* Hides the label and description visually while keeping them available to screen readers.
* @default true
*/
hideLabel?: boolean;
@@ -40,7 +40,7 @@ const SearchButton: SearchButtonType = forwardRef(
size={size}
variant={variant === "secondary" ? "secondary" : "primary"}
className={cl("aksel-search__button-search", className)}
disabled={context?.disabled ?? disabled}
disabled={context?.disabled || disabled}
onClick={composeEventHandlers(onClick, handleClick)}
icon={
<MagnifyingGlassIcon
@@ -23,7 +23,7 @@ export interface SelectProps
*/
label: React.ReactNode;
/**
* Shows label and description for screenreaders only.
* Hides the label and description visually while keeping them available to screen readers.
*/
hideLabel?: boolean;
}
@@ -15,7 +15,7 @@ export interface SwitchProps
*/
children: React.ReactNode;
/**
* If enabled shows the label and description for screenreaders only.
* Hides the label and description visually while keeping them available to screen readers.
*/
hideLabel?: boolean;
/**
@@ -79,7 +79,7 @@ export const Switch = forwardRef<HTMLInputElement, SwitchProps>(
`aksel-switch--${position}`,
{
"aksel-switch--loading": loading,
"aksel-switch--disabled": inputProps.disabled ?? loading,
"aksel-switch--disabled": inputProps.disabled || loading,
"aksel-switch--readonly": readOnly,
"aksel-switch--standalone": hideLabel,
},
@@ -88,7 +88,7 @@ export const Switch = forwardRef<HTMLInputElement, SwitchProps>(
<input
{...omit(rest, ["size", "readOnly"])}
{...omit(inputProps, ["aria-invalid", "aria-describedby"])}
disabled={inputProps.disabled ?? loading}
disabled={inputProps.disabled || loading}
checked={checkedProp}
defaultChecked={defaultChecked}
ref={ref}
@@ -36,7 +36,7 @@ export interface TextareaProps
*/
label: React.ReactNode;
/**
* If enabled shows the label and description for screenreaders only.
* Hides the label and description visually while keeping them available to screen readers.
*/
hideLabel?: boolean;
/**
@@ -81,6 +81,7 @@ export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
hasError,
size,
inputDescriptionId,
readOnly,
} = useFormField(props, "textarea");
const {
@@ -92,7 +93,6 @@ export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
resize,
UNSAFE_autoScrollbar,
i18n,
readOnly,
...rest
} = props;
@@ -147,7 +147,7 @@ export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
</BodyShort>
)}
<TextareaAutosize
{...omit(rest, ["error", "errorId", "size"])}
{...omit(rest, ["error", "errorId", "size", "readOnly"])}
{...inputProps}
onChange={composeEventHandlers(
props.onChange,
@@ -20,7 +20,7 @@ export interface TextFieldProps
*/
htmlSize?: number;
/**
* If enabled shows the label and description for screenreaders only
* Hides the label and description visually while keeping them available to screen readers
*/
hideLabel?: boolean;
/**
@@ -54,6 +54,7 @@ export const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
hasError,
size,
inputDescriptionId,
readOnly,
} = useFormField(props, "textField");
const {
@@ -63,7 +64,6 @@ export const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
htmlSize,
hideLabel = false,
type = "text",
readOnly,
...rest
} = props;
@@ -107,7 +107,7 @@ export const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
</BodyShort>
)}
<input
{...omit(rest, ["error", "errorId", "size"])}
{...omit(rest, ["error", "errorId", "size", "readOnly"])}
{...inputProps}
ref={ref}
type={type}
+4 -1
View File
@@ -53,7 +53,6 @@ export interface LookupProps
* </Lookup>
* ```
*/
export const Lookup = forwardRef<HTMLSpanElement, LookupProps>(
(
{
@@ -174,3 +173,7 @@ export const Lookup = forwardRef<HTMLSpanElement, LookupProps>(
);
export default Lookup;
/** @deprecated Lookup is no longer in preview. Import it from `@navikt/ds-react` instead. */
const LookupDeprecated = Lookup;
export { LookupDeprecated };
@@ -0,0 +1,2 @@
"use client";
export { LookupDeprecated as Lookup, type LookupProps } from "./Lookup";
+2 -2
View File
@@ -1,5 +1,5 @@
"use client";
export { DataGrid } from "./data-grid";
export { Lookup } from "./lookup";
export { Badge } from "./badge";
export { LookupDeprecated as Lookup } from "./lookup/Lookup";
export { BadgeDeprecated as Badge } from "./badge/Badge";
export { RadioCard, RadioCardGroup } from "./form/radio-card";
@@ -121,7 +121,7 @@ export const Default = () => {
<div>Selected: {selectedItem}</div>
<Listbox setVirtuallyFocusedOptionId={setVirtuallyFocusedOptionId}>
<Listbox.InputSlot>
<Listbox.InputSlot virtuallyFocusedOptionId={virtuallyFocusedOptionId}>
<Search
label="Velg noe"
hideLabel={false}
@@ -177,7 +177,7 @@ export const Optimized: StoryFn<OptimizedProps> = ({ count, highlight }) => {
<div>Selected: {selectedItem}</div>
<Listbox setVirtuallyFocusedOptionId={setVirtuallyFocusedOptionId}>
<Listbox.InputSlot>
<Listbox.InputSlot virtuallyFocusedOptionId={virtuallyFocusedOptionId}>
<Search
label="Velg noe"
hideLabel={false}
@@ -269,7 +269,9 @@ export const WithFloating = () => {
<Listbox setVirtuallyFocusedOptionId={setVirtuallyFocusedOptionId}>
<Floating.Anchor>
<Listbox.InputSlot>
<Listbox.InputSlot
virtuallyFocusedOptionId={virtuallyFocusedOptionId}
>
<Search
label="Velg noe"
hideLabel={false}
@@ -104,7 +104,7 @@ export const ArrowDown: StoryObj = {
expect(getVirtuallyFocusedValue(canvasElement)).toBe("last");
pressKey(canvasElement, "ArrowDown");
expect(getVirtuallyFocusedValue(canvasElement)).toBe("first");
expect(getVirtuallyFocusedValue(canvasElement)).toBe("last"); // No looping
},
};
@@ -127,7 +127,7 @@ export const ArrowUp: StoryObj = {
expect(getVirtuallyFocusedValue(canvasElement)).toBe("first");
pressKey(canvasElement, "ArrowUp");
expect(getVirtuallyFocusedValue(canvasElement)).toBe("last");
expect(getVirtuallyFocusedValue(canvasElement)).toBe("first"); // No looping
},
};
@@ -163,7 +163,7 @@ export const PageUpAndPageDownKeys: StoryObj = {
expect(getVirtuallyFocusedValue(canvasElement)).toBe("14"); // Last
pressKey(canvasElement, "PageDown");
expect(getVirtuallyFocusedValue(canvasElement)).toBe("14"); // No looping for PageUp/Down
expect(getVirtuallyFocusedValue(canvasElement)).toBe("14"); // No looping
pressKey(canvasElement, "PageUp");
expect(getVirtuallyFocusedValue(canvasElement)).toBe("9");
@@ -175,7 +175,7 @@ export const PageUpAndPageDownKeys: StoryObj = {
expect(getVirtuallyFocusedValue(canvasElement)).toBe("0");
pressKey(canvasElement, "PageUp");
expect(getVirtuallyFocusedValue(canvasElement)).toBe("0"); // No looping for PageUp/Down
expect(getVirtuallyFocusedValue(canvasElement)).toBe("0"); // No looping
},
};
@@ -0,0 +1,4 @@
const getOptionId = (prefix: string, id: string) =>
`${prefix}-${encodeURIComponent(id)}`;
export { getOptionId };
@@ -1,16 +1,26 @@
import React from "react";
import { Slot } from "../../slot/Slot";
import { getOptionId } from "../helpers/getOptionId";
import { useListboxContext } from "../root/Listbox.context";
interface ListboxInputSlotProps {
children: React.ReactElement;
virtuallyFocusedOptionId: string;
}
const ListboxInputSlot = ({ children }: ListboxInputSlotProps) => {
const { activeId } = useListboxContext();
const ListboxInputSlot = ({
children,
virtuallyFocusedOptionId,
}: ListboxInputSlotProps) => {
const { optionIdPrefix } = useListboxContext();
return (
<Slot
aria-activedescendant={activeId} // TODO: Check if it's a problem that this is set even when no option has virtual focus
// NB: Using a static ID and just change which option has the ID does not work in Safari with VoiceOver.
aria-activedescendant={
virtuallyFocusedOptionId
? getOptionId(optionIdPrefix, virtuallyFocusedOptionId)
: undefined
}
// @ts-expect-error - You are meant to use an <input>, but Slot doesn't know that.
autoComplete="off"
role="combobox"
@@ -1,6 +1,7 @@
import React from "react";
import { BodyShort } from "../../../../typography";
import { cl } from "../../../helpers";
import { getOptionId } from "../helpers/getOptionId";
import { useListboxContext } from "../root/Listbox.context";
export interface ListboxOptionProps extends Omit<
@@ -36,7 +37,7 @@ function ListboxOption({
className,
...rest
}: ListboxOptionProps) {
const { activeId, size } = useListboxContext();
const { optionIdPrefix, size } = useListboxContext();
return (
<BodyShort
as="div"
@@ -48,7 +49,7 @@ function ListboxOption({
tabIndex={-1}
data-virtual-focus={hasVirtualFocus}
data-id={id}
id={hasVirtualFocus ? activeId : undefined}
id={getOptionId(optionIdPrefix, id)}
>
<div className="aksel-listbox__option-inner">{children}</div>
</BodyShort>
@@ -3,10 +3,10 @@ import { createStrictContext } from "../../../helpers";
export const { Provider: ListboxProvider, useContext: useListboxContext } =
createStrictContext<{
/**
* ID for the option that currently has virtual focus.
* Set on the active option and referenced by the combobox's `aria-activedescendant`.
* Unique ID for the Listbox instance. Used as prefix for the IDs of the options,
* which in turn are referenced in `aria-activedescendant` in InputSlot.
*/
activeId: string;
optionIdPrefix: string;
/**
* Updates which option currently has virtual focus.
*/
@@ -34,8 +34,7 @@ const ListboxComponent = forwardRef<HTMLDivElement, ListboxProps>(
}: ListboxProps,
ref,
) => {
const id = useId();
const activeId = `aksel-listbox-${id}-active`;
const optionIdPrefix = useId();
const virtuallyFocusOption = (element: HTMLElement | null) => {
setVirtuallyFocusedOptionId(element?.dataset.id || "");
@@ -86,14 +85,9 @@ const ListboxComponent = forwardRef<HTMLDivElement, ListboxProps>(
return;
}
const nextOption = getNextElement(focusedOptionElm);
if (!nextOption) {
// TODO: Consider whether we want looping.
// If yes, consider if PageUp/PageDown should loop too.
// If no, remember to update tests.
virtuallyFocusOption(getFallback());
return;
if (nextOption) {
virtuallyFocusOption(nextOption);
}
virtuallyFocusOption(nextOption);
};
switch (event.key) {
@@ -133,7 +127,7 @@ const ListboxComponent = forwardRef<HTMLDivElement, ListboxProps>(
{...rest}
>
<ListboxProvider
activeId={activeId}
optionIdPrefix={optionIdPrefix}
setVirtuallyFocusedOptionId={setVirtuallyFocusedOptionId}
size={size}
>
@@ -49,7 +49,7 @@ export interface DateInputProps
*/
label: React.ReactNode;
/**
* Shows label and description for screen readers only
* Hides the label and description visually while keeping them available to screen readers
* @default false
*/
hideLabel?: boolean;
+6
View File
@@ -1,5 +1,11 @@
# @navikt/ds-tailwind
## 8.19.2
## 8.19.1
## 8.19.0
## 8.18.0
### Patch Changes
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-tailwind",
"version": "8.18.0",
"version": "8.19.2",
"description": "Tailwind config based on @navikt/ds-tokens",
"author": "Aksel | Nav designsystem team",
"keywords": [
@@ -28,7 +28,7 @@
},
"homepage": "https://aksel.nav.no/grunnleggende/kode/tailwind/",
"devDependencies": {
"@navikt/ds-tokens": "^8.18.0",
"@navikt/ds-tokens": "^8.19.2",
"@types/node": "^24.10.9",
"tailwindcss": "^3.3.3",
"tsx": "^4.23.15",
+6
View File
@@ -1,5 +1,11 @@
# @navikt/ds-tokens
## 8.19.2
## 8.19.1
## 8.19.0
## 8.18.0
## 8.17.2
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-tokens",
"version": "8.18.0",
"version": "8.19.2",
"description": "Design-tokens for Nav designsystem",
"author": "Aksel | Nav designsystem team",
"keywords": [
+44
View File
@@ -1,5 +1,49 @@
# Changelog
## 8.19.2
### @navikt/ds-css
- Search: Icon more visible in forced-colors mode ([#5386](https://github.com/navikt/aksel/pull/5386))
### @navikt/ds-react
- TextField: Now inherits `readOnly` from Fieldset. No longer gets read-only styling when `disabled` and `readOnly`. ([#5328](https://github.com/navikt/aksel/pull/5328))
- Textarea: Now inherits `readOnly` from Fieldset. No longer gets read-only styling when `disabled` and `readOnly`. ([#5328](https://github.com/navikt/aksel/pull/5328))
- Fieldset: Nested Fieldsets now inherits the parent's `disabled` state. ([#5328](https://github.com/navikt/aksel/pull/5328))
- Button: `loading={true}` and `disabled={false}` now disable the element. ([#5328](https://github.com/navikt/aksel/pull/5328))
- Switch: `loading={true}` and `disabled={false}` now disable the element. ([#5328](https://github.com/navikt/aksel/pull/5328))
- Search.Button: `loading={true}` and `disabled={false}` now disable SearchButton. ([#5328](https://github.com/navikt/aksel/pull/5328))
- Badge: No longer sets aria-hidden internally automatically. ([#5397](https://github.com/navikt/aksel/pull/5397))
## 8.19.1
### @navikt/ds-css
- Accordion: `@media print` now auto-opens every Accordion ([#5363](https://github.com/navikt/aksel/pull/5363))
- ExpansionCard: `@media print` now auto-opens every ExpansionCard ([#5363](https://github.com/navikt/aksel/pull/5363))
- ReadMore: `@media print` now auto-opens every ReadMore ([#5363](https://github.com/navikt/aksel/pull/5363))
## 8.19.0
### @navikt/aksel
- CLI: Update clipboardy to v5.3.2 ([#5357](https://github.com/navikt/aksel/pull/5357))
- CLI: Update figlet to 1.11.3 ([#5360](https://github.com/navikt/aksel/pull/5360))
### @navikt/aksel-icons
- Icons: Added new icon MenuHamburgerMagnifyingGlass ([#5368](https://github.com/navikt/aksel/pull/5368))
## 8.18.0
### @navikt/aksel
+6 -6
View File
@@ -11,11 +11,11 @@
"sanity:update": "yarn workspace website update:sanity"
},
"dependencies": {
"@navikt/aksel": "^8.18.0",
"@navikt/aksel-icons": "^8.18.0",
"@navikt/ds-css": "^8.18.0",
"@navikt/ds-react": "^8.18.0",
"@navikt/ds-tailwind": "^8.18.0",
"@navikt/ds-tokens": "^8.18.0"
"@navikt/aksel": "^8.19.2",
"@navikt/aksel-icons": "^8.19.2",
"@navikt/ds-css": "^8.19.2",
"@navikt/ds-react": "^8.19.2",
"@navikt/ds-tailwind": "^8.19.2",
"@navikt/ds-tokens": "^8.19.2"
}
}
@@ -6669,133 +6669,6 @@
}
}
},
{
"name": "ds_endringslogg_artikkel",
"type": "document",
"attributes": {
"_id": {
"type": "objectAttribute",
"value": {
"type": "string"
}
},
"_type": {
"type": "objectAttribute",
"value": {
"type": "string",
"value": "ds_endringslogg_artikkel"
}
},
"_createdAt": {
"type": "objectAttribute",
"value": {
"type": "string"
}
},
"_updatedAt": {
"type": "objectAttribute",
"value": {
"type": "string"
}
},
"_rev": {
"type": "objectAttribute",
"value": {
"type": "string"
}
},
"heading": {
"type": "objectAttribute",
"value": {
"type": "string"
},
"optional": true
},
"slug": {
"type": "objectAttribute",
"value": {
"type": "inline",
"name": "slug"
},
"optional": true
},
"endringsdato": {
"type": "objectAttribute",
"value": {
"type": "string"
},
"optional": true
},
"endringstype": {
"type": "objectAttribute",
"value": {
"type": "union",
"of": [
{
"type": "string",
"value": "design"
},
{
"type": "string",
"value": "dokumentasjon"
},
{
"type": "string",
"value": "kode"
}
]
},
"optional": true
},
"content": {
"type": "objectAttribute",
"value": {
"type": "inline",
"name": "riktekst_grunnleggende"
},
"optional": true
},
"visMer": {
"type": "objectAttribute",
"value": {
"type": "boolean"
},
"optional": true
},
"artikler": {
"type": "objectAttribute",
"value": {
"type": "array",
"of": {
"type": "union",
"of": [
{
"type": "inline",
"name": "ds_artikkel.reference"
},
{
"type": "inline",
"name": "komponent_artikkel.reference"
},
{
"type": "inline",
"name": "templates_artikkel.reference"
}
]
}
},
"optional": true
},
"seo": {
"type": "objectAttribute",
"value": {
"type": "inline",
"name": "seo"
},
"optional": true
}
}
},
{
"name": "grunnleggende_landingsside",
"type": "document",
@@ -7304,6 +7177,278 @@
}
}
},
{
"type": "type",
"name": "ds_endringslogg_artikkel.reference",
"value": {
"type": "object",
"attributes": {
"_ref": {
"type": "objectAttribute",
"value": {
"type": "string"
}
},
"_type": {
"type": "objectAttribute",
"value": {
"type": "string",
"value": "reference"
}
},
"_weak": {
"type": "objectAttribute",
"value": {
"type": "boolean"
},
"optional": true
}
},
"dereferencesTo": "ds_endringslogg_artikkel"
}
},
{
"name": "newsletter_todo",
"type": "document",
"attributes": {
"_id": {
"type": "objectAttribute",
"value": {
"type": "string"
}
},
"_type": {
"type": "objectAttribute",
"value": {
"type": "string",
"value": "newsletter_todo"
}
},
"_createdAt": {
"type": "objectAttribute",
"value": {
"type": "string"
}
},
"_updatedAt": {
"type": "objectAttribute",
"value": {
"type": "string"
}
},
"_rev": {
"type": "objectAttribute",
"value": {
"type": "string"
}
},
"todoList": {
"type": "objectAttribute",
"value": {
"type": "array",
"of": {
"type": "object",
"attributes": {
"articleRef": {
"type": "objectAttribute",
"value": {
"type": "union",
"of": [
{
"type": "inline",
"name": "komponent_artikkel.reference"
},
{
"type": "inline",
"name": "ds_artikkel.reference"
},
{
"type": "inline",
"name": "aksel_artikkel.reference"
},
{
"type": "inline",
"name": "aksel_blogg.reference"
},
{
"type": "inline",
"name": "aksel_prinsipp.reference"
},
{
"type": "inline",
"name": "aksel_standalone.reference"
},
{
"type": "inline",
"name": "templates_artikkel.reference"
},
{
"type": "inline",
"name": "ds_endringslogg_artikkel.reference"
}
]
},
"optional": true
},
"dateAdded": {
"type": "objectAttribute",
"value": {
"type": "string"
},
"optional": true
},
"_type": {
"type": "objectAttribute",
"value": {
"type": "string",
"value": "inline"
}
}
},
"rest": {
"type": "object",
"attributes": {
"_key": {
"type": "objectAttribute",
"value": {
"type": "string"
}
}
}
}
}
},
"optional": true
}
}
},
{
"name": "ds_endringslogg_artikkel",
"type": "document",
"attributes": {
"_id": {
"type": "objectAttribute",
"value": {
"type": "string"
}
},
"_type": {
"type": "objectAttribute",
"value": {
"type": "string",
"value": "ds_endringslogg_artikkel"
}
},
"_createdAt": {
"type": "objectAttribute",
"value": {
"type": "string"
}
},
"_updatedAt": {
"type": "objectAttribute",
"value": {
"type": "string"
}
},
"_rev": {
"type": "objectAttribute",
"value": {
"type": "string"
}
},
"heading": {
"type": "objectAttribute",
"value": {
"type": "string"
},
"optional": true
},
"slug": {
"type": "objectAttribute",
"value": {
"type": "inline",
"name": "slug"
},
"optional": true
},
"endringsdato": {
"type": "objectAttribute",
"value": {
"type": "string"
},
"optional": true
},
"endringstype": {
"type": "objectAttribute",
"value": {
"type": "union",
"of": [
{
"type": "string",
"value": "design"
},
{
"type": "string",
"value": "dokumentasjon"
},
{
"type": "string",
"value": "kode"
}
]
},
"optional": true
},
"content": {
"type": "objectAttribute",
"value": {
"type": "inline",
"name": "riktekst_grunnleggende"
},
"optional": true
},
"visMer": {
"type": "objectAttribute",
"value": {
"type": "boolean"
},
"optional": true
},
"artikler": {
"type": "objectAttribute",
"value": {
"type": "array",
"of": {
"type": "union",
"of": [
{
"type": "inline",
"name": "ds_artikkel.reference"
},
{
"type": "inline",
"name": "komponent_artikkel.reference"
},
{
"type": "inline",
"name": "templates_artikkel.reference"
}
]
}
},
"optional": true
},
"seo": {
"type": "objectAttribute",
"value": {
"type": "inline",
"name": "seo"
},
"optional": true
}
}
},
{
"name": "designsystemStatistics",
"type": "document",
@@ -0,0 +1,57 @@
import { createClient } from "@sanity/client";
import { documentEventHandler } from "@sanity/functions";
import { format } from "date-fns/format";
import { SANITY_API_VERSION } from "../../sanity.env";
const NEWSLETTER_TODO_ID = "newsletter_todo";
const handler = documentEventHandler(async ({ context, event }) => {
const client = createClient({
...context.clientOptions,
apiVersion: SANITY_API_VERSION,
useCdn: false,
});
const { data } = event;
const { local } = context; // local is true when running locally
try {
const alreadyAdded = await client.fetch<boolean>(
`count(*[_id == $todoId][0].todoList[articleRef._ref == $ref]) > 0`,
{ todoId: NEWSLETTER_TODO_ID, ref: data._id },
);
if (alreadyAdded) {
console.info(
`Document (${data._id}) already in ${NEWSLETTER_TODO_ID}, skipping`,
);
return;
}
await client
.transaction()
.createIfNotExists({
_id: NEWSLETTER_TODO_ID,
_type: NEWSLETTER_TODO_ID,
})
.patch(NEWSLETTER_TODO_ID, (patch) =>
patch.setIfMissing({ todoList: [] }).append("todoList", [
{
_type: "inline",
articleRef: { _type: "reference", _ref: data._id, _weak: true },
dateAdded: format(new Date(), "yyyy-MM-dd"),
},
]),
)
.commit({ autoGenerateArrayKeys: true, dryRun: local });
console.info(
local
? `(LOCAL TEST MODE - Content Lake not updated) Added document (${data._id}) to ${NEWSLETTER_TODO_ID}`
: `Added document (${data._id}) to ${NEWSLETTER_TODO_ID}`,
);
} catch (error) {
console.error(`Error adding document to ${NEWSLETTER_TODO_ID}:`, error);
}
});
export { handler };
+3 -3
View File
@@ -23,14 +23,14 @@
"@sanity/locale-nb-no": "1.1.39",
"@sanity/preview-url-secret": "4.1.5",
"@sanity/table": "3.1.16",
"@sanity/vision": "6.16.0",
"@sanity/vision": "6.17.0",
"date-fns": "^4.0.0",
"lodash": "4.18.1",
"react": "19.3.0",
"react-dom": "19.3.0",
"react-rx": "^5.1.1",
"rxjs": "^7.8.2",
"sanity": "6.16.0",
"sanity": "6.17.0",
"sanity-plugin-iframe-pane": "5.0.35",
"sanity-plugin-media": "6.1.8",
"sanity-plugin-references": "1.2.0",
@@ -41,7 +41,7 @@
"@sanity/blueprints": "0.24.0",
"@sanity/export": "7.0.3",
"@sanity/functions": "1.7.2",
"@sanity/schema": "6.16.0",
"@sanity/schema": "6.17.0",
"@types/lodash": "4.17.25",
"@types/react": "19.2.14",
"tsx": "^4.23.15",
@@ -45,6 +45,15 @@ export default defineBlueprint({
},
src: "./functions/unpublish",
}),
defineDocumentFunction({
name: "newsletter-todo",
event: {
on: ["create"],
filter: `_type in ${allPublishedAtDocuments} || (_type == "ds_endringslogg_artikkel" && endringstype == "dokumentasjon")`,
projection: "{_id}",
},
src: "./functions/newsletter-todo",
}),
defineSyncTagInvalidateFunction({
name: "invalidate-tags",
event: {
@@ -52,6 +52,7 @@ export const workspaceConfig = defineConfig([
schema,
document: {
newDocumentOptions: newDocumentsCreator,
singletons: ["newsletter_todo"],
},
scheduledDrafts: {
enabled: false,
@@ -98,6 +99,7 @@ export const workspaceConfig = defineConfig([
schema,
document: {
newDocumentOptions: newDocumentsCreator,
singletons: ["newsletter_todo"],
},
scheduledDrafts: {
enabled: false,
@@ -0,0 +1,54 @@
import { defineArrayMember, defineField, defineType } from "sanity";
import { SchemaConfig } from "../../schema.config";
export const NewsletterTodo = defineType({
title: "Nyhetsbrev Todo",
name: "newsletter_todo",
type: "document",
liveEdit: true,
fields: [
defineField({
title: "TODO",
name: "todoList",
type: "array",
options: {
sortable: false,
},
of: [
defineArrayMember({
type: "object",
name: "inline",
fields: [
defineField({
name: "articleRef",
title: "Artikkel",
type: "reference",
weak: true,
to: [
...SchemaConfig.allArticleDocuments.map((type) => ({ type })),
{ type: "ds_endringslogg_artikkel" },
],
options: {
disableNew: true,
},
readOnly: true,
}),
defineField({ type: "date", name: "dateAdded", readOnly: true }),
],
}),
],
}),
],
preview: {
select: {
todoList: "todoList",
},
prepare: ({ todoList }) => {
return {
title: "Nyhetsbrev TODO",
subtitle: `Totalt: ${todoList?.length || 0}`,
};
},
},
});
@@ -29,3 +29,4 @@ export * from "./admin/redirects";
export * from "./admin/articleViews";
export * from "./admin/cookies";
export * from "./admin/ds-stats";
export * from "./admin/newsletter-todo";
@@ -18,7 +18,6 @@ export const KomponentIntro = defineType({
name: "brukes_til",
title: "Egnet til:",
of: [{ type: "string" }],
validation: (Rule) => Rule.required().min(1),
}),
defineField({
type: "array",
@@ -13,6 +13,7 @@ export const schema: SchemaPluginOptions = {
document.ArticleViews,
document.CookieTracker,
document.DesignsystemStatistics,
document.NewsletterTodo,
/* Komponentsider */
document.KodeEksempelDoc,
@@ -32,7 +32,7 @@ export function adminStructure(S: StructureBuilder) {
.schemaType(`aksel_ds_forside`)
.icon(ImageIcon)
.id(`aksel_ds_forside_dokument`),
S.listItem().singleton("newsletter_todo"),
S.listItem()
.title("Standalone-sider")
.child(
@@ -7,14 +7,16 @@ const eyebrowMap = {
templates_artikkel: "Maler",
};
type DesignsystemetEyebrowProps =
| {
type?: "komponent_artikkel" | "ds_artikkel" | "templates_artikkel";
text?: never;
}
| { type?: never; text: string };
type DesignsystemetEyebrowProps = {
type?: "komponent_artikkel" | "ds_artikkel" | "templates_artikkel";
text?: string;
};
function DesignsystemetEyebrow({ type, text }: DesignsystemetEyebrowProps) {
if (text) {
return <BodyShort className={styles.pageHeaderEyebrow}>{text}</BodyShort>;
}
if (type) {
return (
<BodyShort className={styles.pageHeaderEyebrow}>
@@ -23,10 +25,6 @@ function DesignsystemetEyebrow({ type, text }: DesignsystemetEyebrowProps) {
);
}
if (text) {
return <BodyShort className={styles.pageHeaderEyebrow}>{text}</BodyShort>;
}
return null;
}
@@ -8,18 +8,20 @@ function DesignsystemetKomponentIntro({
}: {
data: KOMPONENT_BY_SLUG_QUERY_RESULT;
}) {
const useFor = data?.intro?.brukes_til;
const avoidUseFor = data?.intro?.brukes_ikke_til;
const useFor = data?.intro?.brukes_til ?? [];
const avoidUseFor = data?.intro?.brukes_ikke_til ?? [];
const internal = data?.status?.internal;
if (!useFor && !avoidUseFor) {
const showUseFor = internal || useFor.length > 0;
const showAvoidUseFor = avoidUseFor.length > 0;
if (!showUseFor && !showAvoidUseFor) {
return null;
}
const internal = data?.status?.internal;
return (
<VStack gap="space-24" marginBlock="space-0 space-28">
{useFor && (
{showUseFor && (
<div>
<Heading size="small" level="2" spacing>
Egnet til:
@@ -29,7 +31,7 @@ function DesignsystemetKomponentIntro({
{internal && (
<WebsiteListItem icon>Bruk på interne flater</WebsiteListItem>
)}
{data?.intro?.brukes_til?.map((x) => (
{useFor.map((x) => (
<WebsiteListItem icon key={x}>
<MarkdownText>{x}</MarkdownText>
</WebsiteListItem>
@@ -37,7 +39,7 @@ function DesignsystemetKomponentIntro({
</WebsiteList>
</div>
)}
{avoidUseFor && (
{showAvoidUseFor && (
<div>
<Heading size="small" level="2" spacing>
Uegnet til:
@@ -1,4 +1,5 @@
import type { PortableTextBlock } from "next-sanity";
import { SchemaConfig } from "aksel-sanity-studio/schema";
import { type PortableTextBlock, stegaClean } from "next-sanity";
import { Box, Heading } from "@navikt/ds-react";
import { DesignsystemetEyebrow } from "@/app/(routes)/(designsystemet)/_ui/Designsystemet.eyebrow";
import type {
@@ -40,17 +41,40 @@ type DesignsystemetPageT = {
| MONSTER_MALER_BY_SLUG_QUERY_RESULT;
};
const typeToName = (
type?: "komponent_artikkel" | "ds_artikkel" | "templates_artikkel",
) => {
switch (stegaClean(type)) {
case "komponent_artikkel":
return "komponenter";
case "ds_artikkel":
return "grunnleggende";
case "templates_artikkel":
return "templates";
default:
return undefined;
}
};
async function DesignsystemetPageHeader({ data }: DesignsystemetPageT) {
const statusTag = getStatusTag(data?.status?.tag);
const isComponentPage = data?._type === "komponent_artikkel";
const type = typeToName(data?._type);
const kategori = stegaClean(data?.kategori);
const eyebrowText = type
? SchemaConfig.categoryLookup(type).find(
(category) => category.value === kategori,
)
: undefined;
return (
<Box
marginBlock={`space-0 ${isComponentPage ? "space-28" : "space-0"}`}
data-color={statusTag?.colorRole}
>
<DesignsystemetEyebrow type={data?._type} />
<DesignsystemetEyebrow type={data?._type} text={eyebrowText?.title} />
<Box
marginBlock={`space-0 ${isComponentPage ? "space-8" : "space-0"}`}
asChild
@@ -1,4 +1,4 @@
import { BodyShort, HGrid, Heading, VStack } from "@navikt/ds-react";
import { BodyShort, Box, HGrid, Heading, VStack } from "@navikt/ds-react";
import { ContactCard } from "@/app/(routes)/(designsystemet)/_ui/DesignsystemetContactCard";
import type { KOMPONENT_BY_SLUG_QUERY_RESULT } from "@/app/_sanity/query-types";
import { Avatar, avatarUrl } from "@/app/_ui/avatar/Avatar";
@@ -29,25 +29,29 @@ async function DesignsystemetPageFooter({
return (
<VStack gap="space-8">
{contact && (
<>
<Heading level="2" size="small">
Forvalter
</Heading>
<Avatar
type={humanizeRedaksjonType(contact.type)}
name={contact.title ?? ""}
key={contact.title}
imageSrc={avatarUrl(contact.avatar_id?.current ?? "missing")}
showName
/>
</>
)}
{updateDate && (
<BodyShort size="small" as="span" textColor="subtle">
{`Artikkel oppdatert ${updateDate}`}
</BodyShort>
)}
<Box marginBlock="space-28 space-0">
{contact && (
<VStack gap="space-8">
<Heading level="2" size="small">
Forvalter
</Heading>
<Avatar
type={humanizeRedaksjonType(contact.type)}
name={contact.title ?? ""}
key={contact.title}
imageSrc={avatarUrl(contact.avatar_id?.current ?? "missing")}
showName
/>
</VStack>
)}
{updateDate && (
<Box marginBlock="space-12 space-0">
<BodyShort size="small" as="span" textColor="subtle">
{`Artikkel oppdatert ${updateDate}`}
</BodyShort>
</Box>
)}
</Box>
<HGrid gap="space-24" columns={{ md: 2 }} data-block-margin="space-28">
{href && (
@@ -21,6 +21,7 @@ import type {
} from "@/app/_sanity/query-types";
import { urlForImage } from "@/app/_sanity/utils";
import { NextLink } from "@/app/_ui/next-link/NextLink";
import { UmamiLink } from "@/app/_ui/umami/UmamiLink";
import styles from "./Hero.module.css";
type GpIntroHeroProps = {
@@ -28,6 +29,9 @@ type GpIntroHeroProps = {
description?: string;
image?: NonNullable<GOD_PRAKSIS_TEMA_BY_SLUG_QUERY_RESULT>["pictogram"];
isCollapsible?: boolean;
overrides?: {
isNavnoTema?: boolean;
};
} & DynamicFetchOptions;
async function GodPraksisIntroHero({
@@ -37,6 +41,7 @@ async function GodPraksisIntroHero({
isCollapsible = false,
perspective,
stega,
overrides,
}: GpIntroHeroProps) {
const imageUrl = urlForImage(image)?.url();
@@ -74,6 +79,19 @@ async function GodPraksisIntroHero({
className={styles.godPraksisHeroDescription}
>
{description}
{overrides?.isNavnoTema && (
<>
{" "}
<UmamiLink
inlineText
href="https://aksel.nav.no/komponenter/dekoratoren/dekoratoren"
data-color="neutral"
lenkegruppe="tema-hero-custom"
>
Informasjon om dekoratøren finner du i designsystemet.
</UmamiLink>
</>
)}
</BodyLong>
)}
{isCollapsible ? (
@@ -272,6 +272,14 @@ async function DynamicTemaPage(props: Props) {
}
}
/**
* Context:
* nav.no wanted to link to new dekoratoren article in /komponenter from their god-praksis page
*/
const isNavnoTema = temaPage._id.includes(
"0ca17e98-86a9-4e9e-abc1-af53043a21c3",
);
return (
<div>
<GodPraksisIntroHero
@@ -281,6 +289,9 @@ async function DynamicTemaPage(props: Props) {
isCollapsible
perspective={perspective}
stega={stega}
overrides={{
isNavnoTema,
}}
/>
<VStack
gap="space-48"
+3 -7
View File
@@ -1,9 +1,7 @@
import { draftMode } from "next/headers";
import { SanityLive } from "@/app/_sanity/live";
import { ConsentBanner } from "@/app/_ui/consent-banner/ConsentBanner";
import { CookieConsentProvider } from "@/app/_ui/cookie-consent/CookieConsent.Provider";
import { DraftOverlay } from "@/app/_ui/draft-overlay/DraftOverlay";
import { Umami } from "@/app/_ui/umami/Umami";
import { SiteTracking } from "@/app/_ui/umami/SiteTracking";
export default async function IndexLayout({
children,
@@ -14,12 +12,10 @@ export default async function IndexLayout({
return (
<>
<CookieConsentProvider>
<Umami isDraftMode={isDraftMode} />
<ConsentBanner />
<SiteTracking isDraftMode={isDraftMode}>
{children}
{isDraftMode && <DraftOverlay />}
</CookieConsentProvider>
</SiteTracking>
{isDraftMode && <SanityLive includeDrafts onWelcome={false} />}
</>
);
+52 -21
View File
@@ -1181,26 +1181,6 @@ export type Templates_landingsside = {
seo?: Seo;
};
export type Ds_endringslogg_artikkel = {
_id: string;
_type: "ds_endringslogg_artikkel";
_createdAt: string;
_updatedAt: string;
_rev: string;
heading?: string;
slug?: Slug;
endringsdato?: string;
endringstype?: "design" | "dokumentasjon" | "kode";
content?: Riktekst_grunnleggende;
visMer?: boolean;
artikler?: ArrayOf<
| Ds_artikkelReference
| Komponent_artikkelReference
| Templates_artikkelReference
>;
seo?: Seo;
};
export type Grunnleggende_landingsside = {
_id: string;
_type: "grunnleggende_landingsside";
@@ -1282,6 +1262,55 @@ export type Kode_eksempler_fil = {
};
};
export type Ds_endringslogg_artikkelReference = {
_ref: string;
_type: "reference";
_weak?: boolean;
[internalGroqTypeReferenceTo]?: "ds_endringslogg_artikkel";
};
export type Newsletter_todo = {
_id: string;
_type: "newsletter_todo";
_createdAt: string;
_updatedAt: string;
_rev: string;
todoList?: Array<{
articleRef?:
| Komponent_artikkelReference
| Ds_artikkelReference
| Aksel_artikkelReference
| Aksel_bloggReference
| Aksel_prinsippReference
| Aksel_standaloneReference
| Templates_artikkelReference
| Ds_endringslogg_artikkelReference;
dateAdded?: string;
_type: "inline";
_key: string;
}>;
};
export type Ds_endringslogg_artikkel = {
_id: string;
_type: "ds_endringslogg_artikkel";
_createdAt: string;
_updatedAt: string;
_rev: string;
heading?: string;
slug?: Slug;
endringsdato?: string;
endringstype?: "design" | "dokumentasjon" | "kode";
content?: Riktekst_grunnleggende;
visMer?: boolean;
artikler?: ArrayOf<
| Ds_artikkelReference
| Komponent_artikkelReference
| Templates_artikkelReference
>;
seo?: Seo;
};
export type DesignsystemStatistics = {
_id: string;
_type: "designsystemStatistics";
@@ -2059,11 +2088,13 @@ export type AllSanitySchemaTypes =
| SanityImageHotspot
| Godpraksis_landingsside
| Templates_landingsside
| Ds_endringslogg_artikkel
| Grunnleggende_landingsside
| Komponenter_landingsside
| Token_kategori
| Kode_eksempler_fil
| Ds_endringslogg_artikkelReference
| Newsletter_todo
| Ds_endringslogg_artikkel
| DesignsystemStatistics
| Cookie_tracker
| Article_views
@@ -16,6 +16,7 @@ function DoDont(props: ExtractPortableComponentProps<"do_dont">) {
return (
<HGrid
data-text-prose
data-block-margin="space-28"
columns="repeat(auto-fill, minmax(18rem, 1fr))"
gap="space-24"
@@ -0,0 +1,27 @@
"use client";
import { ConsentBanner } from "@/app/_ui/consent-banner/ConsentBanner";
import { CookieConsentProvider } from "@/app/_ui/cookie-consent/CookieConsent.Provider";
import { Umami } from "@/app/_ui/umami/Umami";
/**
* Cookie consent, consent banner and Umami.
* Root-level `not-found.tsx` and `error.tsx` render outside `(routes)/layout.tsx`, so they need this too.
*/
function SiteTracking({
isDraftMode = false,
children,
}: {
isDraftMode?: boolean;
children: React.ReactNode;
}) {
return (
<CookieConsentProvider>
<Umami isDraftMode={isDraftMode} />
<ConsentBanner />
{children}
</CookieConsentProvider>
);
}
export { SiteTracking };
@@ -1,15 +1,16 @@
"use client";
import { useEffect } from "react";
import { umamiTrack } from "@/app/_ui/umami/Umami.track";
import { umamiTrackWhenReady } from "@/app/_ui/umami/Umami.track";
/**
* Sometimes we need to track that a spesific page was visited, but since Umami is client-side only, we can't use the `page.tsx` file to track it.
*/
function UmamiNotFoundPageLog() {
useEffect(() => {
umamiTrack("404", { url: window.location.pathname });
}, []);
useEffect(
() => umamiTrackWhenReady("404", { url: window.location.pathname }),
[],
);
return null;
}
@@ -14,4 +14,25 @@ function umamiTrack(event: string, properties?: Record<string, unknown>): void {
}
}
export { umamiTrack };
const UMAMI_READY_EVENT = "aksel:umami-ready";
/**
* Tracks the event once the Umami script has loaded.
* Use on mount, where the script may not have loaded yet (e.g. direct visits).
* @returns Cleanup function that cancels a pending track.
*/
function umamiTrackWhenReady(
event: string,
properties?: Record<string, unknown>,
): () => void {
if (window.umami) {
umamiTrack(event, properties);
return () => {};
}
const handler = () => umamiTrack(event, properties);
window.addEventListener(UMAMI_READY_EVENT, handler, { once: true });
return () => window.removeEventListener(UMAMI_READY_EVENT, handler);
}
export { UMAMI_READY_EVENT, umamiTrack, umamiTrackWhenReady };
@@ -3,6 +3,7 @@
import Script from "next/script";
import { useMemo } from "react";
import { useCookieConsent } from "@/app/_ui/cookie-consent/CookieConsent.Provider";
import { UMAMI_READY_EVENT } from "@/app/_ui/umami/Umami.track";
import { IS_NEXT_SERVERSIDE } from "@/ui-utils/is-server";
const trackingId = process.env.UMAMI_TRACKING_ID;
@@ -44,6 +45,7 @@ function Umami({ isDraftMode = false }: { isDraftMode?: boolean }) {
data-website-id={trackingId}
data-tag={umamiTag}
data-exclude-search="true"
onLoad={() => window.dispatchEvent(new Event(UMAMI_READY_EVENT))}
/>
);
}
@@ -0,0 +1,50 @@
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
import { UMAMI_READY_EVENT, umamiTrackWhenReady } from "../Umami.track";
type FakeWindow = EventTarget & {
umami?: { track: ReturnType<typeof vi.fn> };
};
describe("umamiTrackWhenReady", () => {
let fakeWindow: FakeWindow;
beforeEach(() => {
fakeWindow = new EventTarget();
vi.stubGlobal("window", fakeWindow);
});
afterEach(() => {
vi.unstubAllGlobals();
});
test("tracks immediately when Umami is loaded", () => {
fakeWindow.umami = { track: vi.fn() };
umamiTrackWhenReady("404", { url: "/a" });
expect(fakeWindow.umami.track).toHaveBeenCalledWith("404", { url: "/a" });
});
test("tracks once Umami signals ready", () => {
umamiTrackWhenReady("404", { url: "/a" });
const track = vi.fn();
fakeWindow.umami = { track };
fakeWindow.dispatchEvent(new Event(UMAMI_READY_EVENT));
fakeWindow.dispatchEvent(new Event(UMAMI_READY_EVENT));
expect(track).toHaveBeenCalledTimes(1);
expect(track).toHaveBeenCalledWith("404", { url: "/a" });
});
test("cleanup cancels a pending track", () => {
const cleanup = umamiTrackWhenReady("404", { url: "/a" });
cleanup();
const track = vi.fn();
fakeWindow.umami = { track };
fakeWindow.dispatchEvent(new Event(UMAMI_READY_EVENT));
expect(track).not.toHaveBeenCalled();
});
});
+12 -7
View File
@@ -4,7 +4,8 @@ import { type ReactElement, useEffect } from "react";
import { Page } from "@navikt/ds-react/Page";
import { logger } from "@navikt/next-logger";
import GenericErrorPage from "@/app/_ui/generic-error-page";
import { umamiTrack } from "@/app/_ui/umami/Umami.track";
import { SiteTracking } from "@/app/_ui/umami/SiteTracking";
import { umamiTrackWhenReady } from "@/app/_ui/umami/Umami.track";
export default function ErrorPage({
error,
@@ -15,13 +16,17 @@ export default function ErrorPage({
logger.error(error);
}, [error]);
useEffect(() => {
umamiTrack("client-error", { url: window.location.pathname });
}, []);
useEffect(
() =>
umamiTrackWhenReady("client-error", { url: window.location.pathname }),
[],
);
return (
<Page className="vk-error">
<GenericErrorPage />
</Page>
<SiteTracking>
<Page className="vk-error">
<GenericErrorPage />
</Page>
</SiteTracking>
);
}
+33
View File
@@ -0,0 +1,33 @@
"use client";
import { type ReactElement, useEffect } from "react";
import { Page } from "@navikt/ds-react/Page";
import { logger } from "@navikt/next-logger";
import GenericErrorPage from "@/app/_ui/generic-error-page";
import "./globals.css";
/**
* Replaces the root layout when it throws, so it must render its own <html> and <body>.
* Avoids ThemeProvider/tracking providers, since those may be what failed.
*/
export default function GlobalError({
error,
}: {
error: Error & { digest?: string };
}): ReactElement {
useEffect(() => {
logger.error(error);
}, [error]);
return (
<html lang="no">
<body>
<title>Noe gikk galt - Aksel.nav.no</title>
<Page className="vk-error">
<GenericErrorPage />
</Page>
</body>
</html>
);
}
+63 -51
View File
@@ -4,69 +4,81 @@ import { Page, PageBlock } from "@navikt/ds-react/Page";
import Footer from "@/app/_ui/footer/Footer";
import { Header } from "@/app/_ui/header/Header";
import { WebsiteList, WebsiteListItem } from "@/app/_ui/typography/WebsiteList";
import { SiteTracking } from "@/app/_ui/umami/SiteTracking";
import { UmamiNotFoundPageLog } from "@/app/_ui/umami/Umami.log";
import GitHubIssueLink from "./_ui/github-issue-link";
export default function NotFound() {
return (
<Page data-aksel-template="404-v4" footer={<Footer />} className="vk-error">
<UmamiNotFoundPageLog />
<Header />
<PageBlock as="main" width="text" gutters>
<Box paddingBlock="space-64 space-80">
<VStack gap="space-64">
<VStack gap="space-48" align="start">
<SiteTracking>
<Page
data-aksel-template="404-v4"
footer={<Footer />}
className="vk-error"
>
<UmamiNotFoundPageLog />
<Header />
<PageBlock as="main" width="text" gutters>
<Box paddingBlock="space-64 space-80">
<VStack gap="space-64">
<VStack gap="space-48" align="start">
<div>
<Heading
level="1"
size="large"
data-aksel-heading-color
spacing
>
Beklager, vi fant ikke siden
</Heading>
<VStack gap="space-12">
<BodyShort>
Denne siden kan være slettet eller flyttet, eller det er
en feil i lenken.
</BodyShort>
<WebsiteList>
<WebsiteListItem icon>
Bruk søket eller menyen
</WebsiteListItem>
<WebsiteListItem icon>
<Link href="/">Gå til forsiden</Link>
</WebsiteListItem>
</WebsiteList>
</VStack>
</div>
<GitHubIssueLink
labels="bug 🐛"
template="bug-report.md"
title="[Aksel.nav.no - 404]"
includeUrlInBody
>
<BugIcon aria-hidden />
Meld gjerne fra om at lenken ikke virker
</GitHubIssueLink>
</VStack>
<div>
<Heading
level="1"
level="2"
size="large"
data-aksel-heading-color
spacing
data-aksel-heading-color
>
Beklager, vi fant ikke siden
Page not found
</Heading>
<VStack gap="space-12">
<BodyShort>
Denne siden kan være slettet eller flyttet, eller det er en
feil i lenken.
</BodyShort>
<WebsiteList>
<WebsiteListItem icon>
Bruk søket eller menyen
</WebsiteListItem>
<WebsiteListItem icon>
<Link href="/">Gå til forsiden</Link>
</WebsiteListItem>
</WebsiteList>
</VStack>
<BodyShort spacing>
The page you requested cannot be found.
</BodyShort>
<BodyShort>
Go to the <Link href="/">front page</Link>, or use one of the
links in the menu.
</BodyShort>
</div>
<GitHubIssueLink
labels="bug 🐛"
template="bug-report.md"
title="[Aksel.nav.no - 404]"
includeUrlInBody
>
<BugIcon aria-hidden />
Meld gjerne fra om at lenken ikke virker
</GitHubIssueLink>
</VStack>
<div>
<Heading level="2" size="large" spacing data-aksel-heading-color>
Page not found
</Heading>
<BodyShort spacing>
The page you requested cannot be found.
</BodyShort>
<BodyShort>
Go to the <Link href="/">front page</Link>, or use one of the
links in the menu.
</BodyShort>
</div>
</VStack>
</Box>
</PageBlock>
</Page>
</Box>
</PageBlock>
</Page>
</SiteTracking>
);
}
+7 -7
View File
@@ -44,7 +44,7 @@
"jscodeshift": "17.4.0",
"lodash": "4.18.1",
"lz-string": "^1.5.0",
"next": "16.3.6",
"next": "16.3.8",
"next-logger": "^5.0.2",
"next-sanity": "13.2.3",
"next-themes": "^0.4.6",
@@ -52,7 +52,7 @@
"prism-react-renderer": "^2.0.0",
"react": "19.3.0",
"react-dom": "19.3.0",
"react-hook-form": "^7.53.0",
"react-hook-form": "^7.90.0",
"react-markdown": "^9.0.1",
"react-responsive-masonry": "2.7.2",
"react-snowfall": "^2.4.0",
@@ -60,7 +60,7 @@
"rxjs": "^7.8.2",
"semver": "^7.7.2",
"server-only": "^0.0.1",
"sharp": "0.35.4",
"sharp": "0.35.5",
"swr": "^2.4.2",
"tinycolor2": "^1.6.0",
"typescript-cookie": "^1.0.6",
@@ -72,12 +72,12 @@
"@babel/parser": "^7.27.5",
"@babel/traverse": "^7.27.4",
"@google-cloud/bigquery": "^9.0.2",
"@next/bundle-analyzer": "16.3.6",
"@next/eslint-plugin-next": "16.3.6",
"@next/bundle-analyzer": "16.3.8",
"@next/eslint-plugin-next": "16.3.8",
"@playwright/test": "1.61.1",
"@portabletext/block-tools": "^6.0.0",
"@portabletext/markdown": "^1.4.4",
"@sanity/schema": "6.16.0",
"@sanity/schema": "6.17.0",
"@types/jscodeshift": "17.3.0",
"@types/jsdom": "^27.0.0",
"@types/lodash": "4.17.25",
@@ -91,7 +91,7 @@
"copyfiles": "^2.4.1",
"cross-env": "^10.1.0",
"jsdom": "29.1.1",
"json5": "2.2.1",
"json5": "2.2.3",
"pino-pretty": "^13.1.3",
"rss": "^1.2.2",
"showdown": "^2.1.0",
@@ -4,10 +4,10 @@ import { withDsExample } from "@/web/examples/withDsExample";
const Example = () => {
return (
<HStack align="center" gap="space-8">
<Badge count={5} aria-label="5 ulest" />
<Badge count={5} data-color="accent" aria-label="5 ulest" />
<Badge count={5} data-color="neutral" aria-label="5 ulest" />
<Badge count={5} data-color="warning" aria-label="5 ulest" />
<Badge count={5} />
<Badge count={5} data-color="accent" />
<Badge count={5} data-color="neutral" />
<Badge count={5} data-color="warning" />
</HStack>
);
};
@@ -4,12 +4,12 @@ import { withDsExample } from "@/web/examples/withDsExample";
const Example = () => {
return (
<VStack gap="space-12">
<Badge count={5}>
<Badge count={5} aria-hidden>
<Button variant="secondary" aria-label="Meldinger, 5 uleste">
Meldinger
</Button>
</Badge>
<Badge count={120}>
<Badge count={120} aria-hidden>
<Button variant="secondary" aria-label="Meldinger, 120 uleste">
Meldinger
</Button>
@@ -4,17 +4,17 @@ import { withDsExample } from "@/web/examples/withDsExample";
const Example = () => {
return (
<VStack gap="space-12">
<HStack align="center" gap="space-8">
<HStack align="center" gap="space-8" as="span">
<BodyShort as="span" weight="semibold">
Inbox
</BodyShort>
<Badge count={5} data-color="neutral" aria-label="5 ulest" />
<Badge count={5} data-color="neutral" />
</HStack>
<HStack align="center" gap="space-8">
<HStack align="center" gap="space-8" as="span">
<BodyShort as="span" weight="semibold">
Inbox
</BodyShort>
<Badge count={120} data-color="neutral" aria-label="120 ulest" />
<Badge count={120} data-color="neutral" />
</HStack>
</VStack>
);
@@ -30,5 +30,5 @@ export const Demo = {
export const args: ExampleArgsT = {
index: 0,
desc: "Badge kan brukes på linje med tekst for å f.eks. indikere antall uleste meldinger. Husk å alltid inkludere en hjelpetekst for å forklare hva tallet representerer.",
desc: "Badge kan brukes på linje med tekst for å f.eks. indikere antall uleste meldinger.",
};
@@ -7,7 +7,7 @@ const Example = () => {
<Badge>
<Button
variant="secondary"
title="Uleste meldinger"
title="Uleste varsler"
icon={<BellIcon aria-hidden />}
/>
</Badge>
@@ -24,5 +24,4 @@ export const Demo = {
export const args: ExampleArgsT = {
index: 3,
desc: "",
};
@@ -4,9 +4,12 @@ import { withDsExample } from "@/web/examples/withDsExample";
const Example = () => {
return (
<HStack align="center" gap="space-6">
<Badge data-color="accent" aria-label="Uleste meldinger" />
<Badge data-color="accent" />
<BodyShort as="span" weight="semibold">
Inbox
Innboks
<BodyShort as="span" visuallyHidden>
, med nye meldinger
</BodyShort>
</BodyShort>
</HStack>
);
@@ -4,10 +4,10 @@ import { withDsExample } from "@/web/examples/withDsExample";
const Example = () => {
return (
<HStack align="center" gap="space-8">
<Badge count={5} aria-label="5 ulest" />
<Badge count={999} aria-label="999 ulest" />
<Badge count={10} maxCount={8} aria-label="10 ulest" />
<Badge count={200} maxCount={140} aria-label="200 ulest" />
<Badge count={5} />
<Badge count={999} />
<Badge count={10} maxCount={8} />
<Badge count={200} maxCount={140} />
</HStack>
);
};
@@ -19,8 +19,8 @@ const Example = () => {
value="inbox"
label={
<HStack align="center" gap="space-8">
<BodyShort as="span">Inbox</BodyShort>
<Badge count={5} data-color="accent" aria-label="5 ulest" />
<BodyShort as="span">Innboks</BodyShort>
<Badge count={5} data-color="accent" />
</HStack>
}
icon={<InboxDownIcon aria-hidden />}
@@ -0,0 +1,43 @@
import { useState } from "react";
import { RadioCard, RadioCardGroup } from "@navikt/ds-react/PREVIEW";
import { withDsExample } from "@/web/examples/withDsExample";
const Example = () => {
const [val, setVal] = useState("hyre");
return (
<RadioCardGroup
legend="Hva slags lønn får du fra fisket?"
onChange={setVal}
value={val}
>
<RadioCard
value="lott"
description="Du får betalt ut fra hva fartøyet fanger - ikke fast lønn"
>
Lott - andel av fangsten
</RadioCard>
<RadioCard
value="hyre"
description="Du får fast lønn fra arbeidsgiveren din, som en vanlig ansatt"
>
Hyre - fast lønn
</RadioCard>
<RadioCard value="begge" description="Du får både fast hyre og lott">
Både lott og hyre
</RadioCard>
</RadioCardGroup>
);
};
// EXAMPLES DO NOT INCLUDE CONTENT BELOW THIS LINE
export default withDsExample(Example);
/* Storybook story */
export const Demo = {
render: Example,
};
export const args: ExampleArgsT = {
index: 6,
};
@@ -2,13 +2,10 @@ import { RadioCard, RadioCardGroup } from "@navikt/ds-react/PREVIEW";
import { withDsExample } from "@/web/examples/withDsExample";
const Example = () => {
const handleChange = (val: string) => console.info(val);
return (
<RadioCardGroup
legend="Hva slags lønn får du fra fisket?"
description="Dette gjelder også provisjon og andre tillegg."
onChange={handleChange}
>
<RadioCard
value="lott"
@@ -0,0 +1,41 @@
import { RadioCard, RadioCardGroup } from "@navikt/ds-react/PREVIEW";
import { withDsExample } from "@/web/examples/withDsExample";
const Example = () => {
return (
<RadioCardGroup
legend="Hva slags lønn får du fra fisket?"
defaultValue="hyre"
disabled
>
<RadioCard
value="lott"
description="Du får betalt ut fra hva fartøyet fanger - ikke fast lønn"
>
Lott - andel av fangsten
</RadioCard>
<RadioCard
value="hyre"
description="Du får fast lønn fra arbeidsgiveren din, som en vanlig ansatt"
>
Hyre - fast lønn
</RadioCard>
<RadioCard value="begge" description="Du får både fast hyre og lott">
Både lott og hyre
</RadioCard>
</RadioCardGroup>
);
};
// EXAMPLES DO NOT INCLUDE CONTENT BELOW THIS LINE
export default withDsExample(Example);
/* Storybook story */
export const Demo = {
render: Example,
};
export const args: ExampleArgsT = {
index: 99,
desc: "Vi fraråder bruk av disabled state. Vurder om du trenger å vise feltet i det hele tatt, om du heller kan bruke `readOnly`, eller bare kan skrive det ut i ren tekst.",
};
Loaded 100 of 111 files, more files were not shown because too many files have changed in this diff. Show more