Compare commits

...
Author SHA1 Message Date
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
139 changed files with 4255 additions and 1246 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}}
+20
View File
@@ -1,5 +1,25 @@
# @navikt/aksel-icons
## 4.12.0
## 4.11.2
## 4.11.1
## 4.11.0
## 4.10.2
## 4.10.1
## 4.10.0
## 4.9.1
## 4.9.0
## 4.8.0
## 4.7.4
## 4.7.3
@@ -58,7 +58,7 @@
.icon-button {
min-height: 3rem;
min-width: 3rem;
border-radius: 2px;
border-radius: var(--a-border-radius-small);
display: grid;
place-content: center;
background-color: var(--a-surface-default);
@@ -1,9 +1,9 @@
import meta from "@navikt/aksel-icons/metadata";
const subCategorizeIcons = (
icons: typeof meta[1][]
): { sub_category: string; icons: typeof meta[1][] }[] => {
const categories: { sub_category: string; icons: typeof meta[1][] }[] = [];
icons: (typeof meta)[1][]
): { sub_category: string; icons: (typeof meta)[1][] }[] => {
const categories: { sub_category: string; icons: (typeof meta)[1][] }[] = [];
for (const icon of icons) {
const i = categories.findIndex(
@@ -19,14 +19,14 @@ const subCategorizeIcons = (
};
export const categorizeIcons = (
icons: typeof meta[1][]
icons: (typeof meta)[1][]
): {
category: string;
sub_categories: { sub_category: string; icons: typeof meta[1][] }[];
sub_categories: { sub_category: string; icons: (typeof meta)[1][] }[];
}[] => {
const categories: {
category: string;
icons: typeof meta[1][];
icons: (typeof meta)[1][];
}[] = [];
for (const icon of icons) {
@@ -42,14 +42,14 @@ export const categorizeIcons = (
.map((x) => ({ ...x, sub_categories: subCategorizeIcons(x.icons) }));
};
const noFill = (icon: typeof meta[1], icons: typeof meta[1][]) => {
const noFill = (icon: (typeof meta)[1], icons: (typeof meta)[1][]) => {
const foundFill = icons.find(
(x) => x.name.endsWith("Fill") && x.name.replace("Fill", "") === icon.name
);
return !foundFill;
};
export const getFillIcon = (icons: typeof meta[1][]) => {
export const getFillIcon = (icons: (typeof meta)[1][]) => {
return icons.filter(
(x, _, z) => x.variant.toLowerCase() === "fill" || noFill(x, z)
);
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/aksel-icons",
"version": "4.7.4",
"version": "4.12.0",
"author": "Aksel | NAV",
"homepage": "https://aksel.nav.no/ikoner",
"repository": {
+20
View File
@@ -1,5 +1,25 @@
# @navikt/aksel-stylelint
## 4.12.0
## 4.11.2
## 4.11.1
## 4.11.0
## 4.10.2
## 4.10.1
## 4.10.0
## 4.9.1
## 4.9.0
## 4.8.0
## 4.7.4
## 4.7.3
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/aksel-stylelint",
"version": "4.7.4",
"version": "4.12.0",
"author": "Aksel | NAV",
"homepage": "https://aksel.nav.no/grunnleggende/kode/stylelint",
"repository": {
@@ -35,8 +35,8 @@
"dev": "yarn watch:lint"
},
"devDependencies": {
"@navikt/ds-css": "^4.7.4",
"@navikt/ds-tokens": "^4.7.4",
"@navikt/ds-css": "^4.12.0",
"@navikt/ds-tokens": "^4.12.0",
"@types/jest": "^29.0.0",
"concurrently": "7.2.1",
"copyfiles": "2.4.1",
+70
View File
@@ -1,5 +1,75 @@
# @navikt/aksel
## 4.12.0
### Patch Changes
- Updated dependencies [[`1c482e03b`](https://github.com/navikt/aksel/commit/1c482e03b845c4e6f6f1e174a58992fbeeabb52e), [`107d6f3d9`](https://github.com/navikt/aksel/commit/107d6f3d935cbc9bb94bc15cea63431cd15bbd61), [`09d41e9b6`](https://github.com/navikt/aksel/commit/09d41e9b6da5486a55faaf8088191b4a8374e420), [`a402777c7`](https://github.com/navikt/aksel/commit/a402777c70a18899ef4239b10a6335a671e26167)]:
- @navikt/ds-css@4.12.0
## 4.11.2
### Patch Changes
- Updated dependencies [[`72321e384`](https://github.com/navikt/aksel/commit/72321e3846f785318ebdba9f8e06cf13b09cd524)]:
- @navikt/ds-css@4.11.2
## 4.11.1
### Patch Changes
- Updated dependencies [[`e68ab5cb2`](https://github.com/navikt/aksel/commit/e68ab5cb2cd91c2f0234c864ee71821ed07a8203)]:
- @navikt/ds-css@4.11.1
## 4.11.0
### Patch Changes
- Updated dependencies [[`64901adcc`](https://github.com/navikt/aksel/commit/64901adccb8f19fbe06cdab4a70d47978e90e343), [`0de8238ec`](https://github.com/navikt/aksel/commit/0de8238ecf22e9f1fbc0e1b0cfe7a5cd7254901e), [`647b14a23`](https://github.com/navikt/aksel/commit/647b14a23888f82bacc4806d3afaacb5e39178fa)]:
- @navikt/ds-css@4.11.0
## 4.10.2
### Patch Changes
- Updated dependencies []:
- @navikt/ds-css@4.10.2
## 4.10.1
### Patch Changes
- Updated dependencies [[`d3bd1202e`](https://github.com/navikt/aksel/commit/d3bd1202eddb23dd3f1f09c3e4086ff3b19737d6)]:
- @navikt/ds-css@4.10.1
## 4.10.0
### Patch Changes
- Updated dependencies [[`267799ca0`](https://github.com/navikt/aksel/commit/267799ca0ecc0c549e159f94124befb654e29331)]:
- @navikt/ds-css@4.10.0
## 4.9.1
### Patch Changes
- Updated dependencies [[`eff8a902c`](https://github.com/navikt/aksel/commit/eff8a902c50fd5ebc56616e866aa0a61b9eb5efc)]:
- @navikt/ds-css@4.9.1
## 4.9.0
### Patch Changes
- Updated dependencies [[`e033b34c3`](https://github.com/navikt/aksel/commit/e033b34c3b85fb98dd11dd0300bb20075e1b06e8)]:
- @navikt/ds-css@4.9.0
## 4.8.0
### Patch Changes
- Updated dependencies [[`8a82e1c1f`](https://github.com/navikt/aksel/commit/8a82e1c1f66ea38fca77b25d74b14926bd817dd9)]:
- @navikt/ds-css@4.8.0
## 4.7.4
### Patch Changes
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/aksel",
"version": "4.7.4",
"version": "4.12.0",
"description": "Aksel command line interface. Handles css-imports, codemods and more",
"author": "Aksel | NAV designsystem team",
"license": "MIT",
@@ -29,7 +29,7 @@
},
"homepage": "https://aksel.nav.no/grunnleggende/kode/kommandolinje",
"dependencies": {
"@navikt/ds-css": "4.7.4",
"@navikt/ds-css": "4.12.0",
"axios": "1.3.6",
"chalk": "4.1.0",
"clipboardy": "^2.3.0",
+68
View File
@@ -1,5 +1,73 @@
# @navikt/ds-css
## 4.12.0
### Minor Changes
- CopyButton: Har nå prop 'iconPosition' for å høyre/venstre aligne ikon ([#2173](https://github.com/navikt/aksel/pull/2173))
### Patch Changes
- List: Fikset sentrering, margins ([#2168](https://github.com/navikt/aksel/pull/2168))
- Checkbox: Checkmark er nå SVG-ikon og ikke Base64 ([#2171](https://github.com/navikt/aksel/pull/2171))
- Alert: Fikset alignment av status-ikon mot tekst ([#2179](https://github.com/navikt/aksel/pull/2179))
## 4.11.2
### Patch Changes
- :lipstick: ToggleGroup: fjern semibold fra selected button ([#2167](https://github.com/navikt/aksel/pull/2167))
## 4.11.1
### Patch Changes
- Border-radius: Fikset hardkodet border-radius i Datepicker, ToggleGroup og Combobox. ([#2159](https://github.com/navikt/aksel/pull/2159))
## 4.11.0
### Minor Changes
- HGrid: Ny komponent 'HGrid' er ute i Beta: [Dokumentasjon](https://aksel.nav.no/komponenter/core/hgrid) ([#1838](https://github.com/navikt/aksel/pull/1838))
### Patch Changes
- List: Fikset alignment av ikoner ([#2149](https://github.com/navikt/aksel/pull/2149))
- Font: La til egen font for semibold italic for bedre skalering cross-browser ([#2150](https://github.com/navikt/aksel/pull/2150))
## 4.10.2
### Patch Changes
- Link: Fikset visited farger og inline SVG-reset ved bruk av tailwind
## 4.10.0
### Minor Changes
- Link: La til 'variant', 'underline' og 'inlineText'-prop ([#2093](https://github.com/navikt/aksel/pull/2093))
## 4.9.1
### Patch Changes
- Tweaks to combobox - updated example, small bugfixes, better affordance for selected options and added flag for "isAddedByUser" to onToggleSelected ([#2144](https://github.com/navikt/aksel/pull/2144))
## 4.9.0
### Minor Changes
- Table: ExpandableRow kan nå åpnes med 'expandOnRowClick'-prop ([#2127](https://github.com/navikt/aksel/pull/2127))
## 4.8.0
### Minor Changes
- Nye komponenter `VStack`, `HStack` og `Spacer` for å enklere kunne lage layout med flexbox og spacing-variabler. ([#2040](https://github.com/navikt/aksel/pull/2040))
## 4.7.4
### Patch Changes
+8 -1
View File
@@ -24,10 +24,17 @@
font-size: 1.5rem;
align-self: flex-start;
height: var(--a-font-line-height-xlarge);
margin: var(--a-spacing-1);
margin-inline: var(--a-spacing-1);
margin-block: 0;
margin-block-start: -1px;
background: radial-gradient(circle, var(--a-surface-default) 50%, 0, transparent);
}
.navds-alert--close-button > .navds-alert__icon,
.navds-alert--small > .navds-alert__icon {
margin-block-start: 0;
}
.navds-alert--small > .navds-alert__icon {
height: var(--a-font-line-height-large);
}
+7
View File
@@ -5,6 +5,13 @@
font-display: swap;
src: url("https://cdn.nav.no/aksel/fonts/SourceSans3-italic.woff2") format("woff2");
}
@font-face {
font-family: "Source Sans Pro";
font-style: italic;
font-weight: 600;
font-display: swap;
src: url("https://cdn.nav.no/aksel/fonts/SourceSans3-italic-latin-semibold") format("woff2");
}
@font-face {
font-family: "Source Sans Pro";
font-style: normal;
+17 -1
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",
@@ -134,6 +138,18 @@ const StyleMappings = {
{ component: "Select", main: formCss, dependencies: [typoCss] },
{ component: "Skeleton", main: "skeleton.css", dependencies: [] },
{ component: "Stepper", main: "stepper.css", dependencies: [typoCss] },
{
component: "HStack",
main: "stack.css",
},
{
component: "VStack",
main: "stack.css",
},
{
component: "Spacer",
main: "stack.css",
},
{ component: "Switch", main: formCss, dependencies: [typoCss] },
{ component: "Table", main: "table.css", dependencies: [typoCss] },
{ component: "Tabs", main: "tabs.css", dependencies: [typoCss] },
+7
View File
@@ -52,9 +52,16 @@
.navds-copybutton__icon {
font-size: 1.5rem;
display: flex;
}
.navds-copybutton__icon:first-of-type {
margin-left: -0.25rem;
}
.navds-copybutton__icon:last-of-type {
margin-right: -0.25rem;
}
.navds-copybutton__icon:only-child {
margin: 0;
}
+1 -1
View File
@@ -233,7 +233,7 @@
text-decoration: none;
border: none;
background: none;
border-radius: 3px;
border-radius: calc(var(--a-border-radius-medium) - 1px);
padding: var(--a-spacing-3);
align-items: center;
justify-content: center;
+26 -3
View File
@@ -9,9 +9,10 @@
position: relative;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
width: 100%;
border: 1px solid var(--a-border-default);
padding: calc(var(--a-spacing-2) - 2px) var(--a-spacing-2);
}
.navds-combobox__wrapper-inner > :first-child {
@@ -33,6 +34,7 @@
.navds-combobox__selected-options {
gap: var(--a-spacing-2);
align-items: center;
}
.navds-combobox__selected-options > li {
@@ -67,6 +69,7 @@
margin: 0;
min-width: 10ch;
width: 100%;
height: 2rem;
}
.navds-combobox__input:focus-visible {
@@ -85,7 +88,7 @@
}
@supports not selector(:focus-visible) {
.navds-combobox__input:focus-visible {
.navds-combobox__input:focus {
outline: none;
border: none;
box-shadow: none;
@@ -101,6 +104,17 @@
}
}
@supports not selector(:has) {
.navds-combobox--focused .navds-combobox__wrapper-inner {
box-shadow: 0 0 0 1px var(--a-surface-default) inset, var(--a-shadow-focus);
box-shadow: var(--a-shadow-focus);
}
.navds-combobox--focused .navds-combobox__wrapper-inner.navds-combobox__wrapper-inner--virtually-unfocused {
box-shadow: none;
}
}
.navds-combobox__button-clear {
border-radius: var(--a-border-radius-medium);
color: var(--ac-combobox-clear-icon, var(--a-text-subtle));
@@ -239,10 +253,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 +288,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 */
+21 -5
View File
@@ -114,14 +114,27 @@
}
.navds-checkbox__input:checked + .navds-checkbox__label::before {
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMyAxMCI+ICAgIDxnPiAgICA8cGF0aCBmaWxsPSIjRkZGRkZGIiBkPSJNNCwxMGMtMC40LDAtMC44LTAuMS0xLjEtMC40TDAuNCw3LjFDMC4xLDYuOCwwLDYuNCwwLDZzMC4yLTAuOCwwLjUtMS4xQzEsNC40LDIsNC40LDIuNSw0LjlMNCw2LjRsNi40LTYgICAgQzEwLjgsMC4xLDExLjEsMCwxMS41LDBjMC40LDAsMC44LDAuMiwxLDAuNWMwLjYsMC42LDAuNSwxLjYtMC4xLDIuMXYwTDUsOS42QzQuNyw5LjksNC40LDEwLDQsMTB6IE0xMS44LDEuOUwxMS44LDEuOSAgICBDMTEuOCwxLjksMTEuOCwxLjksMTEuOCwxLjl6IE0xMS4yLDEuMUMxMS4yLDEuMSwxMS4yLDEuMSwxMS4yLDEuMUwxMS4yLDEuMXoiLz4gICAgPC9nPjwvc3ZnPg==);
background-position: 0.375rem center;
background-repeat: no-repeat;
background-size: 0.8125rem;
box-shadow: none;
background-color: var(--ac-radio-checkbox-action, var(--a-surface-action));
}
.navds-checkbox__input:where(:not(:checked)) + .navds-checkbox__label > .navds-checkbox__icon {
display: none;
}
.navds-checkbox__input:checked + .navds-checkbox__label > .navds-checkbox__icon {
color: var(--ac-radio-checkbox-bg, var(--a-surface-default));
position: absolute;
height: 1.5rem;
transform: translate(0.375rem);
pointer-events: none;
}
.navds-checkbox--small .navds-checkbox__input:checked + .navds-checkbox__label > .navds-checkbox__icon {
transform: translate(0.25rem, -10%);
height: 1.25rem;
}
.navds-checkbox--small > .navds-checkbox__input:checked + .navds-checkbox__label::before {
background-position: 0.25rem center;
}
@@ -283,11 +296,14 @@
}
.navds-checkbox--readonly > .navds-checkbox__input:checked + .navds-checkbox__label::before {
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMyAxMCI+ICAgIDxnPiAgICA8cGF0aCBmaWxsPSJyZ2JhKDAsIDAsIDAsIDAuNjUpIiBkPSJNNCwxMGMtMC40LDAtMC44LTAuMS0xLjEtMC40TDAuNCw3LjFDMC4xLDYuOCwwLDYuNCwwLDZzMC4yLTAuOCwwLjUtMS4xQzEsNC40LDIsNC40LDIuNSw0LjlMNCw2LjRsNi40LTYgICAgQzEwLjgsMC4xLDExLjEsMCwxMS41LDBjMC40LDAsMC44LDAuMiwxLDAuNWMwLjYsMC42LDAuNSwxLjYtMC4xLDIuMXYwTDUsOS42QzQuNyw5LjksNC40LDEwLDQsMTB6IE0xMS44LDEuOUwxMS44LDEuOSAgICBDMTEuOCwxLjksMTEuOCwxLjksMTEuOCwxLjl6IE0xMS4yLDEuMUMxMS4yLDEuMSwxMS4yLDEuMSwxMS4yLDEuMUwxMS4yLDEuMXoiLz4gICAgPC9nPjwvc3ZnPg==);
box-shadow: inset 0 0 0 2px var(--__ac-radio-checkbox-readonly-border);
background-color: var(--__ac-radio-checkbox-readonly-bg);
}
.navds-checkbox--readonly > .navds-checkbox__input:checked + .navds-checkbox__label > .navds-checkbox__icon {
color: var(--a-icon-subtle);
}
.navds-radio--readonly > .navds-radio__input:checked + .navds-radio__label::before {
box-shadow: inset 0 0 0 2px var(--__ac-radio-checkbox-readonly-border), inset 0 0 0 4px var(--__ac-radio-checkbox-readonly-bg);
background-color: var(--a-icon-subtle);
+55
View File
@@ -0,0 +1,55 @@
.navds-hgrid {
--__ac-hgrid-columns-xs: initial;
--__ac-hgrid-columns-sm: initial;
--__ac-hgrid-columns-md: initial;
--__ac-hgrid-columns-lg: initial;
--__ac-hgrid-columns-xl: initial;
--__ac-hgrid-columns: var(--__ac-hgrid-columns-xs);
--__ac-hgrid-gap-xs: initial;
--__ac-hgrid-gap-sm: initial;
--__ac-hgrid-gap-md: initial;
--__ac-hgrid-gap-lg: initial;
--__ac-hgrid-gap-xl: initial;
--__ac-hgrid-gap: var(--__ac-hgrid-gap-xs);
display: grid;
grid-template-columns: var(--__ac-hgrid-columns);
gap: var(--__ac-hgrid-gap);
}
@media (min-width: 480px) {
.navds-hgrid {
--__ac-hgrid-columns: var(--__ac-hgrid-columns-sm, var(--__ac-hgrid-columns-xs));
--__ac-hgrid-gap: var(--__ac-hgrid-gap-sm, var(--__ac-hgrid-gap-xs));
}
}
@media (min-width: 768px) {
.navds-hgrid {
--__ac-hgrid-columns: var(--__ac-hgrid-columns-md, var(--__ac-hgrid-columns-sm, var(--__ac-hgrid-columns-xs)));
--__ac-hgrid-gap: var(--__ac-hgrid-gap-md, var(--__ac-hgrid-gap-sm, var(--__ac-hgrid-gap-xs)));
}
}
@media (min-width: 1024px) {
.navds-hgrid {
--__ac-hgrid-columns: var(
--__ac-hgrid-columns-lg,
var(--__ac-hgrid-columns-md, var(--__ac-hgrid-columns-sm, var(--__ac-hgrid-columns-xs)))
);
--__ac-hgrid-gap: var(--__ac-hgrid-gap-lg, var(--__ac-hgrid-gap-md, var(--__ac-hgrid-gap-sm, var(--__ac-hgrid-gap-xs))));
}
}
@media (min-width: 1280px) {
.navds-hgrid {
--__ac-hgrid-columns: var(
--__ac-hgrid-columns-xl,
var(--__ac-hgrid-columns-lg, var(--__ac-hgrid-columns-md, var(--__ac-hgrid-columns-sm, var(--__ac-hgrid-columns-xs))))
);
--__ac-hgrid-gap: var(
--__ac-hgrid-gap-xl,
var(--__ac-hgrid-gap-lg, var(--__ac-hgrid-gap-md, var(--__ac-hgrid-gap-sm, var(--__ac-hgrid-gap-xs))))
);
}
}
+2
View File
@@ -14,6 +14,7 @@
@import "guide-panel.css";
@import "form/index.css";
@import "help-text.css";
@import "hgrid.css";
@import "internalheader.css";
@import "link.css";
@import "loader.css";
@@ -29,6 +30,7 @@
@import "link-panel.css";
@import "read-more.css";
@import "skeleton.css";
@import "stack.css";
@import "stepper.css";
@import "table.css";
@import "tabs.css";
+35 -3
View File
@@ -6,15 +6,35 @@
gap: var(--a-spacing-1);
}
.navds-alert .navds-link,
.navds-confirmation-panel .navds-link {
color: var(--a-text-default);
.navds-link.navds-link--inline-text {
display: inline;
}
.navds-link.navds-link--inline-text > svg {
/* stylelint-disable-next-line plugin/declaration-block-no-ignored-properties */
margin: 0.1em 0.1em -0.1em;
transform: translateY(0.05em);
display: inline;
vertical-align: baseline;
}
.navds-link:hover {
text-decoration: none;
}
.navds-link.navds-link--remove-underline {
text-decoration: none;
}
.navds-link.navds-link--remove-underline:hover {
text-decoration: underline;
}
.navds-alert .navds-link,
.navds-confirmation-panel .navds-link {
color: var(--a-text-default);
}
.navds-link:focus-visible {
outline: none;
color: var(--ac-link-focus-text, var(--a-text-on-action));
@@ -45,3 +65,15 @@
color: inherit;
flex-shrink: 0;
}
.navds-link--action {
color: var(--ac-link-action-text, var(--a-text-action));
}
.navds-link--neutral {
color: var(--ac-link-neutral-text, var(--a-text-default));
}
.navds-link--subtle {
color: var(--ac-link-subtle-text, var(--a-text-subtle));
}
+40 -9
View File
@@ -1,14 +1,24 @@
.navds-list ul,
.navds-list ol {
padding: 0;
margin-block: var(--a-spacing-4);
}
.navds-list--nested {
margin: var(--a-spacing-4) 0 var(--a-spacing-8) 0;
.navds-list--nested ul,
.navds-list--nested ol {
margin-block: var(--a-spacing-2);
}
.navds-list li:not(.navds-list__item--noMargin) {
margin-bottom: var(--a-spacing-5);
.navds-list li {
margin-block-end: var(--a-spacing-5);
}
.navds-list--small li {
margin-block-end: var(--a-spacing-2);
}
.navds-list--nested li {
margin-block-end: var(--a-spacing-3);
}
.navds-list:last-child > ul > li:last-child,
@@ -34,15 +44,30 @@
.navds-list .navds-list__item-marker--bullet {
display: flex;
margin-top: 0.375rem;
align-items: center;
height: var(--a-font-line-height-large);
grid-column: 1 / 2;
/* stylelint-disable-next-line csstools/value-no-unknown-custom-properties */
color: var(--ac-list-marker-ul-color, var(--ac-list-marker-color, var(--a-icon-default)));
}
.navds-list--small .navds-list__item-marker--bullet {
height: var(--a-font-line-height-medium);
}
.navds-list__item-marker--icon {
/* stylelint-disable-next-line csstools/value-no-unknown-custom-properties */
color: var(--ac-list-marker-icon-color, var(--ac-list-marker-color, var(--a-icon-default)));
display: flex;
align-items: center;
height: var(--a-font-line-height-large);
padding-block-end: 1px;
}
.navds-list--small .navds-list__item-marker--icon {
height: var(--a-font-line-height-medium);
}
.navds-list__item-marker--icon svg {
flex-shrink: 0;
}
.navds-list ul .navds-list__item-content {
@@ -51,6 +76,7 @@
.navds-list ol {
padding-left: var(--a-spacing-2);
list-style: decimal;
}
.navds-list ol li {
@@ -60,7 +86,12 @@
.navds-list ol li::marker {
font-weight: var(--a-font-weight-bold);
font-size: var(--a-font-size-medium);
/* stylelint-disable-next-line csstools/value-no-unknown-custom-properties */
font-size: var(--a-font-size-large);
color: var(--ac-list-marker-ol-color, var(--ac-list-marker-color, var(--a-icon-default)));
line-height: var(--a-font-line-height-large);
}
.navds-list--small ol li::marker {
font-size: var(--a-font-size-small);
line-height: var(--a-font-line-height-medium);
}
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-css",
"version": "4.7.4",
"version": "4.12.0",
"description": "CSS for NAV Designsystem",
"author": "Aksel | NAV designsystem team",
"keywords": [
@@ -27,7 +27,7 @@
"css:get-version": "node config/get-version.js"
},
"devDependencies": {
"@navikt/ds-tokens": "^4.7.4",
"@navikt/ds-tokens": "^4.12.0",
"cssnano": "6.0.0",
"fast-glob": "3.2.11",
"lodash": "4.17.21",
+59
View File
@@ -0,0 +1,59 @@
.navds-stack {
--__ac-stack-align: initial;
--__ac-stack-justify: initial;
--__ac-stack-direction: initial;
--__ac-stack-wrap: initial;
--__ac-stack-gap-xs: initial;
--__ac-stack-gap-sm: initial;
--__ac-stack-gap-md: initial;
--__ac-stack-gap-lg: initial;
--__ac-stack-gap-xl: initial;
--__ac-stack-gap: var(--__ac-stack-gap-xs);
gap: var(--__ac-stack-gap);
display: flex;
align-items: var(--__ac-stack-align);
justify-content: var(--__ac-stack-justify);
flex-flow: var(--__ac-stack-direction) var(--__ac-stack-wrap);
}
.navds-stack__spacer {
flex: 1;
justify-self: stretch;
align-self: stretch;
}
.navds-stack > .navds-stack__spacer {
margin-block-start: calc(var(--__ac-stack-gap) * -1);
}
.navds-hstack > .navds-stack__spacer {
margin-inline-start: calc(var(--__ac-stack-gap) * -1);
}
@media (min-width: 480px) {
.navds-stack {
--__ac-stack-gap: var(--__ac-stack-gap-sm, var(--__ac-stack-gap-xs));
}
}
@media (min-width: 768px) {
.navds-stack {
--__ac-stack-gap: var(--__ac-stack-gap-md, var(--__ac-stack-gap-sm, var(--__ac-stack-gap-xs)));
}
}
@media (min-width: 1024px) {
.navds-stack {
--__ac-stack-gap: var(--__ac-stack-gap-lg, var(--__ac-stack-gap-md, var(--__ac-stack-gap-sm, var(--__ac-stack-gap-xs))));
}
}
@media (min-width: 1280px) {
.navds-stack {
--__ac-stack-gap: var(
--__ac-stack-gap-xl,
var(--__ac-stack-gap-lg, var(--__ac-stack-gap-md, var(--__ac-stack-gap-sm, var(--__ac-stack-gap-xs))))
);
}
}
+5 -1
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));
}
@@ -154,6 +154,10 @@
transition: border-bottom-color 190ms cubic-bezier(0.6, 0.04, 0.98, 0.335);
}
.navds-table__expandable-row:not(.navds-table__expandable-row--expansion-disabled):hover {
cursor: pointer;
}
.navds-table__expandable-row--open .navds-table__data-cell {
border-bottom-color: transparent;
}
+1 -2
View File
@@ -34,7 +34,7 @@
cursor: pointer;
background-color: var(--ac-toggle-group-button-bg, var(--a-surface-transparent));
color: var(--ac-toggle-group-button-text, var(--a-text-default));
border-radius: 2px;
border-radius: var(--a-border-radius-small);
min-width: fit-content;
font-weight: var(--a-font-weight-regular);
}
@@ -113,7 +113,6 @@
.navds-toggle-group__button[aria-checked="true"] {
background-color: var(--ac-toggle-group-selected-bg, var(--a-surface-action-selected));
color: var(--ac-toggle-group-selected-text, var(--a-text-on-action));
font-weight: var(--a-font-weight-bold);
}
.navds-toggle-group--neutral > .navds-toggle-group__button[aria-pressed="true"],
+4 -1
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",
+112
View File
@@ -1,5 +1,117 @@
# @navikt/ds-react
## 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
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-react",
"version": "4.7.4",
"version": "4.12.0",
"description": "Aksel react-components for NAV designsystem",
"author": "Aksel | NAV designsystem team",
"license": "MIT",
@@ -38,7 +38,7 @@
},
"dependencies": {
"@floating-ui/react": "0.24.1",
"@navikt/aksel-icons": "^4.7.4",
"@navikt/aksel-icons": "^4.12.0",
"@radix-ui/react-tabs": "1.0.0",
"@radix-ui/react-toggle-group": "1.0.0",
"clsx": "^1.2.1",
+1
View File
@@ -102,6 +102,7 @@ export const Alert = forwardRef<HTMLDivElement, AlertProps>(
{
"navds-alert--full-width": fullWidth,
"navds-alert--inline": inline,
"navds-alert--close-button": closeButton,
}
)}
>
+11 -1
View File
@@ -185,6 +185,16 @@ export const WithCloseButton: Story = {
</BodyLong>
<Link href="#">Id elit esse enim reprehenderit</Link>
</AlertWithCloseButton>
<AlertWithCloseButton>
<DsHeading spacing size="small" level="3">
Aliquip duis est in commodo pariatur
</DsHeading>
<BodyLong>
Ullamco ullamco laborum et commodo sint culpa cupidatat culpa qui
laboris ex. Labore ex occaecat proident qui qui fugiat magna. Fugiat
sint commodo consequat eu aute.
</BodyLong>
</AlertWithCloseButton>
</div>
);
},
@@ -197,6 +207,6 @@ export const WithCloseButton: Story = {
});
const buttonsAfter = canvas.getAllByTitle("Lukk Alert");
expect(buttonsAfter.length).toBe(1);
expect(buttonsAfter.length).toBe(2);
},
};
+1 -5
View File
@@ -126,11 +126,7 @@ export const Button: OverridableComponent<ButtonProps, HTMLButtonElement> =
<span className="navds-button__icon">{icon}</span>
)}
{children && (
<Label
as="span"
size={size === "medium" ? "medium" : "small"}
aria-live="polite"
>
<Label as="span" size={size === "medium" ? "medium" : "small"}>
{children}
</Label>
)}
@@ -148,8 +148,9 @@ export const Loading = {
</div>
),
args: { chromatic: { disableSnapshot: true } },
chromatic: { disableSnapshot: true },
parameters: {
chromatic: { disableSnapshot: true },
},
};
export const Icon = () => (
@@ -275,6 +276,7 @@ export const LoadingWithAs = {
</div>
),
args: { chromatic: { disableSnapshot: true } },
chromatic: { disableSnapshot: true },
parameters: {
chromatic: { disableSnapshot: true },
},
};
@@ -63,6 +63,11 @@ export interface CopyButtonProps
* @default 'Kopiert'
*/
activeTitle?: string;
/**
* Icon position in Button
* @default "left"
*/
iconPosition?: "left" | "right";
}
/**
@@ -91,6 +96,7 @@ export const CopyButton = forwardRef<HTMLButtonElement, CopyButtonProps>(
activeDuration = 2000,
title = "Kopier",
activeTitle = "Kopiert",
iconPosition = "left",
...rest
},
ref
@@ -119,6 +125,25 @@ export const CopyButton = forwardRef<HTMLButtonElement, CopyButtonProps>(
}, activeDuration);
};
const CopyIcon = () => {
return active ? (
<span className="navds-copybutton__icon">
{activeIcon ?? (
<CheckmarkIcon
aria-hidden={!!text}
title={text ? undefined : activeTitle}
/>
)}
</span>
) : (
<span className="navds-copybutton__icon">
{icon ?? (
<FilesIcon aria-hidden={!!text} title={text ? undefined : title} />
)}
</span>
);
};
return (
<button
ref={ref}
@@ -138,25 +163,7 @@ export const CopyButton = forwardRef<HTMLButtonElement, CopyButtonProps>(
onClick={handleClick}
>
<span className="navds-copybutton__content">
{active ? (
<span className="navds-copybutton__icon">
{activeIcon ?? (
<CheckmarkIcon
aria-hidden={!!text}
title={text ? undefined : activeTitle}
/>
)}
</span>
) : (
<span className="navds-copybutton__icon">
{icon ?? (
<FilesIcon
aria-hidden={!!text}
title={text ? undefined : title}
/>
)}
</span>
)}
{iconPosition === "left" && <CopyIcon />}
{text &&
(active ? (
@@ -176,6 +183,7 @@ export const CopyButton = forwardRef<HTMLButtonElement, CopyButtonProps>(
{text}
</Label>
))}
{iconPosition === "right" && <CopyIcon />}
</span>
</button>
);
@@ -60,6 +60,16 @@ export const Variants = {
),
};
export const IconPosition = {
render: () => (
<div className="colgap">
<CopyButton copyText="3.14" iconPosition="left" text="Kopier" />
<CopyButton copyText="3.14" iconPosition="right" text="Kopier" />
</div>
),
};
export const Sizes = {
render: () => (
<div className="colgap">
@@ -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);
}
}
@@ -50,10 +53,12 @@ const ComboboxWrapper = ({
{
"navds-search--error": hasError,
"navds-search--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 (
+28 -8
View File
@@ -31,10 +31,15 @@ export interface ExpandableRowProps extends Omit<RowProps, "content"> {
*/
onOpenChange?: (open: boolean) => void;
/**
* Disable expansio
* Disable expansion. shadeOnHover will not be visible.
* @default false
*/
expansionDisabled?: boolean;
/**
* Makes the whole row clickable
* @default false
*/
expandOnRowClick?: boolean;
/**
* The width of the expanded row's internal cell
* @default 999
@@ -58,6 +63,7 @@ export const ExpandableRow: ExpandableRowType = forwardRef(
open,
onOpenChange,
expansionDisabled = false,
expandOnRowClick = false,
colSpan = 999,
...rest
},
@@ -65,9 +71,22 @@ export const ExpandableRow: ExpandableRowType = forwardRef(
) => {
const [internalOpen, setInternalOpen] = useState<boolean>(defaultOpen);
const id = useId();
const isOpen = open ?? internalOpen;
const expansionHandler = (e) => {
onOpenChange?.(!isOpen);
if (open === undefined) {
setInternalOpen((open) => !open);
}
e.stopPropagation();
};
const onRowClick = (e) => {
if (e.target.nodeName === "TD" || e.target.nodeName === "TH") {
expansionHandler(e);
}
};
return (
<>
<Row
@@ -75,7 +94,13 @@ export const ExpandableRow: ExpandableRowType = forwardRef(
ref={ref}
className={cl("navds-table__expandable-row", className, {
"navds-table__expandable-row--open": isOpen,
"navds-table__expandable-row--expansion-disabled":
expansionDisabled,
})}
onClick={(e) => {
!expansionDisabled && expandOnRowClick && onRowClick(e);
rest?.onClick?.(e);
}}
>
{togglePlacement === "right" && children}
<DataCell
@@ -89,12 +114,7 @@ export const ExpandableRow: ExpandableRowType = forwardRef(
className="navds-table__toggle-expand-button"
aria-controls={id}
aria-expanded={isOpen}
onClick={() => {
onOpenChange?.(!isOpen);
if (open === undefined) {
setInternalOpen((open) => !open);
}
}}
onClick={expansionHandler}
>
<ChevronDownIcon
className="navds-table__expandable-icon"
@@ -1,6 +1,6 @@
import React, { useState } from "react";
import { Table } from "..";
import { Link } from "../..";
import { Button, Checkbox, Link } from "../..";
export default {
title: "ds-react/Table",
@@ -37,6 +37,7 @@ export const Expandable = () => {
};
export const ExpandableSmall = () => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const [open, setOpen] = useState(false);
return (
<Table size="small">
@@ -227,3 +228,63 @@ export const ExpandableOpen = () => {
</Table>
);
};
export const ClickableRow = {
render: () => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const [isRowOpen1, setIsRowOpen1] = useState(false);
// eslint-disable-next-line react-hooks/rules-of-hooks
const [isRowOpen2, setIsRowOpen2] = useState(false);
return (
<>
<Table zebraStripes>
<Table.Header>
<Table.Row>
<Table.HeaderCell>Navn</Table.HeaderCell>
<Table.HeaderCell>Info</Table.HeaderCell>
<Table.HeaderCell aria-hidden />
</Table.Row>
</Table.Header>
<Table.Body>
<Table.ExpandableRow
content={<div>placeholder row 1</div>}
togglePlacement="right"
onOpenChange={setIsRowOpen1}
data-testid="row1"
open={isRowOpen1}
expandOnRowClick
>
<Table.DataCell>Ola</Table.DataCell>
<Table.DataCell>
<Button
size="xsmall"
onClick={(e) => {
alert("Mer info");
}}
>
Mer info
</Button>
</Table.DataCell>
</Table.ExpandableRow>
<Table.ExpandableRow
content={<div>placeholder row 2</div>}
togglePlacement="right"
onOpenChange={setIsRowOpen2}
data-testid="row2"
open={isRowOpen2}
expandOnRowClick
>
<Table.DataCell>Hans</Table.DataCell>
<Table.DataCell>
<Checkbox hideLabel size="small">
Sett
</Checkbox>
</Table.DataCell>
</Table.ExpandableRow>
</Table.Body>
</Table>
</>
);
},
};
@@ -0,0 +1,9 @@
import * as AllIcons from "@navikt/aksel-icons";
import React from "react";
const icons = Object.keys(AllIcons).map((key) => AllIcons[key]);
export const RandomIcon = () => {
const Icon = icons[Math.floor(Math.random() * icons.length)];
return <Icon />;
};
+20
View File
@@ -1,5 +1,25 @@
# @navikt/ds-tailwind
## 4.12.0
## 4.11.2
## 4.11.1
## 4.11.0
## 4.10.2
## 4.10.1
## 4.10.0
## 4.9.1
## 4.9.0
## 4.8.0
## 4.7.4
## 4.7.3
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-tailwind",
"version": "4.7.4",
"version": "4.12.0",
"description": "Tailwind config based on @navikt/ds-tokens",
"author": "Aksel | NAV designsystem team",
"keywords": [
@@ -22,7 +22,7 @@
"directory": "@navikt/core/tailwind"
},
"devDependencies": {
"@navikt/ds-tokens": "^4.7.4",
"@navikt/ds-tokens": "^4.12.0",
"@types/jest": "^29.0.0",
"color": "4.2.3",
"jest": "^29.0.0",
+20
View File
@@ -1,5 +1,25 @@
# @navikt/ds-tokens
## 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
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-tokens",
"version": "4.7.4",
"version": "4.12.0",
"description": "Design-tokens for NAV designsystem",
"author": "Aksel | NAV designsystem team",
"keywords": [
+116
View File
@@ -1,5 +1,121 @@
# Changelog
## 4.12.0
### @navikt/ds-css
- CopyButton: Har nå prop 'iconPosition' for å høyre/venstre aligne ikon ([#2173](https://github.com/navikt/aksel/pull/2173))
<!---->
- 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))
### @navikt/ds-react
- Combobox: La til støtte for feilmeldinger i Combobox ([#2182](https://github.com/navikt/aksel/pull/2182))
- CopyButton: Har nå prop 'iconPosition' for å høyre/venstre aligne ikon ([#2173](https://github.com/navikt/aksel/pull/2173))
<!---->
- List: Fikset sentrering, margins ([#2168](https://github.com/navikt/aksel/pull/2168))
- Combobox: Kjører nå 'onChange' + 'onClear' når input blir reset programmatisk ([#2183](https://github.com/navikt/aksel/pull/2183))
- Combobox: Fikset custom-options i singleselect ([#2180](https://github.com/navikt/aksel/pull/2180))
- Combobox: Fjernet unødvendige 'onClear'-calls når man velger verdier ([#2170](https://github.com/navikt/aksel/pull/2170))
- Checkbox: Checkmark er nå SVG-ikon og ikke Base64 ([#2171](https://github.com/navikt/aksel/pull/2171))
- Combobox: Lukker nå nedtrekksmeny hvis man legger til ny option i singleselect ([#2177](https://github.com/navikt/aksel/pull/2177))
## 4.11.2
### @navikt/ds-css
- :lipstick: ToggleGroup: fjern semibold fra selected button ([#2167](https://github.com/navikt/aksel/pull/2167))
## 4.11.1
### @navikt/ds-css
- Border-radius: Fikset hardkodet border-radius i Datepicker, ToggleGroup og Combobox. ([#2159](https://github.com/navikt/aksel/pull/2159))
## 4.11.0
### @navikt/ds-css
- HGrid: Ny komponent 'HGrid' er ute i Beta: [Dokumentasjon](https://aksel.nav.no/komponenter/core/hgrid) ([#1838](https://github.com/navikt/aksel/pull/1838))
<!---->
- List: Fikset alignment av ikoner ([#2149](https://github.com/navikt/aksel/pull/2149))
- Font: La til egen font for semibold italic for bedre skalering cross-browser ([#2150](https://github.com/navikt/aksel/pull/2150))
### @navikt/ds-react
- HGrid: Ny komponent 'HGrid' er ute i Beta: [Dokumentasjon](https://aksel.nav.no/komponenter/core/hgrid) ([#1838](https://github.com/navikt/aksel/pull/1838))
<!---->
- 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))
## 4.10.2
### @navikt/ds-css
- Link: Fikset visited farger og inline SVG-reset ved bruk av tailwind
## 4.10.0
### @navikt/ds-css
- Link: La til 'variant', 'underline' og 'inlineText'-prop ([#2093](https://github.com/navikt/aksel/pull/2093))
### @navikt/ds-react
- Link: La til 'variant', 'underline' og 'inlineText'-prop ([#2093](https://github.com/navikt/aksel/pull/2093))
## 4.9.1
### @navikt/ds-css
- Tweaks to combobox - updated example, small bugfixes, better affordance for selected options and added flag for "isAddedByUser" to onToggleSelected ([#2144](https://github.com/navikt/aksel/pull/2144))
### @navikt/ds-react
- Button: Fikset aria-live bug der knapp alltid ble lest opp av skjermleser ved render ([#2143](https://github.com/navikt/aksel/pull/2143))
- Tweaks to combobox - updated example, small bugfixes, better affordance for selected options and added flag for "isAddedByUser" to onToggleSelected ([#2144](https://github.com/navikt/aksel/pull/2144))
## 4.9.0
### @navikt/ds-css
- Table: ExpandableRow kan nå åpnes med 'expandOnRowClick'-prop ([#2127](https://github.com/navikt/aksel/pull/2127))
### @navikt/ds-react
- Table: ExpandableRow kan nå åpnes med 'expandOnRowClick'-prop ([#2127](https://github.com/navikt/aksel/pull/2127))
## 4.8.0
### @navikt/ds-css
- Nye komponenter `VStack`, `HStack` og `Spacer` for å enklere kunne lage layout med flexbox og spacing-variabler. ([#2040](https://github.com/navikt/aksel/pull/2040))
### @navikt/ds-react
- Nye komponenter `VStack`, `HStack` og `Spacer` for å enklere kunne lage layout med flexbox og spacing-variabler. ([#2040](https://github.com/navikt/aksel/pull/2040))
## 4.7.4
### @navikt/ds-css
+5 -5
View File
@@ -15,11 +15,11 @@
"website"
],
"dependencies": {
"@navikt/aksel-icons": "^4.7.4",
"@navikt/ds-css": "^4.7.4",
"@navikt/ds-react": "^4.7.4",
"@navikt/ds-tailwind": "^4.7.4",
"@navikt/ds-tokens": "^4.7.4"
"@navikt/aksel-icons": "^4.12.0",
"@navikt/ds-css": "^4.12.0",
"@navikt/ds-react": "^4.12.0",
"@navikt/ds-tailwind": "^4.12.0",
"@navikt/ds-tokens": "^4.12.0"
},
"devDependencies": {
"prettier-plugin-tailwindcss": "^0.2.3"
@@ -44,11 +44,11 @@ const GetIcon = (s: string) => {
const getText = (s: string) => {
switch (s) {
case "do":
return "Do: ";
return "Gjør: ";
case "dont":
return "Don't: ";
return "Unngå: ";
case "warning":
return "Fare på ferde: ";
return "Utfordrende: ";
default:
return "";
}
@@ -74,7 +74,7 @@ const Element = ({ block }: { block: DoDontT["blokker"][number] }) => {
)}
aria-hidden
>
<span className="absolute right-[10px] z-10 translate-y-2">
<span className="absolute right-[2px] z-10">
{GetIcon(block.variant)}
</span>
</div>
@@ -1,4 +1,5 @@
import { PencilIcon } from "@navikt/aksel-icons";
import { Button } from "@navikt/ds-react";
import { getParameters } from "codesandbox/lib/api/define";
const indexTsx = `import React from 'react';
@@ -50,16 +51,18 @@ export const CodeSandbox = ({ code }: { code: string }) => {
action="https://codesandbox.io/api/v1/sandboxes/define"
method="POST"
target="_blank"
className="h-8"
>
<input type="hidden" name="parameters" value={parameters} />
<input type="hidden" name="query" value="module=App.js" />
<button
<Button
variant="tertiary-neutral"
size="small"
type="submit"
className="focus-visible:shadow-focus-inset flex items-center justify-center gap-1 rounded-sm px-3 py-2 hover:bg-gray-100 focus:outline-none active:bg-gray-200"
icon={<PencilIcon aria-hidden fontSize="1.5rem" />}
>
<PencilIcon aria-hidden fontSize="1.5rem" />
CodeSandbox
</button>
</Button>
</form>
);
};
@@ -1,11 +1,16 @@
import { capitalize, Snippet } from "@/components";
import { withErrorBoundary } from "@/error-boundary";
import { CodeExapmplesT } from "@/types";
import { BodyLong, Chips, Link } from "@navikt/ds-react";
import {
ExternalLinkIcon,
LaptopIcon,
MobileSmallIcon,
} from "@navikt/aksel-icons";
import { BodyLong, Button, Chips, HStack } from "@navikt/ds-react";
import cl from "clsx";
import { useEffect, useState } from "react";
import { CodeSandbox } from "./CodeSandbox";
import { useRouter } from "next/router";
import { useEffect, useRef, useState } from "react";
import { CodeSandbox } from "./CodeSandbox";
const iframePadding = 192;
const iframeId = "example-iframe";
@@ -15,6 +20,7 @@ const ComponentExamples = ({ node }: { node: CodeExapmplesT }) => {
const [frameState, setFrameState] = useState(300);
const [unloaded, setUnloaded] = useState(true);
const router = useRouter();
const iframeRef = useRef<HTMLIFrameElement>(null);
const handleExampleLoad = () => {
let attempts = 0;
@@ -139,10 +145,11 @@ const ComponentExamples = ({ node }: { node: CodeExapmplesT }) => {
id={iframeId}
aria-label={`${node?.dir?.title} ${fil.navn} eksempel`}
className={cl(
"min-w-80 block w-full max-w-full resize-x overflow-auto bg-white shadow-[20px_0_20px_-20px_rgba(0,0,0,0.22)]",
"min-w-80 block w-full max-w-full resize-x bg-white shadow-[20px_0_20px_-20px_rgba(0,0,0,0.22)]",
{ invisible: unloaded }
)}
title="Kode-eksempler"
ref={iframeRef}
/>
{unloaded && (
<div className="absolute inset-0 mx-auto flex flex-col items-center justify-center gap-2">
@@ -153,18 +160,50 @@ const ComponentExamples = ({ node }: { node: CodeExapmplesT }) => {
</div>
)}
</div>
<div className="mb-2 flex justify-center gap-2 rounded-b border border-gray-300 px-2 py-1 text-base sm:justify-end ">
<CodeSandbox code={fil.innhold.trim()} />
<Link
href={`/eksempler/${node.dir.title}/${fil.navn.replace(
".tsx",
""
)}`}
className="si-ignore text-gray-900"
target="_blank"
>
Åpne i nytt vindu
</Link>
<div className="mb-2 rounded-b border border-gray-300 p-1">
<HStack gap="4" justify="space-between">
<div className="hidden sm:block">
<HStack gap="2">
<Button
variant="tertiary-neutral"
size="small"
icon={
<MobileSmallIcon title="Sett eksempel til mobilbredde" />
}
onClick={() =>
(iframeRef.current.style.width = "360px")
}
/>
<Button
variant="tertiary-neutral"
size="small"
icon={
<LaptopIcon title="Sett eksempel til desktopbredde" />
}
onClick={() => (iframeRef.current.style.width = "")}
/>
</HStack>
</div>
<HStack gap="2">
<CodeSandbox code={fil.innhold.trim()} />
<Button
variant="tertiary-neutral"
size="small"
icon={
<ExternalLinkIcon title="Åpne eksempel i nytt vindu" />
}
target="_blank"
className="si-ignore"
as="a"
href={`/eksempler/${node.dir.title}/${fil.navn.replace(
".tsx",
""
)}`}
/>
</HStack>
</HStack>
</div>
<Snippet
@@ -137,7 +137,7 @@ export function TableOfContents({
hidden: !renderToc,
"col-start-3 max-w-prose md:sticky md:top-20 lg:flex":
aksel && renderToc,
"mt-12 mr-auto h-full xl:flex": !aksel && renderToc,
"mr-auto mt-12 h-full xl:flex": !aksel && renderToc,
}
)}
>
@@ -1,7 +1,7 @@
import { useMedia } from "@/utils";
import * as Icons from "@navikt/aksel-icons";
import meta from "@navikt/aksel-icons/metadata";
import { Heading, Modal, Search, ToggleGroup } from "@navikt/ds-react";
import { HGrid, Heading, Modal, Search, ToggleGroup } from "@navikt/ds-react";
import cl from "clsx";
import Footer from "components/layout/footer/Footer";
import { Header } from "components/layout/header/Header";
@@ -76,6 +76,12 @@ export const IconPage = ({ name }: { name: string }) => {
hideModal && Modal.setAppElement("#__next");
}, [hideModal]);
const subCategoryWordLength = Math.max(
...categories.map((cat) =>
Math.max(...cat.sub_categories.map((sub) => sub.sub_category.length))
)
);
return (
<>
<Head>
@@ -177,56 +183,68 @@ export const IconPage = ({ name }: { name: string }) => {
>
{cat.category}
</Heading>
<div className="grid w-full gap-2">
<div className="grid w-full">
{cat.sub_categories.map((sub) => {
return (
<div key={sub.sub_category}>
<Heading
level="3"
size="xsmall"
className="text-text-subtle mb-1"
<div
key={sub.sub_category}
className="border-t-border-subtle border-t py-2"
>
<HGrid
columns={{
xs: "1fr",
md: `${subCategoryWordLength}ch 1fr`,
}}
gap={{ xs: "0", md: "2" }}
>
{sub.sub_category}
</Heading>
<div className="gap-05 flex flex-wrap">
{sub.icons.map((i) => {
const T = Icons[`${i.id}Icon`];
if (T === undefined) {
return null;
}
return (
<Link
href={`/ikoner/${i.id}`}
scroll={false}
key={i.id}
prefetch={false}
id={i.id}
tabIndex={0}
ref={(el) => {
if (name === i.id) {
focusRef.current = el;
}
}}
className={cl(
"hover:bg-surface-hover bg-surface-default active:bg-surface-neutral-subtle-hover group relative grid aspect-square w-11 shrink-0 place-items-center rounded focus:outline-none focus:ring-blue-800 focus-visible:ring-2",
{
"from-surface-selected bg-surface-selected ring-border-alt-3 z-10 bg-gradient-to-br to-teal-50 ring-1":
i.id === name,
}
)}
>
<span className="navds-sr-only">
{i.name}
</span>
<T
className="text-3xl"
aria-hidden
alt=""
/>
</Link>
);
})}
</div>
<Heading
level="3"
size="xsmall"
className="text-text-subtle leading-[44px]"
>
{sub.sub_category}
</Heading>
<div className="gap-05 flex flex-wrap">
{sub.icons.map((i) => {
const T = Icons[`${i.id}Icon`];
if (T === undefined) {
return null;
}
return (
<Link
href={`/ikoner/${i.id}`}
scroll={false}
key={i.id}
prefetch={false}
id={i.id}
tabIndex={0}
ref={(el) => {
if (name === i.id) {
focusRef.current = el;
}
}}
className={cl(
"hover:bg-surface-neutral-subtle-hover bg-surface-subtle active:bg-surface-neutral-subtle-hover group relative grid aspect-square w-11 shrink-0 place-items-center rounded focus:outline-none focus:ring-blue-800 focus-visible:ring-2",
{
"from-surface-selected bg-surface-selected ring-border-alt-3 z-10 bg-gradient-to-br to-teal-50 ring-1":
i.id === name,
}
)}
>
<span className="navds-sr-only">
{i.name}
</span>
<T
className="text-3xl"
aria-hidden
alt=""
/>
</Link>
);
})}
</div>
</HGrid>
</div>
);
})}
@@ -87,7 +87,7 @@ export const IconSidebar = ({
href="/ikoner"
scroll={false}
prefetch={false}
className="min-h-11 hover:bg-surface-hover focus-visible:shadow-focus active:bg-surface-neutral-subtle-hover absolute right-2 top-2 grid aspect-square place-content-center rounded text-xl focus:outline-none"
className="min-h-11 hover:bg-surface-hover focus-visible:shadow-focus active:bg-surface-neutral-subtle-hover absolute right-2 top-4 grid aspect-square place-content-center rounded text-xl focus:outline-none"
onClick={() => {
focusRef?.current?.focus?.();
}}
@@ -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)
);
@@ -186,7 +186,7 @@ const options: {
icon: <ChangeLogIconOutline />,
},
"komponent-beta": {
text: "Komponenten er under utvikling, men klar for adopsjon. Vi er ønsker gjerne innspill på hvordan den fungerer og hvilken forbedringer vi kan gjøre.",
text: "Komponenten er under utvikling, men klar for adopsjon. Vi ønsker gjerne innspill på hvordan den fungerer og hvilken forbedringer vi kan gjøre.",
link: `${issueUrl}&labels=forespørsel+🥰%2Ckomponenter+🧩%2Cbeta+🧪&template=update-component.yml&title=%5BInnspill+til+komponent%5D%3A+`,
heading: "Beta",
icon: (
+8 -8
View File
@@ -30,15 +30,15 @@
"dependencies": {
"@amplitude/analytics-browser": "^2.0.0",
"@sanity/block-content-to-react": "^3.0.0",
"@sanity/client": "^6.1.5",
"@sanity/client": "^6.4.6",
"@sanity/code-input": "4.1.1",
"@sanity/color-input": "^3.1.0",
"@sanity/icons": "2.4.1",
"@sanity/image-url": "1.0.2",
"@sanity/preview-kit": "^2.3.0",
"@sanity/table": "1.0.1",
"@sanity/ui": "^1.7.0",
"@sanity/vision": "3.14.1",
"@sanity/ui": "^1.7.4",
"@sanity/vision": "3.15.1",
"@svgr/webpack": "^5.5.0",
"boring-avatars": "1.10.1",
"clsx": "^1.2.1",
@@ -48,7 +48,7 @@
"lodash": "^4.17.21",
"next": "^13.2.1",
"next-logger": "^3.0.2",
"next-sanity": "^5.1.0",
"next-sanity": "^5.2.7",
"pino": "^8.14.1",
"prism-react-renderer": "^1.3.0",
"prismjs": "^1.27.0",
@@ -56,14 +56,14 @@
"react-dom": "^18.0.0",
"react-modal": "3.15.1",
"react-responsive-masonry": "^2.1.7",
"sanity": "3.14.1",
"sanity": "3.15.1",
"sanity-plugin-asset-source-unsplash": "^1.1.0",
"sanity-plugin-media": "^2.0.5",
"sharp": "0.31.3",
"sitemap": "^7.0.0",
"styled-components": "^5.2",
"swr": "^1.1.2",
"tailwindcss": "^3.2.0",
"tailwindcss": "^3.3.3",
"tinycolor2": "^1.6.0"
},
"devDependencies": {
@@ -72,8 +72,8 @@
"@next/bundle-analyzer": "^12.1.0",
"@next/eslint-plugin-next": "^12.2.0",
"@playwright/test": "1.35.1",
"@sanity/block-tools": "^3.12.2",
"@sanity/schema": "^3.12.2",
"@sanity/block-tools": "^3.15.1",
"@sanity/schema": "^3.15.1",
"@testing-library/jest-dom": "^5.16.0",
"@testing-library/react": "^13.3.0",
"@types/jest": "^29.0.0",
@@ -41,5 +41,5 @@ export const Demo = {
};
export const args = {
index: 1,
index: 4,
};
@@ -40,5 +40,5 @@ export const Demo = {
};
export const args = {
index: 0,
index: 1,
};
@@ -59,5 +59,5 @@ export const Demo = {
};
export const args = {
index: 0,
index: 2,
};
@@ -40,5 +40,5 @@ export const Demo = {
};
export const args = {
index: 0,
index: 3,
};
@@ -23,6 +23,8 @@ const initialSelectedOptions = ["Norge"];
export const Example = () => {
const [value, setValue] = useState("");
const mockPersistUserAddedValues = (value, isSelected) => undefined;
const [selectedOptions, setSelectedOptions] = useState(
initialSelectedOptions
);
@@ -31,17 +33,25 @@ export const Example = () => {
[value]
);
const onToggleSelected = (option, isSelected) => {
const onToggleSelected = (
option: string,
isSelected: boolean,
isCustomOption: boolean
) => {
if (isSelected) {
setSelectedOptions([...selectedOptions, option]);
} else {
setSelectedOptions(selectedOptions.filter((o) => o !== option));
}
if (isCustomOption) {
mockPersistUserAddedValues(option, isSelected);
}
};
return (
<div>
<UNSAFE_Combobox
allowNewValues
label="Hvilke land har du besøkt de siste 6 ukene? Velg opptil flere."
filteredOptions={filteredOptions}
isMultiSelect
@@ -20,6 +20,7 @@ export const Example = () => {
return (
<div>
<UNSAFE_Combobox
allowNewValues
label="Hva er dine favorittdrikker? Legg gjerne til flere alternativer."
options={initialOptions}
isMultiSelect
@@ -0,0 +1,36 @@
import { UNSAFE_Combobox } from "@navikt/ds-react";
import { withDsExample } from "components/website-modules/examples/withDsExample";
const initialOptions = [
"banana",
"apple",
"tangerine",
"pear",
"grape",
"kiwi",
"mango",
"passion fruit",
"pineapple",
"strawberry",
"watermelon",
"grape fruit",
];
export const Example = () => {
return (
<div>
<UNSAFE_Combobox
label="Hva er din favorittfrukt?"
options={initialOptions}
error="Du må velge en favorittfrukt."
/>
</div>
);
};
export default withDsExample(Example, "static");
export const args = {
index: 0,
desc: "Ved Single Select velger brukeren kun ett valg fra nedtrekkslisten.",
};
@@ -0,0 +1,63 @@
import { HGrid } from "@navikt/ds-react";
import { withDsExample } from "components/website-modules/examples/withDsExample";
const Example = () => {
return (
<Background>
<HGrid
gap="6"
columns={{ xs: "repeat(auto-fit, minmax(10rem, 1fr))", md: 4 }}
>
<Placeholder height="240px" />
<Placeholder height="240px" />
<Placeholder height="240px" />
<Placeholder height="240px" />
</HGrid>
</Background>
);
};
const Placeholder = ({ height = "auto", width = "auto" }) => {
return (
<div
style={{
display: "inherit",
background: "var(--a-deepblue-500)",
height: height ?? undefined,
width: width ?? undefined,
}}
/>
);
};
const Background = ({
children,
width = "100%",
}: {
children: React.ReactNode;
width?: string;
}) => {
return (
<div
style={{
background: "var(--a-deepblue-100)",
width,
height: "auto",
}}
>
{children}
</div>
);
};
export default withDsExample(Example, "static");
/* Storybook story */
export const Demo = {
render: Example,
};
export const args = {
index: 1,
desc: "Columns støtter både statisk antall kolonner med 'number' og mer fleksible kolonner med 'string'. ",
};
@@ -0,0 +1,59 @@
import { HGrid } from "@navikt/ds-react";
import { withDsExample } from "components/website-modules/examples/withDsExample";
const Example = () => {
return (
<Background>
<HGrid gap="6" columns={3}>
<Placeholder height="240px" />
<Placeholder height="240px" />
<Placeholder height="240px" />
</HGrid>
</Background>
);
};
const Placeholder = ({ height = "auto", width = "auto" }) => {
return (
<div
style={{
display: "inherit",
background: "var(--a-deepblue-500)",
height: height ?? undefined,
width: width ?? undefined,
}}
/>
);
};
const Background = ({
children,
width = "100%",
}: {
children: React.ReactNode;
width?: string;
}) => {
return (
<div
style={{
background: "var(--a-deepblue-100)",
width,
height: "auto",
}}
>
{children}
</div>
);
};
export default withDsExample(Example, "static");
/* Storybook story */
export const Demo = {
render: Example,
};
export const args = {
index: 0,
desc: "HGrid lar deg enkelt dele innholdet opp i kolonner. Basert på CSS-grid",
};
@@ -0,0 +1,60 @@
import { HGrid } from "@navikt/ds-react";
import { withDsExample } from "components/website-modules/examples/withDsExample";
const Example = () => {
return (
<Background>
<HGrid gap="6" columns={{ xs: 1, sm: 2, md: 4 }}>
<Placeholder height="240px" />
<Placeholder height="240px" />
<Placeholder height="240px" />
<Placeholder height="240px" />
</HGrid>
</Background>
);
};
const Placeholder = ({ height = "auto", width = "auto" }) => {
return (
<div
style={{
display: "inherit",
background: "var(--a-deepblue-500)",
height: height ?? undefined,
width: width ?? undefined,
}}
/>
);
};
const Background = ({
children,
width = "100%",
}: {
children: React.ReactNode;
width?: string;
}) => {
return (
<div
style={{
background: "var(--a-deepblue-100)",
width,
height: "auto",
}}
>
{children}
</div>
);
};
export default withDsExample(Example, "static");
/* Storybook story */
export const Demo = {
render: Example,
};
export const args = {
index: 2,
desc: "Med responsive kolonner kan man dynamiskt tilpasse dem basert på brekkpunktene våre.",
};
@@ -0,0 +1,60 @@
import { HGrid } from "@navikt/ds-react";
import { withDsExample } from "components/website-modules/examples/withDsExample";
const Example = () => {
return (
<Background>
<HGrid gap={{ xs: "2", md: "8" }} columns={3}>
<Placeholder height="240px" />
<Placeholder height="240px" />
<Placeholder height="240px" />
<Placeholder height="240px" />
</HGrid>
</Background>
);
};
const Placeholder = ({ height = "auto", width = "auto" }) => {
return (
<div
style={{
display: "inherit",
background: "var(--a-deepblue-500)",
height: height ?? undefined,
width: width ?? undefined,
}}
/>
);
};
const Background = ({
children,
width = "100%",
}: {
children: React.ReactNode;
width?: string;
}) => {
return (
<div
style={{
background: "var(--a-deepblue-100)",
width,
height: "auto",
}}
>
{children}
</div>
);
};
export default withDsExample(Example, "static");
/* Storybook story */
export const Demo = {
render: Example,
};
export const args = {
index: 3,
desc: "Med responsiv gap kan man dynamiskt tilpasse spacing basert på brekkpunktene våre.",
};
@@ -0,0 +1,73 @@
import { HStack, VStack } from "@navikt/ds-react";
import { withDsExample } from "components/website-modules/examples/withDsExample";
const Example = () => {
return (
<VStack gap="12">
<HStack gap="3" align="center">
<Placeholder text="center" />
<Placeholder />
<Placeholder />
<Placeholder />
<Placeholder />
</HStack>
<HStack gap="3" align="end">
<Placeholder text="end" />
<Placeholder />
<Placeholder />
<Placeholder />
<Placeholder />
</HStack>
<HStack gap="3" align="start">
<Placeholder text="start" />
<Placeholder />
<Placeholder />
<Placeholder />
<Placeholder />
</HStack>
<HStack gap="3" align="stretch">
<Placeholder text="stretch" />
<Placeholder />
<Placeholder />
<Placeholder />
<Placeholder />
</HStack>
<HStack gap="3" align="baseline">
<Placeholder text="baseline" />
<Placeholder text="text" noPadding />
<Placeholder text="text" noPadding />
<Placeholder text="text" noPadding />
<Placeholder text="text" noPadding />
</HStack>
</VStack>
);
};
export default withDsExample(Example);
/* Storybook story */
export const Demo = {
render: Example,
};
export const args = {
index: 2,
desc: "Endrer 'align-items'.",
};
const Placeholder = ({
text,
noPadding,
}: {
text?: string;
noPadding?: boolean;
}) => {
return (
<div
className="min-h-4 text-text-on-action h-auto w-auto rounded bg-teal-600 p-2"
style={{ padding: noPadding && 0 }}
>
{text}
</div>
);
};
@@ -0,0 +1,29 @@
import { HStack } from "@navikt/ds-react";
import { withDsExample } from "components/website-modules/examples/withDsExample";
const Example = () => {
return (
<HStack gap="4">
<Placeholder />
<Placeholder />
<Placeholder />
<Placeholder />
</HStack>
);
};
export default withDsExample(Example);
/* Storybook story */
export const Demo = {
render: Example,
};
export const args = {
index: 0,
desc: "HStack er en enkel layout-komponent for flexbox.",
};
const Placeholder = () => {
return <div className="aspect-square h-12 rounded bg-teal-500" />;
};
@@ -0,0 +1,76 @@
import { HStack, VStack } from "@navikt/ds-react";
import { withDsExample } from "components/website-modules/examples/withDsExample";
const Example = () => {
return (
<div className="min-w-72 bg-deepblue-50">
<VStack gap="12">
<HStack gap="3" justify="center">
<Placeholder text="center" />
<Placeholder />
<Placeholder />
<Placeholder />
</HStack>
<HStack gap="3" justify="end">
<Placeholder text="end" />
<Placeholder />
<Placeholder />
<Placeholder />
</HStack>
<HStack gap="3" justify="start">
<Placeholder text="start" />
<Placeholder />
<Placeholder />
<Placeholder />
</HStack>
<HStack gap="3" justify="space-around">
<Placeholder text="around" />
<Placeholder />
<Placeholder />
<Placeholder />
</HStack>
<HStack gap="3" justify="space-between">
<Placeholder text="between" />
<Placeholder />
<Placeholder />
<Placeholder />
</HStack>
<HStack gap="3" justify="space-evenly">
<Placeholder text="evenly" />
<Placeholder />
<Placeholder />
<Placeholder />
</HStack>
</VStack>
</div>
);
};
export default withDsExample(Example);
/* Storybook story */
export const Demo = {
render: Example,
};
export const args = {
index: 3,
desc: "Endrer 'justify-content'.",
};
const Placeholder = ({
text,
noPadding,
}: {
text?: string;
noPadding?: boolean;
}) => {
return (
<div
className="min-h-4 text-text-on-action h-auto w-auto rounded bg-teal-600 p-2"
style={{ padding: noPadding && 0 }}
>
{text}
</div>
);
};
@@ -0,0 +1,32 @@
import { HStack } from "@navikt/ds-react";
import { withDsExample } from "components/website-modules/examples/withDsExample";
const Example = () => {
return (
<HStack gap="4" wrap={false}>
<Placeholder />
<Placeholder />
<Placeholder />
<Placeholder />
<Placeholder />
<Placeholder />
<Placeholder />
</HStack>
);
};
export default withDsExample(Example);
/* Storybook story */
export const Demo = {
render: Example,
};
export const args = {
index: 1,
desc: "Som standard er 'wrap' satt til 'true'.",
};
const Placeholder = () => {
return <div className="aspect-square h-12 rounded bg-teal-500" />;
};
Loaded 100 of 139 files, more files were not shown because too many files have changed in this diff. Show more