Compare commits

...
Author SHA1 Message Date
github-actions[bot]andgithub-actions[bot] 265e105fd0 Version Packages (#2202)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-08-22 15:10:46 +02:00
Ken 8d934e0931 🐛 Checkmark, Confirmationpanel og Tokens patch (#2199) 2023-08-22 15:05:15 +02:00
Ken 6981fbccff 🐛 Fikset kalkulering av ikon-navn på ikonside 2023-08-18 18:27:11 +02:00
github-actions[bot]andgithub-actions[bot] bfee5a822e Version Packages (#2192)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-08-18 13:09:28 +02:00
Halvor Haugan 58ed3b5367 🏷️ Modal: Bedre type for width (#2191) 2023-08-18 12:51:56 +02:00
github-actions[bot]andgithub-actions[bot] 00b3dd7d59 Version Packages (#2190)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-08-18 12:20:30 +02:00
Ken 90db7dd0c1 📝 changeset 2023-08-18 12:16:01 +02:00
Ken b6d0304d5a Merge branch 'main' of https://github.com/navikt/aksel 2023-08-18 12:00:15 +02:00
Ken de0a797e09 ⚰️ Fjernet duplikat OG-bilde 2023-08-18 11:59:54 +02:00
github-actions[bot]andgithub-actions[bot] c4eaf0f350 Version Packages (#2186)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-08-18 11:41:26 +02:00
Ken fb9897e6a0 🐛 Fikset fokusrekkefølge på søk 2023-08-18 09:25:49 +02:00
Halvor HauganandKen 3438b5d7e7 ✨ Modal: støtte for header og footer (#2135)
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
2023-08-17 16:40:52 +02:00
Ken 3583062824 🎨 Oppdatert Action og Gray (#2092) 2023-08-17 16:14:42 +02:00
Ken 2993f809ad La til large-size for Table, oppdatert expand-knapp (#2178) 2023-08-17 16:14:24 +02:00
github-actions[bot]andgithub-actions[bot] fe25e1fdf2 Version Packages (#2185)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-08-17 16:03:48 +02:00
Vegard HaugstvedtandKen 5cda39ce19 Bugfix/missing red border on combobox with error (#2184)
Co-authored-by: Ken <ken.aleksander@gmail.com>
2023-08-17 16:00:25 +02:00
github-actions[bot]andgithub-actions[bot] 6cb25e44de Version Packages (#2172)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-08-17 14:24:27 +02:00
Ken 5812d6ee87 📝 Oppdatert changesets 2023-08-17 14:14:01 +02:00
Ken a402777c70 🐛 Alignet alert-ikon med tekst (#2179) 2023-08-17 14:02:56 +02:00
Vegard Haugstvedt d4064bd461 Call Combobox.onClear when clearin input field programatically (#2183) 2023-08-17 14:00:05 +02:00
KenandHalvor Haugan 1c482e03b8 Oppdatert list: Small-variant, bugfixes (#2168)
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
2023-08-17 13:56:59 +02:00
Vegard HaugstvedtandKen 608e1f3944 Feature/combobox error message (#2182)
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
2023-08-17 13:26:10 +02:00
Ken 107d6f3d93 CopyButton prop for iconPosition (#2173) 2023-08-17 13:21:54 +02:00
Ken 09d41e9b6d 🎨 Checkbox checkmark er nå SVG, ikke base64 (#2171) 2023-08-17 12:49:33 +02:00
Ken ebacbddd44 🐛 Reverserte major-update av stylelint 2023-08-17 12:47:34 +02:00
Ken e58b072c84 🎨 Oppdatert storybook, fikset chromatic-PR kommentar 2023-08-17 12:02:03 +02:00
Lars Eirik Korsgaard HansenandKen e7cb3753a0 🐛 fix: Combobox , SingleSelect: allow custom options (#2180)
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
2023-08-17 11:38:31 +02:00
Lars Eirik Korsgaard Hansen 7486ba11f9 🐛 Combobox, SingleSelect: close dropdown menu upon selecting option (#2177)
* isAddedByUser changed to isCustomOption to match language used elsewhere

* list closes when choosing option on SingleSelect

* changeset
2023-08-16 15:43:59 +02:00
Vegard Haugstvedt cf01a6e162 Bugfix/combobox.on clear is fired more times than expected (#2170) 2023-08-16 10:45:02 +02:00
Ken a67f95433f 🐛 Fikset dodont tekst og ikon for god-praksis 2023-08-16 10:44:41 +02:00
110 changed files with 3833 additions and 1403 deletions

No files matched your search

+2 -2
View File
@@ -75,7 +75,7 @@ jobs:
with:
issue-number: ${{ github.event.pull_request.number }}
comment-author: "github-actions[bot]"
body-includes: github-comment-finder
body-includes: Storybook demo
- name: Create or update comment
if: steps.chromatic_tests.outcome == 'success' && !contains(github.event.head_commit.message, '[ci skip]') && github.event_name != 'push'
@@ -84,7 +84,7 @@ jobs:
comment-id: ${{ steps.fc.outputs.comment-id }}
issue-number: ${{ github.event.pull_request.number }}
body: |
### [<img id="github-comment-finder" src="https://raw.githubusercontent.com/storybookjs/brand/main/logo/logo-storybook-inverse.svg" height="32"/>](${{steps.storybook_url.outputs.replaced}})
## [Storybook demo](${{steps.storybook_url.outputs.replaced}})
[${{ steps.vars.outputs.sha_short }}](https://github.com/navikt/aksel/commit/${{ steps.vars.outputs.sha_short }}) | ${{steps.chromatic_tests.outputs.componentCount}} komponenter | ${{steps.chromatic_tests.outputs.specCount}} stories
edit-mode: replace
+12
View File
@@ -1,5 +1,17 @@
# @navikt/aksel-icons
## 5.0.3
## 5.0.2
## 5.0.1
## 5.0.0
## 4.12.1
## 4.12.0
## 4.11.2
## 4.11.1
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/aksel-icons",
"version": "4.11.2",
"version": "5.0.3",
"author": "Aksel | NAV",
"homepage": "https://aksel.nav.no/ikoner",
"repository": {
+12
View File
@@ -1,5 +1,17 @@
# @navikt/aksel-stylelint
## 5.0.3
## 5.0.2
## 5.0.1
## 5.0.0
## 4.12.1
## 4.12.0
## 4.11.2
## 4.11.1
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/aksel-stylelint",
"version": "4.11.2",
"version": "5.0.3",
"author": "Aksel | NAV",
"homepage": "https://aksel.nav.no/grunnleggende/kode/stylelint",
"repository": {
@@ -35,8 +35,8 @@
"dev": "yarn watch:lint"
},
"devDependencies": {
"@navikt/ds-css": "^4.11.2",
"@navikt/ds-tokens": "^4.11.2",
"@navikt/ds-css": "^5.0.3",
"@navikt/ds-tokens": "^5.0.3",
"@types/jest": "^29.0.0",
"concurrently": "7.2.1",
"copyfiles": "2.4.1",
@@ -25,4 +25,17 @@ export const deprecations: DeprecatedList = [
message:
"In v4.1.0 Chips. Toggle no longer handles special alignment for checkmark-icon, thus removing this class",
},
{
classes: ["navds-modal__overlay"],
message:
"Removed in v5.0.0, use `.navds-modal::backdrop` and `.navds-modal--polyfilled + .backdrop` instead",
},
{
classes: ["navds-modal__button--shake"],
message: "Removed in v5.0.0",
},
{
classes: ["navds-modal__content"],
message: "Renamed to `.navds-modal__body` in v5.0.0",
},
];
+42
View File
@@ -1,5 +1,47 @@
# @navikt/aksel
## 5.0.3
### Patch Changes
- Updated dependencies [[`8d934e093`](https://github.com/navikt/aksel/commit/8d934e0931caaa6f774a8f1b20fa34a19ece7218), [`8d934e093`](https://github.com/navikt/aksel/commit/8d934e0931caaa6f774a8f1b20fa34a19ece7218)]:
- @navikt/ds-css@5.0.3
## 5.0.2
### Patch Changes
- Updated dependencies []:
- @navikt/ds-css@5.0.2
## 5.0.1
### Patch Changes
- Updated dependencies [[`90db7dd0c`](https://github.com/navikt/aksel/commit/90db7dd0c120c16a387d3169c05c1f33dc694323)]:
- @navikt/ds-css@5.0.1
## 5.0.0
### Patch Changes
- Updated dependencies [[`2993f809a`](https://github.com/navikt/aksel/commit/2993f809adbf26fe88237066fea7f3e1a21b6690), [`2993f809a`](https://github.com/navikt/aksel/commit/2993f809adbf26fe88237066fea7f3e1a21b6690), [`3438b5d7e`](https://github.com/navikt/aksel/commit/3438b5d7e75037889946d4f89e95cd1f91bbb2d0)]:
- @navikt/ds-css@5.0.0
## 4.12.1
### Patch Changes
- Updated dependencies [[`5cda39ce1`](https://github.com/navikt/aksel/commit/5cda39ce19fa3f25ae18a0b07dd85e97378edd26)]:
- @navikt/ds-css@4.12.1
## 4.12.0
### Patch Changes
- Updated dependencies [[`1c482e03b`](https://github.com/navikt/aksel/commit/1c482e03b845c4e6f6f1e174a58992fbeeabb52e), [`107d6f3d9`](https://github.com/navikt/aksel/commit/107d6f3d935cbc9bb94bc15cea63431cd15bbd61), [`09d41e9b6`](https://github.com/navikt/aksel/commit/09d41e9b6da5486a55faaf8088191b4a8374e420), [`a402777c7`](https://github.com/navikt/aksel/commit/a402777c70a18899ef4239b10a6335a671e26167)]:
- @navikt/ds-css@4.12.0
## 4.11.2
### Patch Changes
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/aksel",
"version": "4.11.2",
"version": "5.0.3",
"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": "4.11.2",
"@navikt/ds-css": "5.0.3",
"axios": "1.3.6",
"chalk": "4.1.0",
"clipboardy": "^2.3.0",
+51
View File
@@ -1,5 +1,56 @@
# @navikt/ds-css
## 5.0.3
### Patch Changes
- 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))
## 5.0.2
## 5.0.1
### Patch Changes
- Checkbox: Hotfix da checkmark ikke var sentrert hvis man brukte tailwind ([`90db7dd0c`](https://github.com/navikt/aksel/commit/90db7dd0c120c16a387d3169c05c1f33dc694323))
## 5.0.0
### Major Changes
- 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)
### Patch Changes
- 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))
## 4.12.1
### Patch Changes
- Added red border to Combobox in error state ([#2184](https://github.com/navikt/aksel/pull/2184))
## 4.12.0
### Minor Changes
- CopyButton: Har nå prop 'iconPosition' for å høyre/venstre aligne ikon ([#2173](https://github.com/navikt/aksel/pull/2173))
### Patch Changes
- 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
### Patch Changes
+8 -1
View File
@@ -24,10 +24,17 @@
font-size: 1.5rem;
align-self: flex-start;
height: var(--a-font-line-height-xlarge);
margin: var(--a-spacing-1);
margin-inline: var(--a-spacing-1);
margin-block: 0;
margin-block-start: -1px;
background: radial-gradient(circle, var(--a-surface-default) 50%, 0, transparent);
}
.navds-alert--close-button > .navds-alert__icon,
.navds-alert--small > .navds-alert__icon {
margin-block-start: 0;
}
.navds-alert--small > .navds-alert__icon {
height: var(--a-font-line-height-large);
}
+1 -1
View File
@@ -121,7 +121,7 @@ const StyleMappings = {
{
component: "Modal",
main: "modal.css",
dependencies: ["button.css"],
dependencies: [typoCss, "button.css"],
},
{
component: "Pagination",
+7
View File
@@ -52,9 +52,16 @@
.navds-copybutton__icon {
font-size: 1.5rem;
display: flex;
}
.navds-copybutton__icon:first-of-type {
margin-left: -0.25rem;
}
.navds-copybutton__icon:last-of-type {
margin-right: -0.25rem;
}
.navds-copybutton__icon:only-child {
margin: 0;
}
+15
View File
@@ -32,6 +32,16 @@
cursor: text;
}
.navds-combobox--error .navds-text-field__input:not(:hover):not(:disabled) {
border-color: var(--ac-combobox-error-border, var(--a-border-danger));
box-shadow: 0 0 0 1px var(--ac-combobox-error-border, var(--a-border-danger));
}
.navds-combobox--error
.navds-text-field__input:not(:hover):not(:disabled):not(.navds-combobox__wrapper-inner--virtually-unfocused):focus-within {
box-shadow: 0 0 0 1px var(--ac-combobox-error-border, var(--a-border-danger)), var(--a-shadow-focus);
}
.navds-combobox__selected-options {
gap: var(--a-spacing-2);
align-items: center;
@@ -253,6 +263,11 @@
padding-left: calc(var(--a-spacing-3) - 4px);
}
.navds-form-field--small .navds-combobox__list-item--focus,
.navds-form-field--small .navds-combobox__list-item:hover {
padding-left: calc(var(--a-spacing-2) - 4px);
}
.navds-combobox__list-item--selected {
background-color: var(--ac-combobox-list-item-selected-bg, var(--a-surface-selected));
}
+26 -5
View File
@@ -114,14 +114,32 @@
}
.navds-checkbox__input:checked + .navds-checkbox__label::before {
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMyAxMCI+ICAgIDxnPiAgICA8cGF0aCBmaWxsPSIjRkZGRkZGIiBkPSJNNCwxMGMtMC40LDAtMC44LTAuMS0xLjEtMC40TDAuNCw3LjFDMC4xLDYuOCwwLDYuNCwwLDZzMC4yLTAuOCwwLjUtMS4xQzEsNC40LDIsNC40LDIuNSw0LjlMNCw2LjRsNi40LTYgICAgQzEwLjgsMC4xLDExLjEsMCwxMS41LDBjMC40LDAsMC44LDAuMiwxLDAuNWMwLjYsMC42LDAuNSwxLjYtMC4xLDIuMXYwTDUsOS42QzQuNyw5LjksNC40LDEwLDQsMTB6IE0xMS44LDEuOUwxMS44LDEuOSAgICBDMTEuOCwxLjksMTEuOCwxLjksMTEuOCwxLjl6IE0xMS4yLDEuMUMxMS4yLDEuMSwxMS4yLDEuMSwxMS4yLDEuMUwxMS4yLDEuMXoiLz4gICAgPC9nPjwvc3ZnPg==);
background-position: 0.375rem center;
background-repeat: no-repeat;
background-size: 0.8125rem;
box-shadow: none;
background-color: var(--ac-radio-checkbox-action, var(--a-surface-action));
}
.navds-checkbox__input:where(:not(:checked)) + .navds-checkbox__label > .navds-checkbox__icon {
display: none;
}
.navds-checkbox__input:checked + .navds-checkbox__label > .navds-checkbox__icon {
color: var(--ac-radio-checkbox-bg, var(--a-surface-default));
position: absolute;
height: 1.5rem;
transform: translate(0.375rem);
pointer-events: none;
}
.navds-checkbox--small .navds-checkbox__input:checked + .navds-checkbox__label > .navds-checkbox__icon {
transform: translate(0.25rem, -10%);
}
/* Tailwind sets all svg to block */
.navds-checkbox__icon > svg {
display: inline;
vertical-align: initial;
}
.navds-checkbox--small > .navds-checkbox__input:checked + .navds-checkbox__label::before {
background-position: 0.25rem center;
}
@@ -283,11 +301,14 @@
}
.navds-checkbox--readonly > .navds-checkbox__input:checked + .navds-checkbox__label::before {
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMyAxMCI+ICAgIDxnPiAgICA8cGF0aCBmaWxsPSJyZ2JhKDAsIDAsIDAsIDAuNjUpIiBkPSJNNCwxMGMtMC40LDAtMC44LTAuMS0xLjEtMC40TDAuNCw3LjFDMC4xLDYuOCwwLDYuNCwwLDZzMC4yLTAuOCwwLjUtMS4xQzEsNC40LDIsNC40LDIuNSw0LjlMNCw2LjRsNi40LTYgICAgQzEwLjgsMC4xLDExLjEsMCwxMS41LDBjMC40LDAsMC44LDAuMiwxLDAuNWMwLjYsMC42LDAuNSwxLjYtMC4xLDIuMXYwTDUsOS42QzQuNyw5LjksNC40LDEwLDQsMTB6IE0xMS44LDEuOUwxMS44LDEuOSAgICBDMTEuOCwxLjksMTEuOCwxLjksMTEuOCwxLjl6IE0xMS4yLDEuMUMxMS4yLDEuMSwxMS4yLDEuMSwxMS4yLDEuMUwxMS4yLDEuMXoiLz4gICAgPC9nPjwvc3ZnPg==);
box-shadow: inset 0 0 0 2px var(--__ac-radio-checkbox-readonly-border);
background-color: var(--__ac-radio-checkbox-readonly-bg);
}
.navds-checkbox--readonly > .navds-checkbox__input:checked + .navds-checkbox__label > .navds-checkbox__icon {
color: var(--a-icon-subtle);
}
.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);
+33 -9
View File
@@ -1,14 +1,24 @@
.navds-list ul,
.navds-list ol {
padding: 0;
margin-block: var(--a-spacing-4);
}
.navds-list--nested {
margin: var(--a-spacing-4) 0 var(--a-spacing-8) 0;
.navds-list--nested ul,
.navds-list--nested ol {
margin-block: var(--a-spacing-2);
}
.navds-list li:not(.navds-list__item--noMargin) {
margin-bottom: var(--a-spacing-5);
.navds-list li {
margin-block-end: var(--a-spacing-5);
}
.navds-list--small li {
margin-block-end: var(--a-spacing-2);
}
.navds-list--nested li {
margin-block-end: var(--a-spacing-3);
}
.navds-list:last-child > ul > li:last-child,
@@ -34,17 +44,25 @@
.navds-list .navds-list__item-marker--bullet {
display: flex;
margin-top: 0.375rem;
align-items: center;
height: var(--a-font-line-height-large);
grid-column: 1 / 2;
/* stylelint-disable-next-line csstools/value-no-unknown-custom-properties */
color: var(--ac-list-marker-ul-color, var(--ac-list-marker-color, var(--a-icon-default)));
}
.navds-list--small .navds-list__item-marker--bullet {
height: var(--a-font-line-height-medium);
}
.navds-list__item-marker--icon {
/* stylelint-disable-next-line csstools/value-no-unknown-custom-properties */
color: var(--ac-list-marker-icon-color, var(--ac-list-marker-color, var(--a-icon-default)));
display: flex;
align-items: center;
height: var(--a-font-line-height-large);
padding-block-end: 1px;
}
.navds-list--small .navds-list__item-marker--icon {
height: var(--a-font-line-height-medium);
}
@@ -58,6 +76,7 @@
.navds-list ol {
padding-left: var(--a-spacing-2);
list-style: decimal;
}
.navds-list ol li {
@@ -67,7 +86,12 @@
.navds-list ol li::marker {
font-weight: var(--a-font-weight-bold);
font-size: var(--a-font-size-medium);
/* stylelint-disable-next-line csstools/value-no-unknown-custom-properties */
font-size: var(--a-font-size-large);
color: var(--ac-list-marker-ol-color, var(--ac-list-marker-color, var(--a-icon-default)));
line-height: var(--a-font-line-height-large);
}
.navds-list--small ol li::marker {
font-size: var(--a-font-size-small);
line-height: var(--a-font-line-height-medium);
}
+127 -44
View File
@@ -1,69 +1,152 @@
.ReactModal__Body--open {
.navds-modal__document-body {
overflow: hidden;
}
.navds-modal {
background-color: var(--ac-modal-bg, var(--a-surface-default));
border-radius: var(--a-border-radius-medium);
display: block;
position: relative;
overflow: auto;
max-height: 100%;
border: 1px solid var(--ac-modal-border, var(--a-border-subtle));
border-radius: var(--a-border-radius-large);
box-shadow: var(--a-shadow-xlarge);
padding: 0;
position: fixed;
max-height: 100%;
max-width: 100%;
}
.navds-modal__content {
padding: var(--a-spacing-4);
.navds-modal[open] {
display: flex;
flex-direction: column;
animation: akselModalFadeIn 0.35s cubic-bezier(0.15, 1, 0.3, 1);
}
.navds-modal:focus-visible,
.navds-modal--focus {
box-shadow: var(--a-shadow-focus);
outline: none;
.navds-modal--polyfilled {
top: 50%;
transform: translate(0, -50%);
overflow: auto;
/* From polyfill (dialog-polyfill/dist/dialog-polyfill.css): */
left: 0;
right: 0;
width: fit-content;
height: fit-content;
margin: auto;
}
@supports not selector(:focus-visible) {
.navds-modal:focus,
.navds-modal--focus {
box-shadow: var(--a-shadow-focus);
outline: none;
.navds-modal--polyfilled:not([open]) {
display: none; /* from polyfill */
}
._dialog_overlay {
/* From polyfill */
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
.navds-modal--small {
width: var(--ac-modal-width-small, 450px);
}
.navds-modal--medium {
width: var(--ac-modal-width-medium, 700px);
}
@media (min-width: 480px) {
.navds-modal {
max-width: calc((100% - 6px) - 2em);
max-height: calc((100% - 6px) - 2em);
}
.navds-modal--autowidth {
max-width: min(700px, calc((100% - 6px) - 2em));
}
}
.navds-modal__overlay {
.navds-modal::backdrop {
/*
* Cannot use --a-surface-backdrop because ::backdrop does not inherit from anything but itself.
* But consumers can do `::backdrop { --ac-modal-backdrop: white; }`
* We can add ::backdrop to tokens.css when we remove the polyfill.
*/
background: var(--ac-modal-backdrop, rgb(2 20 49 / 0.49));
animation: akselModalBackdropFadeIn 0.35s cubic-bezier(0.15, 1, 0.3, 1);
}
.navds-modal--polyfilled + .backdrop /* Cannot be combined with ::backdrop selector */ {
background: var(--ac-modal-backdrop, rgb(2 20 49 / 0.49));
/* From polyfill: */
position: fixed;
z-index: var(--a-z-index-modal);
top: 0;
right: 0;
bottom: 0;
left: 0;
right: 0;
background-color: var(--ac-modal-backdrop, var(--a-surface-backdrop));
padding: var(--a-spacing-4);
display: flex;
align-items: center;
justify-content: center;
}
.navds-modal__button--shake {
transform: rotate(0deg);
transition: transform 100ms cubic-bezier(0.82, 2, 1, 0.3);
}
/* prettier-ignore */
.navds-modal__overlay:active > .navds-modal:not(:active) > .navds-modal__button--shake {
transform: rotate(10deg);
transition: transform 100ms cubic-bezier(0.82, -2, 1, 0.3);
}
.navds-modal__button {
position: absolute;
top: var(--a-spacing-4);
right: var(--a-spacing-4);
display: flex;
padding: var(--a-spacing-2);
margin-left: var(--a-spacing-4);
float: right;
}
.navds-modal__button svg {
height: 1.5rem;
width: 1.5rem;
.navds-modal__header {
padding: var(--a-spacing-6) var(--a-spacing-6) var(--a-spacing-4) var(--a-spacing-6);
}
.navds-modal__header-icon svg {
display: inline;
vertical-align: -0.25rem;
margin-right: var(--a-spacing-1);
}
.navds-modal__label {
font-weight: 600;
color: var(--a-text-subtle);
}
.navds-modal__body {
padding: var(--a-spacing-6);
overflow: auto;
overscroll-behavior: contain;
}
.navds-modal__header + .navds-modal__body {
padding-top: var(--a-spacing-0);
}
.navds-modal__footer {
display: flex;
flex-flow: row-reverse wrap;
gap: var(--a-spacing-4);
padding: var(--a-spacing-4) var(--a-spacing-6) var(--a-spacing-6) var(--a-spacing-6);
}
.navds-modal__footer :nth-of-type(2) {
margin-left: auto;
}
@keyframes akselModalFadeIn {
from {
opacity: 0;
transform: translate3d(0, calc(5% + 4px), 0);
}
50% {
opacity: 1;
}
to {
transform: none;
}
}
@keyframes akselModalBackdropFadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-css",
"version": "4.11.2",
"version": "5.0.3",
"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": "^4.11.2",
"@navikt/ds-tokens": "^5.0.3",
"cssnano": "6.0.0",
"fast-glob": "3.2.11",
"lodash": "4.17.21",
+58 -35
View File
@@ -45,7 +45,7 @@
.navds-table__header-cell,
.navds-table__data-cell {
display: table-cell;
padding: var(--a-spacing-4) var(--a-spacing-3);
padding: var(--a-spacing-3) var(--a-spacing-2);
border-bottom: 1px solid var(--ac-table-row-border, var(--a-border-default));
text-align: left;
}
@@ -74,9 +74,14 @@
border-bottom-color: transparent;
}
.navds-table--large .navds-table__header-cell,
.navds-table--large .navds-table__data-cell {
padding: var(--a-spacing-4) var(--a-spacing-2);
}
.navds-table--small .navds-table__header-cell,
.navds-table--small .navds-table__data-cell {
padding: var(--a-spacing-2) var(--a-spacing-3);
padding: var(--a-spacing-1) var(--a-spacing-2);
}
.navds-table .navds-checkbox .navds-checkbox__input {
@@ -154,7 +159,7 @@
transition: border-bottom-color 190ms cubic-bezier(0.6, 0.04, 0.98, 0.335);
}
.navds-table__expandable-row:not(.navds-table__expandable-row--expansion-disabled):hover {
.navds-table__expandable-row.navds-table__expandable-row--clickable:not(.navds-table__expandable-row--expansion-disabled):hover {
cursor: pointer;
}
@@ -171,55 +176,73 @@
}
.navds-table__toggle-expand-cell {
padding: 0;
width: 3.5rem;
padding: 0 var(--a-spacing-2);
position: relative;
width: 2.5rem;
}
.navds-table--large .navds-table__toggle-expand-cell {
padding: 0 var(--a-spacing-2);
}
.navds-table--small .navds-table__toggle-expand-cell {
padding: 0;
width: 2.25rem;
padding: var(--a-spacing-1) var(--a-spacing-2);
}
.navds-table__toggle-expand-button {
all: unset;
display: flex;
align-items: center;
display: grid;
place-content: center;
cursor: pointer;
padding: var(--a-spacing-4);
margin: 0;
background: transparent;
border: none;
border-radius: var(--a-border-radius-medium);
height: 2rem;
width: 2rem;
font-size: 1.5rem;
}
.navds-table--small .navds-table__toggle-expand-button {
padding: var(--a-spacing-2);
.navds-table__expandable-icon {
transition: transform 150ms ease-in-out;
}
.navds-table__toggle-expand-button:hover,
.navds-table__expandable-row--clickable:hover .navds-table__toggle-expand-button {
background-color: var(--a-surface-hover);
}
.navds-table__toggle-expand-cell--open > :where(.navds-table__toggle-expand-button) :where(.navds-table__expandable-icon) {
transform: translateY(0) rotate(180deg);
}
.navds-table__toggle-expand-button:hover > :where(.navds-table__expandable-icon) {
transform: translateY(1px);
}
.navds-table__toggle-expand-cell--open > .navds-table__toggle-expand-button:hover > :where(.navds-table__expandable-icon) {
transform: translateY(-1px) rotate(180deg);
}
.navds-table__toggle-expand-button:focus-visible {
box-shadow: inset var(--a-shadow-focus);
outline: none;
box-shadow: var(--a-shadow-focus);
}
@supports not selector(:focus-visible) {
.navds-table__toggle-expand-button:focus {
box-shadow: inset var(--a-shadow-focus);
outline: none;
box-shadow: var(--a-shadow-focus);
}
}
.navds-table__expandable-icon {
font-size: 1.5rem;
}
.navds-table--small .navds-table__expandable-icon {
font-size: 1.25rem;
}
.navds-table__toggle-expand-button:hover .navds-table__expandable-icon {
transform: translateY(1px);
}
.navds-table__toggle-expand-cell--open .navds-table__expandable-icon {
transform: rotate(180deg);
}
.navds-table__toggle-expand-cell--open .navds-table__toggle-expand-button:hover .navds-table__expandable-icon {
transform: translateY(-1px) rotate(180deg);
.navds-table__toggle-expand-button::after {
inset: 0;
z-index: 1;
position: absolute;
content: "";
height: 100%;
width: 100%;
cursor: pointer;
}
.navds-table__expanded-row-cell {
@@ -235,9 +258,9 @@
}
.navds-table__expanded-row-content {
padding: var(--a-spacing-4) 4.25rem;
padding: var(--a-spacing-4) calc(var(--a-spacing-2) + 3rem);
}
.navds-table--small .navds-table__expanded-row-content {
padding: var(--a-spacing-2) var(--a-spacing-12);
padding: var(--a-spacing-2) calc(var(--a-spacing-2) + 3rem);
}
+6 -2
View File
@@ -196,7 +196,10 @@
},
"modal": {
"--ac-modal-bg": "--a-surface-default",
"--ac-modal-backdrop": "--a-surface-backdrop"
"--ac-modal-border": "--a-border-subtle",
"--ac-modal-backdrop": "--a-surface-backdrop",
"--ac-modal-width-small": "450px",
"--ac-modal-width-medium": "700px"
},
"pagination": {
"--ac-pagination-text": "--a-text-default",
@@ -388,7 +391,8 @@
"--ac-combobox-list-item-selected-hover-bg": "--a-surface-action-subtle-hover",
"--ac-combobox-list-item-loading-bg": "--a-surface-default",
"--ac-combobox-list-item-hover-border-left": "--a-border-strong",
"--ac-combobox-list-item-selected-hover-border-left": "--a-border-focus"
"--ac-combobox-list-item-selected-hover-border-left": "--a-border-focus",
"--ac-combobox-error-border": "--a-border-danger"
},
"select": {
"--ac-select-bg": "--a-surface-default",
+78
View File
@@ -1,5 +1,83 @@
# @navikt/ds-react
## 5.0.3
### Patch Changes
- ConfirmationPanel: Fikset opplesing av feilmelding ([#2199](https://github.com/navikt/aksel/pull/2199))
- Updated dependencies []:
- @navikt/aksel-icons@5.0.3
## 5.0.2
### Patch Changes
- :label: Modal: Bedre type for `width` ([#2191](https://github.com/navikt/aksel/pull/2191))
- Updated dependencies []:
- @navikt/aksel-icons@5.0.2
## 5.0.1
### Patch Changes
- Updated dependencies []:
- @navikt/aksel-icons@5.0.1
## 5.0.0
### Major Changes
- 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)
- :boom: Provider: `appElement` er fjernet
### Patch Changes
- Table: ExpandableRow har oppdatert knapp for å matche Accordion og ExpansionCard ([#2178](https://github.com/navikt/aksel/pull/2178))
- Table: Har lagt til ny size: 'large'. ([#2178](https://github.com/navikt/aksel/pull/2178))
- Updated dependencies []:
- @navikt/aksel-icons@5.0.0
## 4.12.1
### Patch Changes
- Added red border to Combobox in error state ([#2184](https://github.com/navikt/aksel/pull/2184))
- Updated dependencies []:
- @navikt/aksel-icons@4.12.1
## 4.12.0
### Minor Changes
- Combobox: La til støtte for feilmeldinger i Combobox ([#2182](https://github.com/navikt/aksel/pull/2182))
- CopyButton: Har nå prop 'iconPosition' for å høyre/venstre aligne ikon ([#2173](https://github.com/navikt/aksel/pull/2173))
### Patch Changes
- List: Fikset sentrering, margins ([#2168](https://github.com/navikt/aksel/pull/2168))
- Combobox: Kjører nå 'onChange' + 'onClear' når input blir reset programmatisk ([#2183](https://github.com/navikt/aksel/pull/2183))
- Combobox: Fikset custom-options i singleselect ([#2180](https://github.com/navikt/aksel/pull/2180))
- Combobox: Fjernet unødvendige 'onClear'-calls når man velger verdier ([#2170](https://github.com/navikt/aksel/pull/2170))
- Checkbox: Checkmark er nå SVG-ikon og ikke Base64 ([#2171](https://github.com/navikt/aksel/pull/2171))
- Combobox: Lukker nå nedtrekksmeny hvis man legger til ny option i singleselect ([#2177](https://github.com/navikt/aksel/pull/2177))
- Updated dependencies []:
- @navikt/aksel-icons@4.12.0
## 4.11.2
### Patch Changes
+3 -5
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-react",
"version": "4.11.2",
"version": "5.0.3",
"description": "Aksel react-components for NAV designsystem",
"author": "Aksel | NAV designsystem team",
"license": "MIT",
@@ -38,13 +38,12 @@
},
"dependencies": {
"@floating-ui/react": "0.24.1",
"@navikt/aksel-icons": "^4.11.2",
"@navikt/aksel-icons": "^5.0.3",
"@radix-ui/react-tabs": "1.0.0",
"@radix-ui/react-toggle-group": "1.0.0",
"clsx": "^1.2.1",
"date-fns": "2.29.3",
"react-day-picker": "8.3.4",
"react-modal": "3.15.1"
"react-day-picker": "8.3.4"
},
"devDependencies": {
"@testing-library/dom": "8.13.0",
@@ -53,7 +52,6 @@
"@testing-library/user-event": "^14.2.0",
"@types/faker": "5.5.8",
"@types/jest": "^29.0.0",
"@types/react-modal": "^3.13.1",
"concurrently": "7.2.1",
"copyfiles": "^2.4.1",
"faker": "5.5.3",
+1
View File
@@ -102,6 +102,7 @@ export const Alert = forwardRef<HTMLDivElement, AlertProps>(
{
"navds-alert--full-width": fullWidth,
"navds-alert--inline": inline,
"navds-alert--close-button": closeButton,
}
)}
>
+11 -1
View File
@@ -185,6 +185,16 @@ export const WithCloseButton: Story = {
</BodyLong>
<Link href="#">Id elit esse enim reprehenderit</Link>
</AlertWithCloseButton>
<AlertWithCloseButton>
<DsHeading spacing size="small" level="3">
Aliquip duis est in commodo pariatur
</DsHeading>
<BodyLong>
Ullamco ullamco laborum et commodo sint culpa cupidatat culpa qui
laboris ex. Labore ex occaecat proident qui qui fugiat magna. Fugiat
sint commodo consequat eu aute.
</BodyLong>
</AlertWithCloseButton>
</div>
);
},
@@ -197,6 +207,6 @@ export const WithCloseButton: Story = {
});
const buttonsAfter = canvas.getAllByTitle("Lukk Alert");
expect(buttonsAfter.length).toBe(1);
expect(buttonsAfter.length).toBe(2);
},
};
@@ -63,6 +63,11 @@ export interface CopyButtonProps
* @default 'Kopiert'
*/
activeTitle?: string;
/**
* Icon position in Button
* @default "left"
*/
iconPosition?: "left" | "right";
}
/**
@@ -91,6 +96,7 @@ export const CopyButton = forwardRef<HTMLButtonElement, CopyButtonProps>(
activeDuration = 2000,
title = "Kopier",
activeTitle = "Kopiert",
iconPosition = "left",
...rest
},
ref
@@ -119,6 +125,25 @@ export const CopyButton = forwardRef<HTMLButtonElement, CopyButtonProps>(
}, activeDuration);
};
const CopyIcon = () => {
return active ? (
<span className="navds-copybutton__icon">
{activeIcon ?? (
<CheckmarkIcon
aria-hidden={!!text}
title={text ? undefined : activeTitle}
/>
)}
</span>
) : (
<span className="navds-copybutton__icon">
{icon ?? (
<FilesIcon aria-hidden={!!text} title={text ? undefined : title} />
)}
</span>
);
};
return (
<button
ref={ref}
@@ -138,25 +163,7 @@ export const CopyButton = forwardRef<HTMLButtonElement, CopyButtonProps>(
onClick={handleClick}
>
<span className="navds-copybutton__content">
{active ? (
<span className="navds-copybutton__icon">
{activeIcon ?? (
<CheckmarkIcon
aria-hidden={!!text}
title={text ? undefined : activeTitle}
/>
)}
</span>
) : (
<span className="navds-copybutton__icon">
{icon ?? (
<FilesIcon
aria-hidden={!!text}
title={text ? undefined : title}
/>
)}
</span>
)}
{iconPosition === "left" && <CopyIcon />}
{text &&
(active ? (
@@ -176,6 +183,7 @@ export const CopyButton = forwardRef<HTMLButtonElement, CopyButtonProps>(
{text}
</Label>
))}
{iconPosition === "right" && <CopyIcon />}
</span>
</button>
);
@@ -60,6 +60,16 @@ export const Variants = {
),
};
export const IconPosition = {
render: () => (
<div className="colgap">
<CopyButton copyText="3.14" iconPosition="left" text="Kopier" />
<CopyButton copyText="3.14" iconPosition="right" text="Kopier" />
</div>
),
};
export const Sizes = {
render: () => (
<div className="colgap">
@@ -109,9 +109,9 @@ export interface DatePickerDefaultProps
onOpenToggle?: () => void;
/**
* Avoid using if possible!
* Changes what CSS position property to use
* Changes what CSS position property to use.
* You want to use "fixed" if parent wrapper has position relative, but you want popover to escape
* @default "absolute"
* @default See Popover
*/
strategy?: "absolute" | "fixed";
/**
@@ -184,7 +184,7 @@ export const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>(
open: _open,
onClose,
onOpenToggle,
strategy = "absolute",
strategy,
bubbleEscape = false,
...rest
},
@@ -1,9 +1,9 @@
import { useCallback, useEffect } from "react";
import { useCallback, useEffect, RefObject } from "react";
export const useEscape = (
open: boolean,
setOpen: (openState: boolean) => void,
focusRef: any
focusRef: RefObject<HTMLElement>
) => {
const handleClose = useCallback(() => {
setOpen(false);
@@ -11,7 +11,12 @@ export const useEscape = (
}, [focusRef, setOpen]);
const escape = useCallback(
(e) => open && e.key === "Escape" && handleClose(),
(event: KeyboardEvent) => {
if (open && event.key === "Escape") {
event.preventDefault(); // This prevents modal from closing when using datepicker inside modal
handleClose();
}
},
[handleClose, open]
);
@@ -88,12 +88,7 @@ export const ConfirmationPanel = forwardRef<
{label}
</Checkbox>
</div>
<div
className="navds-form-field__error"
id={errorId}
aria-relevant="additions removals"
aria-live="polite"
>
<div className="navds-form-field__error" id={errorId} role="alert">
{showErrorMsg && <ErrorMessage size={size}>{props.error}</ErrorMessage>}
</div>
</div>
@@ -86,6 +86,23 @@ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
}}
/>
<label htmlFor={inputProps.id} className="navds-checkbox__label">
<span className="navds-checkbox__icon">
<svg
xmlns="http://www.w3.org/2000/svg"
width="13"
height="10"
viewBox="0 0 13 10"
fill="none"
focusable={false}
role="img"
aria-hidden
>
<path
d="M4.03524 6.41478L10.4752 0.404669C11.0792 -0.160351 12.029 -0.130672 12.5955 0.47478C13.162 1.08027 13.1296 2.03007 12.5245 2.59621L5.02111 9.59934C4.74099 9.85904 4.37559 10 4.00025 10C3.60651 10 3.22717 9.84621 2.93914 9.56111L0.439143 7.06111C-0.146381 6.47558 -0.146381 5.52542 0.439143 4.93989C1.02467 4.35437 1.97483 4.35437 2.56036 4.93989L4.03524 6.41478Z"
fill="currentColor"
/>
</svg>
</span>
<span
className={cl("navds-checkbox__content", {
"navds-sr-only": props.hideLabel,
@@ -1,6 +1,6 @@
import cl from "clsx";
import React, { forwardRef, useMemo, useRef } from "react";
import { BodyShort, Label, mergeRefs } from "../..";
import { BodyShort, ErrorMessage, Label, mergeRefs } from "../..";
import ClearButton from "./ClearButton";
import FilteredOptions from "./FilteredOptions/FilteredOptions";
import { useFilteredOptionsContext } from "./FilteredOptions/filteredOptionsContext";
@@ -39,12 +39,15 @@ export const Combobox = forwardRef<
const {
clearInput,
error,
errorId,
focusInput,
hasError,
inputDescriptionId,
inputProps,
inputRef,
value,
showErrorMsg,
size = "medium",
} = useInputContext();
@@ -129,6 +132,14 @@ export const Combobox = forwardRef<
</div>
<FilteredOptions />
</div>
<div
className="navds-form-field__error"
id={errorId}
aria-relevant="additions removals"
aria-live="polite"
>
{showErrorMsg && <ErrorMessage size={size}>{error}</ErrorMessage>}
</div>
</ComboboxWrapper>
);
});
@@ -49,10 +49,9 @@ const ComboboxWrapper = ({
className,
"navds-form-field",
`navds-form-field--${inputSize}`,
"navds-search",
{
"navds-search--error": hasError,
"navds-search--disabled": !!inputProps.disabled,
"navds-combobox--error": hasError,
"navds-combobox--disabled": !!inputProps.disabled,
"navds-combobox--focused": hasFocusWithin,
}
)}
@@ -8,7 +8,6 @@ import { useInputContext } from "../Input/inputContext";
const FilteredOptions = () => {
const {
clearInput,
inputProps: { id },
size,
value,
@@ -51,7 +50,8 @@ const FilteredOptions = () => {
tabIndex={-1}
onPointerUp={(event) => {
toggleOption(value, event);
clearInput(event);
if (!isMultiSelect && !selectedOptions.includes(value))
toggleIsListOpen(false);
}}
id={`${id}-combobox-new-option`}
className={cl("navds-combobox__list-item__new-option", {
@@ -92,10 +92,8 @@ const FilteredOptions = () => {
tabIndex={-1}
onPointerUp={(event) => {
toggleOption(option, event);
clearInput(event);
if (!isMultiSelect) {
if (!isMultiSelect && !selectedOptions.includes(option))
toggleIsListOpen(false);
}
}}
role="option"
aria-selected={selectedOptions.includes(option)}
@@ -8,6 +8,7 @@ import React, {
useRef,
useLayoutEffect,
} from "react";
import cl from "clsx";
import { useCustomOptionsContext } from "../customOptionsContext";
import { useInputContext } from "../Input/inputContext";
import usePrevious from "../../../util/usePrevious";
@@ -58,7 +59,7 @@ export const FilteredOptionsProvider = ({ children, value: props }) => {
} = props;
const filteredOptionsRef = useRef<HTMLUListElement | null>(null);
const {
inputProps: { id },
inputProps: { "aria-describedby": partialAriaDescribedBy, id },
value,
searchTerm,
setValue,
@@ -124,18 +125,26 @@ export const FilteredOptionsProvider = ({ children, value: props }) => {
}, [allowNewValues, isValueNew]);
const ariaDescribedBy = useMemo(() => {
let activeOption;
if (!isLoading && filteredOptions.length === 0) {
return `${id}-no-hits`;
activeOption = `${id}-no-hits`;
} else if ((value && value !== "") || isLoading) {
if (shouldAutocomplete && filteredOptions[0]) {
return `${id}-option-${filteredOptions[0].replace(" ", "-")}`;
} else if (isLoading) {
return `${id}-is-loading`;
activeOption = `${id}-option-${filteredOptions[0].replace(" ", "-")}`;
} else if (isListOpen && isLoading) {
activeOption = `${id}-is-loading`;
}
} else {
return undefined;
}
}, [isLoading, value, shouldAutocomplete, filteredOptions, id]);
return cl(activeOption, partialAriaDescribedBy) || undefined;
}, [
isListOpen,
isLoading,
value,
partialAriaDescribedBy,
shouldAutocomplete,
filteredOptions,
id,
]);
const currentOption = useMemo(() => {
if (filteredOptionsIndex == null) {
@@ -22,8 +22,12 @@ interface InputProps
const Input = forwardRef<HTMLInputElement, InputProps>(
({ inputClassName, error, errorId, ...rest }, ref) => {
const { clearInput, inputProps, onChange, size, value } = useInputContext();
const { selectedOptions, removeSelectedOption, toggleOption } =
useSelectedOptionsContext();
const {
selectedOptions,
removeSelectedOption,
toggleOption,
isMultiSelect,
} = useSelectedOptionsContext();
const {
activeDecendantId,
allowNewValues,
@@ -47,7 +51,8 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
event.preventDefault();
// Selecting a value from the dropdown / FilteredOptions
toggleOption(currentOption, event);
clearInput(event);
if (!isMultiSelect && !selectedOptions.includes(currentOption))
toggleIsListOpen(false);
} else if (shouldAutocomplete && selectedOptions.includes(value)) {
event.preventDefault();
// Trying to set the same value that is already set, so just clearing the input
@@ -56,15 +61,18 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
event.preventDefault();
// Autocompleting or adding a new value
toggleOption(value, event);
clearInput(event);
if (!isMultiSelect && !selectedOptions.includes(value))
toggleIsListOpen(false);
}
},
[
allowNewValues,
clearInput,
currentOption,
isMultiSelect,
selectedOptions,
shouldAutocomplete,
toggleIsListOpen,
toggleOption,
value,
]
@@ -162,6 +170,7 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
aria-autocomplete={shouldAutocomplete ? "both" : "list"}
aria-activedescendant={activeDecendantId}
aria-describedby={ariaDescribedBy}
aria-invalid={inputProps["aria-invalid"]}
className={cl(
inputClassName,
"navds-combobox__input",
@@ -13,6 +13,7 @@ import { useFormField, FormFieldType } from "../../useFormField";
interface InputContextType extends FormFieldType {
clearInput: (event: React.PointerEvent | React.KeyboardEvent) => void;
error?: string;
focusInput: () => void;
inputRef: React.RefObject<HTMLInputElement>;
value: string;
@@ -80,10 +81,11 @@ export const InputContextProvider = ({ children, value: props }) => {
const clearInput = useCallback(
(event: React.PointerEvent | React.KeyboardEvent) => {
onClear?.(event);
externalOnChange?.(null, "");
setValue("");
setSearchTerm("");
},
[onClear, setSearchTerm, setValue]
[externalOnChange, onClear, setValue]
);
const focusInput = useCallback(() => {
@@ -101,6 +103,7 @@ export const InputContextProvider = ({ children, value: props }) => {
value={{
...formFieldProps,
clearInput,
error,
focusInput,
inputRef,
value,
@@ -42,8 +42,12 @@ export const SelectedOptionsProvider = ({
>;
}) => {
const { clearInput, focusInput } = useInputContext();
const { customOptions, removeCustomOption, addCustomOption } =
useCustomOptionsContext();
const {
customOptions,
removeCustomOption,
addCustomOption,
setCustomOptions,
} = useCustomOptionsContext();
const {
allowNewValues,
isMultiSelect,
@@ -60,11 +64,12 @@ export const SelectedOptionsProvider = ({
const addSelectedOption = useCallback(
(option: string) => {
const isAddedByUser = !options
const isCustomOption = !options
.map((opt) => opt.toLowerCase())
.includes(option?.toLowerCase?.());
if (isAddedByUser) {
if (isCustomOption) {
allowNewValues && addCustomOption(option);
!isMultiSelect && setSelectedOptions([]);
} else if (isMultiSelect) {
setSelectedOptions((prevSelectedOptions) => [
...prevSelectedOptions,
@@ -72,16 +77,24 @@ export const SelectedOptionsProvider = ({
]);
} else {
setSelectedOptions([option]);
setCustomOptions([]);
}
onToggleSelected?.(option, true, isAddedByUser);
onToggleSelected?.(option, true, isCustomOption);
},
[addCustomOption, allowNewValues, isMultiSelect, onToggleSelected, options]
[
addCustomOption,
allowNewValues,
isMultiSelect,
onToggleSelected,
options,
setCustomOptions,
]
);
const removeSelectedOption = useCallback(
(option: string) => {
const isAddedByUser = customOptions.includes(option);
if (isAddedByUser) {
const isCustomOption = customOptions.includes(option);
if (isCustomOption) {
removeCustomOption(option);
} else {
setSelectedOptions((prevSelectedOptions) =>
@@ -90,7 +103,7 @@ export const SelectedOptionsProvider = ({
)
);
}
onToggleSelected?.(option, false, isAddedByUser);
onToggleSelected?.(option, false, isCustomOption);
},
[customOptions, onToggleSelected, removeCustomOption]
);
@@ -102,16 +115,13 @@ export const SelectedOptionsProvider = ({
} else {
addSelectedOption(option);
}
if (!isMultiSelect) {
clearInput(event);
}
clearInput(event);
focusInput();
},
[
addSelectedOption,
clearInput,
focusInput,
isMultiSelect,
removeSelectedOption,
selectedOptions,
]
@@ -3,7 +3,7 @@ import { Meta } from "@storybook/react";
import React, { useState, useId, useMemo } from "react";
import { userEvent, within } from "@storybook/testing-library";
import { Chips, UNSAFE_Combobox, TextField } from "../../index";
import { expect } from "@storybook/jest";
import { expect, jest } from "@storybook/jest";
export default {
title: "ds-react/Combobox",
@@ -94,6 +94,27 @@ MultiSelect.args = {
size: "medium",
};
export function WithAddNewOptions(props) {
const id = useId();
return (
<DemoContainer dataTheme={props.darkMode}>
<UNSAFE_Combobox
id={id}
label="Komboboks med mulighet for å legge til nye verdier"
options={props.options}
allowNewValues={props.allowNewValues}
shouldAutocomplete={props.shouldAutoComplete}
/>
</DemoContainer>
);
}
WithAddNewOptions.args = {
options,
allowNewValues: true,
shouldAutoComplete: true,
};
export function MultiSelectWithAddNewOptions(props) {
const id = useId();
return (
@@ -101,7 +122,7 @@ export function MultiSelectWithAddNewOptions(props) {
<UNSAFE_Combobox
id={id}
isMultiSelect={props.isMultiSelect}
label="Komboboks (med mulighet for å legge til nye verdier)"
label="Multiselect komboboks med mulighet for å legge til nye verdier"
options={props.options}
allowNewValues={props.allowNewValues}
/>
@@ -295,6 +316,33 @@ ComboboxSizes.args = {
options,
};
export const WithError = {
args: {
error: "Du må velge en favorittfrukt.",
isLoading: true,
},
render: (props) => {
const [hasSelectedValue, setHasSelectedValue] = useState(false);
const [isLoading, setIsLoading] = useState(false);
return (
<DemoContainer dataTheme={props.darkMode}>
<UNSAFE_Combobox
filteredOptions={isLoading ? [] : undefined}
options={options}
label="Hva er dine favorittfrukter?"
error={!hasSelectedValue && props.error}
isLoading={isLoading}
onChange={() => {
setIsLoading(true);
setTimeout(() => setIsLoading(false), 2000);
}}
onToggleSelected={(_, isSelected) => setHasSelectedValue(isSelected)}
/>
</DemoContainer>
);
},
};
function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
@@ -324,6 +372,7 @@ export const CancelInputTest = {
userEvent.keyboard("{Escape}");
await sleep(1000);
userEvent.keyboard("{ArrowDown}");
await sleep(500);
const banana = canvas.getByText("banana");
userEvent.click(banana);
},
@@ -421,3 +470,42 @@ export const AddWhenAddNewDisabledTest = {
expect(invalidSelect).not.toBeInTheDocument();
},
};
export const TestThatCallbacksOnlyFireWhenExpected = {
args: {
onChange: jest.fn(),
onClear: jest.fn(),
onToggleSelected: jest.fn(),
},
render: (props) => {
return (
<DemoContainer dataTheme={props.darkMode}>
<UNSAFE_Combobox
options={options}
label="Hva er dine favorittfrukter?"
{...props}
/>
</DemoContainer>
);
},
play: async ({ canvasElement, args }) => {
args.onToggleSelected.mockClear();
args.onClear.mockClear();
args.onChange.mockClear();
const canvas = within(canvasElement);
const input = canvas.getByLabelText("Hva er dine favorittfrukter?");
const searchWord = "tangerine";
userEvent.click(input);
await userEvent.type(input, searchWord, { delay: 200 });
await sleep(250);
userEvent.keyboard("{ArrowDown}");
await sleep(250);
userEvent.keyboard("{Enter}");
await sleep(250);
expect(args.onClear.mock.calls).toHaveLength(1);
expect(args.onToggleSelected.mock.calls).toHaveLength(1);
expect(args.onChange.mock.calls).toHaveLength(searchWord.length + 1);
},
};
@@ -1,10 +1,12 @@
import React, { useState, useCallback, createContext, useContext } from "react";
import { useInputContext } from "./Input/inputContext";
import { useSelectedOptionsContext } from "./SelectedOptions/selectedOptionsContext";
type CustomOptionsContextType = {
customOptions: string[];
removeCustomOption: (option: string) => void;
addCustomOption: (option: string) => void;
setCustomOptions: React.Dispatch<React.SetStateAction<string[]>>;
};
const CustomOptionsContext = createContext<CustomOptionsContextType>(
@@ -14,6 +16,7 @@ const CustomOptionsContext = createContext<CustomOptionsContextType>(
export const CustomOptionsProvider = ({ children }) => {
const [customOptions, setCustomOptions] = useState<string[]>([]);
const { focusInput } = useInputContext();
const { isMultiSelect } = useSelectedOptionsContext();
const removeCustomOption = useCallback(
(option) => {
@@ -27,16 +30,21 @@ export const CustomOptionsProvider = ({ children }) => {
const addCustomOption = useCallback(
(option) => {
setCustomOptions((prevOptions) => [...prevOptions, option]);
if (isMultiSelect) {
setCustomOptions((prevOptions) => [...prevOptions, option]);
} else {
setCustomOptions([option]);
}
focusInput();
},
[focusInput, setCustomOptions]
[focusInput, isMultiSelect, setCustomOptions]
);
const customOptionsState = {
customOptions,
removeCustomOption,
addCustomOption,
setCustomOptions,
};
return (
@@ -66,7 +66,10 @@ export interface ComboboxProps
* @param event
* @returns
*/
onChange?: (event: ChangeEvent<HTMLInputElement>) => void;
onChange?: (
event: ChangeEvent<HTMLInputElement> | null,
value?: string
) => void;
/**
* Callback function triggered whenever the input field is cleared
*
@@ -79,13 +82,13 @@ export interface ComboboxProps
*
* @param option
* @param isSelected - Whether the option has been selected or unselected
* @param isAddedByUser - Whether the option comes from user input, instead of from the list
* @param isCustomOption - Whether the option comes from user input, instead of from the list
* @returns
*/
onToggleSelected?: (
option: string,
isSelected: boolean,
isAddedByUser: boolean
isCustomOption: boolean
) => void;
/**
* List of selected options.
@@ -1,7 +1,8 @@
import React, { useState } from "react";
import { ConfirmationPanel } from "../index";
import { Meta } from "@storybook/react";
import React, { useState } from "react";
import { Link } from "../..";
import { ConfirmationPanel } from "../index";
export default {
title: "ds-react/ConfirmationPanel",
component: ConfirmationPanel,
+21 -5
View File
@@ -24,6 +24,11 @@ export interface ListProps extends React.HTMLAttributes<HTMLDivElement> {
* @default "h3"
*/
headingTag?: React.ElementType<any>;
/**
* Changes padding, height and font-size
* @default medium
*/
size?: "medium" | "small";
}
export interface ListComponent
@@ -37,13 +42,15 @@ export interface ListComponent
}
interface ListContextProps {
listType: "ul" | "ol";
listType: ListProps["as"];
isNested: boolean | null;
size: ListProps["size"];
}
export const ListContext = createContext<ListContextProps>({
listType: "ul",
isNested: null,
size: "medium",
});
export const List = forwardRef<HTMLDivElement, ListProps>(
@@ -55,35 +62,44 @@ export const List = forwardRef<HTMLDivElement, ListProps>(
title,
description,
headingTag = "h3",
size,
...rest
},
ref
) => {
const ariaId = useId();
const { isNested } = useContext(ListContext);
const { isNested, size: _size } = useContext(ListContext);
const listSize = size ?? _size ?? "medium";
return (
<ListContext.Provider
value={{
listType: ListTag,
isNested: isNested === null ? false : true,
size: listSize,
}}
>
<div
{...rest}
ref={ref}
className={cl("navds-list", className, {
className={cl("navds-list", `navds-list--${listSize}`, className, {
"navds-list--nested": isNested === null ? false : true,
})}
>
{title && (
<Heading id={`tittel-${ariaId}`} size="small" as={headingTag}>
<Heading
id={`tittel-${ariaId}`}
size={listSize === "medium" ? "small" : "xsmall"}
as={headingTag}
>
{title}
</Heading>
)}
{description && (
<BodyShort id={`description-${ariaId}`}>{description}</BodyShort>
<BodyShort size={listSize} id={`description-${ariaId}`}>
{description}
</BodyShort>
)}
<ListTag
aria-labelledby={title && `tittel-${ariaId}`}
+4 -10
View File
@@ -20,7 +20,7 @@ export interface ListItemProps extends React.HTMLAttributes<HTMLLIElement> {
export const ListItem = forwardRef<HTMLLIElement, ListItemProps>(
({ className, children, title, icon, ...rest }, ref) => {
const { listType, isNested } = useContext(ListContext);
const { listType, size } = useContext(ListContext);
if (listType === "ol" && icon) {
console.warn(
@@ -29,13 +29,7 @@ export const ListItem = forwardRef<HTMLLIElement, ListItemProps>(
}
return (
<li
{...rest}
ref={ref}
className={cl("navds-list__item", className, {
"navds-list__item--noMargin": isNested,
})}
>
<li {...rest} ref={ref} className={cl("navds-list__item", className)}>
{listType === "ul" && (
<div
className={cl({
@@ -62,9 +56,9 @@ export const ListItem = forwardRef<HTMLLIElement, ListItemProps>(
</div>
)}
<BodyShort as="div" size="small" className="navds-list__item-content">
<BodyShort as="div" size={size} className="navds-list__item-content">
{title && (
<Label as="p" size="small">
<Label as="p" size={size}>
{title}
</Label>
)}
+132 -1
View File
@@ -6,7 +6,7 @@ import {
} from "@navikt/aksel-icons";
import { Meta } from "@storybook/react";
import React from "react";
import { List } from "..";
import { List, VStack } from "..";
export default {
title: "ds-react/List",
@@ -91,6 +91,137 @@ export const WithHeading = {
},
};
export const SizesUl = {
render: () => {
return (
<VStack gap="8">
<List
title="Medium list"
description="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam vel risus volutpat."
size="medium"
>
<List.Item title="Lorem Ipsum Dolor Sit Amet">
Beskrivelsen på punkter er nærmere forklart <a href="/">her</a>
</List.Item>
<List.Item title="Consectetur Adipiscing Elit">
Beskrivelsen på punkter er nærmere forklart <a href="/">her</a>
</List.Item>
<List.Item>
Beskrivelsen på punkter er nærmere forklart <a href="/">her</a>
</List.Item>
</List>
<List
title="Small list"
description="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam vel risus volutpat."
size="small"
>
<List.Item title="Lorem Ipsum Dolor Sit Amet">
Beskrivelsen på punkter er nærmere forklart <a href="/">her</a>
</List.Item>
<List.Item title="Consectetur Adipiscing Elit">
Beskrivelsen på punkter er nærmere forklart <a href="/">her</a>
</List.Item>
<List.Item>
Beskrivelsen på punkter er nærmere forklart <a href="/">her</a>
</List.Item>
</List>
</VStack>
);
},
};
export const SizesOl = {
render: () => {
return (
<VStack gap="8">
<List
title="Medium list"
description="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam vel risus volutpat."
size="medium"
as="ol"
>
<List.Item title="Lorem Ipsum Dolor Sit Amet">
Beskrivelsen på punkter er nærmere forklart <a href="/">her</a>
</List.Item>
<List.Item title="Consectetur Adipiscing Elit">
Beskrivelsen på punkter er nærmere forklart <a href="/">her</a>
</List.Item>
<List.Item>
Beskrivelsen på punkter er nærmere forklart <a href="/">her</a>
</List.Item>
</List>
<List
title="Small list"
description="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam vel risus volutpat."
size="small"
as="ol"
>
<List.Item title="Lorem Ipsum Dolor Sit Amet">
Beskrivelsen på punkter er nærmere forklart <a href="/">her</a>
</List.Item>
<List.Item title="Consectetur Adipiscing Elit">
Beskrivelsen på punkter er nærmere forklart <a href="/">her</a>
</List.Item>
<List.Item>
Beskrivelsen på punkter er nærmere forklart <a href="/">her</a>
</List.Item>
</List>
</VStack>
);
},
};
export const SizesIcons = {
render: () => {
return (
<VStack gap="8">
<List
title="Medium list"
description="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam vel risus volutpat."
size="medium"
>
<List.Item
icon={<HeadHeartIcon aria-hidden />}
title="Lorem Ipsum Dolor Sit Amet"
>
Beskrivelsen på punkter er nærmere forklart <a href="/">her</a>
</List.Item>
<List.Item
icon={<HeadHeartIcon aria-hidden />}
title="Consectetur Adipiscing Elit"
>
Beskrivelsen på punkter er nærmere forklart <a href="/">her</a>
</List.Item>
<List.Item icon={<HeadHeartIcon aria-hidden />}>
Beskrivelsen på punkter er nærmere forklart <a href="/">her</a>
</List.Item>
</List>
<List
title="Small list"
description="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam vel risus volutpat."
size="small"
>
<List.Item
icon={<HeadHeartIcon aria-hidden />}
title="Lorem Ipsum Dolor Sit Amet"
>
Beskrivelsen på punkter er nærmere forklart <a href="/">her</a>
</List.Item>
<List.Item
icon={<HeadHeartIcon aria-hidden />}
title="Consectetur Adipiscing Elit"
>
Beskrivelsen på punkter er nærmere forklart <a href="/">her</a>
</List.Item>
<List.Item icon={<HeadHeartIcon aria-hidden />}>
Beskrivelsen på punkter er nærmere forklart <a href="/">her</a>
</List.Item>
</List>
</VStack>
);
},
};
export const Icons = {
render: () => {
return (
+172 -122
View File
@@ -1,76 +1,91 @@
import React, { forwardRef, useEffect, useMemo, useRef } from "react";
import React, {
forwardRef,
useContext,
useEffect,
useMemo,
useRef,
} from "react";
import cl from "clsx";
import ReactModal from "react-modal";
import dialogPolyfill from "./dialog-polyfill";
import { Detail, Heading, mergeRefs, useId } from "..";
import ModalBody from "./ModalBody";
import ModalHeader from "./ModalHeader";
import ModalFooter from "./ModalFooter";
import { getCloseHandler, useBodyScrollLock } from "./ModalUtils";
import { ModalContext } from "./ModalContext";
import { Button, mergeRefs, useProvider } from "..";
import ModalContent, { ModalContentType } from "./ModalContent";
import { XMarkIcon } from "@navikt/aksel-icons";
const needPolyfill =
typeof window !== "undefined" && window.HTMLDialogElement === undefined;
export interface ModalProps {
export interface ModalProps
extends React.DialogHTMLAttributes<HTMLDialogElement> {
/**
* Content for the header. Alteratively you can use <Modal.Header> instead for more control,
* but then you have to set `aria-label` or `aria-labelledby` on the modal manually.
*/
header?: {
label?: string;
icon?: React.ReactNode;
heading: string;
/**
* Heading size
* @default "medium"
* */
size?: "medium" | "small";
/**
* Removes close-button (X) when false
* @default true
*/
closeButton?: boolean;
};
/**
* Modal content
*/
children: React.ReactNode;
/**
* Open state for modal
* Whether the modal should be visible or not.
* Remember to use the `onClose` callback to keep your local state in sync.
* You can also use `ref.current.openModal()` and `ref.current.close()`.
*/
open: boolean;
open?: boolean;
/**
* Callback for modal wanting to close
* Called when the modal has been closed
*/
onClose: () => void;
onClose?: React.ReactEventHandler<HTMLDialogElement>;
/**
* If modal should close on overlay click (click outside Modal)
* @default true
* Called when the user wants to close the modal (clicked the close button or pressed Esc).
* @returns Whether to close the modal
*/
shouldCloseOnOverlayClick?: boolean;
onBeforeClose?: () => boolean | void;
/**
* Called when the user presses the Esc key, unless `onBeforeClose()` returns `false`.
*/
onCancel?: React.ReactEventHandler<HTMLDialogElement>;
/**
* @default fit-content (up to 700px)
* */
width?: "medium" | "small" | number | `${number}${string}`;
/**
* User defined classname for modal
*/
className?: string;
/**
* User defined classname for modal
* Sets aria-labelledby on modal.
* No need to set this manually if the `header` prop is used. A reference to `header.heading` will be created automatically.
* @warning If not using `header`, you should set either `aria-labelledby` or `aria-label`.
*/
overlayClassName?: string;
/**
* Removes close-button(X) when false
* @default true
*/
closeButton?: boolean;
/**
*
*/
shouldCloseOnEsc?: boolean;
/**
* Allows custom styling of ReactModal, in accordance with their typing
*/
style?: ReactModal.Styles;
/**
* Callback for setting parent element modal will attach to
*/
parentSelector?(): HTMLElement;
"aria-labelledby"?: string;
"aria-describedby"?: string;
"aria-modal"?: boolean;
/**
* Sets aria-label on modal
* @warning This should be set if not using 'aria-labelledby' or 'aria-describedby'
*/
"aria-label"?: string;
}
interface ModalComponent
extends ModalLifecycle,
React.ForwardRefExoticComponent<
ModalProps & React.RefAttributes<ReactModal>
> {
Content: ModalContentType;
extends React.ForwardRefExoticComponent<
ModalProps & React.RefAttributes<HTMLDialogElement>
> {
Header: typeof ModalHeader;
Body: typeof ModalBody;
Footer: typeof ModalFooter;
}
type ModalLifecycle = {
setAppElement: (element: any) => void;
};
/**
* A component that displays a modal dialog.
*
@@ -78,112 +93,147 @@ type ModalLifecycle = {
* @see 🏷️ {@link ModalProps}
*
* @example
* State change with `useRef`
* ```jsx
* const ref = useRef<HTMLDialogElement>(null);
* <Button onClick={() => ref.current?.showModal()}>Open modal</Button>
* <Modal
* ref={ref}
* header={{
* label: "Optional label",
* icon: <FileIcon aria-hidden />,
* heading: "My heading",
* }}
* >
* <Modal.Body>
* <BodyLong>Hello world</BodyLong>
* </Modal.Body>
* <Modal.Footer>
* <Button>Save</Button>
* <Button type="button" variant="tertiary" onClick={() => ref.current?.close()}>Close</Button>
* </Modal.Footer>
* </Modal>
* ```
* @example
* State change with `useState`
* ```jsx
* const [open, setOpen] = useState(false);
*
* <Modal
* open={open}
* aria-label="Modal demo"
* onClose={() => setOpen((x) => !x)}
* onClose={() => setOpen(false)}
* aria-labelledby="modal-heading"
* >
* <Modal.Content>
* <Heading spacing level="1" size="large" id="modal-heading">
* Viktig info
* </Heading>
* <BodyLong spacing>
* Hallo!
* </BodyLong>
* </Modal.Content>
* <Modal.Header>
* <Heading level="1" size="large" id="modal-heading">My heading</Heading>
* </Modal.Header>
* <Modal.Body>
* <BodyLong>Hello world</BodyLong>
* </Modal.Body>
* </Modal>
* ```
*/
export const Modal = forwardRef<ReactModal, ModalProps>(
export const Modal = forwardRef<HTMLDialogElement, ModalProps>(
(
{
header,
children,
open,
onClose,
onBeforeClose,
onCancel,
width,
className,
overlayClassName,
shouldCloseOnOverlayClick = true,
shouldCloseOnEsc = true,
closeButton = true,
"aria-describedby": ariaDescribedBy,
"aria-labelledby": ariaLabelledBy,
"aria-modal": ariaModal,
"aria-label": contentLabel,
"aria-labelledby": ariaLabelledby,
style,
parentSelector,
...rest
},
}: ModalProps,
ref
) => {
const modalRef = useRef<ReactModal | null>(null);
const modalRef = useRef<HTMLDialogElement>(null);
const mergedRef = useMemo(() => mergeRefs([modalRef, ref]), [ref]);
const buttonRef = useRef<HTMLButtonElement>(null);
const rootElement = useProvider()?.rootElement;
const appElement = useProvider()?.appElement;
const ariaLabelId = useId();
if (useContext(ModalContext)) {
console.error("Modals should not be nested");
}
useEffect(() => {
appElement && Modal.setAppElement(appElement);
}, [appElement]);
const onModalCloseRequest = (e) => {
if (shouldCloseOnOverlayClick || e.type === "keydown") {
onClose();
} else if (buttonRef.current) {
buttonRef.current.focus();
if (needPolyfill && modalRef.current) {
dialogPolyfill.registerDialog(modalRef.current);
}
};
}, [modalRef]);
const getParentSelector = () => {
if (parentSelector) {
return parentSelector;
useEffect(() => {
// We need to have this in a useEffect so that the content renders before the modal is displayed,
// and in case `open` is true initially.
if (modalRef.current && open !== undefined) {
if (open && !modalRef.current.open) {
modalRef.current.showModal();
} else if (!open && modalRef.current.open) {
modalRef.current.close();
}
}
return rootElement !== undefined
? () => rootElement as HTMLElement
: undefined;
};
}, [modalRef, open]);
useBodyScrollLock(modalRef, "navds-modal__document-body");
const isWidthPreset =
typeof width === "string" && ["small", "medium"].includes(width);
return (
<ReactModal
{...rest}
parentSelector={getParentSelector()}
style={style}
isOpen={open}
<dialog
ref={mergedRef}
className={cl("navds-modal", className)}
overlayClassName={cl("navds-modal__overlay", overlayClassName)}
shouldCloseOnOverlayClick={shouldCloseOnOverlayClick}
shouldCloseOnEsc={shouldCloseOnEsc}
onRequestClose={(e) => onModalCloseRequest(e)}
aria={{
describedby: ariaDescribedBy,
labelledby: ariaLabelledBy,
modal: ariaModal,
className={cl("navds-modal", className, {
"navds-modal--polyfilled": needPolyfill,
"navds-modal--autowidth": !width,
[`navds-modal--${width}`]: isWidthPreset,
})}
style={{
...style,
...(!isWidthPreset ? { width } : {}),
}}
contentLabel={contentLabel}
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
}
{...rest}
>
{children}
{closeButton && (
<Button
className={cl("navds-modal__button", {
"navds-modal__button--shake": shouldCloseOnOverlayClick,
})}
size="small"
variant="tertiary-neutral"
ref={buttonRef}
onClick={onClose}
icon={<XMarkIcon title="Lukk modalvindu" />}
/>
)}
</ReactModal>
<ModalContext.Provider
value={{
closeHandler: getCloseHandler(modalRef, header, onBeforeClose),
}}
>
{header && (
<ModalHeader>
{header.label && (
<Detail className="navds-modal__label">{header.label}</Detail>
)}
<Heading
size={header.size ?? "medium"}
level="1"
id={ariaLabelId}
>
<span className="navds-modal__header-icon">{header.icon}</span>
{header.heading}
</Heading>
</ModalHeader>
)}
{children}
</ModalContext.Provider>
</dialog>
);
}
) as ModalComponent;
Modal.setAppElement = (element) => ReactModal.setAppElement(element);
Modal.Content = ModalContent;
Modal.Header = ModalHeader;
Modal.Body = ModalBody;
Modal.Footer = ModalFooter;
export default Modal;
@@ -0,0 +1,14 @@
import React, { forwardRef } from "react";
import cl from "clsx";
export interface ModalBodyProps extends React.HTMLAttributes<HTMLDivElement> {
children: React.ReactNode;
}
const ModalBody = forwardRef<HTMLDivElement, ModalBodyProps>(
({ className, ...rest }, ref) => (
<div {...rest} ref={ref} className={cl("navds-modal__body", className)} />
)
);
export default ModalBody;
@@ -1,26 +0,0 @@
import React, { forwardRef } from "react";
import cl from "clsx";
export interface ModalContentProps
extends React.HTMLAttributes<HTMLDivElement> {
/**
* Modal.Content content
*/
children: React.ReactNode;
}
export type ModalContentType = React.ForwardRefExoticComponent<
ModalContentProps & React.RefAttributes<HTMLDivElement>
>;
const ModalContent: ModalContentType = forwardRef(
({ className, ...rest }, ref) => (
<div
{...rest}
ref={ref}
className={cl("navds-modal__content", className)}
/>
)
);
export default ModalContent;
@@ -0,0 +1,6 @@
import React from "react";
interface ModalContextProps {
closeHandler?: React.MouseEventHandler<HTMLButtonElement>;
}
export const ModalContext = React.createContext<ModalContextProps | null>(null);
@@ -0,0 +1,14 @@
import React, { forwardRef } from "react";
import cl from "clsx";
export interface ModalFooterProps extends React.HTMLAttributes<HTMLDivElement> {
children: React.ReactNode;
}
const ModalFooter = forwardRef<HTMLDivElement, ModalFooterProps>(
({ className, ...rest }, ref) => (
<div {...rest} ref={ref} className={cl("navds-modal__footer", className)} />
)
);
export default ModalFooter;
@@ -0,0 +1,42 @@
import React, { forwardRef, useContext } from "react";
import cl from "clsx";
import { XMarkIcon } from "@navikt/aksel-icons";
import { Button } from "../button";
import { ModalContext } from "./ModalContext";
export interface ModalHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
children?: React.ReactNode;
/**
* Removes close-button (X) when false
* @default true
*/
closeButton?: boolean;
}
const ModalHeader = forwardRef<HTMLDivElement, ModalHeaderProps>(
({ children, className, closeButton = true, ...rest }, ref) => {
const context = useContext(ModalContext);
if (context === null) {
console.error("<Modal.Header> has to be used within a <Modal>");
return null;
}
return (
<div {...rest} ref={ref} className={cl("navds-modal__header", className)}>
{context.closeHandler && closeButton && (
<Button
type="button"
className="navds-modal__button"
size="small"
variant="tertiary-neutral"
onClick={context.closeHandler}
icon={<XMarkIcon title="Lukk modalvindu" />}
/>
)}
{children}
</div>
);
}
);
export default ModalHeader;
@@ -0,0 +1,37 @@
import React from "react";
import type { ModalProps } from "./Modal";
export function getCloseHandler(
modalRef: React.RefObject<HTMLDialogElement>,
header: ModalProps["header"],
onBeforeClose: ModalProps["onBeforeClose"]
) {
if (header && header.closeButton === false) return undefined;
if (onBeforeClose) {
return () => onBeforeClose() !== false && modalRef.current?.close();
}
return () => modalRef.current?.close();
}
export function useBodyScrollLock(
modalRef: React.RefObject<HTMLDialogElement>,
bodyClass: string
) {
React.useEffect(() => {
if (!modalRef.current) return;
if (modalRef.current.open) document.body.classList.add(bodyClass); // In case `open` is true initially
const observer = new MutationObserver(() => {
if (modalRef.current?.open) document.body.classList.add(bodyClass);
else document.body.classList.remove(bodyClass);
});
observer.observe(modalRef.current, {
attributes: true,
attributeFilter: ["open"],
});
return () => {
observer.disconnect();
document.body.classList.remove(bodyClass); // In case modal is unmounted before it's closed
};
}, [modalRef, bodyClass]);
}
@@ -0,0 +1,980 @@
// @ts-nocheck
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
/**
* Dispatches the passed event to both an "on<type>" handler as well as via the
* normal dispatch operation. Does not bubble.
*
* @param {!EventTarget} target
* @param {!Event} event
* @return {boolean}
*/
function safeDispatchEvent(target, event) {
var check = "on" + event.type.toLowerCase();
if (typeof target[check] === "function") {
target[check](event);
}
return target.dispatchEvent(event);
}
/**
* @param {Element} el to check for stacking context
* @return {boolean} whether this el or its parents creates a stacking context
*/
function createsStackingContext(el) {
while (el && el !== document.body) {
var s = window.getComputedStyle(el);
var invalid = function (k, ok) {
return !(s[k] === undefined || s[k] === ok);
};
if (
s.opacity < 1 ||
invalid("zIndex", "auto") ||
invalid("transform", "none") ||
invalid("mixBlendMode", "normal") ||
invalid("filter", "none") ||
invalid("perspective", "none") ||
s["isolation"] === "isolate" ||
s.position === "fixed" ||
s.webkitOverflowScrolling === "touch"
) {
return true;
}
el = el.parentElement;
}
return false;
}
/**
* Finds the nearest <dialog> from the passed element.
*
* @param {Element} el to search from
* @return {HTMLDialogElement} dialog found
*/
function findNearestDialog(el) {
while (el) {
if (el.localName === "dialog") {
return /** @type {HTMLDialogElement} */ el;
}
if (el.parentElement) {
el = el.parentElement;
} else if (el.parentNode) {
el = el.parentNode.host;
} else {
el = null;
}
}
return null;
}
/**
* Blur the specified element, as long as it's not the HTML body element.
* This works around an IE9/10 bug - blurring the body causes Windows to
* blur the whole application.
*
* @param {Element} el to blur
*/
function safeBlur(el) {
// Find the actual focused element when the active element is inside a shadow root
while (el && el.shadowRoot && el.shadowRoot.activeElement) {
el = el.shadowRoot.activeElement;
}
if (el && el.blur && el !== document.body) {
el.blur();
}
}
/**
* @param {!NodeList} nodeList to search
* @param {Node} node to find
* @return {boolean} whether node is inside nodeList
*/
function inNodeList(nodeList, node) {
for (var i = 0; i < nodeList.length; ++i) {
if (nodeList[i] === node) {
return true;
}
}
return false;
}
/**
* @param {HTMLFormElement} el to check
* @return {boolean} whether this form has method="dialog"
*/
function isFormMethodDialog(el) {
if (!el || !el.hasAttribute("method")) {
return false;
}
return el.getAttribute("method").toLowerCase() === "dialog";
}
/**
* @param {!DocumentFragment|!Element} hostElement
* @return {?Element}
*/
function findFocusableElementWithin(hostElement) {
// Note that this is 'any focusable area'. This list is probably not exhaustive, but the
// alternative involves stepping through and trying to focus everything.
var opts = ["button", "input", "keygen", "select", "textarea"];
var query = opts.map(function (el) {
return el + ":not([disabled])";
});
// TODO(samthor): tabindex values that are not numeric are not focusable.
query.push('[tabindex]:not([disabled]):not([tabindex=""])'); // tabindex != "", not disabled
var target = hostElement.querySelector(query.join(", "));
if (!target && "attachShadow" in Element.prototype) {
// If we haven't found a focusable target, see if the host element contains an element
// which has a shadowRoot.
// Recursively search for the first focusable item in shadow roots.
var elems = hostElement.querySelectorAll("*");
for (var i = 0; i < elems.length; i++) {
if (elems[i].tagName && elems[i].shadowRoot) {
target = findFocusableElementWithin(elems[i].shadowRoot);
if (target) {
break;
}
}
}
}
return target;
}
/**
* Determines if an element is attached to the DOM.
* @param {Element} element to check
* @return {boolean} whether the element is in DOM
*/
function isConnected(element) {
return element.isConnected || document.body.contains(element);
}
/**
* @param {!Event} event
* @return {?Element}
*/
function findFormSubmitter(event) {
if (event.submitter) {
return event.submitter;
}
var form = event.target;
if (!(form instanceof HTMLFormElement)) {
return null;
}
var submitter = dialogPolyfill.formSubmitter;
if (!submitter) {
var target = event.target;
var root = ("getRootNode" in target && target.getRootNode()) || document;
submitter = root.activeElement;
}
if (!submitter || submitter.form !== form) {
return null;
}
return submitter;
}
/**
* @param {!Event} event
*/
function maybeHandleSubmit(event) {
if (event.defaultPrevented) {
return;
}
var form = /** @type {!HTMLFormElement} */ event.target;
// We'd have a value if we clicked on an imagemap.
var value = dialogPolyfill.imagemapUseValue;
var submitter = findFormSubmitter(event);
if (value === null && submitter) {
value = submitter.value;
}
// There should always be a dialog as this handler is added specifically on them, but check just
// in case.
var dialog = findNearestDialog(form);
if (!dialog) {
return;
}
// Prefer formmethod on the button.
var formmethod =
(submitter && submitter.getAttribute("formmethod")) ||
form.getAttribute("method");
if (formmethod !== "dialog") {
return;
}
event.preventDefault();
if (value != null) {
// nb. we explicitly check against null/undefined
dialog.close(value);
} else {
dialog.close();
}
}
/**
* @param {!HTMLDialogElement} dialog to upgrade
* @constructor
*/
function dialogPolyfillInfo(dialog) {
this.dialog_ = dialog;
this.replacedStyleTop_ = false;
this.openAsModal_ = false;
// Set a11y role. Browsers that support dialog implicitly know this already.
if (!dialog.hasAttribute("role")) {
dialog.setAttribute("role", "dialog");
}
dialog.show = this.show.bind(this);
dialog.showModal = this.showModal.bind(this);
dialog.close = this.close.bind(this);
dialog.addEventListener("submit", maybeHandleSubmit, false);
if (!("returnValue" in dialog)) {
dialog.returnValue = "";
}
if ("MutationObserver" in window) {
var mo = new MutationObserver(this.maybeHideModal.bind(this));
mo.observe(dialog, { attributes: true, attributeFilter: ["open"] });
} else {
// IE10 and below support. Note that DOMNodeRemoved etc fire _before_ removal. They also
// seem to fire even if the element was removed as part of a parent removal. Use the removed
// events to force downgrade (useful if removed/immediately added).
var removed = false;
var cb = function () {
removed ? this.downgradeModal() : this.maybeHideModal();
removed = false;
}.bind(this);
var timeout;
var delayModel = function (ev) {
if (ev.target !== dialog) {
return;
} // not for a child element
var cand = "DOMNodeRemoved";
removed |= ev.type.substr(0, cand.length) === cand;
window.clearTimeout(timeout);
timeout = window.setTimeout(cb, 0);
};
["DOMAttrModified", "DOMNodeRemoved", "DOMNodeRemovedFromDocument"].forEach(
function (name) {
dialog.addEventListener(name, delayModel);
}
);
}
// Note that the DOM is observed inside DialogManager while any dialog
// is being displayed as a modal, to catch modal removal from the DOM.
Object.defineProperty(dialog, "open", {
set: this.setOpen.bind(this),
get: dialog.hasAttribute.bind(dialog, "open"),
});
this.backdrop_ = document.createElement("div");
this.backdrop_.className = "backdrop";
this.backdrop_.addEventListener(
"mouseup",
this.backdropMouseEvent_.bind(this)
);
this.backdrop_.addEventListener(
"mousedown",
this.backdropMouseEvent_.bind(this)
);
this.backdrop_.addEventListener("click", this.backdropMouseEvent_.bind(this));
}
dialogPolyfillInfo.prototype = /** @type {HTMLDialogElement.prototype} */ {
get dialog() {
return this.dialog_;
},
/**
* Maybe remove this dialog from the modal top layer. This is called when
* a modal dialog may no longer be tenable, e.g., when the dialog is no
* longer open or is no longer part of the DOM.
*/
maybeHideModal: function () {
if (this.dialog_.hasAttribute("open") && isConnected(this.dialog_)) {
return;
}
this.downgradeModal();
},
/**
* Remove this dialog from the modal top layer, leaving it as a non-modal.
*/
downgradeModal: function () {
if (!this.openAsModal_) {
return;
}
this.openAsModal_ = false;
this.dialog_.style.zIndex = "";
// This won't match the native <dialog> exactly because if the user set top on a centered
// polyfill dialog, that top gets thrown away when the dialog is closed. Not sure it's
// possible to polyfill this perfectly.
if (this.replacedStyleTop_) {
this.dialog_.style.top = "";
this.replacedStyleTop_ = false;
}
// Clear the backdrop and remove from the manager.
this.backdrop_.parentNode &&
this.backdrop_.parentNode.removeChild(this.backdrop_);
dialogPolyfill.dm.removeDialog(this);
},
/**
* @param {boolean} value whether to open or close this dialog
*/
setOpen: function (value) {
if (value) {
this.dialog_.hasAttribute("open") ||
this.dialog_.setAttribute("open", "");
} else {
this.dialog_.removeAttribute("open");
this.maybeHideModal(); // nb. redundant with MutationObserver
}
},
/**
* Handles mouse events ('mouseup', 'mousedown', 'click') on the fake .backdrop element, redirecting them as if
* they were on the dialog itself.
*
* @param {!Event} e to redirect
*/
backdropMouseEvent_: function (e) {
if (!this.dialog_.hasAttribute("tabindex")) {
// Clicking on the backdrop should move the implicit cursor, even if dialog cannot be
// focused. Create a fake thing to focus on. If the backdrop was _before_ the dialog, this
// would not be needed - clicks would move the implicit cursor there.
var fake = document.createElement("div");
this.dialog_.insertBefore(fake, this.dialog_.firstChild);
fake.tabIndex = -1;
fake.focus();
this.dialog_.removeChild(fake);
} else {
this.dialog_.focus();
}
var redirectedEvent = document.createEvent("MouseEvents");
redirectedEvent.initMouseEvent(
e.type,
e.bubbles,
e.cancelable,
window,
e.detail,
e.screenX,
e.screenY,
e.clientX,
e.clientY,
e.ctrlKey,
e.altKey,
e.shiftKey,
e.metaKey,
e.button,
e.relatedTarget
);
this.dialog_.dispatchEvent(redirectedEvent);
e.stopPropagation();
},
/**
* Focuses on the first focusable element within the dialog. This will always blur the current
* focus, even if nothing within the dialog is found.
*/
focus_: function () {
// Find element with `autofocus` attribute, or fall back to the first form/tabindex control.
var target = this.dialog_.querySelector("[autofocus]:not([disabled])");
if (!target && this.dialog_.tabIndex >= 0) {
target = this.dialog_;
}
if (!target) {
target = findFocusableElementWithin(this.dialog_);
}
safeBlur(document.activeElement);
target && target.focus();
},
/**
* Sets the zIndex for the backdrop and dialog.
*
* @param {number} dialogZ
* @param {number} backdropZ
*/
updateZIndex: function (dialogZ, backdropZ) {
if (dialogZ < backdropZ) {
throw new Error("dialogZ should never be < backdropZ");
}
this.dialog_.style.zIndex = dialogZ;
this.backdrop_.style.zIndex = backdropZ;
},
/**
* Shows the dialog. If the dialog is already open, this does nothing.
*/
show: function () {
if (!this.dialog_.open) {
this.setOpen(true);
this.focus_();
}
},
/**
* Show this dialog modally.
*/
showModal: function () {
if (this.dialog_.hasAttribute("open")) {
throw new Error(
"Failed to execute 'showModal' on dialog: The element is already open, and therefore cannot be opened modally."
);
}
if (!isConnected(this.dialog_)) {
throw new Error(
"Failed to execute 'showModal' on dialog: The element is not in a Document."
);
}
if (!dialogPolyfill.dm.pushDialog(this)) {
throw new Error(
"Failed to execute 'showModal' on dialog: There are too many open modal dialogs."
);
}
if (createsStackingContext(this.dialog_.parentElement)) {
console.warn(
"A dialog is being shown inside a stacking context. " +
"This may cause it to be unusable. For more information, see this link: " +
"https://github.com/GoogleChrome/dialog-polyfill/#stacking-context"
);
}
this.setOpen(true);
this.openAsModal_ = true;
// Optionally center vertically, relative to the current viewport.
if (dialogPolyfill.needsCentering(this.dialog_)) {
dialogPolyfill.reposition(this.dialog_);
this.replacedStyleTop_ = true;
} else {
this.replacedStyleTop_ = false;
}
// Insert backdrop.
this.dialog_.parentNode.insertBefore(
this.backdrop_,
this.dialog_.nextSibling
);
// Focus on whatever inside the dialog.
this.focus_();
},
/**
* Closes this HTMLDialogElement. This is optional vs clearing the open
* attribute, however this fires a 'close' event.
*
* @param {string=} opt_returnValue to use as the returnValue
*/
close: function (opt_returnValue) {
if (!this.dialog_.hasAttribute("open")) {
throw new Error(
"Failed to execute 'close' on dialog: The element does not have an 'open' attribute, and therefore cannot be closed."
);
}
this.setOpen(false);
// Leave returnValue untouched in case it was set directly on the element
if (opt_returnValue !== undefined) {
this.dialog_.returnValue = opt_returnValue;
}
// Triggering "close" event for any attached listeners on the <dialog>.
var closeEvent = new window.CustomEvent("close", {
bubbles: false,
cancelable: false,
});
safeDispatchEvent(this.dialog_, closeEvent);
},
};
var dialogPolyfill = {};
dialogPolyfill.reposition = function (element) {
var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
var topValue = scrollTop + (window.innerHeight - element.offsetHeight) / 2;
element.style.top = Math.max(scrollTop, topValue) + "px";
};
dialogPolyfill.isInlinePositionSetByStylesheet = function (element) {
for (var i = 0; i < document.styleSheets.length; ++i) {
var styleSheet = document.styleSheets[i];
var cssRules = null;
// Some browsers throw on cssRules.
try {
cssRules = styleSheet.cssRules;
} catch (e) {
/* empty */
}
if (!cssRules) {
continue;
}
for (var j = 0; j < cssRules.length; ++j) {
var rule = cssRules[j];
var selectedNodes = null;
// Ignore errors on invalid selector texts.
try {
selectedNodes = document.querySelectorAll(rule.selectorText);
} catch (e) {
/* empty */
}
if (!selectedNodes || !inNodeList(selectedNodes, element)) {
continue;
}
var cssTop = rule.style.getPropertyValue("top");
var cssBottom = rule.style.getPropertyValue("bottom");
if (
(cssTop && cssTop !== "auto") ||
(cssBottom && cssBottom !== "auto")
) {
return true;
}
}
}
return false;
};
dialogPolyfill.needsCentering = function (dialog) {
var computedStyle = window.getComputedStyle(dialog);
if (computedStyle.position !== "absolute") {
return false;
}
// We must determine whether the top/bottom specified value is non-auto. In
// WebKit/Blink, checking computedStyle.top == 'auto' is sufficient, but
// Firefox returns the used value. So we do this crazy thing instead: check
// the inline style and then go through CSS rules.
if (
(dialog.style.top !== "auto" && dialog.style.top !== "") ||
(dialog.style.bottom !== "auto" && dialog.style.bottom !== "")
) {
return false;
}
return !dialogPolyfill.isInlinePositionSetByStylesheet(dialog);
};
/**
* @param {!Element} element to force upgrade
*/
dialogPolyfill.forceRegisterDialog = function (element) {
if (window.HTMLDialogElement || element.showModal) {
console.warn(
"This browser already supports <dialog>, the polyfill " +
"may not work correctly",
element
);
}
if (element.localName !== "dialog") {
throw new Error("Failed to register dialog: The element is not a dialog.");
}
new dialogPolyfillInfo(/** @type {!HTMLDialogElement} */ element);
};
/**
* @param {!Element} element to upgrade, if necessary
*/
dialogPolyfill.registerDialog = function (element) {
if (!element.showModal) {
dialogPolyfill.forceRegisterDialog(element);
}
};
/**
* @constructor
*/
dialogPolyfill.DialogManager = function () {
/** @type {!Array<!dialogPolyfillInfo>} */
this.pendingDialogStack = [];
var checkDOM = this.checkDOM_.bind(this);
// The overlay is used to simulate how a modal dialog blocks the document.
// The blocking dialog is positioned on top of the overlay, and the rest of
// the dialogs on the pending dialog stack are positioned below it. In the
// actual implementation, the modal dialog stacking is controlled by the
// top layer, where z-index has no effect.
this.overlay = document.createElement("div");
this.overlay.className = "_dialog_overlay";
this.overlay.addEventListener(
"click",
function (e) {
this.forwardTab_ = undefined;
e.stopPropagation();
checkDOM([]); // sanity-check DOM
}.bind(this)
);
this.handleKey_ = this.handleKey_.bind(this);
this.handleFocus_ = this.handleFocus_.bind(this);
this.zIndexLow_ = 100000;
this.zIndexHigh_ = 100000 + 150;
this.forwardTab_ = undefined;
if ("MutationObserver" in window) {
this.mo_ = new MutationObserver(function (records) {
var removed = [];
records.forEach(function (rec) {
for (var i = 0, c; (c = rec.removedNodes[i]); ++i) {
if (!(c instanceof Element)) {
continue;
} else if (c.localName === "dialog") {
removed.push(c);
}
removed = removed.concat(c.querySelectorAll("dialog"));
}
});
removed.length && checkDOM(removed);
});
}
};
/**
* Called on the first modal dialog being shown. Adds the overlay and related
* handlers.
*/
dialogPolyfill.DialogManager.prototype.blockDocument = function () {
document.documentElement.addEventListener("focus", this.handleFocus_, true);
document.addEventListener("keydown", this.handleKey_);
this.mo_ && this.mo_.observe(document, { childList: true, subtree: true });
};
/**
* Called on the first modal dialog being removed, i.e., when no more modal
* dialogs are visible.
*/
dialogPolyfill.DialogManager.prototype.unblockDocument = function () {
document.documentElement.removeEventListener(
"focus",
this.handleFocus_,
true
);
document.removeEventListener("keydown", this.handleKey_);
this.mo_ && this.mo_.disconnect();
};
/**
* Updates the stacking of all known dialogs.
*/
dialogPolyfill.DialogManager.prototype.updateStacking = function () {
var zIndex = this.zIndexHigh_;
for (var i = 0, dpi; (dpi = this.pendingDialogStack[i]); ++i) {
dpi.updateZIndex(--zIndex, --zIndex);
if (i === 0) {
this.overlay.style.zIndex = --zIndex;
}
}
// Make the overlay a sibling of the dialog itself.
var last = this.pendingDialogStack[0];
if (last) {
var p = last.dialog.parentNode || document.body;
p.appendChild(this.overlay);
} else if (this.overlay.parentNode) {
this.overlay.parentNode.removeChild(this.overlay);
}
};
/**
* @param {Element} candidate to check if contained or is the top-most modal dialog
* @return {boolean} whether candidate is contained in top dialog
*/
dialogPolyfill.DialogManager.prototype.containedByTopDialog_ = function (
candidate
) {
while ((candidate = findNearestDialog(candidate))) {
for (var i = 0, dpi; (dpi = this.pendingDialogStack[i]); ++i) {
if (dpi.dialog === candidate) {
return i === 0; // only valid if top-most
}
}
candidate = candidate.parentElement;
}
return false;
};
dialogPolyfill.DialogManager.prototype.handleFocus_ = function (event) {
var target = event.composedPath ? event.composedPath()[0] : event.target;
if (this.containedByTopDialog_(target)) {
return;
}
if (document.activeElement === document.documentElement) {
return;
}
event.preventDefault();
event.stopPropagation();
safeBlur(/** @type {Element} */ target);
if (this.forwardTab_ === undefined) {
return;
} // move focus only from a tab key
var dpi = this.pendingDialogStack[0];
var dialog = dpi.dialog;
var position = dialog.compareDocumentPosition(target);
if (position & Node.DOCUMENT_POSITION_PRECEDING) {
if (this.forwardTab_) {
// forward
dpi.focus_();
} else if (target !== document.documentElement) {
// backwards if we're not already focused on <html>
document.documentElement.focus();
}
} else {
// TODO: Focus after the dialog, is ignored.
}
return false;
};
dialogPolyfill.DialogManager.prototype.handleKey_ = function (event) {
this.forwardTab_ = undefined;
if (event.keyCode === 27) {
event.preventDefault();
event.stopPropagation();
var cancelEvent = new window.CustomEvent("cancel", {
bubbles: false,
cancelable: true,
});
var dpi = this.pendingDialogStack[0];
if (dpi && safeDispatchEvent(dpi.dialog, cancelEvent)) {
dpi.dialog.close();
}
} else if (event.keyCode === 9) {
this.forwardTab_ = !event.shiftKey;
}
};
/**
* Finds and downgrades any known modal dialogs that are no longer displayed. Dialogs that are
* removed and immediately readded don't stay modal, they become normal.
*
* @param {!Array<!HTMLDialogElement>} removed that have definitely been removed
*/
dialogPolyfill.DialogManager.prototype.checkDOM_ = function (removed) {
// This operates on a clone because it may cause it to change. Each change also calls
// updateStacking, which only actually needs to happen once. But who removes many modal dialogs
// at a time?!
var clone = this.pendingDialogStack.slice();
clone.forEach(function (dpi) {
if (removed.indexOf(dpi.dialog) !== -1) {
dpi.downgradeModal();
} else {
dpi.maybeHideModal();
}
});
};
/**
* @param {!dialogPolyfillInfo} dpi
* @return {boolean} whether the dialog was allowed
*/
dialogPolyfill.DialogManager.prototype.pushDialog = function (dpi) {
var allowed = (this.zIndexHigh_ - this.zIndexLow_) / 2 - 1;
if (this.pendingDialogStack.length >= allowed) {
return false;
}
if (this.pendingDialogStack.unshift(dpi) === 1) {
this.blockDocument();
}
this.updateStacking();
return true;
};
/**
* @param {!dialogPolyfillInfo} dpi
*/
dialogPolyfill.DialogManager.prototype.removeDialog = function (dpi) {
var index = this.pendingDialogStack.indexOf(dpi);
if (index === -1) {
return;
}
this.pendingDialogStack.splice(index, 1);
if (this.pendingDialogStack.length === 0) {
this.unblockDocument();
}
this.updateStacking();
};
if (typeof window !== "undefined" && window.HTMLDialogElement === undefined) {
dialogPolyfill.dm = new dialogPolyfill.DialogManager();
dialogPolyfill.formSubmitter = null;
dialogPolyfill.imagemapUseValue = null;
}
/**
* Installs global handlers, such as click listers and native method overrides. These are needed
* even if a no dialog is registered, as they deal with <form method="dialog">.
*/
if (typeof window !== "undefined" && window.HTMLDialogElement === undefined) {
/**
* If HTMLFormElement translates method="DIALOG" into 'get', then replace the descriptor with
* one that returns the correct value.
*/
var testForm = document.createElement("form");
testForm.setAttribute("method", "dialog");
if (testForm.method !== "dialog") {
var methodDescriptor = Object.getOwnPropertyDescriptor(
HTMLFormElement.prototype,
"method"
);
if (methodDescriptor) {
// nb. Some older iOS and older PhantomJS fail to return the descriptor. Don't do anything
// and don't bother to update the element.
var realGet = methodDescriptor.get;
methodDescriptor.get = function () {
if (isFormMethodDialog(this)) {
return "dialog";
}
return realGet.call(this);
};
var realSet = methodDescriptor.set;
/** @this {HTMLElement} */
methodDescriptor.set = function (v) {
if (typeof v === "string" && v.toLowerCase() === "dialog") {
return this.setAttribute("method", v);
}
return realSet.call(this, v);
};
Object.defineProperty(
HTMLFormElement.prototype,
"method",
methodDescriptor
);
}
}
/**
* Global 'click' handler, to capture the <input type="submit"> or <button> element which has
* submitted a <form method="dialog">. Needed as Safari and others don't report this inside
* document.activeElement.
*/
document.addEventListener(
"click",
function (ev) {
dialogPolyfill.formSubmitter = null;
dialogPolyfill.imagemapUseValue = null;
if (ev.defaultPrevented) {
return;
} // e.g. a submit which prevents default submission
var target = /** @type {Element} */ ev.target;
if ("composedPath" in ev) {
var path = ev.composedPath();
target = path.shift() || target;
}
if (!target || !isFormMethodDialog(target.form)) {
return;
}
var valid =
target.type === "submit" &&
["button", "input"].indexOf(target.localName) > -1;
if (!valid) {
if (!(target.localName === "input" && target.type === "image")) {
return;
}
// this is a <input type="image">, which can submit forms
dialogPolyfill.imagemapUseValue = ev.offsetX + "," + ev.offsetY;
}
var dialog = findNearestDialog(target);
if (!dialog) {
return;
}
dialogPolyfill.formSubmitter = target;
},
false
);
/**
* Global 'submit' handler. This handles submits of `method="dialog"` which are invalid, i.e.,
* outside a dialog. They get prevented.
*/
document.addEventListener("submit", function (ev) {
var form = ev.target;
var dialog = findNearestDialog(form);
if (dialog) {
return; // ignore, handle there
}
var submitter = findFormSubmitter(ev);
var formmethod =
(submitter && submitter.getAttribute("formmethod")) ||
form.getAttribute("method");
if (formmethod === "dialog") {
ev.preventDefault();
}
});
/**
* Replace the native HTMLFormElement.submit() method, as it won't fire the
* submit event and give us a chance to respond.
*/
var nativeFormSubmit = HTMLFormElement.prototype.submit;
var replacementFormSubmit = function () {
if (!isFormMethodDialog(this)) {
return nativeFormSubmit.call(this);
}
var dialog = findNearestDialog(this);
dialog && dialog.close();
};
HTMLFormElement.prototype.submit = replacementFormSubmit;
}
export default dialogPolyfill as {
registerDialog(dialog: HTMLDialogElement): void;
forceRegisterDialog(dialog: HTMLDialogElement): void;
};
+3 -1
View File
@@ -1,2 +1,4 @@
export { default as Modal, type ModalProps } from "./Modal";
export { type ModalContentProps } from "./ModalContent";
export { type ModalHeaderProps } from "./ModalHeader";
export { type ModalBodyProps } from "./ModalBody";
export { type ModalFooterProps } from "./ModalFooter";
+148 -65
View File
@@ -1,5 +1,5 @@
/* eslint-disable react-hooks/rules-of-hooks */
import React, { useEffect, useState } from "react";
import React, { useRef, useState } from "react";
import { FileIcon } from "@navikt/aksel-icons";
import { BodyLong, Button, Heading } from "..";
import Modal from "./Modal";
@@ -7,78 +7,161 @@ export default {
title: "ds-react/Modal",
component: Modal,
parameters: {
chromatic: { delay: 1000 },
chromatic: { pauseAnimationAtEnd: true },
},
};
export const Default = {
render: (props) => {
const [open, setOpen] = useState(false);
useEffect(() => {
Modal.setAppElement?.("#root");
}, []);
return (
<>
<Button onClick={() => setOpen(true)}>Open Modal</Button>
<Modal
open={open}
onClose={() => setOpen(false)}
aria-labelledby="header123"
{...props}
>
<Modal.Content>
<Heading spacing id="header123" level="1" size="large">
Header
</Heading>
<Heading spacing level="2" size="medium">
Header
</Heading>
<BodyLong>
Voluptate laboris mollit dolore qui. Magna elit.
</BodyLong>
</Modal.Content>
</Modal>
</>
);
},
args: {
shouldCloseOnOverlayClick: true,
closeButton: true,
},
};
export const ParentSelector = () => {
const [open, setOpen] = useState(true);
useEffect(() => {
Modal.setAppElement?.("#root");
}, []);
const parentEl = document.getElementById("custom-container");
export const WithUseRef = () => {
const ref = useRef<HTMLDialogElement>(null);
const ref2 = useRef<HTMLDialogElement>(null);
return (
<>
<div id="custom-container" />
<Button onClick={() => setOpen(true)}>Open Modal</Button>
<Button onClick={() => ref.current?.showModal()}>Open Modal</Button>
<Modal
open={open}
onClose={() => setOpen(false)}
aria-labelledby="header123"
parentSelector={parentEl ? () => parentEl : undefined}
open={ref.current ? undefined : true /* initially open */}
ref={ref}
header={{
label: "Optional label",
icon: <FileIcon aria-hidden />,
heading: "Title",
size: "small",
}}
>
<Modal.Content>
<Heading spacing id="header123" level="1" size="large">
Header
</Heading>
<Heading spacing level="2" size="medium">
Header
</Heading>
<BodyLong>Voluptate laboris mollit dolore qui. Magna elit.</BodyLong>
</Modal.Content>
<Modal.Body>
<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. 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>
{/* Nested modal */}
<Modal
ref={ref2}
onBeforeClose={() =>
window.confirm("Are you sure you want to close the modal?")
}
aria-labelledby="heading123"
>
<Modal.Header>
<Heading size="medium" level="2" id="heading123">
Custom header
</Heading>
</Modal.Header>
<Modal.Body>
Nesting modals is not recommended, but works in most cases.
</Modal.Body>
<Modal.Footer>
<Button onClick={() => ref2.current?.close()}>
Close w/o confirm
</Button>
</Modal.Footer>
</Modal>
<BodyLong>
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.
</BodyLong>
</Modal.Body>
<Modal.Footer>
<Button>Primary</Button>
<Button variant="secondary" onClick={() => ref2.current?.showModal()}>
Secondary
</Button>
<Button variant="tertiary">Tertiary</Button>
</Modal.Footer>
</Modal>
</>
);
};
WithUseRef.storyName = "With useRef";
export const WithUseState = () => {
const [open, setOpen] = useState(true);
const [open2, setOpen2] = useState(false);
return (
<div style={{ minHeight: "1000px", display: "flex", alignItems: "center" }}>
<div style={{ display: "flex", gap: "1em" }}>
<Button variant="secondary">Button</Button>
<Button onClick={() => setOpen(true)}>Open Modal</Button>
<Button variant="secondary">Button</Button>
</div>
<Modal
header={{ heading: "Simple header" }}
open={open}
onClose={() => setOpen(false)}
width={400}
>
<Modal.Body>
This modal has width set to 400.
{/* Nested modal */}
<Modal
open={open2}
onClose={(e) => {
e.stopPropagation(); // onClose wil propagate to parent modal if not stopped
setOpen2(false);
}}
aria-label="Nested modal"
width={800}
>
<Modal.Body>
<BodyLong spacing>
Nesting modals is not recommended, but works in most cases. This
one does not have header or footer, but is bigger than the
parent modal.
</BodyLong>
<BodyLong spacing>Width is set to 800.</BodyLong>
<Button onClick={() => setOpen2(false)}>Close</Button>
</Modal.Body>
</Modal>
</Modal.Body>
<Modal.Footer>
<Button onClick={() => setOpen2(true)}>Primary</Button>
<Button variant="secondary" onClick={() => setOpen(false)}>
Cancel
</Button>
</Modal.Footer>
</Modal>
</div>
);
};
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 Small = () => (
<Modal open width="small" header={{ heading: "Simple header" }}>
<Modal.Body>Lorem ipsum dolor sit amet.</Modal.Body>
</Modal>
);
export const Medium = () => (
<Modal open width="medium" header={{ heading: "Simple header" }}>
<Modal.Body>Lorem ipsum dolor sit amet.</Modal.Body>
</Modal>
);
+6 -2
View File
@@ -16,10 +16,12 @@ import React, {
useCallback,
useMemo,
useRef,
useContext,
} from "react";
import { mergeRefs } from "..";
import { useClientLayoutEffect, useEventListener } from "../util";
import PopoverContent, { PopoverContentType } from "./PopoverContent";
import { ModalContext } from "../modal/ModalContext";
export interface PopoverProps extends HTMLAttributes<HTMLDivElement> {
/**
@@ -117,13 +119,15 @@ export const Popover = forwardRef<HTMLDivElement, PopoverProps>(
onClose,
placement = "top",
offset,
strategy: userStrategy = "absolute",
strategy: userStrategy,
bubbleEscape = false,
...rest
},
ref
) => {
const arrowRef = useRef<HTMLDivElement | null>(null);
const isInModal = useContext(ModalContext) !== null;
const chosenStrategy = userStrategy ?? (isInModal ? "fixed" : "absolute");
const {
x,
@@ -135,7 +139,7 @@ export const Popover = forwardRef<HTMLDivElement, PopoverProps>(
placement: flPlacement,
middlewareData: { arrow: { x: arrowX, y: arrowY } = {} },
} = useFloating({
strategy: userStrategy,
strategy: chosenStrategy,
placement,
open: open,
onOpenChange: onClose,
+1 -6
View File
@@ -2,13 +2,9 @@ import React, { createContext, useContext } from "react";
export interface ProviderContextType {
/**
* Global root-element to attach portals to (Modal, Tooltip)
* Global root-element to attach portals to (Tooltip)
*/
rootElement?: HTMLElement;
/**
* Global config for appElement (Modal)
*/
appElement?: HTMLElement;
}
export const ProviderContext = createContext<ProviderContextType | undefined>(
@@ -18,7 +14,6 @@ export const ProviderContext = createContext<ProviderContextType | undefined>(
export interface ProviderProps {
children?: React.ReactNode;
rootElement?: HTMLElement;
appElement?: HTMLElement;
}
export const useProvider = () => useContext(ProviderContext);
+1 -5
View File
@@ -1,7 +1,6 @@
import React, { forwardRef, useContext } from "react";
import React, { forwardRef } from "react";
import cl from "clsx";
import { BodyShort } from "..";
import { TableContext } from "./Table";
export interface DataCellProps
extends React.TdHTMLAttributes<HTMLTableCellElement> {
@@ -19,8 +18,6 @@ export interface DataCellType
export const DataCell: DataCellType = forwardRef(
({ className, children = "", align, ...rest }, ref) => {
const context = useContext(TableContext);
return (
<BodyShort
as="td"
@@ -28,7 +25,6 @@ export const DataCell: DataCellType = forwardRef(
className={cl("navds-table__data-cell", className, {
[`navds-table__data-cell--align-${align}`]: align,
})}
size={context?.size}
{...rest}
>
{children}
@@ -96,6 +96,7 @@ export const ExpandableRow: ExpandableRowType = forwardRef(
"navds-table__expandable-row--open": isOpen,
"navds-table__expandable-row--expansion-disabled":
expansionDisabled,
"navds-table__expandable-row--clickable": expandOnRowClick,
})}
onClick={(e) => {
!expansionDisabled && expandOnRowClick && onRowClick(e);
@@ -110,8 +111,8 @@ export const ExpandableRow: ExpandableRowType = forwardRef(
>
{!expansionDisabled && (
<button
type="button"
className="navds-table__toggle-expand-button"
type="button"
aria-controls={id}
aria-expanded={isOpen}
onClick={expansionHandler}
+1 -5
View File
@@ -1,6 +1,5 @@
import React, { forwardRef, useContext } from "react";
import cl from "clsx";
import { TableContext } from "./Table";
import React, { forwardRef } from "react";
export interface HeaderCellProps
extends React.ThHTMLAttributes<HTMLTableCellElement> {
@@ -19,14 +18,11 @@ export interface HeaderCellType
export const HeaderCell: HeaderCellType = forwardRef(
({ className, children, align, ...rest }, ref) => {
const context = useContext(TableContext);
return (
<th
ref={ref}
className={cl("navds-table__header-cell", "navds-label", className, {
[`navds-table__header-cell--align-${align}`]: align,
"navds-label--small": context?.size === "small",
})}
{...rest}
>
+3 -4
View File
@@ -16,10 +16,10 @@ export interface SortState {
export interface TableProps
extends React.TableHTMLAttributes<HTMLTableElement> {
/**
* Changes padding
* Changes padding around Cells
* @default "medium"
*/
size?: "medium" | "small";
size?: "large" | "medium" | "small";
/**
* Zebra striped table
* @default false
@@ -49,7 +49,6 @@ export interface TableType
}
export interface TableContextProps {
size: "medium" | "small";
onSortChange?: (sortKey: string) => void;
sort?: SortState;
}
@@ -100,7 +99,7 @@ export const Table = forwardRef(
},
ref
) => (
<TableContext.Provider value={{ size, onSortChange, sort }}>
<TableContext.Provider value={{ onSortChange, sort }}>
<table
{...rest}
ref={ref}
@@ -36,8 +36,44 @@ export const Expandable = () => {
);
};
export const ExpandableLarge = () => {
const [open, setOpen] = useState(false);
return (
<Table size="large">
<Table.Header>
<Table.Row>
<Table.HeaderCell />
{columns.map(({ key, name }) => (
<Table.HeaderCell key={key}>{name}</Table.HeaderCell>
))}
</Table.Row>
</Table.Header>
<Table.Body>
{data.slice(0, 1).map((data) => (
<Table.ExpandableRow content={data.content} key={data.name}>
{columns.map(({ key }) => (
<Table.DataCell key={key}>{data[key]}</Table.DataCell>
))}
</Table.ExpandableRow>
))}
{data.slice(1, 2).map((data) => (
<Table.ExpandableRow
content={data.content}
key={data.name}
open={open}
onOpenChange={(open) => setOpen(open)}
>
{columns.map(({ key }) => (
<Table.DataCell key={key}>{data[key]}</Table.DataCell>
))}
</Table.ExpandableRow>
))}
</Table.Body>
</Table>
);
};
export const ExpandableSmall = () => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const [open, setOpen] = useState(false);
return (
<Table size="small">
@@ -77,7 +77,10 @@ export const NoShadeOnHover = () => <TableComponent shadeOnHover={false} />;
export const ZebraNoShadeOnHover = () => (
<TableComponent zebraStripes shadeOnHover={false} />
);
export const Small = () => <TableComponent size="small" />;
export const SizeLarge = () => <TableComponent size="large" />;
export const SizeMedium = () => <TableComponent size="medium" />;
export const SizeSmall = () => <TableComponent size="small" />;
export const Buttons = () => <TableComponent size="small" button />;
export const WithDivs = () => {
+12
View File
@@ -1,5 +1,17 @@
# @navikt/ds-tailwind
## 5.0.3
## 5.0.2
## 5.0.1
## 5.0.0
## 4.12.1
## 4.12.0
## 4.11.2
## 4.11.1
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-tailwind",
"version": "4.11.2",
"version": "5.0.3",
"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": "^4.11.2",
"@navikt/ds-tokens": "^5.0.3",
"@types/jest": "^29.0.0",
"color": "4.2.3",
"jest": "^29.0.0",
+24
View File
@@ -1,5 +1,29 @@
# @navikt/ds-tokens
## 5.0.3
### Patch Changes
- ConfirmationPanel: Fikset opplesing av feilmelding ([#2199](https://github.com/navikt/aksel/pull/2199))
- Tokens: `--a-icon-alt-3` er nå i synk med figma ([#2199](https://github.com/navikt/aksel/pull/2199))
## 5.0.2
## 5.0.1
## 5.0.0
### Major Changes
- Gray: Gråfarger er nå mindre varme ([#2092](https://github.com/navikt/aksel/pull/2092))
- Action: Selected-varianter av action er nå 'blue' ('deepblue' før) ([#2092](https://github.com/navikt/aksel/pull/2092))
## 4.12.1
## 4.12.0
## 4.11.2
## 4.11.1
+61 -58
View File
@@ -75,28 +75,28 @@
{ "name": "--a-nav-red", "value": "rgba(195, 0, 0, 1)" }
],
"global-gray": [
{ "name": "--a-gray-50", "value": "rgba(247, 247, 247, 1)" },
{ "name": "--a-gray-100", "value": "rgba(241, 241, 241, 1)" },
{ "name": "--a-gray-200", "value": "rgba(229, 229, 229, 1)" },
{ "name": "--a-gray-300", "value": "rgba(207, 207, 207, 1)" },
{ "name": "--a-gray-400", "value": "rgba(176, 176, 176, 1)" },
{ "name": "--a-gray-500", "value": "rgba(143, 143, 143, 1)" },
{ "name": "--a-gray-600", "value": "rgba(112, 112, 112, 1)" },
{ "name": "--a-gray-700", "value": "rgba(89, 89, 89, 1)" },
{ "name": "--a-gray-800", "value": "rgba(64, 64, 64, 1)" },
{ "name": "--a-gray-900", "value": "rgba(38, 38, 38, 1)" }
{ "name": "--a-gray-50", "value": "rgba(242, 243, 245, 1)" },
{ "name": "--a-gray-100", "value": "rgba(236, 238, 240, 1)" },
{ "name": "--a-gray-200", "value": "rgba(224, 227, 230, 1)" },
{ "name": "--a-gray-300", "value": "rgba(203, 207, 213, 1)" },
{ "name": "--a-gray-400", "value": "rgba(170, 176, 186, 1)" },
{ "name": "--a-gray-500", "value": "rgba(131, 140, 154, 1)" },
{ "name": "--a-gray-600", "value": "rgba(103, 111, 123, 1)" },
{ "name": "--a-gray-700", "value": "rgba(82, 89, 98, 1)" },
{ "name": "--a-gray-800", "value": "rgba(66, 71, 79, 1)" },
{ "name": "--a-gray-900", "value": "rgba(35, 38, 42, 1)" }
],
"global-grayalpha": [
{ "name": "--a-grayalpha-50", "value": "rgba(0, 0, 0, 0.03)" },
{ "name": "--a-grayalpha-100", "value": "rgba(0, 0, 0, 0.05)" },
{ "name": "--a-grayalpha-200", "value": "rgba(0, 0, 0, 0.1)" },
{ "name": "--a-grayalpha-300", "value": "rgba(0, 0, 0, 0.19)" },
{ "name": "--a-grayalpha-400", "value": "rgba(0, 0, 0, 0.31)" },
{ "name": "--a-grayalpha-500", "value": "rgba(0, 0, 0, 0.44)" },
{ "name": "--a-grayalpha-600", "value": "rgba(0, 0, 0, 0.56)" },
{ "name": "--a-grayalpha-700", "value": "rgba(0, 0, 0, 0.65)" },
{ "name": "--a-grayalpha-800", "value": "rgba(0, 0, 0, 0.75)" },
{ "name": "--a-grayalpha-900", "value": "rgba(0, 0, 0, 0.85)" }
{ "name": "--a-grayalpha-50", "value": "rgba(38, 55, 89, 0.06)" },
{ "name": "--a-grayalpha-100", "value": "rgba(18, 43, 68, 0.08)" },
{ "name": "--a-grayalpha-200", "value": "rgba(17, 41, 64, 0.13)" },
{ "name": "--a-grayalpha-300", "value": "rgba(7, 26, 54, 0.21)" },
{ "name": "--a-grayalpha-400", "value": "rgba(5, 23, 51, 0.34)" },
{ "name": "--a-grayalpha-500", "value": "rgba(2, 20, 49, 0.49)" },
{ "name": "--a-grayalpha-600", "value": "rgba(2, 15, 34, 0.6)" },
{ "name": "--a-grayalpha-700", "value": "rgba(1, 11, 24, 0.68)" },
{ "name": "--a-grayalpha-800", "value": "rgba(3, 11, 22, 0.75)" },
{ "name": "--a-grayalpha-900", "value": "rgba(2, 5, 9, 0.87)" }
],
"global-blue": [
{ "name": "--a-blue-50", "value": "rgba(230, 240, 255, 1)" },
@@ -196,17 +196,17 @@
],
"semantic-border-default": [
{ "name": "--a-border-default", "value": "rgba(0, 0, 0, 0.44)" },
{ "name": "--a-border-strong", "value": "rgba(0, 0, 0, 0.65)" },
{ "name": "--a-border-divider", "value": "rgba(0, 0, 0, 0.19)" },
{ "name": "--a-border-subtle", "value": "rgba(0, 0, 0, 0.1)" },
{ "name": "--a-border-subtle-hover", "value": "rgba(0, 0, 0, 0.19)" },
{ "name": "--a-border-on-inverted", "value": "rgba(229, 229, 229, 1)" },
{ "name": "--a-border-on-inverted-subtle", "value": "rgba(89, 89, 89, 1)" }
{ "name": "--a-border-default", "value": "rgba(2, 20, 49, 0.49)" },
{ "name": "--a-border-strong", "value": "rgba(1, 11, 24, 0.68)" },
{ "name": "--a-border-divider", "value": "rgba(7, 26, 54, 0.21)" },
{ "name": "--a-border-subtle", "value": "rgba(7, 26, 54, 0.21)" },
{ "name": "--a-border-subtle-hover", "value": "rgba(5, 23, 51, 0.34)" },
{ "name": "--a-border-on-inverted", "value": "rgba(224, 227, 230, 1)" },
{ "name": "--a-border-on-inverted-subtle", "value": "rgba(82, 89, 98, 1)" }
],
"semantic-border-action": [
{ "name": "--a-border-action", "value": "rgba(0, 103, 197, 1)" },
{ "name": "--a-border-action-selected", "value": "rgba(0, 91, 130, 1)" },
{ "name": "--a-border-action-selected", "value": "rgba(0, 69, 156, 1)" },
{ "name": "--a-border-selected", "value": "rgba(0, 103, 197, 1)" }
],
"semantic-border-status": [
@@ -228,19 +228,19 @@
{ "name": "--a-border-alt-3", "value": "rgba(0, 91, 130, 1)" }
],
"semantic-text": [
{ "name": "--a-text-default", "value": "rgba(38, 38, 38, 1)" },
{ "name": "--a-text-subtle", "value": "rgba(0, 0, 0, 0.65)" },
{ "name": "--a-text-default", "value": "rgba(35, 38, 42, 1)" },
{ "name": "--a-text-subtle", "value": "rgba(1, 11, 24, 0.68)" },
{ "name": "--a-text-visited", "value": "rgba(99, 70, 137, 1)" },
{ "name": "--a-text-danger", "value": "rgba(195, 0, 0, 1)" },
{ "name": "--a-text-action", "value": "rgba(0, 103, 197, 1)" },
{ "name": "--a-text-action-selected", "value": "rgba(0, 91, 130, 1)" },
{ "name": "--a-text-action-selected", "value": "rgba(0, 69, 156, 1)" },
{
"name": "--a-text-action-on-action-subtle",
"value": "rgba(0, 86, 180, 1)"
},
{ "name": "--a-text-on-warning", "value": "rgba(38, 38, 38, 1)" },
{ "name": "--a-text-on-info", "value": "rgba(38, 38, 38, 1)" },
{ "name": "--a-text-on-alt-2", "value": "rgba(38, 38, 38, 1)" },
{ "name": "--a-text-on-warning", "value": "rgba(35, 38, 42, 1)" },
{ "name": "--a-text-on-info", "value": "rgba(35, 38, 42, 1)" },
{ "name": "--a-text-on-alt-2", "value": "rgba(35, 38, 42, 1)" },
{ "name": "--a-text-on-inverted", "value": "rgba(255, 255, 255, 1)" },
{ "name": "--a-text-on-neutral", "value": "rgba(255, 255, 255, 1)" },
{ "name": "--a-text-on-action", "value": "rgba(255, 255, 255, 1)" },
@@ -251,15 +251,15 @@
],
"semantic-surface-default": [
{ "name": "--a-surface-default", "value": "rgba(255, 255, 255, 1)" },
{ "name": "--a-surface-hover", "value": "rgba(0, 0, 0, 0.05)" },
{ "name": "--a-surface-active", "value": "rgba(0, 0, 0, 0.1)" },
{ "name": "--a-surface-hover", "value": "rgba(18, 43, 68, 0.08)" },
{ "name": "--a-surface-active", "value": "rgba(17, 41, 64, 0.13)" },
{ "name": "--a-surface-selected", "value": "rgba(230, 240, 255, 1)" },
{ "name": "--a-surface-subtle", "value": "rgba(247, 247, 247, 1)" },
{ "name": "--a-surface-subtle", "value": "rgba(242, 243, 245, 1)" },
{ "name": "--a-surface-transparent", "value": "rgba(255, 255, 255, 0)" },
{ "name": "--a-surface-backdrop", "value": "rgba(0, 0, 0, 0.65)" },
{ "name": "--a-surface-inverted-hover", "value": "rgba(64, 64, 64, 1)" },
{ "name": "--a-surface-inverted-active", "value": "rgba(89, 89, 89, 1)" },
{ "name": "--a-surface-inverted", "value": "rgba(38, 38, 38, 1)" }
{ "name": "--a-surface-backdrop", "value": "rgba(2, 20, 49, 0.49)" },
{ "name": "--a-surface-inverted-hover", "value": "rgba(66, 71, 79, 1)" },
{ "name": "--a-surface-inverted-active", "value": "rgba(82, 89, 98, 1)" },
{ "name": "--a-surface-inverted", "value": "rgba(35, 38, 42, 1)" }
],
"semantic-surface-action": [
{ "name": "--a-surface-action-subtle", "value": "rgba(230, 240, 255, 1)" },
@@ -269,24 +269,27 @@
},
{ "name": "--a-surface-action", "value": "rgba(0, 103, 197, 1)" },
{ "name": "--a-surface-action-hover", "value": "rgba(0, 86, 180, 1)" },
{ "name": "--a-surface-action-active", "value": "rgba(0, 91, 130, 1)" },
{ "name": "--a-surface-action-selected", "value": "rgba(0, 91, 130, 1)" },
{ "name": "--a-surface-action-active", "value": "rgba(0, 69, 156, 1)" },
{ "name": "--a-surface-action-selected", "value": "rgba(0, 69, 156, 1)" },
{
"name": "--a-surface-action-selected-hover",
"value": "rgba(0, 80, 119, 1)"
"value": "rgba(0, 52, 125, 1)"
}
],
"semantic-surface-neutral": [
{ "name": "--a-surface-neutral-subtle", "value": "rgba(0, 0, 0, 0.05)" },
{ "name": "--a-surface-neutral-subtle", "value": "rgba(18, 43, 68, 0.08)" },
{
"name": "--a-surface-neutral-subtle-hover",
"value": "rgba(0, 0, 0, 0.1)"
"value": "rgba(17, 41, 64, 0.13)"
},
{ "name": "--a-surface-neutral-moderate", "value": "rgba(0, 0, 0, 0.1)" },
{ "name": "--a-surface-neutral", "value": "rgba(89, 89, 89, 1)" },
{ "name": "--a-surface-neutral-hover", "value": "rgba(64, 64, 64, 1)" },
{ "name": "--a-surface-neutral-selected", "value": "rgba(38, 38, 38, 1)" },
{ "name": "--a-surface-neutral-active", "value": "rgba(38, 38, 38, 1)" }
{
"name": "--a-surface-neutral-moderate",
"value": "rgba(17, 41, 64, 0.13)"
},
{ "name": "--a-surface-neutral", "value": "rgba(82, 89, 98, 1)" },
{ "name": "--a-surface-neutral-hover", "value": "rgba(66, 71, 79, 1)" },
{ "name": "--a-surface-neutral-selected", "value": "rgba(35, 38, 42, 1)" },
{ "name": "--a-surface-neutral-active", "value": "rgba(35, 38, 42, 1)" }
],
"semantic-surface-success": [
{ "name": "--a-surface-success-subtle", "value": "rgba(204, 241, 214, 1)" },
@@ -356,18 +359,18 @@
},
{
"name": "--a-bg-subtle",
"value": "rgba(241, 241, 241, 1)"
"value": "rgba(236, 238, 240, 1)"
}
],
"semantic-icon-defaults": [
{ "name": "--a-icon-default", "value": "rgba(38, 38, 38, 1)" },
{ "name": "--a-icon-subtle", "value": "rgba(0, 0, 0, 0.65)" },
{ "name": "--a-icon-default", "value": "rgba(35, 38, 42, 1)" },
{ "name": "--a-icon-subtle", "value": "rgba(1, 11, 24, 0.68)" },
{ "name": "--a-icon-on-neutral", "value": "rgba(255, 255, 255, 1)" },
{ "name": "--a-icon-on-inverted", "value": "rgba(255, 255, 255, 1)" }
],
"semantic-icon-action": [
{ "name": "--a-icon-action", "value": "rgba(0, 103, 197, 1)" },
{ "name": "--a-icon-action-selected", "value": "rgba(0, 91, 130, 1)" },
{ "name": "--a-icon-action-selected", "value": "rgba(0, 69, 156, 1)" },
{
"name": "--a-icon-action-on-action-subtle",
"value": "rgba(0, 86, 180, 1)"
@@ -380,14 +383,14 @@
{ "name": "--a-icon-danger", "value": "rgba(195, 0, 0, 1)" },
{ "name": "--a-icon-on-danger", "value": "rgba(255, 255, 255, 1)" },
{ "name": "--a-icon-warning", "value": "rgba(199, 115, 0, 1)" },
{ "name": "--a-icon-on-warning", "value": "rgba(38, 38, 38, 1)" },
{ "name": "--a-icon-on-warning", "value": "rgba(35, 38, 42, 1)" },
{ "name": "--a-icon-info", "value": "rgba(35, 107, 125, 1)" },
{ "name": "--a-icon-on-info", "value": "rgba(38, 38, 38, 1)" }
{ "name": "--a-icon-on-info", "value": "rgba(35, 38, 42, 1)" }
],
"semantic-icon-alt": [
{ "name": "--a-icon-alt-1", "value": "rgba(99, 70, 137, 1)" },
{ "name": "--a-icon-alt-2", "value": "rgba(127, 137, 0, 1)" },
{ "name": "--a-icon-alt-3", "value": "rgba(51, 128, 165, 1)" }
{ "name": "--a-icon-alt-3", "value": "rgba(0, 91, 130, 1)" }
],
"semantic-data-surface": [
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-tokens",
"version": "4.11.2",
"version": "5.0.3",
"description": "Design-tokens for NAV designsystem",
"author": "Aksel | NAV designsystem team",
"keywords": [
+20 -20
View File
@@ -13,66 +13,66 @@
},
"gray": {
"50": {
"value": "rgba(247, 247, 247, 1)"
"value": "rgba(242, 243, 245, 1)"
},
"100": {
"value": "rgba(241, 241, 241, 1)"
"value": "rgba(236, 238, 240, 1)"
},
"200": {
"value": "rgba(229, 229, 229, 1)"
"value": "rgba(224, 227, 230, 1)"
},
"300": {
"value": "rgba(207, 207, 207, 1)"
"value": "rgba(203, 207, 213, 1)"
},
"400": {
"value": "rgba(176, 176, 176, 1)"
"value": "rgba(170, 176, 186, 1)"
},
"500": {
"value": "rgba(143, 143, 143, 1)"
"value": "rgba(131, 140, 154, 1)"
},
"600": {
"value": "rgba(112, 112, 112, 1)"
"value": "rgba(103, 111, 123, 1)"
},
"700": {
"value": "rgba(89, 89, 89, 1)"
"value": "rgba(82, 89, 98, 1)"
},
"800": {
"value": "rgba(64, 64, 64, 1)"
"value": "rgba(66, 71, 79, 1)"
},
"900": {
"value": "rgba(38, 38, 38, 1)"
"value": "rgba(35, 38, 42, 1)"
}
},
"grayalpha": {
"50": {
"value": "rgba(0, 0, 0, 0.03)"
"value": "rgba(38, 55, 89, 0.06)"
},
"100": {
"value": "rgba(0, 0, 0, 0.05)"
"value": "rgba(18, 43, 68, 0.08)"
},
"200": {
"value": "rgba(0, 0, 0, 0.1)"
"value": "rgba(17, 41, 64, 0.13)"
},
"300": {
"value": "rgba(0, 0, 0, 0.19)"
"value": "rgba(7, 26, 54, 0.21)"
},
"400": {
"value": "rgba(0, 0, 0, 0.31)"
"value": "rgba(5, 23, 51, 0.34)"
},
"500": {
"value": "rgba(0, 0, 0, 0.44)"
"value": "rgba(2, 20, 49, 0.49)"
},
"600": {
"value": "rgba(0, 0, 0, 0.56)"
"value": "rgba(2, 15, 34, 0.6)"
},
"700": {
"value": "rgba(0, 0, 0, 0.65)"
"value": "rgba(1, 11, 24, 0.68)"
},
"800": {
"value": "rgba(0, 0, 0, 0.75)"
"value": "rgba(3, 11, 22, 0.75)"
},
"900": {
"value": "rgba(0, 0, 0, 0.85)"
"value": "rgba(2, 5, 9, 0.87)"
}
},
"blue": {
+10 -10
View File
@@ -15,7 +15,7 @@
},
"action": {
"selected": {
"value": "{a.deepblue.500.value}"
"value": "{a.blue.700.value}"
},
"on": {
"action": {
@@ -89,7 +89,7 @@
"value": "{a.transparent.value}"
},
"backdrop": {
"value": "{a.grayalpha.700.value}"
"value": "{a.grayalpha.500.value}"
},
"inverted": {
"hover": {
@@ -115,13 +115,13 @@
"value": "{a.blue.600.value}"
},
"active": {
"value": "{a.deepblue.500.value}"
"value": "{a.blue.700.value}"
},
"selected": {
"hover": {
"value": "{a.deepblue.600.value}"
"value": "{a.blue.800.value}"
},
"@": { "value": "{a.deepblue.500.value}" }
"@": { "value": "{a.blue.700.value}" }
},
"@": {
"value": "{a.blue.500.value}"
@@ -233,11 +233,11 @@
"value": "{a.grayalpha.300.value}"
},
"subtle": {
"hover": { "value": "{a.grayalpha.300.value}" },
"@": { "value": "{a.grayalpha.200.value}" }
"hover": { "value": "{a.grayalpha.400.value}" },
"@": { "value": "{a.grayalpha.300.value}" }
},
"action": {
"selected": { "value": "{a.deepblue.500.value}" },
"selected": { "value": "{a.blue.700.value}" },
"@": { "value": "{a.blue.500.value}" }
},
"selected": {
@@ -287,7 +287,7 @@
"value": "{a.grayalpha.700.value}"
},
"action": {
"selected": { "value": "{a.deepblue.500.value}" },
"selected": { "value": "{a.blue.700.value}" },
"on": {
"action": {
"subtle": {
@@ -316,7 +316,7 @@
"value": "{a.limegreen.700.value}"
},
"alt3": {
"value": "{a.deepblue.400.value}"
"value": "{a.deepblue.500.value}"
},
"on": {
"neutral": {
+242 -133
View File
@@ -1,5 +1,114 @@
# Changelog
## 5.0.3
### @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))
- Tokens: `--a-icon-alt-3` er nå i synk med figma ([#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))
## 5.0.2
### @navikt/ds-react
- :label: Modal: Bedre type for `width` ([#2191](https://github.com/navikt/aksel/pull/2191))
## 5.0.1
### @navikt/ds-css
- Checkbox: Hotfix da checkmark ikke var sentrert hvis man brukte tailwind ([`90db7dd0c`](https://github.com/navikt/aksel/commit/90db7dd0c120c16a387d3169c05c1f33dc694323))
## 5.0.0
### @navikt/ds-react
- 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)
- :boom: Provider: `appElement` er fjernet
<!---->
- Table: ExpandableRow har oppdatert knapp for å matche Accordion og ExpansionCard ([#2178](https://github.com/navikt/aksel/pull/2178))
- Table: Har lagt til ny size: 'large'. ([#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))
- Action: Selected-varianter av action er nå 'blue' ('deepblue' før) ([#2092](https://github.com/navikt/aksel/pull/2092))
### @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))
## 4.12.1
### @navikt/ds-react
- Added red border to Combobox in error state ([#2184](https://github.com/navikt/aksel/pull/2184))
### @navikt/ds-css
- Added red border to Combobox in error state ([#2184](https://github.com/navikt/aksel/pull/2184))
## 4.12.0
### @navikt/ds-react
- Combobox: La til støtte for feilmeldinger i Combobox ([#2182](https://github.com/navikt/aksel/pull/2182))
- 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))
- Combobox: Kjører nå 'onChange' + 'onClear' når input blir reset programmatisk ([#2183](https://github.com/navikt/aksel/pull/2183))
- Combobox: Fikset custom-options i singleselect ([#2180](https://github.com/navikt/aksel/pull/2180))
- Combobox: Fjernet unødvendige 'onClear'-calls når man velger verdier ([#2170](https://github.com/navikt/aksel/pull/2170))
- Checkbox: Checkmark er nå SVG-ikon og ikke Base64 ([#2171](https://github.com/navikt/aksel/pull/2171))
- 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
@@ -14,16 +123,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))
@@ -34,6 +133,16 @@
- 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
@@ -42,19 +151,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
@@ -62,36 +167,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
@@ -100,11 +209,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))
@@ -116,10 +225,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))
@@ -130,28 +235,36 @@
- 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))
@@ -160,10 +273,6 @@
- - 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
@@ -178,25 +287,29 @@
## 4.4.0
### @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-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-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))
## 4.3.0
### @navikt/ds-react
- Fikset klassenavn brukt for popover i Datepicker og Monthpicker ([PR](https://github.com/navikt/aksel/pull/2041))
## 4.3.0
- Popover og Helptext har nå luft mot siden av skjerm på mindre flater ([PR](https://github.com/navikt/aksel/pull/2069))
### @navikt/ds-css
@@ -206,20 +319,16 @@
- 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
@@ -236,16 +345,16 @@
## 4.1.5
### @navikt/aksel-icons
- Nye ikoner `ChevronRightLast` og `ChevronLeftLast` ([PR](https://github.com/navikt/aksel/pull/2054))
### @navikt/ds-react
- `OverridableComponent` fungerer nå med komponenter som allerede bruker 'as'-prop. ([PR](https://github.com/navikt/aksel/pull/2051))
- Popover: `bubbleEscape`-prop tilbyr muligheten for escape-events til å sendes opp gjennom dom-treet. ([PR](https://github.com/navikt/aksel/pull/2052))
### @navikt/aksel-icons
- Nye ikoner `ChevronRightLast` og `ChevronLeftLast` ([PR](https://github.com/navikt/aksel/pull/2054))
## 4.1.4
### @navikt/ds-css
@@ -276,22 +385,6 @@
- Deprecated klassenavn `navds-chips--icon-left` ([PR](https://github.com/navikt/aksel/pull/2035))
### @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/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-react
- Chips: `neutral` og `action`-varianter for Chips.Toggle. `checkmark`-prop for Chips.Toggle ([PR](https://github.com/navikt/aksel/pull/2035))
@@ -304,13 +397,29 @@
- Accordion: La til `indent`-prop ([PR](https://github.com/navikt/aksel/pull/2027))
### @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-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-tailwind
- :tada: Fargetokens for datavisualisering. ([PR](https://github.com/navikt/aksel/pull/2032))
## 4.0.0
### @navikt/aksel
### @navikt/ds-react
- Codemod for migrering av Datepicker/Monthpicker ut av Beta. `npx @navikt/aksel codemod v4-date` ([PR](https://github.com/navikt/aksel/pull/2026))
- Datepicker og Monthpicker er ute av beta. [Guide](https://aksel.nav.no/grunnleggende/kode/migrering#h4ef68ae743b0) ([PR](https://github.com/navikt/aksel/pull/2026))
- 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))
- 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
@@ -322,11 +431,11 @@
- Styling for flyttede komponenter finnes nå på CDN [Guide](https://aksel.nav.no/grunnleggende/kode/css-import)
### @navikt/ds-react
### @navikt/aksel
- Datepicker og Monthpicker er ute av beta. [Guide](https://aksel.nav.no/grunnleggende/kode/migrering#h4ef68ae743b0) ([PR](https://github.com/navikt/aksel/pull/2026))
- Codemod for migrering av Datepicker/Monthpicker ut av Beta. `npx @navikt/aksel codemod v4-date` ([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))
- 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))
## 3.4.2
@@ -346,11 +455,11 @@
- Stylelint-pakke for Aksel ([PR](https://github.com/navikt/aksel/pull/1973))
### @navikt/ds-css
### @navikt/ds-react
- Tag: `moderate`-variant ([PR](https://github.com/navikt/aksel/pull/2010))
### @navikt/ds-react
### @navikt/ds-css
- Tag: `moderate`-variant ([PR](https://github.com/navikt/aksel/pull/2010))
@@ -374,11 +483,9 @@
## 3.2.3
### @navikt/ds-css
### @navikt/aksel-icons
- 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))
- Nye ikoner `FileParagraph` og `FilePlus` ([PR](https://github.com/navikt/aksel/pull/1998))
### @navikt/ds-react
@@ -386,9 +493,11 @@
- CopyButton: `xsmall`-variant for bruk i tabeller ([PR](https://github.com/navikt/aksel/pull/1994))
### @navikt/aksel-icons
### @navikt/ds-css
- Nye ikoner `FileParagraph` og `FilePlus` ([PR](https://github.com/navikt/aksel/pull/1998))
- 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
@@ -410,6 +519,10 @@
## 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))
@@ -418,10 +531,6 @@
- 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
@@ -432,16 +541,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
@@ -536,14 +645,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
@@ -572,14 +681,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
@@ -658,14 +767,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
@@ -696,6 +805,12 @@
## 2.2.0
### @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-tokens
- Justeringer av semantiske fargetokens, statusfarger nå mer tydelig ([PR](https://github.com/navikt/aksel/pull/1787))
@@ -708,12 +823,6 @@
- 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))
## 2.1.7
### @navikt/ds-react
@@ -758,14 +867,14 @@
## 2.1.0
### @navikt/ds-tokens
- Fikset feil danger-hover token ([PR](https://github.com/navikt/aksel/pull/1665))
### @navikt/ds-react
- Datepicker/Monthpicker: år med 2 siffer i input fungerer nå
### @navikt/ds-tokens
- Fikset feil danger-hover token ([PR](https://github.com/navikt/aksel/pull/1665))
## 2.0.18
### @navikt/ds-react
@@ -828,23 +937,23 @@
## 2.0.0
### @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))
### @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
### @navikt/ds-tailwind
- Fonter blir bruk i all typografi blir nå lastet fra CDN
- Alle token er oppdatert til nytt format. ([Guide](https://aksel.nav.no/grunnleggende/kode/migrering#h76f47744d112))
## 1.5.10
@@ -890,16 +999,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
@@ -926,10 +1035,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
@@ -952,6 +1057,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
+5 -5
View File
@@ -15,11 +15,11 @@
"website"
],
"dependencies": {
"@navikt/aksel-icons": "^4.11.2",
"@navikt/ds-css": "^4.11.2",
"@navikt/ds-react": "^4.11.2",
"@navikt/ds-tailwind": "^4.11.2",
"@navikt/ds-tokens": "^4.11.2"
"@navikt/aksel-icons": "^5.0.3",
"@navikt/ds-css": "^5.0.3",
"@navikt/ds-react": "^5.0.3",
"@navikt/ds-tailwind": "^5.0.3",
"@navikt/ds-tokens": "^5.0.3"
},
"devDependencies": {
"prettier-plugin-tailwindcss": "^0.2.3"
@@ -1,13 +1,11 @@
import { MenuHamburgerIcon, XMarkIcon } from "@navikt/aksel-icons";
import { MenuHamburgerIcon } from "@navikt/aksel-icons";
import { Button } from "@navikt/ds-react";
import { Modal } from "@navikt/ds-react";
import cl from "clsx";
import Link from "next/link";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import ReactModal from "react-modal";
import { useState } from "react";
import { logNav } from "../..";
import styles from "./header.module.css";
const LinkElement = ({ name, href, onClick }) => {
const { asPath } = useRouter();
@@ -41,14 +39,9 @@ const LinkElement = ({ name, href, onClick }) => {
export const Hamburger = () => {
const [open, setOpen] = useState(false);
useEffect(() => {
ReactModal.setAppElement("#__next");
}, []);
return (
<>
<div className="z-[1050] mr-0 grid h-full place-content-center">
<div className="mr-2 grid h-full place-content-center lg:hidden">
<button
onClick={() => setOpen(!open)}
aria-expanded={open}
@@ -60,25 +53,16 @@ export const Hamburger = () => {
aria-label="Åpne meny"
/>
</button>
<ReactModal
isOpen={open}
onRequestClose={() => setOpen(false)}
aria={{ modal: true }}
overlayClassName={styles.modalOverlay}
contentLabel="Meny"
className="bg-surface-default shadow-xlarge absolute left-4 right-4 top-0 block rounded px-11 py-14 sm:left-auto sm:right-6 sm:w-96 sm:max-w-[90%] lg:hidden"
>
</div>
<Modal
open={open}
onClose={() => setOpen(false)}
header={{ heading: "Meny" }}
width="small"
>
<Modal.Body>
<nav aria-label="hovedmeny">
<ul>
<Button
variant="tertiary"
onClick={() => setOpen(false)}
className={cl(
styles.akselTertiaryButton,
"absolute right-2 top-2"
)}
icon={<XMarkIcon title="lukk meny" fontSize="1.5rem" />}
/>
<LinkElement
onClick={() => setOpen(false)}
name="God praksis"
@@ -106,8 +90,8 @@ export const Hamburger = () => {
/>
</ul>
</nav>
</ReactModal>
</div>
</Modal.Body>
</Modal>
</>
);
};
@@ -114,9 +114,8 @@ export const Header = ({
<div className="z-[1050] ml-auto mr-4 flex justify-center lg:ml-0 lg:mr-0">
<GlobalSearch />
</div>
<div className="mr-2 h-full lg:hidden">
<Hamburger />
</div>
<Hamburger />
</div>
</header>
</>
@@ -1,14 +1,4 @@
/* stylelint-disable aksel/design-token-no-global-override */
.modalOverlay {
z-index: var(--a-z-index-modal);
@apply bg-surface-backdrop fixed inset-0 grid place-items-center lg:hidden;
}
.modalOverlaySearch {
z-index: 3000;
@apply bg-deepblue-900/20 fixed inset-0 grid place-items-center;
}
.akselTertiaryButton {
--a-text-action: var(--a-deepblue-800);
--a-text-action-on-action-subtle: var(--a-deepblue-800);
@@ -50,13 +50,7 @@ export const SearchForm = () => {
onSubmit={(e) => e.preventDefault()}
className="bg-surface-default grid w-full gap-2"
>
<div className="flex flex-row-reverse items-center gap-1 p-1">
<Button
variant="tertiary-neutral"
icon={<XMarkIcon title="Lukk søk" />}
onClick={close}
type="button"
/>
<div className="flex items-center gap-1 p-1">
<Search
label={
<span className="flex items-center gap-2">
@@ -83,6 +77,15 @@ export const SearchForm = () => {
placeholder="Søk gjennom hele aksel"
className="border-none pr-0 focus:shadow-none"
/>
<div className="grid h-full w-12 place-content-center">
<Button
variant="tertiary-neutral"
icon={<XMarkIcon title="Lukk søk" />}
onClick={close}
type="button"
size="small"
/>
</div>
</div>
{chipsToShow.length !== 0 && (
<Chips className="px-4 pb-4 md:px-10">
@@ -1,9 +1,6 @@
import { MagnifyingGlassIcon } from "@navikt/aksel-icons";
import { Button } from "@navikt/ds-react";
import cl from "clsx";
import { useContext, useEffect } from "react";
import ReactModal from "react-modal";
import styles from "../header.module.css";
import { Button, Modal } from "@navikt/ds-react";
import { useContext } from "react";
import { Error } from "./Error";
import { SearchForm } from "./Form";
import { Results } from "./Results";
@@ -13,10 +10,6 @@ export const Search = () => {
const { open, setOpen } = useContext(SearchContext);
const { close } = useContext(SearchNavigationContext);
useEffect(() => {
ReactModal.setAppElement("#__next");
}, []);
return (
<>
<Button
@@ -35,19 +28,17 @@ export const Search = () => {
>
Søk
</Button>
<ReactModal
isOpen={open}
onRequestClose={close}
aria={{ modal: true }}
contentLabel="Søk"
className="bg-surface-default focus-visible:shadow-focus animate-searchModal shadow-xlarge absolute inset-0 mx-auto my-auto flex h-[90%] max-h-[52rem] w-full max-w-[90%] flex-col overflow-x-hidden rounded-lg focus:outline-none md:h-[80%] lg:max-w-4xl"
overlayClassName={cl(styles.modalOverlaySearch, "backdrop-blur-[2px]")}
htmlOpenClassName="overflow-hidden"
<Modal
open={open}
onClose={close}
aria-label="Søk"
className="h-[90%] max-h-[52rem] "
width="medium"
>
<Error />
<SearchForm />
<Results />
</ReactModal>
</Modal>
</>
);
};
@@ -44,11 +44,11 @@ const GetIcon = (s: string) => {
const getText = (s: string) => {
switch (s) {
case "do":
return "Do: ";
return "Gjør: ";
case "dont":
return "Don't: ";
return "Unngå: ";
case "warning":
return "Fare på ferde: ";
return "Utfordrende: ";
default:
return "";
}
@@ -74,7 +74,7 @@ const Element = ({ block }: { block: DoDontT["blokker"][number] }) => {
)}
aria-hidden
>
<span className="absolute right-[10px] z-10 translate-y-2">
<span className="absolute right-[2px] z-10">
{GetIcon(block.variant)}
</span>
</div>
@@ -9,11 +9,10 @@ import { SuggestionBlock } from "components/website-modules/suggestionblock";
import Fuse from "fuse.js";
import Head from "next/head";
import Link from "next/link";
import { useRouter } from "next/router";
import { useEffect, useMemo, useRef, useState } from "react";
import { categorizeIcons, getFillIcon } from "./utils";
import { useMemo, useRef, useState } from "react";
import { IconSidebar } from "./Sidebar";
import { TitleLinks } from "./TitleLinks";
import { categorizeIcons, getFillIcon } from "./utils";
const fuseStroke = new Fuse(
Object.values(meta).filter((x) => x.variant.toLowerCase() === "stroke"),
@@ -70,16 +69,14 @@ export const IconPage = ({ name }: { name: string }) => {
);
}, [toggle, query, strokeIcons, fillIcons]);
const router = useRouter();
useEffect(() => {
hideModal && Modal.setAppElement("#__next");
}, [hideModal]);
const subCategoryWordLength = Math.max(
...categories.map((cat) =>
Math.max(...cat.sub_categories.map((sub) => sub.sub_category.length))
)
const subCategoryWordLength = useMemo(
() =>
Math.max(
...categorizeIcons(strokeIcons).map((cat) =>
Math.max(...cat.sub_categories.map((sub) => sub.sub_category.length))
)
),
[strokeIcons]
);
return (
@@ -260,15 +257,9 @@ export const IconPage = ({ name }: { name: string }) => {
{!hideModal && (
<Modal
open={!!name}
onClose={() =>
router.push(`/ikoner#${name}`, undefined, {
shallow: true,
})
}
closeButton={false}
aria-modal
aria-label={`${name} ikon`}
className="bg-surface-default focus-visible:shadow-focus z-modal absolute block h-full overflow-y-auto rounded px-2 py-6 focus:outline-none sm:px-6"
className="rounded px-2 py-6 sm:px-6"
onClose={() => null}
>
{name && <IconSidebar name={name} focusRef={focusRef} />}
</Modal>
@@ -1,5 +1,5 @@
export const getImage = (key: string, variant: "OG" | "thumbnail") => {
const bloggOptions = 11;
const bloggOptions = 9;
const hash = Math.abs(
key.split("").reduce(function (a, b) {
-1
View File
@@ -54,7 +54,6 @@
"prismjs": "^1.27.0",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-modal": "3.15.1",
"react-responsive-masonry": "^2.1.7",
"sanity": "3.15.1",
"sanity-plugin-asset-source-unsplash": "^1.1.0",
@@ -36,14 +36,14 @@ export const Example = () => {
const onToggleSelected = (
option: string,
isSelected: boolean,
isAddedByUser: boolean
isCustomOption: boolean
) => {
if (isSelected) {
setSelectedOptions([...selectedOptions, option]);
} else {
setSelectedOptions(selectedOptions.filter((o) => o !== option));
}
if (isAddedByUser) {
if (isCustomOption) {
mockPersistUserAddedValues(option, isSelected);
}
};
@@ -0,0 +1,36 @@
import { UNSAFE_Combobox } from "@navikt/ds-react";
import { withDsExample } from "components/website-modules/examples/withDsExample";
const initialOptions = [
"banana",
"apple",
"tangerine",
"pear",
"grape",
"kiwi",
"mango",
"passion fruit",
"pineapple",
"strawberry",
"watermelon",
"grape fruit",
];
export const Example = () => {
return (
<div>
<UNSAFE_Combobox
label="Hva er din favorittfrukt?"
options={initialOptions}
error="Du må velge en favorittfrukt."
/>
</div>
);
};
export default withDsExample(Example, "static");
export const args = {
index: 0,
desc: "Ved Single Select velger brukeren kun ett valg fra nedtrekkslisten.",
};
@@ -1,44 +1,25 @@
import {
DatePicker,
useDatepicker,
Button,
Modal,
Heading,
} from "@navikt/ds-react";
import { DatePicker, useDatepicker, Button, Modal } from "@navikt/ds-react";
import { withDsExample } from "components/website-modules/examples/withDsExample";
import { useEffect, useState } from "react";
import { useRef } from "react";
import format from "date-fns/format";
import nbLocale from "date-fns/locale/nb";
const Example = () => {
const [open, setOpen] = useState(false);
const ref = useRef<HTMLDialogElement>(null);
const { datepickerProps, inputProps, selectedDay } = useDatepicker({
fromDate: new Date("Aug 23 2019"),
onDateChange: console.log,
});
useEffect(() => {
Modal.setAppElement("#__next");
}, []);
return (
<>
<Button onClick={() => setOpen(true)}>Åpne modal</Button>
<Button onClick={() => ref.current?.showModal()}>Åpne modal</Button>
<Modal
open={open}
aria-label="Modal demo"
onClose={() => setOpen((x) => !x)}
shouldCloseOnEsc={!datepickerProps.open}
aria-labelledby="modal-heading"
>
<Modal.Content className="min-w-96 max-w-full">
<Heading spacing level="1" size="large" id="modal-heading">
Heading
</Heading>
<div className="min-h-96">
<DatePicker {...datepickerProps} strategy="fixed">
<Modal ref={ref} header={{ heading: "Heading" }}>
<Modal.Body>
<div className="min-h-96 min-w-96 max-w-full">
<DatePicker {...datepickerProps}>
<DatePicker.Input {...inputProps} label="Velg dato" />
</DatePicker>
<div className="pt-4">
@@ -46,7 +27,7 @@ const Example = () => {
format(selectedDay, "dd.MM.yyyy", { locale: nbLocale })}
</div>
</div>
</Modal.Content>
</Modal.Body>
</Modal>
</>
);
@@ -61,5 +42,4 @@ export const Demo = {
export const args = {
index: 11,
desc: "Ved bruk av datepicker i Modal er det viktig at 'Escape' ikke lukker selve modalen hvis datepicker er åpen. Bruk 'shouldCloseOnEsc' for å toggle dette når datepicker er åpen.",
};
@@ -41,6 +41,6 @@ export const Demo = {
};
export const args = {
index: 2,
index: 3,
desc: "Husk å sette aria-hidden på ikonet.",
};
@@ -25,6 +25,6 @@ export const Demo = {
};
export const args = {
index: 1,
desc: "Husk å sette riktig nivå/tag på List elementet hvis du bruker en tittel.",
index: 2,
desc: "Husk å sette riktig heading-nivå/tag hvis du bruker en title.",
};
@@ -0,0 +1,32 @@
import { List } from "@navikt/ds-react";
import { withDsExample } from "components/website-modules/examples/withDsExample";
const Example = () => {
return (
<List as="ul" size="small" title="Det kan også være aktuelt hvis du:">
<List.Item>
står i fare for å miste jobben etter å ha vært sykmeldt helt eller
delvis i 12 måneder, eller
</List.Item>
<List.Item>
har full eller gradert uføretrygd, men ønsker å jobbe. NAV må ha vurdert
om andre arbeidsmarkedstiltak og virkemidler er aktuelle.
</List.Item>
<List.Item>
er en arbeidssøker med varig og vesentlig nedsatt arbeidsevne som kan
bli ansatt i en vanlig jobb.
</List.Item>
</List>
);
};
export default withDsExample(Example);
/* Storybook story */
export const Demo = {
render: Example,
};
export const args = {
index: 1,
};
@@ -32,5 +32,5 @@ export const Demo = {
};
export const args = {
index: 3,
index: 4,
};
@@ -0,0 +1,47 @@
import { BodyLong, Button, Heading, Modal } from "@navikt/ds-react";
import { withDsExample } from "components/website-modules/examples/withDsExample";
import { useState } from "react";
const Example = () => {
const [open, setOpen] = useState(false);
return (
<>
<Button onClick={() => setOpen(true)}>Åpne modal</Button>
<Modal
open={open}
onClose={() => setOpen(false)}
aria-labelledby="modal-heading"
>
<Modal.Header>
<Heading level="1" size="large" id="modal-heading">
Laborum proident id ullamco
</Heading>
</Modal.Header>
<Modal.Body>
<BodyLong>
Culpa aliquip ut cupidatat laborum minim quis ex in aliqua. Qui
incididunt dolor do ad ut. Incididunt eiusmod nostrud deserunt duis
laborum. Proident aute culpa qui nostrud velit adipisicing minim.
Consequat aliqua aute dolor do sit Lorem nisi mollit velit. Aliqua
exercitation non minim minim pariatur sunt laborum ipsum.
Exercitation nostrud est laborum magna non non aliqua qui esse.
</BodyLong>
</Modal.Body>
</Modal>
</>
);
};
export default withDsExample(Example);
/* Storybook story */
export const Demo = {
render: Example,
};
export const args = {
index: 3,
desc: "Headeren tilbys som en egen komponent for de tilfellene du trenger mer fleksiblitet enn header-propen gir deg. Begge varianter har mulighet for å skjule lukkeknappen.",
};
@@ -1,31 +1,17 @@
import { BodyLong, Button, Heading, Modal } from "@navikt/ds-react";
import { BodyLong, Button, Modal } from "@navikt/ds-react";
import { withDsExample } from "components/website-modules/examples/withDsExample";
import { useEffect, useState } from "react";
import { useRef } from "react";
const Example = () => {
const [open, setOpen] = useState(false);
useEffect(() => {
Modal.setAppElement("#__next");
}, []);
const ref = useRef<HTMLDialogElement>(null);
return (
<>
<Button onClick={() => setOpen(true)}>Åpne modal</Button>
<Modal
open={open}
aria-label="Modal demo"
onClose={() => setOpen((x) => !x)}
aria-labelledby="modal-heading"
>
<Modal.Content>
<Heading spacing level="1" size="large" id="modal-heading">
Laborum proident id ullamco
</Heading>
<Heading spacing level="2" size="medium">
Excepteur labore nostrud incididunt exercitation.
</Heading>
<BodyLong spacing>
<div className="py-16">
<Button onClick={() => ref.current?.showModal()}>Åpne modal</Button>
<Modal ref={ref} header={{ heading: "Overskrift" }}>
<Modal.Body>
<BodyLong>
Culpa aliquip ut cupidatat laborum minim quis ex in aliqua. Qui
incididunt dolor do ad ut. Incididunt eiusmod nostrud deserunt duis
laborum. Proident aute culpa qui nostrud velit adipisicing minim.
@@ -33,9 +19,28 @@ const Example = () => {
exercitation non minim minim pariatur sunt laborum ipsum.
Exercitation nostrud est laborum magna non non aliqua qui esse.
</BodyLong>
</Modal.Content>
</Modal.Body>
<Modal.Footer>
<Button type="button" onClick={() => ref.current?.close()}>
Primær
</Button>
<Button
type="button"
variant="secondary"
onClick={() => ref.current?.close()}
>
Sekundær
</Button>
<Button
type="button"
variant="tertiary"
onClick={() => ref.current?.close()}
>
Tertiær
</Button>
</Modal.Footer>
</Modal>
</>
</div>
);
};
@@ -0,0 +1,43 @@
import { Button, Modal, TextField } from "@navikt/ds-react";
import { withDsExample } from "components/website-modules/examples/withDsExample";
import { useRef } from "react";
const Example = () => {
const ref = useRef<HTMLDialogElement>(null);
return (
<div className="py-12">
<Button onClick={() => ref.current?.showModal()}>Åpne modal</Button>
<Modal ref={ref} header={{ heading: "Skjema" }} width={400}>
<Modal.Body>
<form method="dialog" id="skjema" onSubmit={(e) => alert("onSubmit")}>
<TextField label="Har du noen tilbakemeldinger?" />
</form>
</Modal.Body>
<Modal.Footer>
<Button form="skjema">Send</Button>
<Button
type="button"
variant="secondary"
onClick={() => ref.current?.close()}
>
Avbryt
</Button>
</Modal.Footer>
</Modal>
</div>
);
};
export default withDsExample(Example);
/* Storybook story */
export const Demo = {
render: Example,
};
export const args = {
index: 4,
desc: 'Det er ikke støtte for å ha <form> som direkte barn av <Modal>. Legg <form> enten rundt hele modalen eller inni <Modal.Body>. Sistnevnte gjør det mulig å sette method="dialog", som gjør at modalen lukkes ved submit.',
};
@@ -1,40 +1,43 @@
import { BodyLong, Button, Heading, Modal } from "@navikt/ds-react";
import { BodyLong, Button, Modal } from "@navikt/ds-react";
import { withDsExample } from "components/website-modules/examples/withDsExample";
import { useEffect, useState } from "react";
import { useState } from "react";
const Example = () => {
const [open, setOpen] = useState(false);
useEffect(() => {
Modal.setAppElement("#__next");
}, []);
return (
<>
<Button onClick={() => setOpen(true)}>Åpne modal</Button>
<Modal
open={open}
aria-label="Modal demo"
onClose={() => setOpen((x) => !x)}
closeButton={false}
aria-labelledby="modal-heading"
onClose={() => setOpen(false)}
header={{
heading: "Er du sikker?",
size: "small",
closeButton: false,
}}
width="small"
>
<Modal.Content>
<Heading spacing level="1" size="large" id="modal-heading">
Laborum proident id ullamco
</Heading>
<Heading spacing level="2" size="medium">
Excepteur labore nostrud incididunt exercitation.
</Heading>
<BodyLong spacing>
<Modal.Body>
<BodyLong>
Culpa aliquip ut cupidatat laborum minim quis ex in aliqua. Qui
incididunt dolor do ad ut. Incididunt eiusmod nostrud deserunt duis
laborum. Proident aute culpa qui nostrud velit adipisicing minim.
Consequat aliqua aute dolor do sit Lorem nisi mollit velit. Aliqua
exercitation non minim minim pariatur sunt laborum ipsum.
Exercitation nostrud est laborum magna non non aliqua qui esse.
laborum.
</BodyLong>
</Modal.Content>
</Modal.Body>
<Modal.Footer>
<Button type="button" variant="danger" onClick={() => setOpen(false)}>
Ja, jeg er sikker
</Button>
<Button
type="button"
variant="secondary"
onClick={() => setOpen(false)}
>
Avbryt
</Button>
</Modal.Footer>
</Modal>
</>
);
@@ -48,5 +51,6 @@ export const Demo = {
};
export const args = {
index: 1,
index: 2,
desc: "Lukkeknappen i headeren kan skjules, men da må du ha en annen knapp som lukker modalen.",
};
@@ -0,0 +1,64 @@
import { FileIcon } from "@navikt/aksel-icons";
import { BodyLong, Button, Modal } from "@navikt/ds-react";
import { withDsExample } from "components/website-modules/examples/withDsExample";
import { useRef } from "react";
const Example = () => {
const ref = useRef<HTMLDialogElement>(null);
return (
<div className="py-16">
<Button onClick={() => ref.current?.showModal()}>Åpne modal</Button>
<Modal
ref={ref}
header={{
label: "Modal demo",
icon: <FileIcon aria-hidden />,
heading: "Overskrift",
}}
>
<Modal.Body>
<BodyLong>
Culpa aliquip ut cupidatat laborum minim quis ex in aliqua. Qui
incididunt dolor do ad ut. Incididunt eiusmod nostrud deserunt duis
laborum. Proident aute culpa qui nostrud velit adipisicing minim.
Consequat aliqua aute dolor do sit Lorem nisi mollit velit. Aliqua
exercitation non minim minim pariatur sunt laborum ipsum.
Exercitation nostrud est laborum magna non non aliqua qui esse.
</BodyLong>
</Modal.Body>
<Modal.Footer>
<Button type="button" onClick={() => ref.current?.close()}>
Primær
</Button>
<Button
type="button"
variant="secondary"
onClick={() => ref.current?.close()}
>
Sekundær
</Button>
<Button
type="button"
variant="tertiary"
onClick={() => ref.current?.close()}
>
Tertiær
</Button>
</Modal.Footer>
</Modal>
</div>
);
};
export default withDsExample(Example);
/* Storybook story */
export const Demo = {
render: Example,
};
export const args = {
index: 1,
};
Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 14 KiB

@@ -1,16 +0,0 @@
<svg width="1200" height="675" viewBox="0 0 1200 675" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1090_180341)">
<rect width="1200" height="675" fill="#8669C6"/>
<path d="M935 548L268 -119L932.999 -118.001L1600 549L935 548Z" stroke="#D6C3EE" stroke-width="8" stroke-linejoin="round"/>
<path d="M933.34 -117.66L266.68 549L933.34 1215.66L1600 549L933.34 -117.66Z" stroke="#D6C3EE" stroke-width="8" stroke-linejoin="round"/>
<path d="M267 -118L-400 549L266 549L933 -118L267 -118Z" stroke="#D6C3EE" stroke-width="8" stroke-linejoin="round"/>
<path d="M266.666 -117.666L-400 549L266.666 1215.67L933.332 549L266.666 -117.666Z" stroke="#D6C3EE" stroke-width="8" stroke-linejoin="round"/>
<path d="M934 548.999L267 1216L933 1216L1600 548.999L934 548.999Z" stroke="#D6C3EE" stroke-width="8" stroke-linejoin="round"/>
<path d="M267 1216L-400 549L265.999 548.999L933 1216L267 1216Z" stroke="#D6C3EE" stroke-width="8" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_1090_180341">
<rect width="1200" height="675" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

@@ -1,16 +0,0 @@
<svg width="1200" height="675" viewBox="0 0 1200 675" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1090_180382)">
<rect width="1200" height="675" fill="#EC4899"/>
<path d="M781.57 336.458L420.056 -25.056L780.486 -24.5145L1142 337L781.57 336.458Z" stroke="#F9A8D4" stroke-width="8" stroke-linejoin="round"/>
<path d="M780.67 -24.3296L419.34 337L780.67 698.33L1142 337L780.67 -24.3296Z" stroke="#F9A8D4" stroke-width="8" stroke-linejoin="round"/>
<path d="M419.514 -24.514L58.0001 337L418.972 337L780.486 -24.514L419.514 -24.514Z" stroke="#F9A8D4" stroke-width="8" stroke-linejoin="round"/>
<path d="M419.334 -24.333L58.001 337L419.334 698.333L780.667 337L419.334 -24.333Z" stroke="#F9A8D4" stroke-width="8" stroke-linejoin="round"/>
<path d="M781.028 337L419.514 698.514L780.486 698.514L1142 337L781.028 337Z" stroke="#F9A8D4" stroke-width="8" stroke-linejoin="round"/>
<path d="M419.514 698.514L58.0003 337L418.972 337L780.486 698.514L419.514 698.514Z" stroke="#F9A8D4" stroke-width="8" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_1090_180382">
<rect width="1200" height="675" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

Loaded 100 of 110 files, more files were not shown because too many files have changed in this diff. Show more