Compare commits

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

* list closes when choosing option on SingleSelect

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

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

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

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

* tweak css spacing for inline links with SVG

* add break-all prop to link

* wrap link in span to allow for spacing

* more obvious spacing example

* remove spacing prop from link

* implement pr review comments

* add random icon visual fuzz testing in stories

* remove console.log

* changeset

* add examples to aksel.nav.no

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

* 🐛 Fixed comments

* 🎨 Oppdatert props

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

* change is no longer major (pr review comment)

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

* cleaner changeset (pr review comment)

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

* norwegian changeset (pr review comment)

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

* update changeset (pr review comment)

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

* use neutral token for neutral link variant

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

* use neutral token for neutral link variant

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

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

---------

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

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

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

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

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

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

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

* Added changeset and updated yarn.lock

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

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

* Fix for onBlur in ComboboxWrapper. Without tabindex="-1" event.relatedTarget in onBlur will be null, and we can't check that focus has entirely moved outside the Combobox.
2023-08-09 14:15:26 +02:00
Ken d34a8f45ef 🐛 aria-live fix for button (#2143) 2023-08-09 11:01:45 +02:00
github-actions[bot]andgithub-actions[bot] d1ceb40439 Version Packages (#2141)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-08-07 13:58:17 +02:00
e033b34c3b Table component: adding click event on expandable table rows (#2127)
Co-authored-by: Vegard Haugstvedt <it.vegard@gmail.com>
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
2023-08-07 13:42:07 +02:00
Halvor Haugan de3991d0f0 📝 Fiks skrivefeil i Select-eksempler (#2140) 2023-08-04 18:30:41 +02:00
github-actions[bot]andgithub-actions[bot] 9641215506 Version Packages (#2139)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-08-04 13:43:34 +02:00
Ken 8a82e1c1f6 [Ny komponent] Stack, Spacer (#2040) 2023-08-04 13:13:46 +02:00
github-actions[bot]andgithub-actions[bot] a2dfc8699b Version Packages (#2138)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-08-04 12:33:33 +02:00
Ken 492cfc1801 💄 Oppdatert bruk av REM for font-scaling fix (#2126) 2023-08-04 11:33:05 +02:00
Ken 9ca5932fed ⬆️ Oppdatert dependencies for dependabot warnings (#2134) 2023-08-04 11:18:24 +02:00
Vebjørn NordbyandKen 5950673fbf Legger til htmlInputType time på TextField-type (#2137)
Co-authored-by: Ken <ken.aleksander@gmail.com>
2023-08-04 09:15:17 +02:00
Ken 5b42ff07ee 🐛 Fikset oppdateringer av prop-docs på Aksel.nav.no 2023-08-03 13:41:20 +02:00
github-actions[bot]andgithub-actions[bot] 09ba52ea76 Version Packages (#2132)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-08-03 13:30:24 +02:00
a82bccf707 Removable chips type now defaults to button (#2124)
* Removable chips type now defaults to button

* 📝 Changeset

* 📝 Changeset update

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

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

---------

Co-authored-by: Ken <ken.aleksander@gmail.com>
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
Co-authored-by: Vegard Haugstvedt <it.vegard@gmail.com>
2023-08-03 11:41:31 +02:00
Ken 89ae778c24 🐛 Fikset lasting av aksel-eksempler i storybook 2023-08-03 10:53:25 +02:00
Ken 5b2669d35d ⬆️ Oppdatert storybook til 7.2 (#2128) 2023-08-02 15:35:28 +02:00
Ken 8a82bb4a96 🐛 Fikset layout-bug på komponentsider for enkelte nettlesere 2023-08-02 12:43:19 +02:00
Ken 7871e0f95c 📝 Oppdatert datepicker-eksempler med formatering (#2125) 2023-08-02 12:37:14 +02:00
dependabot[bot] 3182888a07 ⬆️ Bump word-wrap from 1.2.3 to 1.2.4 (#2115)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-02 10:39:55 +02:00
Mathias S. Müller f2742da085 Legg til manglende størrelsesbeskrivelse (#2121) 2023-07-27 14:59:08 +02:00
github-actions[bot]andgithub-actions[bot] 92721aefa0 Version Packages (#2119)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-07-21 11:47:05 +02:00
Ken e6fdfdc9b4 🐛 Fikset stepper-bug (#2116) 2023-07-20 19:50:56 +02:00
github-actions[bot]andgithub-actions[bot] 93975bc935 Version Packages (#2118)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-07-19 20:14:53 +02:00
Ken 3a0dac3997 🐛 Fikset datepicker-bug for dynamsik oppdatering av 'month' (#2117) 2023-07-19 20:12:14 +02:00
Andreas Nordahl 1d43710af0 Remove preconnect link to fonts.googleapis.com(#2114) 2023-07-19 17:45:55 +02:00
github-actions[bot]andgithub-actions[bot] 6df7c78e3c Version Packages (#2111)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-07-17 22:35:24 +02:00
Andreas NordahlandKen 476d79e487 Expose timeline axis label templates as a prop (#2109)
Co-authored-by: Ken <ken.aleksander@gmail.com>
2023-07-17 22:32:16 +02:00
dependabot[bot]andLars Eirik Korsgaard Hansen 238a715c92 ⬆️ Bump semver from 5.7.1 to 5.7.2 (#2107)
Bumps [semver](https://github.com/npm/node-semver) from 5.7.1 to 5.7.2.
- [Release notes](https://github.com/npm/node-semver/releases)
- [Changelog](https://github.com/npm/node-semver/blob/v5.7.2/CHANGELOG.md)
- [Commits](https://github.com/npm/node-semver/compare/v5.7.1...v5.7.2)

---
updated-dependencies:
- dependency-name: semver
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Lars Eirik Korsgaard Hansen <71271458+larseirikhansen@users.noreply.github.com>
2023-07-14 15:06:55 +02:00
Lars Eirik Korsgaard Hansen 0fadd33e3e Combobox fixes (#2112)
Combobox post-release tweaks

* Add dynamic height to examples because of the dropdown

* Examples were not working: remove controlled prop for uncontrolled Comboboxes

* Add an example with controlled Combobox
2023-07-14 11:08:47 +02:00
Ken eadc0bce13 📝 Oppdatert changeset for bedre endringslogg 2023-07-13 15:50:29 +02:00
Lars Eirik Korsgaard Hansen 678db9b11b Update spotty-onions-cough.md 2023-07-13 15:39:40 +02:00
Lars Eirik Korsgaard Hansen e3027cc196 ✨ Ny komponent: Combobox (#1868)
✨Ny komponent: Combobox 🎉 

Combobox er en ny komponent som kombinerer tekstfelt og nedtrekksliste. 
- Velg mellom predefinerte alternativer i nedtrekkslisten
- Bruk input-feltet som filter for alternativene i nedtrekkslisten.
- Legg inn nye svar, hvis svaret du ikke finnes i listen
- Velg mellom SingleSelect eller MultiSelect-versjon for henholdsvis ett og flere valg. 
- Autocomplete svar i inputfeltet når man velger SingleSelect

Dokumentasjonen finner du på https://aksel.nav.no/komponenter/core/combobox
2023-07-13 15:17:05 +02:00
Ken ac0fd790e3 ⚰️ Fjernet ubrukt metrics-løsning (#2098) 2023-07-06 17:42:38 +02:00
SjurandKen c344c9beb4 Update removable.tsx (#2101)
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
2023-07-06 17:22:24 +02:00
SjurandKen 5cb5dea4ff Update toggle-neutral.tsx (#2100)
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
2023-07-06 14:42:51 +02:00
Sjur 054c63b161 Update toggle.tsx (#2099) 2023-07-06 14:42:12 +02:00
Julian Nymark f60ba32f97 split default example into separate examples (#2097)
* split default example into separate examples

* also keep the default

* remove unnecessary div wrapper for single elements
2023-07-06 13:28:26 +02:00
Ken 54811b394d ✨ Eksterne sider åpnes nå i ny fane 2023-07-06 13:14:33 +02:00
Ken c6b0570c4c 🔇 Fjernet API-access for tilbakemeldinger 2023-07-06 11:21:05 +02:00
Ken 8baa1ab0b4 🔒 Fjernet kommentarfelt på aksel 2023-07-05 09:24:10 +02:00
Ken 06ceeb3e6f 🐛 Fikset rouge ';' i preview 2023-07-04 21:55:21 +02:00
Ken 02f19858c7 🐛 Fikset sanity-auth for preview og edit-knapp 2023-07-04 21:51:21 +02:00
Ken 799e26a628 🐛 Fikset lenker på komponentside 2023-07-04 17:11:53 +02:00
Ken f5e5843916 Oppdatert sanity, Lister opp alle utdaterte artikler for forfatter (#2091) 2023-07-04 13:59:33 +02:00
github-actions[bot]andgithub-actions[bot] 4b8ed30554 Version Packages (#2090)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-07-03 14:38:52 +02:00
Ken 28430f1345 🐛 Fikset layout-bug i skjemakomponenter (#2089) 2023-07-03 14:34:50 +02:00
289 changed files with 12244 additions and 5780 deletions

No files matched your search

+3 -3
View File
@@ -75,7 +75,7 @@ jobs:
with:
issue-number: ${{ github.event.pull_request.number }}
comment-author: "github-actions[bot]"
body-includes: github-comment-finder
body-includes: Storybook demo
- name: Create or update comment
if: steps.chromatic_tests.outcome == 'success' && !contains(github.event.head_commit.message, '[ci skip]') && github.event_name != 'push'
@@ -84,7 +84,7 @@ jobs:
comment-id: ${{ steps.fc.outputs.comment-id }}
issue-number: ${{ github.event.pull_request.number }}
body: |
### [<img id="github-comment-finder" src="https://raw.githubusercontent.com/storybookjs/brand/main/logo/logo-storybook-inverse.svg" height="32"/>](${{steps.storybook_url.outputs.replaced}})
## [Storybook demo](${{steps.storybook_url.outputs.replaced}})
[${{ steps.vars.outputs.sha_short }}](https://github.com/navikt/aksel/commit/${{ steps.vars.outputs.sha_short }}) | ${{steps.chromatic_tests.outputs.componentCount}} komponenter | ${{steps.chromatic_tests.outputs.specCount}} stories
edit-mode: replace
@@ -96,7 +96,7 @@ jobs:
comment-id: ${{ steps.fc.outputs.comment-id }}
issue-number: ${{ github.event.pull_request.number }}
body: |
### [<img id="github-comment-finder" src="https://raw.githubusercontent.com/storybookjs/brand/main/logo/logo-storybook-inverse.svg" height="32"/>](${{steps.storybook_url.outputs.replaced}})
## [Storybook demo](${{steps.storybook_url.outputs.replaced}})
### [Endringer til review](${{steps.chromatic_tests.outputs.url}}): ${{steps.chromatic_tests.outputs.changeCount}}
+5
View File
@@ -10,6 +10,11 @@ module.exports = (indexers) => {
const exampleIndexer = async (fileName: string, opts) => {
let code = readFileSync(fileName, "utf-8").toString();
code = code
.split("\n")
.filter((line) => !line.includes("withDsExample"))
.join("\n");
code = code.replace("export const args =", "const args =");
code += `\nexport default { title: "Eksempler/${fileName
+3 -8
View File
@@ -1,5 +1,4 @@
const storyIndexers = require("./indexer");
const path = require("path");
const { mergeConfig } = require("vite");
@@ -59,15 +58,11 @@ module.exports = {
},
async viteFinal(config) {
return mergeConfig(config, {
define: { "process.env": {} },
define: {
"process.env": {},
},
resolve: {
alias: [
/*
// Example
{
find: "@components",
replacement: path.resolve(__dirname, "./src/components"),
}, */
{
find: "components",
replacement: path.resolve(
+42
View File
@@ -1,5 +1,47 @@
# @navikt/aksel-icons
## 5.0.3
## 5.0.2
## 5.0.1
## 5.0.0
## 4.12.1
## 4.12.0
## 4.11.2
## 4.11.1
## 4.11.0
## 4.10.2
## 4.10.1
## 4.10.0
## 4.9.1
## 4.9.0
## 4.8.0
## 4.7.4
## 4.7.3
## 4.7.2
## 4.7.1
## 4.7.0
## 4.6.1
## 4.6.0
## 4.5.0
@@ -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.6.0",
"version": "5.0.3",
"author": "Aksel | NAV",
"homepage": "https://aksel.nav.no/ikoner",
"repository": {
+42
View File
@@ -1,5 +1,47 @@
# @navikt/aksel-stylelint
## 5.0.3
## 5.0.2
## 5.0.1
## 5.0.0
## 4.12.1
## 4.12.0
## 4.11.2
## 4.11.1
## 4.11.0
## 4.10.2
## 4.10.1
## 4.10.0
## 4.9.1
## 4.9.0
## 4.8.0
## 4.7.4
## 4.7.3
## 4.7.2
## 4.7.1
## 4.7.0
## 4.6.1
## 4.6.0
## 4.5.0
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/aksel-stylelint",
"version": "4.6.0",
"version": "5.0.3",
"author": "Aksel | NAV",
"homepage": "https://aksel.nav.no/grunnleggende/kode/stylelint",
"repository": {
@@ -35,8 +35,8 @@
"dev": "yarn watch:lint"
},
"devDependencies": {
"@navikt/ds-css": "^4.6.0",
"@navikt/ds-tokens": "^4.6.0",
"@navikt/ds-css": "^5.0.3",
"@navikt/ds-tokens": "^5.0.3",
"@types/jest": "^29.0.0",
"concurrently": "7.2.1",
"copyfiles": "2.4.1",
@@ -25,4 +25,17 @@ export const deprecations: DeprecatedList = [
message:
"In v4.1.0 Chips. Toggle no longer handles special alignment for checkmark-icon, thus removing this class",
},
{
classes: ["navds-modal__overlay"],
message:
"Removed in v5.0.0, use `.navds-modal::backdrop` and `.navds-modal--polyfilled + .backdrop` instead",
},
{
classes: ["navds-modal__button--shake"],
message: "Removed in v5.0.0",
},
{
classes: ["navds-modal__content"],
message: "Renamed to `.navds-modal__body` in v5.0.0",
},
];
+147
View File
@@ -1,5 +1,152 @@
# @navikt/aksel
## 5.0.3
### Patch Changes
- Updated dependencies [[`8d934e093`](https://github.com/navikt/aksel/commit/8d934e0931caaa6f774a8f1b20fa34a19ece7218), [`8d934e093`](https://github.com/navikt/aksel/commit/8d934e0931caaa6f774a8f1b20fa34a19ece7218)]:
- @navikt/ds-css@5.0.3
## 5.0.2
### Patch Changes
- Updated dependencies []:
- @navikt/ds-css@5.0.2
## 5.0.1
### Patch Changes
- Updated dependencies [[`90db7dd0c`](https://github.com/navikt/aksel/commit/90db7dd0c120c16a387d3169c05c1f33dc694323)]:
- @navikt/ds-css@5.0.1
## 5.0.0
### Patch Changes
- Updated dependencies [[`2993f809a`](https://github.com/navikt/aksel/commit/2993f809adbf26fe88237066fea7f3e1a21b6690), [`2993f809a`](https://github.com/navikt/aksel/commit/2993f809adbf26fe88237066fea7f3e1a21b6690), [`3438b5d7e`](https://github.com/navikt/aksel/commit/3438b5d7e75037889946d4f89e95cd1f91bbb2d0)]:
- @navikt/ds-css@5.0.0
## 4.12.1
### Patch Changes
- Updated dependencies [[`5cda39ce1`](https://github.com/navikt/aksel/commit/5cda39ce19fa3f25ae18a0b07dd85e97378edd26)]:
- @navikt/ds-css@4.12.1
## 4.12.0
### Patch Changes
- Updated dependencies [[`1c482e03b`](https://github.com/navikt/aksel/commit/1c482e03b845c4e6f6f1e174a58992fbeeabb52e), [`107d6f3d9`](https://github.com/navikt/aksel/commit/107d6f3d935cbc9bb94bc15cea63431cd15bbd61), [`09d41e9b6`](https://github.com/navikt/aksel/commit/09d41e9b6da5486a55faaf8088191b4a8374e420), [`a402777c7`](https://github.com/navikt/aksel/commit/a402777c70a18899ef4239b10a6335a671e26167)]:
- @navikt/ds-css@4.12.0
## 4.11.2
### Patch Changes
- Updated dependencies [[`72321e384`](https://github.com/navikt/aksel/commit/72321e3846f785318ebdba9f8e06cf13b09cd524)]:
- @navikt/ds-css@4.11.2
## 4.11.1
### Patch Changes
- Updated dependencies [[`e68ab5cb2`](https://github.com/navikt/aksel/commit/e68ab5cb2cd91c2f0234c864ee71821ed07a8203)]:
- @navikt/ds-css@4.11.1
## 4.11.0
### Patch Changes
- Updated dependencies [[`64901adcc`](https://github.com/navikt/aksel/commit/64901adccb8f19fbe06cdab4a70d47978e90e343), [`0de8238ec`](https://github.com/navikt/aksel/commit/0de8238ecf22e9f1fbc0e1b0cfe7a5cd7254901e), [`647b14a23`](https://github.com/navikt/aksel/commit/647b14a23888f82bacc4806d3afaacb5e39178fa)]:
- @navikt/ds-css@4.11.0
## 4.10.2
### Patch Changes
- Updated dependencies []:
- @navikt/ds-css@4.10.2
## 4.10.1
### Patch Changes
- Updated dependencies [[`d3bd1202e`](https://github.com/navikt/aksel/commit/d3bd1202eddb23dd3f1f09c3e4086ff3b19737d6)]:
- @navikt/ds-css@4.10.1
## 4.10.0
### Patch Changes
- Updated dependencies [[`267799ca0`](https://github.com/navikt/aksel/commit/267799ca0ecc0c549e159f94124befb654e29331)]:
- @navikt/ds-css@4.10.0
## 4.9.1
### Patch Changes
- Updated dependencies [[`eff8a902c`](https://github.com/navikt/aksel/commit/eff8a902c50fd5ebc56616e866aa0a61b9eb5efc)]:
- @navikt/ds-css@4.9.1
## 4.9.0
### Patch Changes
- Updated dependencies [[`e033b34c3`](https://github.com/navikt/aksel/commit/e033b34c3b85fb98dd11dd0300bb20075e1b06e8)]:
- @navikt/ds-css@4.9.0
## 4.8.0
### Patch Changes
- Updated dependencies [[`8a82e1c1f`](https://github.com/navikt/aksel/commit/8a82e1c1f66ea38fca77b25d74b14926bd817dd9)]:
- @navikt/ds-css@4.8.0
## 4.7.4
### Patch Changes
- Updated dependencies [[`492cfc180`](https://github.com/navikt/aksel/commit/492cfc1801df4ab16306185345d5089e24a0d659)]:
- @navikt/ds-css@4.7.4
## 4.7.3
### Patch Changes
- Updated dependencies []:
- @navikt/ds-css@4.7.3
## 4.7.2
### Patch Changes
- Updated dependencies [[`e6fdfdc9b`](https://github.com/navikt/aksel/commit/e6fdfdc9b44156b6eb8ebc0eda3a9259dce800ff)]:
- @navikt/ds-css@4.7.2
## 4.7.1
### Patch Changes
- Updated dependencies []:
- @navikt/ds-css@4.7.1
## 4.7.0
### Patch Changes
- Updated dependencies [[`e3027cc19`](https://github.com/navikt/aksel/commit/e3027cc1966623433cfc2d28915273486c7bf611)]:
- @navikt/ds-css@4.7.0
## 4.6.1
### Patch Changes
- Updated dependencies [[`28430f134`](https://github.com/navikt/aksel/commit/28430f13455a8ecc10447e5e1ad75625d29c4bcb)]:
- @navikt/ds-css@4.6.1
## 4.6.0
### Patch Changes
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/aksel",
"version": "4.6.0",
"version": "5.0.3",
"description": "Aksel command line interface. Handles css-imports, codemods and more",
"author": "Aksel | NAV designsystem team",
"license": "MIT",
@@ -29,7 +29,7 @@
},
"homepage": "https://aksel.nav.no/grunnleggende/kode/kommandolinje",
"dependencies": {
"@navikt/ds-css": "4.6.0",
"@navikt/ds-css": "5.0.3",
"axios": "1.3.6",
"chalk": "4.1.0",
"clipboardy": "^2.3.0",
+133
View File
@@ -1,5 +1,138 @@
# @navikt/ds-css
## 5.0.3
### Patch Changes
- ConfirmationPanel: Fikset opplesing av feilmelding ([#2199](https://github.com/navikt/aksel/pull/2199))
- Checkbox: Fikset checkbox-alignment ved bruk av tailwindcss ([#2199](https://github.com/navikt/aksel/pull/2199))
## 5.0.2
## 5.0.1
### Patch Changes
- Checkbox: Hotfix da checkmark ikke var sentrert hvis man brukte tailwind ([`90db7dd0c`](https://github.com/navikt/aksel/commit/90db7dd0c120c16a387d3169c05c1f33dc694323))
## 5.0.0
### Major Changes
- Oppdatert Modal - Se [Migrering](https://aksel.nav.no/grunnleggende/kode/migrering) ([#2135](https://github.com/navikt/aksel/pull/2135))
- :sparkles: Støtte for header og footer
- :boom: Bruker nå native dialog i stedet for react-modal (med polyfill for eldre nettlesere)
### Patch Changes
- Table: ExpandableRow har oppdatert knapp for å matche Accordion og ExpansionCard ([#2178](https://github.com/navikt/aksel/pull/2178))
- Table: Alle størrelser har justert padding. Small-size table bruker nå også standard typografi-størrelse (18px). ([#2178](https://github.com/navikt/aksel/pull/2178))
## 4.12.1
### Patch Changes
- Added red border to Combobox in error state ([#2184](https://github.com/navikt/aksel/pull/2184))
## 4.12.0
### Minor Changes
- CopyButton: Har nå prop 'iconPosition' for å høyre/venstre aligne ikon ([#2173](https://github.com/navikt/aksel/pull/2173))
### Patch Changes
- List: Fikset sentrering, margins ([#2168](https://github.com/navikt/aksel/pull/2168))
- Checkbox: Checkmark er nå SVG-ikon og ikke Base64 ([#2171](https://github.com/navikt/aksel/pull/2171))
- Alert: Fikset alignment av status-ikon mot tekst ([#2179](https://github.com/navikt/aksel/pull/2179))
## 4.11.2
### Patch Changes
- :lipstick: ToggleGroup: fjern semibold fra selected button ([#2167](https://github.com/navikt/aksel/pull/2167))
## 4.11.1
### Patch Changes
- Border-radius: Fikset hardkodet border-radius i Datepicker, ToggleGroup og Combobox. ([#2159](https://github.com/navikt/aksel/pull/2159))
## 4.11.0
### Minor Changes
- HGrid: Ny komponent 'HGrid' er ute i Beta: [Dokumentasjon](https://aksel.nav.no/komponenter/core/hgrid) ([#1838](https://github.com/navikt/aksel/pull/1838))
### Patch Changes
- List: Fikset alignment av ikoner ([#2149](https://github.com/navikt/aksel/pull/2149))
- Font: La til egen font for semibold italic for bedre skalering cross-browser ([#2150](https://github.com/navikt/aksel/pull/2150))
## 4.10.2
### Patch Changes
- Link: Fikset visited farger og inline SVG-reset ved bruk av tailwind
## 4.10.0
### Minor Changes
- Link: La til 'variant', 'underline' og 'inlineText'-prop ([#2093](https://github.com/navikt/aksel/pull/2093))
## 4.9.1
### Patch Changes
- Tweaks to combobox - updated example, small bugfixes, better affordance for selected options and added flag for "isAddedByUser" to onToggleSelected ([#2144](https://github.com/navikt/aksel/pull/2144))
## 4.9.0
### Minor Changes
- Table: ExpandableRow kan nå åpnes med 'expandOnRowClick'-prop ([#2127](https://github.com/navikt/aksel/pull/2127))
## 4.8.0
### Minor Changes
- Nye komponenter `VStack`, `HStack` og `Spacer` for å enklere kunne lage layout med flexbox og spacing-variabler. ([#2040](https://github.com/navikt/aksel/pull/2040))
## 4.7.4
### Patch Changes
- Oppdatert bruk av REM i komponenter for forbedret utrykk ved font-scaling i browser ([#2126](https://github.com/navikt/aksel/pull/2126))
## 4.7.3
## 4.7.2
### Patch Changes
- Stepper: Fikset `hotizontal`-bug når step var `completed` ([#2116](https://github.com/navikt/aksel/pull/2116))
## 4.7.1
## 4.7.0
### Minor Changes
- Ny komponent Combobox! ([#1868](https://github.com/navikt/aksel/pull/1868))
## 4.6.1
### Patch Changes
- Skjema: Labels og Legends bruker nå inline-flex når readOnly er satt ([#2089](https://github.com/navikt/aksel/pull/2089))
## 4.6.0
### Minor Changes
+8 -1
View File
@@ -24,10 +24,17 @@
font-size: 1.5rem;
align-self: flex-start;
height: var(--a-font-line-height-xlarge);
margin: var(--a-spacing-1);
margin-inline: var(--a-spacing-1);
margin-block: 0;
margin-block-start: -1px;
background: radial-gradient(circle, var(--a-surface-default) 50%, 0, transparent);
}
.navds-alert--close-button > .navds-alert__icon,
.navds-alert--small > .navds-alert__icon {
margin-block-start: 0;
}
.navds-alert--small > .navds-alert__icon {
height: var(--a-font-line-height-large);
}
+7
View File
@@ -5,6 +5,13 @@
font-display: swap;
src: url("https://cdn.nav.no/aksel/fonts/SourceSans3-italic.woff2") format("woff2");
}
@font-face {
font-family: "Source Sans Pro";
font-style: italic;
font-weight: 600;
font-display: swap;
src: url("https://cdn.nav.no/aksel/fonts/SourceSans3-italic-latin-semibold") format("woff2");
}
@font-face {
font-family: "Source Sans Pro";
font-style: normal;
+2 -2
View File
@@ -25,11 +25,11 @@
padding: 0 var(--a-spacing-3);
text-decoration: none;
border-radius: var(--a-border-radius-full);
min-height: 32px;
min-height: 2rem;
}
.navds-chips--small .navds-chips__chip {
min-height: 24px;
min-height: 1.5rem;
padding: 0 var(--a-spacing-2);
}
+22 -1
View File
@@ -54,6 +54,11 @@ const StyleMappings = {
{ component: "Checkbox", main: formCss, dependencies: [typoCss] },
{ component: "CheckboxGroup", main: formCss, dependencies: [typoCss] },
{ component: "Chips", main: "chips.css", dependencies: [typoCss] },
{
component: "UNSAFE_Combobox",
main: "combobox.css",
dependencies: [typoCss, "chips.css"],
},
{
component: "ConfirmationPanel",
main: formCss,
@@ -94,6 +99,10 @@ const StyleMappings = {
main: "help-text.css",
dependencies: ["popover.css"],
},
{
component: "HGrid",
main: "hgrid.css",
},
{ component: "Ingress", main: typoCss },
{
component: "InternalHeader",
@@ -112,7 +121,7 @@ const StyleMappings = {
{
component: "Modal",
main: "modal.css",
dependencies: ["button.css"],
dependencies: [typoCss, "button.css"],
},
{
component: "Pagination",
@@ -129,6 +138,18 @@ const StyleMappings = {
{ component: "Select", main: formCss, dependencies: [typoCss] },
{ component: "Skeleton", main: "skeleton.css", dependencies: [] },
{ component: "Stepper", main: "stepper.css", dependencies: [typoCss] },
{
component: "HStack",
main: "stack.css",
},
{
component: "VStack",
main: "stack.css",
},
{
component: "Spacer",
main: "stack.css",
},
{ component: "Switch", main: formCss, dependencies: [typoCss] },
{ component: "Table", main: "table.css", dependencies: [typoCss] },
{ component: "Tabs", main: "tabs.css", dependencies: [typoCss] },
+9 -2
View File
@@ -52,7 +52,14 @@
.navds-copybutton__icon {
font-size: 1.5rem;
display: flex;
margin-left: -4px;
}
.navds-copybutton__icon:first-of-type {
margin-left: -0.25rem;
}
.navds-copybutton__icon:last-of-type {
margin-right: -0.25rem;
}
.navds-copybutton__icon:only-child {
@@ -60,7 +67,7 @@
}
:where(.navds-copybutton--small, .navds-copybutton--xsmall):where(:not(:only-child)) {
margin: -2px;
margin: -0.125rem;
}
.navds-copybutton:focus-visible {
+3 -4
View File
@@ -223,7 +223,7 @@
.navds-date__field-button {
position: absolute;
right: 1px;
right: 0.0625rem;
top: 50%;
transform: translateY(-50%);
color: var(--ac-date-input-button-text, var(--a-text-default));
@@ -233,12 +233,12 @@
text-decoration: none;
border: none;
background: none;
border-radius: 3px;
border-radius: calc(var(--a-border-radius-medium) - 1px);
padding: var(--a-spacing-3);
align-items: center;
justify-content: center;
font-size: 1.5rem;
height: calc(100% - 2px);
height: calc(100% - 0.125rem);
border-start-start-radius: 0;
border-end-start-radius: 0;
}
@@ -284,7 +284,6 @@
}
/* Readonly */
.navds-date__field--readonly .navds-date__field-button {
cursor: default;
}
+1 -1
View File
@@ -66,7 +66,7 @@
*************************/
.navds-expansioncard__title--small {
margin-top: 10px;
margin-top: 0.625rem;
}
.navds-expansioncard__title--medium {
+320
View File
@@ -0,0 +1,320 @@
.navds-combobox__wrapper {
display: flex;
flex-direction: column;
width: 100%;
position: relative;
}
.navds-combobox__wrapper-inner {
position: relative;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
width: 100%;
padding: calc(var(--a-spacing-2) - 2px) var(--a-spacing-2);
}
.navds-combobox__wrapper-inner > :first-child {
flex: 2;
}
.navds-combobox__wrapper-inner > :last-child {
display: flex;
flex-flow: row nowrap;
}
.navds-form-field--small .navds-combobox__wrapper-inner {
padding: var(--a-spacing-1) calc(var(--a-spacing-3) / 2);
}
.navds-combobox__wrapper-inner:hover {
cursor: text;
}
.navds-combobox--error .navds-text-field__input:not(:hover):not(:disabled) {
border-color: var(--ac-combobox-error-border, var(--a-border-danger));
box-shadow: 0 0 0 1px var(--ac-combobox-error-border, var(--a-border-danger));
}
.navds-combobox--error
.navds-text-field__input:not(:hover):not(:disabled):not(.navds-combobox__wrapper-inner--virtually-unfocused):focus-within {
box-shadow: 0 0 0 1px var(--ac-combobox-error-border, var(--a-border-danger)), var(--a-shadow-focus);
}
.navds-combobox__selected-options {
gap: var(--a-spacing-2);
align-items: center;
}
.navds-combobox__selected-options > li {
margin: auto 0;
border-radius: var(--a-border-radius-full);
}
.navds-combobox__selected-options > li:last-of-type {
display: flex;
flex: 1;
}
.navds-combobox__selected-options--no-bg {
font-family: inherit;
font-size: var(--a-font-size-large);
font-weight: var(--a-font-weight-regular);
letter-spacing: 0;
line-height: var(--a-font-line-height-large);
margin: 0;
padding-left: 0.25rem;
}
.navds-combobox__input-wrapper {
width: 100%;
}
.navds-combobox__input {
z-index: 1;
flex: 1;
border: none;
padding: 0;
margin: 0;
min-width: 10ch;
width: 100%;
height: 2rem;
}
.navds-combobox__input:focus-visible {
outline: none;
border: none;
box-shadow: none;
}
.navds-combobox__wrapper-inner:has(.navds-combobox__input:focus-visible) {
box-shadow: 0 0 0 1px var(--a-surface-default) inset, var(--a-shadow-focus);
box-shadow: var(--a-shadow-focus);
}
.navds-combobox__wrapper-inner:has(.navds-combobox__input:focus-visible).navds-combobox__wrapper-inner--virtually-unfocused {
box-shadow: none;
}
@supports not selector(:focus-visible) {
.navds-combobox__input:focus {
outline: none;
border: none;
box-shadow: none;
}
.navds-combobox__wrapper-inner:has(.navds-combobox__input:focus) {
box-shadow: 0 0 0 1px var(--a-surface-default) inset, var(--a-shadow-focus);
box-shadow: var(--a-shadow-focus);
}
.navds-combobox__wrapper-inner:has(.navds-combobox__input:focus).navds-combobox__wrapper-inner--virtually-unfocused {
box-shadow: none;
}
}
@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));
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
background: none;
border: none;
font-size: 1rem;
padding: 0;
}
.navds-combobox__button-clear svg {
width: 1.5rem;
height: 1.5rem;
}
.navds-combobox__input::-webkit-search-cancel-button {
display: none;
}
.navds-combobox__button-toggle-list {
border-radius: var(--a-border-radius-medium);
color: var(--a-text-default);
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
background: none;
border: none;
font-size: 1rem;
padding: 0;
}
.navds-combobox__button-clear:active:hover,
.navds-combobox__button-toggle-list:active:hover {
color: var(--ac-combobox-clear-icon-active, var(--a-text-action));
}
.navds-combobox__button-clear:hover,
.navds-combobox__button-toggle-list:hover {
color: var(--ac-combobox-clear-icon-hover, var(--a-text-action-selected));
}
.navds-combobox__button-toggle-list:focus-visible {
box-shadow: 0 0 0 1px var(--a-surface-default) inset, var(--a-shadow-focus);
box-shadow: var(--a-shadow-focus);
outline: none;
}
@supports not selector(:focus-visible) {
.navds-combobox__button-toggle-list:focus {
box-shadow: 0 0 0 1px var(--a-surface-default) inset, var(--a-shadow-focus);
box-shadow: var(--a-shadow-focus);
outline: none;
}
}
.navds-combobox__button-toggle-list svg {
width: 1.5rem;
height: 1.5rem;
pointer-events: none;
}
.navds-form-field--small .navds-combobox__button-toggle-list svg,
.navds-form-field--small .navds-combobox__button-clear svg {
width: 1.25rem;
height: 1.25rem;
}
/* dropdown list */
.navds-combobox__list {
max-height: 290px;
overflow-y: auto;
position: absolute;
left: 0;
right: 0;
z-index: 9999;
top: calc(100% + var(--a-spacing-2));
list-style: none;
margin: 0;
border: 1px solid var(--ac-combobox-list-border-color, var(--a-border-divider));
display: flex;
flex-direction: column;
align-items: flex-start;
padding: 0;
box-shadow: var(--a-shadow-small);
border-radius: var(--a-border-radius-medium);
gap: var(--a-spacing-1) 0;
background-color: var(--ac-combobox-list-bg, var(--a-surface-default));
color: var(--ac-combobox-list-text, var(--a-text-default));
}
.navds-combobox__list--closed {
display: none;
}
.navds-combobox__list svg {
height: 1.5rem;
width: 1.5rem;
}
.navds-combobox__list-item,
.navds-combobox__list-item__no-options,
.navds-combobox__list-item__new-option {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: var(--a-spacing-3);
width: 100%;
background-color: var(--ac-combobox-list-item-bg, var(--a-surface-default));
}
.navds-form-field--small .navds-combobox__list-item,
.navds-form-field--small .navds-combobox__list-item__no-options,
.navds-form-field--small .navds-combobox__list-item__new-option {
padding: calc(var(--a-spacing-3) / 2) var(--a-spacing-2);
}
.navds-combobox__list-item--loading {
display: flex;
justify-content: center;
padding: var(--a-spacing-3);
background-color: var(--ac-combobox-list-item-loading-bg, var(--a-surface-default));
width: 100%;
}
.navds-combobox__list-item--focus,
.navds-combobox__list-item:hover {
background-color: var(--ac-combobox-list-item-hover-bg, var(--a-surface-hover));
cursor: pointer;
border-left: 4px solid var(--ac-combobox-list-item-hover-border-left, var(--a-border-strong));
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));
border-left: 4px solid var(--ac-combobox-list-item-selected-hover-border-left, var(--a-border-focus));
padding-left: calc(var(--a-spacing-3) - 4px);
}
.navds-combobox__list-item__new-option {
border-bottom: 1px solid var(--a-border-divider);
background: var(--a-surface-neutral-subtle);
cursor: pointer;
justify-content: flex-start;
gap: 0.25rem;
}
.navds-combobox__list-item__new-option:hover {
border-bottom: 1px solid var(--a-border-divider);
background: var(--a-surface-neutral-subtle-hover);
}
.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: calc(var(--a-border-radius-medium) - 1px);
}
/* Mobile */
@media (max-width: 479px) {
.navds-combobox__button-toggle-list {
right: 0.5rem;
}
/* add bigger click area for input */
.navds-combobox__input {
min-width: min-content;
padding: 0.75rem 0;
}
.navds-combobox__selected-options {
gap: var(--a-spacing-1);
}
}
+4
View File
@@ -30,6 +30,10 @@
padding: 0;
}
.navds-fieldset--readonly > :where(.navds-fieldset__legend) {
display: inline-flex;
}
.navds-fieldset:disabled > .navds-fieldset__legend,
.navds-fieldset:disabled > .navds-fieldset__description {
opacity: 0.3;
+8 -3
View File
@@ -9,7 +9,7 @@
}
.navds-form-field__description {
margin-top: -6px;
margin-top: -0.375rem;
color: var(--ac-form-description, var(--a-text-subtle));
}
@@ -37,8 +37,13 @@
color: var(--ac-form-subdescription, var(--a-text-subtle));
}
.navds-form-field--readonly > :where(.navds-form-field__label) {
display: inline-flex;
}
.navds-form-field__readonly-icon {
margin-top: var(--a-spacing-05);
margin-right: var(--a-spacing-2);
vertical-align: middle;
margin-bottom: 3px;
flex-shrink: 0;
align-self: flex-start;
}
+1
View File
@@ -11,3 +11,4 @@
@import "text-field.css";
@import "textarea.css";
@import "search.css";
@import "combobox.css";
+38 -13
View File
@@ -7,10 +7,10 @@
.navds-checkbox__input,
.navds-radio__input {
position: absolute;
width: 48px;
height: 48px;
width: 3rem;
height: 3rem;
top: 0;
left: -12px;
left: -0.75rem;
z-index: 1;
opacity: 0;
cursor: pointer;
@@ -51,15 +51,15 @@
.navds-checkbox--small > .navds-checkbox__input,
.navds-radio--small > .navds-radio__input {
width: 32px;
height: 32px;
width: 2rem;
height: 2rem;
top: 0;
left: -6px;
left: -0.375rem;
}
.navds-checkbox--small > .navds-checkbox__label,
.navds-radio--small > .navds-radio__label {
padding: 6px 0;
padding: 0.375rem 0;
}
.navds-checkbox--small > .navds-checkbox__label::before,
@@ -114,16 +114,34 @@
}
.navds-checkbox__input:checked + .navds-checkbox__label::before {
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMyAxMCI+ICAgIDxnPiAgICA8cGF0aCBmaWxsPSIjRkZGRkZGIiBkPSJNNCwxMGMtMC40LDAtMC44LTAuMS0xLjEtMC40TDAuNCw3LjFDMC4xLDYuOCwwLDYuNCwwLDZzMC4yLTAuOCwwLjUtMS4xQzEsNC40LDIsNC40LDIuNSw0LjlMNCw2LjRsNi40LTYgICAgQzEwLjgsMC4xLDExLjEsMCwxMS41LDBjMC40LDAsMC44LDAuMiwxLDAuNWMwLjYsMC42LDAuNSwxLjYtMC4xLDIuMXYwTDUsOS42QzQuNyw5LjksNC40LDEwLDQsMTB6IE0xMS44LDEuOUwxMS44LDEuOSAgICBDMTEuOCwxLjksMTEuOCwxLjksMTEuOCwxLjl6IE0xMS4yLDEuMUMxMS4yLDEuMSwxMS4yLDEuMSwxMS4yLDEuMUwxMS4yLDEuMXoiLz4gICAgPC9nPjwvc3ZnPg==);
background-position: 6px center;
background-repeat: no-repeat;
background-size: 13px;
box-shadow: none;
background-color: var(--ac-radio-checkbox-action, var(--a-surface-action));
}
.navds-checkbox__input:where(:not(:checked)) + .navds-checkbox__label > .navds-checkbox__icon {
display: none;
}
.navds-checkbox__input:checked + .navds-checkbox__label > .navds-checkbox__icon {
color: var(--ac-radio-checkbox-bg, var(--a-surface-default));
position: absolute;
height: 1.5rem;
transform: translate(0.375rem);
pointer-events: none;
}
.navds-checkbox--small .navds-checkbox__input:checked + .navds-checkbox__label > .navds-checkbox__icon {
transform: translate(0.25rem, -10%);
}
/* Tailwind sets all svg to block */
.navds-checkbox__icon > svg {
display: inline;
vertical-align: initial;
}
.navds-checkbox--small > .navds-checkbox__input:checked + .navds-checkbox__label::before {
background-position: 4px center;
background-position: 0.25rem center;
}
.navds-checkbox__input:indeterminate:focus-visible + .navds-checkbox__label::before,
@@ -254,6 +272,10 @@
cursor: default;
}
.navds-checkbox--readonly .navds-checkbox__label-text {
display: inline-flex;
}
.navds-checkbox--readonly > .navds-checkbox__input:hover + .navds-checkbox__label,
.navds-radio--readonly > .navds-radio__input:hover + .navds-radio__label {
color: var(--a-text-default);
@@ -279,11 +301,14 @@
}
.navds-checkbox--readonly > .navds-checkbox__input:checked + .navds-checkbox__label::before {
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMyAxMCI+ICAgIDxnPiAgICA8cGF0aCBmaWxsPSJyZ2JhKDAsIDAsIDAsIDAuNjUpIiBkPSJNNCwxMGMtMC40LDAtMC44LTAuMS0xLjEtMC40TDAuNCw3LjFDMC4xLDYuOCwwLDYuNCwwLDZzMC4yLTAuOCwwLjUtMS4xQzEsNC40LDIsNC40LDIuNSw0LjlMNCw2LjRsNi40LTYgICAgQzEwLjgsMC4xLDExLjEsMCwxMS41LDBjMC40LDAsMC44LDAuMiwxLDAuNWMwLjYsMC42LDAuNSwxLjYtMC4xLDIuMXYwTDUsOS42QzQuNyw5LjksNC40LDEwLDQsMTB6IE0xMS44LDEuOUwxMS44LDEuOSAgICBDMTEuOCwxLjksMTEuOCwxLjksMTEuOCwxLjl6IE0xMS4yLDEuMUMxMS4yLDEuMSwxMS4yLDEuMSwxMS4yLDEuMUwxMS4yLDEuMXoiLz4gICAgPC9nPjwvc3ZnPg==);
box-shadow: inset 0 0 0 2px var(--__ac-radio-checkbox-readonly-border);
background-color: var(--__ac-radio-checkbox-readonly-bg);
}
.navds-checkbox--readonly > .navds-checkbox__input:checked + .navds-checkbox__label > .navds-checkbox__icon {
color: var(--a-icon-subtle);
}
.navds-radio--readonly > .navds-radio__input:checked + .navds-radio__label::before {
box-shadow: inset 0 0 0 2px var(--__ac-radio-checkbox-readonly-border), inset 0 0 0 4px var(--__ac-radio-checkbox-readonly-bg);
background-color: var(--a-icon-subtle);
+3 -3
View File
@@ -104,8 +104,8 @@
.navds-search__button-search {
flex-shrink: 0;
min-width: 64px;
min-height: 32px;
min-width: 4rem;
min-height: 2rem;
border-radius: 0;
border-top-right-radius: var(--a-border-radius-medium);
border-bottom-right-radius: var(--a-border-radius-medium);
@@ -116,7 +116,7 @@
}
.navds-form-field--small .navds-search__button-search {
min-width: 40px;
min-width: 2.5rem;
}
.navds-search__button-search.navds-button--secondary {
+2 -2
View File
@@ -6,7 +6,7 @@
color: var(--ac-select-text, var(--a-text-default));
width: 100%;
box-sizing: border-box;
min-height: 48px;
min-height: 3rem;
display: inline-block;
position: relative;
padding: 0.5rem;
@@ -45,7 +45,7 @@
}
.navds-form-field--small .navds-select__input {
min-height: 32px;
min-height: 2rem;
padding: 0 2rem 0 0.25rem;
}
+22 -18
View File
@@ -1,6 +1,6 @@
.navds-switch {
position: relative;
min-height: 48px;
min-height: 3rem;
width: fit-content;
}
@@ -10,7 +10,7 @@
.navds-switch--small {
position: relative;
min-height: 32px;
min-height: 2rem;
}
/* Input */
@@ -18,14 +18,14 @@
cursor: pointer;
position: absolute;
z-index: 1;
width: 48px;
height: 48px;
width: 3rem;
height: 3rem;
opacity: 0;
top: 0;
}
.navds-switch--small > .navds-switch__input {
height: 32px;
height: 2rem;
top: 0;
}
@@ -47,11 +47,11 @@
}
.navds-switch--small > .navds-switch__label-wrapper > .navds-switch__content {
padding: calc(var(--a-spacing-2) - 2px) 0 calc(var(--a-spacing-2) - 2px) 3.25rem;
padding: calc(var(--a-spacing-2) - var(--a-spacing-05)) 0 calc(var(--a-spacing-2) - var(--a-spacing-05)) 3.25rem;
}
.navds-switch--right.navds-switch--small > .navds-switch__label-wrapper > .navds-switch__content {
padding: calc(var(--a-spacing-2) - 2px) 3.25rem calc(var(--a-spacing-2) - 2px) 0;
padding: calc(var(--a-spacing-2) - var(--a-spacing-05)) 3.25rem calc(var(--a-spacing-2) - var(--a-spacing-05)) 0;
}
.navds-switch--with-description,
@@ -70,8 +70,8 @@
/* Track */
.navds-switch__track {
width: 44px;
height: 24px;
width: 2.75rem;
height: 1.5rem;
background-color: var(--ac-switch-bg, var(--a-surface-neutral));
position: absolute;
top: var(--a-spacing-3);
@@ -125,12 +125,12 @@
background-color: var(--ac-switch-thumb-bg, var(--a-surface-default));
color: var(--ac-switch-thumb-icon, var(--a-icon-subtle));
border-radius: var(--a-border-radius-full);
width: 20px;
height: 20px;
width: 1.25rem;
height: 1.25rem;
position: absolute;
transform: translateX(0);
left: 2px;
top: 2px;
left: var(--a-spacing-05);
top: var(--a-spacing-05);
transition: transform 250ms cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
align-items: center;
@@ -138,17 +138,17 @@
}
.navds-switch__input:checked ~ .navds-switch__track > .navds-switch__thumb {
transform: translateX(20px);
transform: translateX(1.25rem);
color: var(--ac-switch-thumb-icon-checked, var(--a-icon-success));
}
@media (hover: hover) and (pointer: fine) {
.navds-switch__input:hover ~ .navds-switch__track > .navds-switch__thumb {
transform: translateX(2px);
transform: translateX(0.125rem);
}
.navds-switch__input:checked:hover ~ .navds-switch__track > .navds-switch__thumb {
transform: translateX(18px);
transform: translateX(1.125rem);
}
}
@@ -157,7 +157,7 @@
}
.navds-switch__input:checked:disabled:hover ~ .navds-switch__track > .navds-switch__thumb {
transform: translateX(20px);
transform: translateX(1.25rem);
}
/* Loader */
@@ -197,6 +197,10 @@
color: var(--a-text-default);
}
.navds-switch--readonly .navds-switch__label {
display: inline-flex;
}
.navds-switch--readonly .navds-switch__thumb {
background-color: var(--a-surface-subtle);
box-shadow: 0 0 0 2px var(--a-border-default);
@@ -212,6 +216,6 @@
}
.navds-switch--readonly > .navds-switch__input:checked:hover ~ .navds-switch__track > .navds-switch__thumb {
transform: translateX(20px);
transform: translateX(1.25rem);
}
}
+3 -3
View File
@@ -13,7 +13,7 @@
background-color: var(--ac-textfield-bg, var(--a-surface-default));
border-radius: var(--a-border-radius-medium);
border: 1px solid var(--ac-textfield-border, var(--a-border-default));
min-height: 48px;
min-height: 3rem;
width: 100%;
color: var(--ac-textfield-text, var(--a-text-default));
}
@@ -27,8 +27,8 @@
}
.navds-form-field--small .navds-text-field__input {
padding: 0 0.5rem;
min-height: 32px;
padding: 0 var(--a-spacing-2);
min-height: 2rem;
}
.navds-text-field__input:hover {
+3 -3
View File
@@ -51,7 +51,7 @@
}
.navds-form-field--small .navds-textarea__input {
padding: 6px;
padding: 0.375rem;
}
.navds-form-field--small .navds-textarea--counter.navds-textarea__input {
@@ -64,8 +64,8 @@
font-style: italic;
position: absolute;
text-align: left;
left: 1px;
bottom: 1px;
left: 0.0625rem;
bottom: 0.0625rem;
padding: var(--a-spacing-1) var(--a-spacing-2);
}
+55
View File
@@ -0,0 +1,55 @@
.navds-hgrid {
--__ac-hgrid-columns-xs: initial;
--__ac-hgrid-columns-sm: initial;
--__ac-hgrid-columns-md: initial;
--__ac-hgrid-columns-lg: initial;
--__ac-hgrid-columns-xl: initial;
--__ac-hgrid-columns: var(--__ac-hgrid-columns-xs);
--__ac-hgrid-gap-xs: initial;
--__ac-hgrid-gap-sm: initial;
--__ac-hgrid-gap-md: initial;
--__ac-hgrid-gap-lg: initial;
--__ac-hgrid-gap-xl: initial;
--__ac-hgrid-gap: var(--__ac-hgrid-gap-xs);
display: grid;
grid-template-columns: var(--__ac-hgrid-columns);
gap: var(--__ac-hgrid-gap);
}
@media (min-width: 480px) {
.navds-hgrid {
--__ac-hgrid-columns: var(--__ac-hgrid-columns-sm, var(--__ac-hgrid-columns-xs));
--__ac-hgrid-gap: var(--__ac-hgrid-gap-sm, var(--__ac-hgrid-gap-xs));
}
}
@media (min-width: 768px) {
.navds-hgrid {
--__ac-hgrid-columns: var(--__ac-hgrid-columns-md, var(--__ac-hgrid-columns-sm, var(--__ac-hgrid-columns-xs)));
--__ac-hgrid-gap: var(--__ac-hgrid-gap-md, var(--__ac-hgrid-gap-sm, var(--__ac-hgrid-gap-xs)));
}
}
@media (min-width: 1024px) {
.navds-hgrid {
--__ac-hgrid-columns: var(
--__ac-hgrid-columns-lg,
var(--__ac-hgrid-columns-md, var(--__ac-hgrid-columns-sm, var(--__ac-hgrid-columns-xs)))
);
--__ac-hgrid-gap: var(--__ac-hgrid-gap-lg, var(--__ac-hgrid-gap-md, var(--__ac-hgrid-gap-sm, var(--__ac-hgrid-gap-xs))));
}
}
@media (min-width: 1280px) {
.navds-hgrid {
--__ac-hgrid-columns: var(
--__ac-hgrid-columns-xl,
var(--__ac-hgrid-columns-lg, var(--__ac-hgrid-columns-md, var(--__ac-hgrid-columns-sm, var(--__ac-hgrid-columns-xs))))
);
--__ac-hgrid-gap: var(
--__ac-hgrid-gap-xl,
var(--__ac-hgrid-gap-lg, var(--__ac-hgrid-gap-md, var(--__ac-hgrid-gap-sm, var(--__ac-hgrid-gap-xs))))
);
}
}
+2
View File
@@ -14,6 +14,7 @@
@import "guide-panel.css";
@import "form/index.css";
@import "help-text.css";
@import "hgrid.css";
@import "internalheader.css";
@import "link.css";
@import "loader.css";
@@ -29,6 +30,7 @@
@import "link-panel.css";
@import "read-more.css";
@import "skeleton.css";
@import "stack.css";
@import "stepper.css";
@import "table.css";
@import "tabs.css";
+1 -1
View File
@@ -2,7 +2,7 @@
display: flex;
align-self: stretch;
background: var(--ac-internalheader-bg, var(--a-surface-inverted));
min-height: var(--a-spacing-12);
min-height: 3rem;
--navds-internalheader-focus: inset 0 0 0 2px var(--a-border-focus-on-inverted);
}
+35 -3
View File
@@ -6,15 +6,35 @@
gap: var(--a-spacing-1);
}
.navds-alert .navds-link,
.navds-confirmation-panel .navds-link {
color: var(--a-text-default);
.navds-link.navds-link--inline-text {
display: inline;
}
.navds-link.navds-link--inline-text > svg {
/* stylelint-disable-next-line plugin/declaration-block-no-ignored-properties */
margin: 0.1em 0.1em -0.1em;
transform: translateY(0.05em);
display: inline;
vertical-align: baseline;
}
.navds-link:hover {
text-decoration: none;
}
.navds-link.navds-link--remove-underline {
text-decoration: none;
}
.navds-link.navds-link--remove-underline:hover {
text-decoration: underline;
}
.navds-alert .navds-link,
.navds-confirmation-panel .navds-link {
color: var(--a-text-default);
}
.navds-link:focus-visible {
outline: none;
color: var(--ac-link-focus-text, var(--a-text-on-action));
@@ -45,3 +65,15 @@
color: inherit;
flex-shrink: 0;
}
.navds-link--action {
color: var(--ac-link-action-text, var(--a-text-action));
}
.navds-link--neutral {
color: var(--ac-link-neutral-text, var(--a-text-default));
}
.navds-link--subtle {
color: var(--ac-link-subtle-text, var(--a-text-subtle));
}
+40 -9
View File
@@ -1,14 +1,24 @@
.navds-list ul,
.navds-list ol {
padding: 0;
margin-block: var(--a-spacing-4);
}
.navds-list--nested {
margin: var(--a-spacing-4) 0 var(--a-spacing-8) 0;
.navds-list--nested ul,
.navds-list--nested ol {
margin-block: var(--a-spacing-2);
}
.navds-list li:not(.navds-list__item--noMargin) {
margin-bottom: var(--a-spacing-5);
.navds-list li {
margin-block-end: var(--a-spacing-5);
}
.navds-list--small li {
margin-block-end: var(--a-spacing-2);
}
.navds-list--nested li {
margin-block-end: var(--a-spacing-3);
}
.navds-list:last-child > ul > li:last-child,
@@ -34,15 +44,30 @@
.navds-list .navds-list__item-marker--bullet {
display: flex;
margin-top: 6px;
align-items: center;
height: var(--a-font-line-height-large);
grid-column: 1 / 2;
/* stylelint-disable-next-line csstools/value-no-unknown-custom-properties */
color: var(--ac-list-marker-ul-color, var(--ac-list-marker-color, var(--a-icon-default)));
}
.navds-list--small .navds-list__item-marker--bullet {
height: var(--a-font-line-height-medium);
}
.navds-list__item-marker--icon {
/* stylelint-disable-next-line csstools/value-no-unknown-custom-properties */
color: var(--ac-list-marker-icon-color, var(--ac-list-marker-color, var(--a-icon-default)));
display: flex;
align-items: center;
height: var(--a-font-line-height-large);
padding-block-end: 1px;
}
.navds-list--small .navds-list__item-marker--icon {
height: var(--a-font-line-height-medium);
}
.navds-list__item-marker--icon svg {
flex-shrink: 0;
}
.navds-list ul .navds-list__item-content {
@@ -51,6 +76,7 @@
.navds-list ol {
padding-left: var(--a-spacing-2);
list-style: decimal;
}
.navds-list ol li {
@@ -60,7 +86,12 @@
.navds-list ol li::marker {
font-weight: var(--a-font-weight-bold);
font-size: var(--a-font-size-medium);
/* stylelint-disable-next-line csstools/value-no-unknown-custom-properties */
font-size: var(--a-font-size-large);
color: var(--ac-list-marker-ol-color, var(--ac-list-marker-color, var(--a-icon-default)));
line-height: var(--a-font-line-height-large);
}
.navds-list--small ol li::marker {
font-size: var(--a-font-size-small);
line-height: var(--a-font-line-height-medium);
}
+127 -44
View File
@@ -1,69 +1,152 @@
.ReactModal__Body--open {
.navds-modal__document-body {
overflow: hidden;
}
.navds-modal {
background-color: var(--ac-modal-bg, var(--a-surface-default));
border-radius: var(--a-border-radius-medium);
display: block;
position: relative;
overflow: auto;
max-height: 100%;
border: 1px solid var(--ac-modal-border, var(--a-border-subtle));
border-radius: var(--a-border-radius-large);
box-shadow: var(--a-shadow-xlarge);
padding: 0;
position: fixed;
max-height: 100%;
max-width: 100%;
}
.navds-modal__content {
padding: var(--a-spacing-4);
.navds-modal[open] {
display: flex;
flex-direction: column;
animation: akselModalFadeIn 0.35s cubic-bezier(0.15, 1, 0.3, 1);
}
.navds-modal:focus-visible,
.navds-modal--focus {
box-shadow: var(--a-shadow-focus);
outline: none;
.navds-modal--polyfilled {
top: 50%;
transform: translate(0, -50%);
overflow: auto;
/* From polyfill (dialog-polyfill/dist/dialog-polyfill.css): */
left: 0;
right: 0;
width: fit-content;
height: fit-content;
margin: auto;
}
@supports not selector(:focus-visible) {
.navds-modal:focus,
.navds-modal--focus {
box-shadow: var(--a-shadow-focus);
outline: none;
.navds-modal--polyfilled:not([open]) {
display: none; /* from polyfill */
}
._dialog_overlay {
/* From polyfill */
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
.navds-modal--small {
width: var(--ac-modal-width-small, 450px);
}
.navds-modal--medium {
width: var(--ac-modal-width-medium, 700px);
}
@media (min-width: 480px) {
.navds-modal {
max-width: calc((100% - 6px) - 2em);
max-height: calc((100% - 6px) - 2em);
}
.navds-modal--autowidth {
max-width: min(700px, calc((100% - 6px) - 2em));
}
}
.navds-modal__overlay {
.navds-modal::backdrop {
/*
* Cannot use --a-surface-backdrop because ::backdrop does not inherit from anything but itself.
* But consumers can do `::backdrop { --ac-modal-backdrop: white; }`
* We can add ::backdrop to tokens.css when we remove the polyfill.
*/
background: var(--ac-modal-backdrop, rgb(2 20 49 / 0.49));
animation: akselModalBackdropFadeIn 0.35s cubic-bezier(0.15, 1, 0.3, 1);
}
.navds-modal--polyfilled + .backdrop /* Cannot be combined with ::backdrop selector */ {
background: var(--ac-modal-backdrop, rgb(2 20 49 / 0.49));
/* From polyfill: */
position: fixed;
z-index: var(--a-z-index-modal);
top: 0;
right: 0;
bottom: 0;
left: 0;
right: 0;
background-color: var(--ac-modal-backdrop, var(--a-surface-backdrop));
padding: var(--a-spacing-4);
display: flex;
align-items: center;
justify-content: center;
}
.navds-modal__button--shake {
transform: rotate(0deg);
transition: transform 100ms cubic-bezier(0.82, 2, 1, 0.3);
}
/* prettier-ignore */
.navds-modal__overlay:active > .navds-modal:not(:active) > .navds-modal__button--shake {
transform: rotate(10deg);
transition: transform 100ms cubic-bezier(0.82, -2, 1, 0.3);
}
.navds-modal__button {
position: absolute;
top: var(--a-spacing-4);
right: var(--a-spacing-4);
display: flex;
padding: var(--a-spacing-2);
margin-left: var(--a-spacing-4);
float: right;
}
.navds-modal__button svg {
height: 1.5rem;
width: 1.5rem;
.navds-modal__header {
padding: var(--a-spacing-6) var(--a-spacing-6) var(--a-spacing-4) var(--a-spacing-6);
}
.navds-modal__header-icon svg {
display: inline;
vertical-align: -0.25rem;
margin-right: var(--a-spacing-1);
}
.navds-modal__label {
font-weight: 600;
color: var(--a-text-subtle);
}
.navds-modal__body {
padding: var(--a-spacing-6);
overflow: auto;
overscroll-behavior: contain;
}
.navds-modal__header + .navds-modal__body {
padding-top: var(--a-spacing-0);
}
.navds-modal__footer {
display: flex;
flex-flow: row-reverse wrap;
gap: var(--a-spacing-4);
padding: var(--a-spacing-4) var(--a-spacing-6) var(--a-spacing-6) var(--a-spacing-6);
}
.navds-modal__footer :nth-of-type(2) {
margin-left: auto;
}
@keyframes akselModalFadeIn {
from {
opacity: 0;
transform: translate3d(0, calc(5% + 4px), 0);
}
50% {
opacity: 1;
}
to {
transform: none;
}
}
@keyframes akselModalBackdropFadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-css",
"version": "4.6.0",
"version": "5.0.3",
"description": "CSS for NAV Designsystem",
"author": "Aksel | NAV designsystem team",
"keywords": [
@@ -27,11 +27,11 @@
"css:get-version": "node config/get-version.js"
},
"devDependencies": {
"@navikt/ds-tokens": "^4.6.0",
"@navikt/ds-tokens": "^5.0.3",
"cssnano": "6.0.0",
"fast-glob": "3.2.11",
"lodash": "4.17.21",
"nodemon": "^2.0.12",
"nodemon": "^3.0.1",
"normalize.css": "^8.0.1",
"postcss": "^8.4.0",
"postcss-cli": "^9.0.0",
+6 -6
View File
@@ -8,12 +8,12 @@
gap: var(--a-spacing-05);
color: var(--ac-read-more-text, var(--a-text-action));
border-radius: var(--a-border-radius-small);
padding: var(--a-spacing-1) var(--a-spacing-1) var(--a-spacing-1) 2px;
padding: var(--a-spacing-1) var(--a-spacing-1) var(--a-spacing-1) var(--a-spacing-05);
text-align: start;
}
.navds-read-more--small .navds-read-more__button {
padding: 2px var(--a-spacing-1) 2px 2px;
padding: var(--a-spacing-05) var(--a-spacing-1) var(--a-spacing-05) var(--a-spacing-05);
}
.navds-read-more__button:hover {
@@ -39,14 +39,14 @@
.navds-read-more__content {
margin-top: var(--a-spacing-1);
border-left: 2px solid var(--ac-read-more-line, var(--a-border-divider));
margin-left: 13px;
padding-left: 13px;
margin-left: 0.8125rem;
padding-left: 0.8125rem;
color: var(--a-text-default);
}
.navds-read-more--small .navds-read-more__content {
margin-left: 11px;
padding-left: 11px;
margin-left: 0.6875rem;
padding-left: 0.6875rem;
}
.navds-read-more__content--closed {
+59
View File
@@ -0,0 +1,59 @@
.navds-stack {
--__ac-stack-align: initial;
--__ac-stack-justify: initial;
--__ac-stack-direction: initial;
--__ac-stack-wrap: initial;
--__ac-stack-gap-xs: initial;
--__ac-stack-gap-sm: initial;
--__ac-stack-gap-md: initial;
--__ac-stack-gap-lg: initial;
--__ac-stack-gap-xl: initial;
--__ac-stack-gap: var(--__ac-stack-gap-xs);
gap: var(--__ac-stack-gap);
display: flex;
align-items: var(--__ac-stack-align);
justify-content: var(--__ac-stack-justify);
flex-flow: var(--__ac-stack-direction) var(--__ac-stack-wrap);
}
.navds-stack__spacer {
flex: 1;
justify-self: stretch;
align-self: stretch;
}
.navds-stack > .navds-stack__spacer {
margin-block-start: calc(var(--__ac-stack-gap) * -1);
}
.navds-hstack > .navds-stack__spacer {
margin-inline-start: calc(var(--__ac-stack-gap) * -1);
}
@media (min-width: 480px) {
.navds-stack {
--__ac-stack-gap: var(--__ac-stack-gap-sm, var(--__ac-stack-gap-xs));
}
}
@media (min-width: 768px) {
.navds-stack {
--__ac-stack-gap: var(--__ac-stack-gap-md, var(--__ac-stack-gap-sm, var(--__ac-stack-gap-xs)));
}
}
@media (min-width: 1024px) {
.navds-stack {
--__ac-stack-gap: var(--__ac-stack-gap-lg, var(--__ac-stack-gap-md, var(--__ac-stack-gap-sm, var(--__ac-stack-gap-xs))));
}
}
@media (min-width: 1280px) {
.navds-stack {
--__ac-stack-gap: var(
--__ac-stack-gap-xl,
var(--__ac-stack-gap-lg, var(--__ac-stack-gap-md, var(--__ac-stack-gap-sm, var(--__ac-stack-gap-xs))))
);
}
}
+11 -1
View File
@@ -40,6 +40,10 @@
background-color: var(--ac-stepper-line-completed, var(--a-border-selected));
}
.navds-stepper__item--behind.navds-stepper__item--completed + .navds-stepper__item > .navds-stepper__line--1 {
background-color: var(--ac-stepper-line-completed, var(--a-border-selected));
}
.navds-stepper__line--1 {
grid-row: line-1;
display: none;
@@ -109,7 +113,7 @@ button.navds-stepper__step {
.navds-stepper__circle--success {
border: none;
background: none;
font-size: 28px;
font-size: 1.75rem;
color: var(--a-text-on-action);
background-color: var(--ac-stepper-text, var(--a-surface-action));
}
@@ -213,6 +217,12 @@ button.navds-stepper__step {
background-color: var(--ac-stepper-non-interactive-line-completed, var(--a-border-strong));
}
.navds-stepper__item--non-interactive.navds-stepper__item--behind.navds-stepper__item--completed
+ .navds-stepper__item
> .navds-stepper__line--1 {
background-color: var(--ac-stepper-non-interactive-line-completed, var(--a-border-strong));
}
:where(.navds-stepper__step--non-interactive.navds-stepper__step--active) .navds-stepper__circle {
background-color: var(--ac-stepper-non-interactive-active-bg, var(--a-surface-inverted));
border-color: var(--ac-stepper-non-interactive-active-border, var(--a-surface-inverted));
+64 -37
View File
@@ -16,7 +16,7 @@
display: table-row;
}
.navds-table__body .navds-table__row--shade-on-hover:hover {
.navds-table__body .navds-table__row--shade-on-hover:not(.navds-table__expandable-row--expansion-disabled):hover {
background-color: var(--ac-table-row-hover, var(--a-bg-subtle));
}
@@ -45,7 +45,7 @@
.navds-table__header-cell,
.navds-table__data-cell {
display: table-cell;
padding: var(--a-spacing-4) var(--a-spacing-3);
padding: var(--a-spacing-3) var(--a-spacing-2);
border-bottom: 1px solid var(--ac-table-row-border, var(--a-border-default));
text-align: left;
}
@@ -74,17 +74,22 @@
border-bottom-color: transparent;
}
.navds-table--large .navds-table__header-cell,
.navds-table--large .navds-table__data-cell {
padding: var(--a-spacing-4) var(--a-spacing-2);
}
.navds-table--small .navds-table__header-cell,
.navds-table--small .navds-table__data-cell {
padding: var(--a-spacing-2) var(--a-spacing-3);
padding: var(--a-spacing-1) var(--a-spacing-2);
}
.navds-table .navds-checkbox .navds-checkbox__input {
top: -12px;
top: -0.75rem;
}
.navds-table .navds-checkbox--small .navds-checkbox__input {
top: -6px;
top: -0.375rem;
}
.navds-table .navds-checkbox .navds-checkbox__label {
@@ -154,6 +159,10 @@
transition: border-bottom-color 190ms cubic-bezier(0.6, 0.04, 0.98, 0.335);
}
.navds-table__expandable-row.navds-table__expandable-row--clickable:not(.navds-table__expandable-row--expansion-disabled):hover {
cursor: pointer;
}
.navds-table__expandable-row--open .navds-table__data-cell {
border-bottom-color: transparent;
}
@@ -167,55 +176,73 @@
}
.navds-table__toggle-expand-cell {
padding: 0;
width: 56px;
padding: 0 var(--a-spacing-2);
position: relative;
width: 2.5rem;
}
.navds-table--large .navds-table__toggle-expand-cell {
padding: 0 var(--a-spacing-2);
}
.navds-table--small .navds-table__toggle-expand-cell {
padding: 0;
width: 36px;
padding: var(--a-spacing-1) var(--a-spacing-2);
}
.navds-table__toggle-expand-button {
all: unset;
display: flex;
align-items: center;
display: grid;
place-content: center;
cursor: pointer;
padding: var(--a-spacing-4);
margin: 0;
background: transparent;
border: none;
border-radius: var(--a-border-radius-medium);
height: 2rem;
width: 2rem;
font-size: 1.5rem;
}
.navds-table--small .navds-table__toggle-expand-button {
padding: var(--a-spacing-2);
.navds-table__expandable-icon {
transition: transform 150ms ease-in-out;
}
.navds-table__toggle-expand-button:hover,
.navds-table__expandable-row--clickable:hover .navds-table__toggle-expand-button {
background-color: var(--a-surface-hover);
}
.navds-table__toggle-expand-cell--open > :where(.navds-table__toggle-expand-button) :where(.navds-table__expandable-icon) {
transform: translateY(0) rotate(180deg);
}
.navds-table__toggle-expand-button:hover > :where(.navds-table__expandable-icon) {
transform: translateY(1px);
}
.navds-table__toggle-expand-cell--open > .navds-table__toggle-expand-button:hover > :where(.navds-table__expandable-icon) {
transform: translateY(-1px) rotate(180deg);
}
.navds-table__toggle-expand-button:focus-visible {
box-shadow: inset var(--a-shadow-focus);
outline: none;
box-shadow: var(--a-shadow-focus);
}
@supports not selector(:focus-visible) {
.navds-table__toggle-expand-button:focus {
box-shadow: inset var(--a-shadow-focus);
outline: none;
box-shadow: var(--a-shadow-focus);
}
}
.navds-table__expandable-icon {
font-size: 1.5rem;
}
.navds-table--small .navds-table__expandable-icon {
font-size: 1.25rem;
}
.navds-table__toggle-expand-button:hover .navds-table__expandable-icon {
transform: translateY(1px);
}
.navds-table__toggle-expand-cell--open .navds-table__expandable-icon {
transform: rotate(180deg);
}
.navds-table__toggle-expand-cell--open .navds-table__toggle-expand-button:hover .navds-table__expandable-icon {
transform: translateY(-1px) rotate(180deg);
.navds-table__toggle-expand-button::after {
inset: 0;
z-index: 1;
position: absolute;
content: "";
height: 100%;
width: 100%;
cursor: pointer;
}
.navds-table__expanded-row-cell {
@@ -231,9 +258,9 @@
}
.navds-table__expanded-row-content {
padding: var(--a-spacing-4) 4.25rem;
padding: var(--a-spacing-4) calc(var(--a-spacing-2) + 3rem);
}
.navds-table--small .navds-table__expanded-row-content {
padding: var(--a-spacing-2) var(--a-spacing-12);
padding: var(--a-spacing-2) calc(var(--a-spacing-2) + 3rem);
}
+4 -4
View File
@@ -20,7 +20,7 @@
.navds-tabs__scroll-button {
padding: var(--a-spacing-3) var(--a-spacing-4);
width: 44px;
width: 2.75rem;
display: flex;
justify-content: center;
align-items: center;
@@ -38,11 +38,11 @@
.navds-tabs--small .navds-tabs__scroll-button {
padding: 0.375rem var(--a-spacing-4);
width: 32px;
width: 2rem;
}
.navds-tabs__tab {
min-height: 48px;
min-height: 3rem;
padding: var(--a-spacing-3) var(--a-spacing-4);
display: inline-flex;
justify-content: center;
@@ -92,7 +92,7 @@
}
.navds-tabs__tab--small {
min-height: 32px;
min-height: 2rem;
padding: 0.375rem var(--a-spacing-4);
}
+3 -3
View File
@@ -5,17 +5,17 @@
align-items: center;
justify-content: center;
padding: var(--a-spacing-05) var(--a-spacing-2);
min-height: 32px;
min-height: 2rem;
line-height: 1;
}
.navds-tag--small {
min-height: 24px;
min-height: 1.5rem;
padding: 0 0.375rem;
}
.navds-tag--xsmall {
min-height: 20px;
min-height: 1.25rem;
padding: 0 var(--a-spacing-1);
}
+3 -2
View File
@@ -12,6 +12,7 @@
height: 1rem;
box-sizing: content-box;
grid-column: 2;
margin-bottom: var(--a-spacing-1);
}
.navds-timeline__row-label {
@@ -181,7 +182,7 @@
0 0 0 4px var(--ac-timeline-pin-bg, var(--a-surface-danger)), 0 0 0 5px var(--a-surface-default);
z-index: 2;
border-radius: var(--a-border-radius-full);
padding: 3.5px;
padding: var(--a-spacing-1);
position: relative;
}
@@ -232,7 +233,7 @@
.navds-timeline__pin-wrapper::before {
content: "";
top: var(--navdsc-timeline-pin-size);
height: calc(94% - var(--navdsc-timeline-pin-size));
height: calc(88% - var(--navdsc-timeline-pin-size));
width: 1px;
margin: 0 auto;
background: var(--a-surface-inverted);
+5 -6
View File
@@ -8,7 +8,7 @@
border-radius: var(--a-border-radius-medium);
background-color: var(--ac-toggle-group-bg, var(--a-surface-transparent));
box-shadow: inset 0 0 0 1px var(--ac-toggle-group-border, var(--a-border-default));
padding: calc(var(--a-spacing-1) + 1px);
padding: calc(var(--a-spacing-1) + 0.0625rem);
gap: var(--a-spacing-1);
display: inline-grid;
grid-auto-flow: column;
@@ -16,7 +16,7 @@
}
.navds-toggle-group--small {
padding: calc(var(--a-spacing-05) + 1px);
padding: calc(var(--a-spacing-05) + 0.0625rem);
}
.navds-toggle-group--neutral {
@@ -29,12 +29,12 @@
align-items: center;
justify-content: center;
padding: var(--a-spacing-1) var(--a-spacing-3);
min-height: 38px;
min-height: 2.375rem;
border: none;
cursor: pointer;
background-color: var(--ac-toggle-group-button-bg, var(--a-surface-transparent));
color: var(--ac-toggle-group-button-text, var(--a-text-default));
border-radius: 2px;
border-radius: var(--a-border-radius-small);
min-width: fit-content;
font-weight: var(--a-font-weight-regular);
}
@@ -113,7 +113,6 @@
.navds-toggle-group__button[aria-checked="true"] {
background-color: var(--ac-toggle-group-selected-bg, var(--a-surface-action-selected));
color: var(--ac-toggle-group-selected-text, var(--a-text-on-action));
font-weight: var(--a-font-weight-bold);
}
.navds-toggle-group--neutral > .navds-toggle-group__button[aria-pressed="true"],
@@ -124,7 +123,7 @@
.navds-toggle-group--small > .navds-toggle-group__button {
padding: var(--a-spacing-05) var(--a-spacing-3);
min-height: 26px;
min-height: 1.625rem;
min-width: fit-content;
}
+24 -2
View File
@@ -175,7 +175,10 @@
"--ac-link-focus-border": "--a-border-focus",
"--ac-link-active-text": "--a-text-on-action",
"--ac-link-active-bg": "--a-border-focus",
"--ac-link-active-border": "--a-border-focus"
"--ac-link-active-border": "--a-border-focus",
"--ac-link-action-text": "--a-text-action",
"--ac-link-neutral-text": "--a-text-default",
"--ac-link-subtle-text": "--a-text-subtle"
},
"list": {
"--ac-list-marker-ul-color": "--ac-list-marker-color",
@@ -193,7 +196,10 @@
},
"modal": {
"--ac-modal-bg": "--a-surface-default",
"--ac-modal-backdrop": "--a-surface-backdrop"
"--ac-modal-border": "--a-border-subtle",
"--ac-modal-backdrop": "--a-surface-backdrop",
"--ac-modal-width-small": "450px",
"--ac-modal-width-medium": "700px"
},
"pagination": {
"--ac-pagination-text": "--a-text-default",
@@ -372,6 +378,22 @@
"--ac-search-button-focus-active-border": "--a-surface-default",
"--ac-search-error-border": "--a-border-danger"
},
"combobox": {
"--ac-combobox-clear-icon": "--a-text-subtle",
"--ac-combobox-clear-icon-hover": "--a-text-action-selected",
"--ac-combobox-clear-icon-active": "--a-text-action",
"--ac-combobox-list-bg": "--a-surface-default",
"--ac-combobox-list-text": "--a-text-default",
"--ac-combobox-list-border-color": "--a-border-divider",
"--ac-combobox-list-item-bg": "--a-surface-default",
"--ac-combobox-list-item-hover-bg": "--a-surface-hover",
"--ac-combobox-list-item-selected-bg": "--a-surface-default",
"--ac-combobox-list-item-selected-hover-bg": "--a-surface-action-subtle-hover",
"--ac-combobox-list-item-loading-bg": "--a-surface-default",
"--ac-combobox-list-item-hover-border-left": "--a-border-strong",
"--ac-combobox-list-item-selected-hover-border-left": "--a-border-focus",
"--ac-combobox-error-border": "--a-border-danger"
},
"select": {
"--ac-select-bg": "--a-surface-default",
"--ac-select-text": "--a-text-default",
+4 -4
View File
@@ -25,8 +25,8 @@
}
.navds-tooltip__arrow {
height: 7px;
width: 7px;
height: 0.4375rem;
width: 0.4375rem;
transform: rotate(45deg);
z-index: -1;
background-color: var(--ac-tooltip-bg, var(--a-surface-inverted));
@@ -45,8 +45,8 @@
color: var(--ac-tooltip-key-text, var(--a-text-on-inverted));
border-radius: var(--a-border-radius-small);
padding: 0 var(--a-spacing-1);
min-width: 18px;
height: 18px;
min-width: 1.125rem;
height: 1.125rem;
display: inline-flex;
align-items: center;
justify-content: center;
+225
View File
@@ -1,5 +1,230 @@
# @navikt/ds-react
## 5.0.3
### Patch Changes
- ConfirmationPanel: Fikset opplesing av feilmelding ([#2199](https://github.com/navikt/aksel/pull/2199))
- Updated dependencies []:
- @navikt/aksel-icons@5.0.3
## 5.0.2
### Patch Changes
- :label: Modal: Bedre type for `width` ([#2191](https://github.com/navikt/aksel/pull/2191))
- Updated dependencies []:
- @navikt/aksel-icons@5.0.2
## 5.0.1
### Patch Changes
- Updated dependencies []:
- @navikt/aksel-icons@5.0.1
## 5.0.0
### Major Changes
- Oppdatert Modal - Se [Migrering](https://aksel.nav.no/grunnleggende/kode/migrering) ([#2135](https://github.com/navikt/aksel/pull/2135))
- :sparkles: Støtte for header og footer
- :boom: Bruker nå native dialog i stedet for react-modal (med polyfill for eldre nettlesere)
- :boom: Provider: `appElement` er fjernet
### Patch Changes
- Table: ExpandableRow har oppdatert knapp for å matche Accordion og ExpansionCard ([#2178](https://github.com/navikt/aksel/pull/2178))
- Table: Har lagt til ny size: 'large'. ([#2178](https://github.com/navikt/aksel/pull/2178))
- Updated dependencies []:
- @navikt/aksel-icons@5.0.0
## 4.12.1
### Patch Changes
- Added red border to Combobox in error state ([#2184](https://github.com/navikt/aksel/pull/2184))
- Updated dependencies []:
- @navikt/aksel-icons@4.12.1
## 4.12.0
### Minor Changes
- Combobox: La til støtte for feilmeldinger i Combobox ([#2182](https://github.com/navikt/aksel/pull/2182))
- CopyButton: Har nå prop 'iconPosition' for å høyre/venstre aligne ikon ([#2173](https://github.com/navikt/aksel/pull/2173))
### Patch Changes
- List: Fikset sentrering, margins ([#2168](https://github.com/navikt/aksel/pull/2168))
- Combobox: Kjører nå 'onChange' + 'onClear' når input blir reset programmatisk ([#2183](https://github.com/navikt/aksel/pull/2183))
- Combobox: Fikset custom-options i singleselect ([#2180](https://github.com/navikt/aksel/pull/2180))
- Combobox: Fjernet unødvendige 'onClear'-calls når man velger verdier ([#2170](https://github.com/navikt/aksel/pull/2170))
- Checkbox: Checkmark er nå SVG-ikon og ikke Base64 ([#2171](https://github.com/navikt/aksel/pull/2171))
- Combobox: Lukker nå nedtrekksmeny hvis man legger til ny option i singleselect ([#2177](https://github.com/navikt/aksel/pull/2177))
- Updated dependencies []:
- @navikt/aksel-icons@4.12.0
## 4.11.2
### Patch Changes
- Updated dependencies []:
- @navikt/aksel-icons@4.11.2
## 4.11.1
### Patch Changes
- Updated dependencies []:
- @navikt/aksel-icons@4.11.1
## 4.11.0
### Minor Changes
- HGrid: Ny komponent 'HGrid' er ute i Beta: [Dokumentasjon](https://aksel.nav.no/komponenter/core/hgrid) ([#1838](https://github.com/navikt/aksel/pull/1838))
### Patch Changes
- Fixes bug where combobox list could not be closed after clicking a chip ([#2155](https://github.com/navikt/aksel/pull/2155))
- Grid: Markert som deprecated. Bruk nye 'HGrid' ([#2153](https://github.com/navikt/aksel/pull/2153))
- Updated dependencies []:
- @navikt/aksel-icons@4.11.0
## 4.10.2
### Patch Changes
- Updated dependencies []:
- @navikt/aksel-icons@4.10.2
## 4.10.1
### Patch Changes
- Updated dependencies []:
- @navikt/aksel-icons@4.10.1
## 4.10.0
### Minor Changes
- Link: La til 'variant', 'underline' og 'inlineText'-prop ([#2093](https://github.com/navikt/aksel/pull/2093))
### Patch Changes
- Updated dependencies []:
- @navikt/aksel-icons@4.10.0
## 4.9.1
### Patch Changes
- Button: Fikset aria-live bug der knapp alltid ble lest opp av skjermleser ved render ([#2143](https://github.com/navikt/aksel/pull/2143))
- Tweaks to combobox - updated example, small bugfixes, better affordance for selected options and added flag for "isAddedByUser" to onToggleSelected ([#2144](https://github.com/navikt/aksel/pull/2144))
- Updated dependencies []:
- @navikt/aksel-icons@4.9.1
## 4.9.0
### Minor Changes
- Table: ExpandableRow kan nå åpnes med 'expandOnRowClick'-prop ([#2127](https://github.com/navikt/aksel/pull/2127))
### Patch Changes
- Updated dependencies []:
- @navikt/aksel-icons@4.9.0
## 4.8.0
### Minor Changes
- Nye komponenter `VStack`, `HStack` og `Spacer` for å enklere kunne lage layout med flexbox og spacing-variabler. ([#2040](https://github.com/navikt/aksel/pull/2040))
### Patch Changes
- Updated dependencies []:
- @navikt/aksel-icons@4.8.0
## 4.7.4
### Patch Changes
- Textfield: La til type 'time' som tilgjengelig option ([#2137](https://github.com/navikt/aksel/pull/2137))
- Updated dependencies []:
- @navikt/aksel-icons@4.7.4
## 4.7.3
### Patch Changes
- Chips: Removable Chips submitter ikke forms ved klikk lengre ([#2124](https://github.com/navikt/aksel/pull/2124))
- Updated dependencies []:
- @navikt/aksel-icons@4.7.3
## 4.7.2
### Patch Changes
- Stepper: Fikset `hotizontal`-bug når step var `completed` ([#2116](https://github.com/navikt/aksel/pull/2116))
- Updated dependencies []:
- @navikt/aksel-icons@4.7.2
## 4.7.1
### Patch Changes
- Datepicker: Fikset bug ved bruk dynamisk oppdatering av minDate. Vist `month` vil nå alltid være oppdatert når datepicker åpnes ([#2117](https://github.com/navikt/aksel/pull/2117))
- Updated dependencies []:
- @navikt/aksel-icons@4.7.1
## 4.7.0
### Minor Changes
- Ny komponent Combobox! ([#1868](https://github.com/navikt/aksel/pull/1868))
### Patch Changes
- Timeline: Har nå egen `axisLabelTemplates`-prop for axixlabel formatering ([#2109](https://github.com/navikt/aksel/pull/2109))
- Combobox post-release tweaks ([#2112](https://github.com/navikt/aksel/pull/2112))
- Updated dependencies []:
- @navikt/aksel-icons@4.7.0
## 4.6.1
### Patch Changes
- Skjema: Labels og Legends bruker nå inline-flex når readOnly er satt ([#2089](https://github.com/navikt/aksel/pull/2089))
- Updated dependencies []:
- @navikt/aksel-icons@4.6.1
## 4.6.0
### Minor Changes
+3 -5
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-react",
"version": "4.6.0",
"version": "5.0.3",
"description": "Aksel react-components for NAV designsystem",
"author": "Aksel | NAV designsystem team",
"license": "MIT",
@@ -38,13 +38,12 @@
},
"dependencies": {
"@floating-ui/react": "0.24.1",
"@navikt/aksel-icons": "^4.6.0",
"@navikt/aksel-icons": "^5.0.3",
"@radix-ui/react-tabs": "1.0.0",
"@radix-ui/react-toggle-group": "1.0.0",
"clsx": "^1.2.1",
"date-fns": "2.29.3",
"react-day-picker": "8.3.4",
"react-modal": "3.15.1"
"react-day-picker": "8.3.4"
},
"devDependencies": {
"@testing-library/dom": "8.13.0",
@@ -53,7 +52,6 @@
"@testing-library/user-event": "^14.2.0",
"@types/faker": "5.5.8",
"@types/jest": "^29.0.0",
"@types/react-modal": "^3.13.1",
"concurrently": "7.2.1",
"copyfiles": "^2.4.1",
"faker": "5.5.3",
+1
View File
@@ -102,6 +102,7 @@ export const Alert = forwardRef<HTMLDivElement, AlertProps>(
{
"navds-alert--full-width": fullWidth,
"navds-alert--inline": inline,
"navds-alert--close-button": closeButton,
}
)}
>
+11 -1
View File
@@ -185,6 +185,16 @@ export const WithCloseButton: Story = {
</BodyLong>
<Link href="#">Id elit esse enim reprehenderit</Link>
</AlertWithCloseButton>
<AlertWithCloseButton>
<DsHeading spacing size="small" level="3">
Aliquip duis est in commodo pariatur
</DsHeading>
<BodyLong>
Ullamco ullamco laborum et commodo sint culpa cupidatat culpa qui
laboris ex. Labore ex occaecat proident qui qui fugiat magna. Fugiat
sint commodo consequat eu aute.
</BodyLong>
</AlertWithCloseButton>
</div>
);
},
@@ -197,6 +207,6 @@ export const WithCloseButton: Story = {
});
const buttonsAfter = canvas.getAllByTitle("Lukk Alert");
expect(buttonsAfter.length).toBe(1);
expect(buttonsAfter.length).toBe(2);
},
};
+1 -5
View File
@@ -126,11 +126,7 @@ export const Button: OverridableComponent<ButtonProps, HTMLButtonElement> =
<span className="navds-button__icon">{icon}</span>
)}
{children && (
<Label
as="span"
size={size === "medium" ? "medium" : "small"}
aria-live="polite"
>
<Label as="span" size={size === "medium" ? "medium" : "small"}>
{children}
</Label>
)}
@@ -148,8 +148,9 @@ export const Loading = {
</div>
),
args: { chromatic: { disableSnapshot: true } },
chromatic: { disableSnapshot: true },
parameters: {
chromatic: { disableSnapshot: true },
},
};
export const Icon = () => (
@@ -275,6 +276,7 @@ export const LoadingWithAs = {
</div>
),
args: { chromatic: { disableSnapshot: true } },
chromatic: { disableSnapshot: true },
parameters: {
chromatic: { disableSnapshot: true },
},
};
+1 -1
View File
@@ -70,7 +70,7 @@ export const Chips: ChipsComponent = forwardRef<HTMLUListElement, ChipsProps>(
})}
>
{React.Children.map(children, (chip, index) => {
return <li key={index + (chip?.toString() ?? "")}>{chip}</li>;
return <li key={chip?.toString() || index}>{chip}</li>;
})}
</ul>
);
+8 -8
View File
@@ -1,7 +1,6 @@
import { XMarkIcon } from "@navikt/aksel-icons";
import cl from "clsx";
import React, { forwardRef } from "react";
import { OverridableComponent } from "../util/OverridableComponent";
export interface RemovableChipsProps
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
@@ -22,26 +21,27 @@ export interface RemovableChipsProps
onDelete?: () => void;
}
export const RemovableChips: OverridableComponent<
RemovableChipsProps,
HTMLButtonElement
> = forwardRef(
export const RemovableChips = forwardRef<
HTMLButtonElement,
RemovableChipsProps
>(
(
{
className,
children,
variant = "action",
as: Component = "button",
removeLabel = "slett",
onDelete,
type = "button",
...rest
},
ref
) => {
return (
<Component
<button
{...rest}
ref={ref}
type={type}
className={cl(
"navds-chips__chip navds-chips__removable navds-chips--icon-right",
className,
@@ -57,7 +57,7 @@ export const RemovableChips: OverridableComponent<
<span className="navds-chips__removable-icon">
<XMarkIcon aria-hidden />
</span>
</Component>
</button>
);
}
);
@@ -63,6 +63,11 @@ export interface CopyButtonProps
* @default 'Kopiert'
*/
activeTitle?: string;
/**
* Icon position in Button
* @default "left"
*/
iconPosition?: "left" | "right";
}
/**
@@ -91,6 +96,7 @@ export const CopyButton = forwardRef<HTMLButtonElement, CopyButtonProps>(
activeDuration = 2000,
title = "Kopier",
activeTitle = "Kopiert",
iconPosition = "left",
...rest
},
ref
@@ -119,6 +125,25 @@ export const CopyButton = forwardRef<HTMLButtonElement, CopyButtonProps>(
}, activeDuration);
};
const CopyIcon = () => {
return active ? (
<span className="navds-copybutton__icon">
{activeIcon ?? (
<CheckmarkIcon
aria-hidden={!!text}
title={text ? undefined : activeTitle}
/>
)}
</span>
) : (
<span className="navds-copybutton__icon">
{icon ?? (
<FilesIcon aria-hidden={!!text} title={text ? undefined : title} />
)}
</span>
);
};
return (
<button
ref={ref}
@@ -138,25 +163,7 @@ export const CopyButton = forwardRef<HTMLButtonElement, CopyButtonProps>(
onClick={handleClick}
>
<span className="navds-copybutton__content">
{active ? (
<span className="navds-copybutton__icon">
{activeIcon ?? (
<CheckmarkIcon
aria-hidden={!!text}
title={text ? undefined : activeTitle}
/>
)}
</span>
) : (
<span className="navds-copybutton__icon">
{icon ?? (
<FilesIcon
aria-hidden={!!text}
title={text ? undefined : title}
/>
)}
</span>
)}
{iconPosition === "left" && <CopyIcon />}
{text &&
(active ? (
@@ -176,6 +183,7 @@ export const CopyButton = forwardRef<HTMLButtonElement, CopyButtonProps>(
{text}
</Label>
))}
{iconPosition === "right" && <CopyIcon />}
</span>
</button>
);
@@ -60,6 +60,16 @@ export const Variants = {
),
};
export const IconPosition = {
render: () => (
<div className="colgap">
<CopyButton copyText="3.14" iconPosition="left" text="Kopier" />
<CopyButton copyText="3.14" iconPosition="right" text="Kopier" />
</div>
),
};
export const Sizes = {
render: () => (
<div className="colgap">
@@ -73,6 +73,7 @@ const DateInput = forwardRef<HTMLInputElement, DateInputProps>((props, ref) => {
"navds-date__field--error": hasError,
"navds-form-field--disabled": !!inputProps.disabled,
"navds-text-field--disabled": !!inputProps.disabled,
"navds-form-field--readonly": readOnly,
"navds-text-field--readonly": readOnly,
"navds-date__field--readonly": readOnly,
}
@@ -109,9 +109,9 @@ export interface DatePickerDefaultProps
onOpenToggle?: () => void;
/**
* Avoid using if possible!
* Changes what CSS position property to use
* Changes what CSS position property to use.
* You want to use "fixed" if parent wrapper has position relative, but you want popover to escape
* @default "absolute"
* @default See Popover
*/
strategy?: "absolute" | "fixed";
/**
@@ -184,7 +184,7 @@ export const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>(
open: _open,
onClose,
onOpenToggle,
strategy = "absolute",
strategy,
bubbleEscape = false,
...rest
},
@@ -166,8 +166,7 @@ export const useDatepicker = (
const handleOpen = useCallback(
(open: boolean) => {
setOpen(open);
!open &&
setMonth(selectedDay ?? defaultSelected ?? defaultMonth ?? today);
open && setMonth(selectedDay ?? defaultSelected ?? defaultMonth ?? today);
},
[defaultMonth, defaultSelected, selectedDay, today]
);
@@ -1,9 +1,9 @@
import { useCallback, useEffect } from "react";
import { useCallback, useEffect, RefObject } from "react";
export const useEscape = (
open: boolean,
setOpen: (openState: boolean) => void,
focusRef: any
focusRef: RefObject<HTMLElement>
) => {
const handleClose = useCallback(() => {
setOpen(false);
@@ -11,7 +11,12 @@ export const useEscape = (
}, [focusRef, setOpen]);
const escape = useCallback(
(e) => open && e.key === "Escape" && handleClose(),
(event: KeyboardEvent) => {
if (open && event.key === "Escape") {
event.preventDefault(); // This prevents modal from closing when using datepicker inside modal
handleClose();
}
},
[handleClose, open]
);
@@ -163,8 +163,7 @@ export const useMonthpicker = (
const handleOpen = useCallback(
(open: boolean) => {
setOpen(open);
!open &&
setYear(selectedMonth ?? defaultSelected ?? defaultYear ?? today);
open && setYear(selectedMonth ?? defaultSelected ?? defaultYear ?? today);
},
[defaultSelected, defaultYear, selectedMonth, today]
);
@@ -1,8 +1,8 @@
import cl from "clsx";
import React, { forwardRef } from "react";
import { OverridableComponent } from "../../../util/OverridableComponent";
import GroupedHeading, { GroupedHeadingProps } from "./Heading";
import GroupedItem, { GroupedItemProps } from "./Item";
import GroupedHeading, { GroupedHeadingProps } from "./GroupedHeading";
import GroupedItem, { GroupedItemProps } from "./GroupedItem";
export interface GroupedListProps
extends React.HTMLAttributes<HTMLDListElement> {
@@ -88,12 +88,7 @@ export const ConfirmationPanel = forwardRef<
{label}
</Checkbox>
</div>
<div
className="navds-form-field__error"
id={errorId}
aria-relevant="additions removals"
aria-live="polite"
>
<div className="navds-form-field__error" id={errorId} role="alert">
{showErrorMsg && <ErrorMessage size={size}>{props.error}</ErrorMessage>}
</div>
</div>
+1
View File
@@ -99,6 +99,7 @@ export const Select = forwardRef<HTMLSelectElement, SelectProps>(
`navds-form-field--${size}`,
{
"navds-form-field--disabled": !!inputProps.disabled,
"navds-form-field--readonly": readOnly,
"navds-select--error": hasError,
"navds-select--readonly": readOnly,
}
+3 -1
View File
@@ -31,7 +31,7 @@ export interface TextFieldProps
* Type of form control. Picking the correct type helps user fill inn their required information
* @default "text"
*/
type?: "email" | "number" | "password" | "tel" | "text" | "url";
type?: "email" | "number" | "password" | "tel" | "text" | "url" | "time";
}
/**
@@ -73,10 +73,12 @@ export const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
className,
"navds-form-field",
`navds-form-field--${size}`,
{
"navds-text-field--error": hasError,
"navds-text-field--disabled": !!inputProps.disabled,
"navds-form-field--disabled": !!inputProps.disabled,
"navds-form-field--readonly": readOnly,
"navds-text-field--readonly": readOnly,
}
)}
+1
View File
@@ -117,6 +117,7 @@ export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
`navds-form-field--${size}`,
{
"navds-form-field--disabled": !!inputProps.disabled,
"navds-form-field--readonly": readOnly,
"navds-textarea--readonly": readOnly,
"navds-textarea--error": hasError,
"navds-textarea--resize": resize,
@@ -86,12 +86,33 @@ 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,
})}
>
<BodyShort as="span" size={size}>
<BodyShort
as="span"
size={size}
className="navds-checkbox__label-text"
>
{!nested && (
<ReadOnlyIcon readOnly={readOnly} nativeReadOnly={false} />
)}
@@ -0,0 +1,29 @@
import React from "react";
import { XMarkIcon } from "@navikt/aksel-icons";
interface ClearButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
handleClear: (event: any) => void;
clearButtonLabel?: string;
}
export const ClearButton: React.FC<ClearButtonProps> = ({
handleClear,
clearButtonLabel,
...rest
}) => {
return (
<button
type="button"
onClick={handleClear}
className="navds-combobox__button-clear"
{...rest}
>
<span className="navds-sr-only">
{clearButtonLabel ? clearButtonLabel : "Tøm"}
</span>
<XMarkIcon aria-hidden />
</button>
);
};
export default ClearButton;
@@ -0,0 +1,147 @@
import cl from "clsx";
import React, { forwardRef, useMemo, useRef } from "react";
import { BodyShort, ErrorMessage, Label, mergeRefs } from "../..";
import ClearButton from "./ClearButton";
import FilteredOptions from "./FilteredOptions/FilteredOptions";
import { useFilteredOptionsContext } from "./FilteredOptions/filteredOptionsContext";
import SelectedOptions from "./SelectedOptions/SelectedOptions";
import ToggleListButton from "./ToggleListButton";
import { ComboboxProps } from "./types";
import { useSelectedOptionsContext } from "./SelectedOptions/selectedOptionsContext";
import ComboboxWrapper from "./ComboboxWrapper";
import { useInputContext } from "./Input/inputContext";
import Input from "./Input/Input";
export const Combobox = forwardRef<
HTMLInputElement,
Omit<ComboboxProps, "onChange" | "options" | "size">
>((props, ref) => {
const {
value: externalValue,
onClear,
className,
hideLabel = false,
description,
label,
clearButton = true,
clearButtonLabel,
toggleListButton = true,
toggleListButtonLabel,
inputClassName,
shouldShowSelectedOptions = true,
...rest
} = props;
const toggleListButtonRef = useRef<HTMLButtonElement>(null);
const { currentOption, toggleIsListOpen } = useFilteredOptionsContext();
const { selectedOptions } = useSelectedOptionsContext();
const {
clearInput,
error,
errorId,
focusInput,
hasError,
inputDescriptionId,
inputProps,
inputRef,
value,
showErrorMsg,
size = "medium",
} = useInputContext();
const mergedInputRef = useMemo(
() => mergeRefs([inputRef, ref]),
[inputRef, ref]
);
return (
<ComboboxWrapper
className={className}
hasError={hasError}
inputProps={inputProps}
inputSize={size}
toggleIsListOpen={toggleIsListOpen}
toggleListButtonRef={toggleListButtonRef}
>
<Label
htmlFor={inputProps.id}
size={size}
className={cl("navds-form-field__label", {
"navds-sr-only": hideLabel,
})}
>
{label}
</Label>
{!!description && (
<BodyShort
as="div"
className={cl("navds-form-field__description", {
"navds-sr-only": hideLabel,
})}
id={inputDescriptionId}
size={size}
>
{description}
</BodyShort>
)}
<div className="navds-combobox__wrapper">
<div
className={cl(
"navds-combobox__wrapper-inner navds-text-field__input",
{
"navds-combobox__wrapper-inner--virtually-unfocused":
currentOption !== null,
}
)}
onClick={focusInput}
>
{!shouldShowSelectedOptions ? (
<Input
id={inputProps.id}
ref={mergedInputRef}
inputClassName={inputClassName}
{...rest}
/>
) : (
<SelectedOptions selectedOptions={selectedOptions} size={size}>
<Input
id={inputProps.id}
ref={mergedInputRef}
inputClassName={inputClassName}
{...rest}
/>
</SelectedOptions>
)}
<div>
{value && clearButton && (
<ClearButton
handleClear={clearInput}
clearButtonLabel={clearButtonLabel}
tabIndex={-1}
/>
)}
{toggleListButton && (
<ToggleListButton
toggleListButtonLabel={toggleListButtonLabel}
ref={toggleListButtonRef}
/>
)}
</div>
</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>
);
});
export default Combobox;
@@ -0,0 +1,99 @@
import React, { forwardRef } from "react";
import Combobox from "./Combobox";
import { FilteredOptionsProvider } from "./FilteredOptions/filteredOptionsContext";
import { CustomOptionsProvider } from "./customOptionsContext";
import { SelectedOptionsProvider } from "./SelectedOptions/selectedOptionsContext";
import { InputContextProvider } from "./Input/inputContext";
import { ComboboxProps } from "./types";
/**
* A component that allows the user to search in a list of options
*
* Has options for allowing only one or multiple options to be selected,
* or adding new, user-submitted values.
*
* @see [📝 Documentation](https://aksel.nav.no/komponenter/core/combobox)
*
* @example
* ```jsx
* const options = ["apple", "banana", "orange"];
*
* return (
* <Combobox
* label="Velg en verdi"
* options={options}
* id="my-combobox"
* shouldAutoComplete
* />
* )
* ```
*/
const ComboboxProvider = forwardRef<HTMLInputElement, ComboboxProps>(
(props, ref) => {
const {
allowNewValues = false,
children,
defaultValue,
error,
errorId,
filteredOptions,
id,
isListOpen,
isLoading = false,
isMultiSelect,
onToggleSelected,
selectedOptions,
options,
value,
onChange,
onClear,
shouldAutocomplete,
size,
...rest
} = props;
return (
<InputContextProvider
value={{
defaultValue,
error,
errorId,
id,
value,
onChange,
onClear,
shouldAutocomplete,
size,
}}
>
<CustomOptionsProvider>
<SelectedOptionsProvider
value={{
allowNewValues,
isMultiSelect,
selectedOptions,
onToggleSelected,
options,
}}
>
<FilteredOptionsProvider
value={{
allowNewValues,
filteredOptions,
isListOpen,
isLoading,
isMultiSelect,
options,
}}
>
<Combobox ref={ref} {...rest}>
{children}
</Combobox>
</FilteredOptionsProvider>
</SelectedOptionsProvider>
</CustomOptionsProvider>
</InputContextProvider>
);
}
);
export default ComboboxProvider;
@@ -0,0 +1,67 @@
import cl from "clsx";
import React, { useRef, useState } from "react";
type ComboboxWrapperProps = {
children: any;
className?: string;
hasError: boolean;
inputProps: {
disabled?: boolean;
};
inputSize: string;
toggleIsListOpen: (isListOpen: boolean) => void;
toggleListButtonRef: React.RefObject<HTMLButtonElement>;
};
const ComboboxWrapper = ({
children,
className,
hasError,
inputProps,
inputSize,
toggleIsListOpen,
toggleListButtonRef,
}: ComboboxWrapperProps) => {
const wrapperRef = useRef<HTMLDivElement | null>(null);
const [hasFocusWithin, setHasFocusWithin] = useState(false);
function onFocusInsideWrapper(e) {
if (
!wrapperRef.current?.contains(e.relatedTarget) &&
toggleListButtonRef?.current !== e.target
) {
toggleIsListOpen(true);
setHasFocusWithin(true);
}
}
function onBlurWrapper(e) {
if (!wrapperRef.current?.contains(e.relatedTarget)) {
toggleIsListOpen(false);
setHasFocusWithin(false);
}
}
return (
<div
ref={wrapperRef}
className={cl(
className,
"navds-form-field",
`navds-form-field--${inputSize}`,
{
"navds-combobox--error": hasError,
"navds-combobox--disabled": !!inputProps.disabled,
"navds-combobox--focused": hasFocusWithin,
}
)}
onFocus={onFocusInsideWrapper}
onBlur={onBlurWrapper}
tabIndex={-1}
>
{children}
</div>
);
};
export default ComboboxWrapper;
@@ -0,0 +1,23 @@
import React from "react";
const CheckIcon = () => {
return (
<svg
width="16"
height="13"
viewBox="0 0 16 13"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
fillRule="evenodd"
clipRule="evenodd"
fill="#005B82"
d="M14.2014 0L16 1.89047L4.77943 13L0 8.39552L1.79361 6.5L4.77418 9.3019L14.2014 0Z"
/>
</svg>
);
};
export default CheckIcon;
@@ -0,0 +1,109 @@
import React from "react";
import cl from "clsx";
import { BodyShort, Label, Loader } from "../../..";
import { CheckmarkIcon, PlusIcon } from "@navikt/aksel-icons";
import { useFilteredOptionsContext } from "./filteredOptionsContext";
import { useSelectedOptionsContext } from "../SelectedOptions/selectedOptionsContext";
import { useInputContext } from "../Input/inputContext";
const FilteredOptions = () => {
const {
inputProps: { id },
size,
value,
} = useInputContext();
const {
allowNewValues,
isLoading,
isListOpen,
filteredOptions,
filteredOptionsIndex,
filteredOptionsRef,
isValueNew,
toggleIsListOpen,
} = useFilteredOptionsContext();
const { isMultiSelect, selectedOptions, toggleOption } =
useSelectedOptionsContext();
return (
<ul
ref={filteredOptionsRef}
className={cl("navds-combobox__list", {
"navds-combobox__list--closed": !isListOpen,
})}
id={`${id}-filtered-options`}
role="listbox"
tabIndex={-1}
>
{isLoading && (
<li
className="navds-combobox__list-item--loading"
role="option"
aria-selected={false}
id={`${id}-is-loading`}
>
<Loader aria-label="Søker..." />
</li>
)}
{isValueNew && allowNewValues && (
<li
tabIndex={-1}
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":
filteredOptionsIndex === -1,
})}
role="option"
aria-selected={false}
>
<PlusIcon aria-hidden />
<BodyShort size={size}>
Legg til{" "}
<Label as="span" size={size}>
&#8220;{value}&#8221;
</Label>
</BodyShort>
</li>
)}
{!isLoading && filteredOptions.length === 0 && (
<li
className="navds-combobox__list-item__no-options"
role="option"
aria-selected={false}
id={`${id}-no-hits`}
>
Ingen søketreff
</li>
)}
{filteredOptions.map((option, index) => (
<li
className={cl("navds-combobox__list-item", {
"navds-combobox__list-item--focus": index === filteredOptionsIndex,
"navds-combobox__list-item--selected":
selectedOptions.includes(option),
})}
id={`${id}-option-${option.replace(" ", "-")}`}
key={option}
tabIndex={-1}
onPointerUp={(event) => {
toggleOption(option, event);
if (!isMultiSelect && !selectedOptions.includes(option))
toggleIsListOpen(false);
}}
role="option"
aria-selected={selectedOptions.includes(option)}
>
<BodyShort size={size}>{option}</BodyShort>
{selectedOptions.includes(option) && <CheckmarkIcon />}
</li>
))}
</ul>
);
};
export default FilteredOptions;
@@ -0,0 +1,275 @@
import React, {
useState,
useEffect,
useMemo,
createContext,
useContext,
useCallback,
useRef,
useLayoutEffect,
} from "react";
import cl from "clsx";
import { useCustomOptionsContext } from "../customOptionsContext";
import { useInputContext } from "../Input/inputContext";
import usePrevious from "../../../util/usePrevious";
const normalizeText = (text: string): string =>
typeof text === "string" ? `${text}`.toLowerCase().trim() : "";
const isPartOfText = (value, text) =>
normalizeText(text).startsWith(normalizeText(value ?? ""));
const isValueInList = (value, list) =>
list?.find((listItem) => normalizeText(value) === normalizeText(listItem));
const getMatchingValuesFromList = (value, list) =>
list?.filter((listItem) => isPartOfText(value, listItem));
type FilteredOptionsContextType = {
activeDecendantId?: string;
allowNewValues?: boolean;
ariaDescribedBy?: string;
filteredOptionsRef: React.RefObject<HTMLUListElement>;
filteredOptionsIndex: number | null;
setFilteredOptionsIndex: (index: number) => void;
isListOpen: boolean;
isLoading?: boolean;
filteredOptions: string[];
isValueNew: boolean;
toggleIsListOpen: (newState?: boolean) => void;
currentOption: string | null;
resetFilteredOptionsIndex: () => void;
moveFocusUp: () => void;
moveFocusDown: () => void;
moveFocusToInput: () => void;
moveFocusToEnd: () => void;
shouldAutocomplete?: boolean;
};
const FilteredOptionsContext = createContext<FilteredOptionsContextType>(
{} as FilteredOptionsContextType
);
export const FilteredOptionsProvider = ({ children, value: props }) => {
const {
allowNewValues,
filteredOptions: externalFilteredOptions,
isListOpen: isExternalListOpen,
isLoading,
options,
} = props;
const filteredOptionsRef = useRef<HTMLUListElement | null>(null);
const {
inputProps: { "aria-describedby": partialAriaDescribedBy, id },
value,
searchTerm,
setValue,
setSearchTerm,
shouldAutocomplete,
} = useInputContext();
const [filteredOptionsIndex, setFilteredOptionsIndex] = useState<
number | null
>(null);
const [isInternalListOpen, setInternalListOpen] = useState(false);
const { customOptions } = useCustomOptionsContext();
const filteredOptions = useMemo(() => {
if (externalFilteredOptions) {
return externalFilteredOptions;
}
const opts = [...customOptions, ...options];
setFilteredOptionsIndex(null);
return getMatchingValuesFromList(searchTerm, opts);
}, [customOptions, externalFilteredOptions, options, searchTerm]);
const previousSearchTerm = usePrevious(searchTerm);
useLayoutEffect(() => {
if (
shouldAutocomplete &&
normalizeText(searchTerm) !== "" &&
(previousSearchTerm?.length || 0) < searchTerm.length &&
filteredOptions.length > 0 &&
!isValueInList(searchTerm, filteredOptions)
) {
setValue(
`${searchTerm}${filteredOptions[0].substring(searchTerm.length)}`
);
setSearchTerm(searchTerm);
}
}, [
filteredOptions,
previousSearchTerm,
searchTerm,
setSearchTerm,
setValue,
shouldAutocomplete,
]);
const isListOpen = useMemo(() => {
return isExternalListOpen ?? isInternalListOpen;
}, [isExternalListOpen, isInternalListOpen]);
const toggleIsListOpen = useCallback((newState?: boolean) => {
setFilteredOptionsIndex(null);
setInternalListOpen((oldState) => newState ?? !oldState);
}, []);
const isValueNew = useMemo(
() => Boolean(value) && !isValueInList(value, filteredOptions),
[value, filteredOptions]
);
const getMinimumIndex = useCallback(() => {
return isValueNew && allowNewValues ? -1 : 0;
}, [allowNewValues, isValueNew]);
const ariaDescribedBy = useMemo(() => {
let activeOption;
if (!isLoading && filteredOptions.length === 0) {
activeOption = `${id}-no-hits`;
} else if ((value && value !== "") || isLoading) {
if (shouldAutocomplete && filteredOptions[0]) {
activeOption = `${id}-option-${filteredOptions[0].replace(" ", "-")}`;
} else if (isListOpen && isLoading) {
activeOption = `${id}-is-loading`;
}
}
return cl(activeOption, partialAriaDescribedBy) || undefined;
}, [
isListOpen,
isLoading,
value,
partialAriaDescribedBy,
shouldAutocomplete,
filteredOptions,
id,
]);
const currentOption = useMemo(() => {
if (filteredOptionsIndex == null) {
return null;
}
if (filteredOptionsIndex === -1) {
return value;
}
return filteredOptions[filteredOptionsIndex];
}, [filteredOptionsIndex, filteredOptions, value]);
const resetFilteredOptionsIndex = () => {
setFilteredOptionsIndex(getMinimumIndex());
};
const scrollToOption = useCallback((newIndex: number) => {
if (
filteredOptionsRef.current &&
filteredOptionsRef.current.children[newIndex]
) {
const child = filteredOptionsRef.current.children[newIndex];
const { top, bottom } = child.getBoundingClientRect();
const parentRect = filteredOptionsRef.current.getBoundingClientRect();
if (top < parentRect.top || bottom > parentRect.bottom) {
child.scrollIntoView({ block: "nearest" });
}
}
}, []);
useEffect(() => {
if (filteredOptionsIndex !== null && isListOpen) {
scrollToOption(filteredOptionsIndex);
}
}, [filteredOptionsIndex, isListOpen, scrollToOption]);
const moveFocusToInput = useCallback(() => {
setFilteredOptionsIndex(null);
toggleIsListOpen(false);
}, [toggleIsListOpen]);
const moveFocusToEnd = useCallback(() => {
const lastIndex = filteredOptions.length - 1;
toggleIsListOpen(true);
setFilteredOptionsIndex(lastIndex);
}, [filteredOptions.length, toggleIsListOpen]);
const moveFocusUp = useCallback(() => {
if (filteredOptionsIndex === null) {
return;
}
if (filteredOptionsIndex === getMinimumIndex()) {
toggleIsListOpen(false);
setFilteredOptionsIndex(null);
} else {
const newIndex = Math.max(getMinimumIndex(), filteredOptionsIndex - 1);
setFilteredOptionsIndex(newIndex);
}
}, [filteredOptionsIndex, getMinimumIndex, toggleIsListOpen]);
const moveFocusDown = useCallback(() => {
if (filteredOptionsIndex === null || !isListOpen) {
toggleIsListOpen(true);
if (allowNewValues || filteredOptions.length >= 1) {
setFilteredOptionsIndex(getMinimumIndex());
}
return;
}
const newIndex = Math.min(
filteredOptionsIndex + 1,
Math.max(getMinimumIndex(), filteredOptions.length - 1)
);
setFilteredOptionsIndex(newIndex);
}, [
allowNewValues,
filteredOptions.length,
filteredOptionsIndex,
getMinimumIndex,
isListOpen,
toggleIsListOpen,
]);
const activeDecendantId = useMemo(() => {
if (filteredOptionsIndex === null) {
return undefined;
} else if (filteredOptionsIndex === -1) {
return `${id}-combobox-new-option`;
} else {
return `${id}-option-${currentOption?.replace(" ", "-")}`;
}
}, [filteredOptionsIndex, currentOption, id]);
const filteredOptionsState = {
activeDecendantId,
allowNewValues,
filteredOptionsRef,
filteredOptionsIndex,
setFilteredOptionsIndex,
shouldAutocomplete,
isListOpen,
isLoading,
filteredOptions,
isValueNew,
toggleIsListOpen,
currentOption,
resetFilteredOptionsIndex,
moveFocusUp,
moveFocusDown,
moveFocusToInput,
moveFocusToEnd,
ariaDescribedBy,
};
return (
<FilteredOptionsContext.Provider value={filteredOptionsState}>
{children}
</FilteredOptionsContext.Provider>
);
};
export const useFilteredOptionsContext = () => {
const context = useContext(FilteredOptionsContext);
if (!context) {
throw new Error(
"useFilteredOptionsContext must be used within a FilteredOptionsProvider"
);
}
return context;
};
@@ -0,0 +1,185 @@
import { omit } from "../../..";
import React, {
useCallback,
forwardRef,
InputHTMLAttributes,
ChangeEvent,
} from "react";
import cl from "clsx";
import { useSelectedOptionsContext } from "../SelectedOptions/selectedOptionsContext";
import { useFilteredOptionsContext } from "../FilteredOptions/filteredOptionsContext";
import { useInputContext } from "./inputContext";
interface InputProps
extends Omit<InputHTMLAttributes<HTMLInputElement>, "value"> {
ref: React.Ref<HTMLInputElement>;
inputClassName?: string;
errorId?: string;
value?: string;
error?: React.ReactNode;
}
const Input = forwardRef<HTMLInputElement, InputProps>(
({ inputClassName, error, errorId, ...rest }, ref) => {
const { clearInput, inputProps, onChange, size, value } = useInputContext();
const {
selectedOptions,
removeSelectedOption,
toggleOption,
isMultiSelect,
} = useSelectedOptionsContext();
const {
activeDecendantId,
allowNewValues,
currentOption,
filteredOptions,
toggleIsListOpen,
isListOpen,
filteredOptionsIndex,
moveFocusUp,
moveFocusDown,
ariaDescribedBy,
moveFocusToInput,
moveFocusToEnd,
setFilteredOptionsIndex,
shouldAutocomplete,
} = useFilteredOptionsContext();
const onEnter = useCallback(
(event: React.KeyboardEvent) => {
if (currentOption) {
event.preventDefault();
// Selecting a value from the dropdown / FilteredOptions
toggleOption(currentOption, 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
clearInput(event);
} else if ((allowNewValues || shouldAutocomplete) && value !== "") {
event.preventDefault();
// Autocompleting or adding a new value
toggleOption(value, event);
if (!isMultiSelect && !selectedOptions.includes(value))
toggleIsListOpen(false);
}
},
[
allowNewValues,
clearInput,
currentOption,
isMultiSelect,
selectedOptions,
shouldAutocomplete,
toggleIsListOpen,
toggleOption,
value,
]
);
const handleKeyUp = (e: React.KeyboardEvent<HTMLInputElement>) => {
e.preventDefault();
switch (e.key) {
case "Escape":
clearInput(e);
toggleIsListOpen(false);
break;
case "Enter":
case "Accept":
onEnter(e);
break;
case "Home":
moveFocusToInput();
break;
case "End":
moveFocusToEnd();
break;
default:
break;
}
};
const handleKeyDown = useCallback(
(e) => {
if (e.key === "Backspace") {
if (value === "") {
const lastSelectedOption =
selectedOptions[selectedOptions.length - 1];
removeSelectedOption(lastSelectedOption);
}
} else if (e.key === "ArrowDown") {
// Check that cursor position is at the end of the input field,
// so we don't interfere with text editing
if (e.target.selectionStart === value?.length) {
e.preventDefault();
moveFocusDown();
}
} else if (e.key === "ArrowUp") {
// Check that the FilteredOptions list is open and has virtual focus.
// Otherwise ignore keystrokes, so it doesn't interfere with text editing
if (isListOpen && filteredOptionsIndex !== null) {
e.preventDefault();
moveFocusUp();
}
}
},
[
value,
selectedOptions,
removeSelectedOption,
moveFocusDown,
isListOpen,
filteredOptionsIndex,
moveFocusUp,
]
);
const onChangeHandler = useCallback(
(event: ChangeEvent<HTMLInputElement>) => {
const newValue = event.target.value;
if (newValue && newValue !== "") {
toggleIsListOpen(true);
} else if (filteredOptions.length === 0) {
toggleIsListOpen(false);
}
onChange(event);
},
[filteredOptions.length, onChange, toggleIsListOpen]
);
const onBlur = () => {
setFilteredOptionsIndex(-1);
};
return (
<input
{...rest}
{...omit(inputProps, ["aria-invalid"])}
ref={ref}
value={value}
onChange={onChangeHandler}
type="text"
role="combobox"
onBlur={onBlur}
onKeyUp={handleKeyUp}
onKeyDown={handleKeyDown}
aria-controls={`${inputProps.id}-filtered-options`}
aria-expanded={!!isListOpen}
autoComplete="off"
aria-autocomplete={shouldAutocomplete ? "both" : "list"}
aria-activedescendant={activeDecendantId}
aria-describedby={ariaDescribedBy}
aria-invalid={inputProps["aria-invalid"]}
className={cl(
inputClassName,
"navds-combobox__input",
"navds-body-short",
`navds-body-${size}`
)}
/>
);
}
);
export default Input;
@@ -0,0 +1,130 @@
import React, {
ChangeEvent,
ChangeEventHandler,
createContext,
useCallback,
useContext,
useLayoutEffect,
useMemo,
useRef,
useState,
} from "react";
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;
setValue: (text: string) => void;
onChange: ChangeEventHandler<HTMLInputElement>;
searchTerm: string;
setSearchTerm: React.Dispatch<React.SetStateAction<string>>;
shouldAutocomplete?: boolean;
}
const InputContext = createContext<InputContextType>({} as InputContextType);
export const InputContextProvider = ({ children, value: props }) => {
const {
defaultValue = "",
description,
disabled,
error,
errorId,
id: externalId,
value: externalValue,
onChange: externalOnChange,
onClear,
shouldAutocomplete,
size,
} = props;
const formFieldProps = useFormField(
{
description,
disabled,
error,
errorId,
id: externalId,
size,
},
"comboboxfield"
);
const inputRef = useRef<HTMLInputElement | null>(null);
const [internalValue, setInternalValue] = useState<string>(defaultValue);
const value = useMemo(
() => String(externalValue ?? internalValue),
[externalValue, internalValue]
);
const [searchTerm, setSearchTerm] = useState(value);
const onChange = useCallback(
(event: ChangeEvent<HTMLInputElement>) => {
const value = event.currentTarget.value;
externalValue ?? setInternalValue(value);
externalOnChange?.(event);
setSearchTerm(value);
},
[externalValue, externalOnChange]
);
const setValue = useCallback(
(text) => {
setInternalValue(text);
},
[setInternalValue]
);
const clearInput = useCallback(
(event: React.PointerEvent | React.KeyboardEvent) => {
onClear?.(event);
externalOnChange?.(null, "");
setValue("");
setSearchTerm("");
},
[externalOnChange, onClear, setValue]
);
const focusInput = useCallback(() => {
inputRef.current?.focus?.();
}, []);
useLayoutEffect(() => {
if (shouldAutocomplete && inputRef && value !== searchTerm) {
inputRef.current?.setSelectionRange?.(searchTerm.length, value.length);
}
}, [value, searchTerm, shouldAutocomplete]);
return (
<InputContext.Provider
value={{
...formFieldProps,
clearInput,
error,
focusInput,
inputRef,
value,
setValue,
onChange,
searchTerm,
setSearchTerm,
shouldAutocomplete,
}}
>
{children}
</InputContext.Provider>
);
};
export const useInputContext = () => {
const context = useContext(InputContext);
if (!context) {
throw new Error(
"useInputContext must be used within an InputContextProvider"
);
}
return context;
};
@@ -0,0 +1,48 @@
import React from "react";
import { Chips } from "../../..";
import { useSelectedOptionsContext } from "./selectedOptionsContext";
import { useInputContext } from "../Input/inputContext";
interface SelectedOptionsProps {
selectedOptions?: string[];
size?: "medium" | "small";
children: React.ReactNode;
}
const Option = ({ option }: { option: string }) => {
const { isMultiSelect, removeSelectedOption } = useSelectedOptionsContext();
const { focusInput } = useInputContext();
const onClick = (e) => {
e.stopPropagation();
removeSelectedOption(option);
focusInput();
};
if (!isMultiSelect) {
return (
<div className="navds-combobox__selected-options--no-bg">{option}</div>
);
}
return <Chips.Removable onClick={onClick}>{option}</Chips.Removable>;
};
const SelectedOptions: React.FC<SelectedOptionsProps> = ({
selectedOptions = [],
size,
children,
}) => {
return (
<Chips className="navds-combobox__selected-options" size={size}>
{selectedOptions.length
? selectedOptions.map((option, i) => (
<Option key={option + i} option={option} />
))
: []}
{children}
</Chips>
);
};
export default SelectedOptions;
@@ -0,0 +1,157 @@
import React, {
createContext,
useCallback,
useContext,
useMemo,
useState,
} from "react";
import usePrevious from "../../../util/usePrevious";
import { useInputContext } from "../Input/inputContext";
import { ComboboxProps } from "../types";
import { useCustomOptionsContext } from "../customOptionsContext";
type SelectedOptionsContextType = {
addSelectedOption: (option: string) => void;
isMultiSelect?: boolean;
removeSelectedOption: (option: string) => void;
prevSelectedOptions?: string[];
selectedOptions: string[];
setSelectedOptions: (any) => void;
toggleOption: (
option: string,
event: React.KeyboardEvent | React.PointerEvent
) => void;
};
const SelectedOptionsContext = createContext<SelectedOptionsContextType>(
{} as SelectedOptionsContextType
);
export const SelectedOptionsProvider = ({
children,
value,
}: {
children: any;
value: Pick<
ComboboxProps,
| "allowNewValues"
| "isMultiSelect"
| "options"
| "selectedOptions"
| "onToggleSelected"
>;
}) => {
const { clearInput, focusInput } = useInputContext();
const {
customOptions,
removeCustomOption,
addCustomOption,
setCustomOptions,
} = useCustomOptionsContext();
const {
allowNewValues,
isMultiSelect,
selectedOptions: externalSelectedOptions,
onToggleSelected,
options,
} = value;
const [internalSelectedOptions, setSelectedOptions] = useState<string[]>([]);
const selectedOptions = useMemo(
() =>
externalSelectedOptions ?? [...customOptions, ...internalSelectedOptions],
[customOptions, externalSelectedOptions, internalSelectedOptions]
);
const addSelectedOption = useCallback(
(option: string) => {
const isCustomOption = !options
.map((opt) => opt.toLowerCase())
.includes(option?.toLowerCase?.());
if (isCustomOption) {
allowNewValues && addCustomOption(option);
!isMultiSelect && setSelectedOptions([]);
} else if (isMultiSelect) {
setSelectedOptions((prevSelectedOptions) => [
...prevSelectedOptions,
option,
]);
} else {
setSelectedOptions([option]);
setCustomOptions([]);
}
onToggleSelected?.(option, true, isCustomOption);
},
[
addCustomOption,
allowNewValues,
isMultiSelect,
onToggleSelected,
options,
setCustomOptions,
]
);
const removeSelectedOption = useCallback(
(option: string) => {
const isCustomOption = customOptions.includes(option);
if (isCustomOption) {
removeCustomOption(option);
} else {
setSelectedOptions((prevSelectedOptions) =>
prevSelectedOptions.filter(
(selectedOption) => selectedOption !== option
)
);
}
onToggleSelected?.(option, false, isCustomOption);
},
[customOptions, onToggleSelected, removeCustomOption]
);
const toggleOption = useCallback(
(option: string, event: React.KeyboardEvent | React.PointerEvent) => {
if (selectedOptions.includes(option)) {
removeSelectedOption(option);
} else {
addSelectedOption(option);
}
clearInput(event);
focusInput();
},
[
addSelectedOption,
clearInput,
focusInput,
removeSelectedOption,
selectedOptions,
]
);
const prevSelectedOptions = usePrevious<string[]>(selectedOptions);
const selectedOptionsState = {
addSelectedOption,
isMultiSelect,
removeSelectedOption,
prevSelectedOptions,
selectedOptions,
setSelectedOptions,
toggleOption,
};
return (
<SelectedOptionsContext.Provider value={selectedOptionsState}>
{children}
</SelectedOptionsContext.Provider>
);
};
export const useSelectedOptionsContext = () => {
const context = useContext(SelectedOptionsContext);
if (!context) {
throw new Error(
"useSelectedOptionsContext must be used within a SelectedOptionsProvider"
);
}
return context;
};
@@ -0,0 +1,37 @@
import React, { forwardRef } from "react";
import { ChevronUpIcon, ChevronDownIcon } from "@navikt/aksel-icons";
import { useFilteredOptionsContext } from "./FilteredOptions/filteredOptionsContext";
interface ToggleListButtonProps {
toggleListButtonLabel?: string;
}
export const ToggleListButton = forwardRef<
HTMLButtonElement,
ToggleListButtonProps
>(({ toggleListButtonLabel }, ref) => {
const { isListOpen, toggleIsListOpen } = useFilteredOptionsContext();
return (
<button
type="button"
onPointerUp={() => toggleIsListOpen()}
onKeyDown={({ key }) => key === "Enter" && toggleIsListOpen()}
className="navds-combobox__button-toggle-list"
aria-expanded={isListOpen}
tabIndex={-1}
ref={ref}
>
<span className="navds-sr-only">
{toggleListButtonLabel ?? "Alternativer"}
</span>
{isListOpen ? (
<ChevronUpIcon aria-hidden />
) : (
<ChevronDownIcon aria-hidden />
)}
</button>
);
});
export default ToggleListButton;
@@ -0,0 +1,511 @@
/* eslint-disable react-hooks/rules-of-hooks */
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, jest } from "@storybook/jest";
export default {
title: "ds-react/Combobox",
component: UNSAFE_Combobox,
argTypes: {
isListOpen: {
control: {
type: "boolean",
},
},
isLoading: {
control: {
type: "boolean",
},
},
},
} as Meta;
const options = [
"banana",
"apple",
"apple pie",
"tangerine",
"pear",
"grape",
"kiwi",
"mango",
"passion fruit",
"pineapple",
"strawberry",
"watermelon",
"grape fruit",
];
const initialSelectedOptions = ["passion fruit", "grape fruit"];
const DemoContainer = ({
dataTheme,
children,
}: {
children: any;
dataTheme: "dark" | "light";
}) => (
<div data-theme={dataTheme} style={{ width: "300px" }}>
{children}
</div>
);
export const Default = (props) => {
const id = useId();
return (
<DemoContainer dataTheme={props.darkMode}>
<UNSAFE_Combobox
options={props.options}
label="Hva er dine favorittfrukter?"
/* everything under here is optional? */
shouldAutocomplete={props.shouldAutoComplete}
size="medium"
id={id}
/>
</DemoContainer>
);
};
Default.args = {
options,
shouldAutoComplete: true,
};
export function MultiSelect(props) {
const id = useId();
return (
<DemoContainer dataTheme={props.darkMode}>
<UNSAFE_Combobox
id={id}
label="Komboboks - velg flere"
options={props.options}
isMultiSelect={props.isMultiSelect}
size={props.size}
/>
</DemoContainer>
);
}
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) {
const id = useId();
return (
<DemoContainer dataTheme={props.darkMode}>
<UNSAFE_Combobox
id={id}
isMultiSelect={props.isMultiSelect}
label="Multiselect komboboks med mulighet for å legge til nye verdier"
options={props.options}
allowNewValues={props.allowNewValues}
/>
</DemoContainer>
);
}
MultiSelectWithAddNewOptions.args = {
allowNewValues: true,
isMultiSelect: true,
options,
shouldAutocomplete: false,
};
export const MultiSelectWithExternalChips = (props) => {
const [selectedOptions, setSelectedOptions] = useState<string[]>(
props.selectedOptions
);
const [value, setValue] = useState("");
const id = useId();
const toggleSelected = (option) =>
selectedOptions.includes(option)
? setSelectedOptions(selectedOptions.filter((opt) => opt !== option))
: setSelectedOptions([...selectedOptions, option]);
return (
<DemoContainer dataTheme={props.darkMode}>
{selectedOptions && (
<Chips>
{selectedOptions.map((option) => (
<Chips.Removable
key={option}
onPointerUp={() => toggleSelected(option)}
onKeyUp={(e) => e.key === "Enter" && toggleSelected(option)}
>
{option}
</Chips.Removable>
))}
</Chips>
)}
<UNSAFE_Combobox
options={options}
selectedOptions={selectedOptions}
onToggleSelected={(option: string) => toggleSelected(option)}
isListOpen={props.isListOpen}
isMultiSelect
value={props.controlled ? value : undefined}
onChange={(event) =>
props.controlled ? setValue(event.currentTarget.value) : undefined
}
label="Komboboks"
size="medium"
error={props.error && "error here"}
id={id}
shouldShowSelectedOptions={false}
/>
</DemoContainer>
);
};
MultiSelectWithExternalChips.args = {
controlled: false,
options,
selectedOptions: [],
};
export function Loading(props) {
const id = useId();
return (
<DemoContainer dataTheme={props.darkMode}>
<UNSAFE_Combobox
id={id}
label="Komboboks (laster)"
options={[]}
selectedOptions={[]}
isListOpen={props.isListOpen}
isLoading={props.isLoading}
/>
</DemoContainer>
);
}
Loading.args = {
isLoading: true,
isListOpen: true,
};
export function ComboboxWithNoHits(props) {
const id = useId();
const [value, setValue] = useState(props.value);
return (
<DemoContainer dataTheme={props.darkMode}>
<UNSAFE_Combobox
id={id}
label="Komboboks (uten søketreff)"
options={props.options}
value={value}
onChange={(event) => setValue(event.currentTarget.value)}
isListOpen={true}
/>
</DemoContainer>
);
}
ComboboxWithNoHits.args = {
options,
value: "Orange",
};
export const Controlled = (props) => {
const id = useId();
const [value, setValue] = useState(props.value);
const [selectedOptions, setSelectedOptions] = useState(props.selectedOptions);
const filteredOptions = useMemo(
() => props.options.filter((option) => option.includes(value)),
[props.options, value]
);
const onToggleSelected = (option, isSelected) => {
if (isSelected) {
setSelectedOptions([...selectedOptions, option]);
} else {
setSelectedOptions(selectedOptions.filter((o) => o !== option));
}
};
return (
<DemoContainer dataTheme={props.darkMode}>
<TextField
label="Overstyr value"
onChange={(event) => setValue(event.target.value)}
value={value}
/>
<br />
<UNSAFE_Combobox
label="Hva er dine favorittfrukter?"
id={id}
filteredOptions={filteredOptions}
isMultiSelect
options={props.options}
onChange={(event) => setValue(event.target.value)}
onToggleSelected={onToggleSelected}
selectedOptions={selectedOptions}
value={value}
/>
</DemoContainer>
);
};
Controlled.args = {
value: "apple",
options,
selectedOptions: initialSelectedOptions,
};
export const ComboboxSizes = (props) => (
<DemoContainer dataTheme={props.darkMode}>
<UNSAFE_Combobox
label="Hva er dine favorittfrukter?"
description="Medium single-select"
options={options}
/>
<br />
<UNSAFE_Combobox
label="Hva er dine favorittfrukter?"
description="Small single-select"
options={options}
size="small"
/>
<br />
<UNSAFE_Combobox
label="Hva er dine favorittfrukter?"
description="Medium multiselect"
options={options}
isMultiSelect
allowNewValues
/>
<br />
<UNSAFE_Combobox
label="Hva er dine favorittfrukter?"
description="Small multiselect"
options={options}
isMultiSelect
size="small"
allowNewValues
/>
</DemoContainer>
);
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));
}
export const CancelInputTest = {
render: (props) => {
return (
<DemoContainer dataTheme={props.darkMode}>
<UNSAFE_Combobox
options={options}
label="Hva er dine favorittfrukter?"
/>
</DemoContainer>
);
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const input = canvas.getByLabelText("Hva er dine favorittfrukter?");
userEvent.click(input);
await userEvent.type(input, "apple", { delay: 200 });
await sleep(1000);
userEvent.keyboard("{ArrowDown}");
await sleep(1000);
userEvent.keyboard("{Escape}");
await sleep(1000);
userEvent.keyboard("{ArrowDown}");
await sleep(500);
const banana = canvas.getByText("banana");
userEvent.click(banana);
},
};
export const RemoveSelectedMultiSelectTest = {
render: (props) => {
return (
<DemoContainer dataTheme={props.darkMode}>
<UNSAFE_Combobox
options={options}
label="Hva er dine favorittfrukter?"
isMultiSelect
/>
</DemoContainer>
);
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const input = canvas.getByLabelText("Hva er dine favorittfrukter?");
userEvent.click(input);
await userEvent.type(input, "apple", { delay: 200 });
await sleep(250);
userEvent.keyboard("{ArrowDown}");
await sleep(250);
userEvent.keyboard("{Enter}");
await sleep(250);
userEvent.keyboard("{Escape}");
await sleep(250);
userEvent.click(input);
await userEvent.type(input, "banana", { delay: 200 });
await sleep(250);
userEvent.keyboard("{ArrowDown}");
await sleep(250);
userEvent.keyboard("{Enter}");
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();
const appleSlettAgain = canvas.queryByLabelText("apple slett");
expect(appleSlettAgain).not.toBeInTheDocument();
},
};
export const AddWhenAddNewDisabledTest = {
render: (props) => {
return (
<DemoContainer dataTheme={props.darkMode}>
<UNSAFE_Combobox
options={options}
label="Hva er dine favorittfrukter?"
isMultiSelect
/>
</DemoContainer>
);
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const input = canvas.getByLabelText("Hva er dine favorittfrukter?");
userEvent.click(input);
await userEvent.type(input, "aaa", { delay: 200 });
await sleep(250);
userEvent.keyboard("{ArrowDown}");
await sleep(250);
userEvent.keyboard("{ArrowDown}");
await sleep(250);
userEvent.keyboard("{Enter}");
await sleep(250);
userEvent.keyboard("{Escape}");
await sleep(250);
const invalidSelect = canvas.queryByLabelText("aaa slett");
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);
},
};
@@ -0,0 +1,123 @@
/* eslint-disable react/jsx-pascal-case */
import { render } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import React, { useId } from "react";
import { UNSAFE_Combobox } from "..";
import { act } from "react-dom/test-utils";
const options = [
"banana",
"apple",
"tangerine",
"pear",
"grape",
"kiwi",
"mango",
"passion fruit",
"pineapple",
"strawberry",
"watermelon",
"grape fruit",
];
const App = (props) => {
const id = useId();
return (
<div data-theme="light">
<UNSAFE_Combobox
label="Hva er dine favorittfrukter?"
size="medium"
variant="simple"
id={id}
{...props}
/>
</div>
);
};
describe("Render combobox", () => {
describe("with multi select", () => {
it("Should be able to search, select and remove selections", async () => {
const utils = render(<App isMultiSelect options={options} />);
await act(async () => {
await userEvent.click(
utils.getByRole("combobox", { name: "Hva er dine favorittfrukter?" })
);
});
await act(async () => {
await userEvent.type(
utils.getByRole("combobox", { name: "Hva er dine favorittfrukter?" }),
"apple"
);
});
await act(async () => {
await userEvent.click(
await utils.findByRole("option", { name: "apple" })
);
});
expect(
await utils.findByRole("option", { name: "apple", selected: true })
).toBeInTheDocument();
await act(async () => {
await userEvent.click(
await utils.findByRole("button", { name: "apple slett" })
);
});
});
});
it("Should show loading icon when loading (used for async search)", async () => {
const utils = render(<App options={[]} isListOpen isLoading />);
expect(await utils.findByRole("option", { name: "venter..." }));
});
});
describe("Combobox state-handling", () => {
it("Should not select previous focused element when closes", async () => {
const utils = render(<App options={options} />);
await act(async () => {
await userEvent.click(
utils.getByRole("combobox", { name: "Hva er dine favorittfrukter?" })
);
});
await act(async () => {
await userEvent.type(
utils.getByRole("combobox", { name: "Hva er dine favorittfrukter?" }),
"ban"
);
await userEvent.keyboard("{ArrowDown}");
await userEvent.keyboard("{ArrowUp}");
await userEvent.keyboard("{Enter}");
});
expect(
await utils.queryByRole("button", { name: "banana slett" })
).toBeNull();
});
it("Should reset list when resetting input (ESC)", async () => {
const utils = render(<App options={options} />);
await act(async () => {
await userEvent.click(
utils.getByRole("combobox", { name: "Hva er dine favorittfrukter?" })
);
});
await act(async () => {
await userEvent.type(
utils.getByRole("combobox", { name: "Hva er dine favorittfrukter?" }),
"apple"
);
await userEvent.keyboard("{ArrowDown}");
await userEvent.keyboard("{Escape}");
await userEvent.keyboard("{ArrowDown}");
});
expect(
await utils.findByRole("option", { name: "banana" })
).toBeInTheDocument();
});
});
@@ -0,0 +1,65 @@
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>(
{} as CustomOptionsContextType
);
export const CustomOptionsProvider = ({ children }) => {
const [customOptions, setCustomOptions] = useState<string[]>([]);
const { focusInput } = useInputContext();
const { isMultiSelect } = useSelectedOptionsContext();
const removeCustomOption = useCallback(
(option) => {
setCustomOptions((prevCustomOptions) =>
prevCustomOptions.filter((o) => o !== option)
);
focusInput();
},
[focusInput, setCustomOptions]
);
const addCustomOption = useCallback(
(option) => {
if (isMultiSelect) {
setCustomOptions((prevOptions) => [...prevOptions, option]);
} else {
setCustomOptions([option]);
}
focusInput();
},
[focusInput, isMultiSelect, setCustomOptions]
);
const customOptionsState = {
customOptions,
removeCustomOption,
addCustomOption,
setCustomOptions,
};
return (
<CustomOptionsContext.Provider value={customOptionsState}>
{children}
</CustomOptionsContext.Provider>
);
};
export const useCustomOptionsContext = () => {
const context = useContext(CustomOptionsContext);
if (!context) {
throw new Error(
"useCustomOptionsContext must be used within a CustomOptionsProvider"
);
}
return context;
};
@@ -0,0 +1,2 @@
export { default as Combobox } from "./ComboboxProvider";
export { type ComboboxProps } from "./types";
@@ -0,0 +1,130 @@
import React, { ChangeEvent, InputHTMLAttributes } from "react";
import { FormFieldProps } from "../useFormField";
export interface ComboboxProps
extends FormFieldProps,
Omit<InputHTMLAttributes<HTMLInputElement>, "size" | "onChange" | "value"> {
/**
* Combobox label
*/
label: React.ReactNode;
/**
* List of options to use for autocompletion
*/
options: string[];
/**
* If enabled, adds an option to add the value of the input as an option whenever there are no options matching the value.
*/
allowNewValues?: boolean;
/**
* If "true" adds a button to clear the value in the input field
*/
clearButton?: boolean;
/**
* Custom name for the clear button. Requires "clearButton" to be "true".
*
* @default "Tøm"
*/
clearButtonLabel?: string;
/**
* A list of options to display in the dropdown list.
* If provided, this overrides the internal search logic in the component.
* Useful for e.g. searching on a server or when overriding the search algorithm to search for synonyms or similar.
*/
filteredOptions?: string[];
/**
* Optionally hide the label visually.
* Not recommended, but can be considered for e.g. search fields in the top menu.
*/
hideLabel?: boolean;
/**
* Custom class name for the input field.
*
* If used for styling, please consider using tokens instead.
*/
inputClassName?: string | undefined;
/**
* Controlled open/closed state for the dropdown list
*/
isListOpen?: boolean;
/**
* Set to "true" when doing an async search and waiting for new filteredOptions.
*
* Will show a spinner in the dropdown and announce to screen readers that it is loading.
*/
isLoading?: boolean;
/**
* Set to "true" to allow multiple selections
*
* This will display selected values as a list of Chips in front of the input field, instead of a selection replacing the value of the input.
*
*/
isMultiSelect?: boolean;
/**
* Callback function triggered whenever the value of the input field is triggered.
*
* @param event
* @returns
*/
onChange?: (
event: ChangeEvent<HTMLInputElement> | null,
value?: string
) => void;
/**
* Callback function triggered whenever the input field is cleared
*
* @param event
* @returns
*/
onClear?: (event: React.PointerEvent | React.KeyboardEvent) => void;
/**
* Callback function triggered whenever an option is selected or de-selected
*
* @param option
* @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,
isCustomOption: boolean
) => void;
/**
* List of selected options.
*
* Use this prop when controlling the selected state outside for the component,
* e.g. for a filter, where options can be toggled elsewhere/programmatically.
*/
selectedOptions?: string[];
/**
* Set to "true" to enable inline autocomplete.
*
* @default false
*/
shouldAutocomplete?: boolean;
/**
* When set to "true" displays selected options as Chips before the input field
*
* @default true
*/
shouldShowSelectedOptions?: boolean;
/**
* When set to "true" displays the toggle button for opening/closing the dropdown list
*
* @default true
*/
toggleListButton?: boolean;
/**
* Custom name for the toggle list-button. Requires "toggleListButton" to be "true".
*
* @default "Alternativer"
*/
toggleListButtonLabel?: string;
/**
* Set this to override the value of the input field.
*
* This converts the input to a controlled input, so you have to use onChange to update the value.
*/
value?: string;
}
+1
View File
@@ -17,6 +17,7 @@ export {
type RadioProps,
} from "./radio";
export { Search, type SearchClearEvent, type SearchProps } from "./search";
export { Combobox as UNSAFE_Combobox, type ComboboxProps } from "./combobox";
export { default as Select, type SelectProps } from "./Select";
export { default as Switch, type SwitchProps } from "./Switch";
export { Counter, default as Textarea, type TextareaProps } from "./Textarea";
@@ -1,7 +1,8 @@
import React, { useState } from "react";
import { ConfirmationPanel } from "../index";
import { Meta } from "@storybook/react";
import React, { useState } from "react";
import { Link } from "../..";
import { ConfirmationPanel } from "../index";
export default {
title: "ds-react/ConfirmationPanel",
component: ConfirmationPanel,
+19 -1
View File
@@ -35,10 +35,28 @@ export interface FormFieldProps {
readOnly?: boolean;
}
export interface FormFieldType {
showErrorMsg: boolean;
hasError: boolean;
errorId: string;
inputDescriptionId: string;
size: "small" | "medium";
inputProps: {
id: string;
"aria-invalid"?: boolean;
"aria-describedby"?: string;
disabled?: boolean;
};
readOnly?: boolean;
}
/**
* Handles props and their state for various form-fields in context with Fieldset
*/
export const useFormField = (props: FormFieldProps, prefix: string) => {
export const useFormField = (
props: FormFieldProps,
prefix: string
): FormFieldType => {
const { size, error, errorId: propErrorId } = props;
const fieldset = useContext(FieldsetContext);
+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" />;
});
Loaded 100 of 289 files, more files were not shown because too many files have changed in this diff. Show more