Merge branch 'main' into new-component-coachmark

This commit is contained in:
Anniken Granberg committed 2026-10-12 18:48:56 +02:00
commit b688d8ab48
206 files changed
+5062 -3590

No files matched your search

+5
View File
@@ -0,0 +1,5 @@
---
"@navikt/ds-css": patch
---
Search: Icon more visible in forced-colors mode
+5 -2
View File
@@ -5,6 +5,7 @@
- Narrow search space; don't replace reading code.
- Before editing: read target file + nearest related files (`index.ts`, story, test, exports).
- Prefer known paths/scripts over broad searches.
- User often edits files between turns. Before reviewing/refactoring, re-read the files (or `git diff`); never rely on earlier reads.
- Ignore release/deploy/changelog/ops scripts unless asked.
## Behavior
@@ -15,7 +16,7 @@
- Answer-first. 0-2 sentences max.
- No thinking aloud; do next edit. Explain only when asked or non-obvious.
- Show only what changes. Minimal diffs; don't paste whole files.
- Edits scoped to one file unless asked. Ask only when truly blocked.
- Edit only the files the change needs (see Definition of done); no unrelated files. Ask only when truly blocked.
- Favor perf/security; avoid re-renders, top-level side effects, unsafe HTML, env var leaks.
## Repo summary
@@ -27,12 +28,13 @@
## Start and validate
1. `corepack enable`
2. `corepack yarn install` (needs `NPM_AUTH_TOKEN` with `read:packages`). Without a token: `corepack yarn workspaces focus @navikt/aksel-icons @navikt/ds-tokens @navikt/ds-css @navikt/ds-react @navikt/ds-tailwind @navikt/aksel @navikt/aksel-stylelint`
2. `corepack yarn install` (needs `NPM_AUTH_TOKEN` with `read:packages`). Without a token: `corepack yarn workspaces focus aksel @navikt/aksel-icons @navikt/ds-tokens @navikt/ds-css @navikt/ds-react @navikt/ds-tailwind @navikt/aksel @navikt/aksel-stylelint`
3. `corepack yarn boot` (builds all public packages; rerun after `corepack yarn clean`)
- Run agent commands through `corepack yarn`. Order: existing root/workspace script → Yarn-local binary → add a pinned dependency only when new tooling is required.
- Never use `npx`, `pnpx`, `pnpm dlx`, `yarn dlx`, global binaries or curl-piped installers. Public consumer docs (`npx @navikt/aksel`) are intentional; keep them.
- If Corepack/Yarn fails because of user-level configuration, report the exact error. Never edit user-home config.
- Under the `navikt/cplt` sandbox, `Parse error when loading ~/.yarnrc.yml` means the home rc file is unreadable (EPERM), not a repo issue. Stop retrying, report it and ask the user to fix it outside the sandbox. Don't touch the repo `.yarnrc.yml`.
- Choosing checks: use the `aksel-local-validation` skill. Changed-file lint first, then the affected workspace, then root suites only when needed.
## Definition of done
@@ -80,6 +82,7 @@ Path-specific rules load automatically from `.github/instructions/` (see each `a
## Coding defaults
- Small diffs. No drive-by refactors or new deps unless asked.
- Propose the simplest maintainable solution first (explicit lists, member-name checks). No TS compiler API/AST tricks in tests (slow). Ask before escalating complexity.
- Copy nearest existing pattern before writing new code.
- Stable public APIs. Prefer additive props/exports.
- `node:` imports for Node builtins. Never import from `esm/`/`cjs/` output.
+45
View File
@@ -15,7 +15,14 @@ updates:
registries:
- npm-github
groups:
next:
applies-to: version-updates
patterns:
- "@next/eslint-plugin-next"
- "@next/bundle-analyzer"
- "next"
sanity:
applies-to: version-updates
patterns:
- "sanity"
- "sanity-*"
@@ -23,17 +30,55 @@ updates:
- "next-sanity"
- "@portabletext/*"
eslint:
applies-to: version-updates
patterns:
- "eslint"
- "@eslint/*"
react:
applies-to: version-updates
patterns:
- "react"
- "react-dom"
storybook:
applies-to: version-updates
patterns:
- "*storybook*"
vitest:
applies-to: version-updates
patterns:
- "vitest"
- "@vitest/*"
# Security updates ignore version-update groups, so mirror them to keep lockstep packages together.
next-security:
applies-to: security-updates
patterns:
- "@next/eslint-plugin-next"
- "@next/bundle-analyzer"
- "next"
sanity-security:
applies-to: security-updates
patterns:
- "sanity"
- "sanity-*"
- "@sanity/*"
- "next-sanity"
- "@portabletext/*"
eslint-security:
applies-to: security-updates
patterns:
- "eslint"
- "@eslint/*"
react-security:
applies-to: security-updates
patterns:
- "react"
- "react-dom"
storybook-security:
applies-to: security-updates
patterns:
- "*storybook*"
vitest-security:
applies-to: security-updates
patterns:
- "vitest"
- "@vitest/*"
+1
View File
@@ -17,6 +17,7 @@ jobs:
uses: actions/checkout@v7
with:
fetch-depth: 1
persist-credentials: false
- name: Setup Node.js
uses: actions/setup-node@v7
+8 -3
View File
@@ -14,15 +14,15 @@ concurrency:
cancel-in-progress: true
permissions:
actions: read
contents: read
packages: write
id-token: write
jobs:
prebuild-app-for-playwright:
name: Build app for Playwright
runs-on: ubuntu-latest
permissions:
contents: read
packages: write # Push E2E image to GHCR
env:
IMAGE_NAME: ${{ github.repository }}-e2e
REGISTRY: ghcr.io
@@ -31,6 +31,7 @@ jobs:
uses: actions/checkout@v7
with:
fetch-depth: 1
persist-credentials: false
- name: Setup and build Next.js application
uses: ./.github/actions/build-website
@@ -61,6 +62,9 @@ jobs:
needs: [prebuild-app-for-playwright]
name: Playwright shard ${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
runs-on: ubuntu-latest
permissions:
contents: read
packages: read # Pull E2E image from GHCR
container:
image: mcr.microsoft.com/playwright:v1.61.1-noble
services:
@@ -81,6 +85,7 @@ jobs:
uses: actions/checkout@v7
with:
fetch-depth: 1
persist-credentials: false
- name: Setup Node.js
uses: actions/setup-node@v7
+4
View File
@@ -24,6 +24,7 @@ jobs:
uses: actions/checkout@v7
with:
fetch-depth: 1
persist-credentials: false
- name: Setup project
uses: ./.github/actions/setup
@@ -41,6 +42,7 @@ jobs:
uses: actions/checkout@v7
with:
fetch-depth: 1
persist-credentials: false
- name: Setup project
uses: ./.github/actions/setup
@@ -58,6 +60,7 @@ jobs:
uses: actions/checkout@v7
with:
fetch-depth: 1
persist-credentials: false
- name: Setup project
uses: ./.github/actions/setup
@@ -78,6 +81,7 @@ jobs:
uses: actions/checkout@v7
with:
fetch-depth: 1
persist-credentials: false
- name: Setup project
uses: ./.github/actions/setup
@@ -22,14 +22,14 @@ jobs:
name: Run Chromatic
runs-on: ubuntu-latest
permissions:
issues: write
contents: read
pull-requests: write
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v7
with:
fetch-depth: 0
persist-credentials: false
- name: Setup project
uses: ./.github/actions/setup
+3
View File
@@ -33,6 +33,7 @@ jobs:
uses: actions/checkout@v7
with:
fetch-depth: 1
persist-credentials: false
ref: main
- name: Setup project
@@ -61,6 +62,7 @@ jobs:
uses: actions/checkout@v7
with:
fetch-depth: 1
persist-credentials: false
- name: Setup project
uses: ./.github/actions/setup
@@ -87,6 +89,7 @@ jobs:
uses: actions/checkout@v7
with:
fetch-depth: 1
persist-credentials: false
- name: Setup project
uses: ./.github/actions/setup
+1
View File
@@ -33,6 +33,7 @@ jobs:
uses: actions/checkout@v7
with:
fetch-depth: 1
persist-credentials: false
- name: Setup project
uses: ./.github/actions/setup
+2
View File
@@ -12,6 +12,7 @@ jobs:
upload:
name: Build and upload token config
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
permissions:
contents: "read"
@@ -22,6 +23,7 @@ jobs:
uses: actions/checkout@v7
with:
fetch-depth: 1
persist-credentials: false
- name: Setup Node.js
uses: actions/setup-node@v7
+3
View File
@@ -12,6 +12,7 @@ jobs:
upload:
name: Upload fonts to CDN
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
permissions:
contents: read
@@ -20,6 +21,8 @@ jobs:
steps:
- name: Checkout code
uses: "actions/checkout@v7"
with:
persist-credentials: false
- name: Upload fonts to CDN
id: upload
+2 -3
View File
@@ -15,15 +15,14 @@ jobs:
name: Build and deploy to dev-gcp
runs-on: ubuntu-latest
permissions:
packages: "write"
contents: "read"
id-token: "write"
deployments: "write"
steps:
- name: Checkout code
uses: actions/checkout@v7
with:
fetch-depth: 1
persist-credentials: false
- name: Setup and build Next.js application
uses: ./.github/actions/build-website
@@ -46,7 +45,7 @@ jobs:
identity_provider: ${{ secrets.NAIS_WORKLOAD_IDENTITY_PROVIDER }}
- name: Deploy to dev-gcp
uses: nais/deploy/actions/deploy@v2
uses: nais/deploy/actions/deploy@v3
env:
RESOURCE: aksel.nav.no/nais-dev.yaml
CLUSTER: dev-gcp
+2 -1
View File
@@ -26,6 +26,7 @@ jobs:
uses: actions/checkout@v7
with:
fetch-depth: 1
persist-credentials: false
- name: Setup project
uses: ./.github/actions/setup
@@ -48,7 +49,7 @@ jobs:
identity_provider: ${{ secrets.NAIS_WORKLOAD_IDENTITY_PROVIDER }}
- name: Deploy to prod-gcp
uses: nais/deploy/actions/deploy@v2
uses: nais/deploy/actions/deploy@v3
env:
RESOURCE: apps/mcp-server/.nais/app.yaml
CLUSTER: prod-gcp
+5 -3
View File
@@ -6,6 +6,7 @@ on:
- main
paths:
- "aksel.nav.no/**"
- "apps/playroom/**"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -23,6 +24,8 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Setup project
uses: ./.github/actions/setup
@@ -42,15 +45,14 @@ jobs:
name: Build and deploy to prod-gcp
runs-on: ubuntu-latest
permissions:
packages: "write"
contents: "read"
id-token: "write"
deployments: "write"
steps:
- name: Checkout code
uses: actions/checkout@v7
with:
fetch-depth: 1
persist-credentials: false
- name: Setup and build Next.js application
uses: ./.github/actions/build-website
@@ -76,7 +78,7 @@ jobs:
identity_provider: ${{ secrets.NAIS_WORKLOAD_IDENTITY_PROVIDER }}
- name: Deploy to prod-gcp
uses: nais/deploy/actions/deploy@v2
uses: nais/deploy/actions/deploy@v3
env:
RESOURCE: aksel.nav.no/nais-prod.yaml
CLUSTER: prod-gcp
+2 -2
View File
@@ -16,7 +16,6 @@ jobs:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/v7-aksel.nav.no'
permissions:
packages: "write"
contents: "read"
id-token: "write"
steps:
@@ -24,6 +23,7 @@ jobs:
uses: actions/checkout@v7
with:
fetch-depth: 1
persist-credentials: false
- name: Setup Node.js
uses: actions/setup-node@v7
@@ -54,7 +54,7 @@ jobs:
identity_provider: ${{ secrets.NAIS_WORKLOAD_IDENTITY_PROVIDER }}
- name: Deploy to prod-gcp
uses: nais/deploy/actions/deploy@v2
uses: nais/deploy/actions/deploy@v3
env:
RESOURCE: v7-aksel.nav.no/nais-prod.yaml
CLUSTER: prod-gcp
+2 -1
View File
@@ -14,7 +14,8 @@ concurrency: ${{ github.workflow }}-${{ github.ref }}
jobs:
build:
name: Release packages
if: ${{ !contains(github.event.head_commit.message, '[ci skip]') }}
# Only publish from main, also when dispatched manually
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
id-token: "write"
+10
View File
@@ -1,5 +1,15 @@
# @navikt/aksel-icons
## 8.19.1
## 8.19.0
### Minor Changes
- Icons: Added new icon MenuHamburgerMagnifyingGlass ([#5368](https://github.com/navikt/aksel/pull/5368))
## 8.18.0
## 8.17.2
### Patch Changes
@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.5 3.25C17.9518 3.25 20.75 6.04822 20.75 9.5C20.75 11.6221 19.6911 13.4953 18.0742 14.625L20.667 19.6562C20.8567 20.0244 20.7119 20.4773 20.3438 20.667C19.9756 20.8567 19.5227 20.7119 19.333 20.3438L16.751 15.3301C16.0524 15.6 15.2938 15.75 14.5 15.75C11.0482 15.75 8.25 12.9518 8.25 9.5C8.25 6.04822 11.0482 3.25 14.5 3.25ZM15.8994 17.25C15.993 17.25 16.0792 17.3026 16.1221 17.3857L16.6377 18.3857C16.723 18.552 16.6021 18.75 16.415 18.75H3.5C3.08579 18.75 2.75 18.4142 2.75 18C2.75 17.5858 3.08579 17.25 3.5 17.25H15.8994ZM14.5 4.75C11.8766 4.75 9.75 6.87665 9.75 9.5C9.75 12.1234 11.8766 14.25 14.5 14.25C17.1234 14.25 19.25 12.1234 19.25 9.5C19.25 6.87665 17.1234 4.75 14.5 4.75ZM6.75195 11.25C6.86836 11.25 6.96896 11.3306 6.99805 11.4434C7.08182 11.7679 7.1863 12.084 7.30957 12.3906C7.37768 12.56 7.2558 12.75 7.07324 12.75H3.5C3.08579 12.75 2.75 12.4142 2.75 12C2.75 11.5858 3.08579 11.25 3.5 11.25H6.75195ZM7.55664 5.25C7.75557 5.25 7.8765 5.47207 7.77734 5.64453C7.60333 5.94718 7.4499 6.26318 7.31738 6.58984C7.27831 6.68596 7.18482 6.75 7.08105 6.75H3.5C3.08579 6.75 2.75 6.41421 2.75 6C2.75 5.58579 3.08579 5.25 3.5 5.25H7.55664Z" fill="#202733"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1,12 @@
name: MenuHamburgerMagnifyingGlass
category: Interface
sub_category: Menu
keywords:
- Menu
- meny
- hamburger
- search
- søk
- forstørrelsesglass
variant: Stroke
created_at: 07.10.2026
+5 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/aksel-icons",
"version": "8.17.2",
"version": "8.19.1",
"author": "Aksel | Nav",
"homepage": "https://aksel.nav.no/ikoner",
"repository": {
@@ -75,19 +75,20 @@
"@figma/rest-api-spec": "^0.41.0",
"@svgr/cli": "8.1.0",
"@types/js-yaml": "^4.0.9",
"@types/lodash": "4.17.25",
"@types/react": "19.2.14",
"adm-zip": "^0.6.1",
"concurrently": "10.0.4",
"copyfiles": "^2.4.1",
"fast-glob": "3.3.3",
"hast-util-select": "6.0.4",
"js-yaml": "^5.4.2",
"js-yaml": "^5.4.3",
"lodash": "4.18.1",
"prettier": "3.9.6",
"react": "19.2.8",
"react": "19.3.0",
"rehype-parse": "9.0.1",
"tsc-alias": "1.9.1",
"tsx": "^4.23.1",
"tsx": "^4.23.15",
"typescript": "6.0.3",
"unified": "11.0.5",
"vitest": "4.1.11"
+6
View File
@@ -1,5 +1,11 @@
# @navikt/aksel-stylelint
## 8.19.1
## 8.19.0
## 8.18.0
## 8.17.2
## 8.17.1
+5 -5
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/aksel-stylelint",
"version": "8.17.2",
"version": "8.19.1",
"type": "module",
"engines": {
"node": ">=18.12.0"
@@ -43,14 +43,14 @@
"dev": "yarn watch:lint"
},
"devDependencies": {
"@navikt/ds-css": "^8.17.2",
"@navikt/ds-tokens": "^8.17.2",
"@navikt/ds-css": "^8.19.1",
"@navikt/ds-tokens": "^8.19.1",
"concurrently": "10.0.4",
"postcss-selector-parser": "^7.1.6",
"postcss-value-parser": "^4.2.0",
"stylelint": "^17.14.1",
"stylelint": "^17.15.0",
"stylelint-test-rule-node": "^1.2.0",
"tsx": "^4.23.1",
"tsx": "^4.23.15",
"typescript": "6.0.3"
}
}
+18
View File
@@ -1,5 +1,23 @@
# @navikt/aksel
## 8.19.1
## 8.19.0
### Patch Changes
- CLI: Update clipboardy to v5.3.2 ([#5357](https://github.com/navikt/aksel/pull/5357))
- CLI: Update figlet to 1.11.3 ([#5360](https://github.com/navikt/aksel/pull/5360))
## 8.18.0
### Patch Changes
- Codemod: Migrations now run in folders that are not git repositories, and a missing git install gets its own error message. `codemod v8` no longer stops before `v8-tokens` because of changes made by earlier migrations in the same run. ([`470a820`](https://github.com/navikt/aksel/commit/470a8207f23aee71243b986ef2836967c2498e71))
- Codemod: `v8-tokens` no longer removes whitespace before replaced tokens or rewrites longer names that contain a token name. ([#5322](https://github.com/navikt/aksel/pull/5322))
## 8.17.2
## 8.17.1
+4 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/aksel",
"version": "8.17.2",
"version": "8.19.1",
"description": "Aksel command line interface. Codemods and other utilities for Aksel users.",
"author": "Aksel | Nav designsystem team",
"license": "MIT",
@@ -42,11 +42,11 @@
"dependencies": {
"chalk": "5.6.2",
"cli-progress": "3.12.0",
"clipboardy": "5.3.1",
"clipboardy": "5.3.2",
"commander": "15.0.0",
"enquirer": "2.4.1",
"fast-glob": "3.3.3",
"figlet": "1.11.0",
"figlet": "1.11.3",
"is-git-clean": "1.1.0",
"jscodeshift": "17.4.0",
"lodash": "4.18.1"
@@ -55,6 +55,7 @@
"@types/cli-progress": "3.11.6",
"@types/is-git-clean": "1.1.2",
"@types/jscodeshift": "17.3.0",
"@types/lodash": "4.17.25",
"rimraf": "6.1.3",
"typescript": "6.0.3",
"vitest": "4.1.11"
@@ -0,0 +1,91 @@
import type { Command } from "commander";
import isGitClean from "is-git-clean";
import { afterEach, describe, expect, test, vi } from "vitest";
import { validateGit } from "../validation";
vi.mock("is-git-clean", () => ({ default: { sync: vi.fn() } }));
const gitSync = vi.mocked(isGitClean.sync);
function createProgram() {
const error = vi.fn((message: string) => {
throw new Error(message);
});
return { program: { error } as unknown as Command, error };
}
function gitError(props: Record<string, unknown>) {
return Object.assign(new Error("Command failed: git status"), props);
}
describe("validateGit", () => {
afterEach(() => {
vi.clearAllMocks();
});
test("passes when the working tree is clean", () => {
gitSync.mockReturnValue(true);
const { program, error } = createProgram();
validateGit({}, program);
expect(error).not.toHaveBeenCalled();
});
test("fails when the working tree has changes", () => {
gitSync.mockReturnValue(false);
const { program, error } = createProgram();
expect(() => validateGit({}, program)).toThrow(/stash or commit/);
expect(error).toHaveBeenCalledTimes(1);
});
test.each([
"fatal: not a git repository (or any of the parent directories): .git\n",
"fatal: Not a git repository (or any of the parent directories): .git\n",
])("passes outside a git repository (%j)", (stderr) => {
gitSync.mockImplementation(() => {
throw gitError({ status: 128, stderr });
});
const { program, error } = createProgram();
validateGit({}, program);
expect(error).not.toHaveBeenCalled();
});
test("reports a missing git binary instead of asking to stash", () => {
gitSync.mockImplementation(() => {
throw gitError({ code: "ENOENT" });
});
const { program, error } = createProgram();
expect(() => validateGit({}, program)).toThrow(/git/i);
expect(error.mock.calls[0][0]).not.toMatch(/stash or commit/);
expect(error.mock.calls[0][0]).toMatch(/--force/);
});
test("fails on other git errors", () => {
gitSync.mockImplementation(() => {
throw gitError({
status: 128,
stderr: "fatal: detected dubious ownership",
});
});
const { program } = createProgram();
expect(() => validateGit({}, program)).toThrow(/stash or commit/);
});
test.each([{ dryRun: true }, { force: true }])(
"skips the check with force",
(options) => {
const { program, error } = createProgram();
validateGit(options, program);
expect(gitSync).not.toHaveBeenCalled();
expect(error).not.toHaveBeenCalled();
},
);
});
@@ -0,0 +1,29 @@
/* @ts-expect-error No avaliable types for testUtils */
import { applyTransform } from "jscodeshift/dist/testUtils";
import { describe, expect, test } from "vitest";
import * as transform from "../update-js-tokens";
const run = (source: string) =>
applyTransform({ ...transform, parser: "tsx" }, {}, { source });
describe("update-js-tokens keeps boundaries intact", () => {
test("does not replace inside longer identifiers", () => {
expect(
run(
`import { NavdsGlobalColorGray100 } from "@navikt/ds-tokens/dist/tokens";\n\nconst a = [NavdsGlobalColorGray100, MyNavdsGlobalColorGray100, NavdsGlobalColorGray1000];`,
),
).toBe(
`import { AGray100 } from "@navikt/ds-tokens/dist/tokens";\n\nconst a = [AGray100, MyNavdsGlobalColorGray100, NavdsGlobalColorGray1000];`,
);
});
test("does not replace similar names when aliased", () => {
expect(
run(
`import { NavdsGlobalColorGray100 as gray } from "@navikt/ds-tokens/dist/tokens";\n\nconst a = [gray, grayish, mygray];`,
),
).toBe(
`import { AGray100 } from "@navikt/ds-tokens/dist/tokens";\n\nconst a = [AGray100, grayish, mygray];`,
);
});
});
@@ -1,5 +1,6 @@
import type { API, JSCodeshift } from "jscodeshift";
import { tokens } from "../../../tokens-map.js";
import { createIdentifierRegex } from "../../../utils/identifier-regex";
import {
getImportSpecifier,
getImportSpecifierName,
@@ -60,8 +61,7 @@ export default function transformer(file: JSCodeshift, api: API) {
let code = root.toSource(getLineTerminator(file.source));
const rgx = new RegExp(`(${localName})`, "gm");
code = code.replace(rgx, out);
code = code.replace(createIdentifierRegex(String(localName)), out);
root = j(code);
}
});
-1
View File
@@ -2,7 +2,6 @@
import { applyTransform } from "jscodeshift/dist/testUtils";
import fs from "node:fs";
import path from "node:path";
// eslint-disable-next-line import/default
import prettier from "prettier";
import { describe, expect, test } from "vitest";
@@ -0,0 +1,11 @@
/**
* Matches a JS identifier as a whole word.
* Lookarounds are zero-width, so replacing the match never removes surrounding whitespace,
* and longer identifiers containing the name (`MyToken`, `Token2`, `$Token`) are left untouched.
*/
function createIdentifierRegex(identifier: string) {
const escaped = identifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
return new RegExp(`(?<![\\w$])${escaped}(?![\\w$])`, "gm");
}
export { createIdentifierRegex };
@@ -218,8 +218,25 @@ describe("getTokenRegex JS", () => {
expect("ATextRed200").not.toMatch(regex);
expect("AATextRed200").not.toMatch(regex);
expect("AATextRed").not.toMatch(regex);
expect("$ATextRed").not.toMatch(regex);
});
/* Since we cant do a positive lookbehind, these cases will be matched */
expect("AATextRed").toMatch(regex);
test("should not include surrounding whitespace in the match", () => {
const regex = getTokenRegex("--a-text-red", "js");
expect(" ATextRed ".match(regex)).toEqual(["ATextRed"]);
});
});
describe("getTokenRegex does not consume leading whitespace", () => {
test.each([
["css", " var(--a-text-red)", "--a-text-red"],
["scss", "margin: 0 $a-text-red", "$a-text-red"],
["less", "margin: 0 @a-text-red", "@a-text-red"],
] as const)("%s", (format, input, expected) => {
expect(input.match(getTokenRegex("--a-text-red", format))).toEqual([
expected,
]);
});
});
@@ -39,26 +39,29 @@ function getFrameworkRegexes({
return regexes;
}
/**
* Transforms replace the full match. We use lookarounds so that the regex does not consume surrounding characters.
*/
function getTokenRegex(
variable: string,
format: "css" | "scss" | "less" | "js",
) {
switch (format) {
case "css":
return new RegExp(`(\\s|^)?(${variable})(?=\\s|$|[^\\w-])`, "gm");
return new RegExp(`(?<![\\w-])(${variable})(?![\\w-])`, "gm");
case "scss":
return new RegExp(
`(\\s|^)?(\\${translateToken(variable, "scss")})(?=\\s|$|[^\\w-])`,
`(?<![\\w-])(\\${translateToken(variable, "scss")})(?![\\w-])`,
"gm",
);
case "less":
return new RegExp(
`(\\s|^)?(${translateToken(variable, "less")})(?=\\s|$|[^\\w-])`,
`(?<![\\w-])(${translateToken(variable, "less")})(?![\\w-])`,
"gm",
);
case "js":
return new RegExp(
`(\\s|^)?(${translateToken(variable, "js")})(?=\\s|$|[^\\w-])`,
`(?<![\\w$])(${translateToken(variable, "js")})(?![\\w$])`,
"gm",
);
}
+5 -2
View File
@@ -4,7 +4,7 @@ import { runTooling } from "./run-tooling.js";
const program = new Command();
function v8TokensCommand() {
function v8TokensCommand({ skipGitCheck = false } = {}) {
program.allowExcessArguments();
program.name(`${chalk.blueBright(`npx @navikt/aksel v8-tokens`)}`);
@@ -24,7 +24,10 @@ function v8TokensCommand() {
program.parse();
const options = program.opts();
runTooling(options as Parameters<typeof runTooling>["0"], program);
return runTooling(
{ ...(options as Parameters<typeof runTooling>["0"]), skipGitCheck },
program,
);
}
export { v8TokensCommand };
@@ -31,6 +31,11 @@ type ToolingOptions = {
dryRun: boolean;
glob: string;
ext: string;
/**
* Set when the caller has already validated git (e.g. `codemod v8`),
* since earlier migrations in the same run leave the tree dirty.
*/
skipGitCheck?: boolean;
};
type CodeshiftOptions = {
@@ -90,6 +95,15 @@ export async function runTooling(
// Show initial status
const initialStatus = getStatus(filepaths);
// Only check git before the first write: later tasks see our own changes.
let gitChecked = !!options.skipGitCheck;
const ensureGitChecked = () => {
if (!gitChecked) {
validateGit(options, program);
gitChecked = true;
}
};
// Task execution loop
let task: TaskName = await getNextTask(initialStatus.status);
let currentStatus = initialStatus;
@@ -105,6 +119,7 @@ export async function runTooling(
program,
currentStatus,
() => getStatus(filepaths, "no-print"),
ensureGitChecked,
);
} catch (error) {
program.error(
@@ -117,8 +132,6 @@ export async function runTooling(
task = await getNextTask(currentStatus.status);
}
process.exit(0);
}
/**
@@ -131,6 +144,7 @@ async function executeTask(
program: Command,
statusStore: TokenStatus,
updateStatus: () => TokenStatus,
ensureGitChecked: () => void,
): Promise<TokenStatus> {
switch (task) {
case "status":
@@ -147,9 +161,7 @@ async function executeTask(
case "less-tokens":
case "js-tokens":
case "tailwind-tokens": {
if (!options.force) {
validateGit(options, program);
}
ensureGitChecked();
const scopedFiles = getScopedFilesForTask(
task,
filepaths,
@@ -179,9 +191,7 @@ async function executeTask(
"tailwind-tokens",
] as const;
if (!options.force) {
validateGit(options, program);
}
ensureGitChecked();
let currentStatus = statusStore;
const summaryData: {
@@ -0,0 +1,73 @@
import type { Command } from "commander";
import Enquirer from "enquirer";
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
import { validateGit } from "../../validation";
import { TokenStatus } from "../config/TokenStatus";
import { runTooling } from "../run-tooling";
vi.mock("enquirer", () => ({ default: { prompt: vi.fn() } }));
vi.mock("fast-glob", () => ({ default: vi.fn(async () => []) }));
vi.mock("jscodeshift/src/Runner", () => ({
run: vi.fn(async () => ({ error: 0, ok: 0, nochange: 0, skip: 0 })),
}));
vi.mock("../../validation", () => ({ validateGit: vi.fn() }));
vi.mock("../tasks/status", () => ({
getStatus: vi.fn(() => new TokenStatus()),
}));
vi.mock("../tasks/print-remaining", () => ({ printRemaining: vi.fn() }));
const prompt = vi.mocked(Enquirer.prompt);
const program = { error: vi.fn() } as unknown as Command;
const baseOptions = { force: false, dryRun: false, glob: "**/*.css", ext: "" };
function selectTasks(...tasks: string[]) {
for (const task of [...tasks, "exit"]) {
prompt.mockResolvedValueOnce({ task });
}
}
describe("runTooling", () => {
beforeEach(() => {
vi.spyOn(console, "info").mockImplementation(() => {});
vi.spyOn(process, "exit").mockImplementation((code) => {
throw new Error(`process.exit(${code})`);
});
});
afterEach(() => {
vi.restoreAllMocks();
vi.clearAllMocks();
});
test("returns instead of exiting the process when the user exits", async () => {
selectTasks();
await expect(runTooling(baseOptions, program)).resolves.toBeUndefined();
expect(process.exit).not.toHaveBeenCalled();
});
test("checks git once, even when several migrations run in a row", async () => {
selectTasks("css-tokens", "scss-tokens", "run-all-migrations");
await runTooling(baseOptions, program);
expect(validateGit).toHaveBeenCalledTimes(1);
expect(program.error).not.toHaveBeenCalled();
});
test("skips the git check when the caller already did it", async () => {
selectTasks("css-tokens", "run-all-migrations");
await runTooling({ ...baseOptions, skipGitCheck: true }, program);
expect(validateGit).not.toHaveBeenCalled();
});
test("does not check git for read-only tasks", async () => {
selectTasks("status", "print-remaining-tokens");
await runTooling(baseOptions, program);
expect(validateGit).not.toHaveBeenCalled();
});
});
@@ -0,0 +1,85 @@
/* @ts-expect-error No avaliable types for testUtils */
import { applyTransform } from "jscodeshift/dist/testUtils";
import { describe, expect, test } from "vitest";
import * as jsTransform from "../v8-tokens-js";
import lessTransform from "../v8-tokens-less";
import scssTransform from "../v8-tokens-scss";
/*
* Fixture tests are compared after Prettier, which hides whitespace bugs.
* These tests compare the exact output.
*/
const runJs = (source: string) =>
applyTransform({ ...jsTransform, parser: "tsx" }, {}, { source });
const runStyle = (
transformer: typeof scssTransform | typeof lessTransform,
source: string,
) => transformer({ path: "test", source });
describe("v8-tokens-js keeps boundaries intact", () => {
const importLine = `import { ASurfaceDefault } from "@navikt/ds-tokens/dist/tokens";\n`;
const expectedImport = `import { BgDefault } from "@navikt/ds-tokens/js";\n`;
test.each([
["return ASurfaceDefault;", "return BgDefault;"],
["const a = ASurfaceDefault;", "const a = BgDefault;"],
["const a = [ASurfaceDefault];", "const a = [BgDefault];"],
["const a = {...ASurfaceDefault};", "const a = {...BgDefault};"],
// biome-ignore lint/suspicious/noTemplateCurlyInString: Testing token inside template literal
["const a = `${ASurfaceDefault}`;", "const a = `${BgDefault}`;"],
])("%s", (input, output) => {
expect(runJs(`${importLine}\n${input}`)).toBe(
`${expectedImport}\n${output}`,
);
});
test("does not replace inside longer identifiers", () => {
expect(
runJs(
`${importLine}\nconst a = [MyASurfaceDefault, ASurfaceDefault2, $ASurfaceDefault];`,
),
).toBe(
`${expectedImport}\nconst a = [MyASurfaceDefault, ASurfaceDefault2, $ASurfaceDefault];`,
);
});
test("replaces aliased imports without touching similar names", () => {
expect(
runJs(
`import { ASurfaceDefault as surf } from "@navikt/ds-tokens/dist/tokens";\n\nconst a = [surf, surface, mysurf];`,
),
).toBe(`${expectedImport}\nconst a = [BgDefault, surface, mysurf];`);
});
});
describe("v8-tokens-scss keeps whitespace", () => {
test.each([
["margin: 0 $a-spacing-4;", "margin: 0 $ax-space-16;"],
["color: $a-text-default;", "color: $ax-text-neutral;"],
["color:$a-text-default;", "color:$ax-text-neutral;"],
["$a-text-default", "$ax-text-neutral"],
["\n\t$a-text-default", "\n\t$ax-text-neutral"],
["color: common.$a-text-default;", "color: common.$ax-text-neutral;"],
[
"padding: $a-spacing-4 $a-spacing-4;",
"padding: $ax-space-16 $ax-space-16;",
],
])("%s", (input, output) => {
expect(runStyle(scssTransform, input)).toBe(output);
});
});
describe("v8-tokens-less keeps whitespace", () => {
test.each([
["margin: 0 @a-spacing-4;", "margin: 0 @ax-space-16;"],
["color: @a-text-default;", "color: @ax-text-neutral;"],
["color:@a-text-default;", "color:@ax-text-neutral;"],
[
"padding: @a-spacing-4 @a-spacing-4;",
"padding: @ax-space-16 @ax-space-16;",
],
])("%s", (input, output) => {
expect(runStyle(lessTransform, input)).toBe(output);
});
});
@@ -1,4 +1,5 @@
import type { API, FileInfo } from "jscodeshift";
import { createIdentifierRegex } from "../../utils/identifier-regex";
import { getImportSpecifier } from "../../utils/imports";
import { getLineTerminator } from "../../utils/lineterminator";
import moveAndRenameImport from "../../utils/packageImports";
@@ -63,8 +64,9 @@ export default function transformer(file: FileInfo, api: API) {
let code = root.toSource(getLineTerminator(src));
const rgx = new RegExp(`(\\s|^)?(${localName})(?=\\s|$|[^\\w-])`, "gm");
code = code.replace(rgx, jsToken);
if (localName) {
code = code.replace(createIdentifierRegex(localName), jsToken);
}
src = code;
root = j(code);
+8 -1
View File
@@ -43,7 +43,14 @@ export function validateGit(options: any, program: Command) {
try {
clean = isGitClean.sync(process.cwd());
} catch (err: any) {
if (err?.stderr && err.stderr.indexOf("Not a git repository") >= 0) {
if (err?.code === "ENOENT") {
program.error(
`${chalk.yellow(
"\nCould not find git, so we can't check for uncommitted changes.",
)}${"\nInstall git, or use the --force flag to override this safety check."}`,
);
}
if (/not a git repository/i.test(String(err?.stderr ?? ""))) {
clean = true;
}
}
+5 -4
View File
@@ -27,13 +27,14 @@ async function run() {
if (args[0] === "codemod") {
if (args.includes("v8-tokens")) {
v8TokensCommand();
return;
await v8TokensCommand();
process.exit(0);
}
codemodCommand((migration) => {
codemodCommand(async (migration) => {
if (migration === "v8-tokens") {
v8TokensCommand();
/* `codemod v8` has already checked git before running earlier migrations */
await v8TokensCommand({ skipGitCheck: true });
}
});
return;
+18
View File
@@ -1,5 +1,23 @@
# @navikt/ds-css
## 8.19.1
### Patch Changes
- Accordion: `@media print` now auto-opens every Accordion ([#5363](https://github.com/navikt/aksel/pull/5363))
- ExpansionCard: `@media print` now auto-opens every ExpansionCard ([#5363](https://github.com/navikt/aksel/pull/5363))
- ReadMore: `@media print` now auto-opens every ReadMore ([#5363](https://github.com/navikt/aksel/pull/5363))
## 8.19.0
## 8.18.0
### Patch Changes
- ActionMenu: Faster and smoother open transition. ([#5296](https://github.com/navikt/aksel/pull/5296))
## 8.17.2
## 8.17.1
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-css",
"version": "8.17.2",
"version": "8.19.1",
"description": "CSS for Nav Designsystem",
"author": "Aksel | Nav designsystem team",
"keywords": [
@@ -32,12 +32,12 @@
"css:get-version": "node config/get-version.js"
},
"devDependencies": {
"@navikt/ds-tokens": "^8.17.2",
"@navikt/ds-tokens": "^8.19.1",
"browserslist": "^4.28.7",
"esbuild": "^0.28.2",
"fast-glob": "3.3.3",
"lightningcss": "^1.33.0",
"tsx": "^4.23.1"
"tsx": "^4.23.15"
},
"browserslist": [
">= 0.2% in NO",
+17
View File
@@ -123,6 +123,11 @@
.aksel-accordion--indent > .aksel-accordion__item > .aksel-accordion__content {
box-shadow: -2px 0 0 0 var(--ax-border-neutral-subtleA);
@media print {
border-left: 1px solid black;
margin-left: calc(var(--ax-space-24) - 2px);
}
}
.aksel-accordion__item {
@@ -163,4 +168,16 @@
border-bottom: 1px solid var(--ax-border-neutral-subtleA);
}
}
@media print {
& > .aksel-accordion__content {
visibility: visible;
/* Override grid layout for print. Grid causes element to stretch unexpectedly across printed pages in FF */
display: block;
margin-block: var(--ax-space-8);
margin-block-end: var(--ax-space-24);
opacity: 1;
}
}
}
+4 -4
View File
@@ -21,13 +21,13 @@
/* stylelint-disable-next-line csstools/value-no-unknown-custom-properties */
transform-origin: var(--__axc-action-menu-content-transform-origin);
transition:
transform 150ms ease allow-discrete,
opacity 150ms ease allow-discrete;
transform 100ms cubic-bezier(0.25, 0.46, 0.45, 0.94) allow-discrete,
opacity 100ms cubic-bezier(0.25, 0.46, 0.45, 0.94) allow-discrete;
&[data-state="open"] {
@starting-style {
opacity: 0.8;
transform: scale3d(0.94, 0.94, 0.94);
opacity: 0.1;
transform: scale3d(0.95, 0.95, 0.95);
}
}
}
+15
View File
@@ -19,6 +19,10 @@
box-shadow: 0 0 0 1px var(--ax-border-default);
border-color: var(--ax-border-default);
}
@media print {
border-color: black;
}
}
/* ------------------------ ExpansionCard Small-size ------------------------ */
@@ -166,6 +170,17 @@
opacity: 1;
}
}
@media print {
/* Override grid layout for print. Grid causes element to stretch unexpectedly across printed pages in FF */
display: block;
visibility: visible;
padding-block: var(--__axc-expansioncard-padding-block);
& .aksel-expansioncard__content-inner {
opacity: 1;
}
}
}
.aksel-expansioncard__content-inner {
+11 -4
View File
@@ -35,7 +35,7 @@
color: var(--ax-text-neutral);
width: 100%;
min-height: 3rem;
display: inline-block;
display: block;
position: relative;
padding: var(--ax-space-8);
padding-right: var(--ax-space-40);
@@ -81,10 +81,11 @@
.aksel-combobox2__field-chevron {
position: absolute;
font-size: 1.5rem;
right: var(--ax-space-8);
top: 0;
margin-top: 0.75rem; /* We don't use dynamic centering because we don't want it to move if height changes */
font-size: 1.5rem;
pointer-events: none;
margin-top: 12px; /* We don't use dynamic centering because we don't want it to move if height changes */
color: var(--ax-text-neutral);
@media (forced-colors: active) {
@@ -92,7 +93,7 @@
}
.aksel-combobox2[data-size="small"] & {
margin-top: 4px;
margin-top: 0.25rem;
}
}
@@ -236,6 +237,12 @@
.aksel-combobox2__option:is([aria-selected="true"], [aria-selected="true"][data-virtual-focus="true"])
.aksel-listbox__option-inner {
background-color: var(--ax-bg-moderate-pressedA);
@media (forced-colors: active) {
background-color: SelectedItem;
color: SelectedItemText;
forced-color-adjust: none;
}
}
.aksel-combobox2__option-checkmark {
+75 -3
View File
@@ -19,6 +19,41 @@
}
}
/* Radio card */
.aksel-radio-card {
width: 100%;
align-content: flex-start;
padding: var(--ax-space-16) var(--ax-space-20);
cursor: pointer;
background-color: var(--ax-bg-default);
border: 1px solid var(--ax-border-neutral-subtleA);
border-radius: var(--ax-radius-8);
&:focus-within::after,
&.aksel-radio--small:focus-within::after {
inset: 0;
border-radius: calc(var(--ax-radius-8) - 1px);
outline-offset: 4px;
}
}
/* Radio card group */
.aksel-radio-card-group {
& > .aksel-radio-buttons {
display: flex;
flex-direction: column;
gap: var(--ax-space-12);
> .aksel-hgrid:not(.aksel-hgrid-gap) {
gap: var(--ax-space-12);
}
}
&.aksel-radio-group--small > .aksel-radio-buttons > .aksel-hgrid:not(.aksel-hgrid-gap) {
gap: var(--ax-space-8);
}
}
/* Small */
.aksel-checkbox--small,
.aksel-radio--small {
@@ -30,6 +65,14 @@
}
}
.aksel-radio-card.aksel-radio--small {
padding: var(--ax-space-12) var(--ax-space-16);
}
.aksel-radio-card-group.aksel-radio-group--small > .aksel-radio-buttons {
gap: var(--ax-space-8);
}
/* Label */
.aksel-checkbox__label,
.aksel-radio__label {
@@ -203,9 +246,27 @@
--__axc-radio-checkbox-marker-border: 7px;
}
/* Radio card border in hover and checked states */
.aksel-radio-card:is(
:hover:not(:where(.aksel-radio--disabled, .aksel-radio--readonly, .aksel-radio--error)),
:has(> .aksel-radio__input:checked):not(:where(.aksel-radio--disabled, .aksel-radio--readonly))
) {
border-color: var(--ax-border-default);
box-shadow: 0 0 0 1px var(--ax-border-default);
}
/* Radio card background in checked state */
.aksel-radio-card:has(> .aksel-radio__input:checked):not(
:where(.aksel-radio--disabled, .aksel-radio--readonly, .aksel-radio--error)
) {
background-color: var(--ax-bg-accent-moderateA);
}
/* Marker colors in unchecked hover state (unless disabled) */
.aksel-checkbox__input:hover:not(:disabled, :checked, :indeterminate),
.aksel-radio__input:hover:not(:disabled, :checked) {
.aksel-radio__input:hover:not(:disabled, :checked),
.aksel-radio-card:hover:not(:where(.aksel-radio--disabled, .aksel-radio--readonly))
> .aksel-radio__input:not(:disabled, :checked) {
border-color: var(--ax-border-strong);
background-color: var(--ax-bg-moderate-hoverA);
}
@@ -222,15 +283,26 @@
opacity: var(--ax-opacity-disabled);
}
/* Radio card background in disabled and readonly states */
.aksel-radio-card:where(.aksel-radio--readonly) {
background-color: var(--ax-bg-neutral-moderate);
}
.aksel-radio-card.aksel-radio--disabled > .aksel-radio__input:checked {
border-color: var(--ax-border-neutral-strong);
}
/* Cursor in disabled state on otherwise clickable elements */
.aksel-checkbox--disabled :where(.aksel-checkbox__input, .aksel-checkbox__label),
.aksel-radio--disabled > :where(.aksel-radio__input, .aksel-radio__label) {
.aksel-radio--disabled > :where(.aksel-radio__input, .aksel-radio__label),
.aksel-radio-card.aksel-radio--disabled {
cursor: not-allowed;
}
/* Cursor in readonly state on otherwise clickable elements */
.aksel-checkbox--readonly :where(.aksel-checkbox__input, .aksel-checkbox__label),
.aksel-radio--readonly > :where(.aksel-radio__input, .aksel-radio__label) {
.aksel-radio--readonly > :where(.aksel-radio__input, .aksel-radio__label),
.aksel-radio-card.aksel-radio--readonly {
cursor: default;
}
+4
View File
@@ -81,6 +81,10 @@
pointer-events: none;
font-size: 1.5rem;
@media (forced-colors: active) {
color: CanvasText;
}
.aksel-search--disabled & {
opacity: var(--ax-opacity-disabled);
}
+4
View File
@@ -49,5 +49,9 @@
outline: 2px solid var(--ax-border-focus);
outline-offset: -2px;
background-color: var(--ax-bg-neutral-moderate-hoverA);
@media (forced-colors: active) {
outline-color: Highlight;
}
}
}
+4
View File
@@ -86,6 +86,10 @@
.aksel-read-more[data-state="closed"] & {
display: none;
@media print {
display: block;
}
}
}
+34
View File
@@ -1,5 +1,39 @@
# @navikt/ds-react
## 8.19.1
### Patch Changes
- Updated dependencies []:
- @navikt/ds-tokens@8.19.1
- @navikt/aksel-icons@8.19.1
## 8.19.0
### Patch Changes
- Updated dependencies [[`c5dd309`](https://github.com/navikt/aksel/commit/c5dd3097e51ef2a9f79964248398bd158660bd3b)]:
- @navikt/aksel-icons@8.19.0
- @navikt/ds-tokens@8.19.0
## 8.18.0
### Minor Changes
- Radio: Add RadioCard and RadioCardGroup components ([#5276](https://github.com/navikt/aksel/pull/5276))
### Patch Changes
- Badge, Lookup: now available outside of Preview scope ([#5344](https://github.com/navikt/aksel/pull/5344))
- DatePicker: Use without hook now correctly closes picker on date-selection ([#5327](https://github.com/navikt/aksel/pull/5327))
- ActionMenu: Menu is no longer modal, Page scroll and interaction outside the menu are no longer blocked while open. ([#5296](https://github.com/navikt/aksel/pull/5296))
- Updated dependencies []:
- @navikt/ds-tokens@8.18.0
- @navikt/aksel-icons@8.18.0
## 8.17.2
### Patch Changes
+37 -8
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-react",
"version": "8.17.2",
"version": "8.19.1",
"description": "React components from the Norwegian Labour and Welfare Administration.",
"author": "Aksel, a team part of the Norwegian Labour and Welfare Administration.",
"license": "MIT",
@@ -86,6 +86,16 @@
"default": "./cjs/badge/index.js"
}
},
"./PREVIEW/RadioCard": {
"import": {
"types": "./esm/form/radio-card/index.d.ts",
"default": "./esm/form/radio-card/index.js"
},
"require": {
"types": "./cjs/form/radio-card/index.d.ts",
"default": "./cjs/form/radio-card/index.js"
}
},
"./types/theme": {
"types": "./esm/types/theme.d.ts"
},
@@ -729,6 +739,26 @@
"default": "./cjs/dialog/index.js"
}
},
"./Lookup": {
"import": {
"types": "./esm/lookup/index.d.ts",
"default": "./esm/lookup/index.js"
},
"require": {
"types": "./cjs/lookup/index.d.ts",
"default": "./cjs/lookup/index.js"
}
},
"./Badge": {
"import": {
"types": "./esm/badge/index.d.ts",
"default": "./esm/badge/index.js"
},
"require": {
"types": "./cjs/badge/index.d.ts",
"default": "./cjs/badge/index.js"
}
},
"./package.json": "./package.json",
"./Theme": {
"import": {
@@ -758,8 +788,8 @@
"dependencies": {
"@floating-ui/react": "0.27.20",
"@floating-ui/react-dom": "^2.1.9",
"@navikt/aksel-icons": "^8.17.2",
"@navikt/ds-tokens": "^8.17.2",
"@navikt/aksel-icons": "^8.19.1",
"@navikt/ds-tokens": "^8.19.1",
"date-fns": "^4.0.0",
"react-day-picker": "10.0.1"
},
@@ -767,23 +797,22 @@
"@testing-library/dom": "10.4.1",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.3.3",
"@testing-library/user-event": "^14.5.2",
"@testing-library/user-event": "^14.6.7",
"@types/jscodeshift": "17.3.0",
"@types/node": "^24.10.9",
"@types/react": "19.2.14",
"@types/react-dom": "19.2.3",
"aksel": "workspace:^",
"concurrently": "10.0.4",
"copyfiles": "^2.4.1",
"cross-env": "^10.1.0",
"jscodeshift": "17.4.0",
"jsdom": "29.1.1",
"react": "19.2.8",
"react-dom": "19.2.8",
"react": "19.3.0",
"react-dom": "19.3.0",
"rimraf": "6.1.3",
"swr": "^2.4.2",
"tsc-alias": "1.9.1",
"tsx": "^4.23.1",
"tsx": "^4.23.15",
"typescript": "6.0.3",
"vitest": "4.1.11"
},
@@ -1,10 +1,10 @@
import { describe, expect, test } from "vitest";
import { type ParsedMeta, parseMetaFiles } from "./parse-meta";
import { type ParsedMeta, readMetaFiles } from "./parse-meta";
import { validateMetas } from "./validate-meta";
describe("component metadata files", () => {
test("all meta files satisfy the metadata spec", () => {
const metas = parseMetaFiles();
const metas = readMetaFiles();
expect(validateMetas(metas)).toEqual([]);
});
});
+150 -87
View File
@@ -1,4 +1,5 @@
import fg from "fast-glob";
import { readFileSync } from "node:fs";
import path from "node:path";
import ts from "typescript";
import { META_GLOB, packageRoot, tsconfigPath } from "./paths";
@@ -34,6 +35,21 @@ interface ParsedMeta {
utils: ResolvedEntry[];
}
/**
* Syntax-only contents of one `*.meta.ts` file. `components`/`utils` hold the
* map labels, e.g. `"Accordion.Item"`, without resolving their declarations.
*/
interface MetaSummary extends Omit<ParsedMeta, "components" | "utils"> {
components: string[];
utils: string[];
}
/** Syntactic parts of a meta file, shared by the summary and full parsers. */
interface MetaSource extends Omit<ParsedMeta, "components" | "utils"> {
componentsMap: ts.ObjectLiteralExpression;
utilsMap: ts.ObjectLiteralExpression | undefined;
}
/**
* Extracts exported "metadata" object through AST
*/
@@ -146,14 +162,122 @@ function createTsProgram(metaFiles: string[]): ts.Program {
});
}
/**
* Returns the display label of a `components`/`utils` map entry, or
* `undefined` for entries that are neither `Name` nor `"Label": Name`.
*/
function getEntryLabel(entry: ts.ObjectLiteralElementLike) {
if (ts.isShorthandPropertyAssignment(entry)) {
return entry.name.text;
}
if (ts.isPropertyAssignment(entry) && ts.isIdentifier(entry.initializer)) {
return ts.isStringLiteral(entry.name)
? entry.name.text
: entry.name.getText();
}
return undefined;
}
/**
* Reads a `components`/`utils` map from the metadata object. Throws when the
* property exists but is not an object literal.
*/
function readEntryMap(
metadataNode: ts.ObjectLiteralExpression,
key: "components" | "utils",
relMeta: string,
) {
const prop = getObjectProperty(metadataNode, key);
if (!prop) {
return undefined;
}
if (
!ts.isPropertyAssignment(prop) ||
!ts.isObjectLiteralExpression(prop.initializer)
) {
throw new Error(`[${relMeta}] metadata.${key} must be an object literal.`);
}
return prop.initializer;
}
/**
* Reads the syntactic parts of a meta file. Needs no type information, so it
* works on a standalone `ts.SourceFile`.
*/
function readMetaSource(
sourceFile: ts.SourceFile,
metaFile: string,
): MetaSource {
const relMeta = path.relative(packageRoot, metaFile);
const metadataNode = findMetadataObject(sourceFile);
if (!metadataNode) {
throw new Error(`[${relMeta}] Could not find a "metadata" object.`);
}
const name = readStringLiteralProperty(metadataNode, "name");
if (!name) {
throw new Error(`[${relMeta}] metadata.name must be a string literal.`);
}
const componentsMap = readEntryMap(metadataNode, "components", relMeta);
if (!componentsMap) {
throw new Error(
`[${relMeta}] metadata.components must be an object literal.`,
);
}
return {
name,
dir: path.relative(packageRoot, path.dirname(metaFile)),
metaFile: relMeta,
keywords: readStringArrayProperty(metadataNode, "keywords"),
related: readStringArrayProperty(metadataNode, "related"),
componentsMap,
utilsMap: readEntryMap(metadataNode, "utils", relMeta),
};
}
function findMetaFiles() {
return fg.sync(META_GLOB, { cwd: packageRoot, absolute: true }).sort();
}
/**
* Reads every `*.meta.ts` file under the package without building a
* TypeScript program. Much faster than {@link parseMetaFiles}, but entries are
* not resolved to declarations. Use for spec validation only.
*/
function readMetaFiles(): MetaSummary[] {
const getLabels = (map: ts.ObjectLiteralExpression) =>
map.properties
.map(getEntryLabel)
.filter((label): label is string => Boolean(label));
return findMetaFiles().map((metaFile) => {
const sourceFile = ts.createSourceFile(
metaFile,
readFileSync(metaFile, "utf8"),
ts.ScriptTarget.Latest,
true,
);
const { componentsMap, utilsMap, ...meta } = readMetaSource(
sourceFile,
metaFile,
);
return {
...meta,
components: getLabels(componentsMap),
utils: utilsMap ? getLabels(utilsMap) : [],
};
});
}
/**
* Reads every `*.meta.ts` file under the package and resolves its
* `components`/`utils` identifiers to their declarations.
*/
function parseMetaFiles(): ParsedMeta[] {
const metaFiles = fg
.sync(META_GLOB, { cwd: packageRoot, absolute: true })
.sort();
const metaFiles = findMetaFiles();
if (metaFiles.length === 0) {
return [];
@@ -177,30 +301,19 @@ function parseMetaFiles(): ParsedMeta[] {
const entries: ResolvedEntry[] = [];
for (const entry of mapExpression.properties) {
let label: string | undefined;
let symbol: ts.Symbol | undefined;
if (ts.isShorthandPropertyAssignment(entry)) {
label = entry.name.text;
symbol = resolveAliasedSymbol(
typeChecker.getShorthandAssignmentValueSymbol(entry),
);
} else if (
ts.isPropertyAssignment(entry) &&
ts.isIdentifier(entry.initializer)
) {
label = ts.isStringLiteral(entry.name)
? entry.name.text
: entry.name.getText();
symbol = resolveAliasedSymbol(
typeChecker.getSymbolAtLocation(entry.initializer),
);
}
const label = getEntryLabel(entry);
if (!label) {
continue;
}
let symbol: ts.Symbol | undefined;
if (ts.isShorthandPropertyAssignment(entry)) {
symbol = typeChecker.getShorthandAssignmentValueSymbol(entry);
} else if (ts.isPropertyAssignment(entry)) {
symbol = typeChecker.getSymbolAtLocation(entry.initializer);
}
symbol = resolveAliasedSymbol(symbol);
const declaration = symbol?.valueDeclaration ?? symbol?.declarations?.[0];
if (!symbol || !declaration) {
throw new Error(
@@ -222,74 +335,24 @@ function parseMetaFiles(): ParsedMeta[] {
return entries;
}
/**
* Reads a `components`/`utils` map from the metadata object and resolves its
* entries. Throws when a required map is missing or either map is not an
* object literal.
*/
function resolveEntryMap(
metadataNode: ts.ObjectLiteralExpression,
key: "components" | "utils",
metaFile: string,
relMeta: string,
required: boolean,
): ResolvedEntry[] {
const prop = getObjectProperty(metadataNode, key);
if (!prop) {
if (required) {
throw new Error(
`[${relMeta}] metadata.${key} must be an object literal.`,
);
}
return [];
}
if (
!ts.isPropertyAssignment(prop) ||
!ts.isObjectLiteralExpression(prop.initializer)
) {
throw new Error(
`[${relMeta}] metadata.${key} must be an object literal.`,
);
}
return resolveEntries(prop.initializer, metaFile);
}
function parseMetaFile(metaFile: string): ParsedMeta {
const relMeta = path.relative(packageRoot, metaFile);
return metaFiles.map((metaFile) => {
const sourceFile = tsProgram.getSourceFile(metaFile);
if (!sourceFile) {
throw new Error(`Could not load meta file: ${relMeta}`);
throw new Error(
`Could not load meta file: ${path.relative(packageRoot, metaFile)}`,
);
}
const metadataNode = findMetadataObject(sourceFile);
if (!metadataNode) {
throw new Error(`[${relMeta}] Could not find a "metadata" object.`);
}
const name = readStringLiteralProperty(metadataNode, "name");
if (!name) {
throw new Error(`[${relMeta}] metadata.name must be a string literal.`);
}
const { componentsMap, utilsMap, ...meta } = readMetaSource(
sourceFile,
metaFile,
);
return {
name,
dir: path.relative(packageRoot, path.dirname(metaFile)),
metaFile: relMeta,
keywords: readStringArrayProperty(metadataNode, "keywords"),
related: readStringArrayProperty(metadataNode, "related"),
components: resolveEntryMap(
metadataNode,
"components",
metaFile,
relMeta,
true,
),
utils: resolveEntryMap(metadataNode, "utils", metaFile, relMeta, false),
...meta,
components: resolveEntries(componentsMap, metaFile),
utils: utilsMap ? resolveEntries(utilsMap, metaFile) : [],
};
}
return metaFiles.map(parseMetaFile);
});
}
export { parseMetaFiles };
export type { ResolvedEntry, ParsedMeta };
export { parseMetaFiles, readMetaFiles };
export type { ResolvedEntry, ParsedMeta, MetaSummary };
@@ -1,4 +1,4 @@
import type { ParsedMeta } from "./parse-meta";
import type { MetaSummary, ParsedMeta } from "./parse-meta";
/** Characters allowed in a Sanity document `_id`. */
const SANITY_ID_PATTERN = /^[A-Za-z0-9_.-]+$/;
@@ -9,7 +9,7 @@ const SANITY_ID_PATTERN = /^[A-Za-z0-9_.-]+$/;
* valid). Kept pure so it can be exercised both by the extractor (which throws
* on any error) and by the test-suite.
*/
function validateMetas(metas: ParsedMeta[]): string[] {
function validateMetas(metas: (ParsedMeta | MetaSummary)[]): string[] {
const errors: string[] = [];
const nameToMeta = new Map<string, string>();
@@ -102,7 +102,7 @@ const ActionMenuRoot = ({
rootElement={rootElement}
size={size}
>
<Menu open={open} onOpenChange={setOpen} modal>
<Menu open={open} onOpenChange={setOpen} modal={false}>
{children}
</Menu>
</ActionMenuProvider>
+1 -2
View File
@@ -120,10 +120,9 @@ function normalizeCount(value: number, fallback: number) {
return Number.isFinite(value) ? Math.max(0, Math.floor(value)) : fallback;
}
// eslint-disable-next-line @typescript-eslint/no-namespace, import/export
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace Badge {
export type Props = BadgeProps;
}
// eslint-disable-next-line import/export
export { Badge };
@@ -221,12 +221,11 @@ function isFieldVisible(
return fields?.[key] !== false;
}
// eslint-disable-next-line @typescript-eslint/no-namespace, import/export
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace DataGridPreferencesRoot {
export type Props = DataGridPreferencesProps;
}
export type { DataGridPreferencesProps };
// eslint-disable-next-line import/export
export { DataGridPreferencesRoot };
export default DataGridPreferencesRoot;
@@ -140,7 +140,7 @@ const DataGrid = DataGridInternal as unknown as DataGridComponent;
DataGrid.Table = DataGridTable;
DataGrid.Preferences = DataGridPreferences;
// eslint-disable-next-line @typescript-eslint/no-namespace, import/export
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace DataGrid {
export type Props<T = unknown> = DataGridProps<T>;
export type Columns<T = unknown> = ColumnDefinitions<T>;
@@ -166,6 +166,5 @@ export namespace DataGrid {
}
}
// eslint-disable-next-line import/export
export { DataGrid, DataGridInternal }; // DataGridRoot needs to be exported b.c. of docgen
export default DataGrid;
@@ -553,7 +553,7 @@ const DataGridTable = DataGridTableInternal as <RowT>(
props: DataGridTableProps<RowT> & React.RefAttributes<HTMLTableElement>,
) => React.ReactElement | null;
// eslint-disable-next-line @typescript-eslint/no-namespace, import/export
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace DataGridTable {
export type Props<T = unknown> = DataGridTableProps<T>;
export type Sorting = TableSortOptions;
@@ -566,7 +566,6 @@ export namespace DataGridTable {
}
// docgen doesn't work well with type params, so we let it use DataGridTableInternal instead
// eslint-disable-next-line import/export
export { DataGridTable, DataGridTableInternal };
export type { DataGridTableProps };
export default DataGridTable;
@@ -64,7 +64,7 @@ const DatePickerRoot = forwardRef<HTMLDivElement, DatePickerProps>(
onClose,
onOpenToggle,
strategy,
mode,
mode: _mode,
...rest
},
ref,
@@ -83,6 +83,8 @@ const DatePickerRoot = forwardRef<HTMLDivElement, DatePickerProps>(
value: _open,
});
const mode = _mode ?? "single";
const datePickerOpener = useRef<"from" | "to" | null>(null);
/* We use state here to insure that anchor is defined if open is true on initial render */
@@ -142,7 +144,7 @@ const DatePickerRoot = forwardRef<HTMLDivElement, DatePickerProps>(
}}
locale={locale}
translate={translate}
variant={mode ?? "single"}
variant={mode}
popoverProps={{
id: ariaId,
strategy,
@@ -137,7 +137,12 @@ export const Readonly = () => <BasicCombobox readOnly />;
export const Disabled = () => <BasicCombobox disabled />;
export const ErrorStory = () => <BasicCombobox error="Du må velge et land." />;
export const ErrorStory = () => (
<VStack gap="space-16">
<BasicCombobox error="Du må velge et land." defaultSelectedOptions={[]} />
<BasicCombobox error="Ugyldig land." />
</VStack>
);
ErrorStory.storyName = "Error";
export const DefaultOpen = () => <BasicCombobox defaultOpen />;
@@ -462,33 +467,50 @@ export const Testing = () => {
);
};
function WithValidation() {
const [error, setError] = useState("Du må velge et land");
return (
<BasicCombobox
options={nordicCountries}
defaultSelectedOptions={[]}
error={error}
onToggleOption={(option) =>
setError(option.label === "Island" ? "" : "Du må velge Island")
}
/>
);
}
export const ScreenreaderTesting = () => (
<div>
<h2>Med grupper</h2>
<BasicCombobox options={groupedCountries} defaultSelectedOptions={[]} />
<h2>Uten grupper</h2>
<BasicCombobox options={nordicCountries} defaultSelectedOptions={[]} />
<h2>Label skjult visuelt</h2>
<BasicCombobox options={nordicCountries} hideLabel />
<h2>Med feilmelding</h2>
<WithValidation />
<h2>Readonly med verdi</h2>
<BasicCombobox readOnly />
<h2>Readonly uten verdi</h2>
<BasicCombobox readOnly defaultSelectedOptions={[]} />
<h2>Disabled</h2>
<BasicCombobox disabled />
</div>
);
/* TODO:
- Mobil: Vurder å ikke fokusere søkefelt automatisk.
- Mobil: Vurder å ikke ha virtuelt fokus i det hele tatt hvis man ikke har tastatur
Kan kanskje bruke media query, ev. ikke gi noe fokus før man begynner å trykke (som dagens CB. Sjekk hvordan SR takler at ingenting har virtuelt fokus.)
Sjekk om det er mulig å flytte virtuelt fokus uten tastatur (typ med skjermleser).
- Vurder om fokus skal låses til søkefelt (mest aktuelt ved multiselect).
- Åpne på pil ned (og ev. opp)?
- Vurder funksjoner fra gamle CB (ikke brukt: dropp, brukt lite: muliggjør med komposisjon, brukt mye: bygg inn støtte)
- allowNewValues (er dette ofte egentlig Autocomplete?)
- isLoading
- maxSelected
- Skal den hete noe annet enn Combobox?
- Navngivning: Vurder å dele opp i separate komponenter for single- og multiselect.
- Hvordan unngå CSS-konflikt med dagens CB?
- Vurder om Label og Description (og error?) skal være sub-komponenter eller ikke.
- Følge Combobox-pattern (mer)? Kan ikke følge det slavisk uansett.
Pil opp og ned velger
Ikke loop
- A11y-sjekk (skjermleser, zoom, høykontrast...)
- Beslutningsloggen?
- Test med skjermleser
Utfordringer med komposisjon:
- Vanskelig å bruke
- Utfordrende for oss å endre (ref FormSummary)
- Context er litt magisk/uoversiktlig
- Description: Hvordan skal root/trigger vite at aria-describedby skal settes?
Forslag: Tilby enkeltkomponent for de vanligste tilfellene, men også subkomponentene for fleksibilitet.
Kan ev. ha slot/render-props for enkelte ting.
Kan ev. bruke children for å kunne bytte ut/skreddersy innholdet i popup.
*/
*/
@@ -103,10 +103,12 @@ function ComboboxModal({
useEffect(() => {
// See "Overlay" section in CSS file for context. This handles the case when
// the list doesn't overflow and overscroll-behavior doesn't work on iOS.
const scrollToTop = () => (document.documentElement.scrollTop = 0);
visualViewport?.addEventListener("scroll", scrollToTop);
return () => visualViewport?.removeEventListener("scroll", scrollToTop);
}, []);
if (open) {
const scrollToTop = () => (document.documentElement.scrollTop = 0);
visualViewport?.addEventListener("scroll", scrollToTop);
return () => visualViewport?.removeEventListener("scroll", scrollToTop);
}
}, [open]);
return (
<Dialog
@@ -21,32 +21,33 @@ export interface ComboboxPopupProps {
children: React.ReactNode;
}
// TODO: Bør den hete Dropdown eller Menu? (Og Overlay -> Popup?)
export const ComboboxPopup = ({ children }: ComboboxPopupProps) => {
const [virtuallyFocusedOptionValue, setVirtuallyFocusedOptionValue] =
useState("");
const [filterString, setFilterString] = useState("");
const rootContext = useComboboxRootContext();
const ref = useRef<HTMLDivElement | null>(null);
useEffect(
function onOpen() {
const canHover = window.matchMedia("(hover: hover)").matches;
// Virtually focus and scroll to first (selected) option
const selectedOptionElm = ref.current?.querySelector<HTMLElement>(
'[aria-selected="true"]',
);
if (selectedOptionElm) {
setVirtuallyFocusedOptionValue(selectedOptionElm.dataset.id || "");
setTimeout(
() => selectedOptionElm.scrollIntoView({ block: "nearest" }),
0,
);
if (canHover) {
setVirtuallyFocusedOptionValue(selectedOptionElm.dataset.id || "");
}
const scrollToSelectedOption = () =>
selectedOptionElm.scrollIntoView({ block: "nearest" });
setTimeout(scrollToSelectedOption, 0); // For when opened in Floating
queueMicrotask(scrollToSelectedOption); // For when opened in Dialog
} else {
const firstOptionElm =
ref.current?.querySelector<HTMLElement>('[role="option"]');
if (firstOptionElm) {
if (firstOptionElm && canHover) {
setVirtuallyFocusedOptionValue(firstOptionElm.dataset.id || "");
}
}
@@ -0,0 +1,14 @@
import type { ComponentMetadata } from "../../utils/types/metadata";
import { RadioCard, RadioCardGroup } from "./index";
const metadata: ComponentMetadata = {
name: "RadioCard",
components: {
RadioCard,
RadioCardGroup,
},
keywords: ["radio", "radioknapp", "form", "input", "choice", "card"],
related: ["Radio"],
};
export { metadata };
@@ -0,0 +1,118 @@
import type { Meta } from "@storybook/react-vite";
import React from "react";
import { HGrid } from "../../primitives/grid";
import { renderStoriesForChromatic } from "../../utils/renderStoriesForChromatic";
import { RadioCard } from "./RadioCard";
import { RadioCardGroup } from "./RadioCardGroup";
const meta: Meta<typeof RadioCardGroup> = {
title: "ds-react/RadioCard",
component: RadioCardGroup,
subcomponents: { RadioCard },
parameters: {
chromatic: { disable: true },
},
};
export default meta;
export const Default = () => (
<RadioCardGroup
legend="Velg leveringsmåte"
description="Du kan velge ett alternativ."
>
<RadioCard value="digital" description="Sendes til innboksen din.">
Digital levering
</RadioCard>
<RadioCard value="post" description="Sendes til folkeregistrert adresse.">
Post
</RadioCard>
</RadioCardGroup>
);
export const HorizontalLayout = () => (
<RadioCardGroup
legend="Velg leveringsmåte"
description="Du kan velge ett alternativ."
defaultValue="digital"
size="small"
>
<HGrid columns={{ xs: 1, md: 3 }}>
<RadioCard value="digital" description="Sendes til innboksen din.">
Digital levering
</RadioCard>
<RadioCard value="post" description="Sendes til folkeregistrert adresse.">
Post
</RadioCard>
<RadioCard value="seng" description="Blir servert på sengen.">
Servert på sengen
</RadioCard>
</HGrid>
</RadioCardGroup>
);
export const Small = () => (
<RadioCardGroup legend="Velg leveringsmåte" size="small">
<RadioCard value="digital" description="Sendes til innboksen din.">
Digital levering
</RadioCard>
<RadioCard value="post" description="Sendes til folkeregistrert adresse.">
Post
</RadioCard>
</RadioCardGroup>
);
export const ErrorProp = () => (
<RadioCardGroup
legend="Velg leveringsmåte"
error="Du må velge en leveringsmåte."
defaultValue="digital"
>
<RadioCard value="digital" description="Sendes til innboksen din.">
Digital levering
</RadioCard>
<RadioCard value="post" description="Sendes til folkeregistrert adresse.">
Post
</RadioCard>
</RadioCardGroup>
);
export const Readonly = () => (
<RadioCardGroup legend="Valgt leveringsmåte" defaultValue="digital" readOnly>
<RadioCard value="digital" description="Sendes til innboksen din.">
Digital levering
</RadioCard>
<RadioCard value="post" description="Sendes til folkeregistrert adresse.">
Post
</RadioCard>
</RadioCardGroup>
);
export const Disabled = () => (
<RadioCardGroup legend="Velg leveringsmåte" defaultValue="digital" disabled>
<RadioCard value="digital" description="Sendes til innboksen din.">
Digital levering
</RadioCard>
<RadioCard value="post" description="Sendes til folkeregistrert adresse.">
Post
</RadioCard>
</RadioCardGroup>
);
export const Chromatic = renderStoriesForChromatic({
Default,
HorizontalLayout,
Small,
ErrorProp,
Readonly,
Disabled,
});
export const ChromaticDark = renderStoriesForChromatic({
Default,
HorizontalLayout,
Small,
ErrorProp,
Readonly,
Disabled,
});
ChromaticDark.globals = { theme: "dark" };
@@ -0,0 +1,41 @@
import React, { forwardRef } from "react";
import { cl } from "../../utils/helpers";
import { Radio, RadioCardContextProvider } from "../radio/Radio";
import type { RadioProps } from "../radio/types";
type RadioCardProps = RadioProps;
/**
* Radio option styled as a card.
* @see 🏷️ {@link RadioCardProps}
* @example
* ```tsx
* <RadioCardGroup legend="Velg leveringsmåte">
* <RadioCard value="digital" description="Sendes til innboksen din.">
* Digital levering
* </RadioCard>
* <RadioCard value="post">Post</RadioCard>
* </RadioCardGroup>
* ```
*/
const RadioCard = forwardRef<HTMLInputElement, RadioCardProps>(
({ className, ...rest }, ref) => {
return (
<RadioCardContextProvider>
<Radio
{...rest}
ref={ref}
className={cl("aksel-radio-card", className)}
/>
</RadioCardContextProvider>
);
},
);
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace RadioCard {
export type Props = RadioCardProps;
}
export { RadioCard };
export type { RadioCardProps };
@@ -0,0 +1,34 @@
import React, { forwardRef } from "react";
import { cl } from "../../utils/helpers";
import RadioGroup, { type RadioGroupProps } from "../radio/RadioGroup";
type RadioCardGroupProps = RadioGroupProps;
/**
* RadioGroup for `<RadioCard />` elements. Horizontal layout is best suited for a small number of radio cards.
* @see 🏷️ {@link RadioCardGroupProps}
* @example
* ```tsx
* <RadioCardGroup legend="Velg leveringsmåte">
* <RadioCard value="digital">Digital levering</RadioCard>
* <RadioCard value="post">Post</RadioCard>
* </RadioCardGroup>
* ```
*/
const RadioCardGroup = forwardRef<HTMLFieldSetElement, RadioCardGroupProps>(
({ className, ...rest }, ref) => (
<RadioGroup
{...rest}
ref={ref}
className={cl("aksel-radio-card-group", className)}
/>
),
);
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace RadioCardGroup {
export type Props = RadioCardGroupProps;
}
export { RadioCardGroup };
export type { RadioCardGroupProps };
@@ -0,0 +1,3 @@
"use client";
export { RadioCard } from "./RadioCard";
export { RadioCardGroup } from "./RadioCardGroup";
@@ -69,3 +69,22 @@ describe("Uncontrolled RadioGroup", () => {
expect(input2.checked).toBe(true);
});
});
describe("Radio outside RadioCard", () => {
test("does not select when clicking description", async () => {
const user = userEvent.setup();
render(
<RadioGroup legend="legend">
<Radio value={value1} description="Radio description">
{label1}
</Radio>
</RadioGroup>,
);
await user.click(screen.getByText("Radio description"));
expect(
screen.getByRole<HTMLInputElement>("radio", { name: label1 }).checked,
).toBe(false);
});
});
+31 -3
View File
@@ -1,19 +1,29 @@
import React, { forwardRef } from "react";
import React, { forwardRef, useRef } from "react";
import { BodyShort } from "../../typography";
import { omit, useId } from "../../utils-external";
import { cl } from "../../utils/helpers";
import { cl, createStrictContext } from "../../utils/helpers";
import { useMergeRefs } from "../../utils/hooks";
import { RadioInput } from "./radio-input/RadioInput";
import type { RadioProps } from "./types";
import { useRadio } from "./useRadio";
const { Provider: RadioCardContextProvider, useContext: useRadioCardContext } =
createStrictContext({ name: "radio-card" });
export const Radio = forwardRef<HTMLInputElement, RadioProps>(
(props: RadioProps, forwardedRef) => {
const { inputProps, size, hasError, readOnly } = useRadio(props);
const descriptionId = useId();
const cardContext = useRadioCardContext(false);
const radioRef = useRef<HTMLInputElement>(null);
const mergedRefs = useMergeRefs(forwardedRef, radioRef);
const { className, description, children } = props;
return (
// biome-ignore lint/a11y/noStaticElementInteractions: clickable div for radio card
// biome-ignore lint/a11y/useKeyWithClickEvents: clickable div for radio card
<div
className={cl(className, "aksel-radio", `aksel-radio--${size}`, {
"aksel-radio--error": hasError,
@@ -21,9 +31,26 @@ export const Radio = forwardRef<HTMLInputElement, RadioProps>(
"aksel-radio--readonly": readOnly,
})}
data-color={hasError ? "danger" : props["data-color"]}
onClick={(event) => {
if (inputProps.disabled || readOnly || !cardContext) {
return;
}
/* Let input and label handle their own click events */
const target = event.target;
const nativeHandler =
target instanceof Element ? target.closest("label, input") : null;
if (nativeHandler && event.currentTarget.contains(nativeHandler)) {
return;
}
radioRef.current?.click();
radioRef.current?.focus({ preventScroll: true });
}}
>
<RadioInput
ref={forwardedRef}
ref={mergedRefs}
{...omit(props, [
"children",
"size",
@@ -62,3 +89,4 @@ export const Radio = forwardRef<HTMLInputElement, RadioProps>(
);
export default Radio;
export { RadioCardContextProvider };
+2 -1
View File
@@ -1,4 +1,5 @@
"use client";
export { default as Radio } from "./Radio";
export { default as RadioGroup, type RadioGroupProps } from "./RadioGroup";
export { default as RadioGroup } from "./RadioGroup";
export type { RadioGroupProps } from "./RadioGroup";
export type { RadioProps } from "./types";
+1
View File
@@ -202,6 +202,7 @@ export {
type DialogBodyProps,
type DialogFooterProps,
} from "./dialog";
export { Badge } from "./badge";
/**
* Theming
+1
View File
@@ -2,3 +2,4 @@
export { DataGrid } from "./data-grid";
export { Lookup } from "./lookup";
export { Badge } from "./badge";
export { RadioCard, RadioCardGroup } from "./form/radio-card";
@@ -104,7 +104,7 @@ export const ArrowDown: StoryObj = {
expect(getVirtuallyFocusedValue(canvasElement)).toBe("last");
pressKey(canvasElement, "ArrowDown");
expect(getVirtuallyFocusedValue(canvasElement)).toBe("first");
expect(getVirtuallyFocusedValue(canvasElement)).toBe("last"); // No looping
},
};
@@ -127,7 +127,7 @@ export const ArrowUp: StoryObj = {
expect(getVirtuallyFocusedValue(canvasElement)).toBe("first");
pressKey(canvasElement, "ArrowUp");
expect(getVirtuallyFocusedValue(canvasElement)).toBe("last");
expect(getVirtuallyFocusedValue(canvasElement)).toBe("first"); // No looping
},
};
@@ -163,7 +163,7 @@ export const PageUpAndPageDownKeys: StoryObj = {
expect(getVirtuallyFocusedValue(canvasElement)).toBe("14"); // Last
pressKey(canvasElement, "PageDown");
expect(getVirtuallyFocusedValue(canvasElement)).toBe("14"); // No looping for PageUp/Down
expect(getVirtuallyFocusedValue(canvasElement)).toBe("14"); // No looping
pressKey(canvasElement, "PageUp");
expect(getVirtuallyFocusedValue(canvasElement)).toBe("9");
@@ -175,7 +175,7 @@ export const PageUpAndPageDownKeys: StoryObj = {
expect(getVirtuallyFocusedValue(canvasElement)).toBe("0");
pressKey(canvasElement, "PageUp");
expect(getVirtuallyFocusedValue(canvasElement)).toBe("0"); // No looping for PageUp/Down
expect(getVirtuallyFocusedValue(canvasElement)).toBe("0"); // No looping
},
};
@@ -211,3 +211,54 @@ export const EnterKey: StoryObj<{
onClick: fn(),
},
};
const ListboxStoryWithSelectedOption = () => {
const [virtuallyFocusedOptionId, setVirtuallyFocusedOptionId] = useState("");
return (
<Listbox setVirtuallyFocusedOptionId={setVirtuallyFocusedOptionId}>
<Listbox.Options>
<Listbox.Option
id="first-item"
onClick={() => {}}
hasVirtualFocus={virtuallyFocusedOptionId === "first-item"}
aria-selected={false}
>
First item
</Listbox.Option>
<Listbox.Option
id="selected-item"
onClick={() => {}}
hasVirtualFocus={virtuallyFocusedOptionId === "selected-item"}
aria-selected
>
Selected item
</Listbox.Option>
<Listbox.Option
id="last-item"
onClick={() => {}}
hasVirtualFocus={virtuallyFocusedOptionId === "last-item"}
aria-selected={false}
>
Last item
</Listbox.Option>
</Listbox.Options>
</Listbox>
);
};
// When there's a selected option, but no option has virtual focus,
// pressing ArrowUp or ArrowDown should focus the selected option.
export const ArrowDownFocusesSelectedOption: StoryObj = {
render: ListboxStoryWithSelectedOption,
play: async ({ canvasElement }) => {
pressKey(canvasElement, "ArrowDown");
expect(getVirtuallyFocusedValue(canvasElement)).toBe("selected-item");
},
};
export const ArrowUpFocusesSelectedOption: StoryObj = {
render: ListboxStoryWithSelectedOption,
play: async ({ canvasElement }) => {
pressKey(canvasElement, "ArrowUp");
expect(getVirtuallyFocusedValue(canvasElement)).toBe("selected-item");
},
};
@@ -1,6 +1,7 @@
/** biome-ignore-all lint/a11y/useKeyWithMouseEvents: We know what we are doing */
import React from "react";
import { cl } from "../../../helpers";
import { useMedia } from "../../../hooks";
import { useListboxContext } from "../root/Listbox.context";
export interface ListboxOptionsProps extends Omit<
@@ -12,6 +13,8 @@ export interface ListboxOptionsProps extends Omit<
function ListboxOptions({ children, ...rest }: ListboxOptionsProps) {
const { setVirtuallyFocusedOptionId } = useListboxContext();
const cannotHover = useMedia("(hover: none)");
return (
<div
{...rest}
@@ -19,6 +22,11 @@ function ListboxOptions({ children, ...rest }: ListboxOptionsProps) {
role="listbox"
tabIndex={-1}
onMouseOver={(event) => {
if (cannotHover) {
// onMouseOver can be triggered by touch, but we don't want virtual focus in that case.
// We assume you are using touch if the primary input mechanism cannot hover.
return;
}
const target = event.target as HTMLElement;
const optionEl: HTMLElement | null = target.closest('[role="option"]');
if (optionEl) {
@@ -79,18 +79,16 @@ const ListboxComponent = forwardRef<HTMLDivElement, ListboxProps>(
) => {
event.preventDefault();
if (!focusedOptionElm) {
virtuallyFocusOption(getFallback());
const fallback =
listbox.querySelector<HTMLElement>('[aria-selected="true"]') ||
getFallback();
virtuallyFocusOption(fallback);
return;
}
const nextOption = getNextElement(focusedOptionElm);
if (!nextOption) {
// TODO: Consider whether we want looping.
// If yes, consider if PageUp/PageDown should loop too.
// If no, remember to update tests.
virtuallyFocusOption(getFallback());
return;
if (nextOption) {
virtuallyFocusOption(nextOption);
}
virtuallyFocusOption(nextOption);
};
switch (event.key) {
+12
View File
@@ -1,5 +1,17 @@
# @navikt/ds-tailwind
## 8.19.1
## 8.19.0
## 8.18.0
### Patch Changes
- v3 preset adds `rounded-ax-*` and no longer lists radius tokens as colors ([#5329](https://github.com/navikt/aksel/pull/5329))
- v4 theme now uses `@theme inline`, so dark mode and `data-color` work with Tailwind utilities. Font sizes are available as `text-ax-*` and font family as `font-ax-font-family` (`font-family-ax-font-family` is deprecated) ([#5329](https://github.com/navikt/aksel/pull/5329))
## 8.17.2
## 8.17.1
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-tailwind",
"version": "8.17.2",
"version": "8.19.1",
"description": "Tailwind config based on @navikt/ds-tokens",
"author": "Aksel | Nav designsystem team",
"keywords": [
@@ -28,10 +28,10 @@
},
"homepage": "https://aksel.nav.no/grunnleggende/kode/tailwind/",
"devDependencies": {
"@navikt/ds-tokens": "^8.17.2",
"@navikt/ds-tokens": "^8.19.1",
"@types/node": "^24.10.9",
"tailwindcss": "^3.3.3",
"tsx": "^4.23.1",
"tsx": "^4.23.15",
"typescript": "6.0.3",
"vitest": "4.1.11"
},
+2 -1
View File
@@ -19,7 +19,7 @@ const nonColorTokens = [
"font-size",
"font-line-height",
"font-family",
"border-radius",
"radius",
"breakpoint",
"opacity",
];
@@ -48,6 +48,7 @@ export const config = {
lineHeight: prefixTokens(extractTokensForCategory("font-line-height")),
fontFamily: prefixTokens(extractTokensForCategory("font-family")),
opacity: prefixTokens(extractTokensForCategory("opacity")),
borderRadius: prefixTokens(extractTokensForCategory("radius")),
},
},
};
+5 -2
View File
@@ -48,7 +48,8 @@ export const v4Config = {
};
function generateThemeCSS(): string {
const lines: string[] = ["@theme {"];
/* `inline` makes utilities reference `--ax-*` directly, so nested themes (`.dark`, `data-color`, `<Theme>`) resolve correctly */
const lines: string[] = ["@theme inline {"];
for (const [key, value] of Object.entries(v4Config.colors)) {
lines.push(` --color-ax-${key}: ${value};`);
@@ -61,7 +62,7 @@ function generateThemeCSS(): string {
lines.push("");
for (const [key, value] of Object.entries(v4Config.fontSizes)) {
lines.push(` --font-size-ax-${key}: ${value};`);
lines.push(` --text-ax-${key}: ${value};`);
}
lines.push("");
@@ -76,6 +77,8 @@ function generateThemeCSS(): string {
lines.push("");
for (const [key, value] of Object.entries(v4Config.fontFamilies)) {
lines.push(` --font-ax-${key}: ${value};`);
/* @deprecated Kept for backwards compatibility, use `font-ax-*` */
lines.push(` --font-family-ax-${key}: ${value};`);
}
+44 -16
View File
@@ -16,6 +16,7 @@ describe("Tailwind v3 config", () => {
});
});
/* https://tailwindcss.com/docs/theme#theme-variable-namespaces */
const allowedVariables = [
"--color-",
"--font-",
@@ -40,18 +41,32 @@ const allowedVariables = [
"--opacity-",
];
const getVariables = (css: string) =>
css
.split("\n")
.filter((line) => line.trim().startsWith("--"))
.map((line) => line.trim().split(":")[0]);
const nonColorCategories = [
"space",
"shadow",
"font-weight",
"font-size",
"font-line-height",
"font-family",
"radius",
"breakpoint",
"opacity",
];
describe("Tailwind v4 config", () => {
test("should have correct color tokens without non-color categories", () => {
const colorKeys = Object.keys(v4Config.colors);
expect(colorKeys).not.toContain("spacing");
expect(colorKeys).not.toContain("shadow");
expect(colorKeys).not.toContain("font-weight");
expect(colorKeys).not.toContain("font-size");
expect(colorKeys).not.toContain("font-line-height");
expect(colorKeys).not.toContain("font-family");
expect(colorKeys).not.toContain("border-radius");
expect(colorKeys).not.toContain("breakpoint");
expect(colorKeys.some((k) => k.includes("radius"))).toBe(false);
const invalid = colorKeys.filter((key) =>
nonColorCategories.some((category) => key.includes(category)),
);
expect(invalid).toEqual([]);
});
test("should have shadow tokens", () => {
@@ -76,19 +91,32 @@ describe("Tailwind v4 config", () => {
expect(Object.keys(v4Config.radius).length).toBeGreaterThan(0);
});
test("uses `@theme inline` so aliases resolve where the utility is used", () => {
expect(generateThemeCSS().split("\n")[0]).toBe("@theme inline {");
});
test("all generated variables should use allowed Tailwind v4 theme namespaces", () => {
const css = generateThemeCSS();
const variableLines = css
.split("\n")
.filter((line) => line.trim().startsWith("--"))
.map((line) => line.trim().split(":")[0]);
const variables = getVariables(generateThemeCSS());
expect(variableLines.length).toBeGreaterThan(0);
expect(variables.length).toBeGreaterThan(0);
const invalid = variableLines.filter(
const invalid = variables.filter(
(v) => !allowedVariables.some((prefix) => v.startsWith(prefix)),
);
expect(invalid).toEqual([]);
});
test("font families use the `--font-*` namespace", () => {
expect(getVariables(generateThemeCSS())).toContain("--font-ax-font-family");
});
test("font-size keys don't collide with color keys in `text-ax-*`", () => {
const colorKeys = Object.keys(v4Config.colors);
const collisions = Object.keys(v4Config.fontSizes).filter((key) =>
colorKeys.includes(key),
);
expect(collisions).toEqual([]);
});
});
+6
View File
@@ -1,5 +1,11 @@
# @navikt/ds-tokens
## 8.19.1
## 8.19.0
## 8.18.0
## 8.17.2
## 8.17.1
+4 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-tokens",
"version": "8.17.2",
"version": "8.19.1",
"description": "Design-tokens for Nav designsystem",
"author": "Aksel | Nav designsystem team",
"keywords": [
@@ -35,12 +35,13 @@
"homepage": "https://aksel.nav.no/grunnleggende/styling/design-tokens/",
"devDependencies": {
"@figma/plugin-typings": "^1.131.0",
"@types/lodash": "4.17.25",
"colorjs.io": "^0.7.1",
"esbuild": "^0.28.2",
"lightningcss": "^1.33.0",
"lodash": "4.18.1",
"style-dictionary": "^4.1.1",
"tsx": "^4.23.1",
"style-dictionary": "^5.5.5",
"tsx": "^4.23.15",
"typescript": "6.0.3",
"vitest": "4.1.11"
},
@@ -45,15 +45,16 @@ export const getTokensForCollection = async (
return dictionary.allTokens
.filter((token) => !token.figmaIgnore)
.map((token) => {
const reference = getReferences(token.original, dictionary.tokens);
const references = getReferences(token.original, dictionary.tokens);
/*
* Currently only supports 1 level of references.
* The array always includes the token itself as the last element.
*/
return reference.length > 0
return references.length > 1
? prepareToken(
{
...(token as TransformedTokenWithScopes),
alias: createTokenName(reference[0]),
alias: createTokenName(references[0]),
},
dictionary,
)
+1 -1
View File
@@ -41,7 +41,7 @@ export type StyleDictionaryToken<T extends TokenTypes> = {
* Token value
* @example "#000000"
* @example "1px"
* @example "{a.neutral.100.value}"
* @example "{a.neutral.100}"
*/
value: string;
/**
@@ -3,77 +3,77 @@ import type { StyleDictionaryTokenConfig } from "../../../tokens.util";
export const accentSemanticTokenConfig = {
bg: {
"accent-soft": {
value: "{ax.accent.100.value}",
value: "{ax.accent.100}",
type: "color",
group: "background.accent",
comment:
"En svak bakgrunnsfarge som kun brukes til dekor. Dekker ingen kontrastkrav.",
},
"accent-softA": {
value: "{ax.accent.100A.value}",
value: "{ax.accent.100A}",
type: "color",
group: "background.accent",
comment:
"En svak bakgrunnsfarge som kun brukes til dekor. Dekker ingen kontrastkrav. Er delvis gjennomsiktig.",
},
"accent-moderate": {
value: "{ax.accent.200.value}",
value: "{ax.accent.200}",
type: "color",
group: "background.accent",
comment:
"En medium-svak bakgrunnsfarge som brukes til dekor. Om den brukes på meningsbærende elementer må den kombineres med en godkjent border.",
},
"accent-moderateA": {
value: "{ax.accent.200A.value}",
value: "{ax.accent.200A}",
type: "color",
group: "background.accent",
comment:
"En medium-svak bakgrunnsfarge som brukes til dekor. Om den brukes på meningsbærende elementer må den kombineres med en godkjent border. Er delvis gjennomsiktig.",
},
"accent-moderate-hover": {
value: "{ax.accent.300.value}",
value: "{ax.accent.300}",
type: "color",
group: "background.accent",
comment:
"En medium-svak bakgrunnsfarge som brukes til hover-state på meningsbærende elementer. Må kombineres med en godkjent border.",
},
"accent-moderate-hoverA": {
value: "{ax.accent.300A.value}",
value: "{ax.accent.300A}",
type: "color",
group: "background.accent",
comment:
"En medium-svak bakgrunnsfarge som brukes til hover-state på meningsbærende elementer (f.eks. button accent secondary hover). Må kombineres med en godkjent border. Er delvis gjennomsiktig.",
},
"accent-moderate-pressed": {
value: "{ax.accent.400.value}",
value: "{ax.accent.400}",
type: "color",
group: "background.accent",
comment:
"En medium-svak bakgrunnsfarge som brukes til active/selected-state på meningsbærende elementer. Må kombineres med en godkjent border.",
},
"accent-moderate-pressedA": {
value: "{ax.accent.400A.value}",
value: "{ax.accent.400A}",
type: "color",
group: "background.accent",
comment:
"En medium-svak bakgrunnsfarge som brukes til active/selected-state på meningsbærende elementer (f.eks. button accent secondary active). Må kombineres med en godkjent border. Er delvis gjennomsiktig.",
},
"accent-strong": {
value: "{ax.accent.600.value}",
value: "{ax.accent.600}",
type: "color",
group: "background.accent",
comment:
"En sterk bakgrunnsfarge som brukes på meningsbærende elementer (f.eks. button accent primary).",
},
"accent-strong-hover": {
value: "{ax.accent.700.value}",
value: "{ax.accent.700}",
type: "color",
group: "background.accent",
comment:
"En sterk bakgrunnsfarge som brukes til hover-state på meningsbærende elementer (f.eks. button accent primary hover).",
},
"accent-strong-pressed": {
value: "{ax.accent.800.value}",
value: "{ax.accent.800}",
type: "color",
group: "background.accent",
comment:
@@ -82,28 +82,28 @@ export const accentSemanticTokenConfig = {
},
text: {
accent: {
value: "{ax.accent.1000.value}",
value: "{ax.accent.1000}",
type: "color",
group: "text.accent",
comment:
"Sterk farge for tekst og ikoner for rollen accent. Godkjent på alle bakgrunner unntatt strong.",
},
"accent-subtle": {
value: "{ax.accent.800.value}",
value: "{ax.accent.800}",
type: "color",
group: "text.accent",
comment:
"Standard farge for tekst og ikoner med rollen accent. Godkjent på alle bakgrunner unntatt strong.",
},
"accent-decoration": {
value: "{ax.accent.600.value}",
value: "{ax.accent.600}",
type: "color",
group: "text.accent",
comment:
"En farge som kun brukes på ikke-tekstlig innhold (ikoner og andre grafiske elementer). **Ikke godkjent på tekst altså**.",
},
"accent-contrast": {
value: "{ax.neutral.000.value}",
value: "{ax.neutral.000}",
type: "color",
group: "text.accent",
comment:
@@ -112,27 +112,27 @@ export const accentSemanticTokenConfig = {
},
border: {
accent: {
value: "{ax.accent.600.value}",
value: "{ax.accent.600}",
type: "color",
group: "border.accent",
comment: "Standard farge for border.",
},
"accent-subtle": {
value: "{ax.accent.400.value}",
value: "{ax.accent.400}",
type: "color",
group: "border.accent",
comment:
"En svak border-farge som brukes til dekor. Om den brukes på interaktive elementer må den kombineres med elementer som tydelig signaliserer interaksjon.",
},
"accent-subtleA": {
value: "{ax.accent.400A.value}",
value: "{ax.accent.400A}",
type: "color",
group: "border.accent",
comment:
"En svak border-farge som brukes til dekor. Om den brukes på interaktive elementer må den kombineres med elementer som tydelig signaliserer interaksjon. Er delvis gjennomsiktig.",
},
"accent-strong": {
value: "{ax.accent.700.value}",
value: "{ax.accent.700}",
type: "color",
group: "border.accent",
comment:
@@ -3,77 +3,77 @@ import type { StyleDictionaryTokenConfig } from "../../../tokens.util";
export const brandBeigeSemanticTokenConfig = {
bg: {
"brand-beige-soft": {
value: "{ax.brand-beige.100.value}",
value: "{ax.brand-beige.100}",
type: "color",
group: "background.brand-beige",
comment:
"En svak bakgrunnsfarge som kun brukes til dekor. Dekker ingen kontrastkrav.",
},
"brand-beige-softA": {
value: "{ax.brand-beige.100A.value}",
value: "{ax.brand-beige.100A}",
type: "color",
group: "background.brand-beige",
comment:
"En svak bakgrunnsfarge som kun brukes til dekor. Dekker ingen kontrastkrav. Er delvis gjennomsiktig.",
},
"brand-beige-moderate": {
value: "{ax.brand-beige.200.value}",
value: "{ax.brand-beige.200}",
type: "color",
group: "background.brand-beige",
comment:
"En medium-svak bakgrunnsfarge som brukes til dekor. Om den brukes på meningsbærende elementer må den kombineres med en godkjent border.",
},
"brand-beige-moderateA": {
value: "{ax.brand-beige.200A.value}",
value: "{ax.brand-beige.200A}",
type: "color",
group: "background.brand-beige",
comment:
"En medium-svak bakgrunnsfarge som brukes til dekor. Om den brukes på meningsbærende elementer må den kombineres med en godkjent border. Er delvis gjennomsiktig.",
},
"brand-beige-moderate-hover": {
value: "{ax.brand-beige.300.value}",
value: "{ax.brand-beige.300}",
type: "color",
group: "background.brand-beige",
comment:
"En medium-svak bakgrunnsfarge som brukes til hover-state på meningsbærende elementer. Må kombineres med en godkjent border.",
},
"brand-beige-moderate-hoverA": {
value: "{ax.brand-beige.300A.value}",
value: "{ax.brand-beige.300A}",
type: "color",
group: "background.brand-beige",
comment:
"En medium-svak bakgrunnsfarge som brukes til hover-state på meningsbærende elementer. Må kombineres med en godkjent border. Er delvis gjennomsiktig.",
},
"brand-beige-moderate-pressed": {
value: "{ax.brand-beige.400.value}",
value: "{ax.brand-beige.400}",
type: "color",
group: "background.brand-beige",
comment:
"En medium-svak bakgrunnsfarge som brukes til active/selected-state på meningsbærende elementer. Må kombineres med en godkjent border.",
},
"brand-beige-moderate-pressedA": {
value: "{ax.brand-beige.400A.value}",
value: "{ax.brand-beige.400A}",
type: "color",
group: "background.brand-beige",
comment:
"En medium-svak bakgrunnsfarge som brukes til active/selected-state på meningsbærende elementer. Må kombineres med en godkjent border. Er delvis gjennomsiktig.",
},
"brand-beige-strong": {
value: "{ax.brand-beige.600.value}",
value: "{ax.brand-beige.600}",
type: "color",
group: "background.brand-beige",
comment:
"En sterk bakgrunnsfarge som brukes på meningsbærende elementer.",
},
"brand-beige-strong-hover": {
value: "{ax.brand-beige.700.value}",
value: "{ax.brand-beige.700}",
type: "color",
group: "background.brand-beige",
comment:
"En sterk bakgrunnsfarge som brukes til hover-state på meningsbærende elementer.",
},
"brand-beige-strong-pressed": {
value: "{ax.brand-beige.800.value}",
value: "{ax.brand-beige.800}",
type: "color",
group: "background.brand-beige",
comment:
@@ -82,28 +82,28 @@ export const brandBeigeSemanticTokenConfig = {
},
text: {
"brand-beige": {
value: "{ax.brand-beige.1000.value}",
value: "{ax.brand-beige.1000}",
type: "color",
group: "text.brand-beige",
comment:
"Sterk farge for tekst og ikoner for rollen brand beige. Godkjent på alle bakgrunner unntatt strong.",
},
"brand-beige-subtle": {
value: "{ax.brand-beige.800.value}",
value: "{ax.brand-beige.800}",
type: "color",
group: "text.brand-beige",
comment:
"Standard farge for tekst og ikoner med rollen brand beige. Godkjent på alle bakgrunner unntatt strong.",
},
"brand-beige-decoration": {
value: "{ax.brand-beige.600.value}",
value: "{ax.brand-beige.600}",
type: "color",
group: "text.brand-beige",
comment:
"En farge som kun brukes på ikke-tekstlig innhold (ikoner og andre grafiske elementer). **Ikke godkjent på tekst altså**.",
},
"brand-beige-contrast": {
value: "{ax.neutral.000.value}",
value: "{ax.neutral.000}",
type: "color",
group: "text.brand-beige",
comment:
@@ -112,27 +112,27 @@ export const brandBeigeSemanticTokenConfig = {
},
border: {
"brand-beige": {
value: "{ax.brand-beige.600.value}",
value: "{ax.brand-beige.600}",
type: "color",
group: "border.brand-beige",
comment: "Standard farge for border.",
},
"brand-beige-subtle": {
value: "{ax.brand-beige.400.value}",
value: "{ax.brand-beige.400}",
type: "color",
group: "border.brand-beige",
comment:
"En svak border-farge som brukes til dekor. Om den brukes på interaktive elementer må den kombineres med elementer som tydelig signaliserer interaksjon.",
},
"brand-beige-subtleA": {
value: "{ax.brand-beige.400A.value}",
value: "{ax.brand-beige.400A}",
type: "color",
group: "border.brand-beige",
comment:
"En svak border-farge som brukes til dekor. Om den brukes på interaktive elementer må den kombineres med elementer som tydelig signaliserer interaksjon. Er delvis gjennomsiktig.",
},
"brand-beige-strong": {
value: "{ax.brand-beige.700.value}",
value: "{ax.brand-beige.700}",
type: "color",
group: "border.brand-beige",
comment:
@@ -3,77 +3,77 @@ import type { StyleDictionaryTokenConfig } from "../../../tokens.util";
export const brandBlueSemanticTokenConfig = {
bg: {
"brand-blue-soft": {
value: "{ax.brand-blue.100.value}",
value: "{ax.brand-blue.100}",
type: "color",
group: "background.brand-blue",
comment:
"En svak bakgrunnsfarge som kun brukes til dekor. Dekker ingen kontrastkrav.",
},
"brand-blue-softA": {
value: "{ax.brand-blue.100A.value}",
value: "{ax.brand-blue.100A}",
type: "color",
group: "background.brand-blue",
comment:
"En svak bakgrunnsfarge som kun brukes til dekor. Dekker ingen kontrastkrav. Er delvis gjennomsiktig.",
},
"brand-blue-moderate": {
value: "{ax.brand-blue.200.value}",
value: "{ax.brand-blue.200}",
type: "color",
group: "background.brand-blue",
comment:
"En medium-svak bakgrunnsfarge som brukes til dekor. Om den brukes på meningsbærende elementer må den kombineres med en godkjent border.",
},
"brand-blue-moderateA": {
value: "{ax.brand-blue.200A.value}",
value: "{ax.brand-blue.200A}",
type: "color",
group: "background.brand-blue",
comment:
"En medium-svak bakgrunnsfarge som brukes til dekor. Om den brukes på meningsbærende elementer må den kombineres med en godkjent border. Er delvis gjennomsiktig.",
},
"brand-blue-moderate-hover": {
value: "{ax.brand-blue.300.value}",
value: "{ax.brand-blue.300}",
type: "color",
group: "background.brand-blue",
comment:
"En medium-svak bakgrunnsfarge som brukes til hover-state på meningsbærende elementer. Må kombineres med en godkjent border.",
},
"brand-blue-moderate-hoverA": {
value: "{ax.brand-blue.300A.value}",
value: "{ax.brand-blue.300A}",
type: "color",
group: "background.brand-blue",
comment:
"En medium-svak bakgrunnsfarge som brukes til hover-state på meningsbærende elementer. Må kombineres med en godkjent border. Er delvis gjennomsiktig.",
},
"brand-blue-moderate-pressed": {
value: "{ax.brand-blue.400.value}",
value: "{ax.brand-blue.400}",
type: "color",
group: "background.brand-blue",
comment:
"En medium-svak bakgrunnsfarge som brukes til active/selected-state på meningsbærende elementer. Må kombineres med en godkjent border.",
},
"brand-blue-moderate-pressedA": {
value: "{ax.brand-blue.400A.value}",
value: "{ax.brand-blue.400A}",
type: "color",
group: "background.brand-blue",
comment:
"En medium-svak bakgrunnsfarge som brukes til active/selected-state på meningsbærende elementer. Må kombineres med en godkjent border. Er delvis gjennomsiktig.",
},
"brand-blue-strong": {
value: "{ax.brand-blue.600.value}",
value: "{ax.brand-blue.600}",
type: "color",
group: "background.brand-blue",
comment:
"En sterk bakgrunnsfarge som brukes på meningsbærende elementer.",
},
"brand-blue-strong-hover": {
value: "{ax.brand-blue.700.value}",
value: "{ax.brand-blue.700}",
type: "color",
group: "background.brand-blue",
comment:
"En sterk bakgrunnsfarge som brukes til hover-state på meningsbærende elementer.",
},
"brand-blue-strong-pressed": {
value: "{ax.brand-blue.800.value}",
value: "{ax.brand-blue.800}",
type: "color",
group: "background.brand-blue",
comment:
@@ -82,28 +82,28 @@ export const brandBlueSemanticTokenConfig = {
},
text: {
"brand-blue": {
value: "{ax.brand-blue.1000.value}",
value: "{ax.brand-blue.1000}",
type: "color",
group: "text.brand-blue",
comment:
"Sterk farge for tekst og ikoner for rollen brand blue. Godkjent på alle bakgrunner unntatt strong.",
},
"brand-blue-subtle": {
value: "{ax.brand-blue.800.value}",
value: "{ax.brand-blue.800}",
type: "color",
group: "text.brand-blue",
comment:
"Standard farge for tekst og ikoner med rollen brand blue. Godkjent på alle bakgrunner unntatt strong.",
},
"brand-blue-decoration": {
value: "{ax.brand-blue.600.value}",
value: "{ax.brand-blue.600}",
type: "color",
group: "text.brand-blue",
comment:
"En farge som kun brukes på ikke-tekstlig innhold (ikoner og andre grafiske elementer). **Ikke godkjent på tekst altså**.",
},
"brand-blue-contrast": {
value: "{ax.neutral.000.value}",
value: "{ax.neutral.000}",
type: "color",
group: "text.brand-blue",
comment:
@@ -112,27 +112,27 @@ export const brandBlueSemanticTokenConfig = {
},
border: {
"brand-blue": {
value: "{ax.brand-blue.600.value}",
value: "{ax.brand-blue.600}",
type: "color",
group: "border.brand-blue",
comment: "Standard farge for border.",
},
"brand-blue-subtle": {
value: "{ax.brand-blue.400.value}",
value: "{ax.brand-blue.400}",
type: "color",
group: "border.brand-blue",
comment:
"En svak border-farge som brukes til dekor. Om den brukes på interaktive elementer må den kombineres med elementer som tydelig signaliserer interaksjon.",
},
"brand-blue-subtleA": {
value: "{ax.brand-blue.400A.value}",
value: "{ax.brand-blue.400A}",
type: "color",
group: "border.brand-blue",
comment:
"En svak border-farge som brukes til dekor. Om den brukes på interaktive elementer må den kombineres med elementer som tydelig signaliserer interaksjon. Er delvis gjennomsiktig.",
},
"brand-blue-strong": {
value: "{ax.brand-blue.700.value}",
value: "{ax.brand-blue.700}",
type: "color",
group: "border.brand-blue",
comment:
@@ -3,77 +3,77 @@ import type { StyleDictionaryTokenConfig } from "../../../tokens.util";
export const brandMagentaSemanticTokenConfig = {
bg: {
"brand-magenta-soft": {
value: "{ax.brand-magenta.100.value}",
value: "{ax.brand-magenta.100}",
type: "color",
group: "background.brand-magenta",
comment:
"En svak bakgrunnsfarge som kun brukes til dekor. Dekker ingen kontrastkrav.",
},
"brand-magenta-softA": {
value: "{ax.brand-magenta.100A.value}",
value: "{ax.brand-magenta.100A}",
type: "color",
group: "background.brand-magenta",
comment:
"En svak bakgrunnsfarge som kun brukes til dekor. Dekker ingen kontrastkrav. Er delvis gjennomsiktig.",
},
"brand-magenta-moderate": {
value: "{ax.brand-magenta.200.value}",
value: "{ax.brand-magenta.200}",
type: "color",
group: "background.brand-magenta",
comment:
"En medium-svak bakgrunnsfarge som brukes til dekor. Om den brukes på meningsbærende elementer må den kombineres med en godkjent border.",
},
"brand-magenta-moderateA": {
value: "{ax.brand-magenta.200A.value}",
value: "{ax.brand-magenta.200A}",
type: "color",
group: "background.brand-magenta",
comment:
"En medium-svak bakgrunnsfarge som brukes til dekor. Om den brukes på meningsbærende elementer må den kombineres med en godkjent border. Er delvis gjennomsiktig.",
},
"brand-magenta-moderate-hover": {
value: "{ax.brand-magenta.300.value}",
value: "{ax.brand-magenta.300}",
type: "color",
group: "background.brand-magenta",
comment:
"En medium-svak bakgrunnsfarge som brukes til hover-state på meningsbærende elementer. Må kombineres med en godkjent border.",
},
"brand-magenta-moderate-hoverA": {
value: "{ax.brand-magenta.300A.value}",
value: "{ax.brand-magenta.300A}",
type: "color",
group: "background.brand-magenta",
comment:
"En medium-svak bakgrunnsfarge som brukes til hover-state på meningsbærende elementer. Må kombineres med en godkjent border. Er delvis gjennomsiktig.",
},
"brand-magenta-moderate-pressed": {
value: "{ax.brand-magenta.400.value}",
value: "{ax.brand-magenta.400}",
type: "color",
group: "background.brand-magenta",
comment:
"En medium-svak bakgrunnsfarge som brukes til active/selected-state på meningsbærende elementer. Må kombineres med en godkjent border.",
},
"brand-magenta-moderate-pressedA": {
value: "{ax.brand-magenta.400A.value}",
value: "{ax.brand-magenta.400A}",
type: "color",
group: "background.brand-magenta",
comment:
"En medium-svak bakgrunnsfarge som brukes til active/selected-state på meningsbærende elementer. Må kombineres med en godkjent border. Er delvis gjennomsiktig.",
},
"brand-magenta-strong": {
value: "{ax.brand-magenta.600.value}",
value: "{ax.brand-magenta.600}",
type: "color",
group: "background.brand-magenta",
comment:
"En sterk bakgrunnsfarge som brukes på meningsbærende elementer.",
},
"brand-magenta-strong-hover": {
value: "{ax.brand-magenta.700.value}",
value: "{ax.brand-magenta.700}",
type: "color",
group: "background.brand-magenta",
comment:
"En sterk bakgrunnsfarge som brukes til hover-state på meningsbærende elementer.",
},
"brand-magenta-strong-pressed": {
value: "{ax.brand-magenta.800.value}",
value: "{ax.brand-magenta.800}",
type: "color",
group: "background.brand-magenta",
comment:
@@ -82,28 +82,28 @@ export const brandMagentaSemanticTokenConfig = {
},
text: {
"brand-magenta": {
value: "{ax.brand-magenta.1000.value}",
value: "{ax.brand-magenta.1000}",
type: "color",
group: "text.brand-magenta",
comment:
"Sterk farge for tekst og ikoner for rollen brand magenta. Godkjent på alle bakgrunner unntatt strong.",
},
"brand-magenta-subtle": {
value: "{ax.brand-magenta.800.value}",
value: "{ax.brand-magenta.800}",
type: "color",
group: "text.brand-magenta",
comment:
"Standard farge for tekst og ikoner med rollen brand magenta. Godkjent på alle bakgrunner unntatt strong.",
},
"brand-magenta-decoration": {
value: "{ax.brand-magenta.600.value}",
value: "{ax.brand-magenta.600}",
type: "color",
group: "text.brand-magenta",
comment:
"En farge som kun brukes på ikke-tekstlig innhold (ikoner og andre grafiske elementer). **Ikke godkjent på tekst altså**.",
},
"brand-magenta-contrast": {
value: "{ax.neutral.000.value}",
value: "{ax.neutral.000}",
type: "color",
group: "text.brand-magenta",
comment:
@@ -112,27 +112,27 @@ export const brandMagentaSemanticTokenConfig = {
},
border: {
"brand-magenta": {
value: "{ax.brand-magenta.600.value}",
value: "{ax.brand-magenta.600}",
type: "color",
group: "border.brand-magenta",
comment: "Standard farge for border.",
},
"brand-magenta-subtle": {
value: "{ax.brand-magenta.400.value}",
value: "{ax.brand-magenta.400}",
type: "color",
group: "border.brand-magenta",
comment:
"En svak border-farge som brukes til dekor. Om den brukes på interaktive elementer må den kombineres med elementer som tydelig signaliserer interaksjon.",
},
"brand-magenta-subtleA": {
value: "{ax.brand-magenta.400A.value}",
value: "{ax.brand-magenta.400A}",
type: "color",
group: "border.brand-magenta",
comment:
"En svak border-farge som brukes til dekor. Om den brukes på interaktive elementer må den kombineres med elementer som tydelig signaliserer interaksjon. Er delvis gjennomsiktig.",
},
"brand-magenta-strong": {
value: "{ax.brand-magenta.700.value}",
value: "{ax.brand-magenta.700}",
type: "color",
group: "border.brand-magenta",
comment:
@@ -3,77 +3,77 @@ import type { StyleDictionaryTokenConfig } from "../../../tokens.util";
export const dangerSemanticTokenConfig = {
bg: {
"danger-soft": {
value: "{ax.danger.100.value}",
value: "{ax.danger.100}",
type: "color",
group: "background.danger",
comment:
"En svak bakgrunnsfarge som kun brukes til dekor. Dekker ingen kontrastkrav.",
},
"danger-softA": {
value: "{ax.danger.100A.value}",
value: "{ax.danger.100A}",
type: "color",
group: "background.danger",
comment:
"En svak bakgrunnsfarge som kun brukes til dekor. Dekker ingen kontrastkrav. Er delvis gjennomsiktig.",
},
"danger-moderate": {
value: "{ax.danger.200.value}",
value: "{ax.danger.200}",
type: "color",
group: "background.danger",
comment:
"En medium-svak bakgrunnsfarge som brukes til dekor. Om den brukes på meningsbærende elementer må den kombineres med en godkjent border (f.eks. alert error).",
},
"danger-moderateA": {
value: "{ax.danger.200A.value}",
value: "{ax.danger.200A}",
type: "color",
group: "background.danger",
comment:
"En medium-svak bakgrunnsfarge som brukes til dekor. Om den brukes på meningsbærende elementer må den kombineres med en godkjent border. Er delvis gjennomsiktig.",
},
"danger-moderate-hover": {
value: "{ax.danger.300.value}",
value: "{ax.danger.300}",
type: "color",
group: "background.danger",
comment:
"En medium-svak bakgrunnsfarge som brukes til hover-state på meningsbærende elementer. Må kombineres med en godkjent border.",
},
"danger-moderate-hoverA": {
value: "{ax.danger.300A.value}",
value: "{ax.danger.300A}",
type: "color",
group: "background.danger",
comment:
"En medium-svak bakgrunnsfarge som brukes til hover-state på meningsbærende elementer. Må kombineres med en godkjent border. Er delvis gjennomsiktig.",
},
"danger-moderate-pressed": {
value: "{ax.danger.400.value}",
value: "{ax.danger.400}",
type: "color",
group: "background.danger",
comment:
"En medium-svak bakgrunnsfarge som brukes til active/selected-state på meningsbærende elementer. Må kombineres med en godkjent border.",
},
"danger-moderate-pressedA": {
value: "{ax.danger.400A.value}",
value: "{ax.danger.400A}",
type: "color",
group: "background.danger",
comment:
"En medium-svak bakgrunnsfarge som brukes til active/selected-state på meningsbærende elementer. Må kombineres med en godkjent border. Er delvis gjennomsiktig.",
},
"danger-strong": {
value: "{ax.danger.600.value}",
value: "{ax.danger.600}",
type: "color",
group: "background.danger",
comment:
"En sterk bakgrunnsfarge som brukes på meningsbærende elementer (f.eks. button danger primary).",
},
"danger-strong-hover": {
value: "{ax.danger.700.value}",
value: "{ax.danger.700}",
type: "color",
group: "background.danger",
comment:
"En sterk bakgrunnsfarge som brukes til hover-state på meningsbærende elementer (f.eks. button danger primary hover).",
},
"danger-strong-pressed": {
value: "{ax.danger.800.value}",
value: "{ax.danger.800}",
type: "color",
group: "background.danger",
comment:
@@ -82,28 +82,28 @@ export const dangerSemanticTokenConfig = {
},
text: {
danger: {
value: "{ax.danger.1000.value}",
value: "{ax.danger.1000}",
type: "color",
group: "text.danger",
comment:
"Sterk farge for tekst og ikoner for rollen danger. Godkjent på alle bakgrunner unntatt strong.",
},
"danger-subtle": {
value: "{ax.danger.800.value}",
value: "{ax.danger.800}",
type: "color",
group: "text.danger",
comment:
"Standard farge for tekst og ikoner med rollen danger. Godkjent på alle bakgrunner unntatt strong.",
},
"danger-decoration": {
value: "{ax.danger.600.value}",
value: "{ax.danger.600}",
type: "color",
group: "text.danger",
comment:
"En farge som kun brukes på ikke-tekstlig innhold (ikoner og andre grafiske elementer). **Ikke godkjent på tekst altså**.",
},
"danger-contrast": {
value: "{ax.neutral.000.value}",
value: "{ax.neutral.000}",
type: "color",
group: "text.danger",
comment:
@@ -112,27 +112,27 @@ export const dangerSemanticTokenConfig = {
},
border: {
danger: {
value: "{ax.danger.600.value}",
value: "{ax.danger.600}",
type: "color",
group: "border.danger",
comment: "Standard farge for border.",
},
"danger-subtle": {
value: "{ax.danger.400.value}",
value: "{ax.danger.400}",
type: "color",
group: "border.danger",
comment:
"En svak border-farge som brukes til dekor. Om den brukes på interaktive elementer må den kombineres med elementer som tydelig signaliserer interaksjon.",
},
"danger-subtleA": {
value: "{ax.danger.400A.value}",
value: "{ax.danger.400A}",
type: "color",
group: "border.danger",
comment:
"En svak border-farge som brukes til dekor. Om den brukes på interaktive elementer må den kombineres med elementer som tydelig signaliserer interaksjon. Er delvis gjennomsiktig.",
},
"danger-strong": {
value: "{ax.danger.700.value}",
value: "{ax.danger.700}",
type: "color",
group: "border.danger",
comment:
@@ -3,77 +3,77 @@ import type { StyleDictionaryTokenConfig } from "../../../tokens.util";
export const infoSemanticTokenConfig = {
bg: {
"info-soft": {
value: "{ax.info.100.value}",
value: "{ax.info.100}",
type: "color",
group: "background.info",
comment:
"En svak bakgrunnsfarge som kun brukes til dekor. Dekker ingen kontrastkrav.",
},
"info-softA": {
value: "{ax.info.100A.value}",
value: "{ax.info.100A}",
type: "color",
group: "background.info",
comment:
"En svak bakgrunnsfarge som kun brukes til dekor. Dekker ingen kontrastkrav. Er delvis gjennomsiktig.",
},
"info-moderate": {
value: "{ax.info.200.value}",
value: "{ax.info.200}",
type: "color",
group: "background.info",
comment:
"En medium-svak bakgrunnsfarge som brukes til dekor. Om den brukes på meningsbærende elementer må den kombineres med en godkjent border (f.eks. alert info).",
},
"info-moderateA": {
value: "{ax.info.200A.value}",
value: "{ax.info.200A}",
type: "color",
group: "background.info",
comment:
"En medium-svak bakgrunnsfarge som brukes til dekor. Om den brukes på meningsbærende elementer må den kombineres med en godkjent border. Er delvis gjennomsiktig.",
},
"info-moderate-hover": {
value: "{ax.info.300.value}",
value: "{ax.info.300}",
type: "color",
group: "background.info",
comment:
"En medium-svak bakgrunnsfarge som brukes til hover-state på meningsbærende elementer. Må kombineres med en godkjent border.",
},
"info-moderate-hoverA": {
value: "{ax.info.300A.value}",
value: "{ax.info.300A}",
type: "color",
group: "background.info",
comment:
"En medium-svak bakgrunnsfarge som brukes til hover-state på meningsbærende elementer. Må kombineres med en godkjent border. Er delvis gjennomsiktig.",
},
"info-moderate-pressed": {
value: "{ax.info.400.value}",
value: "{ax.info.400}",
type: "color",
group: "background.info",
comment:
"En medium-svak bakgrunnsfarge som brukes til active/selected-state på meningsbærende elementer. Må kombineres med en godkjent border.",
},
"info-moderate-pressedA": {
value: "{ax.info.400A.value}",
value: "{ax.info.400A}",
type: "color",
group: "background.info",
comment:
"En medium-svak bakgrunnsfarge som brukes til active/selected-state på meningsbærende elementer. Må kombineres med en godkjent border. Er delvis gjennomsiktig.",
},
"info-strong": {
value: "{ax.info.600.value}",
value: "{ax.info.600}",
type: "color",
group: "background.info",
comment:
"En sterk bakgrunnsfarge som brukes på meningsbærende elementer.",
},
"info-strong-hover": {
value: "{ax.info.700.value}",
value: "{ax.info.700}",
type: "color",
group: "background.info",
comment:
"En sterk bakgrunnsfarge som brukes til hover-state på meningsbærende elementer.",
},
"info-strong-pressed": {
value: "{ax.info.800.value}",
value: "{ax.info.800}",
type: "color",
group: "background.info",
comment:
@@ -82,28 +82,28 @@ export const infoSemanticTokenConfig = {
},
text: {
info: {
value: "{ax.info.1000.value}",
value: "{ax.info.1000}",
type: "color",
group: "text.info",
comment:
"Sterk farge for tekst og ikoner for rollen info. Godkjent på alle bakgrunner unntatt strong.",
},
"info-subtle": {
value: "{ax.info.800.value}",
value: "{ax.info.800}",
type: "color",
group: "text.info",
comment:
"Standard farge for tekst og ikoner med rollen info. Godkjent på alle bakgrunner unntatt strong.",
},
"info-decoration": {
value: "{ax.info.600.value}",
value: "{ax.info.600}",
type: "color",
group: "text.info",
comment:
"En farge som kun brukes på ikke-tekstlig innhold (ikoner og andre grafiske elementer). **Ikke godkjent på tekst altså**.",
},
"info-contrast": {
value: "{ax.neutral.000.value}",
value: "{ax.neutral.000}",
type: "color",
group: "text.info",
comment:
@@ -112,27 +112,27 @@ export const infoSemanticTokenConfig = {
},
border: {
info: {
value: "{ax.info.600.value}",
value: "{ax.info.600}",
type: "color",
group: "border.info",
comment: "Standard farge for border.",
},
"info-subtle": {
value: "{ax.info.400.value}",
value: "{ax.info.400}",
type: "color",
group: "border.info",
comment:
"En svak border-farge som brukes til dekor. Om den brukes på interaktive elementer må den kombineres med elementer som tydelig signaliserer interaksjon.",
},
"info-subtleA": {
value: "{ax.info.400A.value}",
value: "{ax.info.400A}",
type: "color",
group: "border.info",
comment:
"En svak border-farge som brukes til dekor. Om den brukes på interaktive elementer må den kombineres med elementer som tydelig signaliserer interaksjon. Er delvis gjennomsiktig.",
},
"info-strong": {
value: "{ax.info.700.value}",
value: "{ax.info.700}",
type: "color",
group: "border.info",
comment:
@@ -3,77 +3,77 @@ import type { StyleDictionaryTokenConfig } from "../../../tokens.util";
export const metaLimeSemanticTokenConfig = {
bg: {
"meta-lime-soft": {
value: "{ax.meta-lime.100.value}",
value: "{ax.meta-lime.100}",
type: "color",
group: "background.meta-lime",
comment:
"En svak bakgrunnsfarge som kun brukes til dekor. Dekker ingen kontrastkrav.",
},
"meta-lime-softA": {
value: "{ax.meta-lime.100A.value}",
value: "{ax.meta-lime.100A}",
type: "color",
group: "background.meta-lime",
comment:
"En svak bakgrunnsfarge som kun brukes til dekor. Dekker ingen kontrastkrav. Er delvis gjennomsiktig.",
},
"meta-lime-moderate": {
value: "{ax.meta-lime.200.value}",
value: "{ax.meta-lime.200}",
type: "color",
group: "background.meta-lime",
comment:
"En medium-svak bakgrunnsfarge som brukes til dekor. Om den brukes på meningsbærende elementer må den kombineres med en godkjent border.",
},
"meta-lime-moderateA": {
value: "{ax.meta-lime.200A.value}",
value: "{ax.meta-lime.200A}",
type: "color",
group: "background.meta-lime",
comment:
"En medium-svak bakgrunnsfarge som brukes til dekor. Om den brukes på meningsbærende elementer må den kombineres med en godkjent border. Er delvis gjennomsiktig.",
},
"meta-lime-moderate-hover": {
value: "{ax.meta-lime.300.value}",
value: "{ax.meta-lime.300}",
type: "color",
group: "background.meta-lime",
comment:
"En medium-svak bakgrunnsfarge som brukes til hover-state på meningsbærende elementer. Må kombineres med en godkjent border.",
},
"meta-lime-moderate-hoverA": {
value: "{ax.meta-lime.300A.value}",
value: "{ax.meta-lime.300A}",
type: "color",
group: "background.meta-lime",
comment:
"En medium-svak bakgrunnsfarge som brukes til hover-state på meningsbærende elementer. Må kombineres med en godkjent border. Er delvis gjennomsiktig.",
},
"meta-lime-moderate-pressed": {
value: "{ax.meta-lime.400.value}",
value: "{ax.meta-lime.400}",
type: "color",
group: "background.meta-lime",
comment:
"En medium-svak bakgrunnsfarge som brukes til active/selected-state på meningsbærende elementer. Må kombineres med en godkjent border.",
},
"meta-lime-moderate-pressedA": {
value: "{ax.meta-lime.400A.value}",
value: "{ax.meta-lime.400A}",
type: "color",
group: "background.meta-lime",
comment:
"En medium-svak bakgrunnsfarge som brukes til active/selected-state på meningsbærende elementer. Må kombineres med en godkjent border. Er delvis gjennomsiktig.",
},
"meta-lime-strong": {
value: "{ax.meta-lime.600.value}",
value: "{ax.meta-lime.600}",
type: "color",
group: "background.meta-lime",
comment:
"En sterk bakgrunnsfarge som brukes på meningsbærende elementer.",
},
"meta-lime-strong-hover": {
value: "{ax.meta-lime.700.value}",
value: "{ax.meta-lime.700}",
type: "color",
group: "background.meta-lime",
comment:
"En sterk bakgrunnsfarge som brukes til hover-state på meningsbærende elementer.",
},
"meta-lime-strong-pressed": {
value: "{ax.meta-lime.800.value}",
value: "{ax.meta-lime.800}",
type: "color",
group: "background.meta-lime",
comment:
@@ -82,28 +82,28 @@ export const metaLimeSemanticTokenConfig = {
},
text: {
"meta-lime": {
value: "{ax.meta-lime.1000.value}",
value: "{ax.meta-lime.1000}",
type: "color",
group: "text.meta-lime",
comment:
"Sterk farge for tekst og ikoner for rollen meta lime. Godkjent på alle bakgrunner unntatt strong.",
},
"meta-lime-subtle": {
value: "{ax.meta-lime.800.value}",
value: "{ax.meta-lime.800}",
type: "color",
group: "text.meta-lime",
comment:
"Standard farge for tekst og ikoner med rollen meta lime. Godkjent på alle bakgrunner unntatt strong.",
},
"meta-lime-decoration": {
value: "{ax.meta-lime.600.value}",
value: "{ax.meta-lime.600}",
type: "color",
group: "text.meta-lime",
comment:
"En farge som kun brukes på ikke-tekstlig innhold (ikoner og andre grafiske elementer). **Ikke godkjent på tekst altså**.",
},
"meta-lime-contrast": {
value: "{ax.neutral.000.value}",
value: "{ax.neutral.000}",
type: "color",
group: "text.meta-lime",
comment:
@@ -112,27 +112,27 @@ export const metaLimeSemanticTokenConfig = {
},
border: {
"meta-lime": {
value: "{ax.meta-lime.600.value}",
value: "{ax.meta-lime.600}",
type: "color",
group: "border.meta-lime",
comment: "Standard farge for border.",
},
"meta-lime-subtle": {
value: "{ax.meta-lime.400.value}",
value: "{ax.meta-lime.400}",
type: "color",
group: "border.meta-lime",
comment:
"En svak border-farge som brukes til dekor. Om den brukes på interaktive elementer må den kombineres med elementer som tydelig signaliserer interaksjon.",
},
"meta-lime-subtleA": {
value: "{ax.meta-lime.400A.value}",
value: "{ax.meta-lime.400A}",
type: "color",
group: "border.meta-lime",
comment:
"En svak border-farge som brukes til dekor. Om den brukes på interaktive elementer må den kombineres med elementer som tydelig signaliserer interaksjon. Er delvis gjennomsiktig.",
},
"meta-lime-strong": {
value: "{ax.meta-lime.700.value}",
value: "{ax.meta-lime.700}",
type: "color",
group: "border.meta-lime",
comment:
@@ -3,77 +3,77 @@ import type { StyleDictionaryTokenConfig } from "../../../tokens.util";
export const metaPurpleSemanticTokenConfig = {
bg: {
"meta-purple-soft": {
value: "{ax.meta-purple.100.value}",
value: "{ax.meta-purple.100}",
type: "color",
group: "background.meta-purple",
comment:
"En svak bakgrunnsfarge som kun brukes til dekor. Dekker ingen kontrastkrav.",
},
"meta-purple-softA": {
value: "{ax.meta-purple.100A.value}",
value: "{ax.meta-purple.100A}",
type: "color",
group: "background.meta-purple",
comment:
"En svak bakgrunnsfarge som kun brukes til dekor. Dekker ingen kontrastkrav. Er delvis gjennomsiktig.",
},
"meta-purple-moderate": {
value: "{ax.meta-purple.200.value}",
value: "{ax.meta-purple.200}",
type: "color",
group: "background.meta-purple",
comment:
"En medium-svak bakgrunnsfarge som brukes til dekor. Om den brukes på meningsbærende elementer må den kombineres med en godkjent border.",
},
"meta-purple-moderateA": {
value: "{ax.meta-purple.200A.value}",
value: "{ax.meta-purple.200A}",
type: "color",
group: "background.meta-purple",
comment:
"En medium-svak bakgrunnsfarge som brukes til dekor. Om den brukes på meningsbærende elementer må den kombineres med en godkjent border. Er delvis gjennomsiktig.",
},
"meta-purple-moderate-hover": {
value: "{ax.meta-purple.300.value}",
value: "{ax.meta-purple.300}",
type: "color",
group: "background.meta-purple",
comment:
"En medium-svak bakgrunnsfarge som brukes til hover-state på meningsbærende elementer. Må kombineres med en godkjent border.",
},
"meta-purple-moderate-hoverA": {
value: "{ax.meta-purple.300A.value}",
value: "{ax.meta-purple.300A}",
type: "color",
group: "background.meta-purple",
comment:
"En medium-svak bakgrunnsfarge som brukes til hover-state på meningsbærende elementer. Må kombineres med en godkjent border. Er delvis gjennomsiktig.",
},
"meta-purple-moderate-pressed": {
value: "{ax.meta-purple.400.value}",
value: "{ax.meta-purple.400}",
type: "color",
group: "background.meta-purple",
comment:
"En medium-svak bakgrunnsfarge som brukes til active/selected-state på meningsbærende elementer. Må kombineres med en godkjent border.",
},
"meta-purple-moderate-pressedA": {
value: "{ax.meta-purple.400A.value}",
value: "{ax.meta-purple.400A}",
type: "color",
group: "background.meta-purple",
comment:
"En medium-svak bakgrunnsfarge som brukes til active/selected-state på meningsbærende elementer. Må kombineres med en godkjent border. Er delvis gjennomsiktig.",
},
"meta-purple-strong": {
value: "{ax.meta-purple.600.value}",
value: "{ax.meta-purple.600}",
type: "color",
group: "background.meta-purple",
comment:
"En sterk bakgrunnsfarge som brukes på meningsbærende elementer.",
},
"meta-purple-strong-hover": {
value: "{ax.meta-purple.700.value}",
value: "{ax.meta-purple.700}",
type: "color",
group: "background.meta-purple",
comment:
"En sterk bakgrunnsfarge som brukes til hover-state på meningsbærende elementer.",
},
"meta-purple-strong-pressed": {
value: "{ax.meta-purple.800.value}",
value: "{ax.meta-purple.800}",
type: "color",
group: "background.meta-purple",
comment:
@@ -82,28 +82,28 @@ export const metaPurpleSemanticTokenConfig = {
},
text: {
"meta-purple": {
value: "{ax.meta-purple.1000.value}",
value: "{ax.meta-purple.1000}",
type: "color",
group: "text.meta-purple",
comment:
"Sterk farge for tekst og ikoner for rollen meta purple. Godkjent på alle bakgrunner unntatt strong.",
},
"meta-purple-subtle": {
value: "{ax.meta-purple.800.value}",
value: "{ax.meta-purple.800}",
type: "color",
group: "text.meta-purple",
comment:
"Standard farge for tekst og ikoner med rollen meta purple. Godkjent på alle bakgrunner unntatt strong.",
},
"meta-purple-decoration": {
value: "{ax.meta-purple.600.value}",
value: "{ax.meta-purple.600}",
type: "color",
group: "text.meta-purple",
comment:
"En farge som kun brukes på ikke-tekstlig innhold (ikoner og andre grafiske elementer). **Ikke godkjent på tekst altså**.",
},
"meta-purple-contrast": {
value: "{ax.neutral.000.value}",
value: "{ax.neutral.000}",
type: "color",
group: "text.meta-purple",
comment:
@@ -112,27 +112,27 @@ export const metaPurpleSemanticTokenConfig = {
},
border: {
"meta-purple": {
value: "{ax.meta-purple.600.value}",
value: "{ax.meta-purple.600}",
type: "color",
group: "border.meta-purple",
comment: "Standard farge for border.",
},
"meta-purple-subtle": {
value: "{ax.meta-purple.400.value}",
value: "{ax.meta-purple.400}",
type: "color",
group: "border.meta-purple",
comment:
"En svak border-farge som brukes til dekor. Om den brukes på interaktive elementer må den kombineres med elementer som tydelig signaliserer interaksjon.",
},
"meta-purple-subtleA": {
value: "{ax.meta-purple.400A.value}",
value: "{ax.meta-purple.400A}",
type: "color",
group: "border.meta-purple",
comment:
"En svak border-farge som brukes til dekor. Om den brukes på interaktive elementer må den kombineres med elementer som tydelig signaliserer interaksjon. Er delvis gjennomsiktig.",
},
"meta-purple-strong": {
value: "{ax.meta-purple.700.value}",
value: "{ax.meta-purple.700}",
type: "color",
group: "border.meta-purple",
comment:
@@ -3,77 +3,77 @@ import type { StyleDictionaryTokenConfig } from "../../../tokens.util";
export const neutralSemanticTokenConfig = {
bg: {
"neutral-soft": {
value: "{ax.neutral.100.value}",
value: "{ax.neutral.100}",
type: "color",
group: "background.neutral",
comment:
"En svak bakgrunnsfarge som kun brukes til dekor. Dekker ingen kontrastkrav.",
},
"neutral-softA": {
value: "{ax.neutral.100A.value}",
value: "{ax.neutral.100A}",
type: "color",
group: "background.neutral",
comment:
"En svak bakgrunnsfarge som kun brukes til dekor. Dekker ingen kontrastkrav. Er delvis gjennomsiktig.",
},
"neutral-moderate": {
value: "{ax.neutral.200.value}",
value: "{ax.neutral.200}",
type: "color",
group: "background.neutral",
comment:
"En medium-svak bakgrunnsfarge som brukes til dekor. Om den brukes på meningsbærende elementer må den kombineres med en godkjent border.",
},
"neutral-moderateA": {
value: "{ax.neutral.200A.value}",
value: "{ax.neutral.200A}",
type: "color",
group: "background.neutral",
comment:
"En medium-svak bakgrunnsfarge som brukes til dekor. Om den brukes på meningsbærende elementer må den kombineres med en godkjent border. Er delvis gjennomsiktig.",
},
"neutral-moderate-hover": {
value: "{ax.neutral.300.value}",
value: "{ax.neutral.300}",
type: "color",
group: "background.neutral",
comment:
"En medium-svak bakgrunnsfarge som brukes til hover-state på meningsbærende elementer. Må kombineres med en godkjent border.",
},
"neutral-moderate-hoverA": {
value: "{ax.neutral.300A.value}",
value: "{ax.neutral.300A}",
type: "color",
group: "background.neutral",
comment:
"En medium-svak bakgrunnsfarge som brukes til hover-state på meningsbærende elementer (f.eks. button neutral secondary hover). Må kombineres med en godkjent border. Er delvis gjennomsiktig.",
},
"neutral-moderate-pressed": {
value: "{ax.neutral.400.value}",
value: "{ax.neutral.400}",
type: "color",
group: "background.neutral",
comment:
"En medium-svak bakgrunnsfarge som brukes til active/selected-state på meningsbærende elementer. Må kombineres med en godkjent border.",
},
"neutral-moderate-pressedA": {
value: "{ax.neutral.400A.value}",
value: "{ax.neutral.400A}",
type: "color",
group: "background.neutral",
comment:
"En medium-svak bakgrunnsfarge som brukes til active/selected-state på meningsbærende elementer (f.eks. button neutral secondary active). Må kombineres med en godkjent border. Er delvis gjennomsiktig.",
},
"neutral-strong": {
value: "{ax.neutral.700.value}",
value: "{ax.neutral.700}",
type: "color",
group: "background.neutral",
comment:
"En sterk bakgrunnsfarge som brukes på meningsbærende elementer (f.eks. button neutral primary).",
},
"neutral-strong-hover": {
value: "{ax.neutral.800.value}",
value: "{ax.neutral.800}",
type: "color",
group: "background.neutral",
comment:
"En sterk bakgrunnsfarge som brukes til hover-state på meningsbærende elementer (f.eks. button neutral primary hover).",
},
"neutral-strong-pressed": {
value: "{ax.neutral.900.value}",
value: "{ax.neutral.900}",
type: "color",
group: "background.neutral",
comment:
@@ -82,28 +82,28 @@ export const neutralSemanticTokenConfig = {
},
text: {
neutral: {
value: "{ax.neutral.1000.value}",
value: "{ax.neutral.1000}",
type: "color",
group: "text.neutral",
comment:
"Standard farge for tekst og ikoner med rollen neutral. Godkjent på alle bakgrunner unntatt strong.",
},
"neutral-subtle": {
value: "{ax.neutral.900.value}",
value: "{ax.neutral.900}",
type: "color",
group: "text.neutral",
comment:
"En nedtonet farge for tekst og ikoner med rollen neutral. Godkjent på alle bakgrunner unntatt strong.",
},
"neutral-decoration": {
value: "{ax.neutral.600.value}",
value: "{ax.neutral.600}",
type: "color",
group: "text.neutral",
comment:
"En farge som kun brukes på ikke-tekstlig innhold (ikoner og andre grafiske elementer). **Ikke godkjent på tekst altså**.",
},
"neutral-contrast": {
value: "{ax.neutral.000.value}",
value: "{ax.neutral.000}",
type: "color",
group: "text.neutral",
comment:
@@ -112,27 +112,27 @@ export const neutralSemanticTokenConfig = {
},
border: {
neutral: {
value: "{ax.neutral.600.value}",
value: "{ax.neutral.600}",
type: "color",
group: "border.neutral",
comment: "Standard farge for border.",
},
"neutral-subtle": {
value: "{ax.neutral.400.value}",
value: "{ax.neutral.400}",
type: "color",
group: "border.neutral",
comment:
"En svak border-farge som brukes til dekor. Om den brukes på interaktive elementer må den kombineres med elementer som tydelig signaliserer interaksjon.",
},
"neutral-subtleA": {
value: "{ax.neutral.400A.value}",
value: "{ax.neutral.400A}",
type: "color",
group: "border.neutral",
comment:
"En svak border-farge som brukes til dekor. Om den brukes på interaktive elementer må den kombineres med elementer som tydelig signaliserer interaksjon. Er delvis gjennomsiktig.",
},
"neutral-strong": {
value: "{ax.neutral.700.value}",
value: "{ax.neutral.700}",
type: "color",
group: "border.neutral",
comment:
@@ -3,77 +3,77 @@ import type { StyleDictionaryTokenConfig } from "../../../tokens.util";
export const successSemanticTokenConfig = {
bg: {
"success-soft": {
value: "{ax.success.100.value}",
value: "{ax.success.100}",
type: "color",
group: "background.success",
comment:
"En svak bakgrunnsfarge som kun brukes til dekor. Dekker ingen kontrastkrav.",
},
"success-softA": {
value: "{ax.success.100A.value}",
value: "{ax.success.100A}",
type: "color",
group: "background.success",
comment:
"En svak bakgrunnsfarge som kun brukes til dekor. Dekker ingen kontrastkrav. Er delvis gjennomsiktig.",
},
"success-moderate": {
value: "{ax.success.200.value}",
value: "{ax.success.200}",
type: "color",
group: "background.success",
comment:
"En medium-svak bakgrunnsfarge som brukes til dekor. Om den brukes på meningsbærende elementer må den kombineres med en godkjent border (f.eks. alert success).",
},
"success-moderateA": {
value: "{ax.success.200A.value}",
value: "{ax.success.200A}",
type: "color",
group: "background.success",
comment:
"En medium-svak bakgrunnsfarge som brukes til dekor. Om den brukes på meningsbærende elementer må den kombineres med en godkjent border. Er delvis gjennomsiktig.",
},
"success-moderate-hover": {
value: "{ax.success.300.value}",
value: "{ax.success.300}",
type: "color",
group: "background.success",
comment:
"En medium-svak bakgrunnsfarge som brukes til hover-state på meningsbærende elementer. Må kombineres med en godkjent border.",
},
"success-moderate-hoverA": {
value: "{ax.success.300A.value}",
value: "{ax.success.300A}",
type: "color",
group: "background.success",
comment:
"En medium-svak bakgrunnsfarge som brukes til hover-state på meningsbærende elementer. Må kombineres med en godkjent border. Er delvis gjennomsiktig.",
},
"success-moderate-pressed": {
value: "{ax.success.400.value}",
value: "{ax.success.400}",
type: "color",
group: "background.success",
comment:
"En medium-svak bakgrunnsfarge som brukes til active/selected-state på meningsbærende elementer. Må kombineres med en godkjent border.",
},
"success-moderate-pressedA": {
value: "{ax.success.400A.value}",
value: "{ax.success.400A}",
type: "color",
group: "background.success",
comment:
"En medium-svak bakgrunnsfarge som brukes til active/selected-state på meningsbærende elementer. Må kombineres med en godkjent border. Er delvis gjennomsiktig.",
},
"success-strong": {
value: "{ax.success.600.value}",
value: "{ax.success.600}",
type: "color",
group: "background.success",
comment:
"En sterk bakgrunnsfarge som brukes på meningsbærende elementer.",
},
"success-strong-hover": {
value: "{ax.success.700.value}",
value: "{ax.success.700}",
type: "color",
group: "background.success",
comment:
"En sterk bakgrunnsfarge som brukes til hover-state på meningsbærende elementer.",
},
"success-strong-pressed": {
value: "{ax.success.800.value}",
value: "{ax.success.800}",
type: "color",
group: "background.success",
comment:
@@ -82,28 +82,28 @@ export const successSemanticTokenConfig = {
},
text: {
success: {
value: "{ax.success.1000.value}",
value: "{ax.success.1000}",
type: "color",
group: "text.success",
comment:
"Sterk farge for tekst og ikoner for rollen success. Godkjent på alle bakgrunner unntatt strong.",
},
"success-subtle": {
value: "{ax.success.800.value}",
value: "{ax.success.800}",
type: "color",
group: "text.success",
comment:
"Standard farge for tekst og ikoner med rollen success. Godkjent på alle bakgrunner unntatt strong.",
},
"success-decoration": {
value: "{ax.success.600.value}",
value: "{ax.success.600}",
type: "color",
group: "text.success",
comment:
"En farge som kun brukes på ikke-tekstlig innhold (ikoner og andre grafiske elementer). **Ikke godkjent på tekst altså**.",
},
"success-contrast": {
value: "{ax.neutral.000.value}",
value: "{ax.neutral.000}",
type: "color",
group: "text.success",
comment:
@@ -112,27 +112,27 @@ export const successSemanticTokenConfig = {
},
border: {
success: {
value: "{ax.success.600.value}",
value: "{ax.success.600}",
type: "color",
group: "border.success",
comment: "Standard farge for border.",
},
"success-subtle": {
value: "{ax.success.400.value}",
value: "{ax.success.400}",
type: "color",
group: "border.success",
comment:
"En svak border-farge som brukes til dekor. Om den brukes på interaktive elementer må den kombineres med elementer som tydelig signaliserer interaksjon.",
},
"success-subtleA": {
value: "{ax.success.400A.value}",
value: "{ax.success.400A}",
type: "color",
group: "border.success",
comment:
"En svak border-farge som brukes til dekor. Om den brukes på interaktive elementer må den kombineres med elementer som tydelig signaliserer interaksjon. Er delvis gjennomsiktig.",
},
"success-strong": {
value: "{ax.success.700.value}",
value: "{ax.success.700}",
type: "color",
group: "border.success",
comment:
@@ -3,77 +3,77 @@ import type { StyleDictionaryTokenConfig } from "../../../tokens.util";
export const warningSemanticTokenConfig = {
bg: {
"warning-soft": {
value: "{ax.warning.100.value}",
value: "{ax.warning.100}",
type: "color",
group: "background.warning",
comment:
"En svak bakgrunnsfarge som kun brukes til dekor. Dekker ingen kontrastkrav.",
},
"warning-softA": {
value: "{ax.warning.100A.value}",
value: "{ax.warning.100A}",
type: "color",
group: "background.warning",
comment:
"En svak bakgrunnsfarge som kun brukes til dekor. Dekker ingen kontrastkrav. Er delvis gjennomsiktig.",
},
"warning-moderate": {
value: "{ax.warning.200.value}",
value: "{ax.warning.200}",
type: "color",
group: "background.warning",
comment:
"En medium-svak bakgrunnsfarge som brukes til dekor. Om den brukes på meningsbærende elementer må den kombineres med en godkjent border (f.eks. alert warning).",
},
"warning-moderateA": {
value: "{ax.warning.200A.value}",
value: "{ax.warning.200A}",
type: "color",
group: "background.warning",
comment:
"En medium-svak bakgrunnsfarge som brukes til dekor. Om den brukes på meningsbærende elementer må den kombineres med en godkjent border. Er delvis gjennomsiktig.",
},
"warning-moderate-hover": {
value: "{ax.warning.300.value}",
value: "{ax.warning.300}",
type: "color",
group: "background.warning",
comment:
"En medium-svak bakgrunnsfarge som brukes til hover-state på meningsbærende elementer. Må kombineres med en godkjent border.",
},
"warning-moderate-hoverA": {
value: "{ax.warning.300A.value}",
value: "{ax.warning.300A}",
type: "color",
group: "background.warning",
comment:
"En medium-svak bakgrunnsfarge som brukes til hover-state på meningsbærende elementer. Må kombineres med en godkjent border. Er delvis gjennomsiktig.",
},
"warning-moderate-pressed": {
value: "{ax.warning.400.value}",
value: "{ax.warning.400}",
type: "color",
group: "background.warning",
comment:
"En medium-svak bakgrunnsfarge som brukes til active/selected-state på meningsbærende elementer. Må kombineres med en godkjent border.",
},
"warning-moderate-pressedA": {
value: "{ax.warning.400A.value}",
value: "{ax.warning.400A}",
type: "color",
group: "background.warning",
comment:
"En medium-svak bakgrunnsfarge som brukes til active/selected-state på meningsbærende elementer. Må kombineres med en godkjent border. Er delvis gjennomsiktig.",
},
"warning-strong": {
value: "{ax.warning.600.value}",
value: "{ax.warning.600}",
type: "color",
group: "background.warning",
comment:
"En sterk bakgrunnsfarge som brukes på meningsbærende elementer.",
},
"warning-strong-hover": {
value: "{ax.warning.700.value}",
value: "{ax.warning.700}",
type: "color",
group: "background.warning",
comment:
"En sterk bakgrunnsfarge som brukes til hover-state på meningsbærende elementer.",
},
"warning-strong-pressed": {
value: "{ax.warning.800.value}",
value: "{ax.warning.800}",
type: "color",
group: "background.warning",
comment:
@@ -82,28 +82,28 @@ export const warningSemanticTokenConfig = {
},
text: {
warning: {
value: "{ax.warning.1000.value}",
value: "{ax.warning.1000}",
type: "color",
group: "text.warning",
comment:
"Sterk farge for tekst og ikoner for rollen warning. Godkjent på alle bakgrunner unntatt strong.",
},
"warning-subtle": {
value: "{ax.warning.800.value}",
value: "{ax.warning.800}",
type: "color",
group: "text.warning",
comment:
"Standard farge for tekst og ikoner med rollen warning. Godkjent på alle bakgrunner unntatt strong.",
},
"warning-decoration": {
value: "{ax.warning.600.value}",
value: "{ax.warning.600}",
type: "color",
group: "text.warning",
comment:
"En farge som kun brukes på ikke-tekstlig innhold (ikoner og andre grafiske elementer). **Ikke godkjent på tekst altså**.",
},
"warning-contrast": {
value: "{ax.neutral.000.value}",
value: "{ax.neutral.000}",
type: "color",
group: "text.warning",
comment:
@@ -112,27 +112,27 @@ export const warningSemanticTokenConfig = {
},
border: {
warning: {
value: "{ax.warning.600.value}",
value: "{ax.warning.600}",
type: "color",
group: "border.warning",
comment: "Standard farge for border.",
},
"warning-subtle": {
value: "{ax.warning.400.value}",
value: "{ax.warning.400}",
type: "color",
group: "border.warning",
comment:
"En svak border-farge som brukes til dekor. Om den brukes på interaktive elementer må den kombineres med elementer som tydelig signaliserer interaksjon.",
},
"warning-subtleA": {
value: "{ax.warning.400A.value}",
value: "{ax.warning.400A}",
type: "color",
group: "border.warning",
comment:
"En svak border-farge som brukes til dekor. Om den brukes på interaktive elementer må den kombineres med elementer som tydelig signaliserer interaksjon. Er delvis gjennomsiktig.",
},
"warning-strong": {
value: "{ax.warning.700.value}",
value: "{ax.warning.700}",
type: "color",
group: "border.warning",
comment:
@@ -14,7 +14,7 @@ export function semanticRootTokens(theme: AkselColorTheme) {
return {
text: {
logo: {
value: theme === "light" ? "#C30000" : "{ax.neutral.1000.value}",
value: theme === "light" ? "#C30000" : "{ax.neutral.1000}",
type: "color",
group: "text",
comment: "Navs logo-farge",
@@ -43,17 +43,14 @@ export function semanticRootTokens(theme: AkselColorTheme) {
"Bakgrunnsfargen som brukes på input-elementer (tekstfelt, sjekkbokser, o.l.) ",
},
raised: {
value:
theme === "light"
? "{ax.neutral.000.value}"
: "{ax.neutral.200.value}",
value: theme === "light" ? "{ax.neutral.000}" : "{ax.neutral.200}",
type: "color",
group: "background",
comment:
"Bakgrunnsfarge på bokser som svever eller som skal fremheves i darkmode. F.eks. Card.",
},
sunken: {
value: theme === "light" ? "{ax.neutral.200.value}" : "#07090D",
value: theme === "light" ? "{ax.neutral.200}" : "#07090D",
type: "color",
group: "background",
comment:
@@ -69,7 +66,7 @@ export function semanticRootTokens(theme: AkselColorTheme) {
},
border: {
focus: {
value: "{ax.neutral.1000.value}",
value: "{ax.neutral.1000}",
type: "color",
group: "border",
/**
@@ -17,88 +17,91 @@ import type { AkselColorRole } from "../../../types";
* --ax-bg-moderate: var(--ax-bg-success-moderate);
* }
* ```
*
*
* Source for all our color generation: https://www.figma.com/design/NGULm4ljBe9o5xKbl5pJqD/Nye-farger?node-id=785-1106&p=f&t=sJq3Y4e1qfUNKkcI-0
*/
export function semanticThemedBaseTokens(role: AkselColorRole) {
return {
bg: {
soft: {
value: `{ax.bg.${role}-soft.value}`,
value: `{ax.bg.${role}-soft}`,
type: "themed-role",
},
softA: {
value: `{ax.bg.${role}-softA.value}`,
value: `{ax.bg.${role}-softA}`,
type: "themed-role",
},
moderate: {
value: `{ax.bg.${role}-moderate.value}`,
value: `{ax.bg.${role}-moderate}`,
type: "themed-role",
},
moderateA: {
value: `{ax.bg.${role}-moderateA.value}`,
value: `{ax.bg.${role}-moderateA}`,
type: "themed-role",
},
"moderate-hover": {
value: `{ax.bg.${role}-moderate-hover.value}`,
value: `{ax.bg.${role}-moderate-hover}`,
type: "themed-role",
},
"moderate-hoverA": {
value: `{ax.bg.${role}-moderate-hoverA.value}`,
value: `{ax.bg.${role}-moderate-hoverA}`,
type: "themed-role",
},
"moderate-pressed": {
value: `{ax.bg.${role}-moderate-pressed.value}`,
value: `{ax.bg.${role}-moderate-pressed}`,
type: "themed-role",
},
"moderate-pressedA": {
value: `{ax.bg.${role}-moderate-pressedA.value}`,
value: `{ax.bg.${role}-moderate-pressedA}`,
type: "themed-role",
},
strong: {
value: `{ax.bg.${role}-strong.value}`,
value: `{ax.bg.${role}-strong}`,
type: "themed-role",
},
"strong-hover": {
value: `{ax.bg.${role}-strong-hover.value}`,
value: `{ax.bg.${role}-strong-hover}`,
type: "themed-role",
},
"strong-pressed": {
value: `{ax.bg.${role}-strong-pressed.value}`,
value: `{ax.bg.${role}-strong-pressed}`,
type: "themed-role",
},
},
text: {
default: {
value: `{ax.text.${role}.value}`,
value: `{ax.text.${role}}`,
type: "themed-role",
},
subtle: {
value: `{ax.text.${role}-subtle.value}`,
value: `{ax.text.${role}-subtle}`,
type: "themed-role",
},
decoration: {
value: `{ax.text.${role}-decoration.value}`,
value: `{ax.text.${role}-decoration}`,
type: "themed-role",
},
contrast: {
value: `{ax.text.${role}-contrast.value}`,
value: `{ax.text.${role}-contrast}`,
type: "themed-role",
},
},
border: {
default: {
value: `{ax.border.${role}.value}`,
value: `{ax.border.${role}}`,
type: "themed-role",
},
subtle: {
value: `{ax.border.${role}-subtle.value}`,
value: `{ax.border.${role}-subtle}`,
type: "themed-role",
},
subtleA: {
value: `{ax.border.${role}-subtleA.value}`,
value: `{ax.border.${role}-subtleA}`,
type: "themed-role",
},
strong: {
value: `{ax.border.${role}-strong.value}`,
value: `{ax.border.${role}-strong}`,
type: "themed-role",
},
},
+50
View File
@@ -1,5 +1,55 @@
# Changelog
## 8.19.1
### @navikt/ds-css
- Accordion: `@media print` now auto-opens every Accordion ([#5363](https://github.com/navikt/aksel/pull/5363))
- ExpansionCard: `@media print` now auto-opens every ExpansionCard ([#5363](https://github.com/navikt/aksel/pull/5363))
- ReadMore: `@media print` now auto-opens every ReadMore ([#5363](https://github.com/navikt/aksel/pull/5363))
## 8.19.0
### @navikt/aksel
- CLI: Update clipboardy to v5.3.2 ([#5357](https://github.com/navikt/aksel/pull/5357))
- CLI: Update figlet to 1.11.3 ([#5360](https://github.com/navikt/aksel/pull/5360))
### @navikt/aksel-icons
- Icons: Added new icon MenuHamburgerMagnifyingGlass ([#5368](https://github.com/navikt/aksel/pull/5368))
## 8.18.0
### @navikt/aksel
- Codemod: Migrations now run in folders that are not git repositories, and a missing git install gets its own error message. `codemod v8` no longer stops before `v8-tokens` because of changes made by earlier migrations in the same run. ([`470a820`](https://github.com/navikt/aksel/commit/470a8207f23aee71243b986ef2836967c2498e71))
- Codemod: `v8-tokens` no longer removes whitespace before replaced tokens or rewrites longer names that contain a token name. ([#5322](https://github.com/navikt/aksel/pull/5322))
### @navikt/ds-css
- ActionMenu: Faster and smoother open transition. ([#5296](https://github.com/navikt/aksel/pull/5296))
### @navikt/ds-react
- Radio: Add RadioCard and RadioCardGroup components ([#5276](https://github.com/navikt/aksel/pull/5276))
* Badge, Lookup: now available outside of Preview scope ([#5344](https://github.com/navikt/aksel/pull/5344))
* DatePicker: Use without hook now correctly closes picker on date-selection ([#5327](https://github.com/navikt/aksel/pull/5327))
* ActionMenu: Menu is no longer modal, Page scroll and interaction outside the menu are no longer blocked while open. ([#5296](https://github.com/navikt/aksel/pull/5296))
### @navikt/ds-tailwind
- v3 preset adds `rounded-ax-*` and no longer lists radius tokens as colors ([#5329](https://github.com/navikt/aksel/pull/5329))
- v4 theme now uses `@theme inline`, so dark mode and `data-color` work with Tailwind utilities. Font sizes are available as `text-ax-*` and font family as `font-ax-font-family` (`font-family-ax-font-family` is deprecated) ([#5329](https://github.com/navikt/aksel/pull/5329))
## 8.17.2
### @navikt/aksel-icons
+1 -1
View File
@@ -37,7 +37,7 @@ På Windows kan du legge inn tokenet som en lokal miljøvariabel.
Nettsiden avhenger av pakker som ikke er åpent tilgjengelig. Du kan likevel bidra på andre deler av repoet ved å kjøre denne kommandoen i stedet for `yarn install`:
```sh
yarn workspaces focus @navikt/aksel-icons @navikt/ds-tokens @navikt/ds-css @navikt/ds-react @navikt/ds-tailwind @navikt/aksel @navikt/aksel-stylelint
yarn workspaces focus aksel @navikt/aksel-icons @navikt/ds-tokens @navikt/ds-css @navikt/ds-react @navikt/ds-tailwind @navikt/aksel @navikt/aksel-stylelint
```
## Utviklingsmiljø
-4
View File
@@ -13,10 +13,6 @@ spec:
ingresses:
- "https://aksel.nav.no"
- "https://design.nav.no"
observability:
autoInstrumentation:
enabled: true
runtime: nodejs
replicas:
disableAutoScaling: true
min: 1
Loaded 100 of 206 files, more files were not shown because too many files have changed in this diff. Show more