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
github-actions[bot]andgithub-actions[bot] dd03078e33 Version Packages (#2169)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-08-15 14:12:40 +02:00
Halvor Haugan 72321e3846 💄 ToggleGroup: fjern semibold fra selected button (#2167) 2023-08-15 13:42:20 +02:00
Ken 1cd8ac93ed 🎨 Oppdatert ikonside med forslag fra Slack (#2164) 2023-08-15 10:50:54 +02:00
Ken 48d6f14103 🐛 Fikset for restriktiv validering av sidetittel i sanity 2023-08-15 10:15:11 +02:00
github-actions[bot]andgithub-actions[bot] 44da7fcdc6 Version Packages (#2160)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-08-14 14:43:22 +02:00
Ken e68ab5cb2c 🎨 Fikset hardkodet border-radius i diverse komponenter (#2159) 2023-08-14 14:40:47 +02:00
Ken c721b392f9 ♻️ Chores Sanity, Storybook
- Oppdatert Storybook, Sanity
- Fikset validering for InputWithCounter i Sanity
- Fikset Accordion-stories rekkefølge
- La til egne knapper for Desktop/mobil-view for eksempler
- Bruker DS-knapper i kode-eksempler på Aksel
2023-08-14 13:40:10 +02:00
Ken 1d0d0ce665 Merge branch 'main' of https://github.com/navikt/aksel 2023-08-13 20:56:42 +02:00
Ken 9d02940543 🔥 Fjernet console.log 2023-08-13 20:50:54 +02:00
Ken 7ff63a2c63 🐛 Fikset validering for sidetittel i sanity 2023-08-13 20:50:13 +02:00
github-actions[bot]andgithub-actions[bot] 49f33a78d8 Version Packages (#2151)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-08-11 15:59:23 +02:00
Ken 06aaec70f0 📝 Oppdatert changeset for HGrid 2023-08-11 15:43:40 +02:00
Ken 5fbf230e47 🎨 Justert antall viste artikkler i masonary 2023-08-11 13:24:26 +02:00
Vegard Haugstvedt 53265c247e It is not possible to close combobox dropdown after clicking a chip (#2155)
* Fixes bug where it is not possible to close dropdown by pressing escape or clicking outside the combobox after clicking the chips to de-select an option

In short, the focus were lost/reset when the chip disappears, but now is reset to the input instead.
Also fixed issue where clicking the chip while virtual focus is in the dropdown caused a click on both the option and the chip, causing it to be de-selected and instantly re-selected.

* Updated "remove selected"-test to also check that closing the dropdown after clicking a chip is possible.

* Added changeset
2023-08-11 12:38:22 +02:00
Ken d78f33df79 🚚 Markert gammelt grid som deprecated (#2153) 2023-08-11 12:37:02 +02:00
Ken 4375c55ab6 📝 Oppdatert storybook template i workflow 2023-08-11 09:45:02 +02:00
Christian SkrøvsethandKen 0de8238ecf Add semibold (600) fonts (#2150)
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
2023-08-11 09:01:59 +02:00
KenandHalvor Haugan 647b14a238 [Primitives] HGrid (#1838)
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
2023-08-10 15:38:58 +02:00
Ken 64901adccb 🎨 Oppdatert List-dokumentasjon, fikset bruk av ikoner med text (#2149) 2023-08-10 13:06:48 +02:00
Ken 49ddb2094c Hotfix: Fikset visited state for Link og vertical-align for SVG i tailwind 2023-08-09 17:06:15 +02:00
github-actions[bot]andgithub-actions[bot] 8a5f209735 Version Packages (#2148)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-08-09 16:30:12 +02:00
Ken d3bd1202ed Hotfix: Fikset visited state for Link og SVG-reset ved bruk av tailwind 2023-08-09 16:19:49 +02:00
github-actions[bot]andgithub-actions[bot] 4bf6027518 Version Packages (#2147)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-08-09 15:34:18 +02:00
Halvor Haugan c8e203c523 ✏️ Fiks skrivefeil i suggestionblock (#2146) 2023-08-09 15:15:41 +02:00
267799ca0e update links (#2093)
* style links with inline

* tweak css spacing for inline links with SVG

* add break-all prop to link

* wrap link in span to allow for spacing

* more obvious spacing example

* remove spacing prop from link

* implement pr review comments

* add random icon visual fuzz testing in stories

* remove console.log

* changeset

* add examples to aksel.nav.no

* 🎨 make inline-flex version opt-in, improve prop naming

* 🐛 Fixed comments

* 🎨 Oppdatert props

* 🎨 refactor links, add examples (remove underline & inline)

* change is no longer major (pr review comment)

Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>

* cleaner changeset (pr review comment)

Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>

* norwegian changeset (pr review comment)

Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>

* update changeset (pr review comment)

Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>

* use neutral token for neutral link variant

Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>

* use neutral token for neutral link variant

Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>

* Update .changeset/proud-tigers-share.md

---------

Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
Co-authored-by: Ken <ken.aleksander@gmail.com>
2023-08-09 15:00:44 +02:00
github-actions[bot]andgithub-actions[bot] 42934af3a1 Version Packages (#2145)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-08-09 14:38:31 +02:00
Vegard Haugstvedt eff8a902c5 Bugfix/combobox bugfixes and tweaks (#2144)
* Clear input field when clicking option in multiselect combobox

Makes the behaviour consistent when clicking with a mouse and when selecting an option with keyboard.

* MultiSelect with "add new" example should actually have the addnewValues prop set...

* Expose through onToggleSelected whether selected values in combobox are added by the user or not.

* Add the additional affordance of bold font-weight for selected options in combobox.

Done to account for screen magnifiers on screens that don't show the background color, where the checkmark is out-of-sight.

* Avoid "jumping" when selecting option in multiselect, stemming from the chips being slightly larger than the padding allowed for (equal to the width of the border)

* Added changeset and updated yarn.lock

* Do not use focus-visible inside fallback for when focus-visible is not supported

* Add fallback focus-styling using JS and a helper class for browsers not supporting :has (Firefox)

* Fix for onBlur in ComboboxWrapper. Without tabindex="-1" event.relatedTarget in onBlur will be null, and we can't check that focus has entirely moved outside the Combobox.
2023-08-09 14:15:26 +02:00
Ken d34a8f45ef 🐛 aria-live fix for button (#2143) 2023-08-09 11:01:45 +02:00
github-actions[bot]andgithub-actions[bot] d1ceb40439 Version Packages (#2141)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-08-07 13:58:17 +02:00
e033b34c3b Table component: adding click event on expandable table rows (#2127)
Co-authored-by: Vegard Haugstvedt <it.vegard@gmail.com>
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
2023-08-07 13:42:07 +02:00
Halvor Haugan de3991d0f0 📝 Fiks skrivefeil i Select-eksempler (#2140) 2023-08-04 18:30:41 +02:00
github-actions[bot]andgithub-actions[bot] 9641215506 Version Packages (#2139)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-08-04 13:43:34 +02:00
Ken 8a82e1c1f6 [Ny komponent] Stack, Spacer (#2040) 2023-08-04 13:13:46 +02:00
192 changed files with 6717 additions and 2060 deletions

No files matched your search

+3 -3
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
@@ -96,7 +96,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}})
### [Endringer til review](${{steps.chromatic_tests.outputs.url}}): ${{steps.chromatic_tests.outputs.changeCount}}
+30
View File
@@ -1,5 +1,35 @@
# @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
## 4.11.0
## 4.10.2
## 4.10.1
## 4.10.0
## 4.9.1
## 4.9.0
## 4.8.0
## 4.7.4
## 4.7.3
@@ -58,7 +58,7 @@
.icon-button {
min-height: 3rem;
min-width: 3rem;
border-radius: 2px;
border-radius: var(--a-border-radius-small);
display: grid;
place-content: center;
background-color: var(--a-surface-default);
@@ -1,9 +1,9 @@
import meta from "@navikt/aksel-icons/metadata";
const subCategorizeIcons = (
icons: typeof meta[1][]
): { sub_category: string; icons: typeof meta[1][] }[] => {
const categories: { sub_category: string; icons: typeof meta[1][] }[] = [];
icons: (typeof meta)[1][]
): { sub_category: string; icons: (typeof meta)[1][] }[] => {
const categories: { sub_category: string; icons: (typeof meta)[1][] }[] = [];
for (const icon of icons) {
const i = categories.findIndex(
@@ -19,14 +19,14 @@ const subCategorizeIcons = (
};
export const categorizeIcons = (
icons: typeof meta[1][]
icons: (typeof meta)[1][]
): {
category: string;
sub_categories: { sub_category: string; icons: typeof meta[1][] }[];
sub_categories: { sub_category: string; icons: (typeof meta)[1][] }[];
}[] => {
const categories: {
category: string;
icons: typeof meta[1][];
icons: (typeof meta)[1][];
}[] = [];
for (const icon of icons) {
@@ -42,14 +42,14 @@ export const categorizeIcons = (
.map((x) => ({ ...x, sub_categories: subCategorizeIcons(x.icons) }));
};
const noFill = (icon: typeof meta[1], icons: typeof meta[1][]) => {
const noFill = (icon: (typeof meta)[1], icons: (typeof meta)[1][]) => {
const foundFill = icons.find(
(x) => x.name.endsWith("Fill") && x.name.replace("Fill", "") === icon.name
);
return !foundFill;
};
export const getFillIcon = (icons: typeof meta[1][]) => {
export const getFillIcon = (icons: (typeof meta)[1][]) => {
return icons.filter(
(x, _, z) => x.variant.toLowerCase() === "fill" || noFill(x, z)
);
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/aksel-icons",
"version": "4.7.4",
"version": "5.0.3",
"author": "Aksel | NAV",
"homepage": "https://aksel.nav.no/ikoner",
"repository": {
+30
View File
@@ -1,5 +1,35 @@
# @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
## 4.11.0
## 4.10.2
## 4.10.1
## 4.10.0
## 4.9.1
## 4.9.0
## 4.8.0
## 4.7.4
## 4.7.3
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/aksel-stylelint",
"version": "4.7.4",
"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.7.4",
"@navikt/ds-tokens": "^4.7.4",
"@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",
},
];
+105
View File
@@ -1,5 +1,110 @@
# @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
- Updated dependencies [[`72321e384`](https://github.com/navikt/aksel/commit/72321e3846f785318ebdba9f8e06cf13b09cd524)]:
- @navikt/ds-css@4.11.2
## 4.11.1
### Patch Changes
- Updated dependencies [[`e68ab5cb2`](https://github.com/navikt/aksel/commit/e68ab5cb2cd91c2f0234c864ee71821ed07a8203)]:
- @navikt/ds-css@4.11.1
## 4.11.0
### Patch Changes
- Updated dependencies [[`64901adcc`](https://github.com/navikt/aksel/commit/64901adccb8f19fbe06cdab4a70d47978e90e343), [`0de8238ec`](https://github.com/navikt/aksel/commit/0de8238ecf22e9f1fbc0e1b0cfe7a5cd7254901e), [`647b14a23`](https://github.com/navikt/aksel/commit/647b14a23888f82bacc4806d3afaacb5e39178fa)]:
- @navikt/ds-css@4.11.0
## 4.10.2
### Patch Changes
- Updated dependencies []:
- @navikt/ds-css@4.10.2
## 4.10.1
### Patch Changes
- Updated dependencies [[`d3bd1202e`](https://github.com/navikt/aksel/commit/d3bd1202eddb23dd3f1f09c3e4086ff3b19737d6)]:
- @navikt/ds-css@4.10.1
## 4.10.0
### Patch Changes
- Updated dependencies [[`267799ca0`](https://github.com/navikt/aksel/commit/267799ca0ecc0c549e159f94124befb654e29331)]:
- @navikt/ds-css@4.10.0
## 4.9.1
### Patch Changes
- Updated dependencies [[`eff8a902c`](https://github.com/navikt/aksel/commit/eff8a902c50fd5ebc56616e866aa0a61b9eb5efc)]:
- @navikt/ds-css@4.9.1
## 4.9.0
### Patch Changes
- Updated dependencies [[`e033b34c3`](https://github.com/navikt/aksel/commit/e033b34c3b85fb98dd11dd0300bb20075e1b06e8)]:
- @navikt/ds-css@4.9.0
## 4.8.0
### Patch Changes
- Updated dependencies [[`8a82e1c1f`](https://github.com/navikt/aksel/commit/8a82e1c1f66ea38fca77b25d74b14926bd817dd9)]:
- @navikt/ds-css@4.8.0
## 4.7.4
### Patch Changes
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/aksel",
"version": "4.7.4",
"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.7.4",
"@navikt/ds-css": "5.0.3",
"axios": "1.3.6",
"chalk": "4.1.0",
"clipboardy": "^2.3.0",
+105
View File
@@ -1,5 +1,110 @@
# @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
- :lipstick: ToggleGroup: fjern semibold fra selected button ([#2167](https://github.com/navikt/aksel/pull/2167))
## 4.11.1
### Patch Changes
- Border-radius: Fikset hardkodet border-radius i Datepicker, ToggleGroup og Combobox. ([#2159](https://github.com/navikt/aksel/pull/2159))
## 4.11.0
### Minor Changes
- HGrid: Ny komponent 'HGrid' er ute i Beta: [Dokumentasjon](https://aksel.nav.no/komponenter/core/hgrid) ([#1838](https://github.com/navikt/aksel/pull/1838))
### Patch Changes
- 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
### Patch Changes
- Link: Fikset visited farger og inline SVG-reset ved bruk av tailwind
## 4.10.0
### Minor Changes
- Link: La til 'variant', 'underline' og 'inlineText'-prop ([#2093](https://github.com/navikt/aksel/pull/2093))
## 4.9.1
### Patch Changes
- 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
### Minor Changes
- Table: ExpandableRow kan nå åpnes med 'expandOnRowClick'-prop ([#2127](https://github.com/navikt/aksel/pull/2127))
## 4.8.0
### Minor Changes
- 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
### 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);
}
+7
View File
@@ -5,6 +5,13 @@
font-display: swap;
src: url("https://cdn.nav.no/aksel/fonts/SourceSans3-italic.woff2") format("woff2");
}
@font-face {
font-family: "Source Sans Pro";
font-style: italic;
font-weight: 600;
font-display: swap;
src: url("https://cdn.nav.no/aksel/fonts/SourceSans3-italic-latin-semibold") format("woff2");
}
@font-face {
font-family: "Source Sans Pro";
font-style: normal;
+18 -2
View File
@@ -57,7 +57,7 @@ const StyleMappings = {
{
component: "UNSAFE_Combobox",
main: "combobox.css",
dependencies: [typoCss],
dependencies: [typoCss, "chips.css"],
},
{
component: "ConfirmationPanel",
@@ -99,6 +99,10 @@ const StyleMappings = {
main: "help-text.css",
dependencies: ["popover.css"],
},
{
component: "HGrid",
main: "hgrid.css",
},
{ component: "Ingress", main: typoCss },
{
component: "InternalHeader",
@@ -117,7 +121,7 @@ const StyleMappings = {
{
component: "Modal",
main: "modal.css",
dependencies: ["button.css"],
dependencies: [typoCss, "button.css"],
},
{
component: "Pagination",
@@ -134,6 +138,18 @@ const StyleMappings = {
{ component: "Select", main: formCss, dependencies: [typoCss] },
{ component: "Skeleton", main: "skeleton.css", dependencies: [] },
{ component: "Stepper", main: "stepper.css", dependencies: [typoCss] },
{
component: "HStack",
main: "stack.css",
},
{
component: "VStack",
main: "stack.css",
},
{
component: "Spacer",
main: "stack.css",
},
{ component: "Switch", main: formCss, dependencies: [typoCss] },
{ component: "Table", main: "table.css", dependencies: [typoCss] },
{ component: "Tabs", main: "tabs.css", dependencies: [typoCss] },
+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;
}
+1 -1
View File
@@ -233,7 +233,7 @@
text-decoration: none;
border: none;
background: none;
border-radius: 3px;
border-radius: calc(var(--a-border-radius-medium) - 1px);
padding: var(--a-spacing-3);
align-items: center;
justify-content: center;
+36 -3
View File
@@ -9,9 +9,10 @@
position: relative;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
width: 100%;
border: 1px solid var(--a-border-default);
padding: calc(var(--a-spacing-2) - 2px) var(--a-spacing-2);
}
.navds-combobox__wrapper-inner > :first-child {
@@ -31,8 +32,19 @@
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;
}
.navds-combobox__selected-options > li {
@@ -67,6 +79,7 @@
margin: 0;
min-width: 10ch;
width: 100%;
height: 2rem;
}
.navds-combobox__input:focus-visible {
@@ -85,7 +98,7 @@
}
@supports not selector(:focus-visible) {
.navds-combobox__input:focus-visible {
.navds-combobox__input:focus {
outline: none;
border: none;
box-shadow: none;
@@ -101,6 +114,17 @@
}
}
@supports not selector(:has) {
.navds-combobox--focused .navds-combobox__wrapper-inner {
box-shadow: 0 0 0 1px var(--a-surface-default) inset, var(--a-shadow-focus);
box-shadow: var(--a-shadow-focus);
}
.navds-combobox--focused .navds-combobox__wrapper-inner.navds-combobox__wrapper-inner--virtually-unfocused {
box-shadow: none;
}
}
.navds-combobox__button-clear {
border-radius: var(--a-border-radius-medium);
color: var(--ac-combobox-clear-icon, var(--a-text-subtle));
@@ -239,10 +263,19 @@
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));
}
.navds-combobox__list-item--selected p {
font-weight: var(--a-font-weight-bold);
}
.navds-combobox__list-item--selected.navds-combobox__list-item--focus,
.navds-combobox__list-item--selected:hover {
background-color: var(--ac-combobox-list-item-selected-hover-bg, var(--a-surface-action-subtle-hover));
@@ -265,7 +298,7 @@
.navds-combobox__list-item__new-option--focus {
box-shadow: var(--a-shadow-focus) inset, var(--a-border-action) 0 0 0 5px inset;
border-radius: 3px;
border-radius: calc(var(--a-border-radius-medium) - 1px);
}
/* Mobile */
+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);
+55
View File
@@ -0,0 +1,55 @@
.navds-hgrid {
--__ac-hgrid-columns-xs: initial;
--__ac-hgrid-columns-sm: initial;
--__ac-hgrid-columns-md: initial;
--__ac-hgrid-columns-lg: initial;
--__ac-hgrid-columns-xl: initial;
--__ac-hgrid-columns: var(--__ac-hgrid-columns-xs);
--__ac-hgrid-gap-xs: initial;
--__ac-hgrid-gap-sm: initial;
--__ac-hgrid-gap-md: initial;
--__ac-hgrid-gap-lg: initial;
--__ac-hgrid-gap-xl: initial;
--__ac-hgrid-gap: var(--__ac-hgrid-gap-xs);
display: grid;
grid-template-columns: var(--__ac-hgrid-columns);
gap: var(--__ac-hgrid-gap);
}
@media (min-width: 480px) {
.navds-hgrid {
--__ac-hgrid-columns: var(--__ac-hgrid-columns-sm, var(--__ac-hgrid-columns-xs));
--__ac-hgrid-gap: var(--__ac-hgrid-gap-sm, var(--__ac-hgrid-gap-xs));
}
}
@media (min-width: 768px) {
.navds-hgrid {
--__ac-hgrid-columns: var(--__ac-hgrid-columns-md, var(--__ac-hgrid-columns-sm, var(--__ac-hgrid-columns-xs)));
--__ac-hgrid-gap: var(--__ac-hgrid-gap-md, var(--__ac-hgrid-gap-sm, var(--__ac-hgrid-gap-xs)));
}
}
@media (min-width: 1024px) {
.navds-hgrid {
--__ac-hgrid-columns: var(
--__ac-hgrid-columns-lg,
var(--__ac-hgrid-columns-md, var(--__ac-hgrid-columns-sm, var(--__ac-hgrid-columns-xs)))
);
--__ac-hgrid-gap: var(--__ac-hgrid-gap-lg, var(--__ac-hgrid-gap-md, var(--__ac-hgrid-gap-sm, var(--__ac-hgrid-gap-xs))));
}
}
@media (min-width: 1280px) {
.navds-hgrid {
--__ac-hgrid-columns: var(
--__ac-hgrid-columns-xl,
var(--__ac-hgrid-columns-lg, var(--__ac-hgrid-columns-md, var(--__ac-hgrid-columns-sm, var(--__ac-hgrid-columns-xs))))
);
--__ac-hgrid-gap: var(
--__ac-hgrid-gap-xl,
var(--__ac-hgrid-gap-lg, var(--__ac-hgrid-gap-md, var(--__ac-hgrid-gap-sm, var(--__ac-hgrid-gap-xs))))
);
}
}
+2
View File
@@ -14,6 +14,7 @@
@import "guide-panel.css";
@import "form/index.css";
@import "help-text.css";
@import "hgrid.css";
@import "internalheader.css";
@import "link.css";
@import "loader.css";
@@ -29,6 +30,7 @@
@import "link-panel.css";
@import "read-more.css";
@import "skeleton.css";
@import "stack.css";
@import "stepper.css";
@import "table.css";
@import "tabs.css";
+35 -3
View File
@@ -6,15 +6,35 @@
gap: var(--a-spacing-1);
}
.navds-alert .navds-link,
.navds-confirmation-panel .navds-link {
color: var(--a-text-default);
.navds-link.navds-link--inline-text {
display: inline;
}
.navds-link.navds-link--inline-text > svg {
/* stylelint-disable-next-line plugin/declaration-block-no-ignored-properties */
margin: 0.1em 0.1em -0.1em;
transform: translateY(0.05em);
display: inline;
vertical-align: baseline;
}
.navds-link:hover {
text-decoration: none;
}
.navds-link.navds-link--remove-underline {
text-decoration: none;
}
.navds-link.navds-link--remove-underline:hover {
text-decoration: underline;
}
.navds-alert .navds-link,
.navds-confirmation-panel .navds-link {
color: var(--a-text-default);
}
.navds-link:focus-visible {
outline: none;
color: var(--ac-link-focus-text, var(--a-text-on-action));
@@ -45,3 +65,15 @@
color: inherit;
flex-shrink: 0;
}
.navds-link--action {
color: var(--ac-link-action-text, var(--a-text-action));
}
.navds-link--neutral {
color: var(--ac-link-neutral-text, var(--a-text-default));
}
.navds-link--subtle {
color: var(--ac-link-subtle-text, var(--a-text-subtle));
}
+40 -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,15 +44,30 @@
.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);
}
.navds-list__item-marker--icon svg {
flex-shrink: 0;
}
.navds-list ul .navds-list__item-content {
@@ -51,6 +76,7 @@
.navds-list ol {
padding-left: var(--a-spacing-2);
list-style: decimal;
}
.navds-list ol li {
@@ -60,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.7.4",
"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.7.4",
"@navikt/ds-tokens": "^5.0.3",
"cssnano": "6.0.0",
"fast-glob": "3.2.11",
"lodash": "4.17.21",
+59
View File
@@ -0,0 +1,59 @@
.navds-stack {
--__ac-stack-align: initial;
--__ac-stack-justify: initial;
--__ac-stack-direction: initial;
--__ac-stack-wrap: initial;
--__ac-stack-gap-xs: initial;
--__ac-stack-gap-sm: initial;
--__ac-stack-gap-md: initial;
--__ac-stack-gap-lg: initial;
--__ac-stack-gap-xl: initial;
--__ac-stack-gap: var(--__ac-stack-gap-xs);
gap: var(--__ac-stack-gap);
display: flex;
align-items: var(--__ac-stack-align);
justify-content: var(--__ac-stack-justify);
flex-flow: var(--__ac-stack-direction) var(--__ac-stack-wrap);
}
.navds-stack__spacer {
flex: 1;
justify-self: stretch;
align-self: stretch;
}
.navds-stack > .navds-stack__spacer {
margin-block-start: calc(var(--__ac-stack-gap) * -1);
}
.navds-hstack > .navds-stack__spacer {
margin-inline-start: calc(var(--__ac-stack-gap) * -1);
}
@media (min-width: 480px) {
.navds-stack {
--__ac-stack-gap: var(--__ac-stack-gap-sm, var(--__ac-stack-gap-xs));
}
}
@media (min-width: 768px) {
.navds-stack {
--__ac-stack-gap: var(--__ac-stack-gap-md, var(--__ac-stack-gap-sm, var(--__ac-stack-gap-xs)));
}
}
@media (min-width: 1024px) {
.navds-stack {
--__ac-stack-gap: var(--__ac-stack-gap-lg, var(--__ac-stack-gap-md, var(--__ac-stack-gap-sm, var(--__ac-stack-gap-xs))));
}
}
@media (min-width: 1280px) {
.navds-stack {
--__ac-stack-gap: var(
--__ac-stack-gap-xl,
var(--__ac-stack-gap-lg, var(--__ac-stack-gap-md, var(--__ac-stack-gap-sm, var(--__ac-stack-gap-xs))))
);
}
}
+62 -35
View File
@@ -16,7 +16,7 @@
display: table-row;
}
.navds-table__body .navds-table__row--shade-on-hover:hover {
.navds-table__body .navds-table__row--shade-on-hover:not(.navds-table__expandable-row--expansion-disabled):hover {
background-color: var(--ac-table-row-hover, var(--a-bg-subtle));
}
@@ -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,6 +159,10 @@
transition: border-bottom-color 190ms cubic-bezier(0.6, 0.04, 0.98, 0.335);
}
.navds-table__expandable-row.navds-table__expandable-row--clickable:not(.navds-table__expandable-row--expansion-disabled):hover {
cursor: pointer;
}
.navds-table__expandable-row--open .navds-table__data-cell {
border-bottom-color: transparent;
}
@@ -167,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 {
@@ -231,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);
}
+1 -2
View File
@@ -34,7 +34,7 @@
cursor: pointer;
background-color: var(--ac-toggle-group-button-bg, var(--a-surface-transparent));
color: var(--ac-toggle-group-button-text, var(--a-text-default));
border-radius: 2px;
border-radius: var(--a-border-radius-small);
min-width: fit-content;
font-weight: var(--a-font-weight-regular);
}
@@ -113,7 +113,6 @@
.navds-toggle-group__button[aria-checked="true"] {
background-color: var(--ac-toggle-group-selected-bg, var(--a-surface-action-selected));
color: var(--ac-toggle-group-selected-text, var(--a-text-on-action));
font-weight: var(--a-font-weight-bold);
}
.navds-toggle-group--neutral > .navds-toggle-group__button[aria-pressed="true"],
+10 -3
View File
@@ -175,7 +175,10 @@
"--ac-link-focus-border": "--a-border-focus",
"--ac-link-active-text": "--a-text-on-action",
"--ac-link-active-bg": "--a-border-focus",
"--ac-link-active-border": "--a-border-focus"
"--ac-link-active-border": "--a-border-focus",
"--ac-link-action-text": "--a-text-action",
"--ac-link-neutral-text": "--a-text-default",
"--ac-link-subtle-text": "--a-text-subtle"
},
"list": {
"--ac-list-marker-ul-color": "--ac-list-marker-color",
@@ -193,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",
@@ -385,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",
+165
View File
@@ -1,5 +1,170 @@
# @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
- Updated dependencies []:
- @navikt/aksel-icons@4.11.2
## 4.11.1
### Patch Changes
- Updated dependencies []:
- @navikt/aksel-icons@4.11.1
## 4.11.0
### Minor Changes
- HGrid: Ny komponent 'HGrid' er ute i Beta: [Dokumentasjon](https://aksel.nav.no/komponenter/core/hgrid) ([#1838](https://github.com/navikt/aksel/pull/1838))
### Patch Changes
- Fixes bug where combobox list could not be closed after clicking a chip ([#2155](https://github.com/navikt/aksel/pull/2155))
- Grid: Markert som deprecated. Bruk nye 'HGrid' ([#2153](https://github.com/navikt/aksel/pull/2153))
- Updated dependencies []:
- @navikt/aksel-icons@4.11.0
## 4.10.2
### Patch Changes
- Updated dependencies []:
- @navikt/aksel-icons@4.10.2
## 4.10.1
### Patch Changes
- Updated dependencies []:
- @navikt/aksel-icons@4.10.1
## 4.10.0
### Minor Changes
- Link: La til 'variant', 'underline' og 'inlineText'-prop ([#2093](https://github.com/navikt/aksel/pull/2093))
### Patch Changes
- Updated dependencies []:
- @navikt/aksel-icons@4.10.0
## 4.9.1
### Patch Changes
- Button: Fikset aria-live bug der knapp alltid ble lest opp av skjermleser ved render ([#2143](https://github.com/navikt/aksel/pull/2143))
- 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))
- Updated dependencies []:
- @navikt/aksel-icons@4.9.1
## 4.9.0
### Minor Changes
- Table: ExpandableRow kan nå åpnes med 'expandOnRowClick'-prop ([#2127](https://github.com/navikt/aksel/pull/2127))
### Patch Changes
- Updated dependencies []:
- @navikt/aksel-icons@4.9.0
## 4.8.0
### Minor Changes
- Nye komponenter `VStack`, `HStack` og `Spacer` for å enklere kunne lage layout med flexbox og spacing-variabler. ([#2040](https://github.com/navikt/aksel/pull/2040))
### Patch Changes
- Updated dependencies []:
- @navikt/aksel-icons@4.8.0
## 4.7.4
### Patch Changes
+3 -5
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-react",
"version": "4.7.4",
"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.7.4",
"@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);
},
};
+1 -5
View File
@@ -126,11 +126,7 @@ export const Button: OverridableComponent<ButtonProps, HTMLButtonElement> =
<span className="navds-button__icon">{icon}</span>
)}
{children && (
<Label
as="span"
size={size === "medium" ? "medium" : "small"}
aria-live="polite"
>
<Label as="span" size={size === "medium" ? "medium" : "small"}>
{children}
</Label>
)}
@@ -148,8 +148,9 @@ export const Loading = {
</div>
),
args: { chromatic: { disableSnapshot: true } },
chromatic: { disableSnapshot: true },
parameters: {
chromatic: { disableSnapshot: true },
},
};
export const Icon = () => (
@@ -275,6 +276,7 @@ export const LoadingWithAs = {
</div>
),
args: { chromatic: { disableSnapshot: true } },
chromatic: { disableSnapshot: true },
parameters: {
chromatic: { disableSnapshot: true },
},
};
@@ -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>
);
});
@@ -1,5 +1,5 @@
import cl from "clsx";
import React, { useRef } from "react";
import React, { useRef, useState } from "react";
type ComboboxWrapperProps = {
children: any;
@@ -23,6 +23,7 @@ const ComboboxWrapper = ({
toggleListButtonRef,
}: ComboboxWrapperProps) => {
const wrapperRef = useRef<HTMLDivElement | null>(null);
const [hasFocusWithin, setHasFocusWithin] = useState(false);
function onFocusInsideWrapper(e) {
if (
@@ -30,12 +31,14 @@ const ComboboxWrapper = ({
toggleListButtonRef?.current !== e.target
) {
toggleIsListOpen(true);
setHasFocusWithin(true);
}
}
function onBlurWrapper(e) {
if (!wrapperRef.current?.contains(e.relatedTarget)) {
toggleIsListOpen(false);
setHasFocusWithin(false);
}
}
@@ -46,14 +49,15 @@ 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,
}
)}
onFocus={onFocusInsideWrapper}
onBlur={onBlurWrapper}
tabIndex={-1}
>
{children}
</div>
@@ -48,7 +48,11 @@ const FilteredOptions = () => {
{isValueNew && allowNewValues && (
<li
tabIndex={-1}
onPointerUp={(event) => toggleOption(value, event)}
onPointerUp={(event) => {
toggleOption(value, event);
if (!isMultiSelect && !selectedOptions.includes(value))
toggleIsListOpen(false);
}}
id={`${id}-combobox-new-option`}
className={cl("navds-combobox__list-item__new-option", {
"navds-combobox__list-item__new-option--focus":
@@ -88,9 +92,8 @@ const FilteredOptions = () => {
tabIndex={-1}
onPointerUp={(event) => {
toggleOption(option, 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,
@@ -37,6 +41,7 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
ariaDescribedBy,
moveFocusToInput,
moveFocusToEnd,
setFilteredOptionsIndex,
shouldAutocomplete,
} = useFilteredOptionsContext();
@@ -46,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
@@ -55,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,
]
@@ -139,6 +148,10 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
[filteredOptions.length, onChange, toggleIsListOpen]
);
const onBlur = () => {
setFilteredOptionsIndex(-1);
};
return (
<input
{...rest}
@@ -148,6 +161,7 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
onChange={onChangeHandler}
type="text"
role="combobox"
onBlur={onBlur}
onKeyUp={handleKeyUp}
onKeyDown={handleKeyDown}
aria-controls={`${inputProps.id}-filtered-options`}
@@ -156,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,
@@ -1,6 +1,7 @@
import React from "react";
import { Chips } from "../../..";
import { useSelectedOptionsContext } from "./selectedOptionsContext";
import { useInputContext } from "../Input/inputContext";
interface SelectedOptionsProps {
selectedOptions?: string[];
@@ -10,10 +11,12 @@ interface SelectedOptionsProps {
const Option = ({ option }: { option: string }) => {
const { isMultiSelect, removeSelectedOption } = useSelectedOptionsContext();
const { focusInput } = useInputContext();
const onClick = (e) => {
e.stopPropagation();
removeSelectedOption(option);
focusInput();
};
if (!isMultiSelect) {
@@ -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,12 +64,12 @@ export const SelectedOptionsProvider = ({
const addSelectedOption = useCallback(
(option: string) => {
if (
!options
.map((opt) => opt.toLowerCase())
.includes(option?.toLowerCase?.())
) {
const isCustomOption = !options
.map((opt) => opt.toLowerCase())
.includes(option?.toLowerCase?.());
if (isCustomOption) {
allowNewValues && addCustomOption(option);
!isMultiSelect && setSelectedOptions([]);
} else if (isMultiSelect) {
setSelectedOptions((prevSelectedOptions) => [
...prevSelectedOptions,
@@ -73,15 +77,24 @@ export const SelectedOptionsProvider = ({
]);
} else {
setSelectedOptions([option]);
setCustomOptions([]);
}
onToggleSelected?.(option, true);
onToggleSelected?.(option, true, isCustomOption);
},
[addCustomOption, allowNewValues, isMultiSelect, onToggleSelected, options]
[
addCustomOption,
allowNewValues,
isMultiSelect,
onToggleSelected,
options,
setCustomOptions,
]
);
const removeSelectedOption = useCallback(
(option: string) => {
if (customOptions.includes(option)) {
const isCustomOption = customOptions.includes(option);
if (isCustomOption) {
removeCustomOption(option);
} else {
setSelectedOptions((prevSelectedOptions) =>
@@ -90,7 +103,7 @@ export const SelectedOptionsProvider = ({
)
);
}
onToggleSelected?.(option, false);
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",
@@ -82,6 +82,7 @@ export function MultiSelect(props) {
label="Komboboks - velg flere"
options={props.options}
isMultiSelect={props.isMultiSelect}
size={props.size}
/>
</DemoContainer>
);
@@ -90,6 +91,28 @@ export function MultiSelect(props) {
MultiSelect.args = {
options,
isMultiSelect: true,
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) {
@@ -99,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}
/>
@@ -293,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));
}
@@ -322,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);
},
@@ -363,12 +414,20 @@ export const RemoveSelectedMultiSelectTest = {
await sleep(250);
userEvent.keyboard("{Enter}");
await sleep(250);
userEvent.keyboard("{Escape}");
await sleep(250);
const appleSlett = canvas.getByLabelText("apple slett");
appleSlett.focus();
await sleep(250);
userEvent.click(appleSlett);
await sleep(250);
const appleOption = canvas.getByRole("option", {
name: "apple",
selected: false,
});
expect(appleOption).toBeVisible();
userEvent.keyboard("{Escape}");
await sleep(250);
expect(appleOption).not.toBeVisible();
const bananaSlett = canvas.getByLabelText("banana slett");
expect(bananaSlett).toBeInTheDocument();
@@ -411,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 (
+11 -3
View File
@@ -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
*
@@ -78,10 +81,15 @@ export interface ComboboxProps
* Callback function triggered whenever an option is selected or de-selected
*
* @param option
* @param isSelected
* @param isSelected - Whether the option has been selected or unselected
* @param isCustomOption - Whether the option comes from user input, instead of from the list
* @returns
*/
onToggleSelected?: (option: string, isSelected: boolean) => void;
onToggleSelected?: (
option: string,
isSelected: 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,
+4
View File
@@ -25,6 +25,10 @@ export interface CellProps extends HTMLAttributes<HTMLDivElement> {
lg?: Column;
}
/**
* @deprecated Use <HGrid /> instead. <Grid /> will be removed in the future.
* @see [📝 Documentation](https://aksel.nav.no/komponenter/core/hgrid)
*/
export const Cell = forwardRef<HTMLDivElement, CellProps>(
({ children, xs, sm, md, lg, className, ...rest }, ref) => {
return (
+4
View File
@@ -8,6 +8,10 @@ export interface GridProps extends HTMLAttributes<HTMLDivElement> {
children: React.ReactNode;
}
/**
* @deprecated Use <HGrid /> instead. <Grid /> will be removed in the future.
* @see [📝 Documentation](https://aksel.nav.no/komponenter/core/hgrid)
*/
export const Grid = forwardRef<HTMLDivElement, GridProps>(
({ children, className, ...rest }, ref) => (
<div ref={ref} className={cl("navds-grid", className)} {...rest}>
-4
View File
@@ -1,6 +1,2 @@
export { default as Cell, type CellProps } from "./Cell";
export { default as Grid, type GridProps } from "./Grid";
export {
default as ContentContainer,
type ContentContainerProps,
} from "./ContentContainer";
+3
View File
@@ -32,3 +32,6 @@ export * from "./toggle-group";
export * from "./tooltip";
export * from "./typography";
export * from "./util";
export * from "./layout/stack";
export * from "./layout/grid";
export * from "./layout/content-container";
@@ -0,0 +1,4 @@
export {
default as ContentContainer,
type ContentContainerProps,
} from "./ContentContainer";
@@ -0,0 +1,101 @@
import React, { forwardRef, HTMLAttributes } from "react";
import cl from "clsx";
import {
getResponsiveProps,
getResponsiveValue,
ResponsiveProp,
SpacingScale,
} from "../utilities/css";
export interface HGridProps extends HTMLAttributes<HTMLDivElement> {
children: React.ReactNode;
/**
* Number of columns to display. Can be a number, a string with a unit or tokens for spesific breakpoints.
* Sets `grid-template-columns`, so `fr`, `minmax` etc. works.
* @example
* columns={{ sm: 1, md: 1, lg: "1fr auto", xl: "1fr auto"}}
* @example
* columns={3}
* @example
* columns="repeat(3, minmax(0, 1fr))"
*/
columns?: ResponsiveProp<number | string>;
/** Spacing between columns. Based on spacing-tokens.
* @example
* gap="6"
* gap={{ sm: "2", md: "2", lg: "6", xl: "6"}}
*/
gap?: ResponsiveProp<SpacingScale>;
}
/**
* Horizontal Grid Primitive with dynamic columns and gap based on breakpoints.
*
* @see [📝 Documentation](https://aksel.nav.no/komponenter/core/hgrid)
* @see 🏷️ {@link HGridProps}
*
* @example
* <HGrid gap="6" columns={3}>
* <div />
* <div />
* <div />
* </HGrid>
* @example
* <HGrid gap={{xs: "2", md: "6"}} columns={3}>
* <div />
* <div />
* <div />
* </HGrid>
* @example
* <HGrid gap="6" columns={{ sm: 1, md: 1, lg: "1fr auto", xl: "1fr auto"}}>
* <div />
* <div />
* <div />
* </HGrid>
*/
export const HGrid = forwardRef<HTMLDivElement, HGridProps>(
({ className, columns, gap, style, ...rest }, ref) => {
const styles: React.CSSProperties = {
...style,
...getResponsiveProps(`hgrid`, "gap", "spacing", gap),
...getResponsiveValue(`hgrid`, "columns", formatGrid(columns)),
};
return (
<div
{...rest}
ref={ref}
className={cl("navds-hgrid", className)}
style={styles}
/>
);
}
);
function formatGrid(
props?: ResponsiveProp<number | string>
): ResponsiveProp<number | string> {
if (!props) {
return {};
}
if (typeof props === "string" || typeof props === "number") {
return getColumnValue(props);
}
return Object.fromEntries(
Object.entries(props).map(([breakpointToken, columnValue]) => [
breakpointToken,
getColumnValue(columnValue),
])
);
}
const getColumnValue = (prop: number | string) => {
if (typeof prop === "number") {
return `repeat(${prop}, minmax(0, 1fr))`;
}
return prop;
};
export default HGrid;
@@ -0,0 +1,102 @@
import React from "react";
import { HGrid } from ".";
const columnsVariants = {
Number: "columnNumber",
String: "columnString",
Object: "columnObject",
};
export default {
title: "ds-react/HGrid",
component: HGrid,
parameters: {
layout: "fullscreen",
},
argTypes: {
columnsType: {
defaultValue: Object.keys(columnsVariants)[0],
options: Object.keys(columnsVariants),
control: { type: "radio" },
},
},
};
/* const getColumnsProp = () */
export const Default = {
render: (props) => (
<HGrid
gap={props?.gap ?? 4}
columns={props[columnsVariants[props.columnsType]]}
>
<Placeholder text="1" />
<Placeholder text="2" />
<Placeholder text="3" />
<Placeholder text="4" />
</HGrid>
),
args: {
columnNumber: 4,
columnObject: { xs: 1, md: 4 },
columnString: "repeat(3, minmax(0, 1fr))",
gap: "4",
},
};
export const Gap = {
render: () => (
<HGrid gap="6">
<Placeholder text="1" />
<Placeholder text="2" />
<Placeholder text="3" />
<Placeholder text="4" />
</HGrid>
),
};
export const DynamicGap = {
render: () => (
<HGrid gap={{ xs: "2", md: "8" }}>
<Placeholder text="1" />
<Placeholder text="2" />
<Placeholder text="3" />
<Placeholder text="4" />
</HGrid>
),
};
export const Columns = {
render: () => (
<HGrid gap="4" columns={2}>
<Placeholder text="1" />
<Placeholder text="2" />
<Placeholder text="3" />
<Placeholder text="4" />
</HGrid>
),
};
export const DynamicColumns = {
render: () => (
<HGrid gap="4" columns={{ sm: "1fr 5fr", md: "2fr 2fr" }}>
<Placeholder text="1" />
<Placeholder text="2" />
</HGrid>
),
};
function Placeholder({ text }) {
return (
<div
style={{
background: "var(--a-deepblue-900)",
height: "5rem",
width: "auto",
color: "white",
}}
>
{text}
</div>
);
}
@@ -0,0 +1 @@
export { default as HGrid, type HGridProps } from "./HGrid";
@@ -0,0 +1,30 @@
import React, { forwardRef } from "react";
import { OverridableComponent } from "../../util/OverridableComponent";
import { StackProps, Stack } from "./Stack";
export type HStackProps = Omit<StackProps, "direction">;
/**
* Layout-primitive for horizontal flexbox
*
* @see [📝 Documentation](https://aksel.nav.no/komponenter/core/Stack)
* @see 🏷️ {@link HStackProps}
* @see [🤖 OverridableComponent](https://aksel.nav.no/grunnleggende/kode/overridablecomponent) support
*
* @example
* <HStack gap="8">
* <MyComponent />
* <MyComponent />
* </HStack>
*
* @example
* // Responsive gap
* <HStack gap={{xs: "2", md: "6"}}>
* <MyComponent />
* <MyComponent />
* </HStack>
*/
export const HStack: OverridableComponent<HStackProps, HTMLDivElement> =
forwardRef((props, ref) => {
return <Stack {...props} ref={ref} direction="row" />;
});
@@ -0,0 +1,15 @@
import React from "react";
/**
* Layout-primitive for auto-spacing between elements
*
* @see [📝 Documentation](https://aksel.nav.no/komponenter/core/Stack)
*
* @example
* <HStack gap="8">
* <MyComponent />
* <Spacer />
* <MyComponent />
* </HStack>
*/
export const Spacer = () => <div className="navds-stack__spacer" />;
@@ -0,0 +1,76 @@
import cl from "clsx";
import React, { forwardRef, HTMLAttributes } from "react";
import { OverridableComponent } from "../../util/OverridableComponent";
import {
getResponsiveProps,
ResponsiveProp,
SpacingScale,
} from "../utilities/css";
export interface StackProps extends HTMLAttributes<HTMLDivElement> {
children: React.ReactNode;
/**
* Justify-content
*/
justify?:
| "start"
| "center"
| "end"
| "space-around"
| "space-between"
| "space-evenly";
/**
* Align-items
*/
align?: "start" | "center" | "end" | "baseline" | "stretch";
/**
* flex-wrap
*/
wrap?: boolean;
/**
* @example
* gap='4'
* gap={{xs: '2', sm: '3', md: '4', lg: '5', xl: '6'}}
*/
gap?: ResponsiveProp<SpacingScale>;
direction: "row" | "column";
}
export const Stack: OverridableComponent<StackProps, HTMLDivElement> =
forwardRef(
(
{
as: Component = "div",
className,
align,
justify,
wrap = true,
gap,
style: _style,
direction,
...rest
},
ref
) => {
const style = {
..._style,
"--__ac-stack-direction": direction,
"--__ac-stack-align": align,
"--__ac-stack-justify": justify,
"--__ac-stack-wrap": wrap ? "wrap" : "nowrap",
...getResponsiveProps(`stack`, "gap", "spacing", gap),
} as React.CSSProperties;
return (
<Component
{...rest}
ref={ref}
style={style}
className={cl("navds-stack", className, {
"navds-vstack": direction === "column",
"navds-hstack": direction === "row",
})}
/>
);
}
);
@@ -0,0 +1,30 @@
import React, { forwardRef } from "react";
import { OverridableComponent } from "../../util/OverridableComponent";
import { StackProps, Stack } from "./Stack";
export type VStackProps = Omit<StackProps, "direction" | "wrap">;
/**
* Layout-primitive for vetical flexbox
*
* @see [📝 Documentation](https://aksel.nav.no/komponenter/core/Stack)
* @see 🏷️ {@link VStackProps}
* @see [🤖 OverridableComponent](https://aksel.nav.no/grunnleggende/kode/overridablecomponent) support
*
* @example
* <VStack gap="8">
* <MyComponent />
* <MyComponent />
* </VStack>
*
* @example
* // Responsive gap
* <VStack gap={{xs: "2", md: "6"}}>
* <MyComponent />
* <MyComponent />
* </VStack>
*/
export const VStack: OverridableComponent<VStackProps, HTMLDivElement> =
forwardRef((props, ref) => {
return <Stack {...props} ref={ref} direction="column" wrap={false} />;
});
@@ -0,0 +1,3 @@
export { HStack, type HStackProps } from "./HStack";
export { VStack, type VStackProps } from "./VStack";
export { Spacer } from "./Spacer";
@@ -0,0 +1,161 @@
import React from "react";
import type { Meta } from "@storybook/react";
import { HStack, VStack, Spacer } from ".";
export default {
title: "ds-react/Stack",
component: HStack,
} satisfies Meta<typeof HStack>;
export const Horizontal = {
render: () => (
<HStack gap="4">
<Placeholders count={4} />
</HStack>
),
};
export const Spacing = {
render: () => (
<div style={{ height: "80vh", display: "flex" }}>
<VStack gap="8">
<Spacer />
<HStack gap="4">
<Placeholders count={1} />
<Spacer />
<Placeholders count={1} />
</HStack>
<HStack gap="4">
<Placeholders count={1} />
<Placeholders count={1} />
</HStack>
<HStack gap="4">
<Placeholders count={2} />
</HStack>
</VStack>
</div>
),
parameters: {
layout: "fullscreen",
},
};
export const Vertical = {
render: () => (
<VStack gap="4">
<Placeholders count={4} />
</VStack>
),
};
export const VerticalDemo = {
render: () => (
<VStack gap="2">
<VStack>
<Placeholders count={4} />
</VStack>
<Placeholders count={4} />
<VStack>
<Placeholders count={4} />
</VStack>
</VStack>
),
};
export const VerticalAlign = {
render: () => (
<VStack gap="4">
<VStack align="start">
<Placeholders count={2} />
</VStack>
<VStack align="center">
<Placeholders count={2} />
</VStack>
<VStack align="end">
<Placeholders count={2} />
</VStack>
</VStack>
),
parameters: {
layout: "fullscreen",
},
};
export const OverrideComponent = {
render: () => (
<VStack gap="4" as="form" onSubmit={(e) => e.preventDefault()}>
<Placeholders count={4} />
</VStack>
),
};
export const Responsive = {
render: () => (
<VStack gap={{ xs: "1", sm: "3", md: "6", lg: "10", xl: "16" }}>
<Placeholders count={4} />
</VStack>
),
};
export const Nested = {
render: () => (
<VStack gap="16">
<Placeholders count={2}>
<VStack gap="4">
<Placeholders count={2} color="gray" />
</VStack>
</Placeholders>
</VStack>
),
};
export const DividerDemo = {
render: () => (
<div style={{ height: "80vh", width: "40rem" }}>
<VStack gap={{ xs: "2", md: "6", lg: "12" }}>
<HStack gap={{ xs: "2", md: "6", lg: "12" }}>
<Placeholders count={1} />
<Spacer />
<Placeholders count={1} />
</HStack>
<hr
style={{
border: "none",
borderBottom: "1px solid var(--a-border-divider)",
margin: 0,
}}
/>
<HStack gap={{ xs: "2", md: "6", lg: "12" }}>
<Placeholders count={2} />
</HStack>
</VStack>
</div>
),
};
function Placeholders({
count,
children,
color,
}: {
count: number;
children?: React.ReactNode;
color?: string;
}) {
return (
<>
{Array.from({ length: count }, (_, i) => (
<div
key={i}
style={{
backgroundColor: color ?? "var(--a-purple-200)",
height: children ? "" : "3rem",
width: children ? "" : "3rem",
}}
>
{children}
</div>
))}
</>
);
}
@@ -0,0 +1,78 @@
export type BreakpointsAlias = "xs" | "sm" | "md" | "lg" | "xl";
export type SpacingScale =
| "0"
| "05"
| "1"
| "2"
| "3"
| "4"
| "5"
| "6"
| "7"
| "8"
| "9"
| "10"
| "11"
| "12"
| "14"
| "16"
| "18"
| "20"
| "24"
| "32";
type ResponsivePropConfig<T = string> = {
// eslint-disable-next-line no-unused-vars
[Breakpoint in BreakpointsAlias]?: T;
};
export type ResponsiveProp<T> = T | ResponsivePropConfig<T>;
export type ResponsiveValue<T = string> = undefined | ResponsiveProp<T>;
export function getResponsiveProps<T = string>(
componentName: string,
componentProp: string,
tokenSubgroup: string,
responsiveProp?: ResponsiveProp<T>
) {
if (!responsiveProp) {
return {};
}
if (typeof responsiveProp === "string") {
return {
[`--__ac-${componentName}-${componentProp}-xs`]: `var(--a-${tokenSubgroup}-${responsiveProp})`,
};
}
return Object.fromEntries(
Object.entries(responsiveProp).map(([breakpointAlias, aliasOrScale]) => [
`--__ac-${componentName}-${componentProp}-${breakpointAlias}`,
`var(--a-${tokenSubgroup}-${aliasOrScale})`,
])
);
}
export function getResponsiveValue<T = string>(
componentName: string,
componentProp: string,
responsiveProp?: ResponsiveValue<T>
) {
if (!responsiveProp) {
return {};
}
if (typeof responsiveProp === "string") {
return {
[`--__ac-${componentName}-${componentProp}-xs`]: responsiveProp,
};
}
return Object.fromEntries(
Object.entries(responsiveProp).map(([breakpointAlias, responsiveValue]) => [
`--__ac-${componentName}-${componentProp}-${breakpointAlias}`,
responsiveValue,
])
);
}
+47 -3
View File
@@ -4,6 +4,23 @@ import { OverridableComponent } from "../util/OverridableComponent";
export interface LinkProps
extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
/**
* Inverts when the underline appears. If this is false,
* the underline does not appear by default, but does appear when the link is hovered.
* This makes it more suitable for use when inlined in text.
* @default true
*/
underline?: boolean;
/**
* Renders link with `display: inline` for better wrapping in text.
* @default false
*/
inlineText?: boolean;
/**
* Variant of the component to use.
* @default "action"
*/
variant?: "action" | "neutral" | "subtle";
/**
* Link text
*/
@@ -22,10 +39,37 @@ export interface LinkProps
* <Link href="#">lenke til ny side</Link>
* <Link as={ReactRouter.Link} to="#">lenke til ny side</Link>
* ```
* @example
* ```jsx
* <BodyLong>
* ...
* <Link href="#" inlineText>lenke til ny side</Link>
* ...
* </BodyLong>
* ```
*/
export const Link: OverridableComponent<LinkProps, HTMLAnchorElement> =
forwardRef(({ as: Component = "a", className, ...rest }, ref) => (
<Component {...rest} ref={ref} className={cl("navds-link", className)} />
));
forwardRef(
(
{
as: Component = "a",
className,
underline = true,
variant = "action",
inlineText = false,
...rest
},
ref
) => (
<Component
{...rest}
ref={ref}
className={cl("navds-link", className, `navds-link--${variant}`, {
"navds-link--remove-underline": !underline,
"navds-link--inline-text": inlineText,
})}
/>
)
);
export default Link;
+143 -13
View File
@@ -6,29 +6,76 @@ import {
ConfirmationPanel as DsConfirmationPanel,
Link,
} from "..";
import { RandomIcon } from "../util/RandomIcon";
export default {
title: "ds-react/Link",
component: Link,
};
const LinkWrapper = ({
children = "Ex aliqua incididunt",
iconRight = false,
iconLeft = false,
variant = "action",
underline = false,
inlineText = true,
}) => (
<>
{" "}
<Link
href="#"
underline={underline}
variant={variant as "action" | "neutral" | "subtle"}
inlineText={inlineText}
>
{iconLeft && (
<>
<RandomIcon />{" "}
</>
)}
{children}
{iconRight && (
<>
{" "}
<RandomIcon />
</>
)}
</Link>{" "}
</>
);
export const Default = {
render: (props) => {
render: ({ icon, inline }) => {
const LinkD = () => (
<Link href="#">
{props.icon && <PlusCircleFillIcon />}Ex aliqua incididunt{" "}
{props.icon && <PlusCircleFillIcon />}
</Link>
<>
{" "}
<Link href="#" underline={!inline} inlineText={inline}>
{icon && <PlusCircleFillIcon />}Ex aliqua incididunt
{icon && <PlusCircleFillIcon />}
</Link>{" "}
</>
);
if (props.inline) {
if (inline) {
return (
<BodyLong>
Incididunt laborum nisi nisi Lorem <LinkD /> in. Laborum aute fugiat
officia adipisicing non veniam dolor nulla non ex consectetur fugiat
eiusmod aute. Culpa sit aute est duis minim in in voluptate velit
fugiat. Laboris est id deserunt ut ea Lorem eu. Esse elit laboris aute
commodo sint laborum fugiat aliqua.
</BodyLong>
<div
className="colgap"
style={{
width: "800px",
border: "1px solid black",
borderRadius: "8px",
}}
>
<BodyLong>
Incididunt laborum nisi nisi Lorem
<LinkD />
in. Laborum aute fugiat officia adipisicing non veniam dolor nulla
non ex consectetur fugiat eiusmod aute. Culpa sit aute est duis
minim in in voluptate velit fugiat. Laboris est id deserunt ut ea
Lorem eu. Esse elit laboris aute commodo sint laborum fugiat aliqua.
</BodyLong>
</div>
);
}
return <LinkD />;
@@ -40,6 +87,49 @@ export const Default = {
},
};
export const InlineInsideBodyLong = {
render: ({ iconLeft, iconRight }) => {
return (
<div
className="colgap"
style={{
width: "800px",
border: "1px solid black",
borderRadius: "8px",
}}
>
<style>{`.storybook-custom-spacing { white-space: pre;}`}</style>
<BodyLong>
<LinkWrapper underline iconLeft={iconLeft} iconRight={iconRight} />
Eiusmod aute.
<LinkWrapper underline iconLeft={iconLeft} iconRight={iconRight} />
Culpa sit aute est duis minim in in voluptate velit Incididunt laborum
nisi nisi Lorem officia adipisicing non veniam
<LinkWrapper underline iconLeft={iconLeft} iconRight={iconRight}>
blah blah blah blah blah blah blah blah blah blah blah blah blah
blah blah blah blah blah blah blah blah
</LinkWrapper>
dolor eu. Esse elit laboris aute commodo sint laborum fugiat aliqua.
<LinkWrapper underline iconLeft={iconLeft} iconRight={iconRight}>
Link
</LinkWrapper>
</BodyLong>
<BodyLong className="storybook-custom-spacing">
Custom{" "}
<LinkWrapper underline iconLeft={iconLeft} iconRight={iconRight}>
link
</LinkWrapper>
{" "}spacing.
</BodyLong>
</div>
);
},
args: {
iconLeft: false,
iconRight: false,
},
};
const DemoLink = () => (
<Link href="#">
<PlusCircleFillIcon aria-hidden /> Ex aliqua incididunt{" "}
@@ -83,3 +173,43 @@ export const ConfirmationPanel = () => {
</div>
);
};
export const Variants = {
render: ({ iconLeft, iconRight }) => {
return (
<div className="colgap">
{["action", "neutral", "subtle"].map((variant) => (
<>
<div>
<LinkWrapper
iconLeft={iconLeft}
iconRight={iconRight}
variant={variant}
/>
</div>
</>
))}
</div>
);
},
args: {
iconLeft: false,
iconRight: false,
},
};
export const InlineLink = {
render: () => (
<BodyLong>
Officia incididunt Culpa sit aute est duis minim in in voluptate velit
Incididunt laborum nisi nisi Lorem officia adipisicing non veniam{" "}
<Link href="#" inlineText={true}>
lenke til ny side
<PlusCircleFillIcon aria-hidden />
</Link>{" "}
Culpa sit aute est duis minim in in voluptate velit Incididunt laborum
nisi nisi Lorem officia adipisicing non veniam occaecat commodo id ad
aliquip.
</BodyLong>
),
};
+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}
+30 -9
View File
@@ -31,10 +31,15 @@ export interface ExpandableRowProps extends Omit<RowProps, "content"> {
*/
onOpenChange?: (open: boolean) => void;
/**
* Disable expansio
* Disable expansion. shadeOnHover will not be visible.
* @default false
*/
expansionDisabled?: boolean;
/**
* Makes the whole row clickable
* @default false
*/
expandOnRowClick?: boolean;
/**
* The width of the expanded row's internal cell
* @default 999
@@ -58,6 +63,7 @@ export const ExpandableRow: ExpandableRowType = forwardRef(
open,
onOpenChange,
expansionDisabled = false,
expandOnRowClick = false,
colSpan = 999,
...rest
},
@@ -65,9 +71,22 @@ export const ExpandableRow: ExpandableRowType = forwardRef(
) => {
const [internalOpen, setInternalOpen] = useState<boolean>(defaultOpen);
const id = useId();
const isOpen = open ?? internalOpen;
const expansionHandler = (e) => {
onOpenChange?.(!isOpen);
if (open === undefined) {
setInternalOpen((open) => !open);
}
e.stopPropagation();
};
const onRowClick = (e) => {
if (e.target.nodeName === "TD" || e.target.nodeName === "TH") {
expansionHandler(e);
}
};
return (
<>
<Row
@@ -75,7 +94,14 @@ export const ExpandableRow: ExpandableRowType = forwardRef(
ref={ref}
className={cl("navds-table__expandable-row", className, {
"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);
rest?.onClick?.(e);
}}
>
{togglePlacement === "right" && children}
<DataCell
@@ -85,16 +111,11 @@ export const ExpandableRow: ExpandableRowType = forwardRef(
>
{!expansionDisabled && (
<button
type="button"
className="navds-table__toggle-expand-button"
type="button"
aria-controls={id}
aria-expanded={isOpen}
onClick={() => {
onOpenChange?.(!isOpen);
if (open === undefined) {
setInternalOpen((open) => !open);
}
}}
onClick={expansionHandler}
>
<ChevronDownIcon
className="navds-table__expandable-icon"
+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}
@@ -1,6 +1,6 @@
import React, { useState } from "react";
import { Table } from "..";
import { Link } from "../..";
import { Button, Checkbox, Link } from "../..";
export default {
title: "ds-react/Table",
@@ -36,6 +36,43 @@ 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 = () => {
const [open, setOpen] = useState(false);
return (
@@ -227,3 +264,63 @@ export const ExpandableOpen = () => {
</Table>
);
};
export const ClickableRow = {
render: () => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const [isRowOpen1, setIsRowOpen1] = useState(false);
// eslint-disable-next-line react-hooks/rules-of-hooks
const [isRowOpen2, setIsRowOpen2] = useState(false);
return (
<>
<Table zebraStripes>
<Table.Header>
<Table.Row>
<Table.HeaderCell>Navn</Table.HeaderCell>
<Table.HeaderCell>Info</Table.HeaderCell>
<Table.HeaderCell aria-hidden />
</Table.Row>
</Table.Header>
<Table.Body>
<Table.ExpandableRow
content={<div>placeholder row 1</div>}
togglePlacement="right"
onOpenChange={setIsRowOpen1}
data-testid="row1"
open={isRowOpen1}
expandOnRowClick
>
<Table.DataCell>Ola</Table.DataCell>
<Table.DataCell>
<Button
size="xsmall"
onClick={(e) => {
alert("Mer info");
}}
>
Mer info
</Button>
</Table.DataCell>
</Table.ExpandableRow>
<Table.ExpandableRow
content={<div>placeholder row 2</div>}
togglePlacement="right"
onOpenChange={setIsRowOpen2}
data-testid="row2"
open={isRowOpen2}
expandOnRowClick
>
<Table.DataCell>Hans</Table.DataCell>
<Table.DataCell>
<Checkbox hideLabel size="small">
Sett
</Checkbox>
</Table.DataCell>
</Table.ExpandableRow>
</Table.Body>
</Table>
</>
);
},
};
@@ -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 = () => {
@@ -0,0 +1,9 @@
import * as AllIcons from "@navikt/aksel-icons";
import React from "react";
const icons = Object.keys(AllIcons).map((key) => AllIcons[key]);
export const RandomIcon = () => {
const Icon = icons[Math.floor(Math.random() * icons.length)];
return <Icon />;
};
+30
View File
@@ -1,5 +1,35 @@
# @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
## 4.11.0
## 4.10.2
## 4.10.1
## 4.10.0
## 4.9.1
## 4.9.0
## 4.8.0
## 4.7.4
## 4.7.3
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-tailwind",
"version": "4.7.4",
"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.7.4",
"@navikt/ds-tokens": "^5.0.3",
"@types/jest": "^29.0.0",
"color": "4.2.3",
"jest": "^29.0.0",
+42
View File
@@ -1,5 +1,47 @@
# @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
## 4.11.0
## 4.10.2
## 4.10.1
## 4.10.0
## 4.9.1
## 4.9.0
## 4.8.0
## 4.7.4
## 4.7.3
+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.7.4",
"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": {
+301 -110
View File
@@ -1,15 +1,206 @@
# Changelog
## 4.7.4
## 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
- Oppdatert bruk av REM i komponenter for forbedret utrykk ved font-scaling i browser ([#2126](https://github.com/navikt/aksel/pull/2126))
- 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
- :lipstick: ToggleGroup: fjern semibold fra selected button ([#2167](https://github.com/navikt/aksel/pull/2167))
## 4.11.1
### @navikt/ds-css
- Border-radius: Fikset hardkodet border-radius i Datepicker, ToggleGroup og Combobox. ([#2159](https://github.com/navikt/aksel/pull/2159))
## 4.11.0
### @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))
<!---->
- Fixes bug where combobox list could not be closed after clicking a chip ([#2155](https://github.com/navikt/aksel/pull/2155))
- 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
- Link: Fikset visited farger og inline SVG-reset ved bruk av tailwind
## 4.10.0
### @navikt/ds-react
- Link: La til 'variant', 'underline' og 'inlineText'-prop ([#2093](https://github.com/navikt/aksel/pull/2093))
### @navikt/ds-css
- Link: La til 'variant', 'underline' og 'inlineText'-prop ([#2093](https://github.com/navikt/aksel/pull/2093))
## 4.9.1
### @navikt/ds-react
- Button: Fikset aria-live bug der knapp alltid ble lest opp av skjermleser ved render ([#2143](https://github.com/navikt/aksel/pull/2143))
- 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-react
- Table: ExpandableRow kan nå åpnes med 'expandOnRowClick'-prop ([#2127](https://github.com/navikt/aksel/pull/2127))
### @navikt/ds-css
- Table: ExpandableRow kan nå åpnes med 'expandOnRowClick'-prop ([#2127](https://github.com/navikt/aksel/pull/2127))
## 4.8.0
### @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-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-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
@@ -18,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))
@@ -34,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))
@@ -48,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))
@@ -78,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
@@ -96,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
@@ -124,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
@@ -154,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
@@ -194,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))
@@ -222,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
@@ -240,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
@@ -264,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))
@@ -292,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
@@ -304,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
@@ -328,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))
@@ -336,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
@@ -350,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
@@ -454,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
@@ -490,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
@@ -576,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
@@ -614,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))
@@ -626,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
@@ -676,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
@@ -746,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
@@ -808,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
@@ -844,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
@@ -870,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
Loaded 100 of 192 files, more files were not shown because too many files have changed in this diff. Show more