Compare commits

...
Author SHA1 Message Date
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
github-actions[bot]andgithub-actions[bot] a2dfc8699b Version Packages (#2138)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-08-04 12:33:33 +02:00
Ken 492cfc1801 💄 Oppdatert bruk av REM for font-scaling fix (#2126) 2023-08-04 11:33:05 +02:00
Ken 9ca5932fed ⬆️ Oppdatert dependencies for dependabot warnings (#2134) 2023-08-04 11:18:24 +02:00
Vebjørn NordbyandKen 5950673fbf Legger til htmlInputType time på TextField-type (#2137)
Co-authored-by: Ken <ken.aleksander@gmail.com>
2023-08-04 09:15:17 +02:00
Ken 5b42ff07ee 🐛 Fikset oppdateringer av prop-docs på Aksel.nav.no 2023-08-03 13:41:20 +02:00
github-actions[bot]andgithub-actions[bot] 09ba52ea76 Version Packages (#2132)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-08-03 13:30:24 +02:00
a82bccf707 Removable chips type now defaults to button (#2124)
* Removable chips type now defaults to button

* 📝 Changeset

* 📝 Changeset update

* Remove OverridableComponent from Chips.Removable. This component should always be a button, and it is not currently used in any other way.

The reason for making this change now, is that we put type="button" on the Chip.Removable to prevent a submit event when the chip is clicked inside a form.

---------

Co-authored-by: Ken <ken.aleksander@gmail.com>
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
Co-authored-by: Vegard Haugstvedt <it.vegard@gmail.com>
2023-08-03 11:41:31 +02:00
Ken 89ae778c24 🐛 Fikset lasting av aksel-eksempler i storybook 2023-08-03 10:53:25 +02:00
Ken 5b2669d35d ⬆️ Oppdatert storybook til 7.2 (#2128) 2023-08-02 15:35:28 +02:00
Ken 8a82bb4a96 🐛 Fikset layout-bug på komponentsider for enkelte nettlesere 2023-08-02 12:43:19 +02:00
Ken 7871e0f95c 📝 Oppdatert datepicker-eksempler med formatering (#2125) 2023-08-02 12:37:14 +02:00
dependabot[bot] 3182888a07 ⬆️ Bump word-wrap from 1.2.3 to 1.2.4 (#2115)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-02 10:39:55 +02:00
Mathias S. Müller f2742da085 Legg til manglende størrelsesbeskrivelse (#2121) 2023-07-27 14:59:08 +02:00
219 changed files with 8881 additions and 4559 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}}
+5
View File
@@ -10,6 +10,11 @@ module.exports = (indexers) => {
const exampleIndexer = async (fileName: string, opts) => {
let code = readFileSync(fileName, "utf-8").toString();
code = code
.split("\n")
.filter((line) => !line.includes("withDsExample"))
.join("\n");
code = code.replace("export const args =", "const args =");
code += `\nexport default { title: "Eksempler/${fileName
+3 -8
View File
@@ -1,5 +1,4 @@
const storyIndexers = require("./indexer");
const path = require("path");
const { mergeConfig } = require("vite");
@@ -59,15 +58,11 @@ module.exports = {
},
async viteFinal(config) {
return mergeConfig(config, {
define: { "process.env": {} },
define: {
"process.env": {},
},
resolve: {
alias: [
/*
// Example
{
find: "@components",
replacement: path.resolve(__dirname, "./src/components"),
}, */
{
find: "components",
replacement: path.resolve(
+30
View File
@@ -1,5 +1,35 @@
# @navikt/aksel-icons
## 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
## 4.7.2
## 4.7.1
@@ -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.2",
"version": "5.0.1",
"author": "Aksel | NAV",
"homepage": "https://aksel.nav.no/ikoner",
"repository": {
+30
View File
@@ -1,5 +1,35 @@
# @navikt/aksel-stylelint
## 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
## 4.7.2
## 4.7.1
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/aksel-stylelint",
"version": "4.7.2",
"version": "5.0.1",
"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.2",
"@navikt/ds-tokens": "^4.7.2",
"@navikt/ds-css": "^5.0.1",
"@navikt/ds-tokens": "^5.0.1",
"@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.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
- Updated dependencies [[`492cfc180`](https://github.com/navikt/aksel/commit/492cfc1801df4ab16306185345d5089e24a0d659)]:
- @navikt/ds-css@4.7.4
## 4.7.3
### Patch Changes
- Updated dependencies []:
- @navikt/ds-css@4.7.3
## 4.7.2
### Patch Changes
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/aksel",
"version": "4.7.2",
"version": "5.0.1",
"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.2",
"@navikt/ds-css": "5.0.1",
"axios": "1.3.6",
"chalk": "4.1.0",
"clipboardy": "^2.3.0",
+103
View File
@@ -1,5 +1,108 @@
# @navikt/ds-css
## 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
- 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
## 4.7.2
### Patch Changes
+8 -1
View File
@@ -24,10 +24,17 @@
font-size: 1.5rem;
align-self: flex-start;
height: var(--a-font-line-height-xlarge);
margin: var(--a-spacing-1);
margin-inline: var(--a-spacing-1);
margin-block: 0;
margin-block-start: -1px;
background: radial-gradient(circle, var(--a-surface-default) 50%, 0, transparent);
}
.navds-alert--close-button > .navds-alert__icon,
.navds-alert--small > .navds-alert__icon {
margin-block-start: 0;
}
.navds-alert--small > .navds-alert__icon {
height: var(--a-font-line-height-large);
}
+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;
+2 -2
View File
@@ -25,11 +25,11 @@
padding: 0 var(--a-spacing-3);
text-decoration: none;
border-radius: var(--a-border-radius-full);
min-height: 32px;
min-height: 2rem;
}
.navds-chips--small .navds-chips__chip {
min-height: 24px;
min-height: 1.5rem;
padding: 0 var(--a-spacing-2);
}
+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] },
+9 -2
View File
@@ -52,7 +52,14 @@
.navds-copybutton__icon {
font-size: 1.5rem;
display: flex;
margin-left: -4px;
}
.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 {
@@ -60,7 +67,7 @@
}
:where(.navds-copybutton--small, .navds-copybutton--xsmall):where(:not(:only-child)) {
margin: -2px;
margin: -0.125rem;
}
.navds-copybutton:focus-visible {
+3 -4
View File
@@ -223,7 +223,7 @@
.navds-date__field-button {
position: absolute;
right: 1px;
right: 0.0625rem;
top: 50%;
transform: translateY(-50%);
color: var(--ac-date-input-button-text, var(--a-text-default));
@@ -233,12 +233,12 @@
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;
font-size: 1.5rem;
height: calc(100% - 2px);
height: calc(100% - 0.125rem);
border-start-start-radius: 0;
border-end-start-radius: 0;
}
@@ -284,7 +284,6 @@
}
/* Readonly */
.navds-date__field--readonly .navds-date__field-button {
cursor: default;
}
+1 -1
View File
@@ -66,7 +66,7 @@
*************************/
.navds-expansioncard__title--small {
margin-top: 10px;
margin-top: 0.625rem;
}
.navds-expansioncard__title--medium {
+39 -6
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 black;
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 {
@@ -52,7 +64,7 @@
letter-spacing: 0;
line-height: var(--a-font-line-height-large);
margin: 0;
padding-left: calc(0.5rem - 4px);
padding-left: 0.25rem;
}
.navds-combobox__input-wrapper {
@@ -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));
@@ -190,8 +214,8 @@
align-items: flex-start;
padding: 0;
box-shadow: var(--a-shadow-small);
border-radius: 4px;
gap: 4px 0;
border-radius: var(--a-border-radius-medium);
gap: var(--a-spacing-1) 0;
background-color: var(--ac-combobox-list-bg, var(--a-surface-default));
color: var(--ac-combobox-list-text, var(--a-text-default));
}
@@ -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 */
+1 -1
View File
@@ -9,7 +9,7 @@
}
.navds-form-field__description {
margin-top: -6px;
margin-top: -0.375rem;
color: var(--ac-form-description, var(--a-text-subtle));
}
+34 -13
View File
@@ -7,10 +7,10 @@
.navds-checkbox__input,
.navds-radio__input {
position: absolute;
width: 48px;
height: 48px;
width: 3rem;
height: 3rem;
top: 0;
left: -12px;
left: -0.75rem;
z-index: 1;
opacity: 0;
cursor: pointer;
@@ -51,15 +51,15 @@
.navds-checkbox--small > .navds-checkbox__input,
.navds-radio--small > .navds-radio__input {
width: 32px;
height: 32px;
width: 2rem;
height: 2rem;
top: 0;
left: -6px;
left: -0.375rem;
}
.navds-checkbox--small > .navds-checkbox__label,
.navds-radio--small > .navds-radio__label {
padding: 6px 0;
padding: 0.375rem 0;
}
.navds-checkbox--small > .navds-checkbox__label::before,
@@ -114,16 +114,34 @@
}
.navds-checkbox__input:checked + .navds-checkbox__label::before {
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMyAxMCI+ICAgIDxnPiAgICA8cGF0aCBmaWxsPSIjRkZGRkZGIiBkPSJNNCwxMGMtMC40LDAtMC44LTAuMS0xLjEtMC40TDAuNCw3LjFDMC4xLDYuOCwwLDYuNCwwLDZzMC4yLTAuOCwwLjUtMS4xQzEsNC40LDIsNC40LDIuNSw0LjlMNCw2LjRsNi40LTYgICAgQzEwLjgsMC4xLDExLjEsMCwxMS41LDBjMC40LDAsMC44LDAuMiwxLDAuNWMwLjYsMC42LDAuNSwxLjYtMC4xLDIuMXYwTDUsOS42QzQuNyw5LjksNC40LDEwLDQsMTB6IE0xMS44LDEuOUwxMS44LDEuOSAgICBDMTEuOCwxLjksMTEuOCwxLjksMTEuOCwxLjl6IE0xMS4yLDEuMUMxMS4yLDEuMSwxMS4yLDEuMSwxMS4yLDEuMUwxMS4yLDEuMXoiLz4gICAgPC9nPjwvc3ZnPg==);
background-position: 6px center;
background-repeat: no-repeat;
background-size: 13px;
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%);
height: 1.25rem;
}
/* Tailwind sets all svg to block */
.navds-checkbox__icon > svg {
display: inline;
}
.navds-checkbox--small > .navds-checkbox__input:checked + .navds-checkbox__label::before {
background-position: 4px center;
background-position: 0.25rem center;
}
.navds-checkbox__input:indeterminate:focus-visible + .navds-checkbox__label::before,
@@ -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);
+3 -3
View File
@@ -104,8 +104,8 @@
.navds-search__button-search {
flex-shrink: 0;
min-width: 64px;
min-height: 32px;
min-width: 4rem;
min-height: 2rem;
border-radius: 0;
border-top-right-radius: var(--a-border-radius-medium);
border-bottom-right-radius: var(--a-border-radius-medium);
@@ -116,7 +116,7 @@
}
.navds-form-field--small .navds-search__button-search {
min-width: 40px;
min-width: 2.5rem;
}
.navds-search__button-search.navds-button--secondary {
+2 -2
View File
@@ -6,7 +6,7 @@
color: var(--ac-select-text, var(--a-text-default));
width: 100%;
box-sizing: border-box;
min-height: 48px;
min-height: 3rem;
display: inline-block;
position: relative;
padding: 0.5rem;
@@ -45,7 +45,7 @@
}
.navds-form-field--small .navds-select__input {
min-height: 32px;
min-height: 2rem;
padding: 0 2rem 0 0.25rem;
}
+18 -18
View File
@@ -1,6 +1,6 @@
.navds-switch {
position: relative;
min-height: 48px;
min-height: 3rem;
width: fit-content;
}
@@ -10,7 +10,7 @@
.navds-switch--small {
position: relative;
min-height: 32px;
min-height: 2rem;
}
/* Input */
@@ -18,14 +18,14 @@
cursor: pointer;
position: absolute;
z-index: 1;
width: 48px;
height: 48px;
width: 3rem;
height: 3rem;
opacity: 0;
top: 0;
}
.navds-switch--small > .navds-switch__input {
height: 32px;
height: 2rem;
top: 0;
}
@@ -47,11 +47,11 @@
}
.navds-switch--small > .navds-switch__label-wrapper > .navds-switch__content {
padding: calc(var(--a-spacing-2) - 2px) 0 calc(var(--a-spacing-2) - 2px) 3.25rem;
padding: calc(var(--a-spacing-2) - var(--a-spacing-05)) 0 calc(var(--a-spacing-2) - var(--a-spacing-05)) 3.25rem;
}
.navds-switch--right.navds-switch--small > .navds-switch__label-wrapper > .navds-switch__content {
padding: calc(var(--a-spacing-2) - 2px) 3.25rem calc(var(--a-spacing-2) - 2px) 0;
padding: calc(var(--a-spacing-2) - var(--a-spacing-05)) 3.25rem calc(var(--a-spacing-2) - var(--a-spacing-05)) 0;
}
.navds-switch--with-description,
@@ -70,8 +70,8 @@
/* Track */
.navds-switch__track {
width: 44px;
height: 24px;
width: 2.75rem;
height: 1.5rem;
background-color: var(--ac-switch-bg, var(--a-surface-neutral));
position: absolute;
top: var(--a-spacing-3);
@@ -125,12 +125,12 @@
background-color: var(--ac-switch-thumb-bg, var(--a-surface-default));
color: var(--ac-switch-thumb-icon, var(--a-icon-subtle));
border-radius: var(--a-border-radius-full);
width: 20px;
height: 20px;
width: 1.25rem;
height: 1.25rem;
position: absolute;
transform: translateX(0);
left: 2px;
top: 2px;
left: var(--a-spacing-05);
top: var(--a-spacing-05);
transition: transform 250ms cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
align-items: center;
@@ -138,17 +138,17 @@
}
.navds-switch__input:checked ~ .navds-switch__track > .navds-switch__thumb {
transform: translateX(20px);
transform: translateX(1.25rem);
color: var(--ac-switch-thumb-icon-checked, var(--a-icon-success));
}
@media (hover: hover) and (pointer: fine) {
.navds-switch__input:hover ~ .navds-switch__track > .navds-switch__thumb {
transform: translateX(2px);
transform: translateX(0.125rem);
}
.navds-switch__input:checked:hover ~ .navds-switch__track > .navds-switch__thumb {
transform: translateX(18px);
transform: translateX(1.125rem);
}
}
@@ -157,7 +157,7 @@
}
.navds-switch__input:checked:disabled:hover ~ .navds-switch__track > .navds-switch__thumb {
transform: translateX(20px);
transform: translateX(1.25rem);
}
/* Loader */
@@ -216,6 +216,6 @@
}
.navds-switch--readonly > .navds-switch__input:checked:hover ~ .navds-switch__track > .navds-switch__thumb {
transform: translateX(20px);
transform: translateX(1.25rem);
}
}
+3 -3
View File
@@ -13,7 +13,7 @@
background-color: var(--ac-textfield-bg, var(--a-surface-default));
border-radius: var(--a-border-radius-medium);
border: 1px solid var(--ac-textfield-border, var(--a-border-default));
min-height: 48px;
min-height: 3rem;
width: 100%;
color: var(--ac-textfield-text, var(--a-text-default));
}
@@ -27,8 +27,8 @@
}
.navds-form-field--small .navds-text-field__input {
padding: 0 0.5rem;
min-height: 32px;
padding: 0 var(--a-spacing-2);
min-height: 2rem;
}
.navds-text-field__input:hover {
+3 -3
View File
@@ -51,7 +51,7 @@
}
.navds-form-field--small .navds-textarea__input {
padding: 6px;
padding: 0.375rem;
}
.navds-form-field--small .navds-textarea--counter.navds-textarea__input {
@@ -64,8 +64,8 @@
font-style: italic;
position: absolute;
text-align: left;
left: 1px;
bottom: 1px;
left: 0.0625rem;
bottom: 0.0625rem;
padding: var(--a-spacing-1) var(--a-spacing-2);
}
+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";
+1 -1
View File
@@ -2,7 +2,7 @@
display: flex;
align-self: stretch;
background: var(--ac-internalheader-bg, var(--a-surface-inverted));
min-height: var(--a-spacing-12);
min-height: 3rem;
--navds-internalheader-focus: inset 0 0 0 2px var(--a-border-focus-on-inverted);
}
+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: 6px;
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;
}
}
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-css",
"version": "4.7.2",
"version": "5.0.1",
"description": "CSS for NAV Designsystem",
"author": "Aksel | NAV designsystem team",
"keywords": [
@@ -27,11 +27,11 @@
"css:get-version": "node config/get-version.js"
},
"devDependencies": {
"@navikt/ds-tokens": "^4.7.2",
"@navikt/ds-tokens": "^5.0.1",
"cssnano": "6.0.0",
"fast-glob": "3.2.11",
"lodash": "4.17.21",
"nodemon": "^2.0.12",
"nodemon": "^3.0.1",
"normalize.css": "^8.0.1",
"postcss": "^8.4.0",
"postcss-cli": "^9.0.0",
+6 -6
View File
@@ -8,12 +8,12 @@
gap: var(--a-spacing-05);
color: var(--ac-read-more-text, var(--a-text-action));
border-radius: var(--a-border-radius-small);
padding: var(--a-spacing-1) var(--a-spacing-1) var(--a-spacing-1) 2px;
padding: var(--a-spacing-1) var(--a-spacing-1) var(--a-spacing-1) var(--a-spacing-05);
text-align: start;
}
.navds-read-more--small .navds-read-more__button {
padding: 2px var(--a-spacing-1) 2px 2px;
padding: var(--a-spacing-05) var(--a-spacing-1) var(--a-spacing-05) var(--a-spacing-05);
}
.navds-read-more__button:hover {
@@ -39,14 +39,14 @@
.navds-read-more__content {
margin-top: var(--a-spacing-1);
border-left: 2px solid var(--ac-read-more-line, var(--a-border-divider));
margin-left: 13px;
padding-left: 13px;
margin-left: 0.8125rem;
padding-left: 0.8125rem;
color: var(--a-text-default);
}
.navds-read-more--small .navds-read-more__content {
margin-left: 11px;
padding-left: 11px;
margin-left: 0.6875rem;
padding-left: 0.6875rem;
}
.navds-read-more__content--closed {
+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))))
);
}
}
+1 -1
View File
@@ -113,7 +113,7 @@ button.navds-stepper__step {
.navds-stepper__circle--success {
border: none;
background: none;
font-size: 28px;
font-size: 1.75rem;
color: var(--a-text-on-action);
background-color: var(--ac-stepper-text, var(--a-surface-action));
}
+64 -37
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,17 +74,22 @@
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 {
top: -12px;
top: -0.75rem;
}
.navds-table .navds-checkbox--small .navds-checkbox__input {
top: -6px;
top: -0.375rem;
}
.navds-table .navds-checkbox .navds-checkbox__label {
@@ -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: 56px;
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: 36px;
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);
}
+4 -4
View File
@@ -20,7 +20,7 @@
.navds-tabs__scroll-button {
padding: var(--a-spacing-3) var(--a-spacing-4);
width: 44px;
width: 2.75rem;
display: flex;
justify-content: center;
align-items: center;
@@ -38,11 +38,11 @@
.navds-tabs--small .navds-tabs__scroll-button {
padding: 0.375rem var(--a-spacing-4);
width: 32px;
width: 2rem;
}
.navds-tabs__tab {
min-height: 48px;
min-height: 3rem;
padding: var(--a-spacing-3) var(--a-spacing-4);
display: inline-flex;
justify-content: center;
@@ -92,7 +92,7 @@
}
.navds-tabs__tab--small {
min-height: 32px;
min-height: 2rem;
padding: 0.375rem var(--a-spacing-4);
}
+3 -3
View File
@@ -5,17 +5,17 @@
align-items: center;
justify-content: center;
padding: var(--a-spacing-05) var(--a-spacing-2);
min-height: 32px;
min-height: 2rem;
line-height: 1;
}
.navds-tag--small {
min-height: 24px;
min-height: 1.5rem;
padding: 0 0.375rem;
}
.navds-tag--xsmall {
min-height: 20px;
min-height: 1.25rem;
padding: 0 var(--a-spacing-1);
}
+3 -2
View File
@@ -12,6 +12,7 @@
height: 1rem;
box-sizing: content-box;
grid-column: 2;
margin-bottom: var(--a-spacing-1);
}
.navds-timeline__row-label {
@@ -181,7 +182,7 @@
0 0 0 4px var(--ac-timeline-pin-bg, var(--a-surface-danger)), 0 0 0 5px var(--a-surface-default);
z-index: 2;
border-radius: var(--a-border-radius-full);
padding: 3.5px;
padding: var(--a-spacing-1);
position: relative;
}
@@ -232,7 +233,7 @@
.navds-timeline__pin-wrapper::before {
content: "";
top: var(--navdsc-timeline-pin-size);
height: calc(94% - var(--navdsc-timeline-pin-size));
height: calc(88% - var(--navdsc-timeline-pin-size));
width: 1px;
margin: 0 auto;
background: var(--a-surface-inverted);
+5 -6
View File
@@ -8,7 +8,7 @@
border-radius: var(--a-border-radius-medium);
background-color: var(--ac-toggle-group-bg, var(--a-surface-transparent));
box-shadow: inset 0 0 0 1px var(--ac-toggle-group-border, var(--a-border-default));
padding: calc(var(--a-spacing-1) + 1px);
padding: calc(var(--a-spacing-1) + 0.0625rem);
gap: var(--a-spacing-1);
display: inline-grid;
grid-auto-flow: column;
@@ -16,7 +16,7 @@
}
.navds-toggle-group--small {
padding: calc(var(--a-spacing-05) + 1px);
padding: calc(var(--a-spacing-05) + 0.0625rem);
}
.navds-toggle-group--neutral {
@@ -29,12 +29,12 @@
align-items: center;
justify-content: center;
padding: var(--a-spacing-1) var(--a-spacing-3);
min-height: 38px;
min-height: 2.375rem;
border: none;
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"],
@@ -124,7 +123,7 @@
.navds-toggle-group--small > .navds-toggle-group__button {
padding: var(--a-spacing-05) var(--a-spacing-3);
min-height: 26px;
min-height: 1.625rem;
min-width: fit-content;
}
+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",
+4 -4
View File
@@ -25,8 +25,8 @@
}
.navds-tooltip__arrow {
height: 7px;
width: 7px;
height: 0.4375rem;
width: 0.4375rem;
transform: rotate(45deg);
z-index: -1;
background-color: var(--ac-tooltip-bg, var(--a-surface-inverted));
@@ -45,8 +45,8 @@
color: var(--ac-tooltip-key-text, var(--a-text-on-inverted));
border-radius: var(--a-border-radius-small);
padding: 0 var(--a-spacing-1);
min-width: 18px;
height: 18px;
min-width: 1.125rem;
height: 1.125rem;
display: inline-flex;
align-items: center;
justify-content: center;
+165
View File
@@ -1,5 +1,170 @@
# @navikt/ds-react
## 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
- Textfield: La til type 'time' som tilgjengelig option ([#2137](https://github.com/navikt/aksel/pull/2137))
- Updated dependencies []:
- @navikt/aksel-icons@4.7.4
## 4.7.3
### Patch Changes
- Chips: Removable Chips submitter ikke forms ved klikk lengre ([#2124](https://github.com/navikt/aksel/pull/2124))
- Updated dependencies []:
- @navikt/aksel-icons@4.7.3
## 4.7.2
### Patch Changes
+3 -5
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-react",
"version": "4.7.2",
"version": "5.0.1",
"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.2",
"@navikt/aksel-icons": "^5.0.1",
"@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 },
},
};
+8 -8
View File
@@ -1,7 +1,6 @@
import { XMarkIcon } from "@navikt/aksel-icons";
import cl from "clsx";
import React, { forwardRef } from "react";
import { OverridableComponent } from "../util/OverridableComponent";
export interface RemovableChipsProps
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
@@ -22,26 +21,27 @@ export interface RemovableChipsProps
onDelete?: () => void;
}
export const RemovableChips: OverridableComponent<
RemovableChipsProps,
HTMLButtonElement
> = forwardRef(
export const RemovableChips = forwardRef<
HTMLButtonElement,
RemovableChipsProps
>(
(
{
className,
children,
variant = "action",
as: Component = "button",
removeLabel = "slett",
onDelete,
type = "button",
...rest
},
ref
) => {
return (
<Component
<button
{...rest}
ref={ref}
type={type}
className={cl(
"navds-chips__chip navds-chips__removable navds-chips--icon-right",
className,
@@ -57,7 +57,7 @@ export const RemovableChips: OverridableComponent<
<span className="navds-chips__removable-icon">
<XMarkIcon aria-hidden />
</span>
</Component>
</button>
);
}
);
@@ -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]
);
@@ -1,8 +1,8 @@
import cl from "clsx";
import React, { forwardRef } from "react";
import { OverridableComponent } from "../../../util/OverridableComponent";
import GroupedHeading, { GroupedHeadingProps } from "./Heading";
import GroupedItem, { GroupedItemProps } from "./Item";
import GroupedHeading, { GroupedHeadingProps } from "./GroupedHeading";
import GroupedItem, { GroupedItemProps } from "./GroupedItem";
export interface GroupedListProps
extends React.HTMLAttributes<HTMLDListElement> {
+1 -1
View File
@@ -31,7 +31,7 @@ export interface TextFieldProps
* Type of form control. Picking the correct type helps user fill inn their required information
* @default "text"
*/
type?: "email" | "number" | "password" | "tel" | "text" | "url";
type?: "email" | "number" | "password" | "tel" | "text" | "url" | "time";
}
/**
@@ -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.
*
+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 (
+1 -1
View File
@@ -5,7 +5,7 @@ import { useId } from "..";
export interface LoaderProps extends SVGProps<SVGSVGElement> {
/**
* Changes Loader width/height
* 64px | 40px | 32px | 24px | 20px | 16px
* 88px | 64px | 40px | 32px | 24px | 20px | 16px
* @default "medium"
*/
size?:
+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 | 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;
Loaded 100 of 219 files, more files were not shown because too many files have changed in this diff. Show more