mirror of
https://github.com/navikt/aksel.git
synced 2026-10-16 10:42:03 +00:00
Compare commits
42
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8866572e69 | ||
|
|
a1accdbd95 | ||
|
|
fdfdebfa99 | ||
|
|
56abe568e2 | ||
|
|
34acbb87a5 | ||
|
|
fd479e3160 | ||
|
|
03ca21b4f6 | ||
|
|
e7804297bf | ||
|
|
a246c9d707 | ||
|
|
d3fb3d4d96 | ||
|
|
d734d3071b | ||
|
|
8d97bba1fe | ||
|
|
eff9b5ecce | ||
|
|
71fc90b301 | ||
|
|
e1d5e1b505 | ||
|
|
45d4f6c478 | ||
|
|
2564e84b9c | ||
|
|
e5637c64a8 | ||
|
|
82a1f32542 | ||
|
|
a8d4b1e23e | ||
|
|
d50ebebad6 | ||
|
|
e292e83128 | ||
|
|
23c472e60b | ||
|
|
058e012c26 | ||
|
|
ffce1f75ae | ||
|
|
46e5ffb6e4 | ||
|
|
d9696b3617 | ||
|
|
df1efb2748 | ||
|
|
2b23f6ab91 | ||
|
|
01d422b01f | ||
|
|
8936db096f | ||
|
|
b66ad3673b | ||
|
|
16eff2ce93 | ||
|
|
bdc0815dd7 | ||
|
|
4c5cdb22e9 | ||
|
|
a1e74fa9bd | ||
|
|
d5e5a6c64f | ||
|
|
11e3a18526 | ||
|
|
930c772bd6 | ||
|
|
ade5d59392 | ||
|
|
d90fe26e26 | ||
|
|
13b148aab6 |
No files matched your search
@@ -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
@@ -1,7 +1,7 @@
|
||||
import { mergeConfig } from "vite";
|
||||
import turbosnap from "vite-plugin-turbosnap";
|
||||
|
||||
module.exports = {
|
||||
export default {
|
||||
staticDirs: ["./public"],
|
||||
|
||||
stories: () => [
|
||||
|
||||
Vendored
+13
@@ -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"
|
||||
]
|
||||
}
|
||||
Vendored
+15
@@ -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"
|
||||
}
|
||||
@@ -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 |
@@ -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,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": {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -51,3 +51,9 @@
|
||||
flex-shrink: 0;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
@media (forced-colors: active) {
|
||||
.navds-form-field--disabled {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -111,3 +111,9 @@
|
||||
stroke-dashoffset: -120px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (forced-colors: active) {
|
||||
.navds-loader__background {
|
||||
stroke: canvastext;
|
||||
}
|
||||
}
|
||||
@@ -168,3 +168,9 @@
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@media (forced-colors: active) {
|
||||
.navds-modal {
|
||||
outline: 2px solid transparent;
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
Vendored
+8
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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": {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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}>
|
||||
“{value}”
|
||||
</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}>
|
||||
“{value}”
|
||||
</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;
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,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),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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,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
File diff suppressed because it is too large.
Load diff
@@ -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
Reference in new issue
Block a user