mirror of
https://github.com/navikt/aksel.git
synced 2026-10-13 01:02:02 +00:00
Compare commits
50
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37165e9f38 | ||
|
|
d19920ca78 | ||
|
|
60d3481300 | ||
|
|
f486937439 | ||
|
|
c62058abc1 | ||
|
|
84d9b8260f | ||
|
|
c108063dcb | ||
|
|
2a8533ad56 | ||
|
|
9b990a2178 | ||
|
|
5fec1f24f4 | ||
|
|
c27c1cfe26 | ||
|
|
5a649d2282 | ||
|
|
baac9cc251 | ||
|
|
d924d523e4 | ||
|
|
fc5a77081d | ||
|
|
1f3477a3e9 | ||
|
|
925450cb73 | ||
|
|
534a6a3b40 | ||
|
|
51eee7f011 | ||
|
|
909ae6aed1 | ||
|
|
cd3859a91e | ||
|
|
338f14e7ac | ||
|
|
aed1d082c3 | ||
|
|
31e9226b55 | ||
|
|
fe15b4d275 | ||
|
|
28e095d5a2 | ||
|
|
43377841ba | ||
|
|
2976ee9626 | ||
|
|
23c135b437 | ||
|
|
7f0498f250 | ||
|
|
5075c8d0a6 | ||
|
|
792eeaba42 | ||
|
|
0c7fc04935 | ||
|
|
0bb339e54a | ||
|
|
f5eb3adaea | ||
|
|
77cf71f814 | ||
|
|
f8c12b0757 | ||
|
|
be8265e13e | ||
|
|
0c7d6fb461 | ||
|
|
b97ac03c5a | ||
|
|
0ddb589138 | ||
|
|
1440c99b96 | ||
|
|
f8cf39c906 | ||
|
|
158c032436 | ||
|
|
6d6e6443dd | ||
|
|
e12ea8694e | ||
|
|
2d4cb67643 | ||
|
|
f5533766c8 | ||
|
|
3d1b0ac204 | ||
|
|
a1678d3b19 |
No files matched your search
@@ -0,0 +1,22 @@
|
||||
name: Eksempel på bruk
|
||||
description: Send et eksempel på ting fra Aksel som er brukt i det fri (tokens, komponenter, maler, etc)
|
||||
title: "[Eksempel på bruk]: "
|
||||
labels: ["dokumentasjon 📖"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Takk for at du bidrar til Aksel!
|
||||
- type: input
|
||||
id: title
|
||||
attributes:
|
||||
label: Lenke til side
|
||||
description: Lenke til siden hvor eksemplet er
|
||||
placeholder:
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: examples
|
||||
attributes:
|
||||
label: Forklaring
|
||||
description: Forklar om det er noe man må gjøre for å se eksemplet, eller hvor på siden det ligger
|
||||
+2
-1
@@ -151,4 +151,5 @@ playwright-report/
|
||||
backup-*.tar.gz
|
||||
sitemap-urls.json
|
||||
*.tar.gz
|
||||
searchindex.json
|
||||
searchindex.json
|
||||
aksel.nav.no/website/public/rss
|
||||
@@ -1,5 +1,19 @@
|
||||
# @navikt/aksel-icons
|
||||
|
||||
## 5.10.0
|
||||
|
||||
## 5.9.2
|
||||
|
||||
## 5.9.1
|
||||
|
||||
## 5.9.0
|
||||
|
||||
## 5.8.0
|
||||
|
||||
## 5.7.6
|
||||
|
||||
## 5.7.5
|
||||
|
||||
## 5.7.4
|
||||
|
||||
## 5.7.3
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/aksel-icons",
|
||||
"version": "5.7.4",
|
||||
"version": "5.10.0",
|
||||
"author": "Aksel | NAV",
|
||||
"homepage": "https://aksel.nav.no/ikoner",
|
||||
"repository": {
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# @navikt/aksel-stylelint
|
||||
|
||||
## 5.10.0
|
||||
|
||||
## 5.9.2
|
||||
|
||||
## 5.9.1
|
||||
|
||||
## 5.9.0
|
||||
|
||||
## 5.8.0
|
||||
|
||||
## 5.7.6
|
||||
|
||||
## 5.7.5
|
||||
|
||||
## 5.7.4
|
||||
|
||||
## 5.7.3
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/aksel-stylelint",
|
||||
"version": "5.7.4",
|
||||
"version": "5.10.0",
|
||||
"author": "Aksel | NAV",
|
||||
"homepage": "https://aksel.nav.no/grunnleggende/kode/stylelint",
|
||||
"repository": {
|
||||
@@ -35,8 +35,8 @@
|
||||
"dev": "yarn watch:lint"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@navikt/ds-css": "^5.7.4",
|
||||
"@navikt/ds-tokens": "^5.7.4",
|
||||
"@navikt/ds-css": "^5.10.0",
|
||||
"@navikt/ds-tokens": "^5.10.0",
|
||||
"@types/jest": "^29.0.0",
|
||||
"concurrently": "7.2.1",
|
||||
"copyfiles": "2.4.1",
|
||||
|
||||
@@ -1,5 +1,54 @@
|
||||
# @navikt/aksel
|
||||
|
||||
## 5.10.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`60d348130`](https://github.com/navikt/aksel/commit/60d34813009377a1a7d84bed6627c60524849957)]:
|
||||
- @navikt/ds-css@5.10.0
|
||||
|
||||
## 5.9.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/ds-css@5.9.2
|
||||
|
||||
## 5.9.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`925450cb7`](https://github.com/navikt/aksel/commit/925450cb73cd792cd3b6ad3ebf9f38ba022ee4df)]:
|
||||
- @navikt/ds-css@5.9.1
|
||||
|
||||
## 5.9.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`51eee7f01`](https://github.com/navikt/aksel/commit/51eee7f011aa016973bb952313cf65a1c55aa382), [`51eee7f01`](https://github.com/navikt/aksel/commit/51eee7f011aa016973bb952313cf65a1c55aa382), [`51eee7f01`](https://github.com/navikt/aksel/commit/51eee7f011aa016973bb952313cf65a1c55aa382), [`51eee7f01`](https://github.com/navikt/aksel/commit/51eee7f011aa016973bb952313cf65a1c55aa382)]:
|
||||
- @navikt/ds-css@5.9.0
|
||||
|
||||
## 5.8.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`0c7fc0493`](https://github.com/navikt/aksel/commit/0c7fc04935b96946b8ed1178c700077b6a1fcefd)]:
|
||||
- @navikt/ds-css@5.8.0
|
||||
|
||||
## 5.7.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`6d6e6443d`](https://github.com/navikt/aksel/commit/6d6e6443dd00464183b03fad6f822e5f3efab100)]:
|
||||
- @navikt/ds-css@5.7.6
|
||||
|
||||
## 5.7.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`3d1b0ac20`](https://github.com/navikt/aksel/commit/3d1b0ac2041bbc289536fa5a0dc65b2170dfc6fa)]:
|
||||
- @navikt/ds-css@5.7.5
|
||||
|
||||
## 5.7.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/aksel",
|
||||
"version": "5.7.4",
|
||||
"version": "5.10.0",
|
||||
"description": "Aksel command line interface. Handles css-imports, codemods and more",
|
||||
"author": "Aksel | NAV designsystem team",
|
||||
"license": "MIT",
|
||||
@@ -29,7 +29,7 @@
|
||||
},
|
||||
"homepage": "https://aksel.nav.no/grunnleggende/kode/kommandolinje",
|
||||
"dependencies": {
|
||||
"@navikt/ds-css": "5.7.4",
|
||||
"@navikt/ds-css": "5.10.0",
|
||||
"axios": "1.3.6",
|
||||
"chalk": "4.1.0",
|
||||
"clipboardy": "^2.3.0",
|
||||
|
||||
@@ -1,5 +1,49 @@
|
||||
# @navikt/ds-css
|
||||
|
||||
## 5.10.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Primitives: Ny komponent `Page` og `Page.Block` ([#2456](https://github.com/navikt/aksel/pull/2456))
|
||||
|
||||
## 5.9.2
|
||||
|
||||
## 5.9.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- :lipstick: Modal: Bedre håndtering av mobiler i landskapsmodus ([#2444](https://github.com/navikt/aksel/pull/2444))
|
||||
|
||||
## 5.9.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Datepicker/Monthpicker: Hvis man bruker komponentene i Modal vil Popover bli erstattet med Modal uansett om man er på desktop eller mobil. ([#2419](https://github.com/navikt/aksel/pull/2419))
|
||||
|
||||
- Datepicker/Monthpicker/Popover: Fjernet `bubbleEscape`-prop. ([#2419](https://github.com/navikt/aksel/pull/2419))
|
||||
|
||||
- useDatepicker/useMonthPicker/useRangedpicker: Fjernet `openOnFocus`-prop, kan nå bare åpnes ved klikk på date-knapp i input. ([#2419](https://github.com/navikt/aksel/pull/2419))
|
||||
|
||||
- Datepicker/Monthpicker: Bytter nå automatisk til Modalvisning på mobil. ([#2419](https://github.com/navikt/aksel/pull/2419))
|
||||
|
||||
## 5.8.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- :bug: Modal: Bedre støtte for Tooltip i Modal ([#2429](https://github.com/navikt/aksel/pull/2429))
|
||||
|
||||
## 5.7.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- :lipstick: Chips.Toggle: Ikon i uvalgt tilstand ([#2395](https://github.com/navikt/aksel/pull/2395))
|
||||
|
||||
## 5.7.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Checkbox, Radio: Transparent-border fungerer nå bedre på mørkere bakgrunner ([#2388](https://github.com/navikt/aksel/pull/2388))
|
||||
|
||||
## 5.7.4
|
||||
|
||||
## 5.7.3
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--a-spacing-1);
|
||||
gap: var(--a-spacing-05);
|
||||
margin: 0;
|
||||
padding: 0 var(--a-spacing-3);
|
||||
text-decoration: none;
|
||||
@@ -97,6 +97,19 @@
|
||||
}
|
||||
}
|
||||
|
||||
.navds-chips--medium .navds-chips__toggle--with-checkmark {
|
||||
padding-left: var(--a-spacing-1-alt);
|
||||
}
|
||||
|
||||
.navds-chips--small .navds-chips__toggle--with-checkmark {
|
||||
padding-left: var(--a-spacing-1);
|
||||
}
|
||||
|
||||
.navds-chips--small .navds-chips__toggle-icon {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
.navds-chips__removable {
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
@@ -35,6 +35,10 @@ const StyleMappings = {
|
||||
component: "Box",
|
||||
main: primitivesCss,
|
||||
},
|
||||
{
|
||||
component: "Page",
|
||||
main: primitivesCss,
|
||||
},
|
||||
{
|
||||
component: "HGrid",
|
||||
main: primitivesCss,
|
||||
|
||||
+26
-18
@@ -149,18 +149,6 @@
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
/* Focus layering */
|
||||
.navds-date__field-input:focus-visible,
|
||||
.navds-date__field-button {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
@supports not selector(:focus-visible) {
|
||||
.navds-date__field-input:focus {
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.navds-date .rdp-day_selected,
|
||||
.navds-monthpicker__month--selected {
|
||||
color: var(--ac-date-selected-text, var(--a-text-on-action));
|
||||
@@ -201,11 +189,11 @@
|
||||
}
|
||||
|
||||
.navds-date__field-input {
|
||||
padding-right: var(--a-spacing-14);
|
||||
padding-right: var(--a-spacing-12);
|
||||
}
|
||||
|
||||
.navds-form-field--small .navds-date__field-input {
|
||||
padding-right: var(--a-spacing-10);
|
||||
padding-right: var(--a-spacing-8);
|
||||
}
|
||||
|
||||
/* Error-handling */
|
||||
@@ -282,10 +270,6 @@
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.navds-date__popover:where(.navds-popover) {
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* Readonly */
|
||||
.navds-date__field--readonly .navds-date__field-button {
|
||||
cursor: default;
|
||||
@@ -310,11 +294,35 @@
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.navds-date__modal.navds-date {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.navds-date__modal-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
padding: var(--a-spacing-4);
|
||||
gap: var(--a-spacing-2);
|
||||
}
|
||||
|
||||
.navds-date__modal-body > .navds-date {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.navds-date__popover:where(.navds-popover) {
|
||||
border: none;
|
||||
}
|
||||
|
||||
@media (min-width: 480px) {
|
||||
.navds-date {
|
||||
padding: var(--a-spacing-5) var(--a-spacing-4);
|
||||
}
|
||||
|
||||
.navds-date__modal-body {
|
||||
padding: var(--a-spacing-6);
|
||||
}
|
||||
|
||||
.navds-date__caption {
|
||||
gap: var(--a-spacing-2);
|
||||
}
|
||||
|
||||
@@ -31,11 +31,13 @@
|
||||
.navds-radio__label::before {
|
||||
content: "";
|
||||
background-color: var(--ac-radio-checkbox-bg, var(--a-surface-default));
|
||||
box-shadow: inset 0 0 0 2px var(--ac-radio-checkbox-border, var(--a-border-default));
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
border-radius: var(--a-border-radius-medium);
|
||||
border-radius: var(--a-border-radius-small);
|
||||
flex-shrink: 0;
|
||||
box-shadow: 0 0 0 2px var(--ac-radio-checkbox-border, var(--a-border-default));
|
||||
width: calc(1.5rem - 0.25rem);
|
||||
height: calc(1.5rem - 0.25rem);
|
||||
margin-block: 2px;
|
||||
margin-inline: 2px;
|
||||
}
|
||||
|
||||
.navds-radio__label::before {
|
||||
@@ -64,36 +66,37 @@
|
||||
|
||||
.navds-checkbox--small > .navds-checkbox__label::before,
|
||||
.navds-radio--small > .navds-radio__label::before {
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
width: calc(1.25rem - 0.25rem);
|
||||
height: calc(1.25rem - 0.25rem);
|
||||
}
|
||||
|
||||
.navds-checkbox__input:focus-visible + .navds-checkbox__label::before,
|
||||
.navds-radio__input:focus-visible + .navds-radio__label::before {
|
||||
box-shadow: inset 0 0 0 2px var(--ac-radio-checkbox-border, var(--a-border-default)), var(--a-shadow-focus);
|
||||
box-shadow: 0 0 0 2px var(--ac-radio-checkbox-border, var(--a-border-default)),
|
||||
0 0 0 2px var(--ac-radio-checkbox-bg, var(--a-surface-default)), 0 0 0 4px var(--a-border-focus);
|
||||
}
|
||||
|
||||
@supports not selector(:focus-visible) {
|
||||
.navds-checkbox__input:focus + .navds-checkbox__label::before,
|
||||
.navds-radio__input:focus + .navds-radio__label::before {
|
||||
box-shadow: inset 0 0 0 2px var(--ac-radio-checkbox-border, var(--a-border-default)), var(--a-shadow-focus);
|
||||
box-shadow: 0 0 0 2px var(--ac-radio-checkbox-border, var(--a-border-default)), 0 0 0 4px var(--a-border-focus);
|
||||
}
|
||||
}
|
||||
|
||||
.navds-checkbox__input:hover:focus-visible + .navds-checkbox__label::before,
|
||||
.navds-radio__input:hover:focus-visible + .navds-radio__label::before {
|
||||
box-shadow: inset 0 0 0 2px var(--ac-radio-checkbox-action, var(--a-surface-action)), var(--a-shadow-focus);
|
||||
box-shadow: 0 0 0 2px var(--ac-radio-checkbox-action, var(--a-surface-action)), 0 0 0 4px var(--a-border-focus);
|
||||
}
|
||||
|
||||
@supports not selector(:focus-visible) {
|
||||
.navds-checkbox__input:hover:focus + .navds-checkbox__label::before,
|
||||
.navds-radio__input:hover:focus + .navds-radio__label::before {
|
||||
box-shadow: inset 0 0 0 2px var(--ac-radio-checkbox-action, var(--a-surface-action)), var(--a-shadow-focus);
|
||||
box-shadow: 0 0 0 2px var(--ac-radio-checkbox-action, var(--a-surface-action)), 0 0 0 4px var(--a-border-focus);
|
||||
}
|
||||
}
|
||||
|
||||
.navds-checkbox__input:indeterminate + .navds-checkbox__label::before {
|
||||
box-shadow: none;
|
||||
box-shadow: 0 0 0 2px var(--ac-radio-checkbox-action, var(--a-surface-action));
|
||||
background-color: var(--ac-radio-checkbox-action, var(--a-surface-action));
|
||||
}
|
||||
|
||||
@@ -114,7 +117,7 @@
|
||||
}
|
||||
|
||||
.navds-checkbox__input:checked + .navds-checkbox__label::before {
|
||||
box-shadow: none;
|
||||
box-shadow: 0 0 0 2px var(--ac-radio-checkbox-action, var(--a-surface-action));
|
||||
background-color: var(--ac-radio-checkbox-action, var(--a-surface-action));
|
||||
}
|
||||
|
||||
@@ -146,31 +149,33 @@
|
||||
|
||||
.navds-checkbox__input:indeterminate:focus-visible + .navds-checkbox__label::before,
|
||||
.navds-checkbox__input:checked:focus-visible + .navds-checkbox__label::before {
|
||||
box-shadow: inset 0 0 0 1px var(--ac-radio-checkbox-bg, var(--a-surface-default)), var(--a-shadow-focus);
|
||||
box-shadow: 0 0 0 1px var(--ac-radio-checkbox-action, var(--a-surface-action)),
|
||||
0 0 0 2px var(--ac-radio-checkbox-bg, var(--a-surface-default)), 0 0 0 4px var(--a-border-focus);
|
||||
}
|
||||
|
||||
@supports not selector(:focus-visible) {
|
||||
.navds-checkbox__input:indeterminate:focus + .navds-checkbox__label::before,
|
||||
.navds-checkbox__input:checked:focus + .navds-checkbox__label::before {
|
||||
box-shadow: inset 0 0 0 1px var(--ac-radio-checkbox-bg, var(--a-surface-default)), var(--a-shadow-focus);
|
||||
box-shadow: 0 0 0 1px var(--ac-radio-checkbox-action, var(--a-surface-action)),
|
||||
0 0 0 2px var(--ac-radio-checkbox-bg, var(--a-surface-default)), 0 0 0 4px var(--a-border-focus);
|
||||
}
|
||||
}
|
||||
|
||||
.navds-radio__input:checked + .navds-radio__label::before {
|
||||
box-shadow: inset 0 0 0 2px var(--ac-radio-checkbox-action, var(--a-surface-action)),
|
||||
inset 0 0 0 4px var(--ac-radio-checkbox-bg, var(--a-surface-default));
|
||||
box-shadow: 0 0 0 2px var(--ac-radio-checkbox-action, var(--a-surface-action)),
|
||||
inset 0 0 0 2px var(--ac-radio-checkbox-bg, var(--a-surface-default));
|
||||
background-color: var(--ac-radio-checkbox-action, var(--a-surface-action));
|
||||
}
|
||||
|
||||
.navds-radio__input:checked:focus-visible + .navds-radio__label::before {
|
||||
box-shadow: inset 0 0 0 2px var(--ac-radio-checkbox-action, var(--a-surface-action)),
|
||||
inset 0 0 0 4px var(--ac-radio-checkbox-bg, var(--a-surface-default)), var(--a-shadow-focus);
|
||||
box-shadow: 0 0 0 2px var(--ac-radio-checkbox-action, var(--a-surface-action)),
|
||||
inset 0 0 0 2px var(--ac-radio-checkbox-bg, var(--a-surface-default)), 0 0 0 4px var(--a-border-focus);
|
||||
}
|
||||
|
||||
@supports not selector(:focus-visible) {
|
||||
.navds-radio__input:checked:focus + .navds-radio__label::before {
|
||||
box-shadow: inset 0 0 0 2px var(--ac-radio-checkbox-action, var(--a-surface-action)),
|
||||
inset 0 0 0 4px var(--ac-radio-checkbox-bg, var(--a-surface-default)), var(--a-shadow-focus);
|
||||
box-shadow: 0 0 0 2px var(--ac-radio-checkbox-action, var(--a-surface-action)),
|
||||
inset 0 0 0 2px var(--ac-radio-checkbox-bg, var(--a-surface-default)), 0 0 0 4px var(--a-border-focus);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,13 +187,13 @@
|
||||
.navds-checkbox__input:hover:not(:disabled):not(:checked):not(:indeterminate):not(:focus-visible)
|
||||
+ .navds-checkbox__label::before,
|
||||
.navds-radio__input:hover:not(:disabled):not(:checked):not(:focus-visible) + .navds-radio__label::before {
|
||||
box-shadow: inset 0 0 0 2px var(--ac-radio-checkbox-action, var(--a-surface-action));
|
||||
box-shadow: 0 0 0 2px var(--ac-radio-checkbox-action, var(--a-surface-action));
|
||||
}
|
||||
|
||||
@supports not selector(:focus-visible) {
|
||||
.navds-checkbox__input:hover:not(:disabled):not(:checked):not(:indeterminate):not(:focus) + .navds-checkbox__label::before,
|
||||
.navds-radio__input:hover:not(:disabled):not(:checked):not(:focus) + .navds-radio__label::before {
|
||||
box-shadow: inset 0 0 0 2px var(--ac-radio-checkbox-action, var(--a-surface-action));
|
||||
box-shadow: 0 0 0 2px var(--ac-radio-checkbox-action, var(--a-surface-action));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,14 +206,14 @@
|
||||
> .navds-checkbox__input:not(:hover):not(:disabled):not(:checked):not(:indeterminate)
|
||||
+ .navds-checkbox__label::before,
|
||||
.navds-radio--error > .navds-radio__input:not(:hover):not(:disabled):not(:checked) + .navds-radio__label::before {
|
||||
box-shadow: inset 0 0 0 2px var(--ac-radio-checkbox-error-border, var(--a-border-danger));
|
||||
box-shadow: 0 0 0 2px var(--ac-radio-checkbox-error-border, var(--a-border-danger));
|
||||
}
|
||||
|
||||
.navds-checkbox--error
|
||||
> .navds-checkbox__input:focus-visible:not(:hover):not(:disabled):not(:checked):not(:indeterminate)
|
||||
+ .navds-checkbox__label::before,
|
||||
.navds-radio--error > .navds-radio__input:focus-visible:not(:hover):not(:disabled):not(:checked) + .navds-radio__label::before {
|
||||
box-shadow: inset 0 0 0 2px var(--ac-radio-checkbox-error-border, var(--a-border-danger)), var(--a-shadow-focus);
|
||||
box-shadow: 0 0 0 2px var(--ac-radio-checkbox-error-border, var(--a-border-danger)), 0 0 0 4px var(--a-border-focus);
|
||||
}
|
||||
|
||||
@supports not selector(:focus-visible) {
|
||||
@@ -216,7 +221,7 @@
|
||||
> .navds-checkbox__input:focus:not(:hover):not(:disabled):not(:checked):not(:indeterminate)
|
||||
+ .navds-checkbox__label::before,
|
||||
.navds-radio--error > .navds-radio__input:focus:not(:hover):not(:disabled):not(:checked) + .navds-radio__label::before {
|
||||
box-shadow: inset 0 0 0 2px var(--ac-radio-checkbox-error-border, var(--a-border-danger)), var(--a-shadow-focus);
|
||||
box-shadow: 0 0 0 2px var(--ac-radio-checkbox-error-border, var(--a-border-danger)), 0 0 0 4px var(--a-border-focus);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,7 +230,7 @@
|
||||
+ .navds-checkbox__label::before,
|
||||
.navds-radio--error > .navds-radio__input:hover:not(:disabled):not(:checked):not(:focus-visible) + .navds-radio__label::before {
|
||||
background-color: var(--ac-radio-checkbox-error-hover-bg, var(--a-surface-danger-subtle));
|
||||
box-shadow: inset 0 0 0 2px var(--ac-radio-checkbox-error-border, var(--a-border-danger));
|
||||
box-shadow: 0 0 0 2px var(--ac-radio-checkbox-error-border, var(--a-border-danger));
|
||||
}
|
||||
|
||||
.navds-checkbox--error
|
||||
@@ -233,7 +238,7 @@
|
||||
+ .navds-checkbox__label::before,
|
||||
.navds-radio--error > .navds-radio__input:focus-visible:hover:not(:disabled):not(:checked) + .navds-radio__label::before {
|
||||
background-color: var(--ac-radio-checkbox-error-hover-bg, var(--a-surface-danger-subtle));
|
||||
box-shadow: inset 0 0 0 2px var(--ac-radio-checkbox-error-border, var(--a-border-danger)), var(--a-shadow-focus);
|
||||
box-shadow: 0 0 0 2px var(--ac-radio-checkbox-error-border, var(--a-border-danger)), 0 0 0 4px var(--a-border-focus);
|
||||
}
|
||||
|
||||
@supports not selector(:focus-visible) {
|
||||
@@ -242,7 +247,7 @@
|
||||
+ .navds-checkbox__label::before,
|
||||
.navds-radio--error > .navds-radio__input:hover:not(:disabled):not(:checked):not(:focus) + .navds-radio__label::before {
|
||||
background-color: var(--ac-radio-checkbox-error-hover-bg, var(--a-surface-danger-subtle));
|
||||
box-shadow: inset 0 0 0 2px var(--ac-radio-checkbox-error-border, var(--a-border-danger));
|
||||
box-shadow: 0 0 0 2px var(--ac-radio-checkbox-error-border, var(--a-border-danger));
|
||||
}
|
||||
|
||||
.navds-checkbox--error
|
||||
@@ -250,7 +255,7 @@
|
||||
+ .navds-checkbox__label::before,
|
||||
.navds-radio--error > .navds-radio__input:focus:hover:not(:disabled):not(:checked) + .navds-radio__label::before {
|
||||
background-color: var(--ac-radio-checkbox-error-hover-bg, var(--a-surface-danger-subtle));
|
||||
box-shadow: inset 0 0 0 2px var(--ac-radio-checkbox-error-border, var(--a-border-danger)), var(--a-shadow-focus);
|
||||
box-shadow: 0 0 0 2px var(--ac-radio-checkbox-error-border, var(--a-border-danger)), 0 0 0 4px var(--a-border-focus);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,7 +295,7 @@
|
||||
.navds-radio--readonly > .navds-radio__input:not(:disabled):not(:checked) + .navds-radio__label::before,
|
||||
.navds-radio--readonly > .navds-radio__input:hover:not(:checked):not(:focus-visible) + .navds-radio__label::before {
|
||||
background-color: var(--__ac-radio-checkbox-readonly-bg);
|
||||
box-shadow: inset 0 0 0 2px var(--__ac-radio-checkbox-readonly-border);
|
||||
box-shadow: 0 0 0 2px var(--__ac-radio-checkbox-readonly-border);
|
||||
}
|
||||
|
||||
.navds-checkbox--readonly > .navds-checkbox__input:focus-visible + .navds-checkbox__label::before,
|
||||
@@ -299,8 +304,8 @@
|
||||
}
|
||||
|
||||
.navds-checkbox--readonly > .navds-checkbox__input:checked + .navds-checkbox__label::before {
|
||||
box-shadow: inset 0 0 0 2px var(--__ac-radio-checkbox-readonly-border);
|
||||
background-color: var(--__ac-radio-checkbox-readonly-bg);
|
||||
box-shadow: 0 0 0 2px var(--__ac-radio-checkbox-readonly-border);
|
||||
}
|
||||
|
||||
.navds-checkbox--readonly > .navds-checkbox__input:checked + .navds-checkbox__label > .navds-checkbox__icon {
|
||||
@@ -308,12 +313,12 @@
|
||||
}
|
||||
|
||||
.navds-radio--readonly > .navds-radio__input:checked + .navds-radio__label::before {
|
||||
box-shadow: inset 0 0 0 2px var(--__ac-radio-checkbox-readonly-border), inset 0 0 0 4px var(--__ac-radio-checkbox-readonly-bg);
|
||||
background-color: var(--a-icon-subtle);
|
||||
box-shadow: 0 0 0 2px var(--__ac-radio-checkbox-readonly-border), inset 0 0 0 2px var(--__ac-radio-checkbox-readonly-bg);
|
||||
}
|
||||
|
||||
.navds-checkbox--readonly > .navds-checkbox__input:indeterminate + .navds-checkbox__label::before {
|
||||
box-shadow: inset 0 0 0 2px var(--__ac-radio-checkbox-readonly-border);
|
||||
box-shadow: 0 0 0 2px var(--__ac-radio-checkbox-readonly-border);
|
||||
background-color: var(--__ac-radio-checkbox-readonly-bg);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
.navds-modal--polyfilled {
|
||||
top: 50%;
|
||||
transform: translate(0, -50%);
|
||||
overflow: auto;
|
||||
|
||||
/* From polyfill (dialog-polyfill/dist/dialog-polyfill.css): */
|
||||
left: 0;
|
||||
@@ -33,6 +32,10 @@
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.navds-modal--polyfilled .navds-modal--polyfilled {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.navds-modal--polyfilled:not([open]) {
|
||||
display: none; /* from polyfill */
|
||||
}
|
||||
@@ -56,12 +59,17 @@
|
||||
|
||||
@media (min-width: 480px) {
|
||||
.navds-modal {
|
||||
max-width: calc((100% - 6px) - 2em);
|
||||
max-height: calc((100% - 6px) - 2em);
|
||||
max-width: calc(100% - 6px - 2em);
|
||||
}
|
||||
|
||||
.navds-modal--autowidth {
|
||||
max-width: min(700px, calc((100% - 6px) - 2em));
|
||||
max-width: min(700px, calc(100% - 6px - 2em));
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-height: 480px) {
|
||||
.navds-modal {
|
||||
max-height: calc(100% - 6px - 2em);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,6 +135,14 @@
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
/* When Datepicker is used nested inside a Modal */
|
||||
.navds-modal--polyfilled .navds-modal--polyfilled.navds-date__nested-modal {
|
||||
min-width: fit-content;
|
||||
max-width: 100vw;
|
||||
max-height: 100vh;
|
||||
animation: none;
|
||||
}
|
||||
|
||||
@keyframes akselModalFadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-css",
|
||||
"version": "5.7.4",
|
||||
"version": "5.10.0",
|
||||
"description": "CSS for NAV Designsystem",
|
||||
"author": "Aksel | NAV designsystem team",
|
||||
"keywords": [
|
||||
@@ -27,7 +27,7 @@
|
||||
"css:get-version": "node config/get-version.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@navikt/ds-tokens": "^5.7.4",
|
||||
"@navikt/ds-tokens": "^5.10.0",
|
||||
"cssnano": "6.0.0",
|
||||
"fast-glob": "3.2.11",
|
||||
"lodash": "4.17.21",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@import "box.css";
|
||||
@import "bleed.css";
|
||||
@import "page.css";
|
||||
@import "hgrid.css";
|
||||
@import "stack.css";
|
||||
@import "responsive.css";
|
||||
@@ -0,0 +1,50 @@
|
||||
.navds-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
min-height: 100lvh;
|
||||
|
||||
/* stylelint-disable-next-line csstools/value-no-unknown-custom-properties */
|
||||
background-color: var(--__ac-page-background);
|
||||
}
|
||||
|
||||
/* Page.Block */
|
||||
.navds-pageblock {
|
||||
margin-inline: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.navds-page__content--grow {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.navds-page__content--fullheight {
|
||||
min-height: 100vh;
|
||||
min-height: 100lvh;
|
||||
}
|
||||
|
||||
.navds-page__content--padding {
|
||||
padding-block-end: var(--a-spacing-8);
|
||||
}
|
||||
|
||||
.navds-pageblock--lg {
|
||||
max-width: 1024px;
|
||||
}
|
||||
|
||||
.navds-pageblock--xl {
|
||||
max-width: 1280px;
|
||||
}
|
||||
|
||||
.navds-pageblock--2xl {
|
||||
max-width: 1440px;
|
||||
}
|
||||
|
||||
.navds-pageblock--gutters {
|
||||
padding-inline: var(--a-spacing-4);
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.navds-pageblock--gutters {
|
||||
padding-inline: var(--a-spacing-12);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,93 @@
|
||||
# @navikt/ds-react
|
||||
|
||||
## 5.10.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Primitives: Ny komponent `Page` og `Page.Block` ([#2456](https://github.com/navikt/aksel/pull/2456))
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/ds-tokens@5.10.0
|
||||
- @navikt/aksel-icons@5.10.0
|
||||
|
||||
## 5.9.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- :bug: Datepicker lukker ikke lenger modalen den er i ([#2451](https://github.com/navikt/aksel/pull/2451))
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/ds-tokens@5.9.2
|
||||
- @navikt/aksel-icons@5.9.2
|
||||
|
||||
## 5.9.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/ds-tokens@5.9.1
|
||||
- @navikt/aksel-icons@5.9.1
|
||||
|
||||
## 5.9.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Datepicker/Monthpicker: Hvis man bruker komponentene i Modal vil Popover bli erstattet med Modal uansett om man er på desktop eller mobil. ([#2419](https://github.com/navikt/aksel/pull/2419))
|
||||
|
||||
- Datepicker/Monthpicker/Popover: Fjernet `bubbleEscape`-prop. ([#2419](https://github.com/navikt/aksel/pull/2419))
|
||||
|
||||
- useDatepicker/useMonthPicker/useRangedpicker: Fjernet `openOnFocus`-prop, kan nå bare åpnes ved klikk på date-knapp i input. ([#2419](https://github.com/navikt/aksel/pull/2419))
|
||||
|
||||
- Datepicker/Monthpicker: Bytter nå automatisk til Modalvisning på mobil. ([#2419](https://github.com/navikt/aksel/pull/2419))
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Added useVirtualFocus hook - used in Combobox for now ([#2394](https://github.com/navikt/aksel/pull/2394))
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/ds-tokens@5.9.0
|
||||
- @navikt/aksel-icons@5.9.0
|
||||
|
||||
## 5.8.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- :sparkles: Modal: Støtte for å lukke ved klikk utenfor ([#2386](https://github.com/navikt/aksel/pull/2386))
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Autocomplete in combobox will not change formatting of the letters while being typed, but will use the casing of the autocompleted word when selecting the option. ([#2207](https://github.com/navikt/aksel/pull/2207))
|
||||
|
||||
- Accordion: Fikset dom-validering ved bruk av `div` i `button`. ([#2426](https://github.com/navikt/aksel/pull/2426))
|
||||
|
||||
- :bug: Modal: Bedre støtte for Tooltip i Modal ([#2429](https://github.com/navikt/aksel/pull/2429))
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/ds-tokens@5.8.0
|
||||
- @navikt/aksel-icons@5.8.0
|
||||
|
||||
## 5.7.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- :lipstick: Chips.Toggle: Ikon i uvalgt tilstand ([#2395](https://github.com/navikt/aksel/pull/2395))
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/ds-tokens@5.7.6
|
||||
- @navikt/aksel-icons@5.7.6
|
||||
|
||||
## 5.7.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Loader: Har nå riktig `ref`-type. ([#2391](https://github.com/navikt/aksel/pull/2391))
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/ds-tokens@5.7.5
|
||||
- @navikt/aksel-icons@5.7.5
|
||||
|
||||
## 5.7.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-react",
|
||||
"version": "5.7.4",
|
||||
"version": "5.10.0",
|
||||
"description": "Aksel react-components for NAV designsystem",
|
||||
"author": "Aksel | NAV designsystem team",
|
||||
"license": "MIT",
|
||||
@@ -38,8 +38,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@floating-ui/react": "0.25.4",
|
||||
"@navikt/aksel-icons": "^5.7.4",
|
||||
"@navikt/ds-tokens": "^5.7.4",
|
||||
"@navikt/aksel-icons": "^5.10.0",
|
||||
"@navikt/ds-tokens": "^5.10.0",
|
||||
"@radix-ui/react-tabs": "1.0.0",
|
||||
"@radix-ui/react-toggle-group": "1.0.0",
|
||||
"clsx": "^1.2.1",
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { ChevronDownIcon } from "@navikt/aksel-icons";
|
||||
import cl from "clsx";
|
||||
import React, { forwardRef, useContext } from "react";
|
||||
import { Heading } from "../typography";
|
||||
import { AccordionContext } from "./AccordionContext";
|
||||
import { AccordionItemContext } from "./AccordionItem";
|
||||
import { ChevronDownIcon } from "@navikt/aksel-icons";
|
||||
|
||||
export interface AccordionHeaderProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
@@ -37,13 +37,13 @@ const AccordionHeader = forwardRef<HTMLButtonElement, AccordionHeaderProps>(
|
||||
aria-expanded={itemContext.open}
|
||||
type="button"
|
||||
>
|
||||
<div className="navds-accordion__icon-wrapper">
|
||||
<span className="navds-accordion__icon-wrapper">
|
||||
<ChevronDownIcon
|
||||
className="navds-accordion__header-chevron"
|
||||
title="Vis mer"
|
||||
aria-hidden
|
||||
/>
|
||||
</div>
|
||||
</span>
|
||||
<Heading
|
||||
size={accordionContext?.headingSize ?? "small"}
|
||||
as="span"
|
||||
|
||||
@@ -6,7 +6,7 @@ export interface ToggleChipsProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
children: string;
|
||||
/**
|
||||
* Toggles aria-pressed and visual-changes
|
||||
* Toggles aria-pressed and visual changes
|
||||
*/
|
||||
selected?: boolean;
|
||||
/**
|
||||
@@ -44,28 +44,38 @@ export const ToggleChips: OverridableComponent<
|
||||
className={cl(
|
||||
"navds-chips__chip navds-chips__toggle",
|
||||
className,
|
||||
`navds-chips__toggle--${variant}`
|
||||
`navds-chips__toggle--${variant}`,
|
||||
{ "navds-chips__toggle--with-checkmark": checkmark }
|
||||
)}
|
||||
aria-pressed={selected}
|
||||
>
|
||||
{selected && checkmark && (
|
||||
{checkmark && (
|
||||
<svg
|
||||
aria-hidden
|
||||
className="navds-chips__toggle-icon"
|
||||
width="10"
|
||||
height="8"
|
||||
viewBox="0 0 10 8"
|
||||
width="1.25em"
|
||||
height="1.25em"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
focusable={false}
|
||||
role="img"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M9.51894 0.45851C9.81799 0.745107 9.8281 1.21987 9.5415 1.51893L3.7915 7.51893C3.51247 7.81009 3.05315 7.82848 2.75174 7.56056L0.501735 5.56056C0.192148 5.28537 0.164263 4.81132 0.439451 4.50173C0.71464 4.19214 1.18869 4.16426 1.49828 4.43944L3.20835 5.95951L8.45852 0.481072C8.74511 0.182015 9.21988 0.171913 9.51894 0.45851Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
{selected ? (
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M10 18.125C14.4873 18.125 18.125 14.4873 18.125 10C18.125 5.51269 14.4873 1.875 10 1.875C5.51269 1.875 1.875 5.51269 1.875 10C1.875 14.4873 5.51269 18.125 10 18.125ZM14.128 7.72904C14.3695 7.44357 14.3339 7.01635 14.0485 6.7748C13.763 6.53326 13.3358 6.56886 13.0942 6.85432L8.60428 12.1606L6.41627 9.97263C6.15185 9.70822 5.72315 9.70822 5.45873 9.97263C5.19431 10.2371 5.19431 10.6658 5.45873 10.9302L8.16706 13.6385C8.30095 13.7724 8.48479 13.8441 8.67397 13.8362C8.86316 13.8284 9.0404 13.7416 9.16271 13.5971L14.128 7.72904Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
) : (
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M10 3.125C6.20304 3.125 3.125 6.20304 3.125 10C3.125 13.797 6.20304 16.875 10 16.875C13.797 16.875 16.875 13.797 16.875 10C16.875 6.20304 13.797 3.125 10 3.125ZM1.875 10C1.875 5.51269 5.51269 1.875 10 1.875C14.4873 1.875 18.125 5.51269 18.125 10C18.125 14.4873 14.4873 18.125 10 18.125C5.51269 18.125 1.875 14.4873 1.875 10Z"
|
||||
fill="var(--a-border-default)"
|
||||
/>
|
||||
)}
|
||||
</svg>
|
||||
)}
|
||||
<span className="navds-chips__chip-text">{children}</span>
|
||||
|
||||
@@ -60,11 +60,11 @@ Default.argTypes = {
|
||||
},
|
||||
};
|
||||
|
||||
export const Toggle = () => {
|
||||
export const Toggle = ({ size }) => {
|
||||
const [selected, setSelected] = useState<number[]>([2, 4]);
|
||||
return (
|
||||
<div className="colgap">
|
||||
<Chips>
|
||||
<Chips size={size}>
|
||||
{options.map((c, y) => (
|
||||
<Chips.Toggle
|
||||
selected={selected.includes(y)}
|
||||
@@ -81,7 +81,7 @@ export const Toggle = () => {
|
||||
</Chips.Toggle>
|
||||
))}
|
||||
</Chips>
|
||||
<Chips>
|
||||
<Chips size={size}>
|
||||
{options.map((c, y) => (
|
||||
<Chips.Toggle
|
||||
variant="neutral"
|
||||
@@ -102,6 +102,12 @@ export const Toggle = () => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Toggle.argTypes = {
|
||||
size: {
|
||||
control: { type: "radio" },
|
||||
options: ["medium", "small"],
|
||||
},
|
||||
};
|
||||
|
||||
export const ToggleNoCheckmark = () => {
|
||||
const [selected, setSelected] = useState<number>(2);
|
||||
|
||||
@@ -13,13 +13,13 @@ interface DateContextContextProps {
|
||||
* Aria-connected ID
|
||||
*/
|
||||
ariaId?: string;
|
||||
/**
|
||||
* Flag for enabled-check
|
||||
*/
|
||||
defined: boolean;
|
||||
}
|
||||
|
||||
export const DateContext = createContext<DateContextContextProps>({
|
||||
open: false,
|
||||
onOpen: () => null,
|
||||
ariaId: undefined,
|
||||
});
|
||||
export const DateContext = createContext<DateContextContextProps | null>(null);
|
||||
|
||||
export const useDateInputContext = () => {
|
||||
const context = useContext(DateContext);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import cl from "clsx";
|
||||
import isWeekend from "date-fns/isWeekend";
|
||||
import React, { forwardRef, useRef, useState } from "react";
|
||||
import React, { forwardRef, useMemo, useRef, useState } from "react";
|
||||
import { DateRange, DayPicker, isMatch } from "react-day-picker";
|
||||
import { Popover } from "../../popover";
|
||||
import { omit, useId } from "../../util";
|
||||
import { DatePickerInput } from "../DateInput";
|
||||
import { mergeRefs, omit, useId } from "../../util";
|
||||
import { DateContext } from "../context";
|
||||
import { DatePickerInput } from "../parts/DateInput";
|
||||
import { DateWrapper } from "../parts/DateWrapper";
|
||||
import { getLocaleFromString, labels } from "../utils";
|
||||
import DatePickerStandalone from "./DatePickerStandalone";
|
||||
import Caption from "./parts/Caption";
|
||||
@@ -79,7 +79,6 @@ export const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>(
|
||||
onClose,
|
||||
onOpenToggle,
|
||||
strategy,
|
||||
bubbleEscape = false,
|
||||
onWeekNumberClick,
|
||||
...rest
|
||||
},
|
||||
@@ -89,6 +88,7 @@ export const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>(
|
||||
const [open, setOpen] = useState(_open ?? false);
|
||||
|
||||
const wrapperRef = useRef<HTMLDivElement | null>(null);
|
||||
const mergedRef = useMemo(() => mergeRefs([wrapperRef, ref]), [ref]);
|
||||
|
||||
const [selectedDates, setSelectedDates] = React.useState<
|
||||
Date | Date[] | DateRange | undefined
|
||||
@@ -110,6 +110,44 @@ export const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>(
|
||||
rest?.onSelect?.(newSelected);
|
||||
};
|
||||
|
||||
const DatePickerComponent = (
|
||||
<DayPicker
|
||||
locale={getLocaleFromString(locale)}
|
||||
mode={mode}
|
||||
onSelect={handleSelect}
|
||||
selected={selected ?? selectedDates}
|
||||
components={{
|
||||
Caption: dropdownCaption ? DropdownCaption : Caption,
|
||||
Head: TableHead,
|
||||
HeadRow,
|
||||
WeekNumber,
|
||||
Row,
|
||||
Day: DayButton,
|
||||
}}
|
||||
className={cl("navds-date", className)}
|
||||
classNames={{
|
||||
vhidden: "navds-sr-only",
|
||||
}}
|
||||
disabled={(day) => {
|
||||
return (disableWeekends && isWeekend(day)) || isMatch(day, disabled);
|
||||
}}
|
||||
weekStartsOn={1}
|
||||
initialFocus={false}
|
||||
labels={labels as any}
|
||||
modifiers={{
|
||||
weekend: (day) => disableWeekends && isWeekend(day),
|
||||
}}
|
||||
modifiersClassNames={{
|
||||
weekend: "rdp-day__weekend",
|
||||
}}
|
||||
showWeekNumber={showWeekNumber}
|
||||
onWeekNumberClick={mode === "multiple" ? onWeekNumberClick : undefined}
|
||||
fixedWeeks
|
||||
showOutsideDays
|
||||
{...omit(rest, ["onSelect"])}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<DateContext.Provider
|
||||
value={{
|
||||
@@ -119,72 +157,27 @@ export const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>(
|
||||
onOpenToggle?.();
|
||||
},
|
||||
ariaId,
|
||||
defined: true,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
ref={wrapperRef}
|
||||
ref={mergedRef}
|
||||
className={cl("navds-date__wrapper", wrapperClassName)}
|
||||
>
|
||||
{children}
|
||||
{(_open ?? open) && (
|
||||
<Popover
|
||||
arrow={false}
|
||||
anchorEl={wrapperRef.current}
|
||||
open={_open ?? open}
|
||||
onClose={() => {
|
||||
onClose?.() ?? setOpen(false);
|
||||
}}
|
||||
placement="bottom-start"
|
||||
id={ariaId}
|
||||
role="dialog"
|
||||
ref={ref}
|
||||
strategy={strategy}
|
||||
className="navds-date__popover"
|
||||
bubbleEscape={bubbleEscape}
|
||||
flip={false}
|
||||
>
|
||||
<DayPicker
|
||||
locale={getLocaleFromString(locale)}
|
||||
mode={mode}
|
||||
onSelect={handleSelect}
|
||||
selected={selected ?? selectedDates}
|
||||
components={{
|
||||
Caption: dropdownCaption ? DropdownCaption : Caption,
|
||||
Head: TableHead,
|
||||
HeadRow,
|
||||
WeekNumber,
|
||||
Row,
|
||||
Day: DayButton,
|
||||
}}
|
||||
className={cl("navds-date", className)}
|
||||
classNames={{
|
||||
vhidden: "navds-sr-only",
|
||||
}}
|
||||
disabled={(day) => {
|
||||
return (
|
||||
(disableWeekends && isWeekend(day)) ||
|
||||
isMatch(day, disabled)
|
||||
);
|
||||
}}
|
||||
weekStartsOn={1}
|
||||
initialFocus={false}
|
||||
labels={labels as any}
|
||||
modifiers={{
|
||||
weekend: (day) => disableWeekends && isWeekend(day),
|
||||
}}
|
||||
modifiersClassNames={{
|
||||
weekend: "rdp-day__weekend",
|
||||
}}
|
||||
showWeekNumber={showWeekNumber}
|
||||
onWeekNumberClick={
|
||||
mode === "multiple" ? onWeekNumberClick : undefined
|
||||
}
|
||||
fixedWeeks
|
||||
showOutsideDays
|
||||
{...omit(rest, ["onSelect"])}
|
||||
/>
|
||||
</Popover>
|
||||
)}
|
||||
<DateWrapper
|
||||
open={_open ?? open}
|
||||
anchor={wrapperRef.current}
|
||||
onClose={() => onClose?.() ?? setOpen(false)}
|
||||
locale={locale}
|
||||
variant={mode}
|
||||
popoverProps={{
|
||||
id: ariaId,
|
||||
strategy,
|
||||
}}
|
||||
>
|
||||
{DatePickerComponent}
|
||||
</DateWrapper>
|
||||
</div>
|
||||
</DateContext.Provider>
|
||||
);
|
||||
|
||||
@@ -3,8 +3,7 @@ import { Meta, StoryObj } from "@storybook/react";
|
||||
import isSameDay from "date-fns/isSameDay";
|
||||
import React, { useId, useState } from "react";
|
||||
import { useDatepicker, useRangeDatepicker } from "..";
|
||||
import { BodyLong, Button, HGrid, HStack, Modal, VStack } from "../..";
|
||||
|
||||
import { BodyLong, Button, HGrid, Modal, VStack } from "../..";
|
||||
import DatePicker, { DatePickerProps } from "./DatePicker";
|
||||
|
||||
const disabledDays = [
|
||||
@@ -19,7 +18,6 @@ export default {
|
||||
|
||||
type DefaultStoryProps = DatePickerProps & {
|
||||
size: "medium" | "small";
|
||||
openOnFocus: boolean;
|
||||
inputfield: boolean;
|
||||
standalone: boolean;
|
||||
};
|
||||
@@ -31,13 +29,11 @@ export const Default: StoryObj<DefaultStoryProps> = {
|
||||
const rangeCtx = useRangeDatepicker({
|
||||
fromDate: new Date("Aug 23 2020"),
|
||||
toDate: new Date("Aug 23 2023"),
|
||||
openOnFocus: props.openOnFocus,
|
||||
});
|
||||
|
||||
const singleCtx = useDatepicker({
|
||||
fromDate: new Date("Aug 23 2020"),
|
||||
toDate: new Date("Aug 23 2023"),
|
||||
openOnFocus: props.openOnFocus,
|
||||
});
|
||||
|
||||
const newProps = {
|
||||
@@ -112,7 +108,6 @@ export const Default: StoryObj<DefaultStoryProps> = {
|
||||
disableWeekends: false,
|
||||
showWeekNumber: false,
|
||||
mode: "single",
|
||||
openOnFocus: true,
|
||||
inputfield: true,
|
||||
standalone: false,
|
||||
},
|
||||
@@ -187,21 +182,6 @@ export const UseRangedDatepicker = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export const OpenOnFocus = () => {
|
||||
const { datepickerProps, inputProps } = useDatepicker({
|
||||
onDateChange: console.log,
|
||||
openOnFocus: false,
|
||||
});
|
||||
|
||||
return (
|
||||
<div style={{ display: "flex", gap: "1rem" }}>
|
||||
<DatePicker {...datepickerProps}>
|
||||
<DatePicker.Input {...inputProps} label="Velg dato" />
|
||||
</DatePicker>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const NB = () => (
|
||||
<DatePicker.Standalone locale="nb" today={new Date("2006-07-01")} />
|
||||
);
|
||||
@@ -334,15 +314,32 @@ export const Size = () => {
|
||||
fromDate: new Date("Aug 23 2019"),
|
||||
toDate: new Date("Feb 23 2024"),
|
||||
onDateChange: console.log,
|
||||
defaultSelected: new Date("Feb 23 2023"),
|
||||
});
|
||||
const { datepickerProps: d2, inputProps: i2 } = useDatepicker({
|
||||
fromDate: new Date("Aug 23 2019"),
|
||||
toDate: new Date("Feb 23 2024"),
|
||||
onDateChange: console.log,
|
||||
defaultSelected: new Date("Feb 23 2023"),
|
||||
});
|
||||
|
||||
return (
|
||||
<div style={{ display: "flex", gap: "1rem" }}>
|
||||
<DatePicker {...datepickerProps} dropdownCaption>
|
||||
<DatePicker.Input size="medium" {...inputProps} label="Velg dato" />
|
||||
<DatePicker.Input
|
||||
placeholder="10/10/2023"
|
||||
size="medium"
|
||||
{...inputProps}
|
||||
label="Velg dato"
|
||||
/>
|
||||
</DatePicker>
|
||||
<DatePicker {...datepickerProps} dropdownCaption>
|
||||
<DatePicker.Input size="small" {...inputProps} label="Velg dato" />
|
||||
<DatePicker {...d2} dropdownCaption>
|
||||
<DatePicker.Input
|
||||
placeholder="10/10/2023"
|
||||
size="small"
|
||||
{...i2}
|
||||
label="Velg dato"
|
||||
/>
|
||||
</DatePicker>
|
||||
</div>
|
||||
);
|
||||
@@ -440,35 +437,36 @@ export const ModalDemo = () => {
|
||||
toDate: new Date("Feb 23 2024"),
|
||||
onDateChange: console.log,
|
||||
});
|
||||
const [open, setOpen] = useState(true);
|
||||
|
||||
return (
|
||||
<Modal open header={{ heading: "Modal-demo" }} width="1024px">
|
||||
<Modal.Body style={{ position: "relative" }}>
|
||||
<BodyLong>
|
||||
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Maiores nisi
|
||||
incidunt ipsum cupiditate nostrum nesciunt, corrupti nihil at atque
|
||||
animi ab aut. Quam iusto harum eligendi magnam nulla repudiandae
|
||||
molestias.
|
||||
</BodyLong>
|
||||
<>
|
||||
<Button onClick={() => setOpen(!open)}>Open modal</Button>
|
||||
<Modal
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
header={{ heading: "Modal-demo" }}
|
||||
>
|
||||
<Modal.Body style={{ position: "relative" }}>
|
||||
<BodyLong spacing>
|
||||
Lorem ipsum dolor sit, amet consectetur adipisicing elit.
|
||||
</BodyLong>
|
||||
|
||||
<HGrid gap="6" columns={{ xs: 1, lg: 2 }}>
|
||||
<HStack justify="center" align="start">
|
||||
<DatePicker.Standalone
|
||||
fromDate={new Date("Aug 23 2019")}
|
||||
toDate={new Date("Feb 23 2024")}
|
||||
/>
|
||||
</HStack>
|
||||
<DatePicker {...datepickerProps} dropdownCaption>
|
||||
<DatePicker.Input {...inputProps} label="Velg dato" />
|
||||
</DatePicker>
|
||||
<HStack justify="center">
|
||||
<DatePicker.Standalone
|
||||
fromDate={new Date("Aug 23 2019")}
|
||||
toDate={new Date("Feb 23 2024")}
|
||||
<DatePicker.Input
|
||||
{...inputProps}
|
||||
label="Velg dato"
|
||||
description="Format: dd.mm.yyyy"
|
||||
/>
|
||||
</HStack>
|
||||
</HGrid>
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
</DatePicker>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button>Neste</Button>
|
||||
<Button variant="secondary">Tilbake</Button>
|
||||
<Button variant="tertiary">Avbryt</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
ModalDemo.parameters = { chromatic: { pauseAnimationAtEnd: true } };
|
||||
@@ -101,9 +101,4 @@ export interface DatePickerDefaultProps
|
||||
* @default See Popover
|
||||
*/
|
||||
strategy?: "absolute" | "fixed";
|
||||
/**
|
||||
* Bubbles Escape keydown-event up trough DOM-tree. This is set to false by default to prevent closing components like Modal on Escape
|
||||
* @default false
|
||||
*/
|
||||
bubbleEscape?: boolean;
|
||||
}
|
||||
@@ -1,17 +1,15 @@
|
||||
import differenceInCalendarDays from "date-fns/differenceInCalendarDays";
|
||||
import isWeekend from "date-fns/isWeekend";
|
||||
import React, { useCallback, useRef, useState } from "react";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import { DayClickEventHandler, isMatch } from "react-day-picker";
|
||||
import { DateInputProps } from "../DateInput";
|
||||
import { DatePickerProps } from "../datepicker/DatePicker";
|
||||
import { DateInputProps } from "../parts/DateInput";
|
||||
import {
|
||||
formatDateForInput,
|
||||
getLocaleFromString,
|
||||
isValidDate,
|
||||
parseDate,
|
||||
} from "../utils";
|
||||
import { useEscape } from "./useEscape";
|
||||
import { useOutsideClickHandler } from "./useOutsideClickHandler";
|
||||
|
||||
export interface UseDatepickerOptions
|
||||
extends Pick<
|
||||
@@ -59,10 +57,10 @@ export interface UseDatepickerOptions
|
||||
*/
|
||||
allowTwoDigitYear?: boolean;
|
||||
/**
|
||||
* Opens datepicker on input-focus
|
||||
* @default true
|
||||
* Will be removed in a future major-version
|
||||
* @deprecated
|
||||
*/
|
||||
openOnFocus?: boolean;
|
||||
openOnFocus?: false;
|
||||
}
|
||||
|
||||
interface UseDatepickerValue {
|
||||
@@ -76,7 +74,14 @@ interface UseDatepickerValue {
|
||||
inputProps: Pick<
|
||||
DateInputProps,
|
||||
"onChange" | "onFocus" | "onBlur" | "value"
|
||||
> & { ref: React.RefObject<HTMLInputElement> };
|
||||
> & {
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
setAnchorRef: React.Dispatch<
|
||||
React.SetStateAction<HTMLButtonElement | null>
|
||||
>;
|
||||
};
|
||||
/**
|
||||
* Resets all states (callback)
|
||||
*/
|
||||
@@ -143,14 +148,11 @@ export const useDatepicker = (
|
||||
onValidate,
|
||||
defaultMonth,
|
||||
allowTwoDigitYear = true,
|
||||
openOnFocus = true,
|
||||
} = opt;
|
||||
|
||||
const [anchorRef, setAnchorRef] = useState<HTMLButtonElement | null>(null);
|
||||
const locale = getLocaleFromString(_locale);
|
||||
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [daypickerRef, setDaypickerRef] = useState<HTMLDivElement>();
|
||||
|
||||
const [defaultSelected, setDefaultSelected] = useState(_defaultSelected);
|
||||
|
||||
// Initialize states
|
||||
@@ -172,14 +174,6 @@ export const useDatepicker = (
|
||||
[defaultMonth, defaultSelected, selectedDay, today]
|
||||
);
|
||||
|
||||
useOutsideClickHandler(open, handleOpen, [
|
||||
daypickerRef,
|
||||
inputRef.current,
|
||||
inputRef.current?.nextSibling,
|
||||
]);
|
||||
|
||||
useEscape(open, handleOpen, inputRef);
|
||||
|
||||
const updateDate = (date?: Date) => {
|
||||
onDateChange?.(date);
|
||||
setSelectedDay(date);
|
||||
@@ -207,7 +201,6 @@ export const useDatepicker = (
|
||||
if (e.target.readOnly) {
|
||||
return;
|
||||
}
|
||||
!open && openOnFocus && handleOpen(true);
|
||||
const day = parseDate(
|
||||
e.target.value,
|
||||
today,
|
||||
@@ -243,7 +236,7 @@ export const useDatepicker = (
|
||||
const handleDayClick: DayClickEventHandler = (day, { selected }) => {
|
||||
if (day && !selected) {
|
||||
handleOpen(false);
|
||||
inputRef.current && inputRef.current.focus();
|
||||
anchorRef?.focus();
|
||||
}
|
||||
|
||||
if (!required && selected) {
|
||||
@@ -319,11 +312,13 @@ export const useDatepicker = (
|
||||
toDate,
|
||||
today,
|
||||
open,
|
||||
onClose: () => {
|
||||
handleOpen(false);
|
||||
anchorRef?.focus();
|
||||
},
|
||||
onOpenToggle: () => handleOpen(!open),
|
||||
disabled,
|
||||
disableWeekends,
|
||||
bubbleEscape: true,
|
||||
ref: setDaypickerRef,
|
||||
};
|
||||
|
||||
const inputProps = {
|
||||
@@ -331,7 +326,7 @@ export const useDatepicker = (
|
||||
onFocus: handleFocus,
|
||||
onBlur: handleBlur,
|
||||
value: inputValue,
|
||||
ref: inputRef,
|
||||
setAnchorRef,
|
||||
};
|
||||
|
||||
return { datepickerProps, inputProps, reset, selectedDay, setSelected };
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import { useCallback, useEffect, RefObject } from "react";
|
||||
|
||||
export const useEscape = (
|
||||
open: boolean,
|
||||
setOpen: (openState: boolean) => void,
|
||||
focusRef: RefObject<HTMLElement>
|
||||
) => {
|
||||
const handleClose = useCallback(() => {
|
||||
setOpen(false);
|
||||
focusRef?.current && focusRef.current.focus();
|
||||
}, [focusRef, setOpen]);
|
||||
|
||||
const escape = useCallback(
|
||||
(event: KeyboardEvent) => {
|
||||
if (open && event.key === "Escape") {
|
||||
event.preventDefault(); // This prevents modal from closing when using datepicker inside modal
|
||||
handleClose();
|
||||
}
|
||||
},
|
||||
[handleClose, open]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener("keydown", escape, false);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("keydown", escape, false);
|
||||
};
|
||||
}, [escape]);
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useCallback, useMemo, useRef, useState } from "react";
|
||||
import { DateInputProps } from "../DateInput";
|
||||
import React, { useCallback, useMemo, useState } from "react";
|
||||
import { MonthPickerProps } from "../monthpicker/types";
|
||||
import { DateInputProps } from "../parts/DateInput";
|
||||
import {
|
||||
formatDateForInput,
|
||||
getLocaleFromString,
|
||||
@@ -8,8 +8,6 @@ import {
|
||||
isValidDate,
|
||||
parseDate,
|
||||
} from "../utils";
|
||||
import { useEscape } from "./useEscape";
|
||||
import { useOutsideClickHandler } from "./useOutsideClickHandler";
|
||||
|
||||
export interface UseMonthPickerOptions
|
||||
extends Pick<
|
||||
@@ -45,8 +43,8 @@ export interface UseMonthPickerOptions
|
||||
*/
|
||||
allowTwoDigitYear?: boolean;
|
||||
/**
|
||||
* Opens datepicker on input-focus
|
||||
* @default true
|
||||
* Will be removed in a future major-version
|
||||
* @deprecated
|
||||
*/
|
||||
openOnFocus?: boolean;
|
||||
}
|
||||
@@ -60,7 +58,12 @@ interface UseMonthPickerValue {
|
||||
* Use: <MonthPicker.Input {...inputProps} />
|
||||
*/
|
||||
inputProps: Pick<DateInputProps, "onChange" | "onFocus" | "value"> & {
|
||||
ref: React.RefObject<HTMLInputElement>;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
setAnchorRef: React.Dispatch<
|
||||
React.SetStateAction<HTMLButtonElement | null>
|
||||
>;
|
||||
};
|
||||
/**
|
||||
* Currently selected Date
|
||||
@@ -138,17 +141,14 @@ export const useMonthpicker = (
|
||||
onValidate,
|
||||
defaultYear,
|
||||
allowTwoDigitYear = true,
|
||||
openOnFocus = true,
|
||||
} = opt;
|
||||
|
||||
const [anchorRef, setAnchorRef] = useState<HTMLButtonElement | null>(null);
|
||||
const [defaultSelected, setDefaultSelected] = useState(_defaultSelected);
|
||||
|
||||
const today = useMemo(() => new Date(), []);
|
||||
const locale = getLocaleFromString(_locale);
|
||||
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [monthpickerRef, setMonthpickerRef] = useState<HTMLDivElement>();
|
||||
|
||||
// Initialize states
|
||||
const [year, setYear] = useState(defaultSelected ?? defaultYear ?? today);
|
||||
const [selectedMonth, setSelectedMonth] = useState(defaultSelected);
|
||||
@@ -169,14 +169,6 @@ export const useMonthpicker = (
|
||||
[defaultSelected, defaultYear, selectedMonth, today]
|
||||
);
|
||||
|
||||
useOutsideClickHandler(open, handleOpen, [
|
||||
monthpickerRef,
|
||||
inputRef.current,
|
||||
inputRef.current?.nextSibling,
|
||||
]);
|
||||
|
||||
useEscape(open, handleOpen, inputRef);
|
||||
|
||||
const updateMonth = (date?: Date) => {
|
||||
onMonthChange?.(date);
|
||||
setSelectedMonth(date);
|
||||
@@ -204,7 +196,7 @@ export const useMonthpicker = (
|
||||
if (e.target.readOnly) {
|
||||
return;
|
||||
}
|
||||
!open && openOnFocus && handleOpen(true);
|
||||
|
||||
const day = parseDate(
|
||||
e.target.value,
|
||||
today,
|
||||
@@ -239,8 +231,8 @@ export const useMonthpicker = (
|
||||
const handleMonthClick = (month?: Date) => {
|
||||
if (month) {
|
||||
handleOpen(false);
|
||||
inputRef.current && inputRef.current.focus();
|
||||
setYear(month);
|
||||
anchorRef?.focus();
|
||||
}
|
||||
|
||||
if (!required && !month) {
|
||||
@@ -314,9 +306,11 @@ export const useMonthpicker = (
|
||||
toDate,
|
||||
open,
|
||||
onOpenToggle: () => handleOpen(!open),
|
||||
onClose: () => {
|
||||
handleOpen(false);
|
||||
anchorRef?.focus();
|
||||
},
|
||||
disabled,
|
||||
bubbleEscape: true,
|
||||
ref: setMonthpickerRef,
|
||||
};
|
||||
|
||||
const inputProps = {
|
||||
@@ -324,7 +318,7 @@ export const useMonthpicker = (
|
||||
onFocus: handleFocus,
|
||||
onBlur: handleBlur,
|
||||
value: inputValue,
|
||||
ref: inputRef,
|
||||
setAnchorRef,
|
||||
};
|
||||
|
||||
return { monthpickerProps, inputProps, reset, selectedMonth, setSelected };
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import { useCallback, useEffect } from "react";
|
||||
|
||||
export const useOutsideClickHandler = (
|
||||
open: boolean,
|
||||
setOpen: (openState: boolean) => void,
|
||||
refs: Array<any>
|
||||
) => {
|
||||
const handleFocusIn = useCallback(
|
||||
(e) => {
|
||||
const composed = e.composedPath?.()?.[0];
|
||||
if (!e?.target || !e?.target?.nodeType || !composed) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!refs.some(
|
||||
(element) =>
|
||||
element?.contains(e.target) || element?.contains(composed)
|
||||
)
|
||||
) {
|
||||
open && setOpen(false);
|
||||
}
|
||||
},
|
||||
[open, refs, setOpen]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener("focusin", handleFocusIn);
|
||||
window.addEventListener("pointerdown", handleFocusIn);
|
||||
return () => {
|
||||
window?.removeEventListener?.("focusin", handleFocusIn);
|
||||
window?.removeEventListener?.("pointerdown", handleFocusIn);
|
||||
};
|
||||
}, [handleFocusIn]);
|
||||
};
|
||||
@@ -1,10 +1,10 @@
|
||||
import differenceInCalendarDays from "date-fns/differenceInCalendarDays";
|
||||
import checkIsBefore from "date-fns/isBefore";
|
||||
import isWeekend from "date-fns/isWeekend";
|
||||
import React, { useRef, useState } from "react";
|
||||
import React, { useState } from "react";
|
||||
import { DateRange, isMatch } from "react-day-picker";
|
||||
import { DateInputProps } from "../DateInput";
|
||||
import { DatePickerProps } from "../datepicker/DatePicker";
|
||||
import { DateInputProps } from "../parts/DateInput";
|
||||
import {
|
||||
formatDateForInput,
|
||||
getLocaleFromString,
|
||||
@@ -12,8 +12,6 @@ import {
|
||||
parseDate,
|
||||
} from "../utils";
|
||||
import { DateValidationT, UseDatepickerOptions } from "./useDatepicker";
|
||||
import { useEscape } from "./useEscape";
|
||||
import { useOutsideClickHandler } from "./useOutsideClickHandler";
|
||||
|
||||
export type RangeValidationT = {
|
||||
from: DateValidationT;
|
||||
@@ -74,14 +72,28 @@ interface UseRangeDatepickerValue {
|
||||
fromInputProps: Pick<
|
||||
DateInputProps,
|
||||
"onChange" | "onFocus" | "onBlur" | "value"
|
||||
> & { ref: React.RefObject<HTMLInputElement> };
|
||||
> & {
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
setAnchorRef: React.Dispatch<
|
||||
React.SetStateAction<HTMLButtonElement | null>
|
||||
>;
|
||||
};
|
||||
/**
|
||||
* Use: <DatePicker.Input label="to" {...toInputProps}/>
|
||||
*/
|
||||
toInputProps: Pick<
|
||||
DateInputProps,
|
||||
"onChange" | "onFocus" | "onBlur" | "value"
|
||||
> & { ref: React.RefObject<HTMLInputElement> };
|
||||
> & {
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
setAnchorRef?: React.Dispatch<
|
||||
React.SetStateAction<HTMLButtonElement | null>
|
||||
>;
|
||||
};
|
||||
/**
|
||||
* Resets all states (callback)
|
||||
*/
|
||||
@@ -218,14 +230,11 @@ export const useRangeDatepicker = (
|
||||
onValidate,
|
||||
defaultMonth,
|
||||
allowTwoDigitYear = true,
|
||||
openOnFocus = true,
|
||||
} = opt;
|
||||
|
||||
const locale = getLocaleFromString(_locale);
|
||||
const [anchorRef, setAnchorRef] = useState<HTMLButtonElement | null>(null);
|
||||
|
||||
const inputRefTo = useRef<HTMLInputElement>(null);
|
||||
const inputRefFrom = useRef<HTMLInputElement>(null);
|
||||
const [daypickerRef, setDaypickerRef] = useState<HTMLDivElement>();
|
||||
const locale = getLocaleFromString(_locale);
|
||||
|
||||
const [defaultSelected, setDefaultSelected] = useState(_defaultSelected);
|
||||
|
||||
@@ -255,20 +264,6 @@ export const useRangeDatepicker = (
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
useOutsideClickHandler(open, setOpen, [
|
||||
daypickerRef,
|
||||
inputRefTo.current,
|
||||
inputRefFrom.current,
|
||||
inputRefTo.current?.nextSibling,
|
||||
inputRefFrom.current?.nextSibling,
|
||||
]);
|
||||
|
||||
useEscape(
|
||||
open,
|
||||
setOpen,
|
||||
selectedRange?.from && !selectedRange?.to ? inputRefTo : inputRefFrom
|
||||
);
|
||||
|
||||
const updateRange = (range?: DateRange) => {
|
||||
onRangeChange?.(range);
|
||||
setSelectedRange(range);
|
||||
@@ -324,7 +319,6 @@ export const useRangeDatepicker = (
|
||||
if (e.target.readOnly) {
|
||||
return;
|
||||
}
|
||||
!open && openOnFocus && setOpen(true);
|
||||
const day = parseDate(
|
||||
e.target.value,
|
||||
today,
|
||||
@@ -378,6 +372,7 @@ export const useRangeDatepicker = (
|
||||
const handleSelect = (range) => {
|
||||
if (range?.from && range?.to) {
|
||||
setOpen(false);
|
||||
anchorRef?.focus();
|
||||
}
|
||||
const prevToRange =
|
||||
!selectedRange?.from && selectedRange?.to ? selectedRange?.to : range?.to;
|
||||
@@ -545,10 +540,12 @@ export const useRangeDatepicker = (
|
||||
mode: "range" as const,
|
||||
open,
|
||||
onOpenToggle: () => setOpen((x) => !x),
|
||||
onClose: () => {
|
||||
setOpen(false);
|
||||
anchorRef?.focus();
|
||||
},
|
||||
disabled,
|
||||
disableWeekends,
|
||||
bubbleEscape: true,
|
||||
ref: setDaypickerRef,
|
||||
};
|
||||
|
||||
const fromInputProps = {
|
||||
@@ -556,7 +553,7 @@ export const useRangeDatepicker = (
|
||||
onFocus: (e) => handleFocus(e, RANGE.FROM),
|
||||
onBlur: (e) => handleBlur(e, RANGE.FROM),
|
||||
value: fromInputValue,
|
||||
ref: inputRefFrom,
|
||||
setAnchorRef,
|
||||
};
|
||||
|
||||
const toInputProps = {
|
||||
@@ -564,7 +561,7 @@ export const useRangeDatepicker = (
|
||||
onFocus: (e) => handleFocus(e, RANGE.TO),
|
||||
onBlur: (e) => handleBlur(e, RANGE.TO),
|
||||
value: toInputValue,
|
||||
ref: inputRefTo,
|
||||
setAnchorRef,
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
export { type DateInputProps } from "./DateInput";
|
||||
export {
|
||||
default as DatePicker,
|
||||
type DatePickerProps,
|
||||
@@ -15,3 +14,4 @@ export {
|
||||
export { default as MonthPicker } from "./monthpicker/MonthPicker";
|
||||
export { type MonthPickerStandaloneProps } from "./monthpicker/MonthPickerStandalone";
|
||||
export { type MonthPickerProps } from "./monthpicker/types";
|
||||
export { type DateInputProps } from "./parts/DateInput";
|
||||
@@ -1,10 +1,10 @@
|
||||
import cl from "clsx";
|
||||
import React, { forwardRef, useRef, useState } from "react";
|
||||
import React, { forwardRef, useMemo, useRef, useState } from "react";
|
||||
import { RootProvider } from "react-day-picker";
|
||||
import { Popover } from "../../popover";
|
||||
import { useId } from "../../util";
|
||||
import { MonthPickerInput } from "../DateInput";
|
||||
import { mergeRefs, useId } from "../../util";
|
||||
import { DateContext, SharedMonthProvider } from "../context";
|
||||
import { MonthPickerInput } from "../parts/DateInput";
|
||||
import { DateWrapper } from "../parts/DateWrapper";
|
||||
import { getLocaleFromString } from "../utils";
|
||||
import MonthCaption from "./MonthCaption";
|
||||
import MonthPickerStandalone from "./MonthPickerStandalone";
|
||||
@@ -74,7 +74,6 @@ export const MonthPicker = forwardRef<HTMLDivElement, MonthPickerProps>(
|
||||
year,
|
||||
onYearChange,
|
||||
strategy = "absolute",
|
||||
bubbleEscape = false,
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
@@ -82,6 +81,7 @@ export const MonthPicker = forwardRef<HTMLDivElement, MonthPickerProps>(
|
||||
const [open, setOpen] = useState(_open ?? false);
|
||||
|
||||
const wrapperRef = useRef<HTMLDivElement | null>(null);
|
||||
const mergedRef = useMemo(() => mergeRefs([wrapperRef, ref]), [ref]);
|
||||
|
||||
const [selectedMonth, setSelectedMonth] = useState<Date | undefined>(
|
||||
defaultSelected
|
||||
@@ -107,51 +107,47 @@ export const MonthPicker = forwardRef<HTMLDivElement, MonthPickerProps>(
|
||||
onOpenToggle?.();
|
||||
},
|
||||
ariaId,
|
||||
defined: true,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
ref={wrapperRef}
|
||||
ref={mergedRef}
|
||||
className={cl("navds-date__wrapper", wrapperClassName)}
|
||||
>
|
||||
{children}
|
||||
{(_open ?? open) && (
|
||||
<Popover
|
||||
arrow={false}
|
||||
anchorEl={wrapperRef.current}
|
||||
open={_open ?? open}
|
||||
onClose={() => onClose?.() ?? setOpen(false)}
|
||||
placement="bottom-start"
|
||||
role="dialog"
|
||||
ref={ref}
|
||||
id={ariaId}
|
||||
className="navds-date navds-date__popover"
|
||||
strategy={strategy}
|
||||
bubbleEscape={bubbleEscape}
|
||||
flip={false}
|
||||
<DateWrapper
|
||||
open={_open ?? open}
|
||||
anchor={wrapperRef.current}
|
||||
onClose={() => onClose?.() ?? setOpen(false)}
|
||||
locale={locale}
|
||||
variant="month"
|
||||
popoverProps={{
|
||||
id: ariaId,
|
||||
strategy,
|
||||
}}
|
||||
>
|
||||
<RootProvider
|
||||
locale={getLocaleFromString(locale)}
|
||||
selected={selected ?? selectedMonth}
|
||||
toDate={toDate}
|
||||
fromDate={fromDate}
|
||||
month={selected ?? selectedMonth}
|
||||
>
|
||||
<RootProvider
|
||||
locale={getLocaleFromString(locale)}
|
||||
selected={selected ?? selectedMonth}
|
||||
toDate={toDate}
|
||||
fromDate={fromDate}
|
||||
month={selected ?? selectedMonth}
|
||||
>
|
||||
<div className={cl("rdp-month", className)}>
|
||||
<SharedMonthProvider
|
||||
dropdownCaption={dropdownCaption}
|
||||
disabled={disabled}
|
||||
selected={selected ?? selectedMonth}
|
||||
onSelect={handleSelect}
|
||||
year={year}
|
||||
onYearChange={onYearChange}
|
||||
>
|
||||
<MonthCaption />
|
||||
<MonthSelector />
|
||||
</SharedMonthProvider>
|
||||
</div>
|
||||
</RootProvider>
|
||||
</Popover>
|
||||
)}
|
||||
<div className={cl("rdp-month", className)}>
|
||||
<SharedMonthProvider
|
||||
dropdownCaption={dropdownCaption}
|
||||
disabled={disabled}
|
||||
selected={selected ?? selectedMonth}
|
||||
onSelect={handleSelect}
|
||||
year={year}
|
||||
onYearChange={onYearChange}
|
||||
>
|
||||
<MonthCaption />
|
||||
<MonthSelector />
|
||||
</SharedMonthProvider>
|
||||
</div>
|
||||
</RootProvider>
|
||||
</DateWrapper>
|
||||
</div>
|
||||
</DateContext.Provider>
|
||||
);
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
import setYear from "date-fns/setYear";
|
||||
import React, { useId, useState } from "react";
|
||||
import { Button, DateInputProps } from "../..";
|
||||
import { useMonthpicker } from "../hooks";
|
||||
import MonthPicker, { MonthPickerProps } from "./MonthPicker";
|
||||
import MonthPicker from "./MonthPicker";
|
||||
import { MonthPickerProps } from "./types";
|
||||
|
||||
export default {
|
||||
title: "ds-react/Monthpicker",
|
||||
@@ -147,3 +149,28 @@ export const UserControlled = () => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const FollowYear = () => {
|
||||
const { monthpickerProps, inputProps, selectedMonth, setSelected } =
|
||||
useMonthpicker({
|
||||
fromDate: new Date("Aug 23 2019"),
|
||||
toDate: new Date("Aug 23 2025"),
|
||||
onMonthChange: console.log,
|
||||
});
|
||||
|
||||
const customYearChange = (yearDate?: Date) => {
|
||||
monthpickerProps.onYearChange?.(yearDate);
|
||||
if (selectedMonth && yearDate) {
|
||||
setSelected(setYear(selectedMonth, yearDate.getFullYear()));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-96">
|
||||
<MonthPicker {...monthpickerProps} onYearChange={customYearChange}>
|
||||
<MonthPicker.Input {...inputProps} label="Velg månede" />
|
||||
</MonthPicker>
|
||||
{selectedMonth && <div className="pt-4">{selectedMonth.getMonth()}</div>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -75,9 +75,4 @@ export interface MonthPickerProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
* @default "absolute"
|
||||
*/
|
||||
strategy?: "absolute" | "fixed";
|
||||
/**
|
||||
* Bubbles Escape keydown-event up trough DOM-tree. This is set to false by default to prevent closing components like Modal on Escape
|
||||
* @default false
|
||||
*/
|
||||
bubbleEscape?: boolean;
|
||||
}
|
||||
+23
-12
@@ -1,11 +1,11 @@
|
||||
import { CalendarIcon } from "@navikt/aksel-icons";
|
||||
import cl from "clsx";
|
||||
import React, { forwardRef, InputHTMLAttributes } from "react";
|
||||
import { FormFieldProps, useFormField } from "../form/useFormField";
|
||||
import { useDateInputContext } from "./context";
|
||||
import { ReadOnlyIcon } from "../form/ReadOnlyIcon";
|
||||
import { BodyShort, ErrorMessage, Label } from "../typography";
|
||||
import { omit } from "../util";
|
||||
import React, { forwardRef, InputHTMLAttributes, useRef } from "react";
|
||||
import { ReadOnlyIcon } from "../../form/ReadOnlyIcon";
|
||||
import { FormFieldProps, useFormField } from "../../form/useFormField";
|
||||
import { BodyShort, ErrorMessage, Label } from "../../typography";
|
||||
import { omit } from "../../util";
|
||||
import { useDateInputContext } from "../context";
|
||||
|
||||
export interface DateInputProps
|
||||
extends FormFieldProps,
|
||||
@@ -28,6 +28,10 @@ export interface DateInputProps
|
||||
* @private
|
||||
*/
|
||||
variant?: "datepicker" | "monthpicker";
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
setAnchorRef?: React.Dispatch<React.SetStateAction<HTMLButtonElement | null>>;
|
||||
}
|
||||
|
||||
const DateInput = forwardRef<HTMLInputElement, DateInputProps>((props, ref) => {
|
||||
@@ -37,9 +41,12 @@ const DateInput = forwardRef<HTMLInputElement, DateInputProps>((props, ref) => {
|
||||
label,
|
||||
description,
|
||||
variant = "datepicker",
|
||||
setAnchorRef,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
const isDatepickerVariant = variant === "datepicker";
|
||||
|
||||
const conditionalVariables = {
|
||||
@@ -50,7 +57,7 @@ const DateInput = forwardRef<HTMLInputElement, DateInputProps>((props, ref) => {
|
||||
},
|
||||
};
|
||||
|
||||
const { onOpen, ariaId, open } = useDateInputContext();
|
||||
const context = useDateInputContext();
|
||||
|
||||
const {
|
||||
inputProps,
|
||||
@@ -108,7 +115,7 @@ const DateInput = forwardRef<HTMLInputElement, DateInputProps>((props, ref) => {
|
||||
{...omit(rest, ["error", "errorId", "size"])}
|
||||
{...inputProps}
|
||||
autoComplete="off"
|
||||
aria-controls={open ? ariaId : undefined}
|
||||
aria-controls={context?.open ? context.ariaId : undefined}
|
||||
readOnly={readOnly}
|
||||
className={cl(
|
||||
"navds-date__field-input",
|
||||
@@ -116,19 +123,23 @@ const DateInput = forwardRef<HTMLInputElement, DateInputProps>((props, ref) => {
|
||||
"navds-body-short",
|
||||
`navds-body-short--${size}`
|
||||
)}
|
||||
size={isDatepickerVariant ? 10 : 14}
|
||||
size={isDatepickerVariant ? 11 : 14}
|
||||
/>
|
||||
<button
|
||||
disabled={inputProps.disabled || readOnly}
|
||||
tabIndex={readOnly ? -1 : open ? -1 : 0}
|
||||
onClick={() => onOpen()}
|
||||
tabIndex={readOnly ? -1 : context?.open ? -1 : 0}
|
||||
onClick={() => {
|
||||
context?.onOpen();
|
||||
setAnchorRef?.(buttonRef.current);
|
||||
}}
|
||||
type="button"
|
||||
className="navds-date__field-button"
|
||||
ref={buttonRef}
|
||||
>
|
||||
<CalendarIcon
|
||||
pointerEvents="none"
|
||||
title={
|
||||
open
|
||||
context?.open
|
||||
? conditionalVariables.iconTitle.close
|
||||
: conditionalVariables.iconTitle.open
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import cl from "clsx";
|
||||
import React, { useContext, useRef } from "react";
|
||||
import { Button } from "../../button";
|
||||
import { Modal } from "../../modal";
|
||||
import { ModalContext } from "../../modal/ModalContext";
|
||||
import { Popover } from "../../popover";
|
||||
import { useMedia } from "../../util/useMedia";
|
||||
import { modalCloseButtonLabel, modalLabel } from "../utils/labels";
|
||||
|
||||
type DateWrapperProps = {
|
||||
open: boolean;
|
||||
children: React.ReactNode;
|
||||
onClose: () => void;
|
||||
anchor: HTMLDivElement | null;
|
||||
locale: "nb" | "nn" | "en";
|
||||
variant: "single" | "multiple" | "range" | "month";
|
||||
popoverProps: {
|
||||
id?: string;
|
||||
strategy?: "absolute" | "fixed";
|
||||
};
|
||||
};
|
||||
|
||||
export const DateWrapper = ({
|
||||
open,
|
||||
children,
|
||||
onClose,
|
||||
anchor,
|
||||
locale,
|
||||
variant,
|
||||
popoverProps,
|
||||
}: DateWrapperProps) => {
|
||||
const modalRef = useRef<HTMLDialogElement>(null);
|
||||
const isInModal = useContext(ModalContext) !== null;
|
||||
const hideModal =
|
||||
useMedia("screen and (min-width: 768px)", true) && !isInModal;
|
||||
|
||||
if (hideModal) {
|
||||
return (
|
||||
<Popover
|
||||
arrow={false}
|
||||
anchorEl={anchor}
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
placement="bottom-start"
|
||||
role="dialog"
|
||||
className={cl("navds-date__popover", {
|
||||
"navds-date": variant === "month",
|
||||
})}
|
||||
flip={false}
|
||||
{...popoverProps}
|
||||
>
|
||||
{children}
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Modal
|
||||
ref={modalRef}
|
||||
open={open}
|
||||
onClose={(event) => {
|
||||
event.stopPropagation();
|
||||
onClose();
|
||||
}}
|
||||
aria-label={modalLabel(locale, variant)}
|
||||
className={cl("navds-date__modal", {
|
||||
"navds-date__nested-modal": isInModal,
|
||||
"navds-date": variant === "month",
|
||||
})}
|
||||
closeOnBackdropClick
|
||||
>
|
||||
<div className="navds-date__modal-body">
|
||||
{children}
|
||||
<Button
|
||||
variant="tertiary"
|
||||
onClick={() => modalRef?.current?.close()}
|
||||
size="small"
|
||||
>
|
||||
{modalCloseButtonLabel(locale)}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -133,3 +133,86 @@ export const labelWeek = (localeCode?: string): string => {
|
||||
return `Uke:`;
|
||||
}
|
||||
};
|
||||
|
||||
const modalLabelSingle = (localeCode?: string): string => {
|
||||
switch (localeCode) {
|
||||
case "nb":
|
||||
return `Velg dato`;
|
||||
case "nn":
|
||||
return `Vel dato`;
|
||||
case "en-GB":
|
||||
return `Pick a date`;
|
||||
default:
|
||||
return `Velg dato`;
|
||||
}
|
||||
};
|
||||
|
||||
const modalLabelMultiple = (localeCode?: string): string => {
|
||||
switch (localeCode) {
|
||||
case "nb":
|
||||
return `Velg datoer`;
|
||||
case "nn":
|
||||
return `Vel datoar`;
|
||||
case "en-GB":
|
||||
return `Pick dates`;
|
||||
default:
|
||||
return `Velg datoer`;
|
||||
}
|
||||
};
|
||||
|
||||
const modalLabelRanged = (localeCode?: string): string => {
|
||||
switch (localeCode) {
|
||||
case "nb":
|
||||
return `Velg start- og sluttdato`;
|
||||
case "nn":
|
||||
return `Vel start- og sluttdato`;
|
||||
case "en-GB":
|
||||
return `Pick a start and end date`;
|
||||
default:
|
||||
return `Velg start- og sluttdato`;
|
||||
}
|
||||
};
|
||||
|
||||
const modalLabelMonth = (localeCode?: string): string => {
|
||||
switch (localeCode) {
|
||||
case "nb":
|
||||
return `Velg måned`;
|
||||
case "nn":
|
||||
return `Vel månad`;
|
||||
case "en-GB":
|
||||
return `Pick a month`;
|
||||
default:
|
||||
return `Velg måned`;
|
||||
}
|
||||
};
|
||||
|
||||
export const modalLabel = (
|
||||
localeCode: string,
|
||||
variant: "single" | "multiple" | "range" | "month"
|
||||
) => {
|
||||
switch (variant) {
|
||||
case "single":
|
||||
return modalLabelSingle(localeCode);
|
||||
case "multiple":
|
||||
return modalLabelMultiple(localeCode);
|
||||
case "range":
|
||||
return modalLabelRanged(localeCode);
|
||||
case "month":
|
||||
return modalLabelMonth(localeCode);
|
||||
default:
|
||||
return modalLabelSingle(localeCode);
|
||||
}
|
||||
};
|
||||
|
||||
export const modalCloseButtonLabel = (localeCode?: string): string => {
|
||||
switch (localeCode) {
|
||||
case "nb":
|
||||
return `Lukk`;
|
||||
case "nn":
|
||||
return `Lukk`;
|
||||
case "en-GB":
|
||||
return `Close`;
|
||||
default:
|
||||
return `Lukk`;
|
||||
}
|
||||
};
|
||||
@@ -33,7 +33,7 @@ export const Combobox = forwardRef<
|
||||
|
||||
const toggleListButtonRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
const { currentOption, toggleIsListOpen } = useFilteredOptionsContext();
|
||||
const { activeDecendantId, toggleIsListOpen } = useFilteredOptionsContext();
|
||||
const { selectedOptions } = useSelectedOptionsContext();
|
||||
|
||||
const {
|
||||
@@ -92,7 +92,7 @@ export const Combobox = forwardRef<
|
||||
"navds-combobox__wrapper-inner navds-text-field__input",
|
||||
{
|
||||
"navds-combobox__wrapper-inner--virtually-unfocused":
|
||||
currentOption !== null,
|
||||
activeDecendantId !== null,
|
||||
}
|
||||
)}
|
||||
onClick={focusInput}
|
||||
|
||||
@@ -65,7 +65,7 @@ const ComboboxProvider = forwardRef<HTMLInputElement, ComboboxProps>(
|
||||
size,
|
||||
}}
|
||||
>
|
||||
<CustomOptionsProvider>
|
||||
<CustomOptionsProvider value={{ isMultiSelect }}>
|
||||
<SelectedOptionsProvider
|
||||
value={{
|
||||
allowNewValues,
|
||||
@@ -81,7 +81,6 @@ const ComboboxProvider = forwardRef<HTMLInputElement, ComboboxProps>(
|
||||
filteredOptions,
|
||||
isListOpen,
|
||||
isLoading,
|
||||
isMultiSelect,
|
||||
options,
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useSelectedOptionsContext } from "../SelectedOptions/selectedOptionsCon
|
||||
import { useInputContext } from "../Input/inputContext";
|
||||
import { Loader } from "../../../loader";
|
||||
import { BodyShort, Label } from "../../../typography";
|
||||
import filteredOptionsUtil from "./filtered-options-util";
|
||||
|
||||
const FilteredOptions = () => {
|
||||
const {
|
||||
@@ -18,25 +19,25 @@ const FilteredOptions = () => {
|
||||
isLoading,
|
||||
isListOpen,
|
||||
filteredOptions,
|
||||
filteredOptionsIndex,
|
||||
filteredOptionsRef,
|
||||
setFilteredOptionsRef,
|
||||
isMouseLastUsedInputDevice,
|
||||
setIsMouseLastUsedInputDevice,
|
||||
isValueNew,
|
||||
setFilteredOptionsIndex,
|
||||
toggleIsListOpen,
|
||||
activeDecendantId,
|
||||
virtualFocus,
|
||||
} = useFilteredOptionsContext();
|
||||
const { isMultiSelect, selectedOptions, toggleOption } =
|
||||
useSelectedOptionsContext();
|
||||
|
||||
return (
|
||||
<ul
|
||||
ref={filteredOptionsRef}
|
||||
ref={setFilteredOptionsRef}
|
||||
className={cl("navds-combobox__list", {
|
||||
"navds-combobox__list--closed": !isListOpen,
|
||||
"navds-combobox__list--with-hover": isMouseLastUsedInputDevice,
|
||||
})}
|
||||
id={`${id}-filtered-options`}
|
||||
id={filteredOptionsUtil.getFilteredOptionsId(id)}
|
||||
role="listbox"
|
||||
tabIndex={-1}
|
||||
>
|
||||
@@ -45,7 +46,8 @@ const FilteredOptions = () => {
|
||||
className="navds-combobox__list-item--loading"
|
||||
role="option"
|
||||
aria-selected={false}
|
||||
id={`${id}-is-loading`}
|
||||
id={filteredOptionsUtil.getIsLoadingId(id)}
|
||||
data-no-focus="true"
|
||||
>
|
||||
<Loader aria-label="Søker..." />
|
||||
</li>
|
||||
@@ -54,8 +56,12 @@ const FilteredOptions = () => {
|
||||
<li
|
||||
tabIndex={-1}
|
||||
onMouseMove={() => {
|
||||
if (filteredOptionsIndex !== -1) {
|
||||
setFilteredOptionsIndex(-1);
|
||||
if (
|
||||
activeDecendantId !== filteredOptionsUtil.getAddNewOptionId(id)
|
||||
) {
|
||||
virtualFocus.moveFocusToElement(
|
||||
filteredOptionsUtil.getAddNewOptionId(id)
|
||||
);
|
||||
setIsMouseLastUsedInputDevice(true);
|
||||
}
|
||||
}}
|
||||
@@ -64,10 +70,10 @@ const FilteredOptions = () => {
|
||||
if (!isMultiSelect && !selectedOptions.includes(value))
|
||||
toggleIsListOpen(false);
|
||||
}}
|
||||
id={`${id}-combobox-new-option`}
|
||||
id={filteredOptionsUtil.getAddNewOptionId(id)}
|
||||
className={cl("navds-combobox__list-item__new-option", {
|
||||
"navds-combobox__list-item__new-option--focus":
|
||||
filteredOptionsIndex === -1,
|
||||
activeDecendantId === filteredOptionsUtil.getAddNewOptionId(id),
|
||||
})}
|
||||
role="option"
|
||||
aria-selected={false}
|
||||
@@ -86,24 +92,30 @@ const FilteredOptions = () => {
|
||||
className="navds-combobox__list-item__no-options"
|
||||
role="option"
|
||||
aria-selected={false}
|
||||
id={`${id}-no-hits`}
|
||||
id={filteredOptionsUtil.getNoHitsId(id)}
|
||||
data-no-focus="true"
|
||||
>
|
||||
Ingen søketreff
|
||||
</li>
|
||||
)}
|
||||
{filteredOptions.map((option, index) => (
|
||||
{filteredOptions.map((option) => (
|
||||
<li
|
||||
className={cl("navds-combobox__list-item", {
|
||||
"navds-combobox__list-item--focus": index === filteredOptionsIndex,
|
||||
"navds-combobox__list-item--focus":
|
||||
activeDecendantId === filteredOptionsUtil.getOptionId(id, option),
|
||||
"navds-combobox__list-item--selected":
|
||||
selectedOptions.includes(option),
|
||||
})}
|
||||
id={`${id}-option-${option.replace(" ", "-")}`}
|
||||
id={filteredOptionsUtil.getOptionId(id, option)}
|
||||
key={option}
|
||||
tabIndex={-1}
|
||||
onMouseMove={() => {
|
||||
if (filteredOptionsIndex !== index) {
|
||||
setFilteredOptionsIndex(index);
|
||||
if (
|
||||
activeDecendantId !== filteredOptionsUtil.getOptionId(id, option)
|
||||
) {
|
||||
virtualFocus.moveFocusToElement(
|
||||
filteredOptionsUtil.getOptionId(id, option)
|
||||
);
|
||||
setIsMouseLastUsedInputDevice(true);
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
const normalizeText = (text: string): string =>
|
||||
typeof text === "string" ? text.toLocaleLowerCase().trim() : "";
|
||||
|
||||
const isPartOfText = (value, text) =>
|
||||
normalizeText(text).startsWith(normalizeText(value ?? ""));
|
||||
|
||||
const isValueInList = (value, list) =>
|
||||
list?.find((listItem) => normalizeText(value) === normalizeText(listItem));
|
||||
|
||||
const getMatchingValuesFromList = (value, list) =>
|
||||
list?.filter((listItem) => isPartOfText(value, listItem));
|
||||
|
||||
const getFilteredOptionsId = (comboboxId: string) =>
|
||||
`${comboboxId}-filtered-options`;
|
||||
|
||||
const getOptionId = (comboboxId: string, option: string) =>
|
||||
`${comboboxId.toLocaleLowerCase()}-option-${option
|
||||
.replace(" ", "-")
|
||||
.toLocaleLowerCase()}`;
|
||||
|
||||
const getAddNewOptionId = (comboboxId: string) =>
|
||||
`${comboboxId}-combobox-new-option`;
|
||||
|
||||
const getIsLoadingId = (comboboxId: string) => `${comboboxId}-is-loading`;
|
||||
|
||||
const getNoHitsId = (comboboxId: string) => `${comboboxId}-no-hits`;
|
||||
|
||||
export default {
|
||||
normalizeText,
|
||||
isPartOfText,
|
||||
isValueInList,
|
||||
getMatchingValuesFromList,
|
||||
getFilteredOptionsId,
|
||||
getAddNewOptionId,
|
||||
getOptionId,
|
||||
getIsLoadingId,
|
||||
getNoHitsId,
|
||||
};
|
||||
@@ -1,11 +1,9 @@
|
||||
import React, {
|
||||
useState,
|
||||
useEffect,
|
||||
useMemo,
|
||||
createContext,
|
||||
useContext,
|
||||
useCallback,
|
||||
useRef,
|
||||
SetStateAction,
|
||||
} from "react";
|
||||
import cl from "clsx";
|
||||
@@ -13,26 +11,29 @@ import { useCustomOptionsContext } from "../customOptionsContext";
|
||||
import { useInputContext } from "../Input/inputContext";
|
||||
import usePrevious from "../../../util/usePrevious";
|
||||
import { useClientLayoutEffect } from "../../../util";
|
||||
import filteredOptionsUtils from "./filtered-options-util";
|
||||
import useVirtualFocus, { VirtualFocusType } from "./useVirtualFocus";
|
||||
import { ComboboxProps } from "../types";
|
||||
|
||||
const normalizeText = (text: string): string =>
|
||||
typeof text === "string" ? `${text}`.toLowerCase().trim() : "";
|
||||
|
||||
const isPartOfText = (value, text) =>
|
||||
normalizeText(text).startsWith(normalizeText(value ?? ""));
|
||||
|
||||
const isValueInList = (value, list) =>
|
||||
list?.find((listItem) => normalizeText(value) === normalizeText(listItem));
|
||||
|
||||
const getMatchingValuesFromList = (value, list) =>
|
||||
list?.filter((listItem) => isPartOfText(value, listItem));
|
||||
type FilteredOptionsProps = {
|
||||
children: any;
|
||||
value: Pick<
|
||||
ComboboxProps,
|
||||
| "allowNewValues"
|
||||
| "filteredOptions"
|
||||
| "isListOpen"
|
||||
| "isLoading"
|
||||
| "options"
|
||||
>;
|
||||
};
|
||||
|
||||
type FilteredOptionsContextType = {
|
||||
activeDecendantId?: string;
|
||||
allowNewValues?: boolean;
|
||||
ariaDescribedBy?: string;
|
||||
filteredOptionsRef: React.RefObject<HTMLUListElement>;
|
||||
filteredOptionsIndex: number | null;
|
||||
setFilteredOptionsIndex: (index: number) => void;
|
||||
setFilteredOptionsRef: React.Dispatch<
|
||||
React.SetStateAction<HTMLUListElement | null>
|
||||
>;
|
||||
isListOpen: boolean;
|
||||
isLoading?: boolean;
|
||||
filteredOptions: string[];
|
||||
@@ -40,19 +41,18 @@ type FilteredOptionsContextType = {
|
||||
setIsMouseLastUsedInputDevice: React.Dispatch<SetStateAction<boolean>>;
|
||||
isValueNew: boolean;
|
||||
toggleIsListOpen: (newState?: boolean) => void;
|
||||
currentOption: string | null;
|
||||
resetFilteredOptionsIndex: () => void;
|
||||
moveFocusUp: () => void;
|
||||
moveFocusDown: () => void;
|
||||
moveFocusToInput: () => void;
|
||||
moveFocusToEnd: () => void;
|
||||
currentOption?: string;
|
||||
shouldAutocomplete?: boolean;
|
||||
virtualFocus: VirtualFocusType;
|
||||
};
|
||||
const FilteredOptionsContext = createContext<FilteredOptionsContextType>(
|
||||
{} as FilteredOptionsContextType
|
||||
);
|
||||
|
||||
export const FilteredOptionsProvider = ({ children, value: props }) => {
|
||||
export const FilteredOptionsProvider = ({
|
||||
children,
|
||||
value: props,
|
||||
}: FilteredOptionsProps) => {
|
||||
const {
|
||||
allowNewValues,
|
||||
filteredOptions: externalFilteredOptions,
|
||||
@@ -60,7 +60,9 @@ export const FilteredOptionsProvider = ({ children, value: props }) => {
|
||||
isLoading,
|
||||
options,
|
||||
} = props;
|
||||
const filteredOptionsRef = useRef<HTMLUListElement | null>(null);
|
||||
const [filteredOptionsRef, setFilteredOptionsRef] =
|
||||
useState<HTMLUListElement | null>(null);
|
||||
const virtualFocus = useVirtualFocus(filteredOptionsRef);
|
||||
const {
|
||||
inputProps: { "aria-describedby": partialAriaDescribedBy, id },
|
||||
value,
|
||||
@@ -70,9 +72,6 @@ export const FilteredOptionsProvider = ({ children, value: props }) => {
|
||||
shouldAutocomplete,
|
||||
} = useInputContext();
|
||||
|
||||
const [filteredOptionsIndex, setFilteredOptionsIndex] = useState<
|
||||
number | null
|
||||
>(null);
|
||||
const [isInternalListOpen, setInternalListOpen] = useState(false);
|
||||
const { customOptions } = useCustomOptionsContext();
|
||||
|
||||
@@ -81,8 +80,7 @@ export const FilteredOptionsProvider = ({ children, value: props }) => {
|
||||
return externalFilteredOptions;
|
||||
}
|
||||
const opts = [...customOptions, ...options];
|
||||
setFilteredOptionsIndex(null);
|
||||
return getMatchingValuesFromList(searchTerm, opts);
|
||||
return filteredOptionsUtils.getMatchingValuesFromList(searchTerm, opts);
|
||||
}, [customOptions, externalFilteredOptions, options, searchTerm]);
|
||||
|
||||
const previousSearchTerm = usePrevious(searchTerm);
|
||||
@@ -90,13 +88,28 @@ export const FilteredOptionsProvider = ({ children, value: props }) => {
|
||||
const [isMouseLastUsedInputDevice, setIsMouseLastUsedInputDevice] =
|
||||
useState(false);
|
||||
|
||||
const filteredOptionsMap = useMemo(
|
||||
() =>
|
||||
options.reduce(
|
||||
(map, _option) => ({
|
||||
...map,
|
||||
[filteredOptionsUtils.getOptionId(id, _option)]: _option,
|
||||
}),
|
||||
{
|
||||
[filteredOptionsUtils.getAddNewOptionId(id)]: allowNewValues
|
||||
? value
|
||||
: undefined,
|
||||
}
|
||||
),
|
||||
[allowNewValues, id, options, value]
|
||||
);
|
||||
|
||||
useClientLayoutEffect(() => {
|
||||
if (
|
||||
shouldAutocomplete &&
|
||||
normalizeText(searchTerm) !== "" &&
|
||||
filteredOptionsUtils.normalizeText(searchTerm) !== "" &&
|
||||
(previousSearchTerm?.length || 0) < searchTerm.length &&
|
||||
filteredOptions.length > 0 &&
|
||||
!isValueInList(searchTerm, filteredOptions)
|
||||
filteredOptions.length > 0
|
||||
) {
|
||||
setValue(
|
||||
`${searchTerm}${filteredOptions[0].substring(searchTerm.length)}`
|
||||
@@ -116,29 +129,30 @@ export const FilteredOptionsProvider = ({ children, value: props }) => {
|
||||
return isExternalListOpen ?? isInternalListOpen;
|
||||
}, [isExternalListOpen, isInternalListOpen]);
|
||||
|
||||
const toggleIsListOpen = useCallback((newState?: boolean) => {
|
||||
setFilteredOptionsIndex(null);
|
||||
setInternalListOpen((oldState) => newState ?? !oldState);
|
||||
}, []);
|
||||
|
||||
const isValueNew = useMemo(
|
||||
() => Boolean(value) && !isValueInList(value, filteredOptions),
|
||||
[value, filteredOptions]
|
||||
const toggleIsListOpen = useCallback(
|
||||
(newState?: boolean) => {
|
||||
virtualFocus.moveFocusToTop();
|
||||
setInternalListOpen((oldState) => newState ?? !oldState);
|
||||
},
|
||||
[virtualFocus]
|
||||
);
|
||||
|
||||
const getMinimumIndex = useCallback(() => {
|
||||
return isValueNew && allowNewValues ? -1 : 0;
|
||||
}, [allowNewValues, isValueNew]);
|
||||
const isValueNew = useMemo(
|
||||
() =>
|
||||
Boolean(value) &&
|
||||
!filteredOptionsMap[filteredOptionsUtils.getOptionId(id, value)],
|
||||
[filteredOptionsMap, id, value]
|
||||
);
|
||||
|
||||
const ariaDescribedBy = useMemo(() => {
|
||||
let activeOption;
|
||||
if (!isLoading && filteredOptions.length === 0) {
|
||||
activeOption = `${id}-no-hits`;
|
||||
activeOption = filteredOptionsUtils.getNoHitsId(id);
|
||||
} else if ((value && value !== "") || isLoading) {
|
||||
if (shouldAutocomplete && filteredOptions[0]) {
|
||||
activeOption = `${id}-option-${filteredOptions[0].replace(" ", "-")}`;
|
||||
activeOption = filteredOptionsUtils.getOptionId(id, filteredOptions[0]);
|
||||
} else if (isListOpen && isLoading) {
|
||||
activeOption = `${id}-is-loading`;
|
||||
activeOption = filteredOptionsUtils.getIsLoadingId(id);
|
||||
}
|
||||
}
|
||||
return cl(activeOption, partialAriaDescribedBy) || undefined;
|
||||
@@ -152,102 +166,21 @@ export const FilteredOptionsProvider = ({ children, value: props }) => {
|
||||
id,
|
||||
]);
|
||||
|
||||
const currentOption = useMemo(() => {
|
||||
if (filteredOptionsIndex == null) {
|
||||
return null;
|
||||
}
|
||||
if (filteredOptionsIndex === -1) {
|
||||
return value;
|
||||
}
|
||||
return filteredOptions[filteredOptionsIndex];
|
||||
}, [filteredOptionsIndex, filteredOptions, value]);
|
||||
const currentOption = useMemo(
|
||||
() =>
|
||||
filteredOptionsMap[virtualFocus.activeElement?.getAttribute("id") || -1],
|
||||
[filteredOptionsMap, virtualFocus]
|
||||
);
|
||||
|
||||
const resetFilteredOptionsIndex = () => {
|
||||
setFilteredOptionsIndex(getMinimumIndex());
|
||||
};
|
||||
|
||||
const scrollToOption = useCallback((newIndex: number) => {
|
||||
if (
|
||||
filteredOptionsRef.current &&
|
||||
filteredOptionsRef.current.children[newIndex]
|
||||
) {
|
||||
const child = filteredOptionsRef.current.children[newIndex];
|
||||
const { top, bottom } = child.getBoundingClientRect();
|
||||
const parentRect = filteredOptionsRef.current.getBoundingClientRect();
|
||||
if (top < parentRect.top || bottom > parentRect.bottom) {
|
||||
child.scrollIntoView({ block: "nearest" });
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (filteredOptionsIndex !== null && isListOpen) {
|
||||
scrollToOption(filteredOptionsIndex);
|
||||
}
|
||||
}, [filteredOptionsIndex, isListOpen, scrollToOption]);
|
||||
|
||||
const moveFocusToInput = useCallback(() => {
|
||||
setFilteredOptionsIndex(null);
|
||||
toggleIsListOpen(false);
|
||||
}, [toggleIsListOpen]);
|
||||
|
||||
const moveFocusToEnd = useCallback(() => {
|
||||
const lastIndex = filteredOptions.length - 1;
|
||||
toggleIsListOpen(true);
|
||||
setFilteredOptionsIndex(lastIndex);
|
||||
}, [filteredOptions.length, toggleIsListOpen]);
|
||||
|
||||
const moveFocusUp = useCallback(() => {
|
||||
if (filteredOptionsIndex === null) {
|
||||
return;
|
||||
}
|
||||
if (filteredOptionsIndex === getMinimumIndex()) {
|
||||
toggleIsListOpen(false);
|
||||
setFilteredOptionsIndex(null);
|
||||
} else {
|
||||
const newIndex = Math.max(getMinimumIndex(), filteredOptionsIndex - 1);
|
||||
setFilteredOptionsIndex(newIndex);
|
||||
}
|
||||
}, [filteredOptionsIndex, getMinimumIndex, toggleIsListOpen]);
|
||||
|
||||
const moveFocusDown = useCallback(() => {
|
||||
if (filteredOptionsIndex === null || !isListOpen) {
|
||||
toggleIsListOpen(true);
|
||||
if (allowNewValues || filteredOptions.length >= 1) {
|
||||
setFilteredOptionsIndex(getMinimumIndex());
|
||||
}
|
||||
return;
|
||||
}
|
||||
const newIndex = Math.min(
|
||||
filteredOptionsIndex + 1,
|
||||
Math.max(getMinimumIndex(), filteredOptions.length - 1)
|
||||
);
|
||||
setFilteredOptionsIndex(newIndex);
|
||||
}, [
|
||||
allowNewValues,
|
||||
filteredOptions.length,
|
||||
filteredOptionsIndex,
|
||||
getMinimumIndex,
|
||||
isListOpen,
|
||||
toggleIsListOpen,
|
||||
]);
|
||||
|
||||
const activeDecendantId = useMemo(() => {
|
||||
if (filteredOptionsIndex === null) {
|
||||
return undefined;
|
||||
} else if (filteredOptionsIndex === -1) {
|
||||
return `${id}-combobox-new-option`;
|
||||
} else {
|
||||
return `${id}-option-${currentOption?.replace(" ", "-")}`;
|
||||
}
|
||||
}, [filteredOptionsIndex, currentOption, id]);
|
||||
const activeDecendantId = useMemo(
|
||||
() => virtualFocus.activeElement?.getAttribute("id") || undefined,
|
||||
[virtualFocus.activeElement]
|
||||
);
|
||||
|
||||
const filteredOptionsState = {
|
||||
activeDecendantId,
|
||||
allowNewValues,
|
||||
filteredOptionsRef,
|
||||
filteredOptionsIndex,
|
||||
setFilteredOptionsIndex,
|
||||
setFilteredOptionsRef,
|
||||
shouldAutocomplete,
|
||||
isListOpen,
|
||||
isLoading,
|
||||
@@ -257,11 +190,7 @@ export const FilteredOptionsProvider = ({ children, value: props }) => {
|
||||
isValueNew,
|
||||
toggleIsListOpen,
|
||||
currentOption,
|
||||
resetFilteredOptionsIndex,
|
||||
moveFocusUp,
|
||||
moveFocusDown,
|
||||
moveFocusToInput,
|
||||
moveFocusToEnd,
|
||||
virtualFocus,
|
||||
ariaDescribedBy,
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import { Dispatch, SetStateAction, useState } from "react";
|
||||
|
||||
export type VirtualFocusType = {
|
||||
activeElement: HTMLElement | undefined;
|
||||
getElementById: (id: string) => HTMLElement | undefined;
|
||||
isFocusOnTheTop: boolean;
|
||||
isFocusOnTheBottom: boolean;
|
||||
setIndex: Dispatch<SetStateAction<number>>;
|
||||
moveFocusUp: () => void;
|
||||
moveFocusDown: () => void;
|
||||
moveFocusToElement: (id: string) => void;
|
||||
moveFocusToTop: () => void;
|
||||
moveFocusToBottom: () => void;
|
||||
};
|
||||
|
||||
const useVirtualFocus = (
|
||||
containerRef: HTMLElement | null
|
||||
): VirtualFocusType => {
|
||||
const [index, setIndex] = useState(-1);
|
||||
|
||||
const listOfAllChildren: Array<HTMLElement> = containerRef?.children
|
||||
? Array.prototype.slice.call(containerRef?.children)
|
||||
: [];
|
||||
const elementsAbleToReceiveFocus = listOfAllChildren.filter(
|
||||
(child) => child.getAttribute("data-no-focus") !== "true"
|
||||
);
|
||||
|
||||
const activeElement = elementsAbleToReceiveFocus[index];
|
||||
const getElementById = (id: string) =>
|
||||
listOfAllChildren.find((element) => element.id === id);
|
||||
const isFocusOnTheTop = index === 0;
|
||||
const isFocusOnTheBottom = index === elementsAbleToReceiveFocus.length - 1;
|
||||
|
||||
const scrollToOption = (newIndex: number) => {
|
||||
const indexOfElementToScrollTo = Math.min(
|
||||
Math.max(newIndex, 0),
|
||||
containerRef?.children.length || 0
|
||||
);
|
||||
if (containerRef?.children[indexOfElementToScrollTo]) {
|
||||
const child = containerRef.children[indexOfElementToScrollTo];
|
||||
const { top, bottom } = child.getBoundingClientRect();
|
||||
const parentRect = containerRef.getBoundingClientRect();
|
||||
if (top < parentRect.top || bottom > parentRect.bottom) {
|
||||
child.scrollIntoView({ block: "nearest" });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const _moveFocusAndScrollTo = (_index: number) => {
|
||||
setIndex(_index);
|
||||
scrollToOption(_index);
|
||||
};
|
||||
const moveFocusUp = () => _moveFocusAndScrollTo(Math.max(index - 1, -1));
|
||||
const moveFocusDown = () =>
|
||||
_moveFocusAndScrollTo(
|
||||
Math.min(index + 1, elementsAbleToReceiveFocus.length - 1)
|
||||
);
|
||||
const moveFocusToTop = () => _moveFocusAndScrollTo(-1);
|
||||
const moveFocusToBottom = () =>
|
||||
_moveFocusAndScrollTo(elementsAbleToReceiveFocus.length - 1);
|
||||
const moveFocusToElement = (id: string) => {
|
||||
const thisElement = elementsAbleToReceiveFocus.find(
|
||||
(_element) => _element.getAttribute("id") === id
|
||||
);
|
||||
const indexOfElement = thisElement
|
||||
? elementsAbleToReceiveFocus.indexOf(thisElement)
|
||||
: -1;
|
||||
if (indexOfElement >= 0) {
|
||||
setIndex(indexOfElement);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
activeElement,
|
||||
getElementById,
|
||||
isFocusOnTheTop,
|
||||
isFocusOnTheBottom,
|
||||
setIndex,
|
||||
moveFocusUp,
|
||||
moveFocusDown,
|
||||
moveFocusToElement,
|
||||
moveFocusToTop,
|
||||
moveFocusToBottom,
|
||||
};
|
||||
};
|
||||
|
||||
export default useVirtualFocus;
|
||||
@@ -9,6 +9,7 @@ import cl from "clsx";
|
||||
import { useSelectedOptionsContext } from "../SelectedOptions/selectedOptionsContext";
|
||||
import { useFilteredOptionsContext } from "../FilteredOptions/filteredOptionsContext";
|
||||
import { useInputContext } from "./inputContext";
|
||||
import filteredOptionsUtil from "../FilteredOptions/filtered-options-util";
|
||||
|
||||
interface InputProps
|
||||
extends Omit<InputHTMLAttributes<HTMLInputElement>, "value"> {
|
||||
@@ -31,44 +32,55 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
allowNewValues,
|
||||
currentOption,
|
||||
filteredOptions,
|
||||
isValueNew,
|
||||
toggleIsListOpen,
|
||||
isListOpen,
|
||||
filteredOptionsIndex,
|
||||
moveFocusUp,
|
||||
moveFocusDown,
|
||||
ariaDescribedBy,
|
||||
moveFocusToInput,
|
||||
moveFocusToEnd,
|
||||
setFilteredOptionsIndex,
|
||||
setIsMouseLastUsedInputDevice,
|
||||
shouldAutocomplete,
|
||||
virtualFocus,
|
||||
} = useFilteredOptionsContext();
|
||||
|
||||
const onEnter = useCallback(
|
||||
(event: React.KeyboardEvent) => {
|
||||
const isTextInSelectedOptions = (text: string) => {
|
||||
return selectedOptions.find(
|
||||
(item) => item.toLocaleLowerCase() === text.toLocaleLowerCase()
|
||||
);
|
||||
};
|
||||
|
||||
if (currentOption) {
|
||||
event.preventDefault();
|
||||
// Selecting a value from the dropdown / FilteredOptions
|
||||
toggleOption(currentOption, event);
|
||||
if (!isMultiSelect && !selectedOptions.includes(currentOption))
|
||||
if (!isMultiSelect && !isTextInSelectedOptions(currentOption)) {
|
||||
toggleIsListOpen(false);
|
||||
} else if (shouldAutocomplete && selectedOptions.includes(value)) {
|
||||
}
|
||||
} else if (shouldAutocomplete && isTextInSelectedOptions(value)) {
|
||||
event.preventDefault();
|
||||
// Trying to set the same value that is already set, so just clearing the input
|
||||
clearInput(event);
|
||||
} else if ((allowNewValues || shouldAutocomplete) && value !== "") {
|
||||
event.preventDefault();
|
||||
// Autocompleting or adding a new value
|
||||
toggleOption(value, event);
|
||||
if (!isMultiSelect && !selectedOptions.includes(value))
|
||||
const selectedValue =
|
||||
allowNewValues && isValueNew ? value : filteredOptions[0];
|
||||
toggleOption(selectedValue, event);
|
||||
if (
|
||||
!isMultiSelect &&
|
||||
!isTextInSelectedOptions(filteredOptions[0] || selectedValue)
|
||||
) {
|
||||
toggleIsListOpen(false);
|
||||
}
|
||||
}
|
||||
},
|
||||
[
|
||||
allowNewValues,
|
||||
clearInput,
|
||||
currentOption,
|
||||
filteredOptions,
|
||||
isMultiSelect,
|
||||
isValueNew,
|
||||
selectedOptions,
|
||||
shouldAutocomplete,
|
||||
toggleIsListOpen,
|
||||
@@ -89,10 +101,10 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
onEnter(e);
|
||||
break;
|
||||
case "Home":
|
||||
moveFocusToInput();
|
||||
virtualFocus.moveFocusToTop();
|
||||
break;
|
||||
case "End":
|
||||
moveFocusToEnd();
|
||||
virtualFocus.moveFocusToBottom();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -113,14 +125,20 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
// so we don't interfere with text editing
|
||||
if (e.target.selectionStart === value?.length) {
|
||||
e.preventDefault();
|
||||
moveFocusDown();
|
||||
if (virtualFocus.activeElement === null || !isListOpen) {
|
||||
toggleIsListOpen(true);
|
||||
}
|
||||
virtualFocus.moveFocusDown();
|
||||
}
|
||||
} else if (e.key === "ArrowUp") {
|
||||
// Check that the FilteredOptions list is open and has virtual focus.
|
||||
// Otherwise ignore keystrokes, so it doesn't interfere with text editing
|
||||
if (isListOpen && filteredOptionsIndex !== null) {
|
||||
if (isListOpen && activeDecendantId) {
|
||||
e.preventDefault();
|
||||
moveFocusUp();
|
||||
if (virtualFocus.isFocusOnTheTop) {
|
||||
toggleIsListOpen(false);
|
||||
}
|
||||
virtualFocus.moveFocusUp();
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -128,11 +146,11 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
value,
|
||||
selectedOptions,
|
||||
removeSelectedOption,
|
||||
moveFocusDown,
|
||||
isListOpen,
|
||||
filteredOptionsIndex,
|
||||
moveFocusUp,
|
||||
activeDecendantId,
|
||||
setIsMouseLastUsedInputDevice,
|
||||
toggleIsListOpen,
|
||||
virtualFocus,
|
||||
]
|
||||
);
|
||||
|
||||
@@ -144,13 +162,14 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
} else if (filteredOptions.length === 0) {
|
||||
toggleIsListOpen(false);
|
||||
}
|
||||
virtualFocus.moveFocusToTop();
|
||||
onChange(event);
|
||||
},
|
||||
[filteredOptions.length, onChange, toggleIsListOpen]
|
||||
[filteredOptions.length, virtualFocus, onChange, toggleIsListOpen]
|
||||
);
|
||||
|
||||
const onBlur = () => {
|
||||
setFilteredOptionsIndex(-1);
|
||||
virtualFocus.moveFocusToTop();
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -165,7 +184,7 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
onBlur={onBlur}
|
||||
onKeyUp={handleKeyUp}
|
||||
onKeyDown={handleKeyDown}
|
||||
aria-controls={`${inputProps.id}-filtered-options`}
|
||||
aria-controls={filteredOptionsUtil.getFilteredOptionsId(inputProps.id)}
|
||||
aria-expanded={!!isListOpen}
|
||||
autoComplete="off"
|
||||
aria-autocomplete={shouldAutocomplete ? "both" : "list"}
|
||||
|
||||
@@ -467,6 +467,50 @@ export const TestThatCallbacksOnlyFireWhenExpected: StoryObj<{
|
||||
},
|
||||
};
|
||||
|
||||
export const TestCasingWhenAutoCompleting = {
|
||||
args: {
|
||||
onChange: jest.fn(),
|
||||
onClear: jest.fn(),
|
||||
onToggleSelected: jest.fn(),
|
||||
},
|
||||
render: (props) => {
|
||||
return (
|
||||
<UNSAFE_Combobox
|
||||
options={["Camel Case", "lowercase", "UPPERCASE"]}
|
||||
label="Liker du best store eller små bokstaver?"
|
||||
shouldAutocomplete
|
||||
allowNewValues
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const input = canvas.getByRole<HTMLInputElement>("combobox");
|
||||
|
||||
// With exisiting option
|
||||
userEvent.click(input);
|
||||
await userEvent.type(input, "cAmEl CaSe", { delay: 250 });
|
||||
await sleep(250);
|
||||
expect(input.value).toBe("cAmEl CaSe");
|
||||
await userEvent.type(input, "{Enter}");
|
||||
await sleep(250);
|
||||
const chips = canvas.getAllByRole("list")[0];
|
||||
const selectedUpperCaseChip = within(chips).getAllByRole("listitem")[0];
|
||||
expect(selectedUpperCaseChip).toHaveTextContent("Camel Case"); // A weird issue is preventing the accessible name from being used in the test, even if it works in VoiceOver
|
||||
|
||||
// With custom option
|
||||
userEvent.click(input);
|
||||
await userEvent.type(input, "cAmEl{Backspace}", { delay: 250 });
|
||||
await sleep(250);
|
||||
expect(input.value).toBe("cAmEl");
|
||||
await userEvent.type(input, "{Enter}");
|
||||
await sleep(250);
|
||||
const selectedNewValueChip = within(chips).getAllByRole("listitem")[0];
|
||||
expect(selectedNewValueChip).toHaveTextContent("cAmEl"); // A weird issue is preventing the accessible name from being used in the test, even if it works in VoiceOver
|
||||
},
|
||||
};
|
||||
|
||||
export const TestHoverAndFocusSwitching: StoryObject = {
|
||||
render: () => {
|
||||
return (
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { useState, useCallback, createContext, useContext } from "react";
|
||||
import { useInputContext } from "./Input/inputContext";
|
||||
import { useSelectedOptionsContext } from "./SelectedOptions/selectedOptionsContext";
|
||||
|
||||
type CustomOptionsContextType = {
|
||||
customOptions: string[];
|
||||
@@ -13,13 +12,19 @@ const CustomOptionsContext = createContext<CustomOptionsContextType>(
|
||||
{} as CustomOptionsContextType
|
||||
);
|
||||
|
||||
export const CustomOptionsProvider = ({ children }) => {
|
||||
export const CustomOptionsProvider = ({
|
||||
children,
|
||||
value,
|
||||
}: {
|
||||
children: any;
|
||||
value: { isMultiSelect?: boolean };
|
||||
}) => {
|
||||
const [customOptions, setCustomOptions] = useState<string[]>([]);
|
||||
const { focusInput } = useInputContext();
|
||||
const { isMultiSelect } = useSelectedOptionsContext();
|
||||
const { isMultiSelect } = value;
|
||||
|
||||
const removeCustomOption = useCallback(
|
||||
(option) => {
|
||||
(option: string) => {
|
||||
setCustomOptions((prevCustomOptions) =>
|
||||
prevCustomOptions.filter((o) => o !== option)
|
||||
);
|
||||
@@ -29,7 +34,7 @@ export const CustomOptionsProvider = ({ children }) => {
|
||||
);
|
||||
|
||||
const addCustomOption = useCallback(
|
||||
(option) => {
|
||||
(option: string) => {
|
||||
if (isMultiSelect) {
|
||||
setCustomOptions((prevOptions) => [...prevOptions, option]);
|
||||
} else {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { InformationIcon } from "@navikt/aksel-icons";
|
||||
import { Meta } from "@storybook/react";
|
||||
import React from "react";
|
||||
import { BodyLong, GuidePanel, VStack } from "../index";
|
||||
import { Meta } from "@storybook/react";
|
||||
import { InformationIcon } from "@navikt/aksel-icons";
|
||||
|
||||
export default {
|
||||
title: "ds-react/GuidePanel",
|
||||
|
||||
@@ -38,3 +38,4 @@ export * from "./layout/stack";
|
||||
export * from "./layout/grid";
|
||||
export * from "./layout/content-container";
|
||||
export * from "./layout/responsive";
|
||||
export * from "./layout/page";
|
||||
@@ -1,10 +1,7 @@
|
||||
import cl from "clsx";
|
||||
import React, { forwardRef } from "react";
|
||||
import {
|
||||
ResponsiveProp,
|
||||
SpacingScale,
|
||||
getResponsiveProps,
|
||||
} from "../utilities/css";
|
||||
import { getResponsiveProps } from "../utilities/css";
|
||||
import { ResponsiveProp, SpacingScale } from "../utilities/types";
|
||||
import { Slot } from "../../util/Slot";
|
||||
|
||||
export type BleedSpacingInline = "0" | "full" | "px" | SpacingScale;
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import cl from "clsx";
|
||||
import React, { forwardRef } from "react";
|
||||
import { OverridableComponent } from "../../util/OverridableComponent";
|
||||
import { getResponsiveProps } from "../utilities/css";
|
||||
import {
|
||||
SpaceDelimitedAttribute,
|
||||
ResponsiveProp,
|
||||
SpacingScale,
|
||||
getResponsiveProps,
|
||||
} from "../utilities/css";
|
||||
import {
|
||||
BackgroundToken,
|
||||
BorderColorToken,
|
||||
ShadowToken,
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import React, { forwardRef, HTMLAttributes } from "react";
|
||||
import cl from "clsx";
|
||||
import {
|
||||
getResponsiveProps,
|
||||
getResponsiveValue,
|
||||
ResponsiveProp,
|
||||
SpacingScale,
|
||||
} from "../utilities/css";
|
||||
import { getResponsiveProps, getResponsiveValue } from "../utilities/css";
|
||||
import { ResponsiveProp, SpacingScale } from "../utilities/types";
|
||||
|
||||
export interface HGridProps extends HTMLAttributes<HTMLDivElement> {
|
||||
children: React.ReactNode;
|
||||
|
||||
@@ -0,0 +1,271 @@
|
||||
import bgColors from "@navikt/ds-tokens/src/colors-bg.json";
|
||||
import type { Meta, StoryFn } from "@storybook/react";
|
||||
import React, { useEffect } from "react";
|
||||
import { Box } from "../box";
|
||||
import { HGrid } from "../grid";
|
||||
import Page from "./Page";
|
||||
import { widths } from "./parts/PageBlock";
|
||||
|
||||
const meta: Meta = {
|
||||
title: "ds-react/Primitives/Page",
|
||||
component: Page,
|
||||
parameters: {
|
||||
layout: "fullscreen",
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
export const Default: StoryFn = ({
|
||||
belowFold,
|
||||
background,
|
||||
width,
|
||||
gutters,
|
||||
contentBlockPadding,
|
||||
}) => (
|
||||
<Page
|
||||
footer={<Footer width={width} gutters={gutters} />}
|
||||
footerPosition={belowFold ? "belowFold" : undefined}
|
||||
background={background}
|
||||
contentBlockPadding={contentBlockPadding}
|
||||
>
|
||||
<Header width={width} gutters={gutters} />
|
||||
<Content width={width} gutters={gutters} />
|
||||
</Page>
|
||||
);
|
||||
|
||||
Default.argTypes = {
|
||||
width: {
|
||||
control: "radio",
|
||||
options: widths,
|
||||
},
|
||||
background: {
|
||||
control: "radio",
|
||||
options: Object.keys(bgColors.a),
|
||||
},
|
||||
belowFold: {
|
||||
control: "boolean",
|
||||
},
|
||||
gutters: {
|
||||
control: "boolean",
|
||||
},
|
||||
contentBlockPadding: {
|
||||
control: "radio",
|
||||
options: ["end", "none"],
|
||||
},
|
||||
};
|
||||
|
||||
Default.args = {
|
||||
belowFold: false,
|
||||
gutters: false,
|
||||
contentBlockPadding: "end",
|
||||
};
|
||||
|
||||
export const BelowFold: StoryFn = () => (
|
||||
<Page
|
||||
footer={<Footer />}
|
||||
footerPosition="belowFold"
|
||||
contentBlockPadding="end"
|
||||
>
|
||||
<Header />
|
||||
<Content />
|
||||
</Page>
|
||||
);
|
||||
|
||||
export const Background: StoryFn = () => (
|
||||
<HGrid columns={2} gap="4">
|
||||
<Page
|
||||
background="bg-default"
|
||||
footer={<div>footer</div>}
|
||||
contentBlockPadding="end"
|
||||
>
|
||||
<div>header</div>
|
||||
<div>content</div>
|
||||
</Page>
|
||||
<Page
|
||||
background="bg-subtle"
|
||||
footer={<div>footer</div>}
|
||||
contentBlockPadding="end"
|
||||
>
|
||||
<div>header</div>
|
||||
<div>content</div>
|
||||
</Page>
|
||||
</HGrid>
|
||||
);
|
||||
|
||||
export const ContentBlockPadding: StoryFn = () => (
|
||||
<HGrid columns={2} gap="6" align="start">
|
||||
<Page
|
||||
footer={<Footer width="lg" gutters />}
|
||||
contentBlockPadding="end"
|
||||
background="bg-subtle"
|
||||
>
|
||||
<Header width="lg" gutters />
|
||||
<Page.Block width="lg" gutters as="main">
|
||||
<Box background="surface-alt-3-subtle" style={{ height: "80vh" }}>
|
||||
Main
|
||||
</Box>
|
||||
</Page.Block>
|
||||
</Page>
|
||||
<Page
|
||||
footer={<Footer width="lg" gutters />}
|
||||
contentBlockPadding="none"
|
||||
background="bg-subtle"
|
||||
>
|
||||
<Header width="lg" gutters />
|
||||
<Page.Block width="lg" gutters as="main">
|
||||
<Box background="surface-alt-3-subtle" style={{ height: "80vh" }}>
|
||||
Main
|
||||
</Box>
|
||||
</Page.Block>
|
||||
</Page>
|
||||
</HGrid>
|
||||
);
|
||||
|
||||
export const Gutters: StoryFn = () => (
|
||||
<HGrid columns={2} gap="6" align="start">
|
||||
<Page footer={<Footer width="lg" gutters />} background="bg-subtle">
|
||||
<Header width="lg" gutters />
|
||||
<Page.Block width="lg" gutters as="main">
|
||||
<Box background="surface-alt-3-subtle" style={{ height: "80vh" }}>
|
||||
Main
|
||||
</Box>
|
||||
</Page.Block>
|
||||
</Page>
|
||||
<Page footer={<Footer width="lg" />} background="bg-subtle">
|
||||
<Header width="lg" />
|
||||
<Page.Block width="lg" as="main">
|
||||
<Box background="surface-alt-3-subtle" style={{ height: "80vh" }}>
|
||||
Main
|
||||
</Box>
|
||||
</Page.Block>
|
||||
</Page>
|
||||
</HGrid>
|
||||
);
|
||||
|
||||
Gutters.parameters = {
|
||||
chromatic: {
|
||||
modes: {
|
||||
mobile_portrait: {
|
||||
viewport: {
|
||||
width: 400,
|
||||
height: 850,
|
||||
},
|
||||
},
|
||||
desktop: {
|
||||
viewport: {
|
||||
width: 1280,
|
||||
height: 960,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const MILJO_URL = "https://www.nav.no/dekoratoren";
|
||||
|
||||
export const WithDecorator: StoryFn = () => {
|
||||
return (
|
||||
<Page
|
||||
contentBlockPadding="end"
|
||||
footerPosition="belowFold"
|
||||
footer={<div id="decorator-footer"></div>}
|
||||
>
|
||||
<div id="decorator-header"></div>
|
||||
<Content width="lg" gutters />
|
||||
<div
|
||||
id="decorator-env"
|
||||
data-src={`${MILJO_URL}/env?context=privatperson`}
|
||||
></div>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
WithDecorator.decorators = [
|
||||
(Story) => {
|
||||
useEffect(() => {
|
||||
const script = document.createElement("script");
|
||||
script.src = `${MILJO_URL}/client.js`;
|
||||
script.async = true;
|
||||
document.body.appendChild(script);
|
||||
|
||||
const styles = document.createElement("link");
|
||||
styles.href = `${MILJO_URL}/css/client.css`;
|
||||
styles.rel = "stylesheet";
|
||||
document.head.appendChild(styles);
|
||||
|
||||
return () => {
|
||||
document.body.removeChild(script);
|
||||
document.head.removeChild(styles);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return <Story />;
|
||||
},
|
||||
];
|
||||
|
||||
WithDecorator.parameters = {
|
||||
chromatic: {
|
||||
disable: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const OutsideBackground = () => {
|
||||
return (
|
||||
<Page
|
||||
contentBlockPadding="end"
|
||||
footer={
|
||||
<Box
|
||||
background="surface-alt-3-subtle"
|
||||
style={{ height: 100 }}
|
||||
as="footer"
|
||||
>
|
||||
<Page.Block width="lg" gutters>
|
||||
Footer
|
||||
</Page.Block>
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
<Box background="surface-alt-1-subtle" style={{ height: 64 }} as="header">
|
||||
<Page.Block width="lg" gutters>
|
||||
Header
|
||||
</Page.Block>
|
||||
</Box>
|
||||
<Box background="surface-alt-2-subtle" style={{ height: 300 }} as="main">
|
||||
<Page.Block width="lg" gutters>
|
||||
main
|
||||
</Page.Block>
|
||||
</Box>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
function Header({ width = "lg", gutters = false }: any) {
|
||||
return (
|
||||
<Page.Block as="header" width={width} gutters={gutters}>
|
||||
<Box background="surface-alt-3-subtle" style={{ height: 64 }}>
|
||||
Header
|
||||
</Box>
|
||||
</Page.Block>
|
||||
);
|
||||
}
|
||||
|
||||
function Content({ width = "lg", gutters = false }: any) {
|
||||
return (
|
||||
<Page.Block width={width} gutters={gutters} as="main">
|
||||
<Box background="surface-alt-3-subtle" style={{ height: 300 }}>
|
||||
Main
|
||||
</Box>
|
||||
</Page.Block>
|
||||
);
|
||||
}
|
||||
|
||||
function Footer({ width = "lg", gutters = false }: any) {
|
||||
return (
|
||||
<Page.Block width={width} gutters={gutters} as="footer">
|
||||
<Box background="surface-alt-3-subtle" style={{ height: 100 }}>
|
||||
Footer
|
||||
</Box>
|
||||
</Page.Block>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
import bgColors from "@navikt/ds-tokens/src/colors-bg.json";
|
||||
import cl from "clsx";
|
||||
import React, { forwardRef } from "react";
|
||||
import { OverridableComponent } from "../../util";
|
||||
import { PageBlock } from "./parts/PageBlock";
|
||||
|
||||
export interface PageProps extends React.HTMLAttributes<HTMLElement> {
|
||||
/**
|
||||
* Overrides html-tag
|
||||
* @default "div"
|
||||
*/
|
||||
as?: "div" | "body";
|
||||
/**
|
||||
* Background color. Accepts a color token.
|
||||
* @default bg-default
|
||||
*/
|
||||
background?: keyof typeof bgColors.a;
|
||||
/**
|
||||
* Allows better positioning of footer
|
||||
*/
|
||||
footer?: React.ReactNode;
|
||||
/**
|
||||
* Places footer below page-fold
|
||||
*/
|
||||
footerPosition?: "belowFold";
|
||||
/**
|
||||
* Adds a standardised padding of 4rem between content and footer
|
||||
* @default block-end
|
||||
*/
|
||||
contentBlockPadding?: "end" | "none";
|
||||
}
|
||||
|
||||
interface PageComponentType
|
||||
extends OverridableComponent<PageProps, HTMLElement> {
|
||||
Block: typeof PageBlock;
|
||||
}
|
||||
|
||||
export const PageComponent: OverridableComponent<PageProps, HTMLElement> =
|
||||
forwardRef(
|
||||
(
|
||||
{
|
||||
as: Component = "div",
|
||||
className,
|
||||
style: _style,
|
||||
footer,
|
||||
children,
|
||||
footerPosition,
|
||||
background = "bg-default",
|
||||
contentBlockPadding = "end",
|
||||
...rest
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const style: React.CSSProperties = {
|
||||
..._style,
|
||||
"--__ac-page-background": `var(--a-${background})`,
|
||||
};
|
||||
|
||||
const belowFold = footerPosition === "belowFold";
|
||||
|
||||
return (
|
||||
<Component
|
||||
{...rest}
|
||||
className={cl("navds-page", className)}
|
||||
ref={ref}
|
||||
style={style}
|
||||
>
|
||||
<div
|
||||
className={cl({
|
||||
"navds-page__content--fullheight": belowFold,
|
||||
"navds-page__content--grow": !belowFold,
|
||||
"navds-page__content--padding": contentBlockPadding === "end",
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
{footer}
|
||||
</Component>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Page helps with establishing a top-level layout for your page
|
||||
*
|
||||
* @see [📝 Documentation](https://aksel.nav.no/komponenter/primitives/page)
|
||||
* @see 🏷️ {@link PageProps}
|
||||
* @see [🤖 OverridableComponent](https://aksel.nav.no/grunnleggende/kode/overridablecomponent) support
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
* <Page
|
||||
* footer={<Page.Block width="xl" gutters />}
|
||||
* >
|
||||
* <Page.Block width="xl" gutters />// Header
|
||||
* <Page.Block width="xl" gutters />// Content
|
||||
* </Page>
|
||||
* ```
|
||||
* @example
|
||||
* Footer placed below page-fold
|
||||
* ```jsx
|
||||
* <Page
|
||||
* footer={<Page.Block width="xl" gutters />}
|
||||
* footerPosition="belowFold"
|
||||
* >
|
||||
* <Page.Block width="xl" gutters />// Header
|
||||
* <Page.Block width="xl" gutters />// Content
|
||||
* </Page>
|
||||
* ```
|
||||
*/
|
||||
const Page = PageComponent as PageComponentType;
|
||||
|
||||
Page.Block = PageBlock;
|
||||
|
||||
export default Page;
|
||||
@@ -0,0 +1,2 @@
|
||||
export { default as Page, type PageProps } from "./Page";
|
||||
export { type PageBlockProps } from "./parts/PageBlock";
|
||||
@@ -0,0 +1,72 @@
|
||||
import cl from "clsx";
|
||||
import React, { forwardRef } from "react";
|
||||
import { OverridableComponent } from "../../../util/OverridableComponent";
|
||||
|
||||
export const widths = ["lg", "xl", "2xl"] as const;
|
||||
|
||||
export interface PageBlockProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
/**
|
||||
* Predefined max-width
|
||||
* @example
|
||||
* lg: 1024px
|
||||
* xl: 1280px
|
||||
* 2xl: 1440px
|
||||
* @default max-width: 100%;
|
||||
*/
|
||||
width?: (typeof widths)[number];
|
||||
/**
|
||||
* Adds a standardised responsive padding-inline
|
||||
* @example
|
||||
* 3rem on > md
|
||||
* 1rem on < md
|
||||
* @default false
|
||||
*/
|
||||
gutters?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Acts as a top-level container for defining max-width, gutters and horizontal centering
|
||||
*
|
||||
* @see [📝 Documentation](https://aksel.nav.no/komponenter/primitives/page)
|
||||
* @see 🏷️ {@link PageBlockProps}
|
||||
* @see [🤖 OverridableComponent](https://aksel.nav.no/grunnleggende/kode/overridablecomponent) support
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
* <Page
|
||||
* footer={<Page.Block width="xl" gutters />}
|
||||
* >
|
||||
* <Page.Block width="xl" gutters />// Header
|
||||
* <Page.Block width="xl" gutters />// Content
|
||||
* </Page>
|
||||
* ```
|
||||
* @example
|
||||
* With background bleed
|
||||
* Wrapping `Page.Block` with `Box` allows the background-color to use full screen-width
|
||||
* ```jsx
|
||||
* <Page
|
||||
* footer={<Box background="..."><Page.Block width="xl" gutters /></Box>}
|
||||
* footerPosition="belowFold"
|
||||
* >
|
||||
* <Box background="..."><Page.Block width="xl" gutters /></Box>//Header
|
||||
* <Box background="..."><Page.Block width="xl" gutters /></Box>//Content
|
||||
* </Page>
|
||||
* ```
|
||||
*/
|
||||
export const PageBlock: OverridableComponent<PageBlockProps, HTMLDivElement> =
|
||||
forwardRef(
|
||||
({ as: Component = "div", gutters, className, width, ...rest }, ref) => {
|
||||
return (
|
||||
<Component
|
||||
{...rest}
|
||||
className={cl(
|
||||
"navds-pageblock",
|
||||
`navds-pageblock--${width}`,
|
||||
className,
|
||||
{ "navds-pageblock--gutters": gutters }
|
||||
)}
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
@@ -1,6 +1,6 @@
|
||||
import cl from "clsx";
|
||||
import React, { forwardRef, HTMLAttributes } from "react";
|
||||
import { BreakpointsAlias } from "../utilities/css";
|
||||
import { BreakpointsAlias } from "../utilities/types";
|
||||
import { Slot } from "../../util/Slot";
|
||||
|
||||
export interface ResponsiveProps extends HTMLAttributes<HTMLDivElement> {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from "react";
|
||||
import { Link } from "../../link";
|
||||
import { Heading, Label } from "../../typography";
|
||||
import { Box } from "../box";
|
||||
import { VStack } from "../stack";
|
||||
import { Link } from "../../link";
|
||||
|
||||
const LinkElement = ({ children }) => {
|
||||
return (
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import cl from "clsx";
|
||||
import React, { forwardRef, HTMLAttributes } from "react";
|
||||
import { OverridableComponent } from "../../util/OverridableComponent";
|
||||
import {
|
||||
getResponsiveProps,
|
||||
getResponsiveValue,
|
||||
ResponsiveProp,
|
||||
SpacingScale,
|
||||
} from "../utilities/css";
|
||||
import { getResponsiveProps, getResponsiveValue } from "../utilities/css";
|
||||
import { ResponsiveProp, SpacingScale } from "../utilities/types";
|
||||
|
||||
export interface StackProps extends HTMLAttributes<HTMLDivElement> {
|
||||
children: React.ReactNode;
|
||||
|
||||
@@ -1,39 +1,4 @@
|
||||
export type BreakpointsAlias = "xs" | "sm" | "md" | "lg" | "xl";
|
||||
|
||||
export type SpacingScale =
|
||||
| "0"
|
||||
| "05"
|
||||
| "1"
|
||||
| "1-alt"
|
||||
| "2"
|
||||
| "3"
|
||||
| "4"
|
||||
| "5"
|
||||
| "6"
|
||||
| "7"
|
||||
| "8"
|
||||
| "9"
|
||||
| "10"
|
||||
| "11"
|
||||
| "12"
|
||||
| "14"
|
||||
| "16"
|
||||
| "18"
|
||||
| "20"
|
||||
| "24"
|
||||
| "32";
|
||||
|
||||
export type SpaceDelimitedAttribute<T extends string> =
|
||||
| T
|
||||
| `${T} ${T}`
|
||||
| `${T} ${T} ${T}`
|
||||
| `${T} ${T} ${T} ${T}`;
|
||||
|
||||
type FixedResponsiveT<T> = {
|
||||
[Breakpoint in BreakpointsAlias]?: T;
|
||||
};
|
||||
|
||||
export type ResponsiveProp<T> = T | FixedResponsiveT<T>;
|
||||
import { ResponsiveProp } from "./types";
|
||||
|
||||
export function getResponsiveValue<T = string>(
|
||||
componentName: string,
|
||||
|
||||
@@ -3,6 +3,7 @@ import surfaceColors from "@navikt/ds-tokens/src/colors-surface.json";
|
||||
import borderColors from "@navikt/ds-tokens/src/colors-border.json";
|
||||
import borderRadii from "@navikt/ds-tokens/src/border.json";
|
||||
import shadows from "@navikt/ds-tokens/src/shadow.json";
|
||||
import Spacing from "@navikt/ds-tokens/src/spacing.json";
|
||||
|
||||
export type BackgroundToken =
|
||||
| keyof typeof bgColors.a
|
||||
@@ -12,3 +13,18 @@ export type BorderRadiiToken =
|
||||
| keyof (typeof borderRadii.a)["border-radius"]
|
||||
| "0";
|
||||
export type ShadowToken = keyof typeof shadows.a.shadow;
|
||||
|
||||
export type BreakpointsAlias = "xs" | "sm" | "md" | "lg" | "xl";
|
||||
|
||||
export type SpacingScale = keyof (typeof Spacing)["a"]["spacing"];
|
||||
|
||||
export type SpaceDelimitedAttribute<T extends string> =
|
||||
| T
|
||||
| `${T} ${T}`
|
||||
| `${T} ${T} ${T}`
|
||||
| `${T} ${T} ${T} ${T}`;
|
||||
type FixedResponsiveT<T> = {
|
||||
[Breakpoint in BreakpointsAlias]?: T;
|
||||
};
|
||||
|
||||
export type ResponsiveProp<T> = T | FixedResponsiveT<T>;
|
||||
@@ -2,7 +2,7 @@ import cl from "clsx";
|
||||
import React, { forwardRef, SVGProps } from "react";
|
||||
import { omit, useId } from "../util";
|
||||
|
||||
export interface LoaderProps extends SVGProps<SVGSVGElement> {
|
||||
export interface LoaderProps extends Omit<SVGProps<SVGSVGElement>, "ref"> {
|
||||
/**
|
||||
* Changes Loader width/height
|
||||
* 88px | 64px | 40px | 32px | 24px | 20px | 16px
|
||||
@@ -31,6 +31,12 @@ export interface LoaderProps extends SVGProps<SVGSVGElement> {
|
||||
* @default "neutral"
|
||||
*/
|
||||
variant?: "neutral" | "interaction" | "inverted";
|
||||
/**
|
||||
* Allows getting a ref to the component instance.
|
||||
* Once the component unmounts, React will set `ref.current` to `null` (or call the ref with `null` if you passed a callback ref).
|
||||
* @see https://react.dev/learn/referencing-values-with-refs#refs-and-the-dom
|
||||
*/
|
||||
ref?: React.Ref<SVGSVGElement>;
|
||||
}
|
||||
|
||||
/* Workaround for @types/react v17/v18 feil */
|
||||
@@ -46,7 +52,7 @@ export type LoaderType = React.ForwardRefExoticComponent<
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
* <Loader size="3xlarge" title="venter..." />
|
||||
* <Loader size="3xlarge" title="Venter..." />
|
||||
* ```
|
||||
*/
|
||||
export const Loader: LoaderType = forwardRef<SVGSVGElement, LoaderProps>(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import { Loader } from "..";
|
||||
import { Meta } from "@storybook/react";
|
||||
import React from "react";
|
||||
import { Loader, LoaderProps } from "..";
|
||||
export default {
|
||||
title: "ds-react/Loader",
|
||||
component: Loader,
|
||||
@@ -29,7 +29,7 @@ export default {
|
||||
} as Meta;
|
||||
|
||||
export const Default = {
|
||||
render: (props: any) => {
|
||||
render: (props: LoaderProps) => {
|
||||
return <Loader {...props} />;
|
||||
},
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import React, {
|
||||
useRef,
|
||||
} from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { DateContext } from "../date/context";
|
||||
import { useProvider } from "../provider";
|
||||
import { Detail, Heading } from "../typography";
|
||||
import { mergeRefs, useId } from "../util";
|
||||
@@ -82,11 +83,13 @@ export const Modal = forwardRef<HTMLDialogElement, ModalProps>(
|
||||
open,
|
||||
onBeforeClose,
|
||||
onCancel,
|
||||
closeOnBackdropClick,
|
||||
width,
|
||||
portal,
|
||||
className,
|
||||
"aria-labelledby": ariaLabelledby,
|
||||
style,
|
||||
onClick,
|
||||
...rest
|
||||
}: ModalProps,
|
||||
ref
|
||||
@@ -97,7 +100,9 @@ export const Modal = forwardRef<HTMLDialogElement, ModalProps>(
|
||||
const rootElement = useProvider()?.rootElement;
|
||||
const portalNode = useFloatingPortalNode({ root: rootElement });
|
||||
|
||||
if (useContext(ModalContext)) {
|
||||
const dateContext = useContext(DateContext);
|
||||
const modalContext = useContext(ModalContext);
|
||||
if (modalContext && !dateContext) {
|
||||
console.error("Modals should not be nested");
|
||||
}
|
||||
|
||||
@@ -108,6 +113,11 @@ export const Modal = forwardRef<HTMLDialogElement, ModalProps>(
|
||||
if (needPolyfill && modalRef.current && portalNode) {
|
||||
dialogPolyfill.registerDialog(modalRef.current);
|
||||
}
|
||||
// We set autofocus on the dialog element to prevent the default behavior where first focusable element gets focus when modal is opened.
|
||||
// This is mainly to fix an edge case where having a Tooltip as the first focusable element would make it activate when you open the modal.
|
||||
// We have to use JS because it doesn't work to set it with a prop (React bug?)
|
||||
// Currently doesn't seem to work in Chrome. See also Tooltip.tsx
|
||||
if (modalRef.current && portalNode) modalRef.current.autofocus = true;
|
||||
}, [modalRef, portalNode]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -128,34 +138,57 @@ export const Modal = forwardRef<HTMLDialogElement, ModalProps>(
|
||||
const isWidthPreset =
|
||||
typeof width === "string" && ["small", "medium"].includes(width);
|
||||
|
||||
const mergedClassName = cl("navds-modal", className, {
|
||||
"navds-modal--polyfilled": needPolyfill,
|
||||
"navds-modal--autowidth": !width,
|
||||
[`navds-modal--${width}`]: isWidthPreset,
|
||||
});
|
||||
|
||||
const mergedStyle = {
|
||||
...style,
|
||||
...(!isWidthPreset ? { width } : {}),
|
||||
};
|
||||
|
||||
const mergedOnCancel: React.DialogHTMLAttributes<HTMLDialogElement>["onCancel"] =
|
||||
(event) => {
|
||||
if (onBeforeClose && onBeforeClose() === false) {
|
||||
event.preventDefault();
|
||||
} else if (onCancel) onCancel(event);
|
||||
};
|
||||
|
||||
const mergedOnClick =
|
||||
closeOnBackdropClick && !needPolyfill // closeOnBackdropClick has issues on polyfill when nesting modals (DatePicker)
|
||||
? (event: React.MouseEvent<HTMLDialogElement>) => {
|
||||
onClick && onClick(event);
|
||||
if (
|
||||
event.target === modalRef.current &&
|
||||
(!onBeforeClose || onBeforeClose() !== false)
|
||||
) {
|
||||
modalRef.current.close();
|
||||
}
|
||||
}
|
||||
: onClick;
|
||||
|
||||
const mergedAriaLabelledBy =
|
||||
!ariaLabelledby && !rest["aria-label"] && header
|
||||
? ariaLabelId
|
||||
: ariaLabelledby;
|
||||
|
||||
const component = (
|
||||
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-noninteractive-element-interactions
|
||||
<dialog
|
||||
{...rest}
|
||||
ref={mergedRef}
|
||||
className={cl("navds-modal", className, {
|
||||
"navds-modal--polyfilled": needPolyfill,
|
||||
"navds-modal--autowidth": !width,
|
||||
[`navds-modal--${width}`]: isWidthPreset,
|
||||
})}
|
||||
style={{
|
||||
...style,
|
||||
...(!isWidthPreset ? { width } : {}),
|
||||
}}
|
||||
onCancel={(event) => {
|
||||
// FYI: onCancel fires when you press Esc
|
||||
if (onBeforeClose && onBeforeClose() === false) {
|
||||
event.preventDefault();
|
||||
} else if (onCancel) onCancel(event);
|
||||
}}
|
||||
aria-labelledby={
|
||||
!ariaLabelledby && !rest["aria-label"] && header
|
||||
? ariaLabelId
|
||||
: ariaLabelledby
|
||||
}
|
||||
className={mergedClassName}
|
||||
style={mergedStyle}
|
||||
onCancel={mergedOnCancel} // FYI: onCancel fires when you press Esc
|
||||
onClick={mergedOnClick}
|
||||
aria-labelledby={mergedAriaLabelledBy}
|
||||
>
|
||||
<ModalContext.Provider
|
||||
value={{
|
||||
closeHandler: getCloseHandler(modalRef, header, onBeforeClose),
|
||||
ref: modalRef,
|
||||
}}
|
||||
>
|
||||
{header && (
|
||||
|
||||
@@ -2,5 +2,6 @@ import React from "react";
|
||||
|
||||
interface ModalContextProps {
|
||||
closeHandler?: React.MouseEventHandler<HTMLButtonElement>;
|
||||
ref: React.RefObject<HTMLDialogElement>;
|
||||
}
|
||||
export const ModalContext = React.createContext<ModalContextProps | null>(null);
|
||||
@@ -1,17 +1,19 @@
|
||||
import React, { useRef, useState } from "react";
|
||||
import { FileIcon } from "@navikt/aksel-icons";
|
||||
import { BodyLong, Button, Heading } from "..";
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
import React, { useRef, useState } from "react";
|
||||
import { BodyLong, Button, Heading, Tooltip } from "..";
|
||||
import Modal from "./Modal";
|
||||
|
||||
export default {
|
||||
const meta: Meta<typeof Modal> = {
|
||||
title: "ds-react/Modal",
|
||||
component: Modal,
|
||||
parameters: {
|
||||
chromatic: { pauseAnimationAtEnd: true },
|
||||
},
|
||||
};
|
||||
export default meta;
|
||||
|
||||
export const WithUseRef = () => {
|
||||
export const WithUseRef: StoryFn = () => {
|
||||
const ref = useRef<HTMLDialogElement>(null);
|
||||
const ref2 = useRef<HTMLDialogElement>(null);
|
||||
|
||||
@@ -27,6 +29,7 @@ export const WithUseRef = () => {
|
||||
heading: "Title",
|
||||
size: "small",
|
||||
}}
|
||||
closeOnBackdropClick
|
||||
>
|
||||
<Modal.Body>
|
||||
<BodyLong spacing>
|
||||
@@ -45,6 +48,7 @@ export const WithUseRef = () => {
|
||||
onBeforeClose={() =>
|
||||
window.confirm("Are you sure you want to close the modal?")
|
||||
}
|
||||
closeOnBackdropClick
|
||||
aria-labelledby="heading123"
|
||||
>
|
||||
<Modal.Header>
|
||||
@@ -85,7 +89,7 @@ export const WithUseRef = () => {
|
||||
};
|
||||
WithUseRef.storyName = "With useRef";
|
||||
|
||||
export const WithUseState = () => {
|
||||
export const WithUseState: StoryFn = () => {
|
||||
const [open, setOpen] = useState(true);
|
||||
const [open2, setOpen2] = useState(false);
|
||||
|
||||
@@ -111,6 +115,7 @@ export const WithUseState = () => {
|
||||
e.stopPropagation(); // onClose wil propagate to parent modal if not stopped
|
||||
setOpen2(false);
|
||||
}}
|
||||
closeOnBackdropClick
|
||||
aria-label="Nested modal"
|
||||
width={800}
|
||||
>
|
||||
@@ -137,31 +142,126 @@ export const WithUseState = () => {
|
||||
};
|
||||
WithUseState.storyName = "With useState";
|
||||
|
||||
export const EmptyHeader = () => (
|
||||
<div style={{ minWidth: "800px", minHeight: "600px" } /* For Chromatic */}>
|
||||
<Modal open>
|
||||
<Modal.Header />
|
||||
<Modal.Body>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
|
||||
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
|
||||
veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
|
||||
commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
|
||||
velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
|
||||
occaecat cupidatat non proident, sunt in culpa qui officia deserunt
|
||||
mollit anim id est laborum.
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
</div>
|
||||
export const EmptyHeader: StoryFn = () => (
|
||||
<Modal open>
|
||||
<Modal.Header />
|
||||
<Modal.Body>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
|
||||
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
|
||||
veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
|
||||
commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
|
||||
velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat
|
||||
cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
|
||||
est laborum.
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
export const Small = () => (
|
||||
export const Small: StoryFn = () => (
|
||||
<Modal open width="small" header={{ heading: "Simple header" }}>
|
||||
<Modal.Body>Lorem ipsum dolor sit amet.</Modal.Body>
|
||||
</Modal>
|
||||
);
|
||||
Small.storyName = "Size = Small";
|
||||
|
||||
export const MediumWithPortal = () => (
|
||||
export const MediumWithPortal: StoryFn = () => (
|
||||
<Modal open portal width="medium" header={{ heading: "Simple header" }}>
|
||||
<Modal.Body>Lorem ipsum dolor sit amet.</Modal.Body>
|
||||
</Modal>
|
||||
);
|
||||
MediumWithPortal.storyName = "Size = Medium (with portal)";
|
||||
|
||||
export const Large800: StoryFn = () => (
|
||||
<Modal open width={800} header={{ heading: "Simple header" }}>
|
||||
<Modal.Body>Lorem ipsum dolor sit amet.</Modal.Body>
|
||||
</Modal>
|
||||
);
|
||||
Large800.storyName = "Size = 800px";
|
||||
|
||||
export const WithTooltip: StoryFn = () => {
|
||||
const ref = useRef<HTMLDialogElement>(null);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Button onClick={() => ref.current?.showModal()}>Open Modal</Button>
|
||||
<Modal
|
||||
open={ref.current ? undefined : true /* initially open */}
|
||||
ref={ref}
|
||||
>
|
||||
<Modal.Body>
|
||||
<div style={{ marginBottom: "1rem" }}>
|
||||
<Tooltip content="This_is_the_first_tooltip">
|
||||
<Button>Test 1</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Tooltip content="This is the second tooltip">
|
||||
<Button>Test 2</Button>
|
||||
</Tooltip>
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const ChromaticViewportTesting: StoryFn = () => (
|
||||
<div id="modal-story-wrapper" style={{ width: "100vw", height: "100vh" }}>
|
||||
<style>{`#storybook-root { padding: 0 !important }`}</style>
|
||||
<Modal
|
||||
open
|
||||
header={{ heading: "Chromatic Viewports Testing", label: "Test" }}
|
||||
>
|
||||
<Modal.Body>
|
||||
<BodyLong spacing>
|
||||
This story is tailored for testing the breakpoints with Chromatic.
|
||||
</BodyLong>
|
||||
<BodyLong spacing>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
|
||||
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad
|
||||
minim veniam, quis nostrud exercitation ullamco laboris nisi ut
|
||||
aliquip ex ea commodo consequat.
|
||||
</BodyLong>
|
||||
<BodyLong spacing>
|
||||
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
|
||||
dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
|
||||
proident, sunt in culpa qui officia deserunt mollit anim id est
|
||||
laborum.
|
||||
</BodyLong>
|
||||
<BodyLong>
|
||||
Sed ut perspiciatis unde omnis iste natus error sit voluptatem
|
||||
accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae
|
||||
ab illo inventore veritatis et quasi architecto beatae vitae dicta
|
||||
sunt explicabo.
|
||||
</BodyLong>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button>Primary</Button>
|
||||
<Button variant="secondary">Secondary</Button>
|
||||
<Button variant="tertiary">Tertiary</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
ChromaticViewportTesting.parameters = {
|
||||
chromatic: {
|
||||
modes: {
|
||||
mobile_portrait: {
|
||||
viewport: {
|
||||
width: 400,
|
||||
height: 850,
|
||||
},
|
||||
},
|
||||
mobile_landscape: {
|
||||
viewport: {
|
||||
width: 850,
|
||||
height: 400,
|
||||
},
|
||||
},
|
||||
desktop: {
|
||||
viewport: {
|
||||
width: 1280,
|
||||
height: 960,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -42,6 +42,13 @@ export interface ModalProps
|
||||
* Called when the user presses the Esc key, unless `onBeforeClose()` returns `false`.
|
||||
*/
|
||||
onCancel?: React.ReactEventHandler<HTMLDialogElement>;
|
||||
/**
|
||||
* Whether to close when clicking on the backdrop.
|
||||
*
|
||||
* **WARNING:** Users may click outside by accident. Don't use if closing can cause data loss, or the modal contains important info.
|
||||
* @default false
|
||||
*/
|
||||
closeOnBackdropClick?: boolean;
|
||||
/**
|
||||
* @default fit-content (up to 700px)
|
||||
* */
|
||||
|
||||
@@ -18,6 +18,7 @@ import React, {
|
||||
useMemo,
|
||||
useRef,
|
||||
} from "react";
|
||||
import { DateContext } from "../date/context";
|
||||
import { ModalContext } from "../modal/ModalContext";
|
||||
import { mergeRefs, useClientLayoutEffect, useEventListener } from "../util";
|
||||
import PopoverContent, { PopoverContentType } from "./PopoverContent";
|
||||
@@ -73,11 +74,6 @@ export interface PopoverProps extends HTMLAttributes<HTMLDivElement> {
|
||||
* @default "absolute"
|
||||
*/
|
||||
strategy?: "absolute" | "fixed";
|
||||
/**
|
||||
* Bubbles Escape keydown-event up trough DOM-tree. This is set to false by default to prevent closing components like Modal on Escape
|
||||
* @default false
|
||||
*/
|
||||
bubbleEscape?: boolean;
|
||||
/**
|
||||
* Changes placement of the floating element in order to keep it in view.
|
||||
* @default true
|
||||
@@ -124,7 +120,6 @@ export const Popover = forwardRef<HTMLDivElement, PopoverProps>(
|
||||
placement = "top",
|
||||
offset,
|
||||
strategy: userStrategy,
|
||||
bubbleEscape = false,
|
||||
flip: _flip = true,
|
||||
...rest
|
||||
},
|
||||
@@ -132,8 +127,9 @@ export const Popover = forwardRef<HTMLDivElement, PopoverProps>(
|
||||
) => {
|
||||
const arrowRef = useRef<HTMLDivElement | null>(null);
|
||||
const isInModal = useContext(ModalContext) !== null;
|
||||
const isInDatepicker = useContext(DateContext) !== null;
|
||||
const chosenStrategy = userStrategy ?? (isInModal ? "fixed" : "absolute");
|
||||
const chosenFlip = isInModal ? true : _flip;
|
||||
const chosenFlip = isInDatepicker ? false : _flip;
|
||||
|
||||
const {
|
||||
x,
|
||||
@@ -160,11 +156,7 @@ export const Popover = forwardRef<HTMLDivElement, PopoverProps>(
|
||||
|
||||
const { getFloatingProps } = useInteractions([
|
||||
useClick(context),
|
||||
useDismiss(context, {
|
||||
bubbles: {
|
||||
escapeKey: bubbleEscape,
|
||||
},
|
||||
}),
|
||||
useDismiss(context),
|
||||
]);
|
||||
|
||||
useClientLayoutEffect(() => {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import {
|
||||
arrow as flArrow,
|
||||
autoUpdate,
|
||||
flip,
|
||||
FloatingPortal,
|
||||
autoUpdate,
|
||||
arrow as flArrow,
|
||||
flip,
|
||||
offset,
|
||||
safePolygon,
|
||||
shift,
|
||||
@@ -14,16 +14,18 @@ import {
|
||||
} from "@floating-ui/react";
|
||||
import cl from "clsx";
|
||||
import React, {
|
||||
HTMLAttributes,
|
||||
cloneElement,
|
||||
forwardRef,
|
||||
HTMLAttributes,
|
||||
useContext,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { ModalContext } from "../modal/ModalContext";
|
||||
import { useProvider } from "../provider";
|
||||
import { Detail } from "../typography";
|
||||
import { mergeRefs, useId } from "../util";
|
||||
import { useProvider } from "../provider";
|
||||
|
||||
export interface TooltipProps extends HTMLAttributes<HTMLDivElement> {
|
||||
/**
|
||||
@@ -110,7 +112,11 @@ export const Tooltip = forwardRef<HTMLDivElement, TooltipProps>(
|
||||
) => {
|
||||
const [open, setOpen] = useState(defaultOpen);
|
||||
const arrowRef = useRef<HTMLDivElement | null>(null);
|
||||
const rootElement = useProvider()?.rootElement;
|
||||
const modalContext = useContext(ModalContext);
|
||||
const providerRootElement = useProvider()?.rootElement;
|
||||
const rootElement = modalContext
|
||||
? modalContext.ref.current
|
||||
: providerRootElement;
|
||||
|
||||
const {
|
||||
x,
|
||||
@@ -133,7 +139,13 @@ export const Tooltip = forwardRef<HTMLDivElement, TooltipProps>(
|
||||
flip({ padding: 5, fallbackPlacements: ["bottom", "top"] }),
|
||||
flArrow({ element: arrowRef, padding: 5 }),
|
||||
],
|
||||
whileElementsMounted: autoUpdate,
|
||||
whileElementsMounted: modalContext
|
||||
? (reference, floating, update) =>
|
||||
// Reduces jumping in Chrome when used in a Modal and it's the first focusable element.
|
||||
// Can be removed when autofocus starts working on <dialog> in Chrome. See also Modal.tsx
|
||||
autoUpdate(reference, floating, update, { animationFrame: true })
|
||||
: autoUpdate,
|
||||
strategy: modalContext ? "fixed" : undefined,
|
||||
});
|
||||
|
||||
const { getReferenceProps, getFloatingProps } = useInteractions([
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import React from "react";
|
||||
import { useMedia } from "../useMedia";
|
||||
|
||||
function TestComponent({ fallback }: { fallback?: boolean }) {
|
||||
const media = useMedia("screen and (min-width: 1024px)", fallback);
|
||||
return <div data-testid="media-id">{`${media}`}</div>;
|
||||
}
|
||||
|
||||
describe("useMedia", () => {
|
||||
test("Should return 'undefined' when no fallback is given", async () => {
|
||||
render(<TestComponent />);
|
||||
expect(screen.getByTestId("media-id").innerHTML).toEqual("undefined");
|
||||
});
|
||||
test("Should return fallback", async () => {
|
||||
render(<TestComponent fallback={true} />);
|
||||
expect(screen.getByTestId("media-id").innerHTML).toEqual("true");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export const noMatchMedia =
|
||||
typeof window !== "undefined" && window.matchMedia === undefined;
|
||||
|
||||
/**
|
||||
* @example useMedia("screen and (min-width: 1024px)")
|
||||
* @param media string
|
||||
* @param fallback boolean
|
||||
* @returns boolean | undefined
|
||||
*/
|
||||
export const useMedia = (
|
||||
media: string,
|
||||
fallback?: boolean
|
||||
): boolean | undefined => {
|
||||
const [matches, setMatches] = useState(fallback);
|
||||
|
||||
useEffect(() => {
|
||||
if (noMatchMedia) {
|
||||
return;
|
||||
}
|
||||
const mediaQueryList = window.matchMedia(media);
|
||||
|
||||
setMatches(mediaQueryList.matches);
|
||||
|
||||
const listener = (evt: MediaQueryListEvent) => {
|
||||
setMatches(evt.matches);
|
||||
};
|
||||
|
||||
mediaQueryList.addEventListener("change", listener);
|
||||
|
||||
return () => {
|
||||
mediaQueryList.removeEventListener("change", listener);
|
||||
};
|
||||
}, [media]);
|
||||
|
||||
return matches;
|
||||
};
|
||||
@@ -1,5 +1,19 @@
|
||||
# @navikt/ds-tailwind
|
||||
|
||||
## 5.10.0
|
||||
|
||||
## 5.9.2
|
||||
|
||||
## 5.9.1
|
||||
|
||||
## 5.9.0
|
||||
|
||||
## 5.8.0
|
||||
|
||||
## 5.7.6
|
||||
|
||||
## 5.7.5
|
||||
|
||||
## 5.7.4
|
||||
|
||||
## 5.7.3
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-tailwind",
|
||||
"version": "5.7.4",
|
||||
"version": "5.10.0",
|
||||
"description": "Tailwind config based on @navikt/ds-tokens",
|
||||
"author": "Aksel | NAV designsystem team",
|
||||
"keywords": [
|
||||
@@ -22,7 +22,7 @@
|
||||
"directory": "@navikt/core/tailwind"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@navikt/ds-tokens": "^5.7.4",
|
||||
"@navikt/ds-tokens": "^5.10.0",
|
||||
"@types/jest": "^29.0.0",
|
||||
"color": "4.2.3",
|
||||
"jest": "^29.0.0",
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# @navikt/ds-tokens
|
||||
|
||||
## 5.10.0
|
||||
|
||||
## 5.9.2
|
||||
|
||||
## 5.9.1
|
||||
|
||||
## 5.9.0
|
||||
|
||||
## 5.8.0
|
||||
|
||||
## 5.7.6
|
||||
|
||||
## 5.7.5
|
||||
|
||||
## 5.7.4
|
||||
|
||||
## 5.7.3
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-tokens",
|
||||
"version": "5.7.4",
|
||||
"version": "5.10.0",
|
||||
"description": "Design-tokens for NAV designsystem",
|
||||
"author": "Aksel | NAV designsystem team",
|
||||
"keywords": [
|
||||
|
||||
+299
-217
@@ -1,5 +1,87 @@
|
||||
# Changelog
|
||||
|
||||
## 5.10.0
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Primitives: Ny komponent `Page` og `Page.Block` ([#2456](https://github.com/navikt/aksel/pull/2456))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Primitives: Ny komponent `Page` og `Page.Block` ([#2456](https://github.com/navikt/aksel/pull/2456))
|
||||
|
||||
## 5.9.2
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- :bug: Datepicker lukker ikke lenger modalen den er i ([#2451](https://github.com/navikt/aksel/pull/2451))
|
||||
|
||||
## 5.9.1
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- :lipstick: Modal: Bedre håndtering av mobiler i landskapsmodus ([#2444](https://github.com/navikt/aksel/pull/2444))
|
||||
|
||||
## 5.9.0
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Datepicker/Monthpicker: Hvis man bruker komponentene i Modal vil Popover bli erstattet med Modal uansett om man er på desktop eller mobil. ([#2419](https://github.com/navikt/aksel/pull/2419))
|
||||
|
||||
- Datepicker/Monthpicker/Popover: Fjernet `bubbleEscape`-prop. ([#2419](https://github.com/navikt/aksel/pull/2419))
|
||||
|
||||
- useDatepicker/useMonthPicker/useRangedpicker: Fjernet `openOnFocus`-prop, kan nå bare åpnes ved klikk på date-knapp i input. ([#2419](https://github.com/navikt/aksel/pull/2419))
|
||||
|
||||
- Datepicker/Monthpicker: Bytter nå automatisk til Modalvisning på mobil. ([#2419](https://github.com/navikt/aksel/pull/2419))
|
||||
|
||||
* Added useVirtualFocus hook - used in Combobox for now ([#2394](https://github.com/navikt/aksel/pull/2394))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Datepicker/Monthpicker: Hvis man bruker komponentene i Modal vil Popover bli erstattet med Modal uansett om man er på desktop eller mobil. ([#2419](https://github.com/navikt/aksel/pull/2419))
|
||||
|
||||
- Datepicker/Monthpicker/Popover: Fjernet `bubbleEscape`-prop. ([#2419](https://github.com/navikt/aksel/pull/2419))
|
||||
|
||||
- useDatepicker/useMonthPicker/useRangedpicker: Fjernet `openOnFocus`-prop, kan nå bare åpnes ved klikk på date-knapp i input. ([#2419](https://github.com/navikt/aksel/pull/2419))
|
||||
|
||||
- Datepicker/Monthpicker: Bytter nå automatisk til Modalvisning på mobil. ([#2419](https://github.com/navikt/aksel/pull/2419))
|
||||
|
||||
## 5.8.0
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- :sparkles: Modal: Støtte for å lukke ved klikk utenfor ([#2386](https://github.com/navikt/aksel/pull/2386))
|
||||
|
||||
* Autocomplete in combobox will not change formatting of the letters while being typed, but will use the casing of the autocompleted word when selecting the option. ([#2207](https://github.com/navikt/aksel/pull/2207))
|
||||
|
||||
* Accordion: Fikset dom-validering ved bruk av `div` i `button`. ([#2426](https://github.com/navikt/aksel/pull/2426))
|
||||
|
||||
* :bug: Modal: Bedre støtte for Tooltip i Modal ([#2429](https://github.com/navikt/aksel/pull/2429))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- :bug: Modal: Bedre støtte for Tooltip i Modal ([#2429](https://github.com/navikt/aksel/pull/2429))
|
||||
|
||||
## 5.7.6
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- :lipstick: Chips.Toggle: Ikon i uvalgt tilstand ([#2395](https://github.com/navikt/aksel/pull/2395))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- :lipstick: Chips.Toggle: Ikon i uvalgt tilstand ([#2395](https://github.com/navikt/aksel/pull/2395))
|
||||
|
||||
## 5.7.5
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Loader: Har nå riktig `ref`-type. ([#2391](https://github.com/navikt/aksel/pull/2391))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Checkbox, Radio: Transparent-border fungerer nå bedre på mørkere bakgrunner ([#2388](https://github.com/navikt/aksel/pull/2388))
|
||||
|
||||
## 5.7.4
|
||||
|
||||
### @navikt/ds-react
|
||||
@@ -8,6 +90,12 @@
|
||||
|
||||
## 5.7.3
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Forms: Fikset bug der size='small' ikke oppdaterte typografi. ([#2372](https://github.com/navikt/aksel/pull/2372))
|
||||
|
||||
- HelpText: Knapp er visuelt mindre. Klikkflate er fortsatt 24px ([#2376](https://github.com/navikt/aksel/pull/2376))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Ny spacing token på 0.375rem ([#2370](https://github.com/navikt/aksel/pull/2370))
|
||||
@@ -16,12 +104,6 @@
|
||||
|
||||
- HelpText: Knapp er visuelt mindre. Klikkflate er fortsatt 24px ([#2376](https://github.com/navikt/aksel/pull/2376))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Forms: Fikset bug der size='small' ikke oppdaterte typografi. ([#2372](https://github.com/navikt/aksel/pull/2372))
|
||||
|
||||
- HelpText: Knapp er visuelt mindre. Klikkflate er fortsatt 24px ([#2376](https://github.com/navikt/aksel/pull/2376))
|
||||
|
||||
### @navikt/ds-tokens
|
||||
|
||||
- Ny spacing token på 0.375rem ([#2370](https://github.com/navikt/aksel/pull/2370))
|
||||
@@ -34,11 +116,11 @@
|
||||
|
||||
## 5.7.1
|
||||
|
||||
### @navikt/ds-css
|
||||
### @navikt/ds-react
|
||||
|
||||
- Radio: readonly håndterer nå hover-state bedre ([#2363](https://github.com/navikt/aksel/pull/2363))
|
||||
|
||||
### @navikt/ds-react
|
||||
### @navikt/ds-css
|
||||
|
||||
- Radio: readonly håndterer nå hover-state bedre ([#2363](https://github.com/navikt/aksel/pull/2363))
|
||||
|
||||
@@ -48,16 +130,16 @@
|
||||
|
||||
- Ikoner: Nye ikoner `ChevronLeftFirstCircle` og `ChevronRightLastCircle` ([#2365](https://github.com/navikt/aksel/pull/2365))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- :lipstick: CopyButton: Justert padding, gap og animasjon ([#2355](https://github.com/navikt/aksel/pull/2355))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Chores: Ryddet opp i sirkulære depdendencies ([#2366](https://github.com/navikt/aksel/pull/2366))
|
||||
|
||||
- :lipstick: CopyButton: Justert padding, gap og animasjon ([#2355](https://github.com/navikt/aksel/pull/2355))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- :lipstick: CopyButton: Justert padding, gap og animasjon ([#2355](https://github.com/navikt/aksel/pull/2355))
|
||||
|
||||
## 5.6.5
|
||||
|
||||
### @navikt/ds-react
|
||||
@@ -98,24 +180,6 @@
|
||||
|
||||
* Ikoner: Nye ikoner `ClipboardCheckmark` og `ClipboardLink` ([#2307](https://github.com/navikt/aksel/pull/2307))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Primitives: ny komponent Bleed ([#2278](https://github.com/navikt/aksel/pull/2278))
|
||||
|
||||
* :recycle: Box: Forenkling av CSS-variabler ([#2279](https://github.com/navikt/aksel/pull/2279))
|
||||
|
||||
* Accordion: Har nå 0.25rem padding-top mellom innhold og heading. ([#2310](https://github.com/navikt/aksel/pull/2310))
|
||||
|
||||
* Added cursor:pointer to Select component ([#2293](https://github.com/navikt/aksel/pull/2293))
|
||||
|
||||
* :bug: Box: sett border-radius riktig ([#2329](https://github.com/navikt/aksel/pull/2329))
|
||||
|
||||
* Stack: Kan nå endre direction, justify og align ved brekkpunkt. `Stack` er også nå en egen komponent sammen med `HStack` og `VStack`. ([#2286](https://github.com/navikt/aksel/pull/2286))
|
||||
|
||||
* Datepicker: Tilpasset padding og sizing på mobil. ([#2311](https://github.com/navikt/aksel/pull/2311))
|
||||
|
||||
* MonthPicker: Tilpasset padding og sizing på mobil. ([#2311](https://github.com/navikt/aksel/pull/2311))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- :recycle: Refaktorering og småfikser ([#2265](https://github.com/navikt/aksel/pull/2265))
|
||||
@@ -136,15 +200,33 @@
|
||||
|
||||
* Remove tabIndex="-1" on Popover to fix VoiceOver navigation issue inside Popover ([#2303](https://github.com/navikt/aksel/pull/2303))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Primitives: ny komponent Bleed ([#2278](https://github.com/navikt/aksel/pull/2278))
|
||||
|
||||
* :recycle: Box: Forenkling av CSS-variabler ([#2279](https://github.com/navikt/aksel/pull/2279))
|
||||
|
||||
* Accordion: Har nå 0.25rem padding-top mellom innhold og heading. ([#2310](https://github.com/navikt/aksel/pull/2310))
|
||||
|
||||
* Added cursor:pointer to Select component ([#2293](https://github.com/navikt/aksel/pull/2293))
|
||||
|
||||
* :bug: Box: sett border-radius riktig ([#2329](https://github.com/navikt/aksel/pull/2329))
|
||||
|
||||
* Stack: Kan nå endre direction, justify og align ved brekkpunkt. `Stack` er også nå en egen komponent sammen med `HStack` og `VStack`. ([#2286](https://github.com/navikt/aksel/pull/2286))
|
||||
|
||||
* Datepicker: Tilpasset padding og sizing på mobil. ([#2311](https://github.com/navikt/aksel/pull/2311))
|
||||
|
||||
* MonthPicker: Tilpasset padding og sizing på mobil. ([#2311](https://github.com/navikt/aksel/pull/2311))
|
||||
|
||||
## 5.5.0
|
||||
|
||||
### @navikt/ds-css
|
||||
### @navikt/ds-react
|
||||
|
||||
- Box: Ny primitive, erstatter dagens `Panel` ([#2195](https://github.com/navikt/aksel/pull/2195))
|
||||
|
||||
* Skeleton: Width fungerer nå med inline-variant av Skeleton ([#2273](https://github.com/navikt/aksel/pull/2273))
|
||||
|
||||
### @navikt/ds-react
|
||||
### @navikt/ds-css
|
||||
|
||||
- Box: Ny primitive, erstatter dagens `Panel` ([#2195](https://github.com/navikt/aksel/pull/2195))
|
||||
|
||||
@@ -162,18 +244,18 @@
|
||||
|
||||
## 5.4.0
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Primitives: Nye komponenter `Show` og `Hide` er lagt til. ([#2222](https://github.com/navikt/aksel/pull/2222))
|
||||
|
||||
* ErrorSummary: Har nå 8px border-radius ([#2256](https://github.com/navikt/aksel/pull/2256))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Primitives: Nye komponenter `Show` og `Hide` er lagt til. ([#2222](https://github.com/navikt/aksel/pull/2222))
|
||||
|
||||
* Search: Kjører nå bare preventDefault ved Escape når `input` inneholder tekst ([#2245](https://github.com/navikt/aksel/pull/2245))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Primitives: Nye komponenter `Show` og `Hide` er lagt til. ([#2222](https://github.com/navikt/aksel/pull/2222))
|
||||
|
||||
* ErrorSummary: Har nå 8px border-radius ([#2256](https://github.com/navikt/aksel/pull/2256))
|
||||
|
||||
### @navikt/ds-tokens
|
||||
|
||||
- Tokens: `data-theme="light"` vil nå tilbakestille theming ([#2238](https://github.com/navikt/aksel/pull/2238))
|
||||
@@ -196,31 +278,25 @@
|
||||
|
||||
## 5.3.3
|
||||
|
||||
### @navikt/ds-css
|
||||
### @navikt/ds-react
|
||||
|
||||
- HGrid: Har nå `align`-prop for bedre kontroll over child-elementer ([#2242](https://github.com/navikt/aksel/pull/2242))
|
||||
|
||||
### @navikt/ds-react
|
||||
### @navikt/ds-css
|
||||
|
||||
- HGrid: Har nå `align`-prop for bedre kontroll over child-elementer ([#2242](https://github.com/navikt/aksel/pull/2242))
|
||||
|
||||
## 5.3.2
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Skeleton: Lagt til as-prop for inline brk av Skeleton med span ([#2239](https://github.com/navikt/aksel/pull/2239))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Skeleton: Lagt til as-prop for inline brk av Skeleton med span ([#2239](https://github.com/navikt/aksel/pull/2239))
|
||||
|
||||
## 5.3.1
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- :bug: fix missing accordion bottom box-shadow on last element (when opened) ([#2229](https://github.com/navikt/aksel/pull/2229))
|
||||
- Skeleton: Lagt til as-prop for inline brk av Skeleton med span ([#2239](https://github.com/navikt/aksel/pull/2239))
|
||||
|
||||
- :lipstick: GuidePanel: justert design ([#2227](https://github.com/navikt/aksel/pull/2227))
|
||||
## 5.3.1
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
@@ -230,11 +306,13 @@
|
||||
|
||||
- :bug: DatePicker: Riktig skriftstørrelse på small input ([#2232](https://github.com/navikt/aksel/pull/2232))
|
||||
|
||||
## 5.3.0
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Typography: Oppdatert med typo-klasser for `textColor`, `weight`, `align`, `visuallyHidden` og `truncated`. ([#2211](https://github.com/navikt/aksel/pull/2211))
|
||||
- :bug: fix missing accordion bottom box-shadow on last element (when opened) ([#2229](https://github.com/navikt/aksel/pull/2229))
|
||||
|
||||
- :lipstick: GuidePanel: justert design ([#2227](https://github.com/navikt/aksel/pull/2227))
|
||||
|
||||
## 5.3.0
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
@@ -246,6 +324,10 @@
|
||||
|
||||
* :wheelchair: Textarea: Skjermleser-spesifikk tekst leses opp sammenhengende ([#2216](https://github.com/navikt/aksel/pull/2216))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Typography: Oppdatert med typo-klasser for `textColor`, `weight`, `align`, `visuallyHidden` og `truncated`. ([#2211](https://github.com/navikt/aksel/pull/2211))
|
||||
|
||||
## 5.2.1
|
||||
|
||||
### @navikt/ds-css
|
||||
@@ -258,12 +340,6 @@
|
||||
|
||||
- Ikoner: Oppdatert ikonpakke ([#2197](https://github.com/navikt/aksel/pull/2197))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- CSS: Popover har nå 8px border-radius. Fikset padding-bug i Select-small. ([#2219](https://github.com/navikt/aksel/pull/2219))
|
||||
|
||||
- Hovering over combobox dropdown will move selection/focus in the list, so we don't end up with a split focus, and reversely when moving focus while hovering ([#2193](https://github.com/navikt/aksel/pull/2193))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Button: Ved bruk av `as`-prop vil `role="button"` nå bli lagt til. Native `onKeyUp` for `Space` er også implementert slik at standard `button`-interaksjon vil være likere uansett html-tag. ([#2154](https://github.com/navikt/aksel/pull/2154))
|
||||
@@ -272,13 +348,13 @@
|
||||
|
||||
- Hovering over combobox dropdown will move selection/focus in the list, so we don't end up with a split focus, and reversely when moving focus while hovering ([#2193](https://github.com/navikt/aksel/pull/2193))
|
||||
|
||||
## 5.1.0
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- :lipstick: Modal: fjern kantlinje ([#2210](https://github.com/navikt/aksel/pull/2210))
|
||||
- CSS: Popover har nå 8px border-radius. Fikset padding-bug i Select-small. ([#2219](https://github.com/navikt/aksel/pull/2219))
|
||||
|
||||
- Tokens: Fikset table-token ([#2204](https://github.com/navikt/aksel/pull/2204))
|
||||
- Hovering over combobox dropdown will move selection/focus in the list, so we don't end up with a split focus, and reversely when moving focus while hovering ([#2193](https://github.com/navikt/aksel/pull/2193))
|
||||
|
||||
## 5.1.0
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
@@ -288,18 +364,24 @@
|
||||
|
||||
* Datepicker: Input setter ikke nå aria-controls før popover åpnes ([#2213](https://github.com/navikt/aksel/pull/2213))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- :lipstick: Modal: fjern kantlinje ([#2210](https://github.com/navikt/aksel/pull/2210))
|
||||
|
||||
- Tokens: Fikset table-token ([#2204](https://github.com/navikt/aksel/pull/2204))
|
||||
|
||||
## 5.0.3
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- ConfirmationPanel: Fikset opplesing av feilmelding ([#2199](https://github.com/navikt/aksel/pull/2199))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- ConfirmationPanel: Fikset opplesing av feilmelding ([#2199](https://github.com/navikt/aksel/pull/2199))
|
||||
|
||||
- Checkbox: Fikset checkbox-alignment ved bruk av tailwindcss ([#2199](https://github.com/navikt/aksel/pull/2199))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- ConfirmationPanel: Fikset opplesing av feilmelding ([#2199](https://github.com/navikt/aksel/pull/2199))
|
||||
|
||||
### @navikt/ds-tokens
|
||||
|
||||
- ConfirmationPanel: Fikset opplesing av feilmelding ([#2199](https://github.com/navikt/aksel/pull/2199))
|
||||
@@ -320,17 +402,6 @@
|
||||
|
||||
## 5.0.0
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Oppdatert Modal - Se [Migrering](https://aksel.nav.no/grunnleggende/kode/migrering) ([#2135](https://github.com/navikt/aksel/pull/2135))
|
||||
|
||||
- :sparkles: Støtte for header og footer
|
||||
- :boom: Bruker nå native dialog i stedet for react-modal (med polyfill for eldre nettlesere)
|
||||
|
||||
* Table: ExpandableRow har oppdatert knapp for å matche Accordion og ExpansionCard ([#2178](https://github.com/navikt/aksel/pull/2178))
|
||||
|
||||
* Table: Alle størrelser har justert padding. Small-size table bruker nå også standard typografi-størrelse (18px). ([#2178](https://github.com/navikt/aksel/pull/2178))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Oppdatert Modal - Se [Migrering](https://aksel.nav.no/grunnleggende/kode/migrering) ([#2135](https://github.com/navikt/aksel/pull/2135))
|
||||
@@ -343,6 +414,17 @@
|
||||
|
||||
* Table: Har lagt til ny size: 'large'. ([#2178](https://github.com/navikt/aksel/pull/2178))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Oppdatert Modal - Se [Migrering](https://aksel.nav.no/grunnleggende/kode/migrering) ([#2135](https://github.com/navikt/aksel/pull/2135))
|
||||
|
||||
- :sparkles: Støtte for header og footer
|
||||
- :boom: Bruker nå native dialog i stedet for react-modal (med polyfill for eldre nettlesere)
|
||||
|
||||
* Table: ExpandableRow har oppdatert knapp for å matche Accordion og ExpansionCard ([#2178](https://github.com/navikt/aksel/pull/2178))
|
||||
|
||||
* Table: Alle størrelser har justert padding. Small-size table bruker nå også standard typografi-størrelse (18px). ([#2178](https://github.com/navikt/aksel/pull/2178))
|
||||
|
||||
### @navikt/ds-tokens
|
||||
|
||||
- Gray: Gråfarger er nå mindre varme ([#2092](https://github.com/navikt/aksel/pull/2092))
|
||||
@@ -351,25 +433,15 @@
|
||||
|
||||
## 4.12.1
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Added red border to Combobox in error state ([#2184](https://github.com/navikt/aksel/pull/2184))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Added red border to Combobox in error state ([#2184](https://github.com/navikt/aksel/pull/2184))
|
||||
|
||||
## 4.12.0
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- CopyButton: Har nå prop 'iconPosition' for å høyre/venstre aligne ikon ([#2173](https://github.com/navikt/aksel/pull/2173))
|
||||
- Added red border to Combobox in error state ([#2184](https://github.com/navikt/aksel/pull/2184))
|
||||
|
||||
* List: Fikset sentrering, margins ([#2168](https://github.com/navikt/aksel/pull/2168))
|
||||
|
||||
* Checkbox: Checkmark er nå SVG-ikon og ikke Base64 ([#2171](https://github.com/navikt/aksel/pull/2171))
|
||||
|
||||
* Alert: Fikset alignment av status-ikon mot tekst ([#2179](https://github.com/navikt/aksel/pull/2179))
|
||||
## 4.12.0
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
@@ -389,6 +461,16 @@
|
||||
|
||||
* Combobox: Lukker nå nedtrekksmeny hvis man legger til ny option i singleselect ([#2177](https://github.com/navikt/aksel/pull/2177))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- CopyButton: Har nå prop 'iconPosition' for å høyre/venstre aligne ikon ([#2173](https://github.com/navikt/aksel/pull/2173))
|
||||
|
||||
* List: Fikset sentrering, margins ([#2168](https://github.com/navikt/aksel/pull/2168))
|
||||
|
||||
* Checkbox: Checkmark er nå SVG-ikon og ikke Base64 ([#2171](https://github.com/navikt/aksel/pull/2171))
|
||||
|
||||
* Alert: Fikset alignment av status-ikon mot tekst ([#2179](https://github.com/navikt/aksel/pull/2179))
|
||||
|
||||
## 4.11.2
|
||||
|
||||
### @navikt/ds-css
|
||||
@@ -403,14 +485,6 @@
|
||||
|
||||
## 4.11.0
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- HGrid: Ny komponent 'HGrid' er ute i Beta: [Dokumentasjon](https://aksel.nav.no/komponenter/core/hgrid) ([#1838](https://github.com/navikt/aksel/pull/1838))
|
||||
|
||||
* List: Fikset alignment av ikoner ([#2149](https://github.com/navikt/aksel/pull/2149))
|
||||
|
||||
* Font: La til egen font for semibold italic for bedre skalering cross-browser ([#2150](https://github.com/navikt/aksel/pull/2150))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- HGrid: Ny komponent 'HGrid' er ute i Beta: [Dokumentasjon](https://aksel.nav.no/komponenter/core/hgrid) ([#1838](https://github.com/navikt/aksel/pull/1838))
|
||||
@@ -419,6 +493,14 @@
|
||||
|
||||
* Grid: Markert som deprecated. Bruk nye 'HGrid' ([#2153](https://github.com/navikt/aksel/pull/2153))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- HGrid: Ny komponent 'HGrid' er ute i Beta: [Dokumentasjon](https://aksel.nav.no/komponenter/core/hgrid) ([#1838](https://github.com/navikt/aksel/pull/1838))
|
||||
|
||||
* List: Fikset alignment av ikoner ([#2149](https://github.com/navikt/aksel/pull/2149))
|
||||
|
||||
* Font: La til egen font for semibold italic for bedre skalering cross-browser ([#2150](https://github.com/navikt/aksel/pull/2150))
|
||||
|
||||
## 4.10.2
|
||||
|
||||
### @navikt/ds-css
|
||||
@@ -427,19 +509,15 @@
|
||||
|
||||
## 4.10.0
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Link: La til 'variant', 'underline' og 'inlineText'-prop ([#2093](https://github.com/navikt/aksel/pull/2093))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Link: La til 'variant', 'underline' og 'inlineText'-prop ([#2093](https://github.com/navikt/aksel/pull/2093))
|
||||
|
||||
## 4.9.1
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Tweaks to combobox - updated example, small bugfixes, better affordance for selected options and added flag for "isAddedByUser" to onToggleSelected ([#2144](https://github.com/navikt/aksel/pull/2144))
|
||||
- Link: La til 'variant', 'underline' og 'inlineText'-prop ([#2093](https://github.com/navikt/aksel/pull/2093))
|
||||
|
||||
## 4.9.1
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
@@ -447,36 +525,40 @@
|
||||
|
||||
- Tweaks to combobox - updated example, small bugfixes, better affordance for selected options and added flag for "isAddedByUser" to onToggleSelected ([#2144](https://github.com/navikt/aksel/pull/2144))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Tweaks to combobox - updated example, small bugfixes, better affordance for selected options and added flag for "isAddedByUser" to onToggleSelected ([#2144](https://github.com/navikt/aksel/pull/2144))
|
||||
|
||||
## 4.9.0
|
||||
|
||||
### @navikt/ds-css
|
||||
### @navikt/ds-react
|
||||
|
||||
- Table: ExpandableRow kan nå åpnes med 'expandOnRowClick'-prop ([#2127](https://github.com/navikt/aksel/pull/2127))
|
||||
|
||||
### @navikt/ds-react
|
||||
### @navikt/ds-css
|
||||
|
||||
- Table: ExpandableRow kan nå åpnes med 'expandOnRowClick'-prop ([#2127](https://github.com/navikt/aksel/pull/2127))
|
||||
|
||||
## 4.8.0
|
||||
|
||||
### @navikt/ds-css
|
||||
### @navikt/ds-react
|
||||
|
||||
- Nye komponenter `VStack`, `HStack` og `Spacer` for å enklere kunne lage layout med flexbox og spacing-variabler. ([#2040](https://github.com/navikt/aksel/pull/2040))
|
||||
|
||||
### @navikt/ds-react
|
||||
### @navikt/ds-css
|
||||
|
||||
- Nye komponenter `VStack`, `HStack` og `Spacer` for å enklere kunne lage layout med flexbox og spacing-variabler. ([#2040](https://github.com/navikt/aksel/pull/2040))
|
||||
|
||||
## 4.7.4
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Oppdatert bruk av REM i komponenter for forbedret utrykk ved font-scaling i browser ([#2126](https://github.com/navikt/aksel/pull/2126))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Textfield: La til type 'time' som tilgjengelig option ([#2137](https://github.com/navikt/aksel/pull/2137))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Oppdatert bruk av REM i komponenter for forbedret utrykk ved font-scaling i browser ([#2126](https://github.com/navikt/aksel/pull/2126))
|
||||
|
||||
## 4.7.3
|
||||
|
||||
### @navikt/ds-react
|
||||
@@ -485,11 +567,11 @@
|
||||
|
||||
## 4.7.2
|
||||
|
||||
### @navikt/ds-css
|
||||
### @navikt/ds-react
|
||||
|
||||
- Stepper: Fikset `hotizontal`-bug når step var `completed` ([#2116](https://github.com/navikt/aksel/pull/2116))
|
||||
|
||||
### @navikt/ds-react
|
||||
### @navikt/ds-css
|
||||
|
||||
- Stepper: Fikset `hotizontal`-bug når step var `completed` ([#2116](https://github.com/navikt/aksel/pull/2116))
|
||||
|
||||
@@ -501,10 +583,6 @@
|
||||
|
||||
## 4.7.0
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Ny komponent Combobox! ([#1868](https://github.com/navikt/aksel/pull/1868))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Ny komponent Combobox! ([#1868](https://github.com/navikt/aksel/pull/1868))
|
||||
@@ -513,38 +591,42 @@
|
||||
|
||||
* Combobox post-release tweaks ([#2112](https://github.com/navikt/aksel/pull/2112))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Ny komponent Combobox! ([#1868](https://github.com/navikt/aksel/pull/1868))
|
||||
|
||||
## 4.6.1
|
||||
|
||||
### @navikt/ds-css
|
||||
### @navikt/ds-react
|
||||
|
||||
- Skjema: Labels og Legends bruker nå inline-flex når readOnly er satt ([#2089](https://github.com/navikt/aksel/pull/2089))
|
||||
|
||||
### @navikt/ds-react
|
||||
### @navikt/ds-css
|
||||
|
||||
- Skjema: Labels og Legends bruker nå inline-flex når readOnly er satt ([#2089](https://github.com/navikt/aksel/pull/2089))
|
||||
|
||||
## 4.6.0
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Skjema: De fleste skjemakomponenter har nå styling for `readOnly`-state ([#2080](https://github.com/navikt/aksel/pull/2080))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Skjema: De fleste skjemakomponenter støtter nå `readOnly`-state ([#2080](https://github.com/navikt/aksel/pull/2080))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Skjema: De fleste skjemakomponenter har nå styling for `readOnly`-state ([#2080](https://github.com/navikt/aksel/pull/2080))
|
||||
|
||||
## 4.5.0
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- - Alert: La til `closeButton`-prop ([#2079](https://github.com/navikt/aksel/pull/2079))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- - Alert: La til `closeButton`-prop ([#2079](https://github.com/navikt/aksel/pull/2079))
|
||||
|
||||
* - Button: Fikset outline-bug i tertiary-variant ved `:active`-state ([#2079](https://github.com/navikt/aksel/pull/2079))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- - Alert: La til `closeButton`-prop ([#2079](https://github.com/navikt/aksel/pull/2079))
|
||||
|
||||
## 4.4.2
|
||||
|
||||
### @navikt/ds-react
|
||||
@@ -559,46 +641,46 @@
|
||||
|
||||
## 4.4.0
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Shadow-tokens er oppdatert: Datepicker har nå ikke border, Modal bruker shadow-xlarge, LinkCard bruker shadow-xsmall ([PR](https://github.com/navikt/aksel/pull/2041))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Fikset klassenavn brukt for popover i Datepicker og Monthpicker ([PR](https://github.com/navikt/aksel/pull/2041))
|
||||
|
||||
### @navikt/ds-tokens
|
||||
|
||||
- Shadow-tokens er oppdatert til mer tydeligere varianter ([PR](https://github.com/navikt/aksel/pull/2041))
|
||||
|
||||
### @navikt/ds-tailwind
|
||||
|
||||
- Shadow-tokens er oppdatert ([PR](https://github.com/navikt/aksel/pull/2041))
|
||||
|
||||
- Oppdatert text-subtle og icon-subtle tokens til 700-skala (tidligere 600). ([PR](https://github.com/navikt/aksel/pull/2036))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Shadow-tokens er oppdatert: Datepicker har nå ikke border, Modal bruker shadow-xlarge, LinkCard bruker shadow-xsmall ([PR](https://github.com/navikt/aksel/pull/2041))
|
||||
|
||||
### @navikt/ds-tokens
|
||||
|
||||
- Shadow-tokens er oppdatert til mer tydeligere varianter ([PR](https://github.com/navikt/aksel/pull/2041))
|
||||
|
||||
## 4.3.0
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Popover og Helptext har nå luft mot siden av skjerm på mindre flater ([PR](https://github.com/navikt/aksel/pull/2069))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Popover/Helptext: Maksbredde på 100vw - 1.5rem. Treffer ikke lengre kanten på skjermen. ([PR](https://github.com/navikt/aksel/pull/2069))
|
||||
|
||||
* ExpansionCard: Ved nesting av komponetene fikk man styling fra parent ([PR](https://github.com/navikt/aksel/pull/2067))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Popover og Helptext har nå luft mot siden av skjerm på mindre flater ([PR](https://github.com/navikt/aksel/pull/2069))
|
||||
|
||||
## 4.2.0
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Chat: `small`-size, innebygde varianter for farge og oppdatert utseende. ([PR](https://github.com/navikt/aksel/pull/2048))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Oppdatert Chat: `size` og `variant`-prop, optional `avatar`, uu og ui-forbedringer ([PR](https://github.com/navikt/aksel/pull/2048))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Chat: `small`-size, innebygde varianter for farge og oppdatert utseende. ([PR](https://github.com/navikt/aksel/pull/2048))
|
||||
|
||||
## 4.1.7
|
||||
|
||||
### @navikt/ds-react
|
||||
@@ -651,11 +733,9 @@
|
||||
|
||||
## 4.1.0
|
||||
|
||||
### @navikt/ds-css
|
||||
### @navikt/aksel-stylelint
|
||||
|
||||
- Chips: `neutral` og `action`-varianter av Chips.Toggle. Opt-out mulighet for Checkmark. Oppdatert checkmark-ikon ([PR](https://github.com/navikt/aksel/pull/2035))
|
||||
|
||||
- Alle description-felter på fieldsets har nå `text-subtle` som farge. ([PR](https://github.com/navikt/aksel/pull/2036))
|
||||
- Deprecated klassenavn `navds-chips--icon-left` ([PR](https://github.com/navikt/aksel/pull/2035))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
@@ -667,20 +747,22 @@
|
||||
|
||||
* Accordion: La til `indent`-prop ([PR](https://github.com/navikt/aksel/pull/2027))
|
||||
|
||||
### @navikt/ds-tailwind
|
||||
|
||||
- :tada: Fargetokens for datavisualisering. ([PR](https://github.com/navikt/aksel/pull/2032))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Chips: `neutral` og `action`-varianter av Chips.Toggle. Opt-out mulighet for Checkmark. Oppdatert checkmark-ikon ([PR](https://github.com/navikt/aksel/pull/2035))
|
||||
|
||||
- Alle description-felter på fieldsets har nå `text-subtle` som farge. ([PR](https://github.com/navikt/aksel/pull/2036))
|
||||
|
||||
### @navikt/ds-tokens
|
||||
|
||||
- :tada: Fargetokens for datavisualisering. ([PR](https://github.com/navikt/aksel/pull/2032))
|
||||
|
||||
- Oppdatert text-subtle og icon-subtle tokens til 700-skala (tidligere 600). ([PR](https://github.com/navikt/aksel/pull/2036))
|
||||
|
||||
### @navikt/ds-tailwind
|
||||
|
||||
- :tada: Fargetokens for datavisualisering. ([PR](https://github.com/navikt/aksel/pull/2032))
|
||||
|
||||
### @navikt/aksel-stylelint
|
||||
|
||||
- Deprecated klassenavn `navds-chips--icon-left` ([PR](https://github.com/navikt/aksel/pull/2035))
|
||||
|
||||
## 4.0.0
|
||||
|
||||
### @navikt/aksel
|
||||
@@ -689,6 +771,12 @@
|
||||
|
||||
- Codemods for migrering fra `@navikt/ds-react-internal` til `@navikt/ds-react` [Guide](https://aksel.nav.no/grunnleggende/kode/migrering#h3b8538e1981d) ([PR](https://github.com/navikt/aksel/pull/2026))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Datepicker og Monthpicker er ute av beta. [Guide](https://aksel.nav.no/grunnleggende/kode/migrering#h4ef68ae743b0) ([PR](https://github.com/navikt/aksel/pull/2026))
|
||||
|
||||
- Komponenter i `@navikt/ds-react-internal` er flyttet over til `@navikt/ds-react`. [Guide](https://aksel.nav.no/grunnleggende/kode/migrering#h3b8538e1981d) ([PR](https://github.com/navikt/aksel/pull/2026))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- All styling fra `@navikt/ds-css-internal` er flyttet til `@navikt/ds-css`. ([PR](https://github.com/navikt/aksel/pull/2026))
|
||||
@@ -699,12 +787,6 @@
|
||||
|
||||
- Styling for flyttede komponenter finnes nå på CDN [Guide](https://aksel.nav.no/grunnleggende/kode/css-import)
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Datepicker og Monthpicker er ute av beta. [Guide](https://aksel.nav.no/grunnleggende/kode/migrering#h4ef68ae743b0) ([PR](https://github.com/navikt/aksel/pull/2026))
|
||||
|
||||
- Komponenter i `@navikt/ds-react-internal` er flyttet over til `@navikt/ds-react`. [Guide](https://aksel.nav.no/grunnleggende/kode/migrering#h3b8538e1981d) ([PR](https://github.com/navikt/aksel/pull/2026))
|
||||
|
||||
## 3.4.2
|
||||
|
||||
### @navikt/aksel-stylelint
|
||||
@@ -719,17 +801,17 @@
|
||||
|
||||
## 3.4.0
|
||||
|
||||
### @navikt/ds-css
|
||||
### @navikt/aksel-stylelint
|
||||
|
||||
- Tag: `moderate`-variant ([PR](https://github.com/navikt/aksel/pull/2010))
|
||||
- Stylelint-pakke for Aksel ([PR](https://github.com/navikt/aksel/pull/1973))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Tag: `moderate`-variant ([PR](https://github.com/navikt/aksel/pull/2010))
|
||||
|
||||
### @navikt/aksel-stylelint
|
||||
### @navikt/ds-css
|
||||
|
||||
- Stylelint-pakke for Aksel ([PR](https://github.com/navikt/aksel/pull/1973))
|
||||
- Tag: `moderate`-variant ([PR](https://github.com/navikt/aksel/pull/2010))
|
||||
|
||||
## 3.3.1
|
||||
|
||||
@@ -755,18 +837,18 @@
|
||||
|
||||
- Nye ikoner `FileParagraph` og `FilePlus` ([PR](https://github.com/navikt/aksel/pull/1998))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Datepicker: Fikset small-variant av datepicker-input. ([PR](https://github.com/navikt/aksel/pull/1993))
|
||||
|
||||
- CopyButton: `xsmall`-size for bruk i tabeller ([PR](https://github.com/navikt/aksel/pull/1994))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Datepicker: Oppdatert small-variant av Datepicker.Input. UI-oppdatert samtidig. ([PR](https://github.com/navikt/aksel/pull/1993))
|
||||
|
||||
- CopyButton: `xsmall`-variant for bruk i tabeller ([PR](https://github.com/navikt/aksel/pull/1994))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Datepicker: Fikset small-variant av datepicker-input. ([PR](https://github.com/navikt/aksel/pull/1993))
|
||||
|
||||
- CopyButton: `xsmall`-size for bruk i tabeller ([PR](https://github.com/navikt/aksel/pull/1994))
|
||||
|
||||
## 3.2.2
|
||||
|
||||
### @navikt/ds-react
|
||||
@@ -787,16 +869,16 @@
|
||||
|
||||
## 3.1.0
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Oppdatert Label og Description spacing for alle skjemakomponenter ([PR](https://github.com/navikt/aksel/pull/1967))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Alle komponenter bruker nå default `:focus-visible` for fokusmarkering. Medfølger også fallback for `:focus` ([PR](https://github.com/navikt/aksel/pull/1966))
|
||||
|
||||
* Oppdatert Label og Description spacing for skjemakomponenter ([PR](https://github.com/navikt/aksel/pull/1967))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Oppdatert Label og Description spacing for alle skjemakomponenter ([PR](https://github.com/navikt/aksel/pull/1967))
|
||||
|
||||
## 3.0.1
|
||||
|
||||
### @navikt/ds-react
|
||||
@@ -807,16 +889,16 @@
|
||||
|
||||
## 3.0.0
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Accordion: Chevron er left-aligned, deler av styling er refaktorert, `size`-props og `neutral`-variant ([PR](https://github.com/navikt/aksel/pull/1964))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Accordion: left-aligner chevron, `neutral`-variant, `size`-prop ([PR](https://github.com/navikt/aksel/pull/1964))
|
||||
|
||||
- Bruker nå nå `@navikt/aksel-icons` for interne ikoner ([PR](https://github.com/navikt/aksel/pull/1964))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Accordion: Chevron er left-aligned, deler av styling er refaktorert, `size`-props og `neutral`-variant ([PR](https://github.com/navikt/aksel/pull/1964))
|
||||
|
||||
## 2.9.8
|
||||
|
||||
### @navikt/aksel-icons
|
||||
@@ -911,14 +993,14 @@
|
||||
|
||||
## 2.8.7
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Button: token for tertiary
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Fikset typografi-bruk for `Radio` og `Checkbox`.
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Button: token for tertiary
|
||||
|
||||
## 2.8.6
|
||||
|
||||
### @navikt/ds-css
|
||||
@@ -947,14 +1029,14 @@
|
||||
|
||||
## 2.8.2
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- ExpansioCard: Oppdatert typografi ([PR](https://github.com/navikt/aksel/pull/1870))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- ExpansioCard: Oppdatert typografibruk ([PR](https://github.com/navikt/aksel/pull/1870))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- ExpansioCard: Oppdatert typografi ([PR](https://github.com/navikt/aksel/pull/1870))
|
||||
|
||||
## 2.8.1
|
||||
|
||||
### @navikt/ds-react
|
||||
@@ -1017,14 +1099,14 @@
|
||||
|
||||
## 2.5.0
|
||||
|
||||
### @navikt/ds-tokens
|
||||
|
||||
- Breakpoint-tokens lagt til ([PR](https://github.com/navikt/aksel/pull/1832))
|
||||
|
||||
### @navikt/ds-tailwind
|
||||
|
||||
- Breakpoint-tokens er lagt til. Overskriver native tailwind-breakpoints ([PR](https://github.com/navikt/aksel/pull/1832))
|
||||
|
||||
### @navikt/ds-tokens
|
||||
|
||||
- Breakpoint-tokens lagt til ([PR](https://github.com/navikt/aksel/pull/1832))
|
||||
|
||||
## 2.4.3
|
||||
|
||||
### @navikt/ds-css
|
||||
@@ -1033,14 +1115,14 @@
|
||||
|
||||
## 2.4.2
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- List: La til støtte for nesting ([PR](https://github.com/navikt/aksel/pull/1823))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- List: la til støtte for nestede lister ([PR](https://github.com/navikt/aksel/pull/1823))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- List: La til støtte for nesting ([PR](https://github.com/navikt/aksel/pull/1823))
|
||||
|
||||
## 2.4.1
|
||||
|
||||
### @navikt/ds-react
|
||||
@@ -1069,18 +1151,18 @@
|
||||
|
||||
## 2.2.0
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- ToggleGroup: `Neutral`-variant. ([PR](https://github.com/navikt/aksel/pull/1789))
|
||||
|
||||
- Button: `Neutral`-variant.
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- ToggleGroup: `neutral`-variant ([PR](https://github.com/navikt/aksel/pull/1789))
|
||||
|
||||
- Button: `neutral`-variant ([PR](https://github.com/navikt/aksel/pull/1789))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- ToggleGroup: `Neutral`-variant. ([PR](https://github.com/navikt/aksel/pull/1789))
|
||||
|
||||
- Button: `Neutral`-variant.
|
||||
|
||||
### @navikt/ds-tokens
|
||||
|
||||
- Justeringer av semantiske fargetokens, statusfarger nå mer tydelig ([PR](https://github.com/navikt/aksel/pull/1787))
|
||||
@@ -1201,24 +1283,24 @@
|
||||
|
||||
## 2.0.0
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Fonter blir bruk i all typografi blir nå lastet fra CDN
|
||||
|
||||
### @navikt/ds-tailwind
|
||||
|
||||
- Alle token er oppdatert til nytt format. ([Guide](https://aksel.nav.no/grunnleggende/kode/migrering#h76f47744d112))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Fontlasting: Fonter lastes nå fra NAV-CDN
|
||||
|
||||
- Tokens: Alle komponenter bruker nå semantiske tokens for som standard, med innebygd støtte for komponent-spesifikke tokens.
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Fonter blir bruk i all typografi blir nå lastet fra CDN
|
||||
|
||||
### @navikt/ds-tokens
|
||||
|
||||
- Alle token er oppdatert til nytt format. ([Guide](https://aksel.nav.no/grunnleggende/kode/migrering#h76f47744d112))
|
||||
|
||||
### @navikt/ds-tailwind
|
||||
|
||||
- Alle token er oppdatert til nytt format. ([Guide](https://aksel.nav.no/grunnleggende/kode/migrering#h76f47744d112))
|
||||
|
||||
## 1.5.10
|
||||
|
||||
### @navikt/ds-react
|
||||
@@ -1263,16 +1345,16 @@
|
||||
|
||||
## 1.5.0
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Tag: `filled`-varianter ([PR](https://github.com/navikt/aksel/pull/1684))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Tag: `filles`-varianter ([PR](https://github.com/navikt/aksel/pull/1684))
|
||||
|
||||
- Ny komponent Chips! ([PR](https://github.com/navikt/aksel/pull/1668))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Tag: `filled`-varianter ([PR](https://github.com/navikt/aksel/pull/1684))
|
||||
|
||||
## 1.4.4
|
||||
|
||||
### @navikt/ds-react
|
||||
@@ -1299,10 +1381,6 @@
|
||||
|
||||
## 1.3.39
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Fikset bruk av `:watch` for diverse parsere (less, parcel)
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- useId-bug fikset ved bruk i både react 17 og 18
|
||||
@@ -1325,6 +1403,10 @@
|
||||
|
||||
- Stepper: `interactive` og `completed`-props for wizard-løsninger
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Fikset bruk av `:watch` for diverse parsere (less, parcel)
|
||||
|
||||
## 1.3.24
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
"website"
|
||||
],
|
||||
"dependencies": {
|
||||
"@navikt/aksel-icons": "^5.7.4",
|
||||
"@navikt/ds-css": "^5.7.4",
|
||||
"@navikt/ds-react": "^5.7.4",
|
||||
"@navikt/ds-tailwind": "^5.7.4",
|
||||
"@navikt/ds-tokens": "^5.7.4"
|
||||
"@navikt/aksel-icons": "^5.10.0",
|
||||
"@navikt/ds-css": "^5.10.0",
|
||||
"@navikt/ds-react": "^5.10.0",
|
||||
"@navikt/ds-tailwind": "^5.10.0",
|
||||
"@navikt/ds-tokens": "^5.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier-plugin-tailwindcss": "^0.2.3"
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import type { StorybookConfig } from "@storybook/nextjs";
|
||||
|
||||
import { readFileSync } from "fs";
|
||||
import { join, dirname } from "path";
|
||||
import { loadCsf } from "@storybook/csf-tools";
|
||||
import type { StorybookConfig } from "@storybook/nextjs";
|
||||
import { readFileSync } from "fs";
|
||||
import { dirname, join } from "path";
|
||||
|
||||
/**
|
||||
* This function is used to resolve the absolute path of a package.
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { amplitudeLogNavigation } from "@/logging";
|
||||
import { BodyLong, Detail, Heading, Link } from "@navikt/ds-react";
|
||||
import {
|
||||
PortableText,
|
||||
@@ -5,30 +6,26 @@ import {
|
||||
PortableTextReactComponents,
|
||||
} from "@portabletext/react";
|
||||
import cl from "clsx";
|
||||
import ExpansionCard from "components/sanity-modules/ExpansionCard";
|
||||
import Accordion from "components/sanity-modules/accordion/Accordion";
|
||||
import Alert from "components/sanity-modules/alert/Alert";
|
||||
import Bilde from "components/sanity-modules/bilde/Bilde";
|
||||
import InnholdsKort from "components/sanity-modules/cards/InnholdsKort";
|
||||
import { InlineCode } from "components/website-modules/InlineCode";
|
||||
import { KBD } from "components/website-modules/KBD";
|
||||
import CodeExamples from "components/sanity-modules/code-examples/CodeExamples";
|
||||
import SnippetLazy from "components/sanity-modules/code-snippet/SnippetLazy";
|
||||
import DoDont from "components/sanity-modules/do-dont/DoDont";
|
||||
import ExpansionCard from "components/sanity-modules/expansioncard/ExpansionCard";
|
||||
import PropsSeksjon from "components/sanity-modules/props/PropsSeksjon";
|
||||
import RelatertInnhold from "components/sanity-modules/relatert-innhold/RelatertInnhold";
|
||||
import SideModul from "components/sanity-modules/side-modul/SideModul";
|
||||
import Tabell from "components/sanity-modules/tabell/Tabell";
|
||||
import TastaturModul from "components/sanity-modules/tastatur-tabell/TastaturTabell";
|
||||
import Tips from "components/sanity-modules/tips/Tips";
|
||||
import TokenTable from "components/sanity-modules/token-tabell/TokenTable";
|
||||
import Video from "components/sanity-modules/video/Video";
|
||||
import InlineCode from "components/website-modules/InlineCode";
|
||||
import KBD from "components/website-modules/KBD";
|
||||
import NextLink from "next/link";
|
||||
import { Children } from "react";
|
||||
import {
|
||||
Accordion,
|
||||
Alert,
|
||||
Bilde,
|
||||
CodeExamples,
|
||||
DoDont,
|
||||
PropsSeksjon,
|
||||
RelatertInnhold,
|
||||
SideModul,
|
||||
Snippet,
|
||||
Tabell,
|
||||
TastaturModul,
|
||||
Tips,
|
||||
TokenTable,
|
||||
UuFeedback,
|
||||
Video,
|
||||
logNav,
|
||||
} from ".";
|
||||
|
||||
const serializers: Partial<PortableTextReactComponents> = {
|
||||
types: {
|
||||
@@ -40,7 +37,7 @@ const serializers: Partial<PortableTextReactComponents> = {
|
||||
bilde: ({ value }) => <Bilde node={value} />,
|
||||
alert: ({ value }) => <Alert node={value} />,
|
||||
expansioncard: ({ value }) => <ExpansionCard node={value} />,
|
||||
kode: ({ value }) => <Snippet node={value} />,
|
||||
kode: ({ value }) => <SnippetLazy node={value} />,
|
||||
tabell_v2: ({ value }) => <Tabell node={value} />,
|
||||
accordion: ({ value }) => <Accordion node={value} />,
|
||||
props_seksjon: ({ value }) => <PropsSeksjon node={value} />,
|
||||
@@ -49,7 +46,6 @@ const serializers: Partial<PortableTextReactComponents> = {
|
||||
video: ({ value }) => <Video node={value} />,
|
||||
tips: ({ value }) => <Tips node={value} />,
|
||||
kode_eksempler: ({ value }) => <CodeExamples node={value} />,
|
||||
uufeedback: ({ value }) => <UuFeedback node={value} />,
|
||||
},
|
||||
unknownType: () => null,
|
||||
block: {
|
||||
@@ -130,14 +126,11 @@ const serializers: Partial<PortableTextReactComponents> = {
|
||||
|
||||
return (
|
||||
<Link
|
||||
as={NextLink}
|
||||
href={href}
|
||||
inlineText
|
||||
onClick={(e) =>
|
||||
logNav(
|
||||
"link",
|
||||
window.location.pathname,
|
||||
e.currentTarget.getAttribute("href")
|
||||
)
|
||||
amplitudeLogNavigation("link", e.currentTarget.getAttribute("href"))
|
||||
}
|
||||
{...(href.startsWith("http") &&
|
||||
!href.startsWith("https://aksel.nav.no/")
|
||||
@@ -160,11 +153,7 @@ const serializers: Partial<PortableTextReactComponents> = {
|
||||
href={href}
|
||||
inlineText
|
||||
onClick={(e) =>
|
||||
logNav(
|
||||
"link",
|
||||
window.location.pathname,
|
||||
e.currentTarget.getAttribute("href")
|
||||
)
|
||||
amplitudeLogNavigation("link", e.currentTarget.getAttribute("href"))
|
||||
}
|
||||
>
|
||||
{text}
|
||||
|
||||
@@ -1,22 +1,4 @@
|
||||
export * from "./assets";
|
||||
export { default as Accordion } from "./sanity-modules/Accordion";
|
||||
export { default as Alert } from "./sanity-modules/Alert";
|
||||
export { default as Bilde } from "./sanity-modules/Bilde";
|
||||
export * from "./sanity-modules/cards";
|
||||
export * from "./sanity-modules/code";
|
||||
export * from "./sanity-modules/code-examples";
|
||||
export { default as DoDont } from "./sanity-modules/DoDont";
|
||||
export { default as IntroKomponent } from "./sanity-modules/IntroSeksjon";
|
||||
export { default as PropsSeksjon } from "./sanity-modules/props/PropsSeksjon";
|
||||
export { default as PropTable } from "./sanity-modules/props/PropTabell";
|
||||
export { default as SideModul } from "./sanity-modules/SideModul";
|
||||
export { default as Tabell } from "./sanity-modules/Tabell";
|
||||
export { default as TastaturModul } from "./sanity-modules/TastaturTabell";
|
||||
export { default as Tips } from "./sanity-modules/Tips";
|
||||
export { default as TokenTable } from "./sanity-modules/TokenTable";
|
||||
export * from "./sanity-modules/UuFeedback";
|
||||
export { default as Video } from "./sanity-modules/Video";
|
||||
export * from "./website-modules/BreadCrumbs";
|
||||
export { default as Feedback } from "./website-modules/feedback";
|
||||
export * from "./website-modules/toc/TOC";
|
||||
export * from "./website-modules/utils";
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { WithSidebar } from "./WithSidebar";
|
||||
import { SanityBlockContent } from "@/sanity-block";
|
||||
import { getBlocks } from "@/sb-util";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { WithSidebar } from "./WithSidebar";
|
||||
|
||||
const meta = {
|
||||
title: "Layout/WithSidebar",
|
||||
@@ -28,8 +28,10 @@ export const KomponenterLanding: Story = {
|
||||
args: {
|
||||
children,
|
||||
pageType: {
|
||||
type: "Komponenter",
|
||||
type: "komponenter",
|
||||
title: "Heading",
|
||||
rootUrl: "/komponenter",
|
||||
rootTitle: "Komponenter",
|
||||
},
|
||||
pageProps: { content: "1" },
|
||||
variant: "landingPage",
|
||||
@@ -65,8 +67,10 @@ export const Komponenter: Story = {
|
||||
args: {
|
||||
children,
|
||||
pageType: {
|
||||
type: "Komponenter",
|
||||
type: "komponenter",
|
||||
title: "Heading",
|
||||
rootUrl: "/komponenter",
|
||||
rootTitle: "Komponenter",
|
||||
},
|
||||
pageProps: { content: "1" },
|
||||
variant: "page",
|
||||
|
||||
@@ -4,7 +4,6 @@ import { capitalize } from "@/utils";
|
||||
import { Box, Detail, Heading, Link } from "@navikt/ds-react";
|
||||
import cl from "clsx";
|
||||
import { Sidebar } from "components/layout/sidebar/Sidebar";
|
||||
import Feedback from "components/website-modules/feedback";
|
||||
import { TableOfContentsv2 } from "components/website-modules/toc/TOCv2";
|
||||
import Image from "next/legacy/image";
|
||||
import NextLink from "next/link";
|
||||
@@ -21,8 +20,10 @@ export const WithSidebar = ({
|
||||
children: React.ReactNode;
|
||||
sidebar: AkselSidebarT;
|
||||
pageType: {
|
||||
type: "Komponenter" | "Grunnleggende";
|
||||
type: "komponenter" | "grunnleggende" | "templates";
|
||||
title: string;
|
||||
rootUrl: string;
|
||||
rootTitle: string;
|
||||
};
|
||||
pageProps: any;
|
||||
intro?: React.ReactNode;
|
||||
@@ -30,7 +31,11 @@ export const WithSidebar = ({
|
||||
variant?: "page" | "landingPage";
|
||||
}) => {
|
||||
return (
|
||||
<Box background="bg-default" paddingBlock="6 24">
|
||||
<Box
|
||||
background="bg-default"
|
||||
paddingBlock="6 24"
|
||||
className="min-h-screen-header"
|
||||
>
|
||||
<div className="mx-auto flex w-full max-w-screen-2xl gap-6">
|
||||
<Sidebar kategori={pageType.type} links={sidebar} />
|
||||
<main
|
||||
@@ -53,13 +58,9 @@ export const WithSidebar = ({
|
||||
<div className="z-[1]">
|
||||
{variant === "page" && pageProps?.kategori && (
|
||||
<Detail as="div" className="mb-2">
|
||||
<NextLink
|
||||
href={`/${pageType.type.toLowerCase()}`}
|
||||
passHref
|
||||
legacyBehavior
|
||||
>
|
||||
<NextLink href={pageType.rootUrl} passHref legacyBehavior>
|
||||
<Link className="text-text-default">
|
||||
{capitalize(pageType.type)}
|
||||
{pageType.rootTitle}
|
||||
</Link>
|
||||
</NextLink>{" "}
|
||||
/ {capitalize(pageProps.kategori)}
|
||||
@@ -111,12 +112,9 @@ export const WithSidebar = ({
|
||||
</div>
|
||||
|
||||
<div className={cl("sm:px-6 md:px-10", { flex: variant === "page" })}>
|
||||
{variant === "page" && (
|
||||
<TableOfContentsv2 changedState={pageProps["content"]} />
|
||||
)}
|
||||
{variant === "page" && <TableOfContentsv2 />}
|
||||
<div className="w-full">
|
||||
{children}
|
||||
<Feedback docId={pageProps?._id} docType={pageProps?._type} />
|
||||
{footer && <div className="w-full">{footer}</div>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { amplitudeLogNavigation } from "@/logging";
|
||||
import { BodyShort, Heading, Link } from "@navikt/ds-react";
|
||||
import { FigmaIcon, GithubIcon, SlackIcon } from "components/assets";
|
||||
import Logo from "components/assets/Logo";
|
||||
import { logNav } from "components/website-modules/utils/amplitude";
|
||||
import dynamic from "next/dynamic";
|
||||
/* import FooterForm from "./FooterForm"; */
|
||||
|
||||
@@ -119,11 +119,7 @@ function FooterLink({ children, href }) {
|
||||
className="text-text-on-inverted focus:shadow-focus focus:text-text-default flex w-fit items-center gap-1 underline hover:no-underline focus:bg-blue-200 focus:shadow-blue-200"
|
||||
href={href}
|
||||
onClick={(e) =>
|
||||
logNav(
|
||||
"footer",
|
||||
window.location.pathname,
|
||||
e.currentTarget.getAttribute("href")
|
||||
)
|
||||
amplitudeLogNavigation("footer", e.currentTarget.getAttribute("href"))
|
||||
}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { MenuHamburgerIcon } from "@navikt/aksel-icons";
|
||||
|
||||
import { amplitudeLogNavigation } from "@/logging";
|
||||
import { Modal } from "@navikt/ds-react";
|
||||
import cl from "clsx";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { useState } from "react";
|
||||
import { logNav } from "../..";
|
||||
|
||||
const LinkElement = ({ name, href, onClick }) => {
|
||||
const { asPath } = useRouter();
|
||||
@@ -23,9 +23,8 @@ const LinkElement = ({ name, href, onClick }) => {
|
||||
}
|
||||
)}
|
||||
onClick={(e) => {
|
||||
logNav(
|
||||
amplitudeLogNavigation(
|
||||
"header",
|
||||
window.location.pathname,
|
||||
e.currentTarget.getAttribute("href")
|
||||
);
|
||||
onClick();
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { amplitudeLogNavigation } from "@/logging";
|
||||
import { MagnifyingGlassIcon } from "@navikt/aksel-icons";
|
||||
import { Button } from "@navikt/ds-react";
|
||||
import cl from "clsx";
|
||||
import Logo from "components/assets/Logo";
|
||||
import { Hamburger } from "components/layout/header/Hamburger";
|
||||
import dynamic from "next/dynamic";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { logNav } from "../..";
|
||||
import { Button } from "@navikt/ds-react";
|
||||
import dynamic from "next/dynamic";
|
||||
import { MagnifyingGlassIcon } from "@navikt/aksel-icons";
|
||||
|
||||
export const GlobalSearch = dynamic(() => import("./search"), {
|
||||
loading: () => (
|
||||
@@ -46,11 +46,7 @@ const LinkElement = ({ name, href, prefetch = undefined }) => {
|
||||
}
|
||||
)}
|
||||
onClick={(e) =>
|
||||
logNav(
|
||||
"header",
|
||||
window.location.pathname,
|
||||
e.currentTarget.getAttribute("href")
|
||||
)
|
||||
amplitudeLogNavigation("header", e.currentTarget.getAttribute("href"))
|
||||
}
|
||||
>
|
||||
{name}
|
||||
@@ -87,9 +83,8 @@ export const Header = ({
|
||||
href="/"
|
||||
passHref
|
||||
onClick={(e) =>
|
||||
logNav(
|
||||
amplitudeLogNavigation(
|
||||
"header",
|
||||
window.location.pathname,
|
||||
e.currentTarget.getAttribute("href")
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { searchOptions } from "@/types";
|
||||
import { XMarkIcon } from "@navikt/aksel-icons";
|
||||
import { Button, Chips, Search } from "@navikt/ds-react";
|
||||
import { KBD } from "components/website-modules/KBD";
|
||||
import KBD from "components/website-modules/KBD";
|
||||
import { useContext, useRef } from "react";
|
||||
import { useShortcut } from "./hooks";
|
||||
import {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { SearchHitT, searchOptions } from "@/types";
|
||||
import { ChevronRightIcon } from "@navikt/aksel-icons";
|
||||
import { Chips } from "@navikt/ds-react";
|
||||
import cl from "clsx";
|
||||
import { Tag } from "components/sanity-modules/frontpage-blocks/Tag";
|
||||
import { Tag } from "components/sanity-modules/frontpage-blocks/latest-articles/Tag";
|
||||
import { StatusTag } from "components/website-modules/StatusTag";
|
||||
import Image from "next/legacy/image";
|
||||
import { default as Link, default as NextLink } from "next/link";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { GroupedHitsT, SearchHitT, searchOptions } from "@/types";
|
||||
import { Heading } from "@navikt/ds-react";
|
||||
import cl from "clsx";
|
||||
import { Tag } from "components/sanity-modules/frontpage-blocks/Tag";
|
||||
import { Tag } from "components/sanity-modules/frontpage-blocks/latest-articles/Tag";
|
||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { Hit } from "./Hit";
|
||||
|
||||
|
||||
@@ -95,6 +95,7 @@ describe("createSearchResult", () => {
|
||||
aksel_blogg: 1,
|
||||
aksel_prinsipp: 1,
|
||||
aksel_standalone: 1,
|
||||
templates_artikkel: 0,
|
||||
};
|
||||
const actual: SearchResultsT = createSearchResult(result, rawResults);
|
||||
expect(actual.hits).toEqual(expected);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { amplitude, AmplitudeEvents } from "@/logging";
|
||||
import { allArticleDocuments } from "@/sanity/config";
|
||||
import { SearchResultsT } from "@/types";
|
||||
import { debounce } from "@navikt/ds-react";
|
||||
import { useMemo, useState } from "react";
|
||||
import useSWRImmutable from "swr/immutable";
|
||||
import { createSearchResult, formatResults, fuseSearch } from "../utils";
|
||||
import { debounce } from "@navikt/ds-react";
|
||||
import { logSearch } from "@/utils";
|
||||
|
||||
export const useSearch = () => {
|
||||
const [fuseResults, setFuseResults] = useState<SearchResultsT>(null);
|
||||
@@ -35,7 +35,7 @@ export const useSearch = () => {
|
||||
...createSearchResult(formatedResults, rawResults),
|
||||
query: value,
|
||||
});
|
||||
logSearch({
|
||||
amplitude.track(AmplitudeEvents.søk, {
|
||||
type: "standard",
|
||||
searchedFromUrl: window.location.pathname,
|
||||
query: value,
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
import { logSearch } from "@/utils";
|
||||
import { AmplitudeEvents, amplitude } from "@/logging";
|
||||
import { createContext, useCallback, useContext } from "react";
|
||||
import { useSearch } from "../hooks";
|
||||
import { SearchContext } from "./SearchProvider";
|
||||
@@ -32,7 +32,7 @@ export const SearchLoggingProvider = ({
|
||||
accuracy: (100 - index / context?.results?.totalHits).toFixed(0),
|
||||
topResult: index <= context?.results?.topResults?.length,
|
||||
};
|
||||
logSearch(data);
|
||||
amplitude.track(AmplitudeEvents.søk, data);
|
||||
},
|
||||
[
|
||||
query,
|
||||
|
||||
@@ -29,6 +29,9 @@ export function createSearchResult(
|
||||
.length,
|
||||
aksel_blogg: rawResults.filter((x: any) => x.item._type === "aksel_blogg")
|
||||
.length,
|
||||
templates_artikkel: rawResults.filter(
|
||||
(x: any) => x.item._type === "templates_artikkel"
|
||||
).length,
|
||||
aksel_prinsipp: rawResults.filter(
|
||||
(x: any) => x.item._type === "aksel_prinsipp"
|
||||
).length,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { amplitudeLogNavigation } from "@/logging";
|
||||
import { AkselSidebarT } from "@/types";
|
||||
import { logNav } from "@/utils";
|
||||
import {
|
||||
ChevronDownIcon,
|
||||
SparklesIcon,
|
||||
@@ -15,22 +15,19 @@ import { useSection } from "./useSection";
|
||||
|
||||
const NavItem = ({
|
||||
link,
|
||||
asPath,
|
||||
}: {
|
||||
link: { heading: string; slug: string; kategori: string; tag?: string };
|
||||
asPath: string;
|
||||
}) => {
|
||||
const isActive = asPath === `/${link.slug}`;
|
||||
const { asPath } = useRouter();
|
||||
const isActive = asPath.split("#")[0] === `/${link.slug}`;
|
||||
|
||||
return (
|
||||
<li>
|
||||
<Link
|
||||
aria-current={isActive ? "page" : undefined}
|
||||
href={`/${link.slug}`}
|
||||
onClick={(e) => {
|
||||
logNav(
|
||||
"meny",
|
||||
window.location.pathname,
|
||||
e.currentTarget.getAttribute("href")
|
||||
);
|
||||
amplitudeLogNavigation("meny", e.currentTarget.getAttribute("href"));
|
||||
}}
|
||||
className={cl(
|
||||
"hover:text-deepblue-800 focus-visible:shadow-focus text-medium pr-05 relative flex overflow-hidden py-1 pl-4 leading-snug before:rounded-full hover:before:transition-colors focus:outline-none focus-visible:z-10 focus-visible:rounded-sm",
|
||||
@@ -81,7 +78,6 @@ const Dropdown = ({
|
||||
links: AkselSidebarT;
|
||||
}) => {
|
||||
const [open, setOpen] = useState(true);
|
||||
const { asPath } = useRouter();
|
||||
|
||||
return (
|
||||
<li className="peer w-full peer-data-[open=true]:mt-6" data-open={open}>
|
||||
@@ -107,7 +103,7 @@ const Dropdown = ({
|
||||
|
||||
<ul hidden={!open} className="pl-2">
|
||||
{links.map((z) => (
|
||||
<NavItem link={z} key={z?.heading} asPath={asPath} />
|
||||
<NavItem link={z} key={z?.heading} />
|
||||
))}
|
||||
</ul>
|
||||
</li>
|
||||
@@ -118,7 +114,7 @@ export const Sidebar = ({
|
||||
kategori,
|
||||
links,
|
||||
}: {
|
||||
kategori: "Komponenter" | "Grunnleggende";
|
||||
kategori: "komponenter" | "grunnleggende" | "templates";
|
||||
links: AkselSidebarT;
|
||||
}) => {
|
||||
const sections = useSection({ kategori, links });
|
||||
|
||||
@@ -1,23 +1,17 @@
|
||||
import { AkselSidebarT } from "@/types";
|
||||
import { useMemo } from "react";
|
||||
import {
|
||||
grunnleggendeKategorier,
|
||||
komponentKategorier,
|
||||
} from "../../../sanity/config";
|
||||
import { sanityCategoryLookup } from "../../../sanity/config";
|
||||
|
||||
export const useSection = ({
|
||||
kategori,
|
||||
links,
|
||||
}: {
|
||||
kategori: "Komponenter" | "Grunnleggende";
|
||||
kategori: "komponenter" | "grunnleggende" | "templates";
|
||||
links: AkselSidebarT;
|
||||
}) => {
|
||||
const sections = useMemo(
|
||||
() =>
|
||||
(kategori === "Komponenter"
|
||||
? komponentKategorier
|
||||
: grunnleggendeKategorier
|
||||
)
|
||||
sanityCategoryLookup(kategori)
|
||||
.map((x) => ({
|
||||
...x,
|
||||
pages: links
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
import React from "react";
|
||||
import { withErrorBoundary } from "@/error-boundary";
|
||||
import TokenView from "./token-view";
|
||||
|
||||
type SpesialT = {
|
||||
_key: string;
|
||||
_type: "spesial_seksjon";
|
||||
modul?: "ikonsok" | "token_kategori";
|
||||
logs?: any[];
|
||||
komponenter?: any;
|
||||
farge?: any;
|
||||
token?: { title: string; kategori: string };
|
||||
};
|
||||
|
||||
const SideModul = ({ node }: { node: SpesialT }) => {
|
||||
if (!node || !node.modul) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const GetModule = () => {
|
||||
switch (node.modul) {
|
||||
case "ikonsok":
|
||||
return null;
|
||||
case "token_kategori":
|
||||
return <TokenView token={node.token} />;
|
||||
default: {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return <div className="mb-16">{GetModule()}</div>;
|
||||
};
|
||||
|
||||
export default withErrorBoundary(SideModul, "SideModul");
|
||||
@@ -1,98 +0,0 @@
|
||||
import { withErrorBoundary } from "@/error-boundary";
|
||||
import { TokenTableT } from "@/types";
|
||||
import { ChevronDownIcon } from "@navikt/aksel-icons";
|
||||
import core from "@navikt/ds-css/tokens.json";
|
||||
import { BodyLong, Button, CopyButton, Label, Link } from "@navikt/ds-react";
|
||||
import cl from "clsx";
|
||||
import { AkselTable, AkselTableRow } from "components/website-modules/Table";
|
||||
import NextLink from "next/link";
|
||||
import { useState } from "react";
|
||||
|
||||
const TokenTable = ({ node }: { node: TokenTableT }) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const tokens: { [key: string]: string } | null =
|
||||
node.title in core ? core[node.title] : null;
|
||||
|
||||
if (!tokens) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const showMore = Object.keys(tokens).length > 8;
|
||||
|
||||
return (
|
||||
<div className="mb-7">
|
||||
<div
|
||||
className={cl({
|
||||
"after:to-bg-default relative max-h-80 overflow-y-hidden after:absolute after:inset-0 after:bg-gradient-to-b after:from-transparent after:via-transparent":
|
||||
!open && showMore,
|
||||
})}
|
||||
>
|
||||
<AkselTable th={[{ text: "Token" }, { text: "Fallback" }]}>
|
||||
{Object.entries(tokens).map(([key, val]) => (
|
||||
<AkselTableRow key={key} tr={[{ text: key }, { text: val }]} />
|
||||
))}
|
||||
</AkselTable>
|
||||
{(open || !showMore) && (
|
||||
<div className="border-border-subtle bg-surface-default relative -mt-9 w-full rounded-b-lg border border-t-0 p-2 pr-14">
|
||||
<CopyButton
|
||||
copyText={Object.entries(tokens).reduce(
|
||||
(prev, cur) =>
|
||||
prev +
|
||||
`${cur[0]}: ${
|
||||
cur[1].startsWith("--") ? `var(${cur[1]})` : `${cur[1]}`
|
||||
};\n`,
|
||||
""
|
||||
)}
|
||||
className="absolute right-2 top-2 z-10"
|
||||
/>
|
||||
<Label size="small" as="span" spacing>
|
||||
Hva er dette?
|
||||
</Label>
|
||||
<BodyLong size="small">
|
||||
Komponent-tokens gir deg muligheten til å sette opp theming eller
|
||||
justere styling uten å måtte overskrive css-klasser. Les gjennom{" "}
|
||||
<NextLink
|
||||
href="/grunnleggende/styling/design-tokens#hec62d38bc813"
|
||||
passHref
|
||||
legacyBehavior
|
||||
>
|
||||
<Link>guiden vår</Link>
|
||||
</NextLink>{" "}
|
||||
eller utforsk alle{" "}
|
||||
<NextLink
|
||||
href="/grunnleggende/styling/design-tokens"
|
||||
passHref
|
||||
legacyBehavior
|
||||
>
|
||||
<Link>design tokens</Link>
|
||||
</NextLink>
|
||||
.
|
||||
</BodyLong>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{!open && showMore && (
|
||||
<div className="bg-surface-default mx-auto grid w-fit -translate-y-[80%] place-content-center shadow-[0_0_8px_8px_var(--a-surface-default)]">
|
||||
<Button
|
||||
variant="secondary-neutral"
|
||||
onClick={() => setOpen(true)}
|
||||
className="group"
|
||||
icon={
|
||||
<ChevronDownIcon
|
||||
className="group-hover:translate-y-05 transition-transform"
|
||||
aria-hidden
|
||||
fontSize="1.5rem"
|
||||
/>
|
||||
}
|
||||
iconPosition="right"
|
||||
>
|
||||
Vis mer
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default withErrorBoundary(TokenTable, "Token-tabell");
|
||||
@@ -1,83 +0,0 @@
|
||||
import { FeedbackT } from "@/types";
|
||||
import { IdContext } from "@/utils";
|
||||
import { Button, Heading, Textarea } from "@navikt/ds-react";
|
||||
import cl from "clsx";
|
||||
import { useRouter } from "next/router";
|
||||
import { useContext, useState } from "react";
|
||||
|
||||
export const UuFeedback = ({ node }: { node: { vis: boolean } }) => {
|
||||
const [textValue, setTextValue] = useState("");
|
||||
const [errorMsg, setErrorMsg] = useState<string | null>(null);
|
||||
const [sent, setSent] = useState(false);
|
||||
|
||||
const idCtx = useContext(IdContext);
|
||||
const { asPath, basePath } = useRouter();
|
||||
|
||||
if (!node?.vis) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const fetchFeedback = () => {
|
||||
const body: FeedbackT = {
|
||||
message: textValue,
|
||||
url: `${basePath}${asPath}`,
|
||||
type: "uu",
|
||||
docId: idCtx?.id,
|
||||
};
|
||||
|
||||
fetch("/api/feedback", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
};
|
||||
|
||||
const handleSend = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (textValue === "") {
|
||||
setErrorMsg(
|
||||
"Tilbakemeldingen kan ikke være tom. Legg til tekst i feltet."
|
||||
);
|
||||
return;
|
||||
}
|
||||
setErrorMsg(null);
|
||||
setSent(true);
|
||||
fetchFeedback();
|
||||
setTextValue("");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="my-8">
|
||||
{!sent && (
|
||||
<form
|
||||
className={cl(
|
||||
"animate-fadeIn mt-4 flex w-full max-w-sm flex-col gap-4"
|
||||
)}
|
||||
>
|
||||
<Textarea
|
||||
error={errorMsg}
|
||||
value={textValue}
|
||||
onChange={(e) => setTextValue(e.target.value)}
|
||||
maxLength={600}
|
||||
minRows={3}
|
||||
label="Tilbakemelding"
|
||||
description="Ikke skriv inn navn eller andre personopplysninger"
|
||||
autoComplete="off"
|
||||
/>
|
||||
<Button className="mr-auto" onClick={handleSend}>
|
||||
Send inn tilbakemelding
|
||||
</Button>
|
||||
</form>
|
||||
)}
|
||||
<div aria-live="polite">
|
||||
{sent && (
|
||||
<>
|
||||
<Heading size="small" as="p" className="mt-8">
|
||||
Takk for tilbakemeldingen!
|
||||
</Heading>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
+14
-5
@@ -1,10 +1,13 @@
|
||||
import { Accordion as DsAccordion } from "@navikt/ds-react";
|
||||
import React from "react";
|
||||
import { withErrorBoundary } from "@/error-boundary";
|
||||
import ErrorBoundary from "@/error-boundary";
|
||||
import { SanityBlockContent } from "@/sanity-block";
|
||||
import { AccordionT } from "@/types";
|
||||
import { Accordion as DsAccordion } from "@navikt/ds-react";
|
||||
|
||||
const Accordion = ({ node }: { node: AccordionT }) => {
|
||||
type AccordionProps = {
|
||||
node: AccordionT;
|
||||
};
|
||||
|
||||
const Accordion = ({ node }: AccordionProps) => {
|
||||
if (!node || node.list.length === 0) {
|
||||
return null;
|
||||
}
|
||||
@@ -25,4 +28,10 @@ const Accordion = ({ node }: { node: AccordionT }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default withErrorBoundary(Accordion, "Accordion");
|
||||
export default function Component(props: AccordionProps) {
|
||||
return (
|
||||
<ErrorBoundary boundaryName="Accordion">
|
||||
<Accordion {...props} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
+13
-2
@@ -1,8 +1,13 @@
|
||||
import ErrorBoundary from "@/error-boundary";
|
||||
import { SanityBlockContent } from "@/sanity-block";
|
||||
import { AlertT } from "@/types";
|
||||
import { Alert as DsAlert, Heading } from "@navikt/ds-react";
|
||||
|
||||
const Alert = ({ node }: { node: AlertT }) => {
|
||||
type AlertProps = {
|
||||
node: AlertT;
|
||||
};
|
||||
|
||||
const Alert = ({ node }: AlertProps) => {
|
||||
return (
|
||||
<div className="mb-7 max-w-2xl">
|
||||
<DsAlert variant={node.variant}>
|
||||
@@ -23,4 +28,10 @@ const Alert = ({ node }: { node: AlertT }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default Alert;
|
||||
export default function Component(props: AlertProps) {
|
||||
return (
|
||||
<ErrorBoundary boundaryName="Alert">
|
||||
<Alert {...props} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
+14
-3
@@ -1,12 +1,17 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
import { withErrorBoundary } from "@/error-boundary";
|
||||
import ErrorBoundary from "@/error-boundary";
|
||||
import { urlFor } from "@/sanity/interface";
|
||||
import { BildeT } from "@/types";
|
||||
import { BodyLong, Link } from "@navikt/ds-react";
|
||||
import cl from "clsx";
|
||||
import NextLink from "next/link";
|
||||
|
||||
const Bilde = ({ node, className }: { node: BildeT; className?: string }) => {
|
||||
type BildeProps = {
|
||||
node: BildeT;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const Bilde = ({ node, className }: BildeProps) => {
|
||||
if (!node || !node.asset) {
|
||||
return null;
|
||||
}
|
||||
@@ -65,4 +70,10 @@ const Bilde = ({ node, className }: { node: BildeT; className?: string }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default withErrorBoundary(Bilde, "Bilde");
|
||||
export default function Component(props: BildeProps) {
|
||||
return (
|
||||
<ErrorBoundary boundaryName="Bilde">
|
||||
<Bilde {...props} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
Loaded 100 of 290 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user