mirror of
https://github.com/navikt/aksel.git
synced 2026-10-22 13:42:02 +00:00
Compare commits
45
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b714ee0af | ||
|
|
a2d0ffda0a | ||
|
|
45aa8baf10 | ||
|
|
3f7b72369e | ||
|
|
8009289ca4 | ||
|
|
ee50050d27 | ||
|
|
4e1cfddd84 | ||
|
|
643ab79c8d | ||
|
|
d134397fd0 | ||
|
|
d8e62db7f1 | ||
|
|
0cf700322c | ||
|
|
2c468cc8c7 | ||
|
|
d722f23a5e | ||
|
|
317940ec17 | ||
|
|
fb0330cf9d | ||
|
|
b9eb6568ad | ||
|
|
cc379525c0 | ||
|
|
6bb7becad7 | ||
|
|
8a513c96cc | ||
|
|
b4e5a3b8bd | ||
|
|
64dd1a529b | ||
|
|
3f9daed6a0 | ||
|
|
a53637c2d7 | ||
|
|
c9e48dfbd0 | ||
|
|
ef2e1f88e3 | ||
|
|
05e88b37b8 | ||
|
|
8ec9677d20 | ||
|
|
2b6c530fef | ||
|
|
f784aac8bb | ||
|
|
1490249f0c | ||
|
|
31c4296568 | ||
|
|
af64a52f83 | ||
|
|
0d6db896c0 | ||
|
|
91b4d8827e | ||
|
|
ea9ff5e63d | ||
|
|
3ab3498ddb | ||
|
|
40e1717fe9 | ||
|
|
06e3234ecd | ||
|
|
43f7fc70f0 | ||
|
|
2994c8d5ac | ||
|
|
1bca520e92 | ||
|
|
c5dd3097e5 | ||
|
|
a8cca91bcd | ||
|
|
bdddbd75d1 | ||
|
|
5e778e84a2 |
No files matched your search
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@navikt/ds-react": patch
|
||||
---
|
||||
|
||||
Lookup, Badge: Mark as deprecated when imported from @navikt/ds-react/PREVIEW
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@navikt/aksel": patch
|
||||
---
|
||||
|
||||
CLI: Update clipboardy to v5.3.2
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@navikt/aksel": patch
|
||||
---
|
||||
|
||||
CLI: Update figlet to 1.11.3
|
||||
@@ -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.
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
@@ -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",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @navikt/aksel-stylelint
|
||||
|
||||
## 8.19.2
|
||||
|
||||
## 8.19.1
|
||||
|
||||
## 8.19.0
|
||||
|
||||
## 8.18.0
|
||||
|
||||
## 8.17.2
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -86,6 +86,10 @@
|
||||
|
||||
.aksel-read-more[data-state="closed"] & {
|
||||
display: none;
|
||||
|
||||
@media print {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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";
|
||||
@@ -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}
|
||||
|
||||
@@ -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";
|
||||
@@ -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;
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @navikt/ds-tailwind
|
||||
|
||||
## 8.19.2
|
||||
|
||||
## 8.19.1
|
||||
|
||||
## 8.19.0
|
||||
|
||||
## 8.18.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @navikt/ds-tokens
|
||||
|
||||
## 8.19.2
|
||||
|
||||
## 8.19.1
|
||||
|
||||
## 8.19.0
|
||||
|
||||
## 8.18.0
|
||||
|
||||
## 8.17.2
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 };
|
||||
@@ -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
|
||||
|
||||
+24
-20
@@ -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"
|
||||
|
||||
@@ -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} />}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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
Reference in new issue
Block a user