Compare commits

...
Author SHA1 Message Date
github-actions[bot]andgithub-actions[bot] 8866572e69 Version Packages (#2686)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-01-31 11:33:47 +01:00
a1accdbd95 ✨ suggest some .vscode workspace settings.json / extensions.json (#2664)
* ✨ suggest some workspace settigs / extensions.json

* 🚚 css-custom-properties -> css-variable-autocomplete

* Update .vscode/settings.json

Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>

* 🔥 remove some extensions

* ✨ add some settings

* 🔥 remove wrongly added files to branch

* Update .vscode/settings.json

* Update .vscode/settings.json

* Update .vscode/settings.json

* 🎨 Fjernet noen uødvendige extensions

* 🎨 La til lokal TS-bruk

---------

Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
Co-authored-by: Ken <ken.aleksander@gmail.com>
2024-01-31 10:47:11 +01:00
Ken fdfdebfa99 🐛 Sentrering av checkmark i Checkbox (#2682)
* 🐛 Checkmark er nå alltid sentrert i checkbox

* 📝 Changeset

* Update @navikt/core/css/form/radio-checkbox.css
2024-01-31 10:47:02 +01:00
Halvor Haugan 56abe568e2 Textarea: debounce ResizeObserver for å unngå feilmelding (#2685) 2024-01-31 10:46:29 +01:00
Halvor Haugan 34acbb87a5 🐛 Textarea: Sett riktig høyde når brukt i Modal + StrictMode (#2679) 2024-01-31 10:35:43 +01:00
Ken fd479e3160 🐞 Datepicker Day Button (#2678)
* 🐛 Fikset Datepicker Day-button

* 📝 Changeset

* 🔥 Fjernet ubrukt referanse til DayButton i Datepicker
2024-01-31 10:30:37 +01:00
github-actions[bot]andgithub-actions[bot] 03ca21b4f6 Version Packages (#2683)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-01-30 22:09:38 +01:00
Vilde Roksand Fauchald e7804297bf Eksponer variabelen "role" i Search-komponenten (#2667)
* expose role variable so its possible to change it

* changeset
2024-01-30 22:07:49 +01:00
github-actions[bot]andgithub-actions[bot] a246c9d707 Version Packages (#2681)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-01-30 16:49:36 +01:00
d3fb3d4d96 🔴 Støtte for høykontrast i komponenter (#2680)
* 💄 Accordion tilpasset høykontrast

* 💄 Alert tilpasset høykontrast

* Legg til tokens for high contrast border og -border width. Tanken er å kunne gjenbruke på tvers av komponenter.

* 💄 Tooltip tilpasset høykontrast

* 💄 ToggleGroup tilpasset høykontrast

* 💄 bedre fargebruk på Accordion

* 🐛 Fjernet deprecated systemfarger

* 💄 alert-farger

* 💄 chat tilpasset høykontrast

* ♿ button css for high contrast

* ♿ Radio og checkbox høykontrast

* ♿ Chips høykontrast

* ♿ ConfirmationPanel høykontrast

* ♿ Tab høykontrast

* ♿ Tabs høykontrast

* ♿ Textfield høykontrast

* ♿ Textarea høykontrast

* ♿ Skeleton høykontrast

* ♿ Link høykontrast

* ♿ LinkPanel høykontrast

* ♿ ExpansionCard høykontrast

* ♿ high contrast CSS for select & datepicker / monthpicker

* ♿ Loader høykontrast

* ♿ dropdown høykontrast

* ♿ HelpText høykontrast

* ♿ Popover høykontrast

* ♿ Switch høykontrast

* ♿ high contrast CSS for ReadMore

* ♿ high contrast CSS for CopyButton

* ♿ high contrast CSS for InternalHeader

* ♿ Modal highcontrast

* ♿ Search høykontrast

* 💄 Timeline tilpasset høykontrast

* ♿ high contrast CSS for Stepper

* ♿ Combobox høykontrast

* Fix "diamond"-shape that should be an "arrow" in Timeline popover

* ♿ high contrast CSS for Switch (small tweaks)

* ♿ high contrast CSS for Switch (remove unused css)

* 🐛 Riktige fokusmarkeringer på radio/checkbox

* chips

* 🐛 Fikset ToggleGroup høykontrast

* 📝 Changeset2

* 🔥 Fjernet high-contrast token fra ds-tokens

* ♿ high contrast CSS for checkbox (tweaks)

* 🔥 Fjernet disabled-prop på radio-story

* Timeline focus in high contrast mode

* 🐛 Fikset fokusmarkering på radio/checkbox

* 🐛 Highlighting av outline var feil satt på ToggleGroup

* 📝 Highlight -> highlight

---------

Co-authored-by: Vegard Haugstvedt <it.vegard@gmail.com>
Co-authored-by: Julian Nymark <julian.nymark@nav.no>
Co-authored-by: Halvor Haugan <halvor.haugan@nav.no>
2024-01-30 16:47:26 +01:00
Julian Nymark d734d3071b 👷 update workflow for chromatic (stop old running action when new is created) 2024-01-30 16:22:04 +01:00
github-actions[bot]andgithub-actions[bot] 8d97bba1fe Version Packages (#2673)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-01-30 15:07:56 +01:00
Ken eff9b5ecce LocationPin-ikon (#2672)
* ✨ LocationPin ikoner

* 📝 Changeset

* Update .changeset/afraid-oranges-grow.md
2024-01-30 14:39:37 +01:00
Halvor Haugan 71fc90b301 Endring av kodeeksempler for CopyButton (#2674) 2024-01-29 19:30:35 +01:00
Ken e1d5e1b505 [tailwind] MaxWidth-token brukt for text i tailwind (#2652)
* ✨ Ny tailwind-token

* ♻️ maxWidth er nå lettere å forstå
2024-01-29 15:51:22 +01:00
Julian Nymark 45d4f6c478 Merge branch 'main' of github.com:navikt/aksel 2024-01-29 13:05:46 +01:00
Julian Nymark 2564e84b9c 👷 update workflow 2024-01-29 13:05:16 +01:00
Halvor Haugan e5637c64a8 Rename main.mts -> .ts (#2670) 2024-01-29 09:00:34 +01:00
Julian Nymark 82a1f32542 👷 update workflow 2024-01-26 15:21:38 +01:00
Julian Nymark a8d4b1e23e 👷 update workflow 2024-01-26 15:19:08 +01:00
Julian Nymark d50ebebad6 Merge branch 'main' of github.com:navikt/aksel 2024-01-26 14:51:17 +01:00
Julian Nymark e292e83128 👷 update workflow 2024-01-26 14:51:07 +01:00
23c472e60b ✨ Combobox: Mulighet for å begrense hvor mange valg bruker kan ta (#2260)
* yarn lock

* setting up maxSelectedOptions

* added correct margin

* Update tsconfig.build.json

* Make border of "max selected"-item in FilteredOptions overlap the list border

* Hide "add new value"-button if "max selected"-limit is reached

* Make MaxSelected example a bit more interactive

* wip styling max-selected option

* Disable filteredOptions that are not selected when maxSelectedOptions is reached

* Fix scrolling/clipping content in FilteredOptions

* Remove hover-effects from non-selectable options

* add prop so user can customize message shown when max num of choices reached

* Operate directly on the DOM nodes when moving virtual focus, instead of relying on index state.

The index state became stale/non-synced when replacing elements in the list.

* make maxSelectedMessage static

* Remove max-selected:hover

Co-authored-by: Vegard Haugstvedt <it.vegard@gmail.com>

* yarn lock update

* 'cursor: not allowed' only hits disabled options

* remove li-margins, add ul-gap with negative margin on max-selected-option

* remove 'a' from initialstate maxSelectedOptions Story

* Update @navikt/core/css/form/combobox.css

* Update @navikt/core/css/form/combobox.css

* Update @navikt/core/react/src/form/combobox/FilteredOptions/FilteredOptions.tsx

* Update @navikt/core/react/src/form/combobox/FilteredOptions/useVirtualFocus.ts

* Update @navikt/core/react/src/form/combobox/FilteredOptions/useVirtualFocus.ts

* Update @navikt/core/react/src/form/combobox/combobox.stories.tsx

* type assertion

* remove tab-index on wrapper div

* prototype for sticky non-selectable options

* "yarn lock"

* Fix feil med at focus-indikator for input-feltet ikke ble vist

* Forenkle og fikse "scroll til element" i FilteredOptions

* Sørg for at disabled FilteredOptions ikke vises under "max antall"-meldingen

* Focused option should not be covered by "max selected" message

* yarn lock

* Fix "scrollIntoView is not a function" error. Possibly needs to be checked for when it occurs, but I haven't figured it out yet

* Loading indicator is no longer exposed as an option, so we have to just search for it by the accessible name.

Should have been able to use findByRole("graphics-document", { name: "Søker..." }), but that is currently broken:
https://github.com/testing-library/dom-testing-library/issues/1131

* Aria-selected should not be placed on elements that are no longer role="option"

* Combine all "max selected options"-related props in one object

* Proper BEM syntax should use -- instead of __ for state-like classes, like --loading, --max-selected and --new-option

* Always show selected options in FilteredOptions, even if they don't match the search text

* Only to the getElementsAbleToReceiveFocus() call once per function, to reduce DOM lookups. Also changed isFocusOnThe[Top | Bottom] to functions, because it was needed for isFocusOnTheBottom to follow the same rule (and not store the value on the top level in the hook.

* Pressing "End" should not only move focus to the end of the list, but also open the list if it is closed. To mirror this, we close the list if moving focus back to the Input with "Home".

* Legg til Aksel-eksempel

* yarn changeset

* Update @navikt/core/css/form/combobox.css

* change 'maxSelectedOptions' prop to 'maxSelected'

* Mark disabled options with aria-disabled

* Add max selected text to aria-describedby, so it is announced when the user enters the input field

* Add role="status" to the wrapper for "no hits", "loading" and "max selected" info messages, so they are announced when they appear

* Remove aria-selected (again)

* fix: form error not showing correctly

* fix: rm left hover-border on add-new-option item

* Bare åpne listen som default, men la intern logikk styre etter første render

* Sticky trenger ikke ligger på hver enkelt av disse, da parent wrapperen også er sticky

* Combobox eksempler var ikke tilgjengelig i `yarn storybook:aksel` på grunn av manglende Demo-eksport

* Fjern ubrukt klasse som er i konflikt med navds-combobox__wrapper

* Trengs ikke etter at max selected ble flyttet ut av listen

* Denne koden gjør ingenting

* Trenger ikke bruke :not, fordi vi tok vekk data-no-focus i en commit tidligere i dag

* Bruk dynamiske selectedOptions for å kunne endre de

* Update @navikt/core/css/form/combobox.css

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

* Update @navikt/core/react/src/form/combobox/FilteredOptions/FilteredOptions.tsx

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

* Update aksel.nav.no/website/pages/eksempler/combobox/with-max-selected-limit.tsx

Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>

* Update @navikt/core/css/form/combobox.css

* Do not render an empty ul for FilteredOptions, as it add a gap at the bottom of the container

* Do not render an empty non-selectables container, as it adds a gap at the top of the container

* Non-selectables should not have navds-combobox__list-item class, as it introduces a few issues with hovering, etc.

* Copy-pasted in an extra class that broke styling for max selected

---------

Co-authored-by: Vegard Haugstvedt <it.vegard@gmail.com>
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
2024-01-26 14:24:35 +01:00
Halvor Haugan 058e012c26 [aksel.nav.no] Endret ord i OutdatedAlert 2024-01-26 14:11:39 +01:00
Julian Nymark ffce1f75ae 👷 update workflow test 2024-01-26 11:23:35 +01:00
Julian Nymark 46e5ffb6e4 👷 test new workflow 2024-01-26 11:11:46 +01:00
Ken d9696b3617 ⬆️ Sync yarn lock (#2669) 2024-01-25 20:52:12 +01:00
Halvor Haugan df1efb2748 Oppgradering av pakker i figma-plugin (#2661) 2024-01-25 11:32:13 +01:00
Julian Nymark 2b23f6ab91 👷 force push github workflow 2024-01-24 15:18:55 +01:00
Ken 01d422b01f 🐛 Fikset default-visning av bloggposter (#2657) 2024-01-24 09:49:20 +01:00
Halvor Haugan 8936db096f [aksel.nav.no] prettier-plugin-sort-imports (#2662) 2024-01-24 09:47:12 +01:00
Andreas NordahlandKen b66ad3673b Ny styling av inline code (#2660)
* forslag til ny styling av code

* Update aksel.nav.no/website/components/website-modules/InlineCode.tsx

---------

Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
2024-01-23 21:21:53 +01:00
Andreas Nordahl 16eff2ce93 use noAmps for InlineCode in SanityBlockContent (#2659)
I en brukertest av Golden Path for frontend skapte fnuttene forvirring i inline code. Jeg tror det beste er å bare fjerne de.
2024-01-23 21:21:00 +01:00
Ken bdc0815dd7 Nytt eksperimentelt API descendants (#2586)
* 🎉 Ny hook og API 'descendants'

* 📝 Bedre storiers

* 🐛 Fikset type-mismatch mellom create-context og useDescendant

* 🐛 Fikset value setting i provider

* 🏷️ Fikset kommentert ut ts-feil

* 🎨 tester bruk av disabled

* 🔥 Fjernet unødvendig id-kobling i descendants typer
2024-01-23 15:28:12 +01:00
Halvor Haugan 4c5cdb22e9 [aksel.nav.no] Vis advarsel på gamle gp-artikler (#2650) 2024-01-23 13:24:59 +01:00
Halvor Haugan a1e74fa9bd Oppdater dependencies shadow-dom eksempel (#2656) 2024-01-23 11:16:19 +01:00
Halvor Haugan d5e5a6c64f Justering av eksempler på ExpansionCard (#2653) 2024-01-19 14:19:57 +01:00
Halvor Haugan 11e3a18526 Fiks clean (#2651) 2024-01-19 09:22:09 +01:00
Halvor Haugan 930c772bd6 [aksel.nav.no] Separat artikkelside for nye God praksis (#2648) 2024-01-18 13:50:40 +01:00
Halvor Haugan ade5d59392 [aksel.nav.no] Fjern forwardRef i ShowMore (#2649) 2024-01-18 13:49:04 +01:00
Julian Nymark d90fe26e26 popular articles (#2628)
* ❇️ new schema for view tracking

* 🚧 wip for article views schema

* 🚧 wip on schema

* 🚧 schema changes for ArticleViews

* 🚧 run prettier with new config
2024-01-17 15:26:48 +01:00
Andreas Nordahl 13b148aab6 Add dockerfile and yaml to syntax highlighting (#2638)
* Add dockerfile and yaml to syntax highlighting

* Remove dockerfile as it is not supported ootb
2024-01-17 12:31:13 +01:00
277 changed files with 5175 additions and 2872 deletions

No files matched your search

+4
View File
@@ -11,6 +11,10 @@ on:
paths:
- "@navikt/**"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
visual_regression_test:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'ignore-chromatic') }}
@@ -0,0 +1,43 @@
name: Update article views
on:
workflow_dispatch:
jobs:
execute:
runs-on: ubuntu-latest
permissions: # TODO: remove once merged
packages: "write"
contents: "read"
id-token: "write"
steps:
- uses: actions/checkout@v3
with:
ref: gp-popular-articles # TODO: remove once merged
fetch-depth: 1
- uses: denoland/setup-deno@v1
with:
deno-version: v1.x
- name: Restore cache
uses: actions/cache@v3
with:
path: "**/node_modules"
key: ${{ runner.os }}-build-${{ github.sha }}
restore-keys: |
${{ runner.os }}-build-
- name: add tokens to .env
run: |
echo "SANITY_WRITE_KEY=${{ secrets.SANITY_UTILITY_WRITE }}" >> scripts/article-views/.env
- uses: nais/docker-build-push@v0
id: docker-push
with:
team: designsystem
push_image: true
dockerfile: scripts/article-views/Dockerfile
project_id: ${{ vars.NAIS_MANAGEMENT_PROJECT_ID }}
identity_provider: ${{ secrets.NAIS_WORKLOAD_IDENTITY_PROVIDER }}
- name: start naisjob prod-gcp
uses: nais/deploy/actions/deploy@master
env:
APIKEY: ${{ secrets.NAIS_DEPLOY_APIKEY }}
RESOURCE: scripts/article-views/nais-update-views-job.yml
CLUSTER: prod-gcp
IMAGE: ${{ steps.docker-push.outputs.image }}
+1 -1
View File
@@ -1,7 +1,7 @@
import { mergeConfig } from "vite";
import turbosnap from "vite-plugin-turbosnap";
module.exports = {
export default {
staticDirs: ["./public"],
stories: () => [
+13
View File
@@ -0,0 +1,13 @@
{
"recommendations": [
"dbaeumer.vscode-eslint",
"denoland.vscode-deno",
"esbenp.prettier-vscode",
"github.vscode-github-actions",
"ms-playwright.playwright",
"sanity-io.vscode-sanity",
"stylelint.vscode-stylelint",
"vunguyentuan.vscode-css-variables",
"bradlc.vscode-tailwindcss"
]
}
+15
View File
@@ -0,0 +1,15 @@
{
"cssVariables.lookupFiles": ["@navikt/core/css/dist/index.css"],
"eslint.problems.shortenToSingleLine": true,
"deno.enablePaths": ["scripts/article-views", "scripts/changelog"],
"editor.defaultFormatter": "esbenp.prettier-vscode",
"deno.lint": true,
"files.watcherExclude": {
"**/.yarn/**": true,
"**/dist": true,
"**/esm": true,
"**/cjs": true
},
"playwright.reuseBrowser": true,
"typescript.tsdk": "node_modules/typescript/lib"
}
+12
View File
@@ -1,5 +1,17 @@
# @navikt/aksel-icons
## 5.17.2
## 5.17.1
## 5.17.0
## 5.16.0
### Patch Changes
- Ikoner: Nytt ikon `LocationPin` ✨ ([#2672](https://github.com/navikt/aksel/pull/2672))
## 5.15.1
## 5.15.0
@@ -13,23 +13,23 @@
"dependencies": {
"@navikt/ds-css": "*",
"@navikt/ds-react": "*",
"fuse.js": "^6.6.2",
"fuse.js": "^7.0.0",
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
"devDependencies": {
"@figma/plugin-typings": "^1.62.0",
"@svgr/core": "^6.5.1",
"@figma/plugin-typings": "^1.84.0",
"@svgr/core": "^8.1.0",
"@types/node": "^18.15.11",
"@types/postcss-url": "^10.0.0",
"@types/postcss-url": "^10.0.4",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"@vitejs/plugin-react": "^3.1.0",
"@vitejs/plugin-react": "^4.2.1",
"postcss": "^8.4.31",
"postcss-url": "^10.1.3",
"typescript": "^5.1.6",
"vite": "^4.5.1",
"vite-plugin-singlefile": "^0.13.3",
"vite-plugin-svgr": "^2.4.0"
"vite": "^5.0.12",
"vite-plugin-singlefile": "^1.0.0",
"vite-plugin-svgr": "^4.2.0"
}
}
@@ -170,7 +170,7 @@ const App = () => {
className="icon-classname icon-button"
>
<T
font-size="2rem"
fontSize="2rem"
title={i.id}
id={`icon-id-${i.id}`}
/>
@@ -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.58075 5.58058C10.0215 3.13981 13.9788 3.13981 16.4196 5.58058C18.829 7.98999 18.7208 11.7547 16.3826 14.4592C15.8985 15.0192 14.6522 16.5427 13.5371 17.9135C12.9773 18.6017 12.4472 19.2555 12.0572 19.7371L12.0002 19.8075L11.9432 19.7371C11.5531 19.2555 11.0231 18.6017 10.4633 17.9135C9.34813 16.5427 8.1019 15.0192 7.61778 14.4592C5.27952 11.7547 5.17135 7.98999 7.58075 5.58058ZM12.0002 21C12.5836 21.4713 12.5836 21.4713 12.5837 21.4712L12.0002 21ZM11.4167 21.4712L11.416 21.4704L11.2433 21.2567C11.1321 21.1193 10.9723 20.9217 10.7775 20.6811C10.3879 20.2 9.85856 19.5472 9.29965 18.8601C8.17735 17.4805 6.9487 15.9788 6.48307 15.4403C3.72084 12.2453 3.46216 7.57786 6.52009 4.51992C9.54666 1.49336 14.4537 1.49336 17.4802 4.51992C20.5382 7.57786 20.2795 12.2453 17.5173 15.4403C17.0516 15.9788 15.823 17.4805 14.7007 18.8601C14.1418 19.5472 13.6125 20.2 13.2229 20.6811C13.0281 20.9217 12.8682 21.1193 12.7571 21.2567L12.5837 21.4712C12.4413 21.6475 12.2268 21.75 12.0002 21.75C11.7736 21.75 11.5591 21.6475 11.4167 21.4712ZM12.0002 21L11.4167 21.4712C11.4167 21.4713 11.4168 21.4713 12.0002 21ZM9.75 10C9.75 8.75736 10.7574 7.75 12 7.75C13.2426 7.75 14.25 8.75736 14.25 10C14.25 11.2426 13.2426 12.25 12 12.25C10.7574 12.25 9.75 11.2426 9.75 10ZM12 6.25C9.92893 6.25 8.25 7.92893 8.25 10C8.25 12.0711 9.92893 13.75 12 13.75C14.0711 13.75 15.75 12.0711 15.75 10C15.75 7.92893 14.0711 6.25 12 6.25Z" fill="#23262A"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

+13
View File
@@ -0,0 +1,13 @@
name: LocationPin
category: Transportation
sub_category: Travel
keywords:
- kart
- pin
- sted
- map
- location
- lokasjon
variant: Stroke
updated_at: 26.01.2024
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="M12.7572 21.2567C12.8684 21.1193 13.0282 20.9217 13.223 20.6811C13.6126 20.2 14.1419 19.5472 14.7008 18.8601C15.8231 17.4805 17.0518 15.9788 17.5174 15.4403C20.2796 12.2453 20.5383 7.57786 17.4804 4.51992C14.4538 1.49336 9.54679 1.49336 6.52022 4.51992C3.46229 7.57786 3.72097 12.2453 6.4832 15.4403C6.94883 15.9788 8.17749 17.4805 9.29978 18.8601C9.85869 19.5472 10.388 20.2 10.7776 20.6811C10.9724 20.9217 11.1323 21.1193 11.2434 21.2567L11.4162 21.4704L11.4168 21.4712C11.5592 21.6475 11.7737 21.75 12.0003 21.75C12.2269 21.75 12.4414 21.6475 12.5838 21.4712L12.7572 21.2567ZM12 6.25C9.92893 6.25 8.25 7.92893 8.25 10C8.25 12.0711 9.92893 13.75 12 13.75C14.0711 13.75 15.75 12.0711 15.75 10C15.75 7.92893 14.0711 6.25 12 6.25Z" fill="#23262A"/>
</svg>

After

Width:  |  Height:  |  Size: 900 B

@@ -0,0 +1,13 @@
name: LocationPinFill
category: Transportation
sub_category: Travel
keywords:
- kart
- pin
- sted
- map
- location
- lokasjon
variant: Fill
updated_at: 26.01.2024
created_at: 07.03.2023
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/aksel-icons",
"version": "5.15.1",
"version": "5.17.2",
"author": "Aksel | NAV",
"homepage": "https://aksel.nav.no/ikoner",
"repository": {
+8
View File
@@ -1,5 +1,13 @@
# @navikt/aksel-stylelint
## 5.17.2
## 5.17.1
## 5.17.0
## 5.16.0
## 5.15.1
## 5.15.0
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/aksel-stylelint",
"version": "5.15.1",
"version": "5.17.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": "^5.15.1",
"@navikt/ds-tokens": "^5.15.1",
"@navikt/ds-css": "^5.17.2",
"@navikt/ds-tokens": "^5.17.2",
"@types/jest": "^29.0.0",
"concurrently": "7.2.1",
"copyfiles": "2.4.1",
+28
View File
@@ -1,5 +1,33 @@
# @navikt/aksel
## 5.17.2
### Patch Changes
- Updated dependencies [[`fdfdebfa9`](https://github.com/navikt/aksel/commit/fdfdebfa99e594099bcea7b5addde9cdabc727a7)]:
- @navikt/ds-css@5.17.2
## 5.17.1
### Patch Changes
- Updated dependencies []:
- @navikt/ds-css@5.17.1
## 5.17.0
### Patch Changes
- Updated dependencies [[`d3fb3d4d9`](https://github.com/navikt/aksel/commit/d3fb3d4d962fac028e7d803dc7c50315d48b4987)]:
- @navikt/ds-css@5.17.0
## 5.16.0
### Patch Changes
- Updated dependencies [[`23c472e60`](https://github.com/navikt/aksel/commit/23c472e60b03fa07a82ac5eed9298b535f8e341c)]:
- @navikt/ds-css@5.16.0
## 5.15.1
### Patch Changes
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/aksel",
"version": "5.15.1",
"version": "5.17.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": "5.15.1",
"@navikt/ds-css": "5.17.2",
"axios": "1.6.0",
"chalk": "4.1.0",
"clipboardy": "^2.3.0",
+20
View File
@@ -1,5 +1,25 @@
# @navikt/ds-css
## 5.17.2
### Patch Changes
- Checkbox: I noen edgecaser ved bruk av shadow-dom ble ikke checkmark sentert i Checkbox ([#2682](https://github.com/navikt/aksel/pull/2682))
## 5.17.1
## 5.17.0
### Minor Changes
- Høykontrast: Komponenter støtter nå standard høykontrast-modus på Windows ([#2680](https://github.com/navikt/aksel/pull/2680))
## 5.16.0
### Minor Changes
- :sparkles: Combobox: Mulighet for å begrense hvor mange valg bruker kan ta ([#2260](https://github.com/navikt/aksel/pull/2260))
## 5.15.1
## 5.15.0
+16 -1
View File
@@ -74,7 +74,8 @@
}
.navds-accordion__header:focus-visible {
outline: none;
outline: 2px solid transparent;
outline-offset: 3px;
box-shadow: var(--a-shadow-focus);
border-radius: var(--a-border-radius-large);
}
@@ -185,3 +186,17 @@
transform: translateY(0);
}
}
@media (forced-colors: active) {
.navds-accordion__header {
border: 1px solid buttonborder;
background-color: canvas;
color: canvastext;
}
.navds-accordion__header:hover {
background-color: canvas;
border: 1px solid highlight;
color: highlight;
}
}
+10 -2
View File
@@ -33,7 +33,7 @@
font-size: 1.5rem;
align-self: flex-start;
height: var(--a-font-line-height-xlarge);
background: radial-gradient(circle, var(--a-surface-default) 50%, 0, transparent);
background: radial-gradient(circle, var(--a-surface-default) 47%, 0, transparent);
}
.navds-alert--small > .navds-alert__icon {
@@ -64,7 +64,7 @@
}
.navds-alert--warning > .navds-alert__icon {
background: radial-gradient(circle at 50% 57%, var(--a-surface-default) 32%, 0, transparent);
background: radial-gradient(circle at 50% 57%, var(--a-surface-default) 30%, 0, transparent);
color: var(--ac-alert-icon-warning-color, var(--a-icon-warning));
}
@@ -99,3 +99,11 @@
flex-flow: row nowrap;
justify-content: flex-end;
}
@media (forced-colors: active) {
.navds-alert {
border: 1px solid canvastext;
background-color: canvas;
color: canvastext;
}
}
+53 -1
View File
@@ -81,7 +81,8 @@
}
.navds-button:focus-visible {
outline: none;
outline: 2px solid transparent;
outline-offset: 2px;
box-shadow: var(--a-shadow-focus);
}
@@ -129,6 +130,16 @@
background-color: var(--ac-button-primary-bg, var(--__ac-button-primary-bg, var(--a-surface-action)));
color: var(--ac-button-primary-text, var(--__ac-button-primary-text, var(--a-text-on-action)));
}
@media (forced-colors: active) {
.navds-button.navds-button--primary {
background-color: highlight;
color: highlighttext;
}
.navds-button.navds-button--primary span {
forced-color-adjust: none;
}
}
.navds-button--primary:hover {
background-color: var(--ac-button-primary-hover-bg, var(--__ac-button-primary-hover-bg, var(--a-surface-action-hover)));
@@ -139,10 +150,17 @@
}
.navds-button--primary:focus-visible {
outline: 2px solid transparent;
outline-offset: 2px;
box-shadow:
inset 0 0 0 1px var(--ac-button-primary-focus-border, var(--__ac-button-primary-focus-border, var(--a-surface-default))),
var(--a-shadow-focus);
}
@media (forced-colors: active) {
.navds-button--primary:focus-visible {
box-shadow: none;
}
}
@supports not selector(:focus-visible) {
.navds-button--primary:focus {
@@ -478,6 +496,7 @@
opacity: 0.3;
}
/* Loader overrides */
.navds-button .navds-loader .navds-loader__foreground {
stroke: var(--ac-button-loader-stroke, currentColor);
}
@@ -486,3 +505,36 @@
.navds-button--danger .navds-loader .navds-loader__background {
stroke: var(--ac-button-primary-loader-stroke-bg, rgb(255 255 255 / 0.3));
}
@media (forced-colors: active) {
.navds-button:not(.navds-button--loading):where(:disabled, .navds-button--disabled) {
opacity: 1;
color: GrayText;
}
.navds-button {
border: 1px solid transparent;
color: ButtonText;
background-color: ButtonFace;
}
.navds-button:hover {
background-color: highlighttext;
border-color: highlight;
color: highlight;
box-shadow: none;
}
.navds-button:hover span {
forced-color-adjust: none;
}
.navds-button .navds-loader .navds-loader__foreground {
stroke: canvas;
}
.navds-button--primary .navds-loader .navds-loader__background,
.navds-button--danger .navds-loader .navds-loader__background {
stroke: canvastext;
}
}
+13
View File
@@ -105,3 +105,16 @@
.navds-chat--right .navds-chat__top-text--left {
align-self: flex-start;
}
@media (forced-colors: active) {
.navds-chat__bubble,
.navds-chat__avatar {
border: 1px solid canvastext;
background-color: canvas;
color: canvastext;
}
.navds-chat__avatar svg {
forced-color-adjust: none;
}
}
+41
View File
@@ -191,3 +191,44 @@
.navds-chips--small .navds-chips--icon-right {
padding-right: var(--a-spacing-05);
}
@media (forced-colors: active) {
.navds-chips__chip {
border: 1px solid transparent;
}
.navds-chips__chip:hover {
background-color: highlighttext;
color: highlight;
}
.navds-chips__chip:focus-visible {
outline: 2px solid transparent;
outline-offset: 2px;
}
.navds-chips__chip:where([aria-pressed="true"], :active, :hover) > span {
forced-color-adjust: none;
}
.navds-chips__toggle:active {
background-color: highlight;
color: highlighttext;
}
.navds-chips__toggle[aria-pressed="true"] {
background-color: selecteditem;
color: selecteditemtext;
border: 1px solid selecteditem;
}
.navds-chips__toggle[aria-pressed="true"]:hover {
background-color: selecteditemtext;
color: selecteditem;
}
.navds-chips__toggle[aria-pressed="true"]:active {
background-color: highlight;
color: highlighttext;
}
}
+22
View File
@@ -28,6 +28,21 @@
place-content: center;
}
@media (forced-colors: active) {
.navds-copybutton {
background-color: ButtonFace;
border-color: ButtonText;
border: solid 1px ButtonText;
color: ButtonText;
}
.navds-copybutton.navds-copybutton:focus-visible {
box-shadow: none;
outline: 2px solid highlight;
outline-offset: 2px;
}
}
.navds-copybutton--icon-right {
--__ac-copybutton-padding: var(--a-spacing-3) var(--a-spacing-4) var(--a-spacing-3) var(--a-spacing-5);
}
@@ -201,3 +216,10 @@
cursor: not-allowed;
opacity: 0.3;
}
@media (forced-colors: active) {
.navds-copybutton:where(:disabled) {
opacity: 1;
border-color: GrayText;
}
}
+15 -2
View File
@@ -58,13 +58,15 @@
}
.navds-dropdown__item:focus-visible {
outline: none;
outline: 2px solid transparent;
outline-offset: -2px;
box-shadow: inset 0 0 0 2px var(--a-border-focus);
}
@supports not selector(:focus-visible) {
.navds-dropdown__item:focus {
outline: none;
outline: 2px solid transparent;
outline-offset: -2px;
box-shadow: inset 0 0 0 2px var(--a-border-focus);
}
}
@@ -75,3 +77,14 @@
background: transparent;
cursor: not-allowed;
}
@media (forced-colors: active) {
.navds-dropdown__item:hover {
color: highlight;
}
.navds-dropdown__item:disabled {
opacity: 1;
color: graytext;
}
}
+10 -2
View File
@@ -145,13 +145,13 @@
}
.navds-expansioncard__header-button:focus-visible {
outline: none;
outline: 3px solid transparent;
box-shadow: var(--a-shadow-focus);
}
@supports not selector(:focus-visible) {
.navds-expansioncard__header-button:focus {
outline: none;
outline: 3px solid transparent;
box-shadow: var(--a-shadow-focus);
}
}
@@ -221,3 +221,11 @@
transform: translateY(0);
}
}
@media (forced-colors: active) {
.navds-expansioncard:hover {
--__ac-expansioncard-border-color: highlight;
outline: 1px solid highlight;
}
}
+112 -19
View File
@@ -39,6 +39,8 @@
.navds-combobox--error
.navds-text-field__input:not(:hover):not(:disabled):not(.navds-combobox__wrapper-inner--virtually-unfocused):focus-within {
outline: 2px solid transparent;
outline-offset: 2px;
box-shadow:
0 0 0 1px var(--ac-combobox-error-border, var(--a-border-danger)),
var(--a-shadow-focus);
@@ -74,7 +76,6 @@
}
.navds-combobox__input {
z-index: 1;
flex: 1;
border: none;
padding: 0;
@@ -95,10 +96,13 @@
0 0 0 1px var(--a-surface-default) inset,
var(--a-shadow-focus);
box-shadow: var(--a-shadow-focus);
outline: 3px solid transparent;
outline-offset: 2px;
}
.navds-combobox__wrapper-inner:has(.navds-combobox__input:focus-visible).navds-combobox__wrapper-inner--virtually-unfocused {
box-shadow: none;
outline: none;
}
@supports not selector(:focus-visible) {
@@ -113,10 +117,13 @@
0 0 0 1px var(--a-surface-default) inset,
var(--a-shadow-focus);
box-shadow: var(--a-shadow-focus);
outline: 3px solid transparent;
outline-offset: 2px;
}
.navds-combobox__wrapper-inner:has(.navds-combobox__input:focus).navds-combobox__wrapper-inner--virtually-unfocused {
box-shadow: none;
outline: none;
}
}
@@ -126,10 +133,13 @@
0 0 0 1px var(--a-surface-default) inset,
var(--a-shadow-focus);
box-shadow: var(--a-shadow-focus);
outline: 3px solid transparent;
outline-offset: 2px;
}
.navds-combobox--focused .navds-combobox__wrapper-inner.navds-combobox__wrapper-inner--virtually-unfocused {
box-shadow: none;
outline: none;
}
}
@@ -208,7 +218,7 @@
height: 1.25rem;
}
/* dropdown list */
/* dropdown & non selectable dropdown items */
.navds-combobox__list {
max-height: 290px;
@@ -216,20 +226,16 @@
position: absolute;
left: 0;
right: 0;
z-index: 9999;
z-index: var(--a-z-index-popover);
top: calc(100% + var(--a-spacing-2));
list-style: none;
margin: 0;
border: 1px solid var(--ac-combobox-list-border-color, var(--a-border-divider));
display: flex;
flex-direction: column;
align-items: flex-start;
padding: 0;
box-shadow: var(--a-shadow-small);
border-radius: var(--a-border-radius-medium);
gap: var(--a-spacing-1) 0;
background-color: var(--ac-combobox-list-bg, var(--a-surface-default));
color: var(--ac-combobox-list-text, var(--a-text-default));
gap: var(--a-spacing-1) 0;
}
.navds-combobox__list--closed {
@@ -241,34 +247,70 @@
width: 1.5rem;
}
.navds-combobox__list_non-selectables {
position: sticky;
top: 0;
left: 0;
right: 0;
z-index: 1;
}
.navds-combobox__list-item,
.navds-combobox__list-item__no-options,
.navds-combobox__list-item__new-option {
.navds-combobox__list-item--loading,
.navds-combobox__list-item--no-options,
.navds-combobox__list-item--new-option,
.navds-combobox__list-item--max-selected {
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));
scroll-margin-top: 50px;
}
.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 {
.navds-form-field--small .navds-combobox__list-item--loading,
.navds-form-field--small .navds-combobox__list-item--no-options,
.navds-form-field--small .navds-combobox__list-item--new-option,
.navds-form-field--small .navds-combobox__list-item--max-selected {
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));
}
.navds-combobox__list-item--max-selected {
background: var(--ac-combobox-list-item-max-selected-bg, var(--a-surface-info-subtle));
border-start-start-radius: calc(var(--a-border-radius-medium) - 1px);
border-start-end-radius: calc(var(--a-border-radius-medium) - 1px);
border: 1px solid var(--ac-combobox-list-item-max-selected-border, var(--a-border-info));
margin-bottom: calc(var(--a-spacing-1) * -1);
}
.navds-combobox__list_non-selectables:hover {
cursor: default;
}
/* ul-list and selectable li-items */
.navds-combobox__list-options {
list-style: none;
margin: 0;
padding: 0;
width: 100%;
display: inherit;
flex-direction: inherit;
gap: inherit;
background-color: inherit;
align-items: flex-start;
}
.navds-combobox__list-item--focus,
.navds-combobox__list--with-hover .navds-combobox__list-item:hover {
.navds-combobox__list--with-hover
.navds-combobox__list-item:not([data-no-focus="true"], .navds-combobox__list-item--new-option):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));
@@ -280,6 +322,11 @@
padding-left: calc(var(--a-spacing-2) - 4px);
}
.navds-combobox__list-item[data-no-focus="true"] {
cursor: not-allowed;
opacity: 0.4;
}
.navds-combobox__list-item--selected {
background-color: var(--ac-combobox-list-item-selected-bg, var(--a-surface-selected));
}
@@ -295,7 +342,7 @@
padding-left: calc(var(--a-spacing-3) - 4px);
}
.navds-combobox__list-item__new-option {
.navds-combobox__list-item--new-option {
border-bottom: 1px solid var(--a-border-divider);
background: var(--a-surface-neutral-subtle);
cursor: pointer;
@@ -303,12 +350,12 @@
gap: 0.25rem;
}
.navds-combobox__list--with-hover .navds-combobox__list-item__new-option:hover {
.navds-combobox__list--with-hover .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 {
.navds-combobox__list-item--new-option--focus {
box-shadow:
var(--a-shadow-focus) inset,
var(--a-border-action) 0 0 0 5px inset;
@@ -332,3 +379,49 @@
gap: var(--a-spacing-1);
}
}
@media (forced-colors: active) {
.navds-combobox__button-clear:hover {
color: highlight;
}
.navds-combobox__wrapper-inner:has(.navds-combobox__input:focus-visible) {
outline-color: highlight;
}
.navds-combobox__list-item--focus,
.navds-combobox__list--with-hover
.navds-combobox__list-item:not([data-no-focus="true"], .navds-combobox__list-item--new-option):hover {
border-left-color: highlight;
color: highlight;
}
.navds-combobox__list-item[data-no-focus="true"] {
opacity: 1;
color: graytext;
}
.navds-combobox__list-item--selected {
background-color: selecteditem;
color: selecteditemtext;
}
.navds-combobox__list-item--selected > * {
forced-color-adjust: none;
}
.navds-combobox__list-item--selected.navds-combobox__list-item--focus,
.navds-combobox__list--with-hover .navds-combobox__list-item--selected:hover {
border-left-color: highlight;
color: highlight;
}
.navds-combobox__list--with-hover .navds-combobox__list-item--new-option:hover {
color: highlight;
}
.navds-combobox__list-item--new-option--focus {
outline: 2px solid highlight;
outline-offset: -3px;
}
}
@@ -8,6 +8,7 @@
background-color: var(--ac-confirmation-panel-bg, var(--a-surface-warning-subtle));
transition: background-color linear 100ms;
justify-self: stretch;
position: relative;
}
.navds-confirmation-panel__content {
@@ -23,3 +24,26 @@
border-color: var(--ac-confirmation-panel-error-border, var(--a-border-danger));
background-color: var(--ac-confirmation-panel-error-bg, var(--a-surface-danger-subtle));
}
@media (forced-colors: active) {
.navds-confirmation-panel__inner::before {
content: "";
position: absolute;
left: 0;
top: 0;
height: 100%;
border-left: 8px solid;
border-color: orange;
forced-color-adjust: none;
border-start-start-radius: calc(var(--a-border-radius-medium) - 1px);
border-end-start-radius: calc(var(--a-border-radius-medium) - 1px);
}
.navds-confirmation-panel--checked .navds-confirmation-panel__inner::before {
border-color: green;
}
.navds-confirmation-panel--error .navds-confirmation-panel__inner::before {
border-color: red;
}
}
+7
View File
@@ -42,3 +42,10 @@
.navds-fieldset:disabled > .navds-fieldset__description {
opacity: 0.3;
}
@media (forced-colors: active) {
.navds-fieldset:disabled > .navds-fieldset__legend,
.navds-fieldset:disabled > .navds-fieldset__description {
opacity: 1;
}
}
+6
View File
@@ -51,3 +51,9 @@
flex-shrink: 0;
align-self: flex-start;
}
@media (forced-colors: active) {
.navds-form-field--disabled {
opacity: 1;
}
}
+86
View File
@@ -72,6 +72,8 @@
.navds-checkbox__input:focus-visible + .navds-checkbox__label::before,
.navds-radio__input:focus-visible + .navds-radio__label::before {
outline: 2px solid transparent;
outline-offset: 2px;
box-shadow:
0 0 0 2px var(--ac-radio-checkbox-border, var(--a-border-default)),
0 0 0 2px var(--ac-radio-checkbox-bg, var(--a-surface-default)),
@@ -81,6 +83,8 @@
@supports not selector(:focus-visible) {
.navds-checkbox__input:focus + .navds-checkbox__label::before,
.navds-radio__input:focus + .navds-radio__label::before {
outline: 2px solid transparent;
outline-offset: 2px;
box-shadow:
0 0 0 2px var(--ac-radio-checkbox-border, var(--a-border-default)),
0 0 0 4px var(--a-border-focus);
@@ -139,6 +143,10 @@
height: 1.5rem;
transform: translate(var(--a-spacing-1-alt));
pointer-events: none;
/* Fixed edcase where when using in shadow-dom, svg will not center */
display: flex;
align-items: center;
}
.navds-checkbox--small .navds-checkbox__input:checked + .navds-checkbox__label > .navds-checkbox__icon {
@@ -352,3 +360,81 @@
.navds-checkbox--readonly > .navds-checkbox__input:indeterminate + .navds-checkbox__label::after {
background-color: var(--a-icon-subtle);
}
@media (forced-colors: active) {
.navds-checkbox__input:focus-visible + .navds-checkbox__label::before,
.navds-radio__input:focus-visible + .navds-radio__label::before {
outline-color: highlight;
}
.navds-checkbox,
.navds-radio {
--__ac-radio-checkbox-high-contrast-bg: field;
--__ac-radio-checkbox-high-contrast-text: fieldtext;
--__ac-radio-checkbox-high-contrast-highlight: highlight;
}
.navds-checkbox__label::before,
.navds-radio__label::before {
border: 1px solid var(--__ac-radio-checkbox-high-contrast-text);
background-color: var(--__ac-radio-checkbox-high-contrast-bg);
}
.navds-checkbox__input:checked + .navds-checkbox__label > .navds-checkbox__icon {
color: var(--__ac-radio-checkbox-high-contrast-text);
}
.navds-checkbox__input:indeterminate + .navds-checkbox__label::after {
background-color: var(--__ac-radio-checkbox-high-contrast-text);
}
.navds-radio__input:checked + .navds-radio__label::before {
border: 1px solid var(--__ac-radio-checkbox-high-contrast-text);
outline: 3px solid var(--__ac-radio-checkbox-high-contrast-bg);
outline-offset: -4px;
background-color: var(--__ac-radio-checkbox-high-contrast-text);
}
.navds-radio__input:checked:focus-visible + .navds-radio__label::before {
border: 1px solid var(--__ac-radio-checkbox-high-contrast-bg);
outline: 2px solid highlight;
outline-offset: 2px;
}
:not(.navds-checkbox--readonly) > .navds-checkbox__input:hover:not(:disabled) + .navds-checkbox__label,
:not(.navds-radio--readonly) > .navds-radio__input:hover:not(:disabled) + .navds-radio__label {
color: highlight;
}
.navds-checkbox__input:focus-visible + .navds-checkbox__label::before {
outline: 2px solid var(--__ac-radio-checkbox-high-contrast-highlight);
}
.navds-checkbox--readonly > .navds-checkbox__input:checked + .navds-checkbox__label::before {
border: 1px solid var(--__ac-radio-checkbox-high-contrast-text);
background-color: var(--__ac-radio-checkbox-high-contrast-bg);
}
.navds-checkbox--readonly > .navds-checkbox__input:indeterminate + .navds-checkbox__label::after {
background-color: var(--__ac-radio-checkbox-high-contrast-text);
}
.navds-radio--readonly > .navds-radio__input:checked + .navds-radio__label::before {
border: 1px solid var(--__ac-radio-checkbox-high-contrast-text);
outline: 3px solid var(--__ac-radio-checkbox-high-contrast-bg);
outline-offset: -4px;
background-color: var(--__ac-radio-checkbox-high-contrast-text);
}
.navds-checkbox--disabled,
.navds-radio--disabled {
opacity: 1;
--__ac-radio-checkbox-high-contrast-bg: field;
--__ac-radio-checkbox-high-contrast-text: graytext;
}
:is(.navds-checkbox--disabled, .navds-radio--disabled) :is(.navds-checkbox__label, .navds-radio__label) {
color: graytext;
}
}
+12 -2
View File
@@ -99,14 +99,14 @@
}
.navds-search__button-clear:focus-visible {
outline: 2px solid transparent;
box-shadow: var(--a-shadow-focus);
outline: none;
}
@supports not selector(:focus-visible) {
.navds-search__button-clear:focus {
outline: 2px solid transparent;
box-shadow: var(--a-shadow-focus);
outline: none;
}
}
@@ -216,3 +216,13 @@
z-index: 1;
}
}
@media (forced-colors: active) {
.navds-modal {
outline: 2px solid transparent;
}
.navds-search__button-clear:hover {
color: highlight;
}
}
+21
View File
@@ -13,6 +13,21 @@
padding-right: 2rem;
}
@media (forced-colors: active) {
.navds-select__input.navds-select__input.navds-select__input {
background-color: ButtonFace;
border-color: ButtonText;
color: ButtonText;
forced-color-adjust: none;
box-shadow: none;
}
.navds-select__input.navds-select__input.navds-select__input:focus-visible {
outline: 2px solid highlight;
outline-offset: 2px;
}
}
.navds-select__input:hover {
border-color: var(--ac-select-hover-bg, var(--a-border-action));
cursor: pointer;
@@ -46,6 +61,12 @@
color: var(--ac-select-text, var(--a-text-default));
}
@media (forced-colors: active) {
.navds-select__chevron {
color: ButtonText;
}
}
.navds-form-field--small .navds-select__input {
min-height: 2rem;
padding: 0 var(--a-spacing-8) 0 var(--a-spacing-2);
+49
View File
@@ -111,6 +111,8 @@
}
.navds-switch__input:focus-visible ~ .navds-switch__track {
outline: 2px solid transparent;
outline-offset: 2px;
box-shadow:
0 0 0 1px var(--a-surface-default),
var(--a-shadow-focus);
@@ -118,6 +120,7 @@
@supports not selector(:focus-visible) {
.navds-switch__input:focus ~ .navds-switch__track {
outline: 2px solid transparent;
box-shadow:
0 0 0 1px var(--a-surface-default),
var(--a-shadow-focus);
@@ -218,3 +221,49 @@
transform: translateX(1.25rem);
}
}
@media (forced-colors: active) {
.navds-switch__input:hover ~ .navds-switch__label-wrapper,
.navds-switch__label-wrapper:hover {
color: highlight;
}
.navds-switch__thumb,
.navds-switch--readonly .navds-switch__thumb {
background-color: fieldtext;
}
.navds-switch__input:checked ~ .navds-switch__track > .navds-switch__thumb,
.navds-switch--readonly .navds-switch__input:checked ~ .navds-switch__track > .navds-switch__thumb {
color: field;
}
.navds-switch__track {
width: calc(2.75rem + 4px);
height: calc(1.5rem + 4px);
top: var(--a-spacing-3);
border: 2px solid fieldtext;
}
.navds-switch__input:focus-visible ~ .navds-switch__track {
outline: 2px solid highlight;
outline-offset: 2px;
}
.navds-switch--disabled:not(.navds-switch--loading) {
opacity: 1;
}
.navds-switch--disabled:not(.navds-switch--loading) .navds-switch__thumb {
background-color: graytext;
}
.navds-switch--disabled:not(.navds-switch--loading) .navds-switch__track {
border-color: graytext;
}
.navds-switch--disabled:not(.navds-switch--loading) .navds-switch__label-wrapper,
.navds-switch--disabled:not(.navds-switch--loading) .navds-switch__label-wrapper:hover {
color: graytext !important;
}
}
+9 -1
View File
@@ -45,7 +45,8 @@
}
.navds-text-field__input:focus-visible {
outline: none;
outline: 2px solid transparent;
outline-offset: 2px;
box-shadow: var(--a-shadow-focus);
}
@@ -101,3 +102,10 @@
.navds-text-field__input[type="search"]::-webkit-search-results-decoration {
-webkit-appearance: none;
}
@media (forced-colors: active) {
.navds-text-field__input {
background-color: field;
color: fieldtext;
}
}
+11 -2
View File
@@ -52,13 +52,15 @@
}
.navds-textarea__input:focus-visible {
outline: none;
outline: 2px solid transparent;
outline-offset: 2px;
box-shadow: var(--a-shadow-focus);
}
@supports not selector(:focus-visible) {
.navds-textarea__input:focus {
outline: none;
outline: 2px solid transparent;
outline-offset: 2px;
box-shadow: var(--a-shadow-focus);
}
}
@@ -141,3 +143,10 @@
border-color: var(--a-border-subtle);
cursor: default;
}
@media (forced-colors: active) {
.navds-textarea__input {
background-color: field;
color: fieldtext;
}
}
+16 -2
View File
@@ -13,7 +13,7 @@
}
.navds-help-text__button:focus-visible {
outline: none;
outline: 2px solid transparent;
box-shadow:
0 0 0 1px var(--a-border-focus),
inset 0 0 0 1px var(--a-border-focus);
@@ -21,7 +21,7 @@
@supports not selector(:focus-visible) {
.navds-help-text__button:focus {
outline: none;
outline: 2px solid transparent;
box-shadow:
0 0 0 1px var(--a-border-focus),
inset 0 0 0 1px var(--a-border-focus);
@@ -64,3 +64,17 @@
display: inherit;
}
}
@media (forced-colors: active) {
.navds-help-text__button:where(:hover, :focus-visible, [aria-expanded="true"]) > .navds-help-text__icon {
display: inherit;
}
.navds-help-text__button:hover > .navds-help-text__icon {
color: highlight;
}
.navds-help-text__button:where(:hover, :focus-visible, [aria-expanded="true"]) > .navds-help-text__icon--filled {
display: none;
}
}
+21
View File
@@ -7,6 +7,27 @@
--navds-internalheader-focus: inset 0 0 0 2px var(--a-border-focus-on-inverted);
}
@media (forced-colors: active) {
.navds-internalheader {
background-color: ButtonFace;
border-color: ButtonText;
border: solid 1px ButtonText;
color: ButtonText;
}
.navds-internalheader a:focus-visible {
box-shadow: none;
outline: 2px solid highlight;
outline-offset: 2px;
}
.navds-internalheader .navds-internalheader__button:focus-visible {
box-shadow: none;
outline: 2px solid highlight;
outline-offset: 2px;
}
}
.navds-internalheader__title {
border: none;
overflow: visible;
+4 -2
View File
@@ -18,14 +18,16 @@
}
.navds-link-panel:focus-visible {
outline: 3px solid transparent;
outline-offset: 2px;
box-shadow: var(--a-shadow-focus);
outline: none;
}
@supports not selector(:focus-visible) {
.navds-link-panel:focus {
outline: 3px solid transparent;
outline-offset: 2px;
box-shadow: var(--a-shadow-focus);
outline: none;
}
}
+3 -3
View File
@@ -36,7 +36,7 @@
}
.navds-link:focus-visible {
outline: none;
outline: 2px solid transparent;
color: var(--ac-link-focus-text, var(--a-text-on-action));
text-decoration: none;
background-color: var(--ac-link-focus-bg, var(--a-border-focus));
@@ -45,7 +45,7 @@
@supports not selector(:focus-visible) {
.navds-link:focus {
outline: none;
outline: 2px solid transparent;
color: var(--ac-link-focus-text, var(--a-text-on-action));
text-decoration: none;
background-color: var(--ac-link-focus-bg, var(--a-border-focus));
@@ -54,7 +54,7 @@
}
.navds-link:active {
outline: none;
outline: 2px solid transparent;
color: var(--ac-link-active-text, var(--a-text-on-action));
text-decoration: none;
background-color: var(--ac-link-active-bg, var(--a-border-focus));
+6
View File
@@ -111,3 +111,9 @@
stroke-dashoffset: -120px;
}
}
@media (forced-colors: active) {
.navds-loader__background {
stroke: canvastext;
}
}
+6
View File
@@ -168,3 +168,9 @@
opacity: 1;
}
}
@media (forced-colors: active) {
.navds-modal {
outline: 2px solid transparent;
}
}
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-css",
"version": "5.15.1",
"version": "5.17.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": "^5.15.1",
"@navikt/ds-tokens": "^5.17.2",
"cssnano": "6.0.0",
"fast-glob": "3.2.11",
"lodash": "4.17.21",
+23
View File
@@ -59,3 +59,26 @@
border-right-color: transparent;
border-top-color: transparent;
}
@media (forced-colors: active) {
.navds-popover[data-placement^="top"] > .navds-popover__arrow {
border-left-color: canvas;
border-top-color: canvas;
}
/* prettier-ignore */
.navds-popover[data-placement^="bottom"] > .navds-popover__arrow {
border-bottom-color: canvas;
border-right-color: canvas;
}
.navds-popover[data-placement^="left"] > .navds-popover__arrow {
border-left-color: canvas;
border-bottom-color: canvas;
}
.navds-popover[data-placement^="right"] > .navds-popover__arrow {
border-right-color: canvas;
border-top-color: canvas;
}
}
+15
View File
@@ -16,6 +16,21 @@
padding: var(--a-spacing-05) var(--a-spacing-1) var(--a-spacing-05) var(--a-spacing-05);
}
@media (forced-colors: active) {
.navds-read-more__button {
background-color: ButtonFace;
border-color: ButtonText;
border: solid 1px ButtonText;
color: ButtonText;
}
.navds-read-more__button.navds-read-more__button:focus-visible {
box-shadow: none;
outline: 2px solid highlight;
outline-offset: 2px;
}
}
.navds-read-more__button:hover {
background-color: var(--ac-read-more-hover-bg, var(--a-surface-hover));
}
+8
View File
@@ -57,3 +57,11 @@
opacity: 0.4;
}
}
@media (forced-colors: active) {
.navds-skeleton {
forced-color-adjust: none;
background-color: var(--a-surface-neutral);
animation-duration: 2s;
}
}
+35
View File
@@ -70,6 +70,41 @@
margin: calc(var(--navds-stepper-border-width) * -1) calc(var(--navds-stepper-border-width) * -1) 1.75rem;
}
@media (forced-colors: active) {
.navds-stepper__step {
background-color: ButtonFace;
color: ButtonText;
}
.navds-stepper__circle.navds-stepper__circle {
border: 0;
}
.navds-stepper__step .navds-stepper__circle {
forced-color-adjust: none;
background-color: ButtonText;
border-color: ButtonText;
color: ButtonFace;
}
.navds-stepper__step.navds-stepper__step:focus-visible {
box-shadow: none;
outline: 2px solid highlight;
outline-offset: 2px;
}
.navds-stepper__step.navds-stepper__step--active .navds-stepper__circle {
forced-color-adjust: none;
background-color: highlight;
border-color: highlighttext;
color: highlighttext;
}
.navds-stepper__line {
background-color: ButtonText;
}
}
button.navds-stepper__step {
appearance: none;
border: none;
+26
View File
@@ -264,3 +264,29 @@
.navds-table--small .navds-table__expanded-row-content {
padding: var(--a-spacing-2) calc(var(--a-spacing-2) + 3rem);
}
@media (forced-colors: active) {
.navds-date.navds-date button.rdp-day_selected {
forced-color-adjust: none;
background-color: highlight;
color: highlighttext;
box-shadow: none;
}
.navds-date.navds-date button {
color: buttontext;
forced-color-adjust: none;
outline: none;
box-shadow: none;
}
.navds-date button:focus-visible {
color: buttontext;
forced-color-adjust: none;
outline: 2px solid highlight;
}
.navds-date.navds-date button.rdp-day_disabled {
color: GrayText;
}
}
+21 -4
View File
@@ -63,14 +63,15 @@
}
.navds-tabs__tab:focus-visible {
outline: none;
outline: 2px solid transparent;
outline-offset: -2px;
box-shadow: inset var(--a-shadow-focus);
color: var(--ac-tabs-focus-text, var(--a-text-default));
}
@supports not selector(:focus-visible) {
.navds-tabs__tab:focus {
outline: none;
outline: 2px solid transparent;
box-shadow: inset var(--a-shadow-focus);
color: var(--ac-tabs-focus-text, var(--a-text-default));
}
@@ -117,13 +118,29 @@
}
.navds-tabs__tabpanel:focus-visible {
outline: none;
outline: 2px solid transparent;
box-shadow: inset 0 0 0 2px var(--a-border-focus);
}
@supports not selector(:focus-visible) {
.navds-tabs__tabpanel:focus {
outline: none;
outline: 2px solid transparent;
box-shadow: inset 0 0 0 2px var(--a-border-focus);
}
}
@media (forced-colors: active) {
.navds-tabs__tab[aria-selected="true"] {
border-bottom: 3px solid canvastext;
padding-block-end: calc(var(--a-spacing-3) - 3px);
}
.navds-tabs__tab--small[aria-selected="true"] {
padding-block-end: calc(var(--a-spacing-1-alt) - 3px);
}
.navds-tabs__tab-icon--top[aria-selected="true"],
.navds-tabs__tab--small.navds-tabs__tab-icon--top[aria-selected="true"] {
padding-block-end: calc(var(--a-spacing-1) - 3px);
}
}
+12
View File
@@ -180,3 +180,15 @@
background-color: var(--ac-tag-alt-3-moderate-bg, var(--a-surface-alt-3-moderate));
color: var(--ac-tag-alt-3-moderate-text, var(--a-text-default));
}
@media (forced-colors: active) {
.navds-tag {
forced-color-adjust: none;
}
.navds-tag--neutral,
.navds-tag--neutral-moderate {
border-color: canvastext;
color: canvastext;
}
}
+54
View File
@@ -393,3 +393,57 @@
border-right-color: transparent;
border-top-color: transparent;
}
@media (forced-colors: active) {
.navds-timeline__period:focus {
outline: 2px solid highlight;
outline-offset: 2px;
}
.navds-timeline__period--success,
.navds-timeline__period--warning,
.navds-timeline__period--info,
.navds-timeline__period--neutral,
.navds-timeline__period--danger {
forced-color-adjust: none;
}
.navds-timeline__row {
border: 1px solid transparent;
}
.navds-timeline__pin-wrapper::before {
border: 1px solid transparent;
}
.navds-timeline__pin-button {
outline: 4px solid transparent;
}
.navds-timeline__pin-button:focus,
.navds-timeline__pin-button:focus-visible {
outline: 4px solid highlight;
outline-offset: 2px;
box-shadow: none;
}
.navds-timeline__popover[data-placement^="top"] > .navds-timeline__popover-arrow {
border-left-color: canvas;
border-top-color: canvas;
}
.navds-timeline__popover[data-placement^="bottom"] > .navds-timeline__popover-arrow {
border-bottom-color: canvas;
border-right-color: canvas;
}
.navds-timeline__popover[data-placement^="left"] > .navds-timeline__popover-arrow {
border-left-color: canvas;
border-bottom-color: canvas;
}
.navds-timeline__popover[data-placement^="right"] > .navds-timeline__popover-arrow {
border-right-color: canvas;
border-top-color: canvas;
}
}
+33 -2
View File
@@ -65,7 +65,8 @@
}
.navds-toggle-group__button:focus-visible {
outline: none;
outline: 2px solid transparent;
outline-offset: 1px;
box-shadow:
0 0 0 1px var(--a-surface-default),
0 0 0 4px var(--a-border-focus);
@@ -73,7 +74,8 @@
@supports not selector(:focus-visible) {
.navds-toggle-group__button:focus {
outline: none;
outline: 2px solid transparent;
outline-offset: 1px;
box-shadow:
0 0 0 1px var(--a-surface-default),
0 0 0 4px var(--a-border-focus);
@@ -144,3 +146,32 @@
.navds-toggle-group--small > .navds-toggle-group__button > .navds-toggle-group__button-inner > svg {
font-size: 1.125rem;
}
@media (forced-colors: active) {
.navds-toggle-group {
border: 2px solid transparent;
}
.navds-toggle-group__button:hover {
color: highlight;
}
.navds-toggle-group__button[aria-checked="true"],
.navds-toggle-group__button[aria-pressed="true"] {
background-color: selecteditem;
color: selecteditemtext;
}
.navds-toggle-group__button[aria-checked="true"] > *,
.navds-toggle-group__button[aria-pressed="true"] > * {
forced-color-adjust: none;
}
.navds-toggle-group__button:active {
background-color: none !important;
}
.navds-toggle-group__button:focus-visible {
outline-color: highlight;
}
}
+2
View File
@@ -392,6 +392,8 @@
"--ac-combobox-list-item-loading-bg": "--a-surface-default",
"--ac-combobox-list-item-hover-border-left": "--a-border-strong",
"--ac-combobox-list-item-selected-hover-border-left": "--a-border-focus",
"--ac-combobox-list-item-max-selected-bg": "--a-surface-info-subtle",
"--ac-combobox-list-item-max-selected-border": "--a-border-info",
"--ac-combobox-error-border": "--a-border-danger"
},
"select": {
+28
View File
@@ -51,3 +51,31 @@
align-items: center;
justify-content: center;
}
@media (forced-colors: active) {
.navds-tooltip {
--__a-tooltip-high-contrast-border: 1px solid transparent;
border: var(--__a-tooltip-high-contrast-border);
}
.navds-tooltip[data-side="top"] .navds-tooltip__arrow {
border-bottom: var(--__a-tooltip-high-contrast-border);
border-right: var(--__a-tooltip-high-contrast-border);
}
.navds-tooltip[data-side="right"] .navds-tooltip__arrow {
border-bottom: var(--__a-tooltip-high-contrast-border);
border-left: var(--__a-tooltip-high-contrast-border);
}
.navds-tooltip[data-side="bottom"] .navds-tooltip__arrow {
border-left: var(--__a-tooltip-high-contrast-border);
border-top: var(--__a-tooltip-high-contrast-border);
}
.navds-tooltip[data-side="left"] .navds-tooltip__arrow {
border-top: var(--__a-tooltip-high-contrast-border);
border-right: var(--__a-tooltip-high-contrast-border);
}
}
+48
View File
@@ -1,5 +1,53 @@
# @navikt/ds-react
## 5.17.2
### Patch Changes
- :bug: Textarea: Sett riktig høyde når brukt i Modal + StrictMode ([#2679](https://github.com/navikt/aksel/pull/2679))
- DatePicker: Knapper fikk ikke riktig aria-label ([#2678](https://github.com/navikt/aksel/pull/2678))
- Textarea: debounce ResizeObserver for å unngå feilmelding ([#2685](https://github.com/navikt/aksel/pull/2685))
- Updated dependencies []:
- @navikt/ds-tokens@5.17.2
- @navikt/aksel-icons@5.17.2
## 5.17.1
### Patch Changes
- Eksponer variabelen "role" i Search-komponenten ([#2667](https://github.com/navikt/aksel/pull/2667))
- Updated dependencies []:
- @navikt/ds-tokens@5.17.1
- @navikt/aksel-icons@5.17.1
## 5.17.0
### Minor Changes
- Høykontrast: Komponenter støtter nå standard høykontrast-modus på Windows ([#2680](https://github.com/navikt/aksel/pull/2680))
### Patch Changes
- Updated dependencies []:
- @navikt/ds-tokens@5.17.0
- @navikt/aksel-icons@5.17.0
## 5.16.0
### Minor Changes
- :sparkles: Combobox: Mulighet for å begrense hvor mange valg bruker kan ta ([#2260](https://github.com/navikt/aksel/pull/2260))
### Patch Changes
- Updated dependencies [[`eff9b5ecc`](https://github.com/navikt/aksel/commit/eff9b5ecce2817e3823ce61be7635359eec98451)]:
- @navikt/aksel-icons@5.16.0
- @navikt/ds-tokens@5.16.0
## 5.15.1
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-react",
"version": "5.15.1",
"version": "5.17.2",
"description": "Aksel react-components for NAV designsystem",
"author": "Aksel | NAV designsystem team",
"license": "MIT",
@@ -38,8 +38,8 @@
},
"dependencies": {
"@floating-ui/react": "0.25.4",
"@navikt/aksel-icons": "^5.15.1",
"@navikt/ds-tokens": "^5.15.1",
"@navikt/aksel-icons": "^5.17.2",
"@navikt/ds-tokens": "^5.17.2",
"@radix-ui/react-tabs": "1.0.0",
"@radix-ui/react-toggle-group": "1.0.0",
"clsx": "^1.2.1",
@@ -11,7 +11,6 @@ import { DateWrapper } from "../parts/DateWrapper";
import { getLocaleFromString, labels } from "../utils";
import DatePickerStandalone from "./DatePickerStandalone";
import Caption from "./parts/Caption";
import DayButton from "./parts/DayButton";
import DropdownCaption from "./parts/DropdownCaption";
import { HeadRow } from "./parts/HeadRow";
import Row from "./parts/Row";
@@ -124,7 +123,6 @@ export const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>(
HeadRow,
WeekNumber,
Row,
Day: DayButton,
}}
className={cl("navds-date", className)}
classNames={{
@@ -5,7 +5,6 @@ import { DateRange, DayPicker, isMatch } from "react-day-picker";
import { omit } from "../../util";
import { getLocaleFromString, labels } from "../utils";
import Caption from "./parts/Caption";
import DayButton from "./parts/DayButton";
import DropdownCaption from "./parts/DropdownCaption";
import { HeadRow } from "./parts/HeadRow";
import Row from "./parts/Row";
@@ -94,7 +93,6 @@ export const DatePickerStandalone: DatePickerStandaloneType = forwardRef<
HeadRow,
WeekNumber,
Row,
Day: DayButton,
}}
className="navds-date"
classNames={{ vhidden: "navds-sr-only" }}
@@ -27,13 +27,13 @@ export const Default: StoryObj<DefaultStoryProps> = {
const [open, setOpen] = useState(false);
const rangeCtx = useRangeDatepicker({
fromDate: new Date("Aug 23 2020"),
toDate: new Date("Aug 23 2023"),
fromDate: new Date("Aug 23 2023"),
toDate: new Date("Aug 23 2029"),
});
const singleCtx = useDatepicker({
fromDate: new Date("Aug 23 2020"),
toDate: new Date("Aug 23 2023"),
toDate: new Date("Aug 23 2029"),
});
const newProps = {
@@ -42,7 +42,7 @@ export const Default: StoryObj<DefaultStoryProps> = {
open,
onClose: () => setOpen(false),
fromDate: new Date("Aug 23 2020"),
toDate: new Date("Aug 23 2023"),
toDate: new Date("Aug 23 2029"),
}
: {}),
};
@@ -1,7 +1,8 @@
import { getUnixTime } from "date-fns";
import React from "react";
import { Day, useDayPicker } from "react-day-picker";
import { useDayPicker } from "react-day-picker";
import { Hide } from "../../../layout/responsive";
import DayButton from "./DayButton";
import WeekNumber from "./WeekNumber";
/**
@@ -37,7 +38,7 @@ function Row(props: RowProps): JSX.Element {
style={styles.cell}
key={getUnixTime(date)}
>
<Day displayMonth={props.displayMonth} date={date} />
<DayButton displayMonth={props.displayMonth} date={date} />
</td>
))}
</tr>
@@ -89,7 +89,7 @@ export const Combobox = forwardRef<
"navds-combobox__wrapper-inner navds-text-field__input",
{
"navds-combobox__wrapper-inner--virtually-unfocused":
activeDecendantId !== null,
activeDecendantId !== undefined,
},
)}
onClick={focusInput}
@@ -43,6 +43,7 @@ const ComboboxProvider = forwardRef<HTMLInputElement, ComboboxProps>(
isMultiSelect,
onToggleSelected,
selectedOptions,
maxSelected,
options,
value,
onChange,
@@ -71,6 +72,7 @@ const ComboboxProvider = forwardRef<HTMLInputElement, ComboboxProps>(
allowNewValues,
isMultiSelect,
selectedOptions,
maxSelected,
onToggleSelected,
options,
}}
@@ -57,7 +57,6 @@ const ComboboxWrapper = ({
)}
onFocus={onFocusInsideWrapper}
onBlur={onBlurWrapper}
tabIndex={-1}
>
{children}
</div>
@@ -27,111 +27,150 @@ const FilteredOptions = () => {
activeDecendantId,
virtualFocus,
} = useFilteredOptionsContext();
const { isMultiSelect, selectedOptions, toggleOption } =
const { isMultiSelect, selectedOptions, toggleOption, maxSelected } =
useSelectedOptionsContext();
const isDisabled = (option) =>
maxSelected?.isLimitReached && !selectedOptions.includes(option);
const shouldRenderNonSelectables =
maxSelected?.isLimitReached || // Render maxSelected message
isLoading || // Render loading message
(!isLoading && filteredOptions.length === 0); // Render no hits message
const shouldRenderFilteredOptionsList =
(allowNewValues && isValueNew && !maxSelected?.isLimitReached) || // Render add new option
filteredOptions.length > 0; // Render filtered options
return (
<ul
ref={setFilteredOptionsRef}
<div
className={cl("navds-combobox__list", {
"navds-combobox__list--closed": !isListOpen,
"navds-combobox__list--with-hover": isMouseLastUsedInputDevice,
})}
id={filteredOptionsUtil.getFilteredOptionsId(id)}
role="listbox"
tabIndex={-1}
>
{isLoading && (
<li
className="navds-combobox__list-item--loading"
role="option"
aria-selected={false}
id={filteredOptionsUtil.getIsLoadingId(id)}
data-no-focus="true"
>
<Loader aria-label="Søker..." />
</li>
{shouldRenderNonSelectables && (
<div className="navds-combobox__list_non-selectables" role="status">
{maxSelected?.isLimitReached && (
<div
className="navds-combobox__list-item--max-selected"
id={filteredOptionsUtil.getMaxSelectedOptionsId(id)}
>
{maxSelected.message ??
`${selectedOptions.length} av ${maxSelected.limit} er valgt.`}
</div>
)}
{isLoading && (
<div
className="navds-combobox__list-item--loading"
id={filteredOptionsUtil.getIsLoadingId(id)}
>
<Loader title="Søker..." />
</div>
)}
{!isLoading && filteredOptions.length === 0 && (
<div
className="navds-combobox__list-item--no-options"
id={filteredOptionsUtil.getNoHitsId(id)}
>
Ingen søketreff
</div>
)}
</div>
)}
{isValueNew && allowNewValues && (
<li
tabIndex={-1}
onMouseMove={() => {
if (
activeDecendantId !== filteredOptionsUtil.getAddNewOptionId(id)
) {
virtualFocus.moveFocusToElement(
filteredOptionsUtil.getAddNewOptionId(id),
);
setIsMouseLastUsedInputDevice(true);
}
}}
onPointerUp={(event) => {
toggleOption(value, event);
if (!isMultiSelect && !selectedOptions.includes(value))
toggleIsListOpen(false);
}}
id={filteredOptionsUtil.getAddNewOptionId(id)}
className={cl("navds-combobox__list-item__new-option", {
"navds-combobox__list-item__new-option--focus":
activeDecendantId === filteredOptionsUtil.getAddNewOptionId(id),
})}
role="option"
aria-selected={false}
{shouldRenderFilteredOptionsList && (
<ul
ref={setFilteredOptionsRef}
role="listbox"
className="navds-combobox__list-options"
>
<PlusIcon aria-hidden />
<BodyShort size={size}>
Legg til{" "}
<Label as="span" size={size}>
&#8220;{value}&#8221;
</Label>
</BodyShort>
</li>
{isValueNew && !maxSelected?.isLimitReached && allowNewValues && (
<li
tabIndex={-1}
onMouseMove={() => {
if (
activeDecendantId !==
filteredOptionsUtil.getAddNewOptionId(id)
) {
virtualFocus.moveFocusToElement(
filteredOptionsUtil.getAddNewOptionId(id),
);
setIsMouseLastUsedInputDevice(true);
}
}}
onPointerUp={(event) => {
toggleOption(value, event);
if (!isMultiSelect && !selectedOptions.includes(value))
toggleIsListOpen(false);
}}
id={filteredOptionsUtil.getAddNewOptionId(id)}
className={cl(
"navds-combobox__list-item navds-combobox__list-item--new-option",
{
"navds-combobox__list-item--new-option--focus":
activeDecendantId ===
filteredOptionsUtil.getAddNewOptionId(id),
},
)}
role="option"
aria-selected={false}
>
<PlusIcon aria-hidden />
<BodyShort size={size}>
Legg til{" "}
<Label as="span" size={size}>
&#8220;{value}&#8221;
</Label>
</BodyShort>
</li>
)}
{filteredOptions.map((option) => (
<li
className={cl("navds-combobox__list-item", {
"navds-combobox__list-item--focus":
activeDecendantId ===
filteredOptionsUtil.getOptionId(id, option),
"navds-combobox__list-item--selected":
selectedOptions.includes(option),
})}
data-no-focus={isDisabled(option) || undefined}
id={filteredOptionsUtil.getOptionId(id, option)}
key={option}
tabIndex={-1}
onMouseMove={() => {
if (
activeDecendantId !==
filteredOptionsUtil.getOptionId(id, option)
) {
virtualFocus.moveFocusToElement(
filteredOptionsUtil.getOptionId(id, option),
);
setIsMouseLastUsedInputDevice(true);
}
}}
onPointerUp={(event) => {
if (isDisabled(option)) {
return;
}
toggleOption(option, event);
if (!isMultiSelect && !selectedOptions.includes(option)) {
toggleIsListOpen(false);
}
}}
role="option"
aria-selected={selectedOptions.includes(option)}
aria-disabled={isDisabled(option) || undefined}
>
<BodyShort size={size}>{option}</BodyShort>
{selectedOptions.includes(option) && <CheckmarkIcon />}
</li>
))}
</ul>
)}
{!isLoading && filteredOptions.length === 0 && (
<li
className="navds-combobox__list-item__no-options"
role="option"
aria-selected={false}
id={filteredOptionsUtil.getNoHitsId(id)}
data-no-focus="true"
>
Ingen søketreff
</li>
)}
{filteredOptions.map((option) => (
<li
className={cl("navds-combobox__list-item", {
"navds-combobox__list-item--focus":
activeDecendantId === filteredOptionsUtil.getOptionId(id, option),
"navds-combobox__list-item--selected":
selectedOptions.includes(option),
})}
id={filteredOptionsUtil.getOptionId(id, option)}
key={option}
tabIndex={-1}
onMouseMove={() => {
if (
activeDecendantId !== filteredOptionsUtil.getOptionId(id, option)
) {
virtualFocus.moveFocusToElement(
filteredOptionsUtil.getOptionId(id, option),
);
setIsMouseLastUsedInputDevice(true);
}
}}
onPointerUp={(event) => {
toggleOption(option, event);
if (!isMultiSelect && !selectedOptions.includes(option))
toggleIsListOpen(false);
}}
role="option"
aria-selected={selectedOptions.includes(option)}
>
<BodyShort size={size}>{option}</BodyShort>
{selectedOptions.includes(option) && <CheckmarkIcon />}
</li>
))}
</ul>
</div>
);
};
@@ -7,8 +7,11 @@ const isPartOfText = (value, text) =>
const isValueInList = (value, list) =>
list?.find((listItem) => normalizeText(value) === normalizeText(listItem));
const getMatchingValuesFromList = (value, list) =>
list?.filter((listItem) => isPartOfText(value, listItem));
const getMatchingValuesFromList = (value, list, alwaysIncluded) =>
list?.filter(
(listItem) =>
isPartOfText(value, listItem) || alwaysIncluded.includes(listItem),
);
const getFilteredOptionsId = (comboboxId: string) =>
`${comboboxId}-filtered-options`;
@@ -25,6 +28,9 @@ const getIsLoadingId = (comboboxId: string) => `${comboboxId}-is-loading`;
const getNoHitsId = (comboboxId: string) => `${comboboxId}-no-hits`;
const getMaxSelectedOptionsId = (comboboxId: string) =>
`${comboboxId}-max-selected-options`;
export default {
normalizeText,
isPartOfText,
@@ -35,4 +41,5 @@ export default {
getOptionId,
getIsLoadingId,
getNoHitsId,
getMaxSelectedOptionsId,
};
@@ -9,6 +9,7 @@ import React, {
} from "react";
import { useClientLayoutEffect, usePrevious } from "../../../util/hooks";
import { useInputContext } from "../Input/inputContext";
import { useSelectedOptionsContext } from "../SelectedOptions/selectedOptionsContext";
import { useCustomOptionsContext } from "../customOptionsContext";
import { ComboboxProps } from "../types";
import filteredOptionsUtils from "./filtered-options-util";
@@ -70,6 +71,7 @@ export const FilteredOptionsProvider = ({
setSearchTerm,
shouldAutocomplete,
} = useInputContext();
const { selectedOptions, maxSelected } = useSelectedOptionsContext();
const [isInternalListOpen, setInternalListOpen] = useState(false);
const { customOptions } = useCustomOptionsContext();
@@ -79,8 +81,18 @@ export const FilteredOptionsProvider = ({
return externalFilteredOptions;
}
const opts = [...customOptions, ...options];
return filteredOptionsUtils.getMatchingValuesFromList(searchTerm, opts);
}, [customOptions, externalFilteredOptions, options, searchTerm]);
return filteredOptionsUtils.getMatchingValuesFromList(
searchTerm,
opts,
selectedOptions,
);
}, [
customOptions,
externalFilteredOptions,
options,
searchTerm,
selectedOptions,
]);
const previousSearchTerm = usePrevious(searchTerm);
@@ -154,10 +166,17 @@ export const FilteredOptionsProvider = ({
activeOption = filteredOptionsUtils.getIsLoadingId(id);
}
}
return cl(activeOption, partialAriaDescribedBy) || undefined;
const maybeMaxSelectedOptionsId =
maxSelected?.isLimitReached &&
filteredOptionsUtils.getMaxSelectedOptionsId(id);
return (
cl(activeOption, maybeMaxSelectedOptionsId, partialAriaDescribedBy) ||
undefined
);
}, [
isListOpen,
isLoading,
maxSelected?.isLimitReached,
value,
partialAriaDescribedBy,
shouldAutocomplete,
@@ -1,11 +1,10 @@
import { Dispatch, SetStateAction, useState } from "react";
import { useState } from "react";
export type VirtualFocusType = {
activeElement: HTMLElement | undefined;
getElementById: (id: string) => HTMLElement | undefined;
isFocusOnTheTop: boolean;
isFocusOnTheBottom: boolean;
setIndex: Dispatch<SetStateAction<number>>;
isFocusOnTheTop: () => boolean;
isFocusOnTheBottom: () => boolean;
moveFocusUp: () => void;
moveFocusDown: () => void;
moveFocusToElement: (id: string) => void;
@@ -16,57 +15,77 @@ export type VirtualFocusType = {
const useVirtualFocus = (
containerRef: HTMLElement | null,
): VirtualFocusType => {
const [index, setIndex] = useState(-1);
const listOfAllChildren: HTMLElement[] = containerRef?.children
? Array.prototype.slice.call(containerRef?.children)
: [];
const elementsAbleToReceiveFocus = listOfAllChildren.filter(
(child) => child.getAttribute("data-no-focus") !== "true",
const [activeElement, setActiveElement] = useState<HTMLElement | undefined>(
undefined,
);
const activeElement = elementsAbleToReceiveFocus[index];
const getElementById = (id: string) =>
listOfAllChildren.find((element) => element.id === id);
const isFocusOnTheTop = index === 0;
const isFocusOnTheBottom = index === elementsAbleToReceiveFocus.length - 1;
const scrollToOption = (newIndex: number) => {
const indexOfElementToScrollTo = Math.min(
Math.max(newIndex, 0),
containerRef?.children.length || 0,
const getListOfAllChildren = (): HTMLElement[] =>
Array.from(containerRef?.children ?? []) as HTMLElement[];
const getElementsAbleToReceiveFocus = () =>
getListOfAllChildren().filter(
(child) => child.getAttribute("data-no-focus") !== "true",
);
if (containerRef?.children[indexOfElementToScrollTo]) {
const child = containerRef.children[indexOfElementToScrollTo];
const { top, bottom } = child.getBoundingClientRect();
const parentRect = containerRef.getBoundingClientRect();
if (top < parentRect.top || bottom > parentRect.bottom) {
child.scrollIntoView({ block: "nearest" });
}
const getElementById = (id: string) =>
getListOfAllChildren().find((element) => element.id === id);
const isFocusOnTheTop = () =>
activeElement
? getElementsAbleToReceiveFocus().indexOf(activeElement) === 0
: false;
const isFocusOnTheBottom = () => {
const elementsAbleToReceiveFocus = getElementsAbleToReceiveFocus();
return activeElement
? elementsAbleToReceiveFocus.indexOf(activeElement) ===
elementsAbleToReceiveFocus.length - 1
: false;
};
const _moveFocusAndScrollTo = (_element?: HTMLElement) => {
setActiveElement(_element);
_element?.scrollIntoView?.({ block: "nearest" });
};
const moveFocusUp = () => {
if (!activeElement) {
return;
}
const elementsAbleToReceiveFocus = getElementsAbleToReceiveFocus();
const _currentIndex = elementsAbleToReceiveFocus.indexOf(activeElement);
const elementAbove = elementsAbleToReceiveFocus[_currentIndex - 1];
if (_currentIndex === 0) {
setActiveElement(undefined);
} else {
_moveFocusAndScrollTo(elementAbove);
}
};
const _moveFocusAndScrollTo = (_index: number) => {
setIndex(_index);
scrollToOption(_index);
const moveFocusDown = () => {
const elementsAbleToReceiveFocus = getElementsAbleToReceiveFocus();
if (!activeElement) {
_moveFocusAndScrollTo(elementsAbleToReceiveFocus[0]);
return;
}
const _currentIndex = elementsAbleToReceiveFocus.indexOf(activeElement);
if (_currentIndex === elementsAbleToReceiveFocus.length - 1) {
return;
} else {
_moveFocusAndScrollTo(elementsAbleToReceiveFocus[_currentIndex + 1]);
}
};
const moveFocusUp = () => _moveFocusAndScrollTo(Math.max(index - 1, -1));
const moveFocusDown = () =>
_moveFocusAndScrollTo(
Math.min(index + 1, elementsAbleToReceiveFocus.length - 1),
const moveFocusToTop = () => _moveFocusAndScrollTo(undefined);
const moveFocusToBottom = () => {
const elementsAbleToReceiveFocus = getElementsAbleToReceiveFocus();
return _moveFocusAndScrollTo(
elementsAbleToReceiveFocus[elementsAbleToReceiveFocus.length - 1],
);
const moveFocusToTop = () => _moveFocusAndScrollTo(-1);
const moveFocusToBottom = () =>
_moveFocusAndScrollTo(elementsAbleToReceiveFocus.length - 1);
};
const moveFocusToElement = (id: string) => {
const thisElement = elementsAbleToReceiveFocus.find(
(_element) => _element.getAttribute("id") === id,
const _element = getElementsAbleToReceiveFocus().find(
(_focusableElement) => _focusableElement.getAttribute("id") === id,
);
const indexOfElement = thisElement
? elementsAbleToReceiveFocus.indexOf(thisElement)
: -1;
if (indexOfElement >= 0) {
setIndex(indexOfElement);
if (_element) {
setActiveElement(_element);
}
};
@@ -75,7 +94,6 @@ const useVirtualFocus = (
getElementById,
isFocusOnTheTop,
isFocusOnTheBottom,
setIndex,
moveFocusUp,
moveFocusDown,
moveFocusToElement,
@@ -101,9 +101,11 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
onEnter(e);
break;
case "Home":
toggleIsListOpen(false);
virtualFocus.moveFocusToTop();
break;
case "End":
toggleIsListOpen(true);
virtualFocus.moveFocusToBottom();
break;
default:
@@ -135,7 +137,7 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
// Otherwise ignore keystrokes, so it doesn't interfere with text editing
if (isListOpen && activeDecendantId) {
e.preventDefault();
if (virtualFocus.isFocusOnTheTop) {
if (virtualFocus.isFocusOnTheTop()) {
toggleIsListOpen(false);
}
virtualFocus.moveFocusUp();
@@ -8,7 +8,7 @@ import React, {
import { usePrevious } from "../../../util/hooks";
import { useInputContext } from "../Input/inputContext";
import { useCustomOptionsContext } from "../customOptionsContext";
import { ComboboxProps } from "../types";
import { ComboboxProps, MaxSelected } from "../types";
type SelectedOptionsContextType = {
addSelectedOption: (option: string) => void;
@@ -16,6 +16,7 @@ type SelectedOptionsContextType = {
removeSelectedOption: (option: string) => void;
prevSelectedOptions?: string[];
selectedOptions: string[];
maxSelected?: MaxSelected & { isLimitReached: boolean };
setSelectedOptions: (any) => void;
toggleOption: (
option: string,
@@ -39,6 +40,7 @@ export const SelectedOptionsProvider = ({
| "options"
| "selectedOptions"
| "onToggleSelected"
| "maxSelected"
>;
}) => {
const { clearInput, focusInput } = useInputContext();
@@ -54,6 +56,7 @@ export const SelectedOptionsProvider = ({
selectedOptions: externalSelectedOptions,
onToggleSelected,
options,
maxSelected,
} = value;
const [internalSelectedOptions, setSelectedOptions] = useState<string[]>([]);
const selectedOptions = useMemo(
@@ -129,6 +132,9 @@ export const SelectedOptionsProvider = ({
const prevSelectedOptions = usePrevious<string[]>(selectedOptions);
const isLimitReached =
!!maxSelected?.limit && selectedOptions.length >= maxSelected.limit;
const selectedOptionsState = {
addSelectedOption,
isMultiSelect,
@@ -137,6 +143,10 @@ export const SelectedOptionsProvider = ({
selectedOptions,
setSelectedOptions,
toggleOption,
maxSelected: maxSelected && {
...maxSelected,
isLimitReached,
},
};
return (
@@ -1,6 +1,6 @@
import { Meta, StoryFn, StoryObj } from "@storybook/react";
import { expect, fn, userEvent, within } from "@storybook/test";
import React, { useId, useMemo, useState } from "react";
import React, { useId, useMemo, useRef, useState } from "react";
import { Chips, ComboboxProps, TextField, UNSAFE_Combobox } from "../../index";
export default {
@@ -37,11 +37,16 @@ Default.args = {
label: "Hva er dine favorittfrukter?",
shouldAutocomplete: true,
isLoading: false,
isMultiSelect: false,
allowNewValues: false,
};
Default.argTypes = {
isListOpen: {
control: { type: "boolean" },
},
maxSelected: {
control: { type: "number" },
},
size: {
options: ["medium", "small"],
defaultValue: "medium",
@@ -284,6 +289,36 @@ export const ComboboxSizes = () => (
</>
);
export const MaxSelectedOptions: StoryFunction = () => {
const id = useId();
const [value, setValue] = useState<string | undefined>("");
const [selectedOptions, setSelectedOptions] = useState([
options[0],
options[1],
]);
const comboboxRef = useRef<HTMLInputElement>(null);
return (
<UNSAFE_Combobox
id={id}
label="Komboboks med begrenset antall valg"
options={options}
maxSelected={{ limit: 2 }}
selectedOptions={selectedOptions}
onToggleSelected={(option, isSelected) =>
isSelected
? setSelectedOptions([...selectedOptions, option])
: setSelectedOptions(selectedOptions.filter((o) => o !== option))
}
isMultiSelect
allowNewValues
isListOpen={comboboxRef.current ? undefined : true}
value={value}
onChange={(event) => setValue(event?.target.value)}
ref={comboboxRef}
/>
);
};
export const WithError: StoryFunction = (props) => {
const [hasSelectedValue, setHasSelectedValue] = useState(false);
const [isLoading, setIsLoading] = useState(false);
@@ -74,9 +74,7 @@ describe("Render combobox", () => {
it("Should show loading icon when loading (used for async search)", async () => {
render(<App options={[]} isListOpen isLoading />);
expect(
await screen.findByRole("option", { name: "venter..." }),
).toBeInTheDocument();
expect(await screen.findByText("Søker...")).toBeInTheDocument();
});
});
@@ -1,6 +1,17 @@
import React, { ChangeEvent, InputHTMLAttributes } from "react";
import { FormFieldProps } from "../useFormField";
export type MaxSelected = {
/**
* The limit for maximum selected options
*/
limit: number;
/**
* Override the message to display when the limit for maximum selected options has been reached
*/
message?: string;
};
export interface ComboboxProps
extends FormFieldProps,
Omit<InputHTMLAttributes<HTMLInputElement>, "size" | "onChange" | "value"> {
@@ -97,6 +108,10 @@ export interface ComboboxProps
* e.g. for a filter, where options can be toggled elsewhere/programmatically.
*/
selectedOptions?: string[];
/**
* Options for the maximum number of selected options.
*/
maxSelected?: MaxSelected;
/**
* Set to "true" to enable inline autocomplete.
*
@@ -66,6 +66,10 @@ export interface SearchProps
* Exposes the HTML size attribute
*/
htmlSize?: number | string;
/*
* Exposes role attribute
*/
role?: string
}
interface SearchComponent
@@ -114,6 +118,7 @@ export const Search = forwardRef<HTMLInputElement, SearchProps>(
onChange,
onSearchClick,
htmlSize,
role,
...rest
} = props;
@@ -205,7 +210,7 @@ export const Search = forwardRef<HTMLInputElement, SearchProps>(
value={value ?? internalValue}
onChange={(e) => handleChange(e.target.value)}
type="search"
role="searchbox"
role={role ?? 'searchbox'}
className={cl(
className,
"navds-search__input",
@@ -1,6 +1,7 @@
import { Meta, StoryFn, StoryObj } from "@storybook/react";
import React, { useState } from "react";
import { Button } from "../../button";
import { Modal } from "../../modal";
import { Textarea } from "../index";
const meta: Meta<typeof Textarea> = {
@@ -186,3 +187,20 @@ AutoScrollbar.argTypes = {
maxRows: { type: "number" },
minRows: { type: "number" },
};
export const ModalStrictMode: StoryFn<typeof Textarea> = () => {
// Story added after fixing an issue where TextareaAutoSize would reach max re-renders
// and set the height to 2px when used in StrictMode in a Modal that is initially open.
return (
<React.StrictMode>
<Modal open>
<Modal.Body>
<Textarea label="Har du noen tilbakemeldinger?" />
</Modal.Body>
</Modal>
</React.StrictMode>
);
};
ModalStrictMode.parameters = {
chromatic: { disable: true }, // Not reproducable in Chromatic
};
@@ -10,7 +10,7 @@ type State = {
overflow?: boolean | undefined;
};
const updateState = (
const checkState = (
prevState: State,
newState: State,
renders: React.MutableRefObject<number>,
@@ -25,20 +25,12 @@ const updateState = (
prevState.overflow !== overflow)
) {
renders.current += 1;
return {
overflow,
outerHeightStyle,
};
return newState;
}
if (process.env.NODE_ENV !== "production") {
if (renders.current === 20) {
console.error(
[
"Textarea: Too many re-renders. The layout is unstable.",
"TextareaAutosize limits the number of renders to prevent an infinite loop.",
].join("\n"),
);
}
if (process.env.NODE_ENV !== "production" && renders.current === 20) {
console.error(
"Textarea: Too many re-renders. The layout is unstable. TextareaAutosize limits the number of renders to prevent an infinite loop.",
);
}
return prevState;
};
@@ -92,9 +84,7 @@ const TextareaAutosize = forwardRef<HTMLTextAreaElement, TextareaAutosizeProps>(
) => {
const { current: isControlled } = useRef(value != null);
const inputRef = useRef<HTMLTextAreaElement>(null);
const handleRef = useMergeRefs(inputRef, ref);
const shadowRef = useRef<HTMLTextAreaElement>(null);
const renders = useRef(0);
const [state, setState] = useState<State>({ outerHeightStyle: 0 });
@@ -153,35 +143,31 @@ const TextareaAutosize = forwardRef<HTMLTextAreaElement, TextareaAutosizeProps>(
return { outerHeightStyle, overflow };
}, [maxRows, minRows, other.placeholder]);
const syncHeight = React.useCallback(() => {
const syncHeight = () => {
const newState = getUpdatedState();
if (isEmpty(newState)) {
return;
}
setState((prevState) => updateState(prevState, newState, renders));
}, [getUpdatedState]);
setState((prevState) => checkState(prevState, newState, renders));
};
useClientLayoutEffect(() => {
const syncHeightWithFlushSync = () => {
const newState = getUpdatedState();
if (isEmpty(newState)) {
return;
}
// In React 18, state updates in a ResizeObserver's callback are happening after
// the paint, this leads to an infinite rendering.
//
// Using flushSync ensures that the states is updated before the next pain.
// Using flushSync ensures that the state is updated before the next paint.
// Related issue - https://github.com/facebook/react/issues/24331
ReactDOM.flushSync(() => {
setState((prevState) => updateState(prevState, newState, renders));
setState((prevState) => checkState(prevState, newState, renders));
});
};
const handleResize = () => {
const handleResize = debounce(() => {
renders.current = 0;
if (inputRef.current?.style.height || inputRef.current?.style.width) {
@@ -193,13 +179,12 @@ const TextareaAutosize = forwardRef<HTMLTextAreaElement, TextareaAutosizeProps>(
}
syncHeightWithFlushSync();
};
});
const debounceHandleResize = debounce(handleResize);
const input = inputRef.current!;
const containerWindow = ownerWindow(input);
containerWindow.addEventListener("resize", debounceHandleResize);
containerWindow.addEventListener("resize", handleResize);
let resizeObserver: ResizeObserver;
if (typeof ResizeObserver !== "undefined") {
@@ -208,8 +193,8 @@ const TextareaAutosize = forwardRef<HTMLTextAreaElement, TextareaAutosizeProps>(
}
return () => {
debounceHandleResize.clear();
containerWindow.removeEventListener("resize", debounceHandleResize);
handleResize.clear();
containerWindow.removeEventListener("resize", handleResize);
if (resizeObserver) {
resizeObserver.disconnect();
}
@@ -0,0 +1,147 @@
import * as React from "react";
import { Box } from "../../../layout/box";
import { HStack } from "../../../layout/stack";
import { createDescendantContext } from "./useDescendant";
export default {
title: "Utilities/Descendants",
parameters: {
chromatic: { disable: true },
},
};
const [
DescendantsProvider,
/* eslint-disable @typescript-eslint/no-unused-vars */
_useDescendantsContext,
useDescendants,
useDescendant,
] = createDescendantContext<HTMLDivElement, { value?: string }>();
function Select({ children }: { children?: React.ReactNode }) {
const descendants = useDescendants();
const count = descendants.count();
React.useEffect(() => {
descendants.last()?.node.focus();
}, [descendants, count]);
return (
<DescendantsProvider value={descendants}>{children}</DescendantsProvider>
);
}
function Option({ value, disabled }: { value?: string; disabled?: boolean }) {
const { register, index, descendants } = useDescendant({
disabled,
});
return (
<Box
ref={register}
role="button"
tabIndex={0}
data-value={value}
onKeyDown={(event) => {
if (event.key === "ArrowDown") {
descendants.next(index)?.node.focus();
} else if (event.key === "ArrowUp") {
descendants.prev(index)?.node.focus();
}
}}
>
Option {index + 1}
</Box>
);
}
export const DynamicUpdates = () => {
const [done, setDone] = React.useState(false);
React.useEffect(() => {
const interval = setInterval(() => setDone((x) => !x), 3000);
return () => window.clearInterval(interval);
}, []);
return (
<Select>
<Option value="option 1" />
<div>
<div>
<Option value="option 2" />
{done && (
<div>
<Option value="option 3" />
<Option value="option 4" />
</div>
)}
</div>
<Option value="option 5" disabled />
</div>
{done && (
<div>
<Option value="option 6" />
<Option value="option 7" />
</div>
)}
</Select>
);
};
function NumberInputWrapper({ children }: { children?: React.ReactNode }) {
const descendants = useDescendants();
React.useEffect(() => {
descendants.first()?.node.focus();
}, [descendants]);
return (
<DescendantsProvider value={descendants}>
<HStack gap="1">{children}</HStack>
</DescendantsProvider>
);
}
function Input() {
const [focused, setFocused] = React.useState(false);
const { register, index, descendants } = useDescendant();
return (
<input
style={{
width: "3rem",
height: "3rem",
borderRadius: "4px",
textAlign: "center",
border: "1px solid var(--a-border-default)",
}}
placeholder={focused ? "" : "0"}
onFocus={() => setFocused(true)}
onBlur={() => setFocused(false)}
ref={register}
type="tel"
autoCapitalize="none"
autoComplete="false"
inputMode="numeric"
onKeyDown={(event) => {
if (event.key === "ArrowRight") {
descendants.next(index, false)?.node.focus();
}
if (event.key === "ArrowLeft") {
descendants.prev(index, false)?.node.focus();
}
}}
/>
);
}
export const NumberInput = () => {
return (
<NumberInputWrapper>
<Input />
<Input />
<Input />
</NumberInputWrapper>
);
};
@@ -0,0 +1,161 @@
/**
* https://github.com/chakra-ui/chakra-ui/tree/5ec0be610b5a69afba01a9c22365155c1b519136/packages/components/descendant
*/
import { getNextIndex, getPrevIndex, isElement, sortNodes } from "./utils";
export type DescendantOptions<T = object> = T & {
/**
* If `true`, the item will be registered in all nodes map
* but omitted from enabled nodes map
*/
disabled?: boolean;
};
export type Descendant<T, K> = DescendantOptions<K> & {
/**
* DOM element of the item
*/
node: T;
/**
* index of item in all nodes map and enabled nodes map
*/
index: number;
};
/**
* @internal
*
* Class to manage descendants and their relative indices in the DOM.
* It uses `node.compareDocumentPosition(...)` under the hood
*/
export class DescendantsManager<
T extends HTMLElement,
K extends Record<string, any> = object,
> {
private descendants = new Map<T, Descendant<T, K>>();
register = (nodeOrOptions: T | null | DescendantOptions<K>) => {
if (nodeOrOptions == null) return;
if (isElement(nodeOrOptions)) {
return this.registerNode(nodeOrOptions);
}
return (node: T | null) => {
this.registerNode(node, nodeOrOptions);
};
};
unregister = (node: T) => {
this.descendants.delete(node);
const sorted = sortNodes(Array.from(this.descendants.keys()));
this.assignIndex(sorted);
};
destroy = () => {
this.descendants.clear();
};
private assignIndex = (descendants: Node[]) => {
this.descendants.forEach((descendant) => {
const index = descendants.indexOf(descendant.node);
descendant.index = index;
descendant.node.dataset["index"] = descendant.index.toString();
});
};
count = () => this.descendants.size;
enabledCount = () => this.enabledValues().length;
values = () => {
const values = Array.from(this.descendants.values());
return values.sort((a, b) => a.index - b.index);
};
enabledValues = () => {
return this.values().filter((descendant) => !descendant.disabled);
};
item = (index: number) => {
if (this.count() === 0) return undefined;
return this.values()[index];
};
enabledItem = (index: number) => {
if (this.enabledCount() === 0) return undefined;
return this.enabledValues()[index];
};
first = () => this.item(0);
firstEnabled = () => this.enabledItem(0);
last = () => this.item(this.descendants.size - 1);
lastEnabled = () => {
const lastIndex = this.enabledValues().length - 1;
return this.enabledItem(lastIndex);
};
indexOf = (node: T | null) => {
if (!node) return -1;
return this.descendants.get(node)?.index ?? -1;
};
enabledIndexOf = (node: T | null) => {
if (node == null) return -1;
return this.enabledValues().findIndex((i) => i.node.isSameNode(node));
};
next = (index: number, loop = true) => {
const next = getNextIndex(index, this.count(), loop);
return this.item(next);
};
nextEnabled = (index: number, loop = true) => {
const item = this.item(index);
if (!item) return;
const enabledIndex = this.enabledIndexOf(item.node);
const nextEnabledIndex = getNextIndex(
enabledIndex,
this.enabledCount(),
loop,
);
return this.enabledItem(nextEnabledIndex);
};
prev = (index: number, loop = true) => {
const prev = getPrevIndex(index, this.count() - 1, loop);
return this.item(prev);
};
prevEnabled = (index: number, loop = true) => {
const item = this.item(index);
if (!item) return;
const enabledIndex = this.enabledIndexOf(item.node);
const prevEnabledIndex = getPrevIndex(
enabledIndex,
this.enabledCount() - 1,
loop,
);
return this.enabledItem(prevEnabledIndex);
};
private registerNode = (node: T | null, options?: DescendantOptions<K>) => {
if (!node || this.descendants.has(node)) return;
const keys = Array.from(this.descendants.keys()).concat(node);
const sorted = sortNodes(keys);
if (options?.disabled) {
options.disabled = !!options.disabled;
}
const descendant = { node, index: -1, ...options };
this.descendants.set(node, descendant as Descendant<T, K>);
this.assignIndex(sorted);
};
}
@@ -0,0 +1,111 @@
/**
* https://github.com/chakra-ui/chakra-ui/tree/5ec0be610b5a69afba01a9c22365155c1b519136/packages/components/descendant
*/
import React, { useRef, useState } from "react";
import { createContext } from "../../create-context";
import { useClientLayoutEffect } from "../useClientLayoutEffect";
import { mergeRefs } from "../useMergeRefs";
import { DescendantOptions, DescendantsManager } from "./descendant";
import { cast } from "./utils";
/**
* @internal
* Initializing DescendantsManager
*/
function useDescendants<
T extends HTMLElement = HTMLElement,
K extends Record<string, any> = object,
>() {
const descendants = useRef(new DescendantsManager<T, K>()).current;
useClientLayoutEffect(() => {
return () => {
descendants.destroy();
};
});
return descendants;
}
const [DescendantsContextProvider, useDescendantsContext] = createContext<
ReturnType<typeof useDescendants>
>({
name: "DescendantsProvider",
errorMessage: "useDescendantsContext must be used within DescendantsProvider",
});
/**
* @internal
* This hook provides information to descendant component:
* - Index compared to other descendants
* - ref callback to register the descendant
* - Its enabled index compared to other enabled descendants
*/
function useDescendant<
T extends HTMLElement = HTMLElement,
K extends Record<string, any> = object,
>(options?: DescendantOptions<K>) {
const descendants = useDescendantsContext();
const [index, setIndex] = useState(-1);
const ref = useRef<T>(null);
useClientLayoutEffect(() => {
return () => {
if (!ref.current) return;
descendants.unregister(ref.current);
};
}, []);
useClientLayoutEffect(() => {
if (!ref.current) return;
const dataIndex = Number(ref.current.dataset["index"]);
if (index != dataIndex && !Number.isNaN(dataIndex)) {
setIndex(dataIndex);
}
});
const refCallback = options
? cast<React.RefCallback<T>>(descendants.register(options))
: cast<React.RefCallback<T>>(descendants.register);
return {
descendants,
index,
enabledIndex: descendants.enabledIndexOf(ref.current),
register: mergeRefs([refCallback, ref]),
};
}
/**
* Provides strongly typed versions of the context provider and hooks above.
*/
export function createDescendantContext<
T extends HTMLElement = HTMLElement,
K extends Record<string, any> = object,
>() {
const ContextProvider = cast<React.Provider<DescendantsManager<T, K>>>(
(props) => (
<DescendantsContextProvider {...props.value}>
{props.children}
</DescendantsContextProvider>
),
);
const _useDescendantsContext = () =>
cast<DescendantsManager<T, K>>(useDescendantsContext());
const _useDescendant = (options?: DescendantOptions<K>) =>
useDescendant<T, K>(options);
const _useDescendants = () => useDescendants<T, K>();
return [
// context provider
ContextProvider,
// call this when you need to read from context
_useDescendantsContext,
// descendants state information, to be called and passed to `ContextProvider`
_useDescendants,
// descendant index information
_useDescendant,
] as const;
}
@@ -0,0 +1,56 @@
/**
* Sort an array of DOM nodes according to the HTML tree order
* @see http://www.w3.org/TR/html5/infrastructure.html#tree-order
* Inspired by
* - https://github.com/floating-ui/floating-ui/blob/8e449abb0bfda143c6a6eb01d3e6943c095b744f/packages/react/src/components/FloatingList.tsx#L8
* - https://github.com/chakra-ui/chakra-ui/tree/5ec0be610b5a69afba01a9c22365155c1b519136/packages/components/descendant
*/
export function sortNodes(nodes: Node[]) {
return nodes.sort((a, b) => {
const compare = a.compareDocumentPosition(b);
if (
compare & Node.DOCUMENT_POSITION_FOLLOWING ||
compare & Node.DOCUMENT_POSITION_CONTAINED_BY
) {
// a < b
return -1;
}
if (
compare & Node.DOCUMENT_POSITION_PRECEDING ||
compare & Node.DOCUMENT_POSITION_CONTAINS
) {
// a > b
return 1;
}
if (
compare & Node.DOCUMENT_POSITION_DISCONNECTED ||
compare & Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC
) {
throw Error("Cannot sort the given nodes.");
} else {
return 0;
}
});
}
export const isElement = (el: any): el is HTMLElement =>
typeof el == "object" &&
"nodeType" in el &&
el.nodeType === Node.ELEMENT_NODE;
export function getNextIndex(current: number, max: number, loop: boolean) {
let next = current + 1;
if (loop && next >= max) next = 0;
return next;
}
export function getPrevIndex(current: number, max: number, loop: boolean) {
let next = current - 1;
if (loop && next < 0) next = max;
return next;
}
export const cast = <T>(value: any) => value as T;
+12
View File
@@ -1,5 +1,17 @@
# @navikt/ds-tailwind
## 5.17.2
## 5.17.1
## 5.17.0
## 5.16.0
### Minor Changes
- Tailwind: La til ny `maxWidth` for text ([#2652](https://github.com/navikt/aksel/pull/2652))
## 5.15.1
## 5.15.0
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-tailwind",
"version": "5.15.1",
"version": "5.17.2",
"description": "Tailwind config based on @navikt/ds-tokens",
"author": "Aksel | NAV designsystem team",
"keywords": [
@@ -24,7 +24,7 @@
"directory": "@navikt/core/tailwind"
},
"devDependencies": {
"@navikt/ds-tokens": "^5.15.1",
"@navikt/ds-tokens": "^5.17.2",
"@types/jest": "^29.0.0",
"color": "4.2.3",
"jest": "^29.0.0",
+6
View File
@@ -0,0 +1,6 @@
/**
* @note This is hardcoded just for the case `--a-text-width-max` at the moment
*/
export const getMaxWidth = (tokens: { [key: string]: string | number }) => {
return { text: tokens["text-width-max"] };
};
+2 -2
View File
@@ -2,6 +2,7 @@ import { writeFileSync } from "fs";
import * as TokensBuild from "@navikt/ds-tokens/dist/tokens";
import { getColors } from "./colors";
import { getBreakpoints } from "./getBreakpoints";
import { getMaxWidth } from "./getMaxWidth";
import kebabCase from "./kebabCase";
import Reducer from "./reducer";
@@ -26,8 +27,7 @@ const config = {
lineHeight: Reducer(tokens, ["font-line-height"]),
fontFamily: Reducer(tokens, ["font-family"]),
borderRadius: Reducer(tokens, ["border-radius"]),
// TODO: Add next major-release
// maxWidth: Reducer(tokens, ["text-width"]),
maxWidth: getMaxWidth(tokens),
},
},
};
+8
View File
@@ -1,5 +1,13 @@
# @navikt/ds-tokens
## 5.17.2
## 5.17.1
## 5.17.0
## 5.16.0
## 5.15.1
## 5.15.0
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-tokens",
"version": "5.15.1",
"version": "5.17.2",
"description": "Design-tokens for NAV designsystem",
"author": "Aksel | NAV designsystem team",
"keywords": [
+354 -316
View File
File diff suppressed because it is too large. Load diff
+5 -5
View File
@@ -16,10 +16,10 @@
"playroom"
],
"dependencies": {
"@navikt/aksel-icons": "^5.15.1",
"@navikt/ds-css": "^5.15.1",
"@navikt/ds-react": "^5.15.1",
"@navikt/ds-tailwind": "^5.15.1",
"@navikt/ds-tokens": "^5.15.1"
"@navikt/aksel-icons": "^5.17.2",
"@navikt/ds-css": "^5.17.2",
"@navikt/ds-react": "^5.17.2",
"@navikt/ds-tailwind": "^5.17.2",
"@navikt/ds-tokens": "^5.17.2"
}
}
@@ -1,5 +1,4 @@
import type { Preview } from "@storybook/react";
import "../dist/tw.css";
const preview: Preview = {
@@ -1,10 +1,13 @@
import { clientConfig } from "@/sanity/config";
import { useEffect, useState } from "react";
import { clientConfig } from "@/sanity/config";
export const useCheckAuth = () => {
const [user, setUser] = useState(true);
export const useCheckAuth = (skipCheck?: boolean) => {
const [user, setUser] = useState(null);
useEffect(() => {
if (skipCheck) {
return;
}
const fetchUser = async () => {
const response = await fetch(
`https://${clientConfig.projectId}.api.sanity.io/v1/users/me`,
@@ -21,7 +24,7 @@ export const useCheckAuth = () => {
setUser(false);
};
fetchUser();
}, []);
}, [skipCheck]);
return user;
};
@@ -0,0 +1,16 @@
import { createContext, useContext } from "react";
export const SanityDataContext = createContext<{
id?: string;
validUser: boolean;
}>(null);
export function useSanityData() {
const ctx = useContext(SanityDataContext);
if (!ctx) {
console.error(
"useSanityData has to be used inside SanityDataContext.provider",
);
}
return ctx;
}
@@ -1,13 +0,0 @@
import { createContext, useContext } from "react";
export const SanityDocIdContext = createContext<{ id?: string }>(null);
export function useSanityDocId() {
const ctx = useContext(SanityDocIdContext);
if (!ctx) {
console.error(
"useSanityDocId has to be used inside SanityDocIdContext.provider",
);
}
return ctx;
}
@@ -1,7 +1,7 @@
import dynamic from "next/dynamic";
import { Box, HGrid, Page, VStack } from "@navikt/ds-react";
import { FigmaIcon, GithubIcon, SlackIcon } from "@/assets/Icons";
import AkselLogo from "@/assets/Logo";
import { Box, HGrid, Page, VStack } from "@navikt/ds-react";
import dynamic from "next/dynamic";
import LinkBlock from "./parts/LinkBlock";
export const EditButton = dynamic(() => import("@/web/EditButton"), {
@@ -1,5 +1,5 @@
import { amplitudeLogNavigation } from "@/logging";
import { Link } from "@navikt/ds-react";
import { amplitudeLogNavigation } from "@/logging";
function FooterLink({ children, href }) {
return (
@@ -39,7 +39,9 @@ export const ArticleCard = ({
return (
<Link
href={`/${slug}`}
href={`/${
slug.replace("god-praksis/", "gp/") /* TODO: Remove replace */
}`}
className={cl(
"w-full flex-shrink overflow-hidden text-ellipsis rounded-large bg-surface-default p-5 shadow-small transition-shadow ease-out hover:shadow-large focus:outline-none focus-visible:shadow-focus",
{
@@ -1,10 +1,10 @@
import AkselLogo from "@/assets/Logo";
import { amplitudeLogNavigation } from "@/logging";
import { MagnifyingGlassIcon } from "@navikt/aksel-icons";
import { Box, Button, HStack, Page, Show, Spacer } from "@navikt/ds-react";
import cl from "clsx";
import dynamic from "next/dynamic";
import Link from "next/link";
import { MagnifyingGlassIcon } from "@navikt/aksel-icons";
import { Box, Button, HStack, Page, Show, Spacer } from "@navikt/ds-react";
import AkselLogo from "@/assets/Logo";
import { amplitudeLogNavigation } from "@/logging";
import { Hamburger } from "./parts/Hamburger";
import HeaderLink from "./parts/HeaderLink";
@@ -1,6 +1,6 @@
import { useState } from "react";
import { MenuHamburgerIcon } from "@navikt/aksel-icons";
import { Button, Modal, Show } from "@navikt/ds-react";
import { useState } from "react";
import HamburgerLink from "./HamburgerLink";
export const Hamburger = () => {
@@ -1,7 +1,7 @@
import { amplitudeLogNavigation } from "@/logging";
import cl from "clsx";
import Link from "next/link";
import { useRouter } from "next/router";
import { amplitudeLogNavigation } from "@/logging";
function HamburgerLink({ name, href, onClick }) {
const { asPath } = useRouter();
@@ -1,7 +1,7 @@
import { amplitudeLogNavigation } from "@/logging";
import cl from "clsx";
import Link from "next/link";
import { useRouter } from "next/router";
import { amplitudeLogNavigation } from "@/logging";
function HeaderLink({ name, href, prefetch = undefined }) {
const { asPath } = useRouter();
@@ -1,9 +1,9 @@
import Link from "next/link";
import { useRouter } from "next/router";
import { Box, Label, Show } from "@navikt/ds-react";
import { amplitudeLogNavigation } from "@/logging";
import { SidebarT } from "@/types";
import { StatusTag } from "@/web/StatusTag";
import { Box, Label, Show } from "@navikt/ds-react";
import Link from "next/link";
import { useRouter } from "next/router";
const Sidebar = ({
kategori,
@@ -1,6 +1,6 @@
import type { Meta, StoryObj } from "@storybook/react";
import { SanityBlockContent } from "@/sanity-block";
import { getBlocks } from "@/sb-util";
import type { Meta, StoryObj } from "@storybook/react";
import { WithSidebar } from "./WithSidebar";
const meta = {
Loaded 100 of 277 files, more files were not shown because too many files have changed in this diff. Show more