Compare commits

..
Author SHA1 Message Date
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
github-actions[bot]andgithub-actions[bot] d242325863 Version Packages (#2087)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-07-03 13:22:31 +02:00
Ken ae70bfa719 ReadOnly for skjemakomponenter (#2080) 2023-07-03 12:37:48 +02:00
Ken 0aef861622 📝 Readme-oppdatering 2023-06-30 14:33:58 +02:00
Ken 525b021e0f 📝 Oppdatert Switch-eksempler 2023-06-30 11:23:22 +02:00
Ken 761344c34e 🎨 Oppdatert sidebar-config for sanity 2023-06-30 10:34:39 +02:00
github-actions[bot]andgithub-actions[bot] a67f61c33f Version Packages (#2084)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-06-29 15:23:12 +02:00
Julian NymarkandKen b05958863b add close button to alert (#2079)
Co-authored-by: Ken <ken.aleksander@gmail.com>
2023-06-29 13:43:10 +02:00
Ken 883ae00dfa 💄 Fikset sidebar :before-element 2023-06-29 13:25:17 +02:00
github-actions[bot]andgithub-actions[bot] 6eb8a79092 Version Packages (#2082)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-06-28 11:35:27 +02:00
Ken 718b3204d8 🐛 Fikset i18n for textarea 2023-06-28 11:25:32 +02:00
Ken 45970ac0fb 🐛 UU dukket ikke opp i søk 2023-06-28 10:39:54 +02:00
Ken fe5e8ada74 🐛 Fikset capitalize på god-praksis tagger 2023-06-28 10:39:07 +02:00
Ken 3e625a5cb5 🐛 Underline brakk på windows for forside-kort 2023-06-28 10:06:23 +02:00
Ken edc3495ce6 🐛 Fikset text-decoration for windows på aksel.nav.no 2023-06-27 20:26:59 +02:00
Ken 2520bc7ff5 🎨 Fikset lenker brukt på Aksel.nav.no
🎨 Intro-cards shadows

💄 Komponentoversikt-cards

💄 Oppdatert lenkevisning for cards på aksel.nav.no
2023-06-27 15:25:06 +02:00
github-actions[bot]andgithub-actions[bot] 44446e753a Version Packages (#2078)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-06-26 13:38:24 +02:00
Halvor Haugan 4156326752 Chat: fjern border, gjør svg mindre, bytt om "subtle" og "neutral" (#2077) 2023-06-26 13:27:25 +02:00
Ken 23970486ed 🐛 fikset date-handling for preview av god-praksis 2023-06-26 12:31:36 +02:00
Ken a8f1069650 🎉 Automatisk changelog på aksel.nav.no
🔥 Fjernet unified fra dependencies

🐛 Fikset changelog-format, la inn støtte for emojies
2023-06-23 18:30:03 +02:00
Julian Nymark f9c8320b74 sort + reverse the semver entries (#2074) 2023-06-23 11:01:33 +02:00
Ken c4839a12ed 📝 Oppdatert PR-template, Changelog-cleanup (#2073) 2023-06-23 10:43:53 +02:00
Halvor Haugan 60f64d7f70 📝 Dropdown: Better Item examples (#2072) 2023-06-22 17:21:12 +02:00
github-actions[bot]andgithub-actions[bot] e32dbe5c5d Version Packages (#2071)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-06-22 15:23:42 +02:00
Ken de36269582 ✨ Oppdaterte shadow-tokens (#2041) 2023-06-22 15:12:42 +02:00
Julian Nymark 6cb6c52770 Changelog generator script (#2046) 2023-06-22 15:10:03 +02:00
Ken a877d24ed0 🐛 Fikset corder-bleed på komponentside-header 2023-06-21 17:10:43 +02:00
github-actions[bot]andgithub-actions[bot] a1b6332fa1 Version Packages (#2070)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-06-21 13:48:45 +02:00
Ken 99c08b8e4a Fikset bug i Popover/Helptext der popover gikk helt ut i kantene (#2069) 2023-06-21 13:44:59 +02:00
Ken d39ed906c6 🐛 Fikset ExpansionCard bug ved nesting (#2067) 2023-06-21 13:40:48 +02:00
github-actions[bot]andgithub-actions[bot] 3b31bd3d75 Version Packages (#2066)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-06-21 11:59:33 +02:00
Halvor HauganandKen b1625ba388 ✨ Chat: ny small-variant, justert design, forbedret UU (#2048)
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
2023-06-21 09:40:50 +02:00
Ken 269d06c277 📝 ErrorSummary prop-dokumentasjon 2023-06-20 15:47:52 +02:00
Ken 0c56273f3b 🐛 Fikset rekkefølge på button-stories 2023-06-20 15:41:13 +02:00
Ken ab6a96aed7 ♻️ Oppdatert komponent demoer (#2063) 2023-06-20 11:46:45 +02:00
Ken 9ee8a72082 🔥 Fjernet død kode, lazy-loader sanity-moduler bare brukt for preview 2023-06-19 19:53:50 +02:00
Ken c2e5a92d8e 🎉 Oppdatert søk (#2062) 2023-06-19 18:28:24 +02:00
Ken 6059aa59aa 🐛 Oppdatert playwright-config for mindre flaky tester 2023-06-19 16:21:13 +02:00
Ken b877ada408 🐛 Fikset playwright-tester 2023-06-19 15:11:59 +02:00
Ken 4e339f183e 🔊 Oppdatert amplitude (#2061) 2023-06-19 14:09:08 +02:00
Ken 1d62bf26ff 🛠️ Diverse småjustering for Aksel (#2060) 2023-06-19 13:19:57 +02:00
github-actions[bot]andgithub-actions[bot] da0fbc3299 Version Packages (#2057)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-06-19 13:02:23 +02:00
Ken 9f59e67e2a ⚡ Lazy-loading date-fns: sparer ~10kB first-load (#2058) 2023-06-19 12:53:28 +02:00
Ken ed7aafcac8 🎨 Oppdatert deploy image (#2059) 2023-06-19 12:53:08 +02:00
Ken dfbbe92a63 🎨 Helptext har nå en prop for wrapperClassName (#2056) 2023-06-19 11:48:53 +02:00
Ken 42b5af64ab 🐛 Timeline Period brakk ved bruk av JSODOM 2023-06-17 10:25:45 +02:00
Ken 329119fa04 💄 DoDont-ikon er nå høyrestilt 2023-06-16 16:21:19 +02:00
Ken 0fc80f45cb ♿ Fikset regression der sanity ikke lengre støttet skjermlesere 2023-06-16 16:07:37 +02:00
Ken 337b9fda13 🐛 Fikset inlinecode bruk i heading 2023-06-16 12:58:26 +02:00
github-actions[bot]andgithub-actions[bot] 659e835da9 Version Packages (#2055)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-06-15 16:38:08 +02:00
Ken e10f4375b1 Merge branch 'main' of https://github.com/navikt/aksel 2023-06-15 16:34:25 +02:00
Ken 9a8bb26c36 🐛 Timeline brakk ved bruk av JSDOM-tester 2023-06-15 16:33:40 +02:00
github-actions[bot]andgithub-actions[bot] 69aea38784 Version Packages (#2053)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-06-15 15:55:48 +02:00
Ken 8f16d1571e 🎨 Oppdatert ikoner (#2054) 2023-06-15 15:51:47 +02:00
Marianne Røsvik e2265c9a78 Added two new icons (#2047) 2023-06-15 15:06:48 +02:00
Ken 925aa7a140 🐛 OverridableComponent omitter nå 'as' (#2051) 2023-06-15 14:46:34 +02:00
Ken 7c17e40cac 🐛 Datepicker og Monthpicker lukker nå popover ved escape (#2052) 2023-06-15 14:46:07 +02:00
github-actions[bot]andgithub-actions[bot] 3c8889ea88 Version Packages (#2050)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-06-13 16:49:54 +02:00
Ken e060961050 🐛 Fikset bruk av text-subtle på skjemaelementer (#2049) 2023-06-13 16:45:35 +02:00
Ken 18aa4ee7fb Merge branch 'main' of https://github.com/navikt/aksel 2023-06-09 14:36:38 +02:00
Ken f44edd76a2 Merge branch 'next' 2023-06-09 14:31:11 +02:00
github-actions[bot]andgithub-actions[bot] 1fa636bdd5 Version Packages (#2044)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-06-09 14:16:43 +02:00
Ken 44a75acb6b 🐛 className ble ikke riktig forwardet til period 2023-06-09 14:14:15 +02:00
Ken 550faee900 💄 Checkboard-pattern for transparent visning av tokens 2023-06-09 13:53:29 +02:00
Karl O 88b6174b0b 📝 La til next-logger så backend logging får rett log-level (#2042) 2023-06-09 13:34:31 +02:00
Ken fbf016b3bd ✨ Forbedringer for Aksel.nav.no UI (#2043) 2023-06-09 13:05:59 +02:00
09fa503b98 initial template for PR (#2019)
* initial template for PR

* update template

* update template

* 📝 Oppdatert template

---------

Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
Co-authored-by: Ken <ken.aleksander@gmail.com>
2023-06-09 09:55:28 +02:00
Ken f5688485f5 ⬆️ Oppdatert Sanity 2023-06-08 18:43:14 +02:00
347 changed files with 11327 additions and 7914 deletions

No files matched your search

+4 -1
View File
@@ -1,6 +1,9 @@
{
"$schema": "https://unpkg.com/@changesets/config@2.2.0/schema.json",
"changelog": ["@changesets/changelog-github", { "repo": "navikt/aksel" }],
"changelog": [
"@svitejs/changesets-changelog-github-compact",
{ "repo": "navikt/aksel" }
],
"commit": false,
"fixed": [
[
+44
View File
@@ -0,0 +1,44 @@
### Description
<!-- PR description/motivation
add links to project-tasks, slack discussions etc here
-->
### Change summary
<!-- Short summary of changes in PR
- added variant x
- fixed bug in y
-->
### PR Checklist 📝 (Remove fields after check!)
#### New component? ✨
- Check that component-styling is correctly configured in `@navikt/core/css/config/_mappings.js`. This is needed for correct build and CDN upload.
- Check that styling is correctly exported from `@navikt/core/css/index.css`
- Check that correct tokens for component is documented in `@navikt/core/css/tokens.json`
- Check that only needed components and types are exported from `@navikt/core/react/src/index.ts`
- Add JSDoc to component (see other components for reference)
- Create storybook-stories for relevant component variations and compositions. Stories are used for [visual regression testing](https://www.chromatic.com/docs/), so make sure they are complete.
- Is the component sufficiently complex to warrant tests? Add some unit-tests for jest.
#### Component-updates 🎉
- Revalidate if everything under `New component` is still accounted for
- Deprecating/removing a CSS class? Add it to `@navikt/aksel-stylelint/src/deprecations.ts` with a suitable description. This helps stylelint-users keep their code clean and bug-free.
#### Documentation 📝
- Add/update component-demos for component `aksel.nav.no/website/pages/eksempler`
- Create/Update documentation in sanity if needed
Note: Props, tokens and examples only updates to sanity on merge to main
#### Versioning 🏷️
- Run `yarn changeset` to generate a version-entry for change.
- - Bug/hotfix: Patch
- - New feature: Minor
- - Breaking change: Major
- Adding breaking changes? Consider adding a codemod for easier migration.
- Breaking changes also needs documentation under "Migration"-page on website
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
playwright:
runs-on: ubuntu-latest
container:
image: mcr.microsoft.com/playwright:v1.33.0-focal
image: mcr.microsoft.com/playwright:v1.35.1-jammy
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
+4
View File
@@ -28,6 +28,10 @@ jobs:
node-version: 16.13.0
cache: yarn
- uses: denoland/setup-deno@v1
with:
deno-version: v1.x
- name: Restore cache
uses: actions/cache@v3
with:
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
name: Smoketest Aksel
runs-on: ubuntu-latest
container:
image: mcr.microsoft.com/playwright:v1.33.0-focal
image: mcr.microsoft.com/playwright:v1.35.1-jammy
steps:
- uses: actions/setup-node@v3
with:
+2 -1
View File
@@ -150,4 +150,5 @@ playwright-report/
/test-results/
backup-*.tar.gz
sitemap-urls.json
*.tar.gz
*.tar.gz
searchindex.json
+1 -1
View File
@@ -4,7 +4,7 @@ import "./layout.css";
export const parameters = {
options: {
storySort: {
method: "",
method: "alphabetical",
order: ["Intro", "ds-react", ["form"], ["Default"], "ds-icons"],
locales: "",
},
+25 -138
View File
@@ -1,214 +1,101 @@
# @navikt/aksel-icons
## 4.1.2
## 4.7.2
## 4.1.1
## 4.7.1
## 4.1.0
## 4.7.0
## 4.0.0
## 4.6.1
## 3.4.2
## 4.6.0
## 3.4.1
## 4.5.0
## 3.4.0
## 4.4.2
## 3.3.1
## 4.4.1
## 3.3.0
## 4.1.5
## 3.2.4
### Patch Changes
- Nye ikoner `ChevronRightLast` og `ChevronLeftLast` ([PR](https://github.com/navikt/aksel/pull/2054))
## 3.2.3
### Patch Changes
- [#1998](https://github.com/navikt/aksel/pull/1998) [`53c98181c`](https://github.com/navikt/aksel/commit/53c98181c327ead3406bec3487c10232af7d463a) Thanks [@KenAJoh](https://github.com/KenAJoh)! - :tada: Nye ikoner!
- :sparkles: FileParagraph
- :sparkles: FilePlus
## 3.2.2
## 3.2.1
## 3.2.0
## 3.1.3
## 3.1.2
## 3.1.1
## 3.1.0
## 3.0.1
## 3.0.0
- Nye ikoner `FileParagraph` og `FilePlus` ([PR](https://github.com/navikt/aksel/pull/1998))
## 2.9.8
### Patch Changes
- [#1959](https://github.com/navikt/aksel/pull/1959) [`8790c5997`](https://github.com/navikt/aksel/commit/8790c5997e0d647419bdb5080c86781d08c196b8) Thanks [@KenAJoh](https://github.com/KenAJoh)! - :lipstick: Statusikoner er nå tydeligere og mer konsistente.
- Oppdatert Statusikoner til å være tydeligere og mer konsistente. ([PR](https://github.com/navikt/aksel/pull/1959))
## 2.9.7
### Patch Changes
- :bug: CheckmarkIcon
- Fikset `CheckmarkIcon`-bug
## 2.9.6
### Patch Changes
- [#1956](https://github.com/navikt/aksel/pull/1956) [`8c8559009`](https://github.com/navikt/aksel/commit/8c8559009982911bc4402913656ba4cc6a223dd1) Thanks [@KenAJoh](https://github.com/KenAJoh)! - :bug: Fikset CheckmarkCircleIcon
## 2.9.5
## 2.9.4
- Fikset `CheckmarkCircleIcon`-bug ([PR](https://github.com/navikt/aksel/pull/1956))
## 2.9.3
### Patch Changes
- [#1950](https://github.com/navikt/aksel/pull/1950) [`c03d53d5a`](https://github.com/navikt/aksel/commit/c03d53d5aa2dc7f06a223d48069fa21ee39bb396) Thanks [@KenAJoh](https://github.com/KenAJoh)! - :sparkles: Nye ikoner
- :tada: BulletList
- :tada: NumberList
## 2.9.2
## 2.9.1
## 2.9.0
## 2.8.16
- Nye ikoner `BulletList` og `NumberList` ([PR](https://github.com/navikt/aksel/pull/1950))
## 2.8.15
### Patch Changes
- [#1928](https://github.com/navikt/aksel/pull/1928) [`dfe923582`](https://github.com/navikt/aksel/commit/dfe923582a57b41c77cfb4fbfa7c56be047ef2b0) Thanks [@KenAJoh](https://github.com/KenAJoh)! - :tada: Lagt til nye ikoner :tada:
- HandKnot.svg
- HandKnotFilled.svg
- [#1930](https://github.com/navikt/aksel/pull/1930) [`6682be1c8`](https://github.com/navikt/aksel/commit/6682be1c8e6562213c64e5be4bed70fef54ab865) Thanks [@KenAJoh](https://github.com/KenAJoh)! - Alle pakker implementerer nå npm provenance (beta)
## 2.8.14
- :tada: Nye ikoner `HandKnot` og `HandKnotFilled` ([PR](https://github.com/navikt/aksel/pull/1928))
## 2.8.13
### Patch Changes
- [#1921](https://github.com/navikt/aksel/pull/1921) [`ecd6241e5`](https://github.com/navikt/aksel/commit/ecd6241e5751519fbda800a5f41a613957dbf77c) Thanks [@KenAJoh](https://github.com/KenAJoh)! - Oppdatert ikoner :tada:
## 2.8.12
- Oppdatert `InformationSquare`-ikon ([PR](https://github.com/navikt/aksel/pull/1921))
## 2.8.11
### Patch Changes
- [#1909](https://github.com/navikt/aksel/pull/1909) [`270427910`](https://github.com/navikt/aksel/commit/2704279107a5fbf93048de37d014e78f6721438f) Thanks [@KenAJoh](https://github.com/KenAJoh)! - Oppdatert ikoner
## 2.8.10
## 2.8.9
- Oppdatert `VideoSlack`, `Questionmark` og `Information`-ikoner ([PR](https://github.com/navikt/aksel/pull/1909))
## 2.8.8
### Patch Changes
- [`ed8e28454`](https://github.com/navikt/aksel/commit/ed8e284540a9e3a5be7d1d5e393f31db86962613) Thanks [@KenAJoh](https://github.com/KenAJoh)! - :sparkles: Nye ikoner
- :tada: VideoSlash
- :tada: VideoSlashFill
## 2.8.7
## 2.8.6
## 2.8.5
- Nye ikoner `VideoSlash` og `VideoSlashFill`
## 2.8.4
### Patch Changes
- Fikset Sourcemap-bug i Aksel-icons
## 2.8.3
## 2.8.2
## 2.8.1
## 2.8.0
## 2.7.8
- Fikset Sourcemap-bug
## 2.7.7
### Patch Changes
- [`a22ac3eb7`](https://github.com/navikt/aksel/commit/a22ac3eb78135bf93d4771802475396849809c03) Thanks [@KenAJoh](https://github.com/KenAJoh)! - :Sparkles: Nye ikoner
- :tada: CaretUpDownFilledDown
- :tada: CaretUpDownFilledUp
- :tada: CloudDown
- :tada: CloudDownFill
- :tada: CloudUp
- :tada: CloudUpFill
- :tada: HddDown
- :tada: HddDownFill
- :tada: HddUp
- :tada: HddUpFill
- :tada: Inbox
- :tada: InboxDown
- :tada: InboxDownFill
- :tada: InboxFill
- :tada: InboxUp
- :tada: InboxUpFill
- :tada: ShoppingBasket
- :tada: ShoppingBasketFill
## 2.7.6
## 2.7.5
- Nye ikoner `CaretUpDownFilledDown`, `CaretUpDownFilledUp`, `CloudDown`, `CloudDownFill`, `CloudUp`, `CloudUpFill`, `HddDown`, `HddDownFill`, `HddUp`, `HddUpFill`, `Inbox`, `InboxDown`, `InboxDownFill`, `InboxFill`, `InboxUp`, `InboxUpFill`, `ShoppingBasket`, `ShoppingBasketFill`
## 2.7.4
### Patch Changes
- [#1852](https://github.com/navikt/aksel/pull/1852) [`a4fd1dd22`](https://github.com/navikt/aksel/commit/a4fd1dd227b9b56b747f962aa0c2b2dc9b4e30a4) Thanks [@KenAJoh](https://github.com/KenAJoh)! - Oppdatert ikonpakke med nye ikoner :rocket:
- :tada: CaretDownFill
- :tada: CaretLeft
- :tada: CaretLeftFill
- :tada: CaretLeftRight
- :tada: CaretLeftRightFill
- :tada: CaretRight
- :tada: CaretRightFill
- :tada: CaretUpDownFill
- :tada: CaretUpFill
- :tada: Escalator
- :tada: Moon
- :tada: MoonFill
- :fire: EscalatorStroke
## 2.7.3
## 2.7.2
## 2.7.1
### Patch Changes
- [`7d9517cf9`](https://github.com/navikt/aksel/commit/7d9517cf90331b57441a0787a04eef66efd1b9b0) Thanks [@KenAJoh](https://github.com/KenAJoh)! - Public-access for ny ikonpakke i GPR
- Nye ikoner, `CaretDownFill`, `CaretLeft`, `CaretLeftFill`, `CaretLeftRight`, `CaretLeftRightFill`, `CaretRight`, `CaretRightFill`, `CaretUpDownFill`, `CaretUpFill`, `Escalator`, `Moon`, `MoonFill`. Fjernet `EscalatorStroke` ([PR](https://github.com/navikt/aksel/pull/1852))
## 2.7.0
### Minor Changes
- [#1847](https://github.com/navikt/aksel/pull/1847) [`de291a972`](https://github.com/navikt/aksel/commit/de291a9722c262d40fecaec7be55b71f86e60160) Thanks [@KenAJoh](https://github.com/KenAJoh)! - Ny ikonpakke med for core icons 3! `@navikt/aksel-icons`
- Ny ikonpakke med for core icons 3! `@navikt/aksel-icons` ([PR](https://github.com/navikt/aksel/pull/1847))
+1 -1
View File
@@ -15,5 +15,5 @@ keywords:
- Sammenføye
- Kildekontroll
variant: Stroke
updated_at: 08.03.2023
updated_at: 24.05.2023
created_at: 07.03.2023
@@ -5,5 +5,5 @@ keywords:
- left
- venstre
variant: Stroke
updated_at: 08.03.2023
updated_at: 14.06.2023
created_at: 07.03.2023
@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.5 6.25C7.5 5.83579 7.16421 5.5 6.75 5.5C6.33579 5.5 6 5.83579 6 6.25V17.75C6 18.1642 6.33579 18.5 6.75 18.5C7.16421 18.5 7.5 18.1642 7.5 17.75V6.25ZM17.7803 7.03033C18.0732 6.73744 18.0732 6.26256 17.7803 5.96967C17.4874 5.67678 17.0126 5.67678 16.7197 5.96967L11.2197 11.4697C11.079 11.6103 11 11.8011 11 12C11 12.1989 11.079 12.3897 11.2197 12.5303L16.7197 18.0303C17.0126 18.3232 17.4874 18.3232 17.7803 18.0303C18.0732 17.7374 18.0732 17.2626 17.7803 16.9697L12.8107 12L17.7803 7.03033Z" fill="#262626"/>
</svg>

After

Width:  |  Height:  |  Size: 664 B

@@ -0,0 +1,12 @@
name: ChevronLeftFirst
category: Arrows
sub_category: Chevron
keywords:
- left
- venstre
- side
- first
- først
variant: Stroke
updated_at: 15.06.2023
created_at: 15.06.2023
@@ -6,5 +6,5 @@ keywords:
- høyre
- side
variant: Stroke
updated_at: 08.03.2023
updated_at: 09.06.2023
created_at: 07.03.2023
@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M18 6.25C18 5.83579 17.6642 5.5 17.25 5.5C16.8358 5.5 16.5 5.83579 16.5 6.25V17.75C16.5 18.1642 16.8358 18.5 17.25 18.5C17.6642 18.5 18 18.1642 18 17.75V6.25ZM7.28033 6.21967C6.98744 5.92678 6.51256 5.92678 6.21967 6.21967C5.92678 6.51256 5.92678 6.98744 6.21967 7.28033L11.1893 12.25L6.21967 17.2197C5.92678 17.5126 5.92678 17.9874 6.21967 18.2803C6.51256 18.5732 6.98744 18.5732 7.28033 18.2803L12.7803 12.7803C13.0732 12.4874 13.0732 12.0126 12.7803 11.7197L7.28033 6.21967Z" fill="#262626"/>
</svg>

After

Width:  |  Height:  |  Size: 648 B

@@ -0,0 +1,10 @@
name: ChevronRightLast
category: Arrows
sub_category: Chevron
keywords:
- right
- høyre
- side
variant: Stroke
updated_at: 15.06.2023
created_at: 15.06.2023
+1 -1
View File
@@ -15,5 +15,5 @@ keywords:
- markup-språk
- kildekode
variant: Stroke
updated_at: 08.03.2023
updated_at: 24.05.2023
created_at: 07.03.2023
+1 -1
View File
@@ -13,5 +13,5 @@ keywords:
- dra og slippe
- skifte
variant: Stroke
updated_at: 08.03.2023
updated_at: 26.05.2023
created_at: 07.03.2023
+1 -1
View File
@@ -23,5 +23,5 @@ keywords:
- milepæler
- hendelser
variant: Stroke
updated_at: 08.03.2023
updated_at: 31.05.2023
created_at: 07.03.2023
+1 -1
View File
@@ -7,5 +7,5 @@ keywords:
- delete
- slett
variant: Stroke
updated_at: 08.03.2023
updated_at: 24.05.2023
created_at: 07.03.2023
+1 -1
View File
@@ -6,5 +6,5 @@ keywords:
- uploading
- send
variant: Stroke
updated_at: 08.03.2023
updated_at: 08.06.2023
created_at: 07.03.2023
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/aksel-icons",
"version": "4.1.2",
"version": "4.7.2",
"author": "Aksel | NAV",
"homepage": "https://aksel.nav.no/ikoner",
"repository": {
+18 -11
View File
@@ -1,34 +1,41 @@
# @navikt/aksel-stylelint
## 4.1.2
## 4.7.2
## 4.1.1
## 4.7.1
## 4.7.0
## 4.6.1
## 4.6.0
## 4.5.0
## 4.4.2
## 4.4.1
## 4.1.0
### Minor Changes
- [#2035](https://github.com/navikt/aksel/pull/2035) [`7b42f536a`](https://github.com/navikt/aksel/commit/7b42f536aa90c1b5dbe2b19f1bbe292701546420) Thanks [@KenAJoh](https://github.com/KenAJoh)! - :tada: Oppdatert Chips
- Toggle Chips har nå varianter: neutral og action
- Toggle Chips har nå en ny prop: `checkmark` som slår av/på checkmark ved selected-state
## 4.0.0
- Deprecated klassenavn `navds-chips--icon-left` ([PR](https://github.com/navikt/aksel/pull/2035))
## 3.4.2
### Patch Changes
- [#2017](https://github.com/navikt/aksel/pull/2017) [`7615dbbb5`](https://github.com/navikt/aksel/commit/7615dbbb54c7c96e2252366188547b27b2ac1142) Thanks [@JulianNymark](https://github.com/JulianNymark)! - add missing dependency
- La til riktige dependencies ([PR](https://github.com/navikt/aksel/pull/2017))
## 3.4.1
### Patch Changes
- :bug: Stylelint hotfix, inkluderer nå dist-mappe (kanskje..)
- Inkluderer nå dist-mappe i release
## 3.4.0
### Minor Changes
- [#1973](https://github.com/navikt/aksel/pull/1973) [`61c411421`](https://github.com/navikt/aksel/commit/61c4114214841ec42f48e1abd0d72d2a42d265ab) Thanks [@JulianNymark](https://github.com/JulianNymark)! - add stylelint plugin
- Stylelint-pakke for Aksel ([PR](https://github.com/navikt/aksel/pull/1973))
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/aksel-stylelint",
"version": "4.1.2",
"version": "4.7.2",
"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.1.2",
"@navikt/ds-tokens": "^4.1.2",
"@navikt/ds-css": "^4.7.2",
"@navikt/ds-tokens": "^4.7.2",
"@types/jest": "^29.0.0",
"concurrently": "7.2.1",
"copyfiles": "2.4.1",
+46 -211
View File
@@ -1,236 +1,71 @@
# @navikt/aksel
## 4.1.2
## 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.1.2
- @navikt/ds-css@4.7.1
## 4.1.1
## 4.7.0
### Patch Changes
- Updated dependencies [[`91343563c`](https://github.com/navikt/aksel/commit/91343563c916a554d8ade5401c28ab4c3b3e26af)]:
- @navikt/ds-css@4.1.1
- Updated dependencies [[`e3027cc19`](https://github.com/navikt/aksel/commit/e3027cc1966623433cfc2d28915273486c7bf611)]:
- @navikt/ds-css@4.7.0
## 4.1.0
## 4.6.1
### Patch Changes
- Updated dependencies [[`7b42f536a`](https://github.com/navikt/aksel/commit/7b42f536aa90c1b5dbe2b19f1bbe292701546420), [`c028f36e2`](https://github.com/navikt/aksel/commit/c028f36e2bc58223ebf8b655980dde7eafb30add), [`b2f796d68`](https://github.com/navikt/aksel/commit/b2f796d681f1c1d76d26d1fd743268c8c618a854), [`db8c38a09`](https://github.com/navikt/aksel/commit/db8c38a094cd183db54aebecb62d5b223920a040)]:
- @navikt/ds-css@4.1.0
- Updated dependencies [[`28430f134`](https://github.com/navikt/aksel/commit/28430f13455a8ecc10447e5e1ad75625d29c4bcb)]:
- @navikt/ds-css@4.6.1
## 4.6.0
### Patch Changes
- Updated dependencies [[`ae70bfa71`](https://github.com/navikt/aksel/commit/ae70bfa7192db46d8dea0dfb0222a857efc46be5)]:
- @navikt/ds-css@4.6.0
## 4.5.0
### Patch Changes
- Updated dependencies [[`b05958863`](https://github.com/navikt/aksel/commit/b05958863b1adadb1b6a3fcceb4477e7411b66b5), [`b05958863`](https://github.com/navikt/aksel/commit/b05958863b1adadb1b6a3fcceb4477e7411b66b5)]:
- @navikt/ds-css@4.5.0
## 4.4.2
### Patch Changes
- Updated dependencies []:
- @navikt/ds-css@4.4.2
## 4.4.1
### Patch Changes
- Updated dependencies [[`415632675`](https://github.com/navikt/aksel/commit/4156326752eaf7a97855b33653850362dada8788)]:
- @navikt/ds-css@4.4.1
## 4.0.0
### Major Changes
- [#2026](https://github.com/navikt/aksel/pull/2026) [`895bdc08e`](https://github.com/navikt/aksel/commit/895bdc08e50647d9b9186cbf0e9ab069323de56c) Thanks [@KenAJoh](https://github.com/KenAJoh)! - :test-tube: Datepicker og Monthpicker er nå ute av beta. Kjør codemod for migrering, eller `cmd/ctrl + shift + f` og erstatt `UNSAFE_` med ``
- Codemod for migrering av Datepicker/Monthpicker ut av Beta. `npx @navikt/aksel codemod v4-date` ([PR](https://github.com/navikt/aksel/pull/2026))
- UNSAFE-prefix er fjernet fra Datepicker og Monthpicker
```bash
npx @navikt/aksel codemod v4-date
```
- [#2026](https://github.com/navikt/aksel/pull/2026) [`895bdc08e`](https://github.com/navikt/aksel/commit/895bdc08e50647d9b9186cbf0e9ab069323de56c) Thanks [@KenAJoh](https://github.com/KenAJoh)! - :truck: Komponenter i `@navikt/ds-react-internal` er flyttet over til `@navikt/ds-react`
- `<Header />` er renamet til `<InternalHeader />`
- Dropdown, Timeline og InternalHeader(Header) er flyttet over til `@navikt/ds-react`
- All styling fra `@navikt/ds-css-internal` er flyttet til `@navikt/ds-css` og classname-prefix er endret fra `navdsi` -> `navds` for flyttede komponenter. Sett opp [stylelint med configen vår](https://aksel.nav.no/grunnleggende/kode/stylelint), så vil du få feilmelding om du bruker gamle klassenavn.
- Fikset cascading-problem mellom dropdown og popover. Dropdown blir ikke lengre påvirket av import-rekkefølge av popover.
- Styling for flyttede komponenter finnes nå på CDN (https://aksel.nav.no/grunnleggende/kode/css-import)
Kjør codemod for migrering:
```bash
// React
npx @navikt/aksel codemod v4-internal-react
// CSS
npx @navikt/aksel codemod v4-internal-css
```
### Patch Changes
- Updated dependencies [[`895bdc08e`](https://github.com/navikt/aksel/commit/895bdc08e50647d9b9186cbf0e9ab069323de56c), [`895bdc08e`](https://github.com/navikt/aksel/commit/895bdc08e50647d9b9186cbf0e9ab069323de56c)]:
- @navikt/ds-css@4.0.0
## 3.4.2
### Patch Changes
- Updated dependencies []:
- @navikt/ds-css@3.4.2
## 3.4.1
### Patch Changes
- Updated dependencies []:
- @navikt/ds-css@3.4.1
## 3.4.0
### Patch Changes
- Updated dependencies [[`b958d41a2`](https://github.com/navikt/aksel/commit/b958d41a20c66327658514fcf24ae20893c9531a)]:
- @navikt/ds-css@3.4.0
## 3.3.1
### Patch Changes
- Updated dependencies []:
- @navikt/ds-css@3.3.1
## 3.3.0
### Patch Changes
- Updated dependencies []:
- @navikt/ds-css@3.3.0
## 3.2.4
### Patch Changes
- Updated dependencies [[`30eee0dac`](https://github.com/navikt/aksel/commit/30eee0dac4e3e5a1379033283a6885e646e80458)]:
- @navikt/ds-css@3.2.4
## 3.2.3
### Patch Changes
- Updated dependencies [[`475e994f4`](https://github.com/navikt/aksel/commit/475e994f494b2080ffc05eb1684b6a244e3e2969), [`444e905e0`](https://github.com/navikt/aksel/commit/444e905e01965fd5cac40ff20c5225b072e67221)]:
- @navikt/ds-css@3.2.3
## 3.2.2
### Patch Changes
- Updated dependencies []:
- @navikt/ds-css@3.2.2
## 3.2.1
### Patch Changes
- Updated dependencies []:
- @navikt/ds-css@3.2.1
## 3.2.0
### Patch Changes
- Updated dependencies [[`affcab14c`](https://github.com/navikt/aksel/commit/affcab14c3d536929dfa64a36f5b43f9d0e8c3b7)]:
- @navikt/ds-css@3.2.0
## 3.1.3
### Patch Changes
- Updated dependencies [[`e8ecf309a`](https://github.com/navikt/aksel/commit/e8ecf309a910e47fd3cea6c54cd993ab1196910f)]:
- @navikt/ds-css@3.1.3
## 3.1.2
### Patch Changes
- Updated dependencies []:
- @navikt/ds-css@3.1.2
## 3.1.1
### Patch Changes
- Updated dependencies []:
- @navikt/ds-css@3.1.1
## 3.1.0
### Patch Changes
- Updated dependencies [[`9d2cd9e7f`](https://github.com/navikt/aksel/commit/9d2cd9e7fffb3cf6310c88229ee39ea85db19bca), [`f1c4c46eb`](https://github.com/navikt/aksel/commit/f1c4c46ebb0522561483f18e3b86b190cf9cc372)]:
- @navikt/ds-css@3.1.0
## 3.0.1
### Patch Changes
- Updated dependencies []:
- @navikt/ds-css@3.0.1
## 3.0.0
### Patch Changes
- Updated dependencies [[`166ee5feb`](https://github.com/navikt/aksel/commit/166ee5feb3c987c4e633eb449812116bfd865d3f), [`166ee5feb`](https://github.com/navikt/aksel/commit/166ee5feb3c987c4e633eb449812116bfd865d3f)]:
- @navikt/ds-css@3.0.0
## 2.9.8
### Patch Changes
- Updated dependencies []:
- @navikt/ds-css@2.9.8
## 2.9.7
### Patch Changes
- Updated dependencies []:
- @navikt/ds-css@2.9.7
## 2.9.6
### Patch Changes
- Updated dependencies []:
- @navikt/ds-css@2.9.6
## 2.9.5
### Patch Changes
- Updated dependencies []:
- @navikt/ds-css@2.9.5
- Codemods for migrering fra `@navikt/ds-react-internal` til `@navikt/ds-react` [Guide](https://aksel.nav.no/grunnleggende/kode/migrering#h3b8538e1981d) ([PR](https://github.com/navikt/aksel/pull/2026))
## 2.9.4
### Patch Changes
- [#1952](https://github.com/navikt/aksel/pull/1952) [`27b2b4a11`](https://github.com/navikt/aksel/commit/27b2b4a11caf5ca05c96c686c453653d8a900e5f) Thanks [@KenAJoh](https://github.com/KenAJoh)! - :truck: Flyttet Codemods from `@navikt/ds-codemods` -> `@navikt/aksel`.
- `@navikt/ds-codemods` vil bli deprecated ettehvert.
- Updated dependencies []:
- @navikt/ds-css@2.9.4
## 2.9.3
### Patch Changes
- Updated dependencies []:
- @navikt/ds-css@2.9.3
## 2.9.2
### Patch Changes
- Updated dependencies []:
- @navikt/ds-css@2.9.2
## 2.9.1
### Patch Changes
- Updated dependencies []:
- @navikt/ds-css@2.9.1
## 2.9.0
### Patch Changes
- Updated dependencies [[`657b7f3f3`](https://github.com/navikt/aksel/commit/657b7f3f3e62c5ce3173e6c95a29fcd237ce7343)]:
- @navikt/ds-css@2.9.0
- :truck: Flyttet Codemods fra `@navikt/ds-codemods` -> `@navikt/aksel`. `@navikt/ds-codemods` regnes nå som deprecated ([PR](https://github.com/navikt/aksel/pull/1952))
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/aksel",
"version": "4.1.2",
"version": "4.7.2",
"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.1.2",
"@navikt/ds-css": "4.7.2",
"axios": "1.3.6",
"chalk": "4.1.0",
"clipboardy": "^2.3.0",
+117 -310
View File
@@ -1,482 +1,289 @@
# @navikt/ds-css
## 4.1.2
## 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
- Skjema: De fleste skjemakomponenter har nå styling for `readOnly`-state ([#2080](https://github.com/navikt/aksel/pull/2080))
## 4.5.0
### Minor Changes
- - Alert: La til `closeButton`-prop ([#2079](https://github.com/navikt/aksel/pull/2079))
### Patch Changes
- - Button: Fikset outline-bug i tertiary-variant ved `:active`-state ([#2079](https://github.com/navikt/aksel/pull/2079))
## 4.4.2
## 4.4.1
### Patch Changes
- :lipstick: Chat: fjernet border, satt avatar svg til 24x24px, byttet om "subtle" og "neutral" ([#2077](https://github.com/navikt/aksel/pull/2077))
## 4.4.0
### Minor Changes
- Shadow-tokens er oppdatert: Datepicker har nå ikke border, Modal bruker shadow-xlarge, LinkCard bruker shadow-xsmall ([PR](https://github.com/navikt/aksel/pull/2041))
## 4.3.0
### Minor Changes
- Popover/Helptext: Maksbredde på 100vw - 1.5rem. Treffer ikke lengre kanten på skjermen. ([PR](https://github.com/navikt/aksel/pull/2069))
### Patch Changes
- ExpansionCard: Ved nesting av komponetene fikk man styling fra parent ([PR](https://github.com/navikt/aksel/pull/2067))
## 4.2.0
### Minor Changes
- Chat: `small`-size, innebygde varianter for farge og oppdatert utseende. ([PR](https://github.com/navikt/aksel/pull/2048))
## 4.1.4
### Patch Changes
- Readmore: setter nå eksplisitt color for å ikke arve text-subtle fra parent. ([PR](https://github.com/navikt/aksel/pull/2049))
## 4.1.1
### Patch Changes
- [`91343563c`](https://github.com/navikt/aksel/commit/91343563c916a554d8ade5401c28ab4c3b3e26af) Thanks [@KenAJoh](https://github.com/KenAJoh)! - :bug: Fikset markering av tekst i skeleton.
- :bug: Fikset cursor-markering av tekst i skeleton.
## 4.1.0
### Minor Changes
- [#2035](https://github.com/navikt/aksel/pull/2035) [`7b42f536a`](https://github.com/navikt/aksel/commit/7b42f536aa90c1b5dbe2b19f1bbe292701546420) Thanks [@KenAJoh](https://github.com/KenAJoh)! - :tada: Oppdatert Chips
- Chips: `neutral` og `action`-varianter av Chips.Toggle. Opt-out mulighet for Checkmark. Oppdatert checkmark-ikon ([PR](https://github.com/navikt/aksel/pull/2035))
- Toggle Chips har nå varianter: neutral og action
- Toggle Chips har nå en ny prop: `checkmark` som slår av/på checkmark ved selected-state
- [#2036](https://github.com/navikt/aksel/pull/2036) [`b2f796d68`](https://github.com/navikt/aksel/commit/b2f796d681f1c1d76d26d1fd743268c8c618a854) Thanks [@KenAJoh](https://github.com/KenAJoh)! - - Oppdatert text-subtle og icon-subtle tokens til 700-skala (tidligere 600).
- Icon-subtle bruker nå grayalpha.
- Alle description-felter på fieldsets har nå text-subtle som farge.
- [#1821](https://github.com/navikt/aksel/pull/1821) [`db8c38a09`](https://github.com/navikt/aksel/commit/db8c38a094cd183db54aebecb62d5b223920a040) Thanks [@KenAJoh](https://github.com/KenAJoh)! - :tada: Ny komponent Skeleton!
- varianter: text, circle, rounded og rectangle
### Patch Changes
- [#2027](https://github.com/navikt/aksel/pull/2027) [`c028f36e2`](https://github.com/navikt/aksel/commit/c028f36e2bc58223ebf8b655980dde7eafb30add) Thanks [@JulianNymark](https://github.com/JulianNymark)! - :sparkles: La til `indent`-prop på Accordion
- Alle description-felter på fieldsets har nå `text-subtle` som farge. ([PR](https://github.com/navikt/aksel/pull/2036))
## 4.0.0
### Major Changes
- [#2026](https://github.com/navikt/aksel/pull/2026) [`895bdc08e`](https://github.com/navikt/aksel/commit/895bdc08e50647d9b9186cbf0e9ab069323de56c) Thanks [@KenAJoh](https://github.com/KenAJoh)! - :test-tube: Datepicker og Monthpicker er nå ute av beta. Kjør codemod for migrering, eller `cmd/ctrl + shift + f` og erstatt `UNSAFE_` med ``
- All styling fra `@navikt/ds-css-internal` er flyttet til `@navikt/ds-css`. ([PR](https://github.com/navikt/aksel/pull/2026))
- UNSAFE-prefix er fjernet fra Datepicker og Monthpicker
- classname-prefix er endret fra `navdsi` -> `navds` for flyttede komponenter.
```bash
npx @navikt/aksel codemod v4-date
```
- Fikset cascading-problem mellom dropdown og popover. Dropdown blir ikke lengre påvirket av import-rekkefølge av popover.
- [#2026](https://github.com/navikt/aksel/pull/2026) [`895bdc08e`](https://github.com/navikt/aksel/commit/895bdc08e50647d9b9186cbf0e9ab069323de56c) Thanks [@KenAJoh](https://github.com/KenAJoh)! - :truck: Komponenter i `@navikt/ds-react-internal` er flyttet over til `@navikt/ds-react`
- `<Header />` er renamet til `<InternalHeader />`
- Dropdown, Timeline og InternalHeader(Header) er flyttet over til `@navikt/ds-react`
- All styling fra `@navikt/ds-css-internal` er flyttet til `@navikt/ds-css` og classname-prefix er endret fra `navdsi` -> `navds` for flyttede komponenter. Sett opp [stylelint med configen vår](https://aksel.nav.no/grunnleggende/kode/stylelint), så vil du få feilmelding om du bruker gamle klassenavn.
- Fikset cascading-problem mellom dropdown og popover. Dropdown blir ikke lengre påvirket av import-rekkefølge av popover.
- Styling for flyttede komponenter finnes nå på CDN (https://aksel.nav.no/grunnleggende/kode/css-import)
Kjør codemod for migrering:
```bash
// React
npx @navikt/aksel codemod v4-internal-react
// CSS
npx @navikt/aksel codemod v4-internal-css
```
## 3.4.2
## 3.4.1
- Styling for flyttede komponenter finnes nå på CDN [Guide](https://aksel.nav.no/grunnleggende/kode/css-import)
## 3.4.0
### Minor Changes
- [#2010](https://github.com/navikt/aksel/pull/2010) [`b958d41a2`](https://github.com/navikt/aksel/commit/b958d41a20c66327658514fcf24ae20893c9531a) Thanks [@HalvorHaugan](https://github.com/HalvorHaugan)! - Ny variant "moderate" i Tag
## 3.3.1
## 3.3.0
## 3.2.4
### Patch Changes
- [#2000](https://github.com/navikt/aksel/pull/2000) [`30eee0dac`](https://github.com/navikt/aksel/commit/30eee0dac4e3e5a1379033283a6885e646e80458) Thanks [@KenAJoh](https://github.com/KenAJoh)! - :bug: Search støtter nå htmlSize-prop
- Tag: `moderate`-variant ([PR](https://github.com/navikt/aksel/pull/2010))
## 3.2.3
### Patch Changes
- [#1993](https://github.com/navikt/aksel/pull/1993) [`475e994f4`](https://github.com/navikt/aksel/commit/475e994f494b2080ffc05eb1684b6a244e3e2969) Thanks [@KenAJoh](https://github.com/KenAJoh)! - :bug: Fikset small-variant av datepicker-input
- Datepicker: Fikset small-variant av datepicker-input. ([PR](https://github.com/navikt/aksel/pull/1993))
- :recycle: Refactored Datepicker.Input-button for bugfix
- [#1994](https://github.com/navikt/aksel/pull/1994) [`444e905e0`](https://github.com/navikt/aksel/commit/444e905e01965fd5cac40ff20c5225b072e67221) Thanks [@KenAJoh](https://github.com/KenAJoh)! - :tada: CopyButton har nå en `xsmall`-variant
- Kan nå lettere brukes i tabeller
## 3.2.2
## 3.2.1
## 3.2.0
### Minor Changes
- [#1982](https://github.com/navikt/aksel/pull/1982) [`affcab14c`](https://github.com/navikt/aksel/commit/affcab14c3d536929dfa64a36f5b43f9d0e8c3b7) Thanks [@KenAJoh](https://github.com/KenAJoh)! - :sparkles: Ny komponent `<CopyButton />`!
- Erstatter `<CopyToClipboard />` fra `@navikt/ds-react-internal`
- CopyToClipboard er markert som deprecated. Den vil fortsatt fungere, men noen lintere vil kunne ende opp med å klage på den.
- CopyButton: `xsmall`-size for bruk i tabeller ([PR](https://github.com/navikt/aksel/pull/1994))
## 3.1.3
### Patch Changes
- [#1976](https://github.com/navikt/aksel/pull/1976) [`e8ecf309a`](https://github.com/navikt/aksel/commit/e8ecf309a910e47fd3cea6c54cd993ab1196910f) Thanks [@KenAJoh](https://github.com/KenAJoh)! - :lipstick: Oppdatert utseende for ToggleGroup
- Medium og Small er begge 10px lavere
- Mindre border-radius
## 3.1.2
## 3.1.1
- :lipstick: Oppdatert utseende for ToggleGroup. `Medium` og `Small`-size er begge 10px lavere, mindre border-radius ([PR](https://github.com/navikt/aksel/pull/1976))
## 3.1.0
### Minor Changes
- [#1966](https://github.com/navikt/aksel/pull/1966) [`f1c4c46eb`](https://github.com/navikt/aksel/commit/f1c4c46ebb0522561483f18e3b86b190cf9cc372) Thanks [@KenAJoh](https://github.com/KenAJoh)! - :lipstick: Alle komponenter bruker nå default `:focus-visible` for fokusmarkering.
(Alle komponenter som bruker `:focus-visible` har også en fallback for `:focus`)
- Alle komponenter bruker nå default `:focus-visible` for fokusmarkering. Medfølger også fallback for `:focus` ([PR](https://github.com/navikt/aksel/pull/1966))
### Patch Changes
- [#1967](https://github.com/navikt/aksel/pull/1967) [`9d2cd9e7f`](https://github.com/navikt/aksel/commit/9d2cd9e7fffb3cf6310c88229ee39ea85db19bca) Thanks [@KenAJoh](https://github.com/KenAJoh)! - :lipstick: Oppdatert Label og Description spacing for skjemakomponenter
## 3.0.1
- Oppdatert Label og Description spacing for skjemakomponenter ([PR](https://github.com/navikt/aksel/pull/1967))
## 3.0.0
### Major Changes
- [#1964](https://github.com/navikt/aksel/pull/1964) [`166ee5feb`](https://github.com/navikt/aksel/commit/166ee5feb3c987c4e633eb449812116bfd865d3f) Thanks [@KenAJoh](https://github.com/KenAJoh)! - **Accordion**
- Accordion oppdatet til å ha chevron left-aligned.
- Neutral-variant lagt til
- Diverse nye size-options for heading og paddinger.
- Deler av Accordion-CSS er refaktorert. Dette vil kunne brekke overskrevne stiler.
- [#1964](https://github.com/navikt/aksel/pull/1964) [`166ee5feb`](https://github.com/navikt/aksel/commit/166ee5feb3c987c4e633eb449812116bfd865d3f) Thanks [@KenAJoh](https://github.com/KenAJoh)! - Aksels løsninger bruker nå `@navikt/aksel-icons`
**Sideeffects av ikonendringer**
- Komponenter som tidligere brukte Expand-ikon har fått oppdatert animasjon (table, readmore og accordion)
- Alert bruker samme ikonstørrelse for alle størrelser
- Chips har justert padding/ikonstørrelser
- Datepicker hover-bug på knapper er fikset
- Helptekst bruker nå et custom-ikon.
- Select har fått justert padding rundt ikon
- Switch bruker samme checkmark som checkbox, er nå avrundet
- ReadMore har justert margin for alignment med ikon, fjernet content-animasjon
- Tabs bruker default text-default nå
- Stepper har endret hvordan den styler checkmark-ikon.
## 2.9.8
## 2.9.7
## 2.9.6
## 2.9.5
## 2.9.4
## 2.9.3
## 2.9.2
## 2.9.1
- Accordion: Chevron er left-aligned, deler av styling er refaktorert, `size`-props og `neutral`-variant ([PR](https://github.com/navikt/aksel/pull/1964))
## 2.9.0
### Minor Changes
- [#1941](https://github.com/navikt/aksel/pull/1941) [`657b7f3f3`](https://github.com/navikt/aksel/commit/657b7f3f3e62c5ce3173e6c95a29fcd237ce7343) Thanks [@KenAJoh](https://github.com/KenAJoh)! - :tada: CSS nå tilgjengelig som separate filer
- Kan lastest fra CDN
- Tilbys nå i minified-versjoner
- Kommandoline: `npx @navikt/aksel css-imports` for å raskt komme i gang med CSS-optimalisering
## 2.8.16
## 2.8.15
### Patch Changes
- [#1930](https://github.com/navikt/aksel/pull/1930) [`6682be1c8`](https://github.com/navikt/aksel/commit/6682be1c8e6562213c64e5be4bed70fef54ab865) Thanks [@KenAJoh](https://github.com/KenAJoh)! - Alle pakker implementerer nå npm provenance (beta)
## 2.8.14
### Patch Changes
- [`f0d9a8853`](https://github.com/navikt/aksel/commit/f0d9a8853f56854a4049bd3f6cc34968e9d6c380) Thanks [@KenAJoh](https://github.com/KenAJoh)! - Fixes shown month on open in Datepicker/MonthPicker, better handling of out-of-range 'today'
## 2.8.13
## 2.8.12
## 2.8.11
## 2.8.10
- CSS nå tilgjengelig som separate filer: Kan lastest fra CDN, minified-versjoner tilgjengelig ([PR](https://github.com/navikt/aksel/pull/1941))
## 2.8.9
### Patch Changes
- [#1905](https://github.com/navikt/aksel/pull/1905) [`212194964`](https://github.com/navikt/aksel/commit/212194964c4dd576e1ffc031316dff8cbe5016a2) Thanks [@KenAJoh](https://github.com/KenAJoh)! - :sparkles: Padding/border-radius tokens for button.css
## 2.8.8
- Button: Padding/border-radius tokens ([PR](https://github.com/navikt/aksel/pull/1905))
## 2.8.7
### Patch Changes
- [`7f3f025db`](https://github.com/navikt/aksel/commit/7f3f025db2ad605df4240605a733d10d08db753a) Thanks [@KenAJoh](https://github.com/KenAJoh)! - Fikset typografi for radio, checkbox. Button token for tertiary
- Button: token for tertiary
## 2.8.6
### Patch Changes
- [`2eb358ad8`](https://github.com/navikt/aksel/commit/2eb358ad888979d21c385b3900973946f3f466be) Thanks [@KenAJoh](https://github.com/KenAJoh)! - Alert, Chips og ErrorSummary har nå bedre utvalg av tokens
- Alert, Chips og ErrorSummary har nå bedre utvalg av tokens
## 2.8.5
### Patch Changes
- [`f66437082`](https://github.com/navikt/aksel/commit/f664370826ed51bda7ad681e1f1799c26d2c1f0a) Thanks [@KenAJoh](https://github.com/KenAJoh)! - :art: Accordion-tokens for header bakgrunn
- Accordion: tokens for header-bakgrunn
- [#1879](https://github.com/navikt/aksel/pull/1879) [`55b585618`](https://github.com/navikt/aksel/commit/55b585618341a4d8bf26ed9b9e129d897b1600dc) Thanks [@KenAJoh](https://github.com/KenAJoh)! - :bug: Fikset tekstfarge i select på iphone
## 2.8.4
## 2.8.3
- Select: Fikset tekstfarge på iphone ([PR](https://github.com/navikt/aksel/pull/1879))
## 2.8.2
### Patch Changes
- [#1870](https://github.com/navikt/aksel/pull/1870) [`9c495cf50`](https://github.com/navikt/aksel/commit/9c495cf5037a7453e51e273ab93e4232576f4958) Thanks [@KenAJoh](https://github.com/KenAJoh)! - Oppdatert typografi for ExpansioCard
## 2.8.1
## 2.8.0
### Minor Changes
- [#1820](https://github.com/navikt/aksel/pull/1820) [`c6d51a019`](https://github.com/navikt/aksel/commit/c6d51a01902e4fd7916a422e17ed175f39acd458) Thanks [@KenAJoh](https://github.com/KenAJoh)! - :tada: Ny komponent! ExpansionCard. Oppfølger fra gammel Ekspanderbartpanel.
- ExpansioCard: Oppdatert typografi ([PR](https://github.com/navikt/aksel/pull/1870))
## 2.7.8
### Patch Changes
- [#1863](https://github.com/navikt/aksel/pull/1863) [`7ace69f21`](https://github.com/navikt/aksel/commit/7ace69f215760bbb5bf997670c897403d8ee558d) Thanks [@it-vegard](https://github.com/it-vegard)! - Added focus indicator for tabpanel
## 2.7.7
## 2.7.6
- Tabs: La til focus-markering for Tabs.Panel ([PR](https://github.com/navikt/aksel/pull/1863))
## 2.7.5
### Patch Changes
- :bug: Fikset token-bug i togglegroup
## 2.7.4
## 2.7.3
## 2.7.2
## 2.7.1
## 2.7.0
## 2.6.2
## 2.6.1
## 2.6.0
## 2.5.1
## 2.5.0
- ToggleGroup: Fikset token-bug
## 2.4.3
### Patch Changes
- [`f962bc781`](https://github.com/navikt/aksel/commit/f962bc7816027c24e334d9754c3e07cda951cf56) Thanks [@KenAJoh](https://github.com/KenAJoh)! - Fikset disabled-opacity bug i select for Chrome
- Select: Fikset disabled + opacity bug for Chrome
## 2.4.2
### Patch Changes
- [#1823](https://github.com/navikt/aksel/pull/1823) [`a7ce61f84`](https://github.com/navikt/aksel/commit/a7ce61f840e5bfaafc4f70e550167fe0095555b8) Thanks [@kschieren](https://github.com/kschieren)! - added support for nested lists
## 2.4.1
## 2.4.0
### Minor Changes
- [#1807](https://github.com/navikt/aksel/pull/1807) [`70eeb24b3`](https://github.com/navikt/aksel/commit/70eeb24b38ac871c7f2e31e1621b949669c401b6) Thanks [@kschieren](https://github.com/kschieren)! - Added List component
- List: La til støtte for nesting ([PR](https://github.com/navikt/aksel/pull/1823))
## 2.3.1
### Patch Changes
- [`241b2e678`](https://github.com/navikt/aksel/commit/241b2e678f1f40909e2d36fe095dcbce8673f174) Thanks [@KenAJoh](https://github.com/KenAJoh)! - Fikset feil bruk av fallback-tokens i textarea, timeline
- Fikset feil bruk av fallback-tokens i Textarea og Timeline
## 2.3.0
### Minor Changes
- [#1813](https://github.com/navikt/aksel/pull/1813) [`7702c6527`](https://github.com/navikt/aksel/commit/7702c65275a4d86a8260852d091e6dd4cfe9217c) Thanks [@ingfo](https://github.com/ingfo)! - Fiksa sentrering av tekst i Select i Firefox
- Select: Fikset sentrering av tekst i Firefox ([PR](https://github.com/navikt/aksel/pull/1813))
### Patch Changes
- [`51b62cca7`](https://github.com/navikt/aksel/commit/51b62cca7b857e92669cd0b09e620b131faf80e5) Thanks [@KenAJoh](https://github.com/KenAJoh)! - Select small 34px høyde -> 32px
- Select: `small`-variant er nå 32px (var 34px)
## 2.2.0
### Minor Changes
- [#1789](https://github.com/navikt/aksel/pull/1789) [`c0929a534`](https://github.com/navikt/aksel/commit/c0929a534dc8effece5435f8be550cb7931a52b1) Thanks [@KenAJoh](https://github.com/KenAJoh)! - Button og ToggleGroup har nå en neutral-variant, oppdatert neutral-tokens
- ToggleGroup: `Neutral`-variant. ([PR](https://github.com/navikt/aksel/pull/1789))
## 2.1.7
## 2.1.6
## 2.1.5
## 2.1.4
## 2.1.3
- Button: `Neutral`-variant.
## 2.1.2
### Patch Changes
- [`62f36da5e`](https://github.com/navikt/aksel/commit/62f36da5e74fc7f53414d42b310c3b57597795bc) Thanks [@KenAJoh](https://github.com/KenAJoh)! - Small textfield har nå 8px horisontal padding (4px før)
## 2.1.1
## 2.1.0
## 2.0.18
## 2.0.17
## 2.0.16
## 2.0.15
### Patch Changes
- [#1748](https://github.com/navikt/aksel/pull/1748) [`22dfd8e60`](https://github.com/navikt/aksel/commit/22dfd8e60315c421749394d35e41f412a00a7593) Thanks [@KenAJoh](https://github.com/KenAJoh)! - toptextPosition prop i Chat for horisontal plassering av navn og dato.
## 2.0.14
## 2.0.13
- TextField: `small`-variant har nå 8px horisontal padding (før 4px)
## 2.0.12
### Patch Changes
- [#1737](https://github.com/navikt/aksel/pull/1737) [`8a1ef5e7e`](https://github.com/navikt/aksel/commit/8a1ef5e7e2c066cb80c13e14329e1585e2c427f8) Thanks [@KenAJoh](https://github.com/KenAJoh)! - :bug: Fikset default visuell error-state for radio
## 2.0.11
## 2.0.10
## 2.0.9
## 2.0.8
## 2.0.7
## 2.0.6
- Radio: Fikset default visuell error-state ([PR](https://github.com/navikt/aksel/pull/1737))
## 2.0.5
### Patch Changes
- Reverserte border-width endrind i button
## 2.0.4
## 2.0.3
## 2.0.2
- Button: Reverserte border-width endrinder (var 1.5px, nå 2px)
## 2.0.1
### Patch Changes
- [`e8007328d`](https://github.com/navikt/aksel/commit/e8007328db3f6d5be696cf24f03304c79be0f3f7) Thanks [@KenAJoh](https://github.com/KenAJoh)! - Bugfixer ved bruk av tokens
- Bugfixer ved bruk av tokens oppdatert i v2.0.0
## 2.0.0
### Major Changes
- [`6b96e4833`](https://github.com/navikt/aksel/commit/6b96e48330c2e013a1acee85cefccd9ccc1aece4) Thanks [@KenAJoh](https://github.com/KenAJoh)! - v2: Tokens update and font-loading
- Fontlasting: Fonter lastes nå fra NAV-CDN
## 1.5.10
- Tokens: Alle komponenter bruker nå semantiske tokens for som standard, med innebygd støtte for komponent-spesifikke tokens.
## 1.5.9
### Patch Changes
- 4px -> 2px gap chips-toggle active
## 1.5.8
## 1.5.7
## 1.5.6
## 1.5.5
## 1.5.4
### Patch Changes
- [#1709](https://github.com/navikt/aksel/pull/1709) [`77302a30b`](https://github.com/navikt/aksel/commit/77302a30b8eef8dc00b2d2e9c904a3f57da81a11) Thanks [@KenAJoh](https://github.com/KenAJoh)! - Oppdatert ikoner
- Chips: 4px -> 2px gap mellom checkmark i Chips.Toggle
## 1.5.3
### Patch Changes
- Byttet om på alt-farge rekkefølge
## 1.5.2
### Patch Changes
- :memo: Rename FilterChips to ToggleChips
- Tokens: Byttet om på rekkefølge av alt-farger
## 1.5.1
### Patch Changes
- Flex-wrap chips.css
- Chips. Bruker nå standard flex-wrap
## 1.5.0
### Minor Changes
- [#1684](https://github.com/navikt/aksel/pull/1684) [`e19bf67b3`](https://github.com/navikt/aksel/commit/e19bf67b337dea39989c68b5e9c2591cf0d5b40f) Thanks [@KenAJoh](https://github.com/KenAJoh)! - :sparkles: Oppdaterte tag-varianter
- [#1668](https://github.com/navikt/aksel/pull/1668) [`97c5f60e9`](https://github.com/navikt/aksel/commit/97c5f60e9111da7e08f55c8d0aa29581f0a9b1ca) Thanks [@KenAJoh](https://github.com/KenAJoh)! - Chips komponent :sparkles:
## 1.4.4
## 1.4.3
### Patch Changes
- :sparkles: Validering og inputFormat i Date-komponenter
## 1.4.2
## 1.4.1
### Patch Changes
- [`3c08651df`](https://github.com/navikt/aksel/commit/3c08651df28c3e19dd8c8a7a1d0032200bec473d) Thanks [@KenAJoh](https://github.com/KenAJoh)! - Date-komponenter følger språkrådets dato-formatering for måneder.
## 1.4.0
- Tag: `filled`-varianter ([PR](https://github.com/navikt/aksel/pull/1684))
## 1.3.39
### Patch Changes
- [`40f0b3340`](https://github.com/navikt/aksel/commit/40f0b3340d01bf85fecc04f2f3a8e8b2acf996f1) Thanks [@KenAJoh](https://github.com/KenAJoh)! - Bump for latest-tag
- Fikset bruk av `:watch` for diverse parsere (less, parcel)
## 1.3.38
## 1.3.24
### Patch Changes
- Tokens tilgjengeligjøres nå også på `:host`. Dette lar shadow-dom brukere konsumere tokens direkte.
+9
View File
@@ -24,6 +24,7 @@
font-size: 1.5rem;
align-self: flex-start;
height: var(--a-font-line-height-xlarge);
margin: var(--a-spacing-1);
background: radial-gradient(circle, var(--a-surface-default) 50%, 0, transparent);
}
@@ -73,3 +74,11 @@
border: none;
padding: 0;
}
.navds-alert__button-wrapper {
align-self: flex-start;
flex: 1;
display: flex;
flex-flow: row nowrap;
justify-content: flex-end;
}
-2
View File
@@ -294,7 +294,6 @@
.navds-button--tertiary:active {
color: var(--ac-button-tertiary-active-text, var(--a-text-on-action));
background-color: var(--ac-button-tertiary-active-bg, var(--a-surface-action-active));
box-shadow: inset 0 0 0 1px var(--a-surface-default);
}
.navds-button--tertiary:active:hover {
@@ -346,7 +345,6 @@
.navds-button--tertiary-neutral:active {
color: var(--ac-button-tertiary-neutral-active-text, var(--a-text-on-neutral));
background-color: var(--ac-button-tertiary-neutral-active-bg, var(--a-surface-neutral-active));
box-shadow: inset 0 0 0 1px var(--a-surface-default);
}
.navds-button--tertiary-neutral:active:hover {
+31 -17
View File
@@ -1,14 +1,12 @@
.navds-chat {
display: flex;
align-items: flex-end;
gap: var(--a-spacing-4);
padding-right: var(--a-spacing-16);
gap: var(--a-spacing-3);
max-width: 40.75rem;
}
.navds-chat--right {
flex-direction: row-reverse;
padding-right: 0;
padding-left: var(--a-spacing-16);
}
.navds-chat__bubble-wrapper {
@@ -26,29 +24,33 @@
.navds-chat__avatar {
align-items: center;
background: var(--ac-chat-avatar-bg, var(--a-bg-subtle));
color: var(--ac-chat-avatar-color, var(--a-text-default));
box-shadow: var(--a-shadow-xsmall);
background-color: var(--ac-chat-avatar-bg, var(--a-surface-neutral-subtle));
border-radius: var(--a-border-radius-full);
color: var(--ac-chat-avatar-color, var(--a-text-default));
display: flex;
flex-shrink: 0;
justify-content: center;
overflow: hidden;
height: 3rem;
width: 3rem;
height: 2.5rem;
width: 2.5rem;
line-height: 2.5rem; /* Makes text properly vertically centered in Chrome */
font-size: 1.06rem;
letter-spacing: 0.024rem;
}
.navds-chat__avatar svg {
align-self: center;
height: 100%;
width: 100%;
max-width: 1.5rem;
max-height: 1.5rem;
}
.navds-chat__bubble {
padding: 1rem;
box-shadow: var(--a-shadow-small);
padding: var(--a-spacing-4);
box-shadow: var(--a-shadow-xsmall);
width: fit-content;
max-width: 37.5rem;
background-color: var(--ac-chat-bubble-bg, var(--a-bg-subtle));
background-color: var(--ac-chat-bubble-bg, var(--a-surface-neutral-subtle));
border-radius: var(--a-border-radius-xlarge);
border-bottom-left-radius: 2px;
display: flex;
@@ -61,11 +63,27 @@
border-bottom-right-radius: 2px;
}
.navds-chat--small .navds-chat__bubble {
padding: var(--a-spacing-3);
}
.navds-chat--info .navds-chat__bubble,
.navds-chat--info .navds-chat__avatar {
background-color: var(--a-surface-info-subtle);
}
.navds-chat--subtle .navds-chat__bubble,
.navds-chat--subtle .navds-chat__avatar {
background-color: var(--a-surface-default);
}
.navds-chat__top-text {
color: var(--ac-chat-top-text, var(--a-text-default));
display: flex;
gap: var(--a-spacing-2);
align-items: baseline;
font-weight: normal;
margin: 0;
}
.navds-chat--right .navds-chat__top-text {
@@ -87,7 +105,3 @@
.navds-chat--right .navds-chat__top-text--left {
align-self: flex-start;
}
.navds-chat__name {
font-weight: var(--a-font-weight-bold);
}
+5
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],
},
{
component: "ConfirmationPanel",
main: formCss,
+10
View File
@@ -278,3 +278,13 @@
visibility: hidden;
pointer-events: none;
}
.navds-date__popover:where(.navds-popover) {
border: none;
}
/* Readonly */
.navds-date__field--readonly .navds-date__field-button {
cursor: default;
}
+6 -3
View File
@@ -77,7 +77,7 @@
margin-top: var(--a-spacing-1);
}
.navds-expansioncard--small :where(.navds-expansioncard__title--small) {
.navds-expansioncard--small > :where(.navds-expansioncard__header) :where(.navds-expansioncard__title--small) {
margin-top: var(--a-spacing-05);
}
@@ -115,7 +115,7 @@
transition: transform 150ms ease-in-out;
}
.navds-expansioncard--open :where(.navds-expansioncard__header-chevron) {
.navds-expansioncard--open > :where(.navds-expansioncard__header) :where(.navds-expansioncard__header-chevron) {
transform: translateY(0) rotate(180deg);
}
@@ -135,7 +135,10 @@
}
}
.navds-expansioncard--open :where(.navds-expansioncard__header-button):hover :where(.navds-expansioncard__header-chevron) {
.navds-expansioncard--open
> :where(.navds-expansioncard__header)
> :where(.navds-expansioncard__header-button):hover
:where(.navds-expansioncard__header-chevron) {
transform: translateY(-1px) rotate(180deg);
}
+287
View File
@@ -0,0 +1,287 @@
.navds-combobox__wrapper {
display: flex;
flex-direction: column;
width: 100%;
position: relative;
}
.navds-combobox__wrapper-inner {
position: relative;
display: flex;
flex-direction: row;
justify-content: space-between;
width: 100%;
border: 1px solid black;
}
.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__selected-options {
gap: var(--a-spacing-2);
}
.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: calc(0.5rem - 4px);
}
.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%;
}
.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-visible {
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;
}
}
.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: 4px;
gap: 4px 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-combobox__list-item--selected {
background-color: var(--ac-combobox-list-item-selected-bg, var(--a-surface-selected));
}
.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: 3px;
}
/* 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;
+11
View File
@@ -36,3 +36,14 @@
.navds-form-field__subdescription {
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);
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";
+58 -1
View File
@@ -22,6 +22,9 @@
cursor: pointer;
display: flex;
gap: var(--a-spacing-2);
--__ac-radio-checkbox-readonly-bg: var(--a-surface-subtle);
--__ac-radio-checkbox-readonly-border: var(--a-border-subtle);
}
.navds-checkbox__label::before,
@@ -102,7 +105,7 @@
background-color: var(--ac-radio-checkbox-bg, var(--a-surface-default));
width: 0.75rem;
height: 0.25rem;
border-radius: 1px; /* Custom value OK */
border-radius: 1px;
flex-shrink: 0;
}
@@ -244,3 +247,57 @@
.navds-radio--disabled > .navds-radio__label {
cursor: not-allowed;
}
/* Readonly */
.navds-checkbox--readonly > :where(.navds-checkbox__input, .navds-checkbox__label),
.navds-radio--readonly > :where(.navds-radio__input, .navds-radio__label) {
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);
}
.navds-checkbox--readonly
> .navds-checkbox__input:not(:disabled):not(:checked):not(:indeterminate)
+ .navds-checkbox__label::before,
.navds-checkbox--readonly
> .navds-checkbox__input:hover:not(:checked):not(:indeterminate):not(:focus-visible)
+ .navds-checkbox__label::before,
.navds-radio--readonly > .navds-radio__input:not(:disabled):not(:checked):not(:indeterminate) + .navds-radio__label::before,
.navds-radio--readonly
> .navds-radio__input:hover:not(:checked):not(:indeterminate):not(:focus-visible)
+ .navds-radio__label::before {
background-color: var(--__ac-radio-checkbox-readonly-bg);
box-shadow: inset 0 0 0 2px var(--__ac-radio-checkbox-readonly-border);
}
.navds-checkbox--readonly > .navds-checkbox__input:focus-visible + .navds-checkbox__label::before,
.navds-radio--readonly > .navds-radio__input:focus-visible + .navds-radio__label::before {
--__ac-radio-checkbox-readonly-border: var(--a-border-subtle), var(--a-shadow-focus);
}
.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-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);
}
.navds-checkbox--readonly > .navds-checkbox__input:indeterminate + .navds-checkbox__label::before {
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:indeterminate + .navds-checkbox__label::after {
background-color: var(--a-icon-subtle);
}
+6
View File
@@ -79,3 +79,9 @@
.navds-select__input:disabled > option {
color: var(--a-text-default);
}
.navds-select--readonly .navds-select__input {
background-color: var(--a-surface-subtle);
border-color: var(--a-border-subtle);
cursor: default;
}
+41
View File
@@ -178,3 +178,44 @@
.navds-switch__input:disabled ~ .navds-switch__label-wrapper {
cursor: not-allowed;
}
/* Readonly */
.navds-switch--readonly > .navds-switch__track,
.navds-switch--readonly > .navds-switch__input:hover ~ .navds-switch__track,
.navds-switch--readonly > .navds-switch__input:checked ~ .navds-switch__track,
.navds-switch--readonly > .navds-switch__input:checked:hover ~ .navds-switch__track {
background-color: var(--a-surface-neutral-moderate);
}
.navds-switch--readonly > .navds-switch__label-wrapper,
.navds-switch--readonly > .navds-switch__input {
cursor: default;
}
.navds-switch--readonly > .navds-switch__input:hover ~ .navds-switch__label-wrapper,
.navds-switch--readonly .navds-switch__label-wrapper:hover {
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);
}
.navds-switch--readonly > .navds-switch__input:checked ~ .navds-switch__track > .navds-switch__thumb {
color: var(--a-icon-subtle);
}
@media (hover: hover) and (pointer: fine) {
.navds-switch--readonly > .navds-switch__input:hover ~ .navds-switch__track > .navds-switch__thumb {
transform: translateX(0);
}
.navds-switch--readonly > .navds-switch__input:checked:hover ~ .navds-switch__track > .navds-switch__thumb {
transform: translateX(20px);
}
}
+4 -5
View File
@@ -73,11 +73,10 @@
cursor: not-allowed;
}
.navds-text-field__input[readonly] {
background-color: var(--ac-textfield-bg, var(--a-surface-default));
border-color: var(--ac-textfield-border, var(--a-border-default));
box-shadow: none;
cursor: not-allowed;
.navds-text-field--readonly .navds-text-field__input {
background-color: var(--a-surface-subtle);
border-color: var(--a-border-subtle);
cursor: default;
}
/**
+4 -5
View File
@@ -99,9 +99,8 @@
cursor: not-allowed;
}
.navds-textarea__input[readonly] {
background-color: var(--ac-textarea-bg, var(--a-surface-default));
border-color: var(--ac-textarea-border, var(--a-border-default));
box-shadow: none;
cursor: not-allowed;
.navds-textarea--readonly .navds-textarea__input {
background-color: var(--a-surface-subtle);
border-color: var(--a-border-subtle);
cursor: default;
}
+1 -1
View File
@@ -33,7 +33,7 @@
.navds-help-text__popover.navds-popover {
background-color: var(--ac-help-text-popover-bg, var(--a-surface-info-subtle));
max-width: 65ch;
max-width: min(65ch, calc(100vw - 1.5rem));
--ac-popover-border: var(--a-border-info);
}
+1 -1
View File
@@ -13,7 +13,7 @@
}
.navds-link-panel:hover {
box-shadow: var(--a-shadow-medium);
box-shadow: var(--a-shadow-small);
border-color: var(--ac-link-panel-hover-border, var(--a-border-action));
}
+1
View File
@@ -9,6 +9,7 @@
position: relative;
overflow: auto;
max-height: 100%;
box-shadow: var(--a-shadow-xlarge);
}
.navds-modal__content {
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-css",
"version": "4.1.2",
"version": "4.7.2",
"description": "CSS for NAV Designsystem",
"author": "Aksel | NAV designsystem team",
"keywords": [
@@ -27,7 +27,7 @@
"css:get-version": "node config/get-version.js"
},
"devDependencies": {
"@navikt/ds-tokens": "^4.1.2",
"@navikt/ds-tokens": "^4.7.2",
"cssnano": "6.0.0",
"fast-glob": "3.2.11",
"lodash": "4.17.21",
+1
View File
@@ -5,6 +5,7 @@
border: 1px solid;
border-color: var(--ac-popover-border, var(--a-border-default));
border-radius: var(--a-border-radius-medium);
max-width: calc(100vw - var(--a-spacing-6));
}
.navds-popover__content {
+1
View File
@@ -41,6 +41,7 @@
border-left: 2px solid var(--ac-read-more-line, var(--a-border-divider));
margin-left: 13px;
padding-left: 13px;
color: var(--a-text-default);
}
.navds-read-more--small .navds-read-more__content {
+10
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;
@@ -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));
+4
View File
@@ -61,6 +61,10 @@
list-style-type: none;
}
.navds-timeline__row-periods:focus {
outline: none;
}
.navds-timeline__period {
height: 100%;
border-radius: var(--a-border-radius-full);
+17 -2
View File
@@ -80,9 +80,9 @@
"--ac-button-tertiary-neutral-active-hover-bg": "--a-surface-neutral-active"
},
"chat": {
"--ac-chat-avatar-bg": "--a-bg-subtle",
"--ac-chat-avatar-bg": "--a-surface-neutral-subtle",
"--ac-chat-avatar-color": "--a-text-default",
"--ac-chat-bubble-bg": "--a-bg-subtle",
"--ac-chat-bubble-bg": "--a-surface-neutral-subtle",
"--ac-chat-top-text": "--a-text-default"
},
"chips": {
@@ -372,6 +372,21 @@
"--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"
},
"select": {
"--ac-select-bg": "--a-surface-default",
"--ac-select-text": "--a-text-default",
File diff suppressed because it is too large. Load diff
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-react",
"version": "4.1.2",
"version": "4.7.2",
"description": "Aksel react-components for NAV designsystem",
"author": "Aksel | NAV designsystem team",
"license": "MIT",
@@ -38,7 +38,7 @@
},
"dependencies": {
"@floating-ui/react": "0.24.1",
"@navikt/aksel-icons": "^4.1.2",
"@navikt/aksel-icons": "^4.7.2",
"@radix-ui/react-tabs": "1.0.0",
"@radix-ui/react-toggle-group": "1.0.0",
"clsx": "^1.2.1",
+49 -18
View File
@@ -3,10 +3,12 @@ import {
CheckmarkCircleFillIcon,
ExclamationmarkTriangleFillIcon,
XMarkOctagonFillIcon,
XMarkIcon,
} from "@navikt/aksel-icons";
import cl from "clsx";
import React, { forwardRef } from "react";
import { BodyLong } from "../typography/BodyLong";
import { Button } from "../button";
export interface AlertProps extends React.HTMLAttributes<HTMLDivElement> {
/**
@@ -32,6 +34,17 @@ export interface AlertProps extends React.HTMLAttributes<HTMLDivElement> {
* @default false
*/
inline?: boolean;
/**
* Removes close-button(X) when false
* Requires onClose to be set
* @default true
*/
closeButton?: boolean;
/**
* Callback for alert wanting to close
* requires closeButton to be true
*/
onClose?: () => void;
}
const Icon = ({ variant, ...props }) => {
@@ -71,27 +84,45 @@ export const Alert = forwardRef<HTMLDivElement, AlertProps>(
size = "medium",
fullWidth = false,
inline = false,
closeButton = false,
onClose,
...rest
},
ref
) => (
<div
{...rest}
ref={ref}
className={cl(
className,
"navds-alert",
`navds-alert--${variant}`,
`navds-alert--${size}`,
{ "navds-alert--full-width": fullWidth, "navds-alert--inline": inline }
)}
>
<Icon variant={variant} className="navds-alert__icon" />
<BodyLong as="div" size={size} className="navds-alert__wrapper">
{children}
</BodyLong>
</div>
)
) => {
return (
<div
{...rest}
ref={ref}
className={cl(
className,
"navds-alert",
`navds-alert--${variant}`,
`navds-alert--${size}`,
{
"navds-alert--full-width": fullWidth,
"navds-alert--inline": inline,
}
)}
>
<Icon variant={variant} className="navds-alert__icon" />
<BodyLong as="div" size={size} className="navds-alert__wrapper">
{children}
</BodyLong>
{closeButton && !inline && (
<div className="navds-alert__button-wrapper">
<Button
className="navds-alert__button"
size="small"
variant="tertiary-neutral"
onClick={onClose}
icon={<XMarkIcon title="Lukk Alert" />}
/>
</div>
)}
</div>
);
}
);
export default Alert;
+75 -22
View File
@@ -2,6 +2,8 @@ import type { Meta, StoryObj } from "@storybook/react";
import React from "react";
import { Alert } from ".";
import { BodyLong, Heading as DsHeading, Link } from "..";
import { within, userEvent } from "@storybook/testing-library";
import { expect } from "@storybook/jest";
const meta: Meta<typeof Alert> = {
title: "ds-react/Alert",
@@ -20,22 +22,14 @@ const variants: Array<"error" | "warning" | "info" | "success"> = [
];
export const Default: Story = {
render: (props) => (
<Alert
variant={props.variant}
size={props.size}
fullWidth={props.fullWidth}
inline={props.inline}
>
{props.children}
</Alert>
),
render: (props) => <Alert {...props} />,
args: {
children: "Id elit esse enim reprehenderit enim nisi veniam nostrud.",
fullWidth: false,
variant: "info",
size: "medium",
closeButton: false,
},
argTypes: {
variant: {
@@ -50,21 +44,39 @@ export const Default: Story = {
},
options: ["medium", "small"],
},
closeButton: {
type: "boolean",
},
},
};
export const Small = () => {
return (
<div className="colgap">
{variants.map((variant, i) => (
<Alert key={variant} variant={variant} size="small">
{new Array(i + 1).fill(
"Id elit esse enim reprehenderit enim nisi veniam nostrud."
)}
</Alert>
))}
</div>
);
export const Small = {
render: (props) => {
return (
<div className="colgap">
{variants.map((variant, i) => (
<Alert
key={variant}
variant={variant}
size="small"
closeButton={props.closeButton}
>
{new Array(i + 1).fill(
"Id elit esse enim reprehenderit enim nisi veniam nostrud."
)}
</Alert>
))}
</div>
);
},
args: {
closeButton: false,
},
argtypes: {
closeButton: {
type: "boolean",
},
},
};
export const FullWidth = () => {
@@ -147,3 +159,44 @@ export const Links = () => {
</div>
);
};
const AlertWithCloseButton = ({ children }: { children?: React.ReactNode }) => {
let [show, setShow] = React.useState(true);
return (
show && (
<Alert variant="success" closeButton onClose={() => setShow(false)}>
{children || "Content"}
</Alert>
)
);
};
export const WithCloseButton: Story = {
render: () => {
return (
<div className="colgap">
<AlertWithCloseButton />
<AlertWithCloseButton>
<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>
<Link href="#">Id elit esse enim reprehenderit</Link>
</AlertWithCloseButton>
</div>
);
},
play: async ({ canvasElement, step }) => {
const canvas = within(canvasElement);
const buttons = canvas.getAllByTitle("Lukk Alert");
await step("click button", async () => {
await userEvent.click(buttons[0]);
});
const buttonsAfter = canvas.getAllByTitle("Lukk Alert");
expect(buttonsAfter.length).toBe(1);
},
};
+9 -5
View File
@@ -17,6 +17,7 @@ export interface BubbleProps extends HTMLAttributes<HTMLDivElement> {
timestamp?: string;
/**
* Background color on bubble
* @deprecated Use `variant` on Chat instead
*/
backgroundColor?: string;
/**
@@ -46,17 +47,20 @@ const Bubble = forwardRef<HTMLDivElement, BubbleProps>(
{...rest}
>
{(timestamp || name) && (
<div
<h3
className={cl(
`navds-chat__top-text`,
toptextPosition && `navds-chat__top-text--${toptextPosition}`
)}
>
{name && <Detail className="navds-chat__name">{name}</Detail>}
{timestamp && (
<Detail className="navds-chat__timestamp">{timestamp}</Detail>
{name && <Detail as="span">{name}</Detail>}
{name && timestamp && (
<Detail as="span" aria-hidden>
&bull;
</Detail>
)}
</div>
{timestamp && <Detail as="span">{timestamp}</Detail>}
</h3>
)}
{children}
</div>
+70 -52
View File
@@ -1,7 +1,10 @@
import React, { forwardRef, HTMLAttributes } from "react";
import cl from "clsx";
import Bubble, { BubbleProps } from "./Bubble";
import { BodyLong, BodyShort } from "../typography";
import Bubble from "./Bubble";
import { BodyLong } from "../typography";
export const POSITIONS = ["left", "right"] as const;
export const SIZES = ["medium", "small"] as const;
export interface ChatProps extends HTMLAttributes<HTMLDivElement> {
/**
@@ -17,27 +20,40 @@ export interface ChatProps extends HTMLAttributes<HTMLDivElement> {
*/
timestamp?: string;
/**
* Avatar for messenger. Regular text for initials works to
* Avatar for messenger. Regular text for initials works too, but it will be hidden for screen readers.
*/
avatar: React.ReactNode;
avatar?: React.ReactNode;
/**
* Changes background color on avatar and bubbles.
* Avoid using the same background as the surface behind Chat.
* @default "neutral"
*/
variant?: "subtle" | "info" | "neutral";
/**
* Background color on bubbles
* @deprecated Use `variant` instead
*/
backgroundColor?: string;
/**
* Background color for avatar
* @deprecated Use `variant` instead
*/
avatarBgColor?: string;
/**
* Positions avatar and Bubbles
* Positions avatar and bubbles
* @default "left"
*/
position?: "left" | "right";
position?: (typeof POSITIONS)[number];
/**
* Hoizontal position of toptext
* @default Same as chat
* @default Same as position
*/
toptextPosition?: "left" | "right";
toptextPosition?: (typeof POSITIONS)[number];
/**
* Affects padding and font size in bubbles
* @default "medium"
*/
size?: (typeof SIZES)[number];
}
interface ChatComponent
@@ -47,24 +63,23 @@ interface ChatComponent
/**
* @see 🏷️ {@link BubbleProps}
*/
Bubble: React.ForwardRefExoticComponent<
BubbleProps & React.RefAttributes<HTMLDivElement>
>;
Bubble: typeof Bubble;
}
/**
* A component for displaying chat messages.
* A component for communicating dialogs between two parties.
*
* @see [📝 Documentation](https://aksel.nav.no/komponenter/core/chat)
* @see 🏷️ {@link ChatProps}
*
* @example
* ```jsx
* <Chat>
* <Chat.Bubble avatar="A" name="Alice">Hello!</Chat.Bubble>
* <Chat avatar="A" name="Alice" timestamp="01.01.21 14:00">
* <Chat.Bubble>Hello!</Chat.Bubble>
* <Chat.Bubble>How can I help you?</Chat.Bubble>
* </Chat>
* <Chat>
* <Chat.Bubble avatar="B" name="Bob">Hi there!</Chat.Bubble>
* <Chat avatar="B" name="Bob" timestamp="01.01.21 14:01" position="right">
* <Chat.Bubble>Hi there!</Chat.Bubble>
* </Chat>
* ```
*/
@@ -77,51 +92,54 @@ export const Chat = forwardRef<HTMLDivElement, ChatProps>(
timestamp,
avatar,
position = "left",
variant = "neutral",
avatarBgColor,
backgroundColor,
toptextPosition,
size = "medium",
...rest
},
}: ChatProps,
ref
) => {
return (
<div
ref={ref}
className={cl(
"navds-chat",
className,
`navds-chat--${position} navds-chat--top-text-${
toptextPosition ?? position
}`
)}
{...rest}
>
<BodyShort
as="div"
) => (
<div
ref={ref}
className={cl(
"navds-chat",
className,
`navds-chat--${position}`,
`navds-chat--top-text-${toptextPosition ?? position}`,
`navds-chat--${size}`,
`navds-chat--${variant}`
)}
{...rest}
>
{avatar && (
<div
className="navds-chat__avatar"
aria-hidden
style={{ backgroundColor: avatarBgColor }}
>
{avatar}
</BodyShort>
<ol className="navds-chat__bubble-wrapper">
{React.Children.map(children, (child, i) => {
if (React.isValidElement(child)) {
return (
<BodyLong as="li">
{React.cloneElement(child, {
name: name && i === 0 ? name : undefined,
timestamp: timestamp && i === 0 ? timestamp : undefined,
backgroundColor,
...child.props,
})}
</BodyLong>
);
}
})}
</ol>
</div>
);
}
</div>
)}
<ol className="navds-chat__bubble-wrapper">
{React.Children.map(children, (child, i) => {
if (React.isValidElement(child)) {
return (
<BodyLong as="li" size={size}>
{React.cloneElement(child, {
name: name && i === 0 ? name : undefined,
timestamp: timestamp && i === 0 ? timestamp : undefined,
backgroundColor,
...child.props,
})}
</BodyLong>
);
}
})}
</ol>
</div>
)
) as ChatComponent;
Chat.Bubble = Bubble;
+150 -128
View File
@@ -1,6 +1,7 @@
import React from "react";
import { Chat } from "../index";
import { Meta } from "@storybook/react";
import { POSITIONS, SIZES } from "./Chat";
export default {
title: "ds-react/Chat",
@@ -16,19 +17,25 @@ export default {
type: "text",
},
},
position: {
control: { type: "radio" },
options: POSITIONS,
},
toptextPosition: {
control: { type: "radio" },
options: POSITIONS,
},
size: {
control: { type: "radio" },
options: SIZES,
},
},
} as Meta;
} satisfies Meta<typeof Chat>;
export const Default = {
render: (props) => {
return (
<Chat
avatar={props?.avatar ?? "ON"}
name={props?.name ?? "Ola Normann"}
timestamp={props?.timestamp ?? "01.01.21 14:00"}
avatarBgColor={props?.avatarBgColor}
backgroundColor={props?.backgroundColor}
>
<Chat {...props}>
<Chat.Bubble>
Aute minim nisi sunt mollit duis sunt nulla minim non proident.
</Chat.Bubble>
@@ -48,141 +55,156 @@ export const Default = {
},
};
export const Farger = () => {
return (
export const Small = () => (
<Chat avatar="ON" name="Ola Normann" timestamp="01.01.21 14:00" size="small">
<Chat.Bubble>
Aute minim nisi sunt mollit duis sunt nulla minim non proident.
</Chat.Bubble>
<Chat.Bubble>Tempor fugiat amet eu sint in in ullamco.</Chat.Bubble>
<Chat.Bubble>
Adipisicing laborum est eu laborum est sit in commodo enim sint laboris
labore nisi ut.
</Chat.Bubble>
</Chat>
);
export const Colors = () => (
<Chat
avatar="NAV"
name="NAV"
timestamp="01.01.21 14:00"
avatarBgColor="var(--a-surface-alt-1-subtle)"
backgroundColor="var(--a-surface-warning-subtle)"
>
<Chat.Bubble>
Aute minim nisi sunt mollit duis sunt nulla minim non proident.
</Chat.Bubble>
<Chat.Bubble backgroundColor="lightYellow">
Tempor fugiat amet eu sint in in ullamco.
</Chat.Bubble>
<Chat.Bubble>
Adipisicing laborum est eu laborum est sit in commodo enim sint laboris
labore nisi ut.
</Chat.Bubble>
</Chat>
);
export const Variants = () => (
<div className="colgap">
<Chat avatar="NAV" name="NAV" timestamp="01.01.21 14:00" variant="info">
<Chat.Bubble>
Aute minim nisi sunt mollit duis sunt nulla minim non proident.
</Chat.Bubble>
</Chat>
<Chat
avatar="KN"
name="Kari Nordmann"
timestamp="01.01.21 14:03"
variant="neutral"
position="right"
>
<Chat.Bubble>Tempor fugiat amet eu sint in in ullamco.</Chat.Bubble>
</Chat>
<Chat
avatar="ON"
name="Ola Nordmann"
timestamp="01.01.21 14:07"
variant="subtle"
position="right"
>
<Chat.Bubble>
Adipisicing laborum est eu laborum est sit in commodo enim sint laboris
labore nisi ut.
</Chat.Bubble>
</Chat>
</div>
);
export const Position = () => (
<div className="colgap">
<Chat avatar="ON" name="Ola Normann" timestamp="01.01.21 14:00">
<Chat.Bubble>
Adipisicing laborum est eu laborum est sit in commodo enim sint laboris
labore nisi ut.
</Chat.Bubble>
</Chat>
<Chat
avatar="KH"
name="Kari Høyli"
timestamp="01.01.21 14:32"
position="right"
variant="info"
>
<Chat.Bubble>
Reprehenderit pariatur officia exercitation laboris.
</Chat.Bubble>
<Chat.Bubble>
Enim velit deserunt do quis. Eu fugiat magna esse dolore ad sunt sit est
dolore incididunt. Occaecat cupidatat magna.
</Chat.Bubble>
</Chat>
<Chat avatar="ON" name="Ola Normann" timestamp="01.01.21 15:00">
<Chat.Bubble>
Exercitation irure Lorem cupidatat culpa anim cillum esse ullamco qui
dolore laborum et Lorem. Labore sunt duis id Lorem voluptate commodo ea
esse. Dolore esse aliqua proident ea ad commodo ut dolore voluptate
labore sunt aute.
</Chat.Bubble>
</Chat>
</div>
);
export const ToptextPosition = () => (
<div className="colgap">
<Chat
avatar="ON"
name="Ola Normann"
timestamp="01.01.21 14:00"
avatarBgColor="var(--a-lightblue-200)"
backgroundColor="var(--a-lightblue-100)"
toptextPosition="right"
>
<Chat.Bubble>
Aute minim nisi sunt mollit duis sunt nulla minim non proident.
</Chat.Bubble>
<Chat.Bubble>Tempor fugiat amet eu sint in in ullamco.</Chat.Bubble>
<Chat.Bubble>
Adipisicing laborum est eu laborum est sit in commodo enim sint laboris
labore nisi ut.
</Chat.Bubble>
</Chat>
);
};
export const Position = () => {
return (
<div className="colgap">
<Chat
avatar="ON"
name="Ola Normann"
timestamp="01.01.21 14:00"
backgroundColor="rgba(255, 249, 240, 1)"
avatarBgColor="rgba(255, 236, 204, 1)"
>
<Chat.Bubble>
Adipisicing laborum est eu laborum est sit in commodo enim sint
laboris labore nisi ut.
</Chat.Bubble>
</Chat>
<Chat
avatar="KH"
name="Kari Høyli"
timestamp="01.01.21 14:32"
position="right"
backgroundColor="rgba(230, 240, 255, 1)"
avatarBgColor="rgba(204, 225, 255, 1)"
>
<Chat.Bubble>
Reprehenderit pariatur officia exercitation laboris.
</Chat.Bubble>
<Chat.Bubble>
Enim velit deserunt do quis. Eu fugiat magna esse dolore ad sunt sit
est dolore incididunt. Occaecat cupidatat magna.
</Chat.Bubble>
</Chat>
<Chat
avatar="ON"
name="Ola Normann"
timestamp="01.01.21 15:00"
backgroundColor="rgba(255, 249, 240, 1)"
avatarBgColor="rgba(255, 236, 204, 1)"
>
<Chat.Bubble>
Exercitation irure Lorem cupidatat culpa anim cillum esse ullamco qui
dolore laborum et Lorem. Labore sunt duis id Lorem voluptate commodo
ea esse. Dolore esse aliqua proident ea ad commodo ut dolore voluptate
labore sunt aute.
</Chat.Bubble>
</Chat>
</div>
);
};
export const ToptextPosition = () => {
return (
<div className="colgap">
<Chat
avatar="ON"
name="Ola Normann"
timestamp="01.01.21 14:00"
backgroundColor="rgba(255, 249, 240, 1)"
avatarBgColor="rgba(255, 236, 204, 1)"
toptextPosition="right"
>
<Chat.Bubble>
Adipisicing laborum est eu laborum est sit in commodo enim sint
laboris labore nisi ut.
</Chat.Bubble>
</Chat>
<Chat
avatar="KH"
name="Kari Høyli"
timestamp="01.01.21 14:32"
position="right"
backgroundColor="rgba(230, 240, 255, 1)"
avatarBgColor="rgba(204, 225, 255, 1)"
toptextPosition="left"
>
<Chat.Bubble>
Reprehenderit pariatur officia exercitation laboris.
</Chat.Bubble>
<Chat.Bubble>
Enim velit deserunt do quis. Eu fugiat magna esse dolore ad sunt sit
est dolore incididunt. Occaecat cupidatat magna.
</Chat.Bubble>
</Chat>
<Chat
avatar="ON"
name="Ola Normann"
timestamp="01.01.21 15:00"
backgroundColor="rgba(255, 249, 240, 1)"
avatarBgColor="rgba(255, 236, 204, 1)"
>
<Chat.Bubble toptextPosition="right">
Exercitation irure Lorem cupidatat culpa anim cillum esse ullamco qui
dolore laborum et Lorem. Labore sunt duis id Lorem voluptate commodo
ea esse. Dolore esse aliqua proident ea ad commodo ut dolore voluptate
labore sunt aute.
</Chat.Bubble>
</Chat>
</div>
);
};
export const Avatar = () => {
return (
<Chat avatar={<Illustration />} timestamp="01.01.21 14:00">
<Chat
name="Kari Høyli"
timestamp="01.01.21 14:32"
position="right"
variant="info"
toptextPosition="left"
>
<Chat.Bubble>
Aute minim nisi sunt mollit duis sunt nulla minim non proident.
Reprehenderit pariatur officia exercitation laboris.
</Chat.Bubble>
<Chat.Bubble>Tempor fugiat amet eu sint in in ullamco.</Chat.Bubble>
<Chat.Bubble>
Adipisicing laborum est eu laborum est sit in commodo enim sint laboris
labore nisi ut.
Enim velit deserunt do quis. Eu fugiat magna esse dolore ad sunt sit est
dolore incididunt. Occaecat cupidatat magna.
</Chat.Bubble>
</Chat>
);
};
<Chat avatar="ON" name="Ola Normann" timestamp="01.01.21 15:00">
<Chat.Bubble toptextPosition="right">
Exercitation irure Lorem cupidatat culpa anim cillum esse ullamco qui
dolore laborum et Lorem. Labore sunt duis id Lorem voluptate commodo ea
esse. Dolore esse aliqua proident ea ad commodo ut dolore voluptate
labore sunt aute.
</Chat.Bubble>
</Chat>
</div>
);
export const Avatar = () => (
<Chat avatar={<Illustration />} timestamp="01.01.21 14:00">
<Chat.Bubble>
Aute minim nisi sunt mollit duis sunt nulla minim non proident.
</Chat.Bubble>
<Chat.Bubble>Tempor fugiat amet eu sint in in ullamco.</Chat.Bubble>
<Chat.Bubble>
Adipisicing laborum est eu laborum est sit in commodo enim sint laboris
labore nisi ut.
</Chat.Bubble>
</Chat>
);
const Illustration = () => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 54 93">
+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>
);
+9 -2
View File
@@ -4,6 +4,7 @@ import React, { forwardRef, InputHTMLAttributes } from "react";
import { BodyShort, ErrorMessage, Label, omit } from "..";
import { FormFieldProps, useFormField } from "../form/useFormField";
import { useDateInputContext } from "./context";
import { ReadOnlyIcon } from "../form/ReadOnlyIcon";
export interface DateInputProps
extends FormFieldProps,
@@ -57,6 +58,7 @@ const DateInput = forwardRef<HTMLInputElement, DateInputProps>((props, ref) => {
errorId,
showErrorMsg,
hasError,
readOnly,
} = useFormField(props, conditionalVariables.prefix);
return (
@@ -71,6 +73,9 @@ 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,
}
)}
>
@@ -81,6 +86,7 @@ const DateInput = forwardRef<HTMLInputElement, DateInputProps>((props, ref) => {
"navds-sr-only": hideLabel,
})}
>
<ReadOnlyIcon readOnly={readOnly} />
{label}
</Label>
{!!description && (
@@ -102,6 +108,7 @@ const DateInput = forwardRef<HTMLInputElement, DateInputProps>((props, ref) => {
{...inputProps}
autoComplete="off"
aria-controls={ariaId}
readOnly={readOnly}
className={cl(
"navds-date__field-input",
"navds-text-field__input",
@@ -111,8 +118,8 @@ const DateInput = forwardRef<HTMLInputElement, DateInputProps>((props, ref) => {
size={14}
/>
<button
disabled={inputProps.disabled}
tabIndex={open ? -1 : 0}
disabled={inputProps.disabled || readOnly}
tabIndex={readOnly ? -1 : open ? -1 : 0}
onClick={() => onOpen()}
type="button"
className="navds-date__field-button"
@@ -114,6 +114,11 @@ export interface DatePickerDefaultProps
* @default "absolute"
*/
strategy?: "absolute" | "fixed";
/**
* Bubbles Escape keydown-event up trough DOM-tree. This is set to false by default to prevent closing components like Modal on Escape
* @default false
*/
bubbleEscape?: boolean;
}
export type DatePickerProps = DatePickerDefaultProps & ConditionalModeProps;
@@ -180,6 +185,7 @@ export const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>(
onClose,
onOpenToggle,
strategy = "absolute",
bubbleEscape = false,
...rest
},
ref
@@ -251,6 +257,8 @@ export const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>(
role="dialog"
ref={ref}
strategy={strategy}
className="navds-date__popover"
bubbleEscape={bubbleEscape}
>
<DayPicker
locale={getLocaleFromString(locale)}
@@ -341,3 +341,25 @@ export const Size = () => {
</div>
);
};
export const Readonly = () => {
const { datepickerProps, inputProps } = useDatepicker({
fromDate: new Date("Aug 23 2019"),
toDate: new Date("Feb 23 2024"),
onDateChange: console.log,
});
return (
<div style={{ display: "flex", gap: "1rem" }}>
<DatePicker {...datepickerProps} dropdownCaption>
<DatePicker.Input
size="medium"
{...inputProps}
value="01.02.2021"
label="Velg dato"
readOnly
/>
</DatePicker>
</div>
);
};
@@ -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]
);
@@ -204,6 +203,9 @@ export const useDatepicker = (
};
const handleFocus: React.FocusEventHandler<HTMLInputElement> = (e) => {
if (e.target.readOnly) {
return;
}
!open && openOnFocus && handleOpen(true);
let day = parseDate(
e.target.value,
@@ -319,6 +321,7 @@ export const useDatepicker = (
onOpenToggle: () => handleOpen(!open),
disabled,
disableWeekends,
bubbleEscape: true,
ref: setDaypickerRef,
};
@@ -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]
);
@@ -201,6 +200,9 @@ export const useMonthpicker = (
};
const handleFocus: React.FocusEventHandler<HTMLInputElement> = (e) => {
if (e.target.readOnly) {
return;
}
!open && openOnFocus && handleOpen(true);
let day = parseDate(
e.target.value,
@@ -312,6 +314,7 @@ export const useMonthpicker = (
open,
onOpenToggle: () => handleOpen(!open),
disabled,
bubbleEscape: true,
ref: setMonthpickerRef,
};
@@ -321,6 +321,9 @@ export const useRangeDatepicker = (
};
const handleFocus = (e, src: RangeT) => {
if (e.target.readOnly) {
return;
}
!open && openOnFocus && setOpen(true);
let day = parseDate(
e.target.value,
@@ -544,6 +547,7 @@ export const useRangeDatepicker = (
onOpenToggle: () => setOpen((x) => !x),
disabled,
disableWeekends,
bubbleEscape: true,
ref: setDaypickerRef,
};
@@ -86,6 +86,11 @@ export interface MonthPickerProps extends React.HTMLAttributes<HTMLDivElement> {
* @default "absolute"
*/
strategy?: "absolute" | "fixed";
/**
* Bubbles Escape keydown-event up trough DOM-tree. This is set to false by default to prevent closing components like Modal on Escape
* @default false
*/
bubbleEscape?: boolean;
}
interface MonthPickerComponent
@@ -153,6 +158,7 @@ export const MonthPicker = forwardRef<HTMLDivElement, MonthPickerProps>(
year,
onYearChange,
strategy = "absolute",
bubbleEscape = false,
},
ref
) => {
@@ -202,8 +208,9 @@ export const MonthPicker = forwardRef<HTMLDivElement, MonthPickerProps>(
role="dialog"
ref={ref}
id={ariaId}
className="navds-date"
className="navds-date navds-date__popover"
strategy={strategy}
bubbleEscape={bubbleEscape}
>
<RootProvider
locale={getLocaleFromString(locale)}
@@ -6,7 +6,7 @@ import { useFormField } from "./useFormField";
export interface ConfirmationPanelProps
extends Omit<
CheckboxProps,
"children" | "indeterminate" | "hideLabel" | "error"
"children" | "indeterminate" | "hideLabel" | "error" | "readOnly"
> {
/**
* Additional information on panel
@@ -3,6 +3,7 @@ import React, { FieldsetHTMLAttributes, forwardRef, useContext } from "react";
import { BodyShort, ErrorMessage, Label, omit } from "../..";
import { FormFieldProps } from "../useFormField";
import { useFieldset } from "./useFieldset";
import { ReadOnlyIcon } from "../ReadOnlyIcon";
export type FieldsetContextProps = {
/**
@@ -21,6 +22,10 @@ export type FieldsetContextProps = {
* Sets fieldset and all form-children to disabled
*/
disabled: boolean;
/**
* Read only-state
*/
readOnly?: boolean;
};
export const FieldsetContext = React.createContext<FieldsetContextProps | null>(
@@ -47,6 +52,7 @@ export interface FieldsetProps
* @default true
*/
errorPropagation?: boolean;
nativeReadOnly?: boolean;
}
export const Fieldset = forwardRef<HTMLFieldSetElement, FieldsetProps>(
@@ -57,6 +63,7 @@ export const Fieldset = forwardRef<HTMLFieldSetElement, FieldsetProps>(
showErrorMsg,
hasError,
size,
readOnly,
inputDescriptionId,
} = useFieldset(props);
@@ -69,6 +76,7 @@ export const Fieldset = forwardRef<HTMLFieldSetElement, FieldsetProps>(
legend,
description,
hideLegend,
nativeReadOnly = true,
...rest
} = props;
@@ -82,17 +90,21 @@ export const Fieldset = forwardRef<HTMLFieldSetElement, FieldsetProps>(
}),
size,
disabled: props.disabled ?? false,
readOnly,
}}
>
<fieldset
{...omit(rest, ["errorId", "error", "size"])}
{...omit(rest, ["errorId", "error", "size", "readOnly"])}
{...inputProps}
ref={ref}
className={cl(
className,
"navds-fieldset",
`navds-fieldset--${size}`,
{ "navds-fieldset--error": hasError }
{
"navds-fieldset--error": hasError,
"navds-fieldset--readonly": readOnly,
}
)}
>
<Label
@@ -102,6 +114,7 @@ export const Fieldset = forwardRef<HTMLFieldSetElement, FieldsetProps>(
"navds-sr-only": !!hideLegend,
})}
>
<ReadOnlyIcon readOnly={readOnly} nativeReadOnly={nativeReadOnly} />
{legend}
</Label>
{!!description && (
@@ -0,0 +1,20 @@
import { PadlockLockedFillIcon } from "@navikt/aksel-icons";
import React from "react";
export const ReadOnlyIcon = ({
readOnly,
nativeReadOnly = true,
}: {
readOnly?: boolean;
nativeReadOnly?: boolean;
}) => {
if (readOnly) {
return (
<PadlockLockedFillIcon
{...(nativeReadOnly ? { "aria-hidden": true } : { title: "readonly" })}
className="navds-form-field__readonly-icon"
/>
);
}
return null;
};
+29 -2
View File
@@ -4,6 +4,7 @@ import React, { forwardRef, SelectHTMLAttributes } from "react";
import { ChevronDownIcon } from "@navikt/aksel-icons";
import { BodyShort, ErrorMessage, Label, omit } from "..";
import { FormFieldProps, useFormField } from "./useFormField";
import { ReadOnlyIcon } from "./ReadOnlyIcon";
export interface SelectProps
extends FormFieldProps,
@@ -55,7 +56,8 @@ export const Select = forwardRef<HTMLSelectElement, SelectProps>(
hasError,
size,
inputDescriptionId,
} = useFormField(props, "textField");
readOnly,
} = useFormField(props, "select");
const {
children,
@@ -68,6 +70,27 @@ export const Select = forwardRef<HTMLSelectElement, SelectProps>(
...rest
} = props;
const readOnlyEventHandlers = {
onMouseDown: (evt) => {
// NOTE: does not prevent click
if (readOnly) {
evt.preventDefault();
// focus on the element as per readonly input behavior
evt.target.focus();
}
},
onKeyDown: (evt) => {
if (
readOnly &&
["ArrowDown", "ArrowUp", "ArrowRight", "ArrowLeft", " "].includes(
evt.key
)
) {
evt.preventDefault();
}
},
};
return (
<div
className={cl(
@@ -76,7 +99,9 @@ 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,
}
)}
>
@@ -87,6 +112,7 @@ export const Select = forwardRef<HTMLSelectElement, SelectProps>(
"navds-sr-only": hideLabel,
})}
>
<ReadOnlyIcon readOnly={readOnly} nativeReadOnly={false} />
{label}
</Label>
{!!description && (
@@ -103,8 +129,9 @@ export const Select = forwardRef<HTMLSelectElement, SelectProps>(
)}
<div className="navds-select__container" style={style}>
<select
{...omit(rest, ["error", "errorId", "size"])}
{...omit(rest, ["error", "errorId", "size", "readOnly"])}
{...inputProps}
{...readOnlyEventHandlers}
ref={ref}
className={cl(
"navds-select__input",
+20 -9
View File
@@ -7,6 +7,7 @@ import React, {
} from "react";
import { BodyShort, Loader, omit } from "..";
import { FormFieldProps, useFormField } from "./useFormField";
import { ReadOnlyIcon } from "./ReadOnlyIcon";
const SelectedIcon = () => (
<svg
@@ -63,12 +64,12 @@ export interface SwitchProps
*
* @example
* ```jsx
* <Switch>Slå på notifikasjoner</Switch>
* <Switch>Varsle med SMS</Switch>
* ```
*/
export const Switch = forwardRef<HTMLInputElement, SwitchProps>(
(props, ref) => {
const { inputProps, size } = useFormField(props, "switch");
const { inputProps, size, readOnly } = useFormField(props, "switch");
const {
children,
@@ -90,11 +91,6 @@ export const Switch = forwardRef<HTMLInputElement, SwitchProps>(
checkedProp !== undefined && setChecked(checkedProp);
}, [checkedProp]);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setChecked(e.target.checked);
props.onChange && props.onChange(e);
};
return (
<div
className={cl(
@@ -105,18 +101,32 @@ export const Switch = forwardRef<HTMLInputElement, SwitchProps>(
{
"navds-switch--loading": loading,
"navds-switch--disabled": inputProps.disabled ?? loading,
"navds-switch--readonly": readOnly,
}
)}
>
<input
{...omit(rest, ["size"])}
{...omit(rest, ["size", "readOnly"])}
{...omit(inputProps, ["aria-invalid", "aria-describedby"])}
disabled={inputProps.disabled ?? loading}
checked={checkedProp}
defaultChecked={defaultChecked}
ref={ref}
type="checkbox"
onChange={(e) => handleChange(e)}
onChange={(e) => {
if (readOnly) {
return;
}
setChecked(e.target.checked);
props.onChange && props.onChange(e);
}}
onClick={(e) => {
if (readOnly) {
e.preventDefault();
return;
}
props?.onClick?.(e);
}}
className={cl(className, "navds-switch__input")}
/>
<span className="navds-switch__track">
@@ -136,6 +146,7 @@ export const Switch = forwardRef<HTMLInputElement, SwitchProps>(
})}
>
<BodyShort as="div" size={size} className="navds-switch__label">
<ReadOnlyIcon readOnly={readOnly} nativeReadOnly={false} />
{children}
</BodyShort>
{description && (
@@ -1,6 +1,7 @@
import cl from "clsx";
import React, { forwardRef, InputHTMLAttributes } from "react";
import { BodyShort, ErrorMessage, Label, omit } from "..";
import { ReadOnlyIcon } from "./ReadOnlyIcon";
import { FormFieldProps, useFormField } from "./useFormField";
export interface TextFieldProps
@@ -62,6 +63,7 @@ export const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
htmlSize,
hideLabel = false,
type = "text",
readOnly,
...rest
} = props;
@@ -71,10 +73,13 @@ 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,
}
)}
>
@@ -85,6 +90,7 @@ export const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
"navds-sr-only": hideLabel,
})}
>
<ReadOnlyIcon readOnly={readOnly} />
{label}
</Label>
@@ -105,6 +111,7 @@ export const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
{...inputProps}
ref={ref}
type={type}
readOnly={readOnly}
className={cl(
"navds-text-field__input",
"navds-body-short",
+8 -2
View File
@@ -3,6 +3,7 @@ import React, { forwardRef, useState } from "react";
import { BodyShort, ErrorMessage, Label, omit, useId } from "..";
import TextareaAutosize from "../util/TextareaAutoSize";
import { FormFieldProps, useFormField } from "./useFormField";
import { ReadOnlyIcon } from "./ReadOnlyIcon";
/**
* TODO: Mulighet for lokalisering av sr-only/counter text
@@ -85,6 +86,7 @@ export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
hideLabel = false,
resize,
i18n,
readOnly,
...rest
} = props;
@@ -115,6 +117,8 @@ 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,
}
@@ -127,6 +131,7 @@ export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
"navds-sr-only": hideLabel,
})}
>
<ReadOnlyIcon readOnly={readOnly} />
{label}
</Label>
{!!description && (
@@ -152,6 +157,7 @@ export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
}
minRows={getMinRows()}
ref={ref}
readOnly={readOnly}
className={cl(
"navds-textarea__input",
"navds-body-short",
@@ -203,8 +209,8 @@ export const Counter = ({ maxLength, currentLength, size, i18n }) => {
size={size}
>
{difference < 0
? `${Math.abs(difference)} ${i18n?.counterLeft ?? "tegn for mye"}`
: `${difference} ${i18n?.counterTooMuch ?? "tegn igjen"}`}
? `${Math.abs(difference)} ${i18n?.counterTooMuch ?? "tegn for mye"}`
: `${difference} ${i18n?.counterLeft ?? "tegn igjen"}`}
</BodyShort>
);
};
@@ -4,6 +4,7 @@ import { BodyShort } from "../../typography";
import { omit } from "../../util";
import { FormFieldProps } from "../useFormField";
import useCheckbox from "./useCheckbox";
import { ReadOnlyIcon } from "../ReadOnlyIcon";
export interface CheckboxProps
extends FormFieldProps,
@@ -42,7 +43,7 @@ export interface CheckboxProps
export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
(props, ref) => {
const { inputProps, hasError, size } = useCheckbox(props);
const { inputProps, hasError, size, readOnly, nested } = useCheckbox(props);
return (
<div
@@ -53,6 +54,7 @@ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
{
"navds-checkbox--error": hasError,
"navds-checkbox--disabled": inputProps.disabled,
"navds-checkbox--readonly": readOnly,
}
)}
>
@@ -65,6 +67,7 @@ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
"hideLabel",
"indeterminate",
"errorId",
"readOnly",
])}
{...inputProps}
type="checkbox"
@@ -88,7 +91,14 @@ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
"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} />
)}
{props.children}
</BodyShort>
{props.description && (
@@ -81,6 +81,7 @@ export const CheckboxGroup = forwardRef<
"navds-checkbox-group",
`navds-checkbox-group--${rest.size ?? fieldset?.size ?? "medium"}`
)}
nativeReadOnly={false}
>
<CheckboxGroupContext.Provider
value={{
@@ -1,7 +1,7 @@
/* eslint-disable react-hooks/rules-of-hooks */
import { Meta } from "@storybook/react";
import React, { useState } from "react";
import { Checkbox, CheckboxGroup } from "../../index";
import { Meta } from "@storybook/react";
export default {
title: "ds-react/Checkbox",
@@ -190,3 +190,37 @@ export const Indeterminate = () => {
</>
);
};
export const Readonly = () => (
<div className="colgap">
<CheckboxGroup
legend="Hvilken frukt liker du?"
defaultValue={["banan"]}
readOnly
>
<Checkbox value="banan">Banan</Checkbox>
<Checkbox value="eple">Eple</Checkbox>
<Checkbox value="druer" indeterminate>
Druer
</Checkbox>
</CheckboxGroup>
<CheckboxGroup
legend="Hvilken frukt liker du?"
error="feilmelding"
defaultValue={["Eple"]}
readOnly
>
<Checkbox value="eple" description="Epler kommer i 4 varianter">
Eple
</Checkbox>
<Checkbox value="banan">Banan</Checkbox>
</CheckboxGroup>
<hr />
<Checkbox value="tekst1" readOnly>
Eple single
</Checkbox>
<Checkbox value="tekst1" checked readOnly>
Banan single
</Checkbox>
</div>
);
@@ -10,7 +10,7 @@ import { omit } from "../..";
const useCheckbox = ({ children, ...props }: CheckboxProps) => {
const checkboxGroup = useContext(CheckboxGroupContext);
const { inputProps, ...rest } = useFormField(
const { inputProps, readOnly, ...rest } = useFormField(
omit(props, ["description"]),
"checkbox"
);
@@ -30,6 +30,8 @@ const useCheckbox = ({ children, ...props }: CheckboxProps) => {
return {
...rest,
readOnly,
nested: !!checkboxGroup,
inputProps: {
...inputProps,
checked: checkboxGroup?.value
@@ -39,9 +41,19 @@ const useCheckbox = ({ children, ...props }: CheckboxProps) => {
? checkboxGroup.defaultValue.includes(props.value)
: props.defaultChecked,
onChange: (e) => {
if (readOnly) {
return;
}
props.onChange && props.onChange(e);
checkboxGroup && checkboxGroup.toggleValue(props.value);
},
onClick: (e) => {
if (readOnly) {
e.preventDefault();
return;
}
props?.onClick?.(e);
},
},
};
};
@@ -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,136 @@
import cl from "clsx";
import React, { forwardRef, useMemo, useRef } from "react";
import { BodyShort, 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,
focusInput,
hasError,
inputDescriptionId,
inputProps,
inputRef,
value,
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>
</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,63 @@
import cl from "clsx";
import React, { useRef } 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);
function onFocusInsideWrapper(e) {
if (
!wrapperRef.current?.contains(e.relatedTarget) &&
toggleListButtonRef?.current !== e.target
) {
toggleIsListOpen(true);
}
}
function onBlurWrapper(e) {
if (!wrapperRef.current?.contains(e.relatedTarget)) {
toggleIsListOpen(false);
}
}
return (
<div
ref={wrapperRef}
className={cl(
className,
"navds-form-field",
`navds-form-field--${inputSize}`,
"navds-search",
{
"navds-search--error": hasError,
"navds-search--disabled": !!inputProps.disabled,
}
)}
onFocus={onFocusInsideWrapper}
onBlur={onBlurWrapper}
>
{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,106 @@
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)}
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) {
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,266 @@
import React, {
useState,
useEffect,
useMemo,
createContext,
useContext,
useCallback,
useRef,
useLayoutEffect,
} from "react";
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: { 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(() => {
if (!isLoading && filteredOptions.length === 0) {
return `${id}-no-hits`;
} else if ((value && value !== "") || isLoading) {
if (shouldAutocomplete && filteredOptions[0]) {
return `${id}-option-${filteredOptions[0].replace(" ", "-")}`;
} else if (isLoading) {
return `${id}-is-loading`;
}
} else {
return undefined;
}
}, [isLoading, value, 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,170 @@
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 } =
useSelectedOptionsContext();
const {
activeDecendantId,
allowNewValues,
currentOption,
filteredOptions,
toggleIsListOpen,
isListOpen,
filteredOptionsIndex,
moveFocusUp,
moveFocusDown,
ariaDescribedBy,
moveFocusToInput,
moveFocusToEnd,
shouldAutocomplete,
} = useFilteredOptionsContext();
const onEnter = useCallback(
(event: React.KeyboardEvent) => {
if (currentOption) {
event.preventDefault();
// Selecting a value from the dropdown / FilteredOptions
toggleOption(currentOption, event);
clearInput(event);
} 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);
clearInput(event);
}
},
[
allowNewValues,
clearInput,
currentOption,
selectedOptions,
shouldAutocomplete,
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]
);
return (
<input
{...rest}
{...omit(inputProps, ["aria-invalid"])}
ref={ref}
value={value}
onChange={onChangeHandler}
type="text"
role="combobox"
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}
className={cl(
inputClassName,
"navds-combobox__input",
"navds-body-short",
`navds-body-${size}`
)}
/>
);
}
);
export default Input;
@@ -0,0 +1,127 @@
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;
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);
setValue("");
setSearchTerm("");
},
[onClear, setSearchTerm, 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,
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,45 @@
import React from "react";
import { Chips } from "../../..";
import { useSelectedOptionsContext } from "./selectedOptionsContext";
interface SelectedOptionsProps {
selectedOptions?: string[];
size?: "medium" | "small";
children: React.ReactNode;
}
const Option = ({ option }: { option: string }) => {
const { isMultiSelect, removeSelectedOption } = useSelectedOptionsContext();
const onClick = (e) => {
e.stopPropagation();
removeSelectedOption(option);
};
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,147 @@
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 } =
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) => {
if (
!options
.map((opt) => opt.toLowerCase())
.includes(option?.toLowerCase?.())
) {
allowNewValues && addCustomOption(option);
} else if (isMultiSelect) {
setSelectedOptions((prevSelectedOptions) => [
...prevSelectedOptions,
option,
]);
} else {
setSelectedOptions([option]);
}
onToggleSelected?.(option, true);
},
[addCustomOption, allowNewValues, isMultiSelect, onToggleSelected, options]
);
const removeSelectedOption = useCallback(
(option: string) => {
if (customOptions.includes(option)) {
removeCustomOption(option);
} else {
setSelectedOptions((prevSelectedOptions) =>
prevSelectedOptions.filter(
(selectedOption) => selectedOption !== option
)
);
}
onToggleSelected?.(option, false);
},
[customOptions, onToggleSelected, removeCustomOption]
);
const toggleOption = useCallback(
(option: string, event: React.KeyboardEvent | React.PointerEvent) => {
if (selectedOptions.includes(option)) {
removeSelectedOption(option);
} else {
addSelectedOption(option);
}
if (!isMultiSelect) {
clearInput(event);
}
focusInput();
},
[
addSelectedOption,
clearInput,
focusInput,
isMultiSelect,
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,413 @@
/* 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 } 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}
/>
</DemoContainer>
);
}
MultiSelect.args = {
options,
isMultiSelect: true,
};
export function MultiSelectWithAddNewOptions(props) {
const id = useId();
return (
<DemoContainer dataTheme={props.darkMode}>
<UNSAFE_Combobox
id={id}
isMultiSelect={props.isMultiSelect}
label="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,
};
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}");
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);
userEvent.keyboard("{Escape}");
await sleep(250);
const appleSlett = canvas.getByLabelText("apple slett");
userEvent.click(appleSlett);
await sleep(250);
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();
},
};
@@ -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,57 @@
import React, { useState, useCallback, createContext, useContext } from "react";
import { useInputContext } from "./Input/inputContext";
type CustomOptionsContextType = {
customOptions: string[];
removeCustomOption: (option: string) => void;
addCustomOption: (option: string) => void;
};
const CustomOptionsContext = createContext<CustomOptionsContextType>(
{} as CustomOptionsContextType
);
export const CustomOptionsProvider = ({ children }) => {
const [customOptions, setCustomOptions] = useState<string[]>([]);
const { focusInput } = useInputContext();
const removeCustomOption = useCallback(
(option) => {
setCustomOptions((prevCustomOptions) =>
prevCustomOptions.filter((o) => o !== option)
);
focusInput();
},
[focusInput, setCustomOptions]
);
const addCustomOption = useCallback(
(option) => {
setCustomOptions((prevOptions) => [...prevOptions, option]);
focusInput();
},
[focusInput, setCustomOptions]
);
const customOptionsState = {
customOptions,
removeCustomOption,
addCustomOption,
};
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,122 @@
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>) => 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
* @returns
*/
onToggleSelected?: (option: string, isSelected: 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;
}
@@ -29,6 +29,18 @@ interface ErrorSummaryComponent
extends React.ForwardRefExoticComponent<
ErrorSummaryProps & React.RefAttributes<HTMLDivElement>
> {
/**
* Link to error
*
* @see [🤖 OverridableComponent](https://aksel.nav.no/grunnleggende/kode/overridablecomponent) support
*
* @example
* ```jsx
* <ErrorSummary.Item href="#1">
* Felt må fylles ut med alder
* </ErrorSummary.Item>
* ```
*/
Item: ErrorSummaryItemType;
}
@@ -8,6 +8,10 @@ export interface ErrorSummaryItemProps
* Link text
*/
children: React.ReactNode;
/**
* Link to errormessage
*/
href?: string;
}
export type ErrorSummaryItemType = OverridableComponent<
+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";
+4 -3
View File
@@ -6,7 +6,7 @@ import { FormFieldProps } from "../useFormField";
import { useRadio } from "./useRadio";
export interface RadioProps
extends Omit<FormFieldProps, "error" | "errorId">,
extends Omit<FormFieldProps, "error" | "errorId" | "readOnly">,
Omit<InputHTMLAttributes<HTMLInputElement>, "size" | "value"> {
/**
* Radio label
@@ -23,17 +23,18 @@ export interface RadioProps
}
export const Radio = forwardRef<HTMLInputElement, RadioProps>((props, ref) => {
const { inputProps, size, hasError } = useRadio(props);
const { inputProps, size, hasError, readOnly } = useRadio(props);
return (
<div
className={cl(props.className, "navds-radio", `navds-radio--${size}`, {
"navds-radio--error": hasError,
"navds-radio--disabled": inputProps.disabled,
"navds-radio--readonly": readOnly,
})}
>
<input
{...omit(props, ["children", "size", "description"])}
{...omit(props, ["children", "size", "description", "readOnly"])}
{...inputProps}
className="navds-radio__input"
ref={ref}
@@ -65,6 +65,7 @@ export const RadioGroup = forwardRef<HTMLFieldSetElement, RadioGroupProps>(
value,
onChange = () => {},
required,
readOnly,
...rest
},
ref
@@ -76,12 +77,14 @@ export const RadioGroup = forwardRef<HTMLFieldSetElement, RadioGroupProps>(
return (
<Fieldset
{...rest}
readOnly={readOnly}
ref={ref}
className={cl(
className,
"navds-radio-group",
`navds-radio-group--${rest.size ?? fieldset?.size ?? "medium"}`
)}
nativeReadOnly={false}
>
<RadioGroupContext.Provider
value={{
@@ -92,3 +92,30 @@ export const GroupDescription = () => (
<Radio value="tekst2">Radiotekst</Radio>
</RadioGroup>
);
export const UUDemo = () => (
<div className="colgap">
<RadioGroup
legend="Hvilken frukt vil du ha?"
description="Du kan bare velge en frukt"
defaultValue="eple"
readOnly
>
<Radio value="eple">Eple</Radio>
<Radio value="banan" description="Bananer er importert fra X">
Banan
</Radio>
<Radio value="druer">Druer</Radio>
</RadioGroup>
<RadioGroup
legend="Når har du ferie?"
defaultValue="1"
readOnly
error="du må velge en ferie"
>
<Radio value="1">August</Radio>
<Radio value="2">Juli</Radio>
<Radio value="3">Juni</Radio>
</RadioGroup>
</div>
);
+12 -1
View File
@@ -10,7 +10,7 @@ import { omit } from "../..";
export const useRadio = (props: RadioProps) => {
const radioGroup = useContext(RadioGroupContext);
const { inputProps, ...rest } = useFormField(
const { inputProps, readOnly, ...rest } = useFormField(
omit(props, ["description"]),
"radio"
);
@@ -25,6 +25,7 @@ export const useRadio = (props: RadioProps) => {
return {
...rest,
readOnly,
inputProps: {
...inputProps,
name: radioGroup?.name,
@@ -37,9 +38,19 @@ export const useRadio = (props: RadioProps) => {
? undefined
: radioGroup?.value === props.value,
onChange: (e) => {
if (readOnly) {
return;
}
props.onChange && props.onChange(e);
radioGroup?.onChange && radioGroup.onChange(props.value);
},
onClick: (e) => {
if (readOnly) {
e.preventDefault();
return;
}
props?.onClick?.(e);
},
required: radioGroup?.required,
type: "radio",
},
Loaded 100 of 347 files, more files were not shown because too many files have changed in this diff. Show more