mirror of
https://github.com/navikt/aksel.git
synced 2026-10-17 19:22:02 +00:00
Compare commits
94
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5866992e7d | ||
|
|
8ab241a980 | ||
|
|
363d76b9b2 | ||
|
|
5da970fd62 | ||
|
|
296e43cab2 | ||
|
|
85fec974dc | ||
|
|
6ddea4eaf2 | ||
|
|
acea9ebb78 | ||
|
|
fdbd634012 | ||
|
|
dcf5452d28 | ||
|
|
0a8c33bbbe | ||
|
|
18b0cf5fa5 | ||
|
|
374a679bc3 | ||
|
|
a2e020d4f3 | ||
|
|
074c8da536 | ||
|
|
bdbd6c24cc | ||
|
|
f0e6c944d8 | ||
|
|
22be148832 | ||
|
|
aac12fe44f | ||
|
|
324358d086 | ||
|
|
946cbeb016 | ||
|
|
f4d22dae68 | ||
|
|
ce2f5a9b90 | ||
|
|
cf167064eb | ||
|
|
73a6ae1bf1 | ||
|
|
95c84e1d01 | ||
|
|
4675857e8f | ||
|
|
2a6e416b5c | ||
|
|
d3c3cafc97 | ||
|
|
678fa6692b | ||
|
|
87aeb94c64 | ||
|
|
4714a5ff32 | ||
|
|
ca1d9be88f | ||
|
|
2654da8b64 | ||
|
|
8d6418a594 | ||
|
|
8269091fb8 | ||
|
|
fd82184057 | ||
|
|
39f2cb8614 | ||
|
|
4bf298f363 | ||
|
|
3a239e4e17 | ||
|
|
4fcfaf3deb | ||
|
|
5dbc66daaa | ||
|
|
555a46b5da | ||
|
|
73b2bca156 | ||
|
|
01411dc11f | ||
|
|
b1b0d23863 | ||
|
|
cf59ce81bf | ||
|
|
00c0ff31e1 | ||
|
|
d278060e59 | ||
|
|
e5d5b67262 | ||
|
|
246f25cbaa | ||
|
|
b2a22438ad | ||
|
|
8b8b724e40 | ||
|
|
9f9bdcc645 | ||
|
|
82bdf8075a | ||
|
|
410e543825 | ||
|
|
40308e0e77 | ||
|
|
fff309b573 | ||
|
|
fe7c2de8fa | ||
|
|
6fedea0ba4 | ||
|
|
9811327509 | ||
|
|
cf2034f914 | ||
|
|
9e5e7f8fdc | ||
|
|
e9dc171d74 | ||
|
|
544b80eeae | ||
|
|
4358b53095 | ||
|
|
de57099b09 | ||
|
|
5a97cb422d | ||
|
|
67fb3bb3a1 | ||
|
|
7bdd5cb748 | ||
|
|
62bcab1ab0 | ||
|
|
61e60d9556 | ||
|
|
9c1eba4450 | ||
|
|
63986d9872 | ||
|
|
761c8ef64f | ||
|
|
4b86b555d8 | ||
|
|
8e1e294fda | ||
|
|
f71594e477 | ||
|
|
d2a89851cd | ||
|
|
d27da71304 | ||
|
|
fcf17fc750 | ||
|
|
e1b091358b | ||
|
|
988ad39ad1 | ||
|
|
524c87e3bd | ||
|
|
8cf6123656 | ||
|
|
866e6a168c | ||
|
|
bc2be15c55 | ||
|
|
4e6fc073fe | ||
|
|
e895c691ce | ||
|
|
186c0fec07 | ||
|
|
746a58b663 | ||
|
|
d767aadde3 | ||
|
|
4359428b40 | ||
|
|
d583276428 |
No files matched your search
+26
-13
@@ -1,4 +1,11 @@
|
||||
// TODO: Convert to new format: https://eslint.org/docs/latest/use/configure/migration-guide
|
||||
/**
|
||||
* TODO:
|
||||
* - Convert to new format (v9): https://eslint.org/docs/latest/use/configure/migration-guide
|
||||
* - Look into opinionated stylistic plugin
|
||||
* - Replace @typescript-eslint/recommended with @typescript-eslint/recommended-type-checked
|
||||
* - We should add "plugin:react/jsx-runtime", but not sure if this will cause problems for projects not using the new JSX transform
|
||||
* - Consider adding the rule "id-length"
|
||||
*/
|
||||
module.exports = {
|
||||
env: {
|
||||
browser: true,
|
||||
@@ -8,15 +15,13 @@ module.exports = {
|
||||
extends: [
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended",
|
||||
//"plugin:react/jsx-runtime", // Not sure if this will cause problems for projects not using the new JSX transform
|
||||
"plugin:react-hooks/recommended",
|
||||
"plugin:jsx-a11y/recommended",
|
||||
"plugin:import/recommended",
|
||||
"plugin:import/typescript",
|
||||
"plugin:storybook/recommended",
|
||||
// TODO: Look into opinionated stylistic plugin
|
||||
// TODO: Replace @typescript-eslint/recommended with @typescript-eslint/recommended-type-checked
|
||||
],
|
||||
/* Allows eslint-plugin-react to detect installed react-version */
|
||||
settings: { react: { version: "detect" } },
|
||||
rules: {
|
||||
"import/no-unresolved": "off",
|
||||
@@ -26,12 +31,6 @@ module.exports = {
|
||||
],
|
||||
"array-callback-return": "error",
|
||||
"object-shorthand": "error",
|
||||
// TODO: Consider adding the rule "id-length"
|
||||
// Temporary:
|
||||
"react/prop-types": "off",
|
||||
"react/display-name": "off",
|
||||
"import/no-named-as-default": "off",
|
||||
// "import/no-cycle": [1],
|
||||
"no-else-return": "error",
|
||||
"no-console": [
|
||||
"warn",
|
||||
@@ -53,7 +52,12 @@ module.exports = {
|
||||
],
|
||||
},
|
||||
],
|
||||
/* Temporarily turned off until code is updated */
|
||||
"react/prop-types": "off",
|
||||
"react/display-name": "off",
|
||||
"import/no-named-as-default": "off",
|
||||
},
|
||||
/* https://eslint.org/docs/latest/use/configure/rules#report-unused-eslint-disable-comments */
|
||||
reportUnusedDisableDirectives: true,
|
||||
overrides: [
|
||||
{
|
||||
@@ -66,15 +70,23 @@ module.exports = {
|
||||
files: ["**/*.ts?(x)"],
|
||||
extends: ["plugin:@typescript-eslint/recommended"],
|
||||
rules: {
|
||||
"@typescript-eslint/no-shadow": ["error", { hoist: "all" }], // TODO: Consider { builtinGlobals: true }
|
||||
/*
|
||||
* TODO:
|
||||
* - Consider { builtinGlobals: true }
|
||||
*/
|
||||
"@typescript-eslint/no-shadow": ["error", { hoist: "all" }],
|
||||
"@typescript-eslint/no-explicit-any": "off", // Temporary
|
||||
"@typescript-eslint/array-type": "error",
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["**/*.test.*", "**/__tests__/*"],
|
||||
plugins: ["vitest"],
|
||||
extends: ["plugin:vitest/recommended", "plugin:testing-library/react"],
|
||||
plugins: ["@vitest"],
|
||||
extends: [
|
||||
"plugin:testing-library/react",
|
||||
/* Needed to support v8 eslint config-syntax */
|
||||
"plugin:@vitest/legacy-recommended",
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
@@ -153,5 +165,6 @@ module.exports = {
|
||||
"**/codemod/**/*.js",
|
||||
"!.storybook",
|
||||
"**/playwright-report/**",
|
||||
"**/tokens/**/plugin.js",
|
||||
],
|
||||
};
|
||||
@@ -34,6 +34,9 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: yarn
|
||||
|
||||
- name: Create empty .env file (Can be removed when env-file-if-exists is implemented in node v22)
|
||||
run: echo "" > ./aksel.nav.no/website/.env
|
||||
|
||||
- name: Build application
|
||||
env:
|
||||
SANITY_READ: ${{ secrets.SANITY_READ }}
|
||||
|
||||
@@ -32,6 +32,9 @@ jobs:
|
||||
yarn boot
|
||||
yarn docgen
|
||||
|
||||
- name: Create empty .env file (Can be removed when env-file-if-exists is implemented in node v22)
|
||||
run: echo "" > ./aksel.nav.no/website/.env
|
||||
|
||||
- name: Update sanity sync
|
||||
env:
|
||||
SANITY_WRITE: ${{ secrets.SANITY_WRITE }}
|
||||
@@ -68,6 +71,9 @@ jobs:
|
||||
- name: Tests
|
||||
run: yarn workspace website test
|
||||
|
||||
- name: Create empty .env file (Can be removed when env-file-if-exists is implemented in node v22)
|
||||
run: echo "" > ./aksel.nav.no/website/.env
|
||||
|
||||
- name: Build application
|
||||
env:
|
||||
SANITY_READ: ${{ secrets.SANITY_READ }}
|
||||
|
||||
@@ -23,6 +23,9 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: cd aksel.nav.no/website && yarn
|
||||
|
||||
- name: Create empty .env file (Can be removed when env-file-if-exists is implemented in node v22)
|
||||
run: echo "" > ./aksel.nav.no/website/.env
|
||||
|
||||
- name: Export dataset
|
||||
env:
|
||||
SANITY_READ: ${{ secrets.SANITY_READ }}
|
||||
|
||||
@@ -31,6 +31,9 @@ jobs:
|
||||
run: |
|
||||
yarn boot
|
||||
|
||||
- name: Create empty .env file (Can be removed when env-file-if-exists is implemented in node v22)
|
||||
run: echo "" > ./aksel.nav.no/website/.env
|
||||
|
||||
- name: Build application
|
||||
env:
|
||||
SANITY_READ: ${{ secrets.SANITY_READ }}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
name: "Update remote token config for figma variables plugin"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
paths:
|
||||
- "@navikt/core/tokens/**"
|
||||
|
||||
jobs:
|
||||
upload:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: "read"
|
||||
id-token: "write"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: yarn
|
||||
- name: set-env for yarn install to @navikt scope
|
||||
shell: bash
|
||||
run: echo "NPM_AUTH_TOKEN=${{ secrets.READER_TOKEN }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn
|
||||
|
||||
- name: Build tokens
|
||||
run: yarn workspace @navikt/ds-tokens build
|
||||
|
||||
- id: upload
|
||||
uses: nais/deploy/actions/cdn-upload/v2@master
|
||||
with:
|
||||
team: designsystem
|
||||
source: "./@navikt/core/tokens/figma-config.json"
|
||||
destination: "./@navikt/tokens"
|
||||
project_id: ${{ vars.NAIS_MANAGEMENT_PROJECT_ID }}
|
||||
identity_provider: ${{ secrets.NAIS_WORKLOAD_IDENTITY_PROVIDER }}
|
||||
source_keep_parent_name: false
|
||||
no_cache_paths: "@navikt/tokens/figma-config.json"
|
||||
|
||||
- run: echo uploaded file ${{ steps.upload.outputs.uploaded }}
|
||||
shell: bash
|
||||
@@ -0,0 +1,37 @@
|
||||
name: Deploy referansesider SPA
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: "read"
|
||||
id-token: "write"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: yarn
|
||||
|
||||
- run: yarn install
|
||||
env:
|
||||
NPM_AUTH_TOKEN: ${{ secrets.READER_TOKEN }}
|
||||
- run: yarn boot
|
||||
- run: cd ./examples/referansesider && yarn && yarn build
|
||||
- uses: nais/deploy/actions/spa-deploy/v2@master
|
||||
with:
|
||||
app: aksel-referansesider
|
||||
team: designsystem
|
||||
source: ./examples/referansesider/dist
|
||||
ingress: https://aksel-referansesider.ansatt.dev.nav.no
|
||||
environment: dev
|
||||
project_id: ${{ vars.NAIS_MANAGEMENT_PROJECT_ID }}
|
||||
identity_provider: ${{ secrets.NAIS_WORKLOAD_IDENTITY_PROVIDER }}
|
||||
@@ -5,24 +5,11 @@ jobs:
|
||||
execute:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
packages: "write"
|
||||
contents: "read"
|
||||
id-token: "write"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- uses: denoland/setup-deno@v1
|
||||
with:
|
||||
deno-version: v1.x
|
||||
- name: Restore cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: "**/node_modules"
|
||||
key: ${{ runner.os }}-build-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-
|
||||
|
||||
- uses: nais/docker-build-push@v0
|
||||
id: docker-push
|
||||
with:
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# @navikt/aksel-icons
|
||||
|
||||
## 7.2.1
|
||||
|
||||
## 7.2.0
|
||||
|
||||
## 7.1.2
|
||||
|
||||
## 7.1.1
|
||||
|
||||
## 7.1.0
|
||||
|
||||
## 7.0.1
|
||||
|
||||
## 7.0.0
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable import/no-named-as-default-member */
|
||||
import fastglob from "fast-glob";
|
||||
import fs from "fs";
|
||||
import jsYaml from "js-yaml";
|
||||
|
||||
@@ -21,8 +21,7 @@ describe(`Each icons has a valid code`, () => {
|
||||
|
||||
test(`has valid attributes on root-node`, () => {
|
||||
const root = select(":root", iconAst);
|
||||
const properties =
|
||||
root && root.properties ? Object.keys(root.properties).sort() : [];
|
||||
const properties = Object.keys(root?.properties ?? []).sort();
|
||||
|
||||
expect(properties).toStrictEqual(
|
||||
["viewBox", "xmlns", "height", "width", "fill"].sort(),
|
||||
@@ -31,45 +30,41 @@ describe(`Each icons has a valid code`, () => {
|
||||
|
||||
test(`has valid xml-attr`, () => {
|
||||
const root = select(":root", iconAst);
|
||||
const xmlns = root && root.properties ? root.properties.xmlns : null;
|
||||
expect(xmlns).toBe("http://www.w3.org/2000/svg");
|
||||
expect(root?.properties?.xmlns).toBe("http://www.w3.org/2000/svg");
|
||||
});
|
||||
|
||||
test(`has valid viewbox`, () => {
|
||||
const root = select(":root", iconAst);
|
||||
const viewbox =
|
||||
root && root.properties ? root.properties.viewBox : null;
|
||||
expect(viewbox).toBe("0 0 24 24");
|
||||
expect(root?.properties?.viewBox).toBe("0 0 24 24");
|
||||
});
|
||||
|
||||
test(`root fill is none`, () => {
|
||||
const root = select(":root", iconAst);
|
||||
const fill = root && root.properties ? root.properties.fill : null;
|
||||
expect(fill).toBe("none");
|
||||
expect(root?.properties?.fill).toBe("none");
|
||||
});
|
||||
|
||||
test(`has valid width and height`, () => {
|
||||
const root = select(":root", iconAst);
|
||||
const width = root && root.properties ? root.properties.width : null;
|
||||
const height = root && root.properties ? root.properties.height : null;
|
||||
expect(width).toBe("24");
|
||||
expect(height).toBe("24");
|
||||
expect(root?.properties?.width).toBe("24");
|
||||
expect(root?.properties?.height).toBe("24");
|
||||
});
|
||||
|
||||
test(`has valid stroke`, () => {
|
||||
const nodes = selectAll("*", iconAst);
|
||||
|
||||
nodes.forEach((n) => {
|
||||
n.properties?.stroke &&
|
||||
if (n.properties?.stroke) {
|
||||
expect(n.properties.stroke).toEqual("#23262A");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test(`has valid stroke-width`, () => {
|
||||
const nodes = selectAll("*", iconAst);
|
||||
nodes.forEach((n) => {
|
||||
n.properties?.strokeWidth &&
|
||||
if (n.properties?.strokeWidth) {
|
||||
expect(n.properties?.strokeWidth).toEqual("1.5");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const file_key = "wEdyFjCQSBR3U7FvrMbPXa";
|
||||
|
||||
export const fetchIcons = async () => {
|
||||
console.info("Fetching new icons from Figma...");
|
||||
const data = await fetch(
|
||||
`https://api.figma.com/v1/files/${file_key}/components`,
|
||||
{
|
||||
@@ -15,6 +16,10 @@ export const fetchIcons = async () => {
|
||||
throw e.message;
|
||||
});
|
||||
|
||||
if (data?.err) {
|
||||
throw new Error(data.err);
|
||||
}
|
||||
|
||||
if (!data?.meta?.components || data?.meta?.components.length === 0) {
|
||||
throw new Error("Fant ingen publiserte ikoner");
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ export const resolveName = (icon) => {
|
||||
|
||||
if (isVariant) {
|
||||
const variant = icon.name.replace("Variant=", "").replace("Stroke", "");
|
||||
const actualName = icon["containing_frame"]?.["containingStateGroup"]?.name;
|
||||
const actualName = icon.containing_frame?.containingStateGroup?.name;
|
||||
const name = `${actualName}${variant}`;
|
||||
return `${startCase(camelCase(name)).replace(/ /g, "")}.svg`;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { config } from "dotenv";
|
||||
import { existsSync, mkdirSync, readdirSync, rmSync, writeFileSync } from "fs";
|
||||
import { resolve } from "path";
|
||||
import { fetchDownloadUrls, fetchIcon, fetchIcons } from "./fetch-icons.mjs";
|
||||
@@ -6,7 +5,6 @@ import { resolveName } from "./icon-name.mjs";
|
||||
import { makeConfig } from "./make-configs.mjs";
|
||||
import { paginate } from "./paginate.mjs";
|
||||
|
||||
config();
|
||||
/* https://www.figma.com/file/wEdyFjCQSBR3U7FvrMbPXa/Core-Icons-Next?node-id=277%3A1221&t=mUJzFvnsceYYXNL5-0 */
|
||||
|
||||
const iconFolder = "./icons";
|
||||
@@ -16,6 +14,7 @@ if (!process.env.FIGMA_TOKEN) {
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.info("Started icon-update from Figma");
|
||||
let icons = await fetchIcons();
|
||||
|
||||
const totalIcons = icons.length;
|
||||
|
||||
@@ -15,6 +15,7 @@ ymlList.forEach((file) => {
|
||||
const ymlData = jsYaml.load(fs.readFileSync(`${basePath}/${file}`), {
|
||||
schema: jsYaml.JSON_SCHEMA,
|
||||
});
|
||||
// biome-ignore lint/complexity/useOptionalChain: optional chain not supported in enviroment. Consider revriting to Typescript
|
||||
if (ymlData.keywords && ymlData.keywords.includes("[ignore-docs]")) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"react-dom": "^18.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@figma/plugin-typings": "^1.84.0",
|
||||
"@figma/plugin-typings": "^1.100.2",
|
||||
"@svgr/core": "^8.1.0",
|
||||
"@types/node": "^18.15.11",
|
||||
"@types/postcss-url": "^10.0.4",
|
||||
@@ -28,7 +28,7 @@
|
||||
"postcss": "^8.4.31",
|
||||
"postcss-url": "^10.1.3",
|
||||
"typescript": "^5.1.6",
|
||||
"vite": "^5.1.7",
|
||||
"vite": "^5.4.6",
|
||||
"vite-plugin-singlefile": "^1.0.0",
|
||||
"vite-plugin-svgr": "^4.2.0"
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ const App = () => {
|
||||
icon.removeEventListener("dragend", dragEvent);
|
||||
}
|
||||
};
|
||||
}, [categories]);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<main tabIndex={-1} className="wrapper">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/aksel-icons",
|
||||
"version": "7.0.1",
|
||||
"version": "7.2.1",
|
||||
"author": "Aksel | NAV",
|
||||
"homepage": "https://aksel.nav.no/ikoner",
|
||||
"repository": {
|
||||
@@ -62,7 +62,7 @@
|
||||
"update-metadata": "node config/metadata.js",
|
||||
"write-packagejson": "echo '{\"type\": \"module\"}' > dist/react/esm/package.json",
|
||||
"build": "yarn copy && yarn create-icons && concurrently \"tsc\" \"tsc -p tsconfig.esm.json && tsc-alias -p tsconfig.esm.json && yarn write-packagejson\" && node config/cleanTypes.js && yarn update-metadata && yarn parse-svg",
|
||||
"fetch-new:icons": "node config/figma/index.mjs",
|
||||
"fetch-new:icons": "tsx --env-file=.env config/figma/index.mjs",
|
||||
"zip:icons": "node config/zip.js",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest watch"
|
||||
@@ -73,7 +73,6 @@
|
||||
"concurrently": "7.2.1",
|
||||
"copyfiles": "^2.4.1",
|
||||
"cross-zip-cli": "1.0.0",
|
||||
"dotenv": "^10.0.0",
|
||||
"fast-glob": "3.2.11",
|
||||
"hast-util-select": "5.0.5",
|
||||
"js-yaml": "^4.1.0",
|
||||
@@ -83,6 +82,7 @@
|
||||
"react": "^18.0.0",
|
||||
"rehype-parse": "8.0.4",
|
||||
"tsc-alias": "1.8.8",
|
||||
"tsx": "^4.7.1",
|
||||
"typescript": "^5.1.6",
|
||||
"unified": "10.1.2",
|
||||
"vitest": "^1.2.2"
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
# @navikt/aksel-stylelint
|
||||
|
||||
## 7.2.1
|
||||
|
||||
## 7.2.0
|
||||
|
||||
## 7.1.2
|
||||
|
||||
## 7.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- List: :boom: Removed class names "navds-list--nested" and "navds-list\_\_item-content" ([#3184](https://github.com/navikt/aksel/pull/3184))
|
||||
|
||||
## 7.1.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Pagination: :boom: Added some classNames to list of deprecated (removed) classes. ([#3164](https://github.com/navikt/aksel/pull/3164))
|
||||
|
||||
## 7.0.1
|
||||
|
||||
## 7.0.0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/aksel-stylelint",
|
||||
"version": "7.0.1",
|
||||
"version": "7.2.1",
|
||||
"author": "Aksel | NAV",
|
||||
"homepage": "https://aksel.nav.no/grunnleggende/kode/stylelint",
|
||||
"repository": {
|
||||
@@ -35,8 +35,8 @@
|
||||
"dev": "yarn watch:lint"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@navikt/ds-css": "^7.0.1",
|
||||
"@navikt/ds-tokens": "^7.0.1",
|
||||
"@navikt/ds-css": "^7.2.1",
|
||||
"@navikt/ds-tokens": "^7.2.1",
|
||||
"concurrently": "7.2.1",
|
||||
"postcss-selector-parser": "^6.0.13",
|
||||
"postcss-value-parser": "^4.2.0",
|
||||
|
||||
@@ -60,4 +60,16 @@ export const deprecations: DeprecatedList = [
|
||||
classes: ["navds-textarea__wrapper"],
|
||||
message: "Removed in v6.0.0",
|
||||
},
|
||||
{
|
||||
classes: [
|
||||
"navds-pagination__prev-next-icon",
|
||||
"navds-pagination__next-text",
|
||||
"navds-pagination__prev-text",
|
||||
],
|
||||
message: "These classes had no effect and were removed in v7.1.0",
|
||||
},
|
||||
{
|
||||
classes: ["navds-list--nested", "navds-list__item-content"],
|
||||
message: "Removed in v7.1.1",
|
||||
},
|
||||
];
|
||||
@@ -1,5 +1,40 @@
|
||||
# @navikt/aksel
|
||||
|
||||
## 7.2.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`6ddea4eaf`](https://github.com/navikt/aksel/commit/6ddea4eaf2e37f20f93d75e1a3a327b038fb209b)]:
|
||||
- @navikt/ds-css@7.2.1
|
||||
|
||||
## 7.2.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/ds-css@7.2.0
|
||||
|
||||
## 7.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`95c84e1d0`](https://github.com/navikt/aksel/commit/95c84e1d014d880bc22eb0053a875771e8399540)]:
|
||||
- @navikt/ds-css@7.1.2
|
||||
|
||||
## 7.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`ca1d9be88`](https://github.com/navikt/aksel/commit/ca1d9be88f45f936ee8546af146f8783d8c43db2), [`73b2bca15`](https://github.com/navikt/aksel/commit/73b2bca156072c945c2fc4e4e849f50a00398eb6), [`d278060e5`](https://github.com/navikt/aksel/commit/d278060e59b17638cbda22dc194e89b01dc2113e)]:
|
||||
- @navikt/ds-css@7.1.1
|
||||
|
||||
## 7.1.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`9c1eba445`](https://github.com/navikt/aksel/commit/9c1eba44508f295cc6261ff96822b18557bd7ddc)]:
|
||||
- @navikt/ds-css@7.1.0
|
||||
|
||||
## 7.0.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/aksel",
|
||||
"version": "7.0.1",
|
||||
"version": "7.2.1",
|
||||
"description": "Aksel command line interface. Handles css-imports, codemods and more",
|
||||
"author": "Aksel | NAV designsystem team",
|
||||
"license": "MIT",
|
||||
@@ -29,7 +29,7 @@
|
||||
},
|
||||
"homepage": "https://aksel.nav.no/grunnleggende/kode/kommandolinje",
|
||||
"dependencies": {
|
||||
"@navikt/ds-css": "7.0.1",
|
||||
"@navikt/ds-css": "7.2.1",
|
||||
"axios": "1.7.4",
|
||||
"chalk": "4.1.0",
|
||||
"clipboardy": "^2.3.0",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
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";
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ export function validateGit(options: any, program: Command) {
|
||||
try {
|
||||
clean = isGitClean.sync(process.cwd());
|
||||
} catch (err: any) {
|
||||
if (err && err.stderr && err.stderr.indexOf("Not a git repository") >= 0) {
|
||||
if (err?.stderr && err.stderr.indexOf("Not a git repository") >= 0) {
|
||||
clean = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ export async function getAllVersions() {
|
||||
const npmPackageData = await axios.get(
|
||||
`https://registry.npmjs.org/@navikt/ds-css`,
|
||||
);
|
||||
return Object.keys(npmPackageData.data["versions"]);
|
||||
return Object.keys(npmPackageData.data.versions);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return [];
|
||||
|
||||
@@ -137,7 +137,7 @@ export async function cssImportsCommand() {
|
||||
|
||||
let foundComponents: string[] = [];
|
||||
|
||||
if (answers["autoscan"] === "yes") {
|
||||
if (answers.autoscan === "yes") {
|
||||
foundComponents = await new Promise((resolve) => {
|
||||
exec(`node ${__dirname}/scan-code.js ${answers.scandir}`, (_, stdout) => {
|
||||
resolve(
|
||||
|
||||
@@ -1,5 +1,35 @@
|
||||
# @navikt/ds-css
|
||||
|
||||
## 7.2.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Combobox: Support PageUp/PageDown in dropdown list. ([#3158](https://github.com/navikt/aksel/pull/3158))
|
||||
|
||||
## 7.2.0
|
||||
|
||||
## 7.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- List: Explicitly set list-style on ol to override reset-CSS ([#3202](https://github.com/navikt/aksel/pull/3202))
|
||||
|
||||
## 7.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- List: Add indentation and remove overly verbose aria attributes ([#3184](https://github.com/navikt/aksel/pull/3184))
|
||||
|
||||
- Datepicker/Select: :lipstick: dim non-interactable icons when readonly is set. ([#3187](https://github.com/navikt/aksel/pull/3187))
|
||||
|
||||
- Combobox: :bug: implement missing readonly and disabled ([#3180](https://github.com/navikt/aksel/pull/3180))
|
||||
|
||||
## 7.1.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Accordion: Remove 'relative' positioning when focused. This resolves some issues where floating elements like popover ended up clipping. ([`9c1eba445`](https://github.com/navikt/aksel/commit/9c1eba44508f295cc6261ff96822b18557bd7ddc))
|
||||
|
||||
## 7.0.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
--__ac-accordion-header-shadow-color: var(--ac-accordion-header-border, var(--a-border-divider));
|
||||
}
|
||||
|
||||
.navds-accordion__item:focus-within {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/*************************
|
||||
* Header *
|
||||
*************************/
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
@media (forced-colors: active) {
|
||||
.navds-copybutton {
|
||||
background-color: ButtonFace;
|
||||
border-color: ButtonText;
|
||||
border: solid 1px ButtonText;
|
||||
color: ButtonText;
|
||||
}
|
||||
|
||||
@@ -281,6 +281,7 @@
|
||||
/* Readonly */
|
||||
.navds-date__field--readonly .navds-date__field-button {
|
||||
cursor: default;
|
||||
color: var(--a-gray-500);
|
||||
}
|
||||
|
||||
.navds-date__caption-button {
|
||||
|
||||
@@ -20,6 +20,36 @@
|
||||
--__ac-combobox-input-height: 1.5rem;
|
||||
}
|
||||
|
||||
.navds-combobox--disabled {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.navds-combobox--disabled .navds-combobox__wrapper *:hover {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.navds-combobox--disabled .navds-text-field__input {
|
||||
border: 1px solid var(--a-border-default);
|
||||
}
|
||||
|
||||
.navds-combobox--disabled .navds-combobox__selected-options {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.navds-combobox--readonly {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.navds-combobox--readonly .navds-combobox__button-toggle-list {
|
||||
color: var(--a-gray-500);
|
||||
}
|
||||
|
||||
.navds-combobox--readonly .navds-text-field__input,
|
||||
.navds-combobox--readonly .navds-combobox__input {
|
||||
background-color: var(--a-surface-subtle);
|
||||
border-color: var(--a-border-subtle);
|
||||
}
|
||||
|
||||
.navds-combobox__button-clear svg,
|
||||
.navds-combobox__button-toggle-list svg,
|
||||
.navds-combobox__list svg {
|
||||
@@ -233,6 +263,7 @@
|
||||
border-radius: var(--a-border-radius-medium);
|
||||
background-color: var(--ac-combobox-list-bg, var(--a-surface-default));
|
||||
color: var(--ac-combobox-list-text, var(--a-text-default));
|
||||
overscroll-behavior: contain;
|
||||
}
|
||||
|
||||
.navds-combobox__list--closed {
|
||||
|
||||
@@ -109,3 +109,7 @@
|
||||
border-color: var(--a-border-subtle);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.navds-select--readonly .navds-select__chevron {
|
||||
color: var(--a-gray-500);
|
||||
}
|
||||
@@ -10,7 +10,6 @@
|
||||
@media (forced-colors: active) {
|
||||
.navds-internalheader {
|
||||
background-color: ButtonFace;
|
||||
border-color: ButtonText;
|
||||
border: solid 1px ButtonText;
|
||||
color: ButtonText;
|
||||
}
|
||||
|
||||
+60
-71
@@ -4,92 +4,81 @@
|
||||
margin-block: var(--a-spacing-4);
|
||||
}
|
||||
|
||||
.navds-list--nested ul,
|
||||
.navds-list--nested ol {
|
||||
margin-block: var(--a-spacing-2);
|
||||
}
|
||||
|
||||
.navds-list--small ul,
|
||||
.navds-list--small ol {
|
||||
margin-block: var(--a-spacing-3);
|
||||
}
|
||||
|
||||
.navds-list li {
|
||||
margin-block-end: var(--a-spacing-2);
|
||||
}
|
||||
|
||||
.navds-list:last-child > ul > li:last-child,
|
||||
.navds-list:last-child > ol > li:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.navds-list ul .navds-list__item {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: var(--a-spacing-3);
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.navds-list ul .navds-list__item::marker {
|
||||
content: none;
|
||||
}
|
||||
|
||||
.navds-list ol .navds-list__item-marker--icon {
|
||||
grid-column: 1 / 2;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.navds-list .navds-list__item-marker--bullet {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: var(--a-font-line-height-xlarge);
|
||||
grid-column: 1 / 2;
|
||||
color: var(--ac-list-marker-ul-color, var(--ac-list-marker-color, var(--a-icon-default)));
|
||||
}
|
||||
|
||||
.navds-list--small .navds-list__item-marker--bullet {
|
||||
height: var(--a-font-line-height-large);
|
||||
}
|
||||
|
||||
.navds-list__item-marker--icon {
|
||||
color: var(--ac-list-marker-icon-color, var(--ac-list-marker-color, var(--a-icon-default)));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: var(--a-font-line-height-xlarge);
|
||||
padding-block-end: 1px;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.navds-list--small .navds-list__item-marker--icon {
|
||||
height: var(--a-font-line-height-large);
|
||||
}
|
||||
|
||||
.navds-list__item-marker--icon svg {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.navds-list ul .navds-list__item-content {
|
||||
grid-column: 2 / 3;
|
||||
.navds-list .navds-list :where(ul, ol) {
|
||||
margin-block: var(--a-spacing-2) 0;
|
||||
}
|
||||
|
||||
.navds-list ol {
|
||||
padding-left: var(--a-spacing-2);
|
||||
list-style: decimal;
|
||||
list-style: decimal; /* This is the default value, but some frameworks have `ol,ul { list-style:none }` */
|
||||
padding-left: 1.7rem;
|
||||
}
|
||||
|
||||
.navds-list ol li {
|
||||
padding-left: var(--a-spacing-1);
|
||||
margin-left: var(--a-spacing-2);
|
||||
.navds-list ol > .navds-list__item {
|
||||
padding-left: 0.3rem;
|
||||
}
|
||||
|
||||
/* SAFARI HACK START */
|
||||
/* stylelint-disable selector-type-no-unknown */
|
||||
|
||||
_::-webkit-full-page-media,
|
||||
_:future,
|
||||
:root .navds-list ol {
|
||||
padding-left: 2.1rem;
|
||||
}
|
||||
|
||||
_::-webkit-full-page-media,
|
||||
_:future,
|
||||
:root .navds-list ol .navds-list__item {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
/* SAFARI HACK END */
|
||||
/* stylelint-enable selector-type-no-unknown */
|
||||
|
||||
.navds-list__item {
|
||||
margin-block-end: var(--a-spacing-2);
|
||||
}
|
||||
|
||||
.navds-list__item:last-child {
|
||||
margin-block-end: 0;
|
||||
}
|
||||
|
||||
.navds-list ul > .navds-list__item {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: var(--a-spacing-2);
|
||||
}
|
||||
|
||||
.navds-list ol li::marker {
|
||||
font-weight: var(--a-font-weight-bold);
|
||||
font-size: var(--a-font-size-large);
|
||||
color: var(--ac-list-marker-ol-color, var(--ac-list-marker-color, var(--a-icon-default)));
|
||||
line-height: var(--a-font-line-height-xlarge);
|
||||
}
|
||||
|
||||
.navds-list--small ol li::marker {
|
||||
font-size: var(--a-font-size-small);
|
||||
line-height: var(--a-font-line-height-large);
|
||||
.navds-list__item-marker {
|
||||
width: 1.5rem;
|
||||
}
|
||||
|
||||
.navds-list ul > li > .navds-list__item-marker {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: var(--a-font-line-height-xlarge);
|
||||
}
|
||||
|
||||
.navds-list--small ul > li > .navds-list__item-marker {
|
||||
height: var(--a-font-line-height-large);
|
||||
}
|
||||
|
||||
.navds-list__item-marker--bullet {
|
||||
padding-left: 0.8rem;
|
||||
color: var(--ac-list-marker-ul-color, var(--ac-list-marker-color, var(--a-icon-default)));
|
||||
}
|
||||
|
||||
.navds-list__item-marker--icon {
|
||||
font-size: 1.5rem;
|
||||
color: var(--ac-list-marker-icon-color, var(--ac-list-marker-color, var(--a-icon-default)));
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-css",
|
||||
"version": "7.0.1",
|
||||
"version": "7.2.1",
|
||||
"description": "CSS for NAV Designsystem",
|
||||
"author": "Aksel | NAV designsystem team",
|
||||
"keywords": [
|
||||
@@ -27,7 +27,8 @@
|
||||
"css:get-version": "node config/get-version.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@navikt/ds-tokens": "^7.0.1",
|
||||
"@navikt/ds-tokens": "^7.2.1",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"cssnano": "6.0.0",
|
||||
"fast-glob": "3.2.11",
|
||||
"lodash": "4.17.21",
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
@media (forced-colors: active) {
|
||||
.navds-read-more__button {
|
||||
background-color: ButtonFace;
|
||||
border-color: ButtonText;
|
||||
border: solid 1px ButtonText;
|
||||
color: ButtonText;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,69 @@
|
||||
# @navikt/ds-react
|
||||
|
||||
## 7.2.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Combobox: Disable autocomple in Firefox on Android to prevent bug ([#3201](https://github.com/navikt/aksel/pull/3201))
|
||||
|
||||
- Combobox: Support PageUp/PageDown in dropdown list. ([#3158](https://github.com/navikt/aksel/pull/3158))
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/ds-tokens@7.2.1
|
||||
- @navikt/aksel-icons@7.2.1
|
||||
|
||||
## 7.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- FileUpload: :boom: Remove UNSAFE-prefix ([#3205](https://github.com/navikt/aksel/pull/3205))
|
||||
|
||||
- List: :sparkles: New size 'large' ([#3207](https://github.com/navikt/aksel/pull/3207))
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Performance: :zap: Optimized memoization for rerendring in some components. ([#3197](https://github.com/navikt/aksel/pull/3197))
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/ds-tokens@7.2.0
|
||||
- @navikt/aksel-icons@7.2.0
|
||||
|
||||
## 7.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/ds-tokens@7.1.2
|
||||
- @navikt/aksel-icons@7.1.2
|
||||
|
||||
## 7.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- List: Add indentation and remove overly verbose aria attributes ([#3184](https://github.com/navikt/aksel/pull/3184))
|
||||
|
||||
- Combobox: :bug: implement missing readonly and disabled ([#3180](https://github.com/navikt/aksel/pull/3180))
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/ds-tokens@7.1.1
|
||||
- @navikt/aksel-icons@7.1.1
|
||||
|
||||
## 7.1.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Pagination: :lipstick: Updated font-weight to 600 and marked 'page'-prop on 'Item' as deprecated. ([#3164](https://github.com/navikt/aksel/pull/3164))
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Combobox: :label: Better typing for maxSelected prop ([#3169](https://github.com/navikt/aksel/pull/3169))
|
||||
|
||||
- ErrorSummary: Remove aria-attributes. ([#3171](https://github.com/navikt/aksel/pull/3171))
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/ds-tokens@7.1.0
|
||||
- @navikt/aksel-icons@7.1.0
|
||||
|
||||
## 7.0.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-react",
|
||||
"version": "7.0.1",
|
||||
"version": "7.2.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",
|
||||
@@ -583,7 +583,7 @@
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"scripts": {
|
||||
"docgen": "yarn ts-node ../../../scripts/docgen.ts",
|
||||
"docgen": "yarn tsx ../../../scripts/docgen.ts",
|
||||
"write-packagejson": "echo '{\"type\": \"module\"}' > esm/package.json",
|
||||
"clean": "rimraf cjs esm",
|
||||
"build": "concurrently \"tsc -p tsconfig.build.json\" \"tsc -p tsconfig.esm.json && tsc-alias -p tsconfig.esm.json && yarn write-packagejson\" ",
|
||||
@@ -594,8 +594,8 @@
|
||||
"dependencies": {
|
||||
"@floating-ui/react": "0.25.4",
|
||||
"@floating-ui/react-dom": "^2.0.9",
|
||||
"@navikt/aksel-icons": "^7.0.1",
|
||||
"@navikt/ds-tokens": "^7.0.1",
|
||||
"@navikt/aksel-icons": "^7.2.1",
|
||||
"@navikt/ds-tokens": "^7.2.1",
|
||||
"clsx": "^2.1.0",
|
||||
"date-fns": "^3.0.0",
|
||||
"react-day-picker": "8.10.0"
|
||||
@@ -614,8 +614,8 @@
|
||||
"react-router-dom": "^6.3.0",
|
||||
"rimraf": "6.0.1",
|
||||
"swr": "^1.1.2",
|
||||
"ts-node": "^10.9.1",
|
||||
"tsc-alias": "1.8.8",
|
||||
"tsx": "^4.19.1",
|
||||
"typescript": "^5.1.6",
|
||||
"vitest": "^1.2.2"
|
||||
},
|
||||
|
||||
@@ -58,7 +58,7 @@ export const Size: Story = {
|
||||
render: () => (
|
||||
<VStack gap="4">
|
||||
{SIZES.map((size) => (
|
||||
<>
|
||||
<React.Fragment key={size}>
|
||||
<h3>{size}</h3>
|
||||
<Chat
|
||||
avatar="ON"
|
||||
@@ -75,7 +75,7 @@ export const Size: Story = {
|
||||
laboris labore nisi ut.
|
||||
</Chat.Bubble>
|
||||
</Chat>
|
||||
</>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</VStack>
|
||||
),
|
||||
@@ -85,7 +85,7 @@ export const Variants: Story = {
|
||||
render: () => (
|
||||
<VStack gap="4">
|
||||
{VARIANTS.map((variant) => (
|
||||
<>
|
||||
<React.Fragment key={variant}>
|
||||
<h3>{variant}</h3>
|
||||
<Chat
|
||||
avatar="NAV"
|
||||
@@ -97,7 +97,7 @@ export const Variants: Story = {
|
||||
Aute minim nisi sunt mollit duis sunt nulla minim non proident.
|
||||
</Chat.Bubble>
|
||||
</Chat>
|
||||
</>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</VStack>
|
||||
),
|
||||
|
||||
@@ -73,40 +73,35 @@ export const Default: StoryObj<DefaultStoryProps> = {
|
||||
locale={props.locale}
|
||||
disableWeekends={props.disableWeekends}
|
||||
>
|
||||
{!props.standalone && (
|
||||
<>
|
||||
{props.inputfield && props.mode !== "multiple" ? (
|
||||
<>
|
||||
{props.mode === "range" ? (
|
||||
<div style={{ display: "flex", gap: "1rem" }}>
|
||||
<DatePicker.Input
|
||||
label="Fra"
|
||||
size={props?.size}
|
||||
{...rangeCtx.fromInputProps}
|
||||
/>
|
||||
<DatePicker.Input
|
||||
label="Til"
|
||||
size={props?.size}
|
||||
{...rangeCtx.toInputProps}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<DatePicker.Input
|
||||
label="Velg dato"
|
||||
size={props?.size}
|
||||
{...singleCtx.inputProps}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<Button onClick={() => setOpen((x) => !x)}>
|
||||
Åpne datovelger
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{!props.standalone &&
|
||||
(props.inputfield && props.mode !== "multiple" ? (
|
||||
<>
|
||||
{props.mode === "range" ? (
|
||||
<div style={{ display: "flex", gap: "1rem" }}>
|
||||
<DatePicker.Input
|
||||
label="Fra"
|
||||
size={props?.size}
|
||||
{...rangeCtx.fromInputProps}
|
||||
/>
|
||||
<DatePicker.Input
|
||||
label="Til"
|
||||
size={props?.size}
|
||||
{...rangeCtx.toInputProps}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<DatePicker.Input
|
||||
label="Velg dato"
|
||||
size={props?.size}
|
||||
{...singleCtx.inputProps}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<Button onClick={() => setOpen((x) => !x)}>
|
||||
Åpne datovelger
|
||||
</Button>
|
||||
))}
|
||||
</Comp>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -19,7 +19,7 @@ const App = () => {
|
||||
};
|
||||
|
||||
describe("Render datepicker", () => {
|
||||
// eslint-disable-next-line vitest/expect-expect
|
||||
// eslint-disable-next-line @vitest/expect-expect
|
||||
test("Should not crash when e.target is window", async () => {
|
||||
render(<App />);
|
||||
|
||||
|
||||
@@ -4,29 +4,29 @@ import { describe, expect, test } from "vitest";
|
||||
import { isValidDate, parseDate } from "..";
|
||||
|
||||
const check = (inp: string) =>
|
||||
// eslint-disable-next-line vitest/valid-expect
|
||||
// eslint-disable-next-line @vitest/valid-expect
|
||||
expect(isValidDate(parseDate(inp, new Date(), nb, "date", false)));
|
||||
|
||||
const checkTwoDigit = (inp: string) =>
|
||||
// eslint-disable-next-line vitest/valid-expect
|
||||
// eslint-disable-next-line @vitest/valid-expect
|
||||
expect(isValidDate(parseDate(inp, new Date(), nb, "date", true)));
|
||||
|
||||
const parse = (inp: string) => parseDate(inp, new Date(), nb, "date", false);
|
||||
|
||||
describe("Parse date-inputs with 4-digit years", () => {
|
||||
// eslint-disable-next-line vitest/expect-expect
|
||||
// eslint-disable-next-line @vitest/expect-expect
|
||||
test("No spaces", () => {
|
||||
check("11052022").toBeTruthy();
|
||||
check("15052022").toBeTruthy();
|
||||
});
|
||||
|
||||
// eslint-disable-next-line vitest/expect-expect
|
||||
// eslint-disable-next-line @vitest/expect-expect
|
||||
test(". divider", () => {
|
||||
check("1.5.2022").toBeTruthy();
|
||||
check("11.05.2022").toBeTruthy();
|
||||
});
|
||||
|
||||
// eslint-disable-next-line vitest/expect-expect
|
||||
// eslint-disable-next-line @vitest/expect-expect
|
||||
test("/ divider", () => {
|
||||
check("1/5/2022").toBeTruthy();
|
||||
check("10/5/2022").toBeTruthy();
|
||||
@@ -34,7 +34,7 @@ describe("Parse date-inputs with 4-digit years", () => {
|
||||
check("10/05/2022").toBeTruthy();
|
||||
});
|
||||
|
||||
// eslint-disable-next-line vitest/expect-expect
|
||||
// eslint-disable-next-line @vitest/expect-expect
|
||||
test("- divider", () => {
|
||||
check("1-5-2022").toBeTruthy();
|
||||
check("10-5-2022").toBeTruthy();
|
||||
@@ -44,19 +44,19 @@ describe("Parse date-inputs with 4-digit years", () => {
|
||||
});
|
||||
|
||||
describe("Parse date-inputs with 2-digit years", () => {
|
||||
// eslint-disable-next-line vitest/expect-expect
|
||||
// eslint-disable-next-line @vitest/expect-expect
|
||||
test("No spaces", () => {
|
||||
checkTwoDigit("110522").toBeTruthy();
|
||||
checkTwoDigit("150522").toBeTruthy();
|
||||
});
|
||||
|
||||
// eslint-disable-next-line vitest/expect-expect
|
||||
// eslint-disable-next-line @vitest/expect-expect
|
||||
test(". divider", () => {
|
||||
checkTwoDigit("1.5.22").toBeTruthy();
|
||||
checkTwoDigit("11.05.22").toBeTruthy();
|
||||
});
|
||||
|
||||
// eslint-disable-next-line vitest/expect-expect
|
||||
// eslint-disable-next-line @vitest/expect-expect
|
||||
test("/ divider", () => {
|
||||
checkTwoDigit("1/5/22").toBeTruthy();
|
||||
checkTwoDigit("10/5/22").toBeTruthy();
|
||||
@@ -64,7 +64,7 @@ describe("Parse date-inputs with 2-digit years", () => {
|
||||
checkTwoDigit("10/05/22").toBeTruthy();
|
||||
});
|
||||
|
||||
// eslint-disable-next-line vitest/expect-expect
|
||||
// eslint-disable-next-line @vitest/expect-expect
|
||||
test("- divider", () => {
|
||||
checkTwoDigit("1-5-22").toBeTruthy();
|
||||
checkTwoDigit("10-5-22").toBeTruthy();
|
||||
@@ -72,14 +72,14 @@ describe("Parse date-inputs with 2-digit years", () => {
|
||||
checkTwoDigit("10-05-22").toBeTruthy();
|
||||
});
|
||||
|
||||
// eslint-disable-next-line vitest/expect-expect
|
||||
// eslint-disable-next-line @vitest/expect-expect
|
||||
test("Dissallow 1 and 3 digit years", () => {
|
||||
checkTwoDigit("11052").toBeFalsy();
|
||||
checkTwoDigit("1105222").toBeFalsy();
|
||||
checkTwoDigit("1105999").toBeFalsy();
|
||||
});
|
||||
|
||||
// eslint-disable-next-line vitest/expect-expect
|
||||
// eslint-disable-next-line @vitest/expect-expect
|
||||
test("Dissallow date before year 1000", () => {
|
||||
checkTwoDigit("11050999").toBeFalsy();
|
||||
checkTwoDigit("11050010").toBeFalsy();
|
||||
|
||||
@@ -82,7 +82,7 @@ export const Menu = forwardRef<HTMLDivElement, DropdownMenuProps>(
|
||||
open={isOpen}
|
||||
onClose={() => {
|
||||
handleToggle(false);
|
||||
onClose && onClose();
|
||||
onClose?.();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -44,8 +44,8 @@ const useCheckbox = (props: CheckboxProps) => {
|
||||
if (readOnly) {
|
||||
return;
|
||||
}
|
||||
props.onChange && props.onChange(e);
|
||||
checkboxGroup && checkboxGroup.toggleValue(props.value);
|
||||
props.onChange?.(e);
|
||||
checkboxGroup?.toggleValue(props.value);
|
||||
},
|
||||
onClick: (e) => {
|
||||
if (readOnly) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import cl from "clsx";
|
||||
import React, { forwardRef } from "react";
|
||||
import { BodyShort, ErrorMessage, Label } from "../../typography";
|
||||
import { ReadOnlyIcon } from "../ReadOnlyIcon";
|
||||
import ComboboxWrapper from "./ComboboxWrapper";
|
||||
import FilteredOptions from "./FilteredOptions/FilteredOptions";
|
||||
import { useFilteredOptionsContext } from "./FilteredOptions/filteredOptionsContext";
|
||||
@@ -27,6 +28,7 @@ export const Combobox = forwardRef<
|
||||
inputProps,
|
||||
showErrorMsg,
|
||||
size = "medium",
|
||||
readOnly,
|
||||
} = useInputContext();
|
||||
|
||||
return (
|
||||
@@ -44,6 +46,7 @@ export const Combobox = forwardRef<
|
||||
"navds-sr-only": hideLabel,
|
||||
})}
|
||||
>
|
||||
<ReadOnlyIcon nativeReadOnly={false} readOnly={readOnly} />
|
||||
{label}
|
||||
</Label>
|
||||
{!!description && (
|
||||
|
||||
@@ -36,6 +36,7 @@ const ComboboxProvider = forwardRef<HTMLInputElement, ComboboxProps>(
|
||||
children,
|
||||
defaultValue,
|
||||
disabled,
|
||||
readOnly,
|
||||
error,
|
||||
errorId,
|
||||
filteredOptions: externalFilteredOptions,
|
||||
@@ -50,19 +51,28 @@ const ComboboxProvider = forwardRef<HTMLInputElement, ComboboxProps>(
|
||||
value,
|
||||
onChange,
|
||||
onClear,
|
||||
shouldAutocomplete,
|
||||
shouldAutocomplete: externalShouldAutocomplete,
|
||||
size,
|
||||
...rest
|
||||
} = props;
|
||||
const options = mapToComboboxOptionArray(externalOptions) || [];
|
||||
const filteredOptions = mapToComboboxOptionArray(externalFilteredOptions);
|
||||
const selectedOptions = mapToComboboxOptionArray(externalSelectedOptions);
|
||||
|
||||
const userAgent =
|
||||
typeof navigator === "undefined" ? "" : navigator.userAgent;
|
||||
const isFirefoxOnAndroid =
|
||||
userAgent.includes("Android") && userAgent.includes("Firefox/");
|
||||
const shouldAutocomplete =
|
||||
!isFirefoxOnAndroid && externalShouldAutocomplete;
|
||||
|
||||
return (
|
||||
<InputContextProvider
|
||||
value={{
|
||||
defaultValue,
|
||||
description: rest.description,
|
||||
disabled,
|
||||
readOnly,
|
||||
error,
|
||||
errorId,
|
||||
id,
|
||||
|
||||
@@ -9,6 +9,7 @@ type ComboboxWrapperProps = {
|
||||
inputProps: {
|
||||
disabled?: boolean;
|
||||
};
|
||||
readOnly?: boolean;
|
||||
inputSize: string;
|
||||
toggleIsListOpen: (isListOpen: boolean) => void;
|
||||
};
|
||||
@@ -21,7 +22,7 @@ const ComboboxWrapper = ({
|
||||
inputSize,
|
||||
toggleIsListOpen,
|
||||
}: ComboboxWrapperProps) => {
|
||||
const { toggleOpenButtonRef, clearInput } = useInputContext();
|
||||
const { toggleOpenButtonRef, clearInput, readOnly } = useInputContext();
|
||||
|
||||
const wrapperRef = useRef<HTMLDivElement | null>(null);
|
||||
const [hasFocusWithin, setHasFocusWithin] = useState(false);
|
||||
@@ -55,6 +56,8 @@ const ComboboxWrapper = ({
|
||||
"navds-combobox--error": hasError,
|
||||
"navds-combobox--disabled": !!inputProps.disabled,
|
||||
"navds-combobox--focused": hasFocusWithin,
|
||||
"navds-combobox--readonly": readOnly,
|
||||
"navds-form-field--readonly": readOnly,
|
||||
},
|
||||
)}
|
||||
onFocus={onFocusInsideWrapper}
|
||||
|
||||
@@ -54,6 +54,7 @@ const FilteredOptions = () => {
|
||||
)}
|
||||
|
||||
{shouldRenderFilteredOptionsList && (
|
||||
/* biome-ignore lint/a11y/useFocusableInteractive: Interaction is not handeled by listbox itself. */
|
||||
<ul
|
||||
ref={setFilteredOptionsRef}
|
||||
role="listbox"
|
||||
|
||||
@@ -58,7 +58,8 @@ const FilteredOptionsProvider = ({
|
||||
useState<HTMLUListElement | null>(null);
|
||||
const virtualFocus = useVirtualFocus(filteredOptionsRef);
|
||||
const {
|
||||
inputProps: { "aria-describedby": partialAriaDescribedBy, id },
|
||||
inputProps: { "aria-describedby": partialAriaDescribedBy, id, disabled },
|
||||
readOnly,
|
||||
value,
|
||||
searchTerm,
|
||||
setValue,
|
||||
@@ -136,7 +137,10 @@ const FilteredOptionsProvider = ({
|
||||
|
||||
const toggleIsListOpen = useCallback(
|
||||
(newState?: boolean) => {
|
||||
virtualFocus.moveFocusToTop();
|
||||
if (disabled || readOnly) {
|
||||
return;
|
||||
}
|
||||
virtualFocus.resetFocus();
|
||||
if (newState ?? !isInternalListOpen) {
|
||||
setHideCaret(!!maxSelected?.isLimitReached);
|
||||
}
|
||||
@@ -147,6 +151,8 @@ const FilteredOptionsProvider = ({
|
||||
maxSelected?.isLimitReached,
|
||||
isInternalListOpen,
|
||||
setHideCaret,
|
||||
disabled,
|
||||
readOnly,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export type VirtualFocusType = {
|
||||
activeElement: HTMLElement | undefined;
|
||||
@@ -10,6 +10,9 @@ export type VirtualFocusType = {
|
||||
moveFocusToElement: (id: string) => void;
|
||||
moveFocusToTop: () => void;
|
||||
moveFocusToBottom: () => void;
|
||||
moveFocusUpBy: (numberOfElements: number) => void;
|
||||
moveFocusDownBy: (numberOfElements: number) => void;
|
||||
resetFocus: () => void;
|
||||
};
|
||||
|
||||
const useVirtualFocus = (
|
||||
@@ -40,11 +43,6 @@ const useVirtualFocus = (
|
||||
: false;
|
||||
};
|
||||
|
||||
const _moveFocusAndScrollTo = (_element?: HTMLElement) => {
|
||||
setActiveElement(_element);
|
||||
_element?.scrollIntoView?.({ block: "nearest" });
|
||||
};
|
||||
|
||||
const moveFocusUp = () => {
|
||||
if (!activeElement) {
|
||||
return;
|
||||
@@ -55,14 +53,14 @@ const useVirtualFocus = (
|
||||
if (_currentIndex === 0) {
|
||||
setActiveElement(undefined);
|
||||
} else {
|
||||
_moveFocusAndScrollTo(elementAbove);
|
||||
setActiveElement(elementAbove);
|
||||
}
|
||||
};
|
||||
|
||||
const moveFocusDown = () => {
|
||||
const elementsAbleToReceiveFocus = getElementsAbleToReceiveFocus();
|
||||
if (!activeElement) {
|
||||
_moveFocusAndScrollTo(elementsAbleToReceiveFocus[0]);
|
||||
setActiveElement(elementsAbleToReceiveFocus[0]);
|
||||
return;
|
||||
}
|
||||
const _currentIndex = elementsAbleToReceiveFocus.indexOf(activeElement);
|
||||
@@ -70,13 +68,17 @@ const useVirtualFocus = (
|
||||
return;
|
||||
}
|
||||
|
||||
_moveFocusAndScrollTo(elementsAbleToReceiveFocus[_currentIndex + 1]);
|
||||
setActiveElement(elementsAbleToReceiveFocus[_currentIndex + 1]);
|
||||
};
|
||||
|
||||
const moveFocusToTop = () => _moveFocusAndScrollTo(undefined);
|
||||
const resetFocus = () => setActiveElement(undefined);
|
||||
const moveFocusToTop = () => {
|
||||
const elementsAbleToReceiveFocus = getElementsAbleToReceiveFocus();
|
||||
setActiveElement(elementsAbleToReceiveFocus[0]);
|
||||
};
|
||||
const moveFocusToBottom = () => {
|
||||
const elementsAbleToReceiveFocus = getElementsAbleToReceiveFocus();
|
||||
return _moveFocusAndScrollTo(
|
||||
setActiveElement(
|
||||
elementsAbleToReceiveFocus[elementsAbleToReceiveFocus.length - 1],
|
||||
);
|
||||
};
|
||||
@@ -89,6 +91,32 @@ const useVirtualFocus = (
|
||||
}
|
||||
};
|
||||
|
||||
const moveFocusUpBy = (numberOfElements: number) => {
|
||||
if (!activeElement) {
|
||||
return;
|
||||
}
|
||||
const elementsAbleToReceiveFocus = getElementsAbleToReceiveFocus();
|
||||
const currentIndex = elementsAbleToReceiveFocus.indexOf(activeElement);
|
||||
const newIndex = Math.max(currentIndex - numberOfElements, 0);
|
||||
setActiveElement(elementsAbleToReceiveFocus[newIndex]);
|
||||
};
|
||||
|
||||
const moveFocusDownBy = (numberOfElements: number) => {
|
||||
const elementsAbleToReceiveFocus = getElementsAbleToReceiveFocus();
|
||||
const currentIndex = activeElement
|
||||
? elementsAbleToReceiveFocus.indexOf(activeElement)
|
||||
: -1;
|
||||
const newIndex = Math.min(
|
||||
currentIndex + numberOfElements,
|
||||
elementsAbleToReceiveFocus.length - 1,
|
||||
);
|
||||
setActiveElement(elementsAbleToReceiveFocus[newIndex]);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
activeElement?.scrollIntoView?.({ block: "nearest" });
|
||||
}, [activeElement]);
|
||||
|
||||
return {
|
||||
activeElement,
|
||||
getElementById,
|
||||
@@ -99,6 +127,9 @@ const useVirtualFocus = (
|
||||
moveFocusToElement,
|
||||
moveFocusToTop,
|
||||
moveFocusToBottom,
|
||||
moveFocusUpBy,
|
||||
moveFocusDownBy,
|
||||
resetFocus,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ interface Props {
|
||||
defaultValue: ComboboxProps["defaultValue"];
|
||||
description: ComboboxProps["description"];
|
||||
disabled: ComboboxProps["disabled"];
|
||||
readOnly: ComboboxProps["readOnly"];
|
||||
error: ComboboxProps["error"];
|
||||
errorId: ComboboxProps["errorId"];
|
||||
id: ComboboxProps["id"];
|
||||
@@ -48,6 +49,7 @@ const InputProvider = ({ children, value: props }: Props) => {
|
||||
defaultValue = "",
|
||||
description,
|
||||
disabled,
|
||||
readOnly,
|
||||
error,
|
||||
errorId,
|
||||
id: externalId,
|
||||
@@ -61,6 +63,7 @@ const InputProvider = ({ children, value: props }: Props) => {
|
||||
{
|
||||
description,
|
||||
disabled,
|
||||
readOnly,
|
||||
error,
|
||||
errorId,
|
||||
id: externalId,
|
||||
@@ -96,7 +99,7 @@ const InputProvider = ({ children, value: props }: Props) => {
|
||||
setInternalValue("");
|
||||
setSearchTerm("");
|
||||
},
|
||||
[externalOnChange, onClear, setInternalValue],
|
||||
[externalOnChange, onClear],
|
||||
);
|
||||
|
||||
const focusInput = useCallback(() => {
|
||||
|
||||
@@ -49,6 +49,7 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
setValue,
|
||||
hideCaret,
|
||||
setHideCaret,
|
||||
readOnly,
|
||||
} = useInputContext();
|
||||
const {
|
||||
selectedOptions,
|
||||
@@ -133,14 +134,6 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
case "Accept":
|
||||
onEnter(e);
|
||||
break;
|
||||
case "Home":
|
||||
toggleIsListOpen(false);
|
||||
virtualFocus.moveFocusToTop();
|
||||
break;
|
||||
case "End":
|
||||
toggleIsListOpen(true);
|
||||
virtualFocus.moveFocusToBottom();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -149,6 +142,9 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
const handleKeyDown = useCallback(
|
||||
(e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
setIsMouseLastUsedInputDevice(false);
|
||||
if (readOnly) {
|
||||
return;
|
||||
}
|
||||
if (e.key === "Backspace") {
|
||||
if (value === "" && shouldShowSelectedOptions) {
|
||||
const lastSelectedOption =
|
||||
@@ -198,6 +194,24 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
}
|
||||
virtualFocus.moveFocusUp();
|
||||
}
|
||||
} else if (e.key === "Home") {
|
||||
e.preventDefault();
|
||||
virtualFocus.moveFocusToTop();
|
||||
} else if (e.key === "End") {
|
||||
e.preventDefault();
|
||||
if (virtualFocus.activeElement === null || !isListOpen) {
|
||||
toggleIsListOpen(true);
|
||||
}
|
||||
virtualFocus.moveFocusToBottom();
|
||||
} else if (e.key === "PageUp") {
|
||||
e.preventDefault();
|
||||
virtualFocus.moveFocusUpBy(6);
|
||||
} else if (e.key === "PageDown") {
|
||||
e.preventDefault();
|
||||
if (virtualFocus.activeElement === null || !isListOpen) {
|
||||
toggleIsListOpen(true);
|
||||
}
|
||||
virtualFocus.moveFocusDownBy(6);
|
||||
}
|
||||
},
|
||||
[
|
||||
@@ -214,6 +228,7 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
setValue,
|
||||
searchTerm,
|
||||
shouldShowSelectedOptions,
|
||||
readOnly,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -225,10 +240,9 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
} else if (filteredOptions.length === 0) {
|
||||
toggleIsListOpen(false);
|
||||
}
|
||||
virtualFocus.moveFocusToTop();
|
||||
onChange(newValue);
|
||||
},
|
||||
[filteredOptions.length, virtualFocus, onChange, toggleIsListOpen],
|
||||
[filteredOptions.length, onChange, toggleIsListOpen],
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -262,6 +276,7 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
aria-activedescendant={activeDecendantId}
|
||||
aria-describedby={ariaDescribedBy}
|
||||
aria-invalid={inputProps["aria-invalid"]}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -45,6 +45,7 @@ export const InputController = forwardRef<
|
||||
size = "medium",
|
||||
inputRef,
|
||||
toggleOpenButtonRef,
|
||||
readOnly,
|
||||
} = useInputContext();
|
||||
|
||||
const { activeDecendantId } = useFilteredOptionsContext();
|
||||
@@ -65,6 +66,7 @@ export const InputController = forwardRef<
|
||||
id={inputProps.id}
|
||||
ref={mergedInputRef}
|
||||
inputClassName={inputClassName}
|
||||
readOnly={readOnly}
|
||||
{...rest}
|
||||
/>
|
||||
) : (
|
||||
@@ -74,6 +76,7 @@ export const InputController = forwardRef<
|
||||
ref={mergedInputRef}
|
||||
inputClassName={inputClassName}
|
||||
shouldShowSelectedOptions={shouldShowSelectedOptions}
|
||||
readOnly={readOnly}
|
||||
{...rest}
|
||||
/>
|
||||
</SelectedOptions>
|
||||
|
||||
@@ -12,7 +12,7 @@ interface SelectedOptionsProps {
|
||||
|
||||
const Option = ({ option }: { option: ComboboxOption }) => {
|
||||
const { isMultiSelect, removeSelectedOption } = useSelectedOptionsContext();
|
||||
const { focusInput } = useInputContext();
|
||||
const { focusInput, readOnly, inputProps } = useInputContext();
|
||||
|
||||
const onClick = (e) => {
|
||||
e.stopPropagation();
|
||||
@@ -28,7 +28,13 @@ const Option = ({ option }: { option: ComboboxOption }) => {
|
||||
);
|
||||
}
|
||||
|
||||
return <Chips.Removable onClick={onClick}>{option.label}</Chips.Removable>;
|
||||
return readOnly || inputProps.disabled ? (
|
||||
<Chips.Toggle variant="neutral" checkmark={false} as="div">
|
||||
{option.label}
|
||||
</Chips.Toggle>
|
||||
) : (
|
||||
<Chips.Removable onClick={onClick}>{option.label}</Chips.Removable>
|
||||
);
|
||||
};
|
||||
|
||||
const SelectedOptions: React.FC<SelectedOptionsProps> = ({
|
||||
|
||||
@@ -4,7 +4,7 @@ import { usePrevious } from "../../../util/hooks";
|
||||
import { useInputContext } from "../Input/Input.context";
|
||||
import { isInList } from "../combobox-utils";
|
||||
import { useComboboxCustomOptions } from "../customOptionsContext";
|
||||
import { ComboboxOption, ComboboxProps, MaxSelected } from "../types";
|
||||
import { ComboboxOption, ComboboxProps } from "../types";
|
||||
|
||||
type SelectedOptionsContextValue = {
|
||||
addSelectedOption: (option: ComboboxOption) => void;
|
||||
@@ -12,7 +12,7 @@ type SelectedOptionsContextValue = {
|
||||
removeSelectedOption: (option: ComboboxOption) => void;
|
||||
prevSelectedOptions?: ComboboxOption[];
|
||||
selectedOptions: ComboboxOption[];
|
||||
maxSelected?: MaxSelected & { isLimitReached: boolean };
|
||||
maxSelected?: ComboboxProps["maxSelected"] & { isLimitReached: boolean };
|
||||
setSelectedOptions: (any) => void;
|
||||
toggleOption: (
|
||||
option: ComboboxOption,
|
||||
@@ -105,9 +105,10 @@ const SelectedOptionsProvider = ({
|
||||
(!!maxSelected?.limit && selectedOptions.length >= maxSelected.limit) ||
|
||||
(!isMultiSelect && selectedOptions.length > 0);
|
||||
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: We explicitly want to run this effect when selectedOptions changes to match the view with the selected options.
|
||||
useEffect(() => {
|
||||
setHideCaret(isLimitReached);
|
||||
}, [selectedOptions, setHideCaret, isLimitReached]);
|
||||
}, [isLimitReached, selectedOptions, setHideCaret]);
|
||||
|
||||
const toggleOption = useCallback(
|
||||
(
|
||||
|
||||
@@ -291,4 +291,40 @@ describe("Render combobox", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("has keyboard navigation", () => {
|
||||
test("for PageDown and PageUp", async () => {
|
||||
render(<App options={options} />);
|
||||
|
||||
const combobox = screen.getByRole("combobox", {
|
||||
name: "Hva er dine favorittfrukter?",
|
||||
});
|
||||
|
||||
const pressKey = async (key: string) => {
|
||||
await act(async () => {
|
||||
await userEvent.keyboard(`{${key}}`);
|
||||
});
|
||||
};
|
||||
|
||||
const hasVirtualFocus = (option: string) =>
|
||||
expect(combobox.getAttribute("aria-activedescendant")).toBe(
|
||||
screen.getByRole("option", { name: option }).id,
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await userEvent.click(combobox);
|
||||
});
|
||||
|
||||
await pressKey("ArrowDown");
|
||||
hasVirtualFocus("apple");
|
||||
await pressKey("PageDown");
|
||||
hasVirtualFocus("mango");
|
||||
await pressKey("PageDown");
|
||||
hasVirtualFocus("watermelon");
|
||||
await pressKey("PageUp");
|
||||
hasVirtualFocus("mango");
|
||||
await pressKey("PageUp");
|
||||
hasVirtualFocus("apple");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -7,7 +7,7 @@ import { VStack } from "../../layout/stack";
|
||||
import { Modal } from "../../modal";
|
||||
import { BodyLong } from "../../typography";
|
||||
import { TextField } from "../textfield";
|
||||
import { UNSAFE_Combobox } from "./index";
|
||||
import { ComboboxProps, UNSAFE_Combobox } from "./index";
|
||||
|
||||
export default {
|
||||
title: "ds-react/Combobox",
|
||||
@@ -38,14 +38,16 @@ const options = [
|
||||
"grapefruit",
|
||||
];
|
||||
|
||||
export const Default: StoryFunction = (props) => (
|
||||
export const Default: StoryFn<ComboboxProps & { maxSelected?: number }> = ({
|
||||
maxSelected,
|
||||
...rest
|
||||
}) => (
|
||||
<UNSAFE_Combobox
|
||||
{...props}
|
||||
maxSelected={{ limit: Number(props.maxSelected) }}
|
||||
{...rest}
|
||||
maxSelected={maxSelected && { limit: maxSelected }}
|
||||
id="combobox"
|
||||
/>
|
||||
);
|
||||
|
||||
Default.args = {
|
||||
options,
|
||||
label: "Hva er din favorittfrukt?",
|
||||
@@ -65,6 +67,9 @@ Default.argTypes = {
|
||||
disabled: {
|
||||
control: { type: "boolean" },
|
||||
},
|
||||
readOnly: {
|
||||
control: { type: "boolean" },
|
||||
},
|
||||
isListOpen: {
|
||||
control: { type: "boolean" },
|
||||
},
|
||||
@@ -482,12 +487,50 @@ export const InModal: StoryFn = () => {
|
||||
};
|
||||
|
||||
export const Disabled: StoryFn = () => {
|
||||
const selectedOptionsMultiple = ["napoleonskake", "donut"];
|
||||
const selectedOptionsSingle = ["pushups"];
|
||||
return (
|
||||
<UNSAFE_Combobox
|
||||
options={options}
|
||||
label="Hva er dine favorittfrukter?"
|
||||
disabled
|
||||
/>
|
||||
<VStack gap="2">
|
||||
<UNSAFE_Combobox
|
||||
options={options}
|
||||
label="Hva er dine favorittfrukter?"
|
||||
description="Bare de fruktene som du spiser minst 5 av om dagen teller vi som en favorittfrukt."
|
||||
selectedOptions={selectedOptionsMultiple}
|
||||
isMultiSelect
|
||||
disabled
|
||||
/>
|
||||
<UNSAFE_Combobox
|
||||
options={options}
|
||||
label="Hva er din favoritthobby?"
|
||||
description="Her snakker vi ting du bruker minst én time på hver dag."
|
||||
selectedOptions={selectedOptionsSingle}
|
||||
disabled
|
||||
/>
|
||||
</VStack>
|
||||
);
|
||||
};
|
||||
|
||||
export const Readonly: StoryFn = () => {
|
||||
const selectedOptionsMultiple = ["napoleonskake", "donut"];
|
||||
const selectedOptionsSingle = ["pushups"];
|
||||
return (
|
||||
<VStack gap="2">
|
||||
<UNSAFE_Combobox
|
||||
options={options}
|
||||
label="Hva er dine favorittfrukter?"
|
||||
description="Bare de fruktene som du spiser minst 5 av om dagen teller vi som en favorittfrukt."
|
||||
selectedOptions={selectedOptionsMultiple}
|
||||
isMultiSelect
|
||||
readOnly
|
||||
/>
|
||||
<UNSAFE_Combobox
|
||||
options={options}
|
||||
label="Hva er din favoritthobby?"
|
||||
description="Her snakker vi ting du bruker minst én time på hver dag."
|
||||
selectedOptions={selectedOptionsSingle}
|
||||
readOnly
|
||||
/>
|
||||
</VStack>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -523,6 +566,8 @@ export const Chromatic: StoryFn = () => {
|
||||
<WithError />
|
||||
<H2>Disabled</H2>
|
||||
<Disabled />
|
||||
<H2>Readonly</H2>
|
||||
<Readonly />
|
||||
</VStack>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -35,7 +35,7 @@ const CustomOptionsProvider = ({
|
||||
);
|
||||
focusInput();
|
||||
},
|
||||
[focusInput, setCustomOptions],
|
||||
[focusInput],
|
||||
);
|
||||
|
||||
const addCustomOption = useCallback(
|
||||
@@ -47,7 +47,7 @@ const CustomOptionsProvider = ({
|
||||
}
|
||||
focusInput();
|
||||
},
|
||||
[focusInput, isMultiSelect, setCustomOptions],
|
||||
[focusInput, isMultiSelect],
|
||||
);
|
||||
|
||||
const customOptionsState = {
|
||||
|
||||
@@ -16,17 +16,6 @@ export type ComboboxOption = {
|
||||
value: string;
|
||||
};
|
||||
|
||||
export type MaxSelected = {
|
||||
/**
|
||||
* The limit for maximum selected options
|
||||
*/
|
||||
limit: number;
|
||||
/**
|
||||
* Override the message to display when the limit for maximum selected options has been reached
|
||||
*/
|
||||
message?: string;
|
||||
};
|
||||
|
||||
export interface ComboboxProps
|
||||
extends FormFieldProps,
|
||||
Omit<
|
||||
@@ -135,7 +124,16 @@ export interface ComboboxProps
|
||||
/**
|
||||
* Options for the maximum number of selected options.
|
||||
*/
|
||||
maxSelected?: MaxSelected;
|
||||
maxSelected?: {
|
||||
/**
|
||||
* The limit for maximum selected options
|
||||
*/
|
||||
limit: number;
|
||||
/**
|
||||
* Override the message to display when the limit for maximum selected options has been reached
|
||||
*/
|
||||
message?: string;
|
||||
};
|
||||
/**
|
||||
* Set to `true` to enable inline autocomplete.
|
||||
*
|
||||
|
||||
@@ -2,7 +2,7 @@ import cl from "clsx";
|
||||
import React, { HTMLAttributes, forwardRef, useRef } from "react";
|
||||
import { BodyShort, Heading } from "../../typography";
|
||||
import { composeEventHandlers } from "../../util/composeEventHandlers";
|
||||
import { useId, useMergeRefs } from "../../util/hooks";
|
||||
import { useMergeRefs } from "../../util/hooks";
|
||||
import ErrorSummaryItem from "./ErrorSummaryItem";
|
||||
|
||||
export interface ErrorSummaryProps
|
||||
@@ -33,13 +33,13 @@ interface ErrorSummaryComponent
|
||||
ErrorSummaryProps & React.RefAttributes<HTMLDivElement>
|
||||
> {
|
||||
/**
|
||||
* Link to error.
|
||||
* Error message with link to field.
|
||||
*
|
||||
* @see [🤖 OverridableComponent](https://aksel.nav.no/grunnleggende/kode/overridablecomponent) support
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
* <ErrorSummary.Item href="#1">
|
||||
* <ErrorSummary.Item href="#id-til-alderfelt">
|
||||
* Felt må fylles ut med alder
|
||||
* </ErrorSummary.Item>
|
||||
* ```
|
||||
@@ -48,7 +48,7 @@ interface ErrorSummaryComponent
|
||||
}
|
||||
|
||||
/**
|
||||
* A component that displays a summary of errors.
|
||||
* Summary of errors in a form.
|
||||
*
|
||||
* @see [📝 Documentation](https://aksel.nav.no/komponenter/core/errorsummary)
|
||||
* @see 🏷️ {@link ErrorSummaryProps}
|
||||
@@ -77,15 +77,13 @@ export const ErrorSummary = forwardRef<HTMLDivElement, ErrorSummaryProps>(
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const headingId = useId();
|
||||
|
||||
const sectionRef = useRef<HTMLDivElement>(null);
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
const headingRef = useRef<HTMLHeadingElement>(null);
|
||||
|
||||
const mergedRef = useMergeRefs(ref, sectionRef);
|
||||
const mergedRef = useMergeRefs(ref, wrapperRef);
|
||||
|
||||
return (
|
||||
<section
|
||||
<div
|
||||
ref={mergedRef}
|
||||
{...rest}
|
||||
className={cl(
|
||||
@@ -94,11 +92,8 @@ export const ErrorSummary = forwardRef<HTMLDivElement, ErrorSummaryProps>(
|
||||
`navds-error-summary--${size}`,
|
||||
)}
|
||||
tabIndex={-1}
|
||||
aria-live="polite"
|
||||
aria-relevant="all"
|
||||
aria-labelledby={headingId}
|
||||
onFocus={composeEventHandlers(rest.onFocus, (event) => {
|
||||
if (event.target === sectionRef.current) {
|
||||
if (event.target === wrapperRef.current) {
|
||||
headingRef?.current?.focus();
|
||||
}
|
||||
})}
|
||||
@@ -107,7 +102,6 @@ export const ErrorSummary = forwardRef<HTMLDivElement, ErrorSummaryProps>(
|
||||
className="navds-error-summary__heading"
|
||||
as={headingTag}
|
||||
size="small"
|
||||
id={headingId}
|
||||
ref={headingRef}
|
||||
tabIndex={-1}
|
||||
>
|
||||
@@ -116,7 +110,7 @@ export const ErrorSummary = forwardRef<HTMLDivElement, ErrorSummaryProps>(
|
||||
<BodyShort as="ul" size={size} className="navds-error-summary__list">
|
||||
{children}
|
||||
</BodyShort>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
) as ErrorSummaryComponent;
|
||||
|
||||
@@ -2,11 +2,7 @@ import { Meta, StoryFn, StoryObj } from "@storybook/react";
|
||||
import { fireEvent, within } from "@storybook/test";
|
||||
import React from "react";
|
||||
import { ImageIcon } from "@navikt/aksel-icons";
|
||||
import {
|
||||
type FileObject,
|
||||
UNSAFE_FileUpload as FileUpload,
|
||||
type FilesPartitioned,
|
||||
} from ".";
|
||||
import { type FileObject, FileUpload, type FilesPartitioned } from ".";
|
||||
|
||||
const meta: Meta<typeof FileUpload.Dropzone> = {
|
||||
title: "ds-react/FileUpload/Dropzone",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
import React from "react";
|
||||
import { FileItem, UNSAFE_FileUpload as FileUpload } from ".";
|
||||
import { FileItem, FileUpload } from ".";
|
||||
import { VStack } from "../../layout/stack";
|
||||
|
||||
const meta: Meta<typeof FileUpload.Item> = {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { UploadIcon } from "@navikt/aksel-icons";
|
||||
import { UNSAFE_FileUpload as FileUpload, FileUploadItemProps } from ".";
|
||||
import { FileUpload, FileUploadItemProps } from ".";
|
||||
import { Alert } from "../../alert";
|
||||
import { Button } from "../../button";
|
||||
import { VStack } from "../../layout/stack";
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
"use client";
|
||||
export { default as UNSAFE_FileUpload } from "./FileUpload";
|
||||
export { default as UNSAFE_FileUploadDropzone } from "./parts/dropzone/Dropzone";
|
||||
export { default as FileUpload } from "./FileUpload";
|
||||
export { default as FileUploadDropzone } from "./parts/dropzone/Dropzone";
|
||||
export { type FileUploadDropzoneProps } from "./parts/dropzone/dropzone.types";
|
||||
export {
|
||||
default as UNSAFE_FileUploadTrigger,
|
||||
default as FileUploadTrigger,
|
||||
type FileUploadTriggerProps,
|
||||
} from "./parts/Trigger";
|
||||
export {
|
||||
@@ -15,7 +15,7 @@ export {
|
||||
type FileRejectionReason,
|
||||
} from "./FileUpload.types";
|
||||
export {
|
||||
default as UNSAFE_FileUploadItem,
|
||||
default as FileUploadItem,
|
||||
type FileUploadItemProps,
|
||||
} from "./parts/item/Item";
|
||||
export { type FileItem, type FileMetadata } from "./parts/item/Item.types";
|
||||
@@ -22,14 +22,17 @@ export interface FormSummaryEditProps
|
||||
export const FormSummaryEditLink: OverridableComponent<
|
||||
FormSummaryEditProps,
|
||||
HTMLAnchorElement
|
||||
> = forwardRef(({ children = "Endre svar", className, ...rest }, ref) => (
|
||||
<Link
|
||||
ref={ref}
|
||||
{...rest}
|
||||
className={cl("navds-form-summary__edit", className)}
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
));
|
||||
> = forwardRef(
|
||||
({ children = "Endre svar", className, as = "a", ...rest }, ref) => (
|
||||
<Link
|
||||
ref={ref}
|
||||
as={as}
|
||||
{...rest}
|
||||
className={cl("navds-form-summary__edit", className)}
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
),
|
||||
);
|
||||
|
||||
export default FormSummaryEditLink;
|
||||
@@ -41,8 +41,8 @@ export const useRadio = (props: RadioProps) => {
|
||||
if (readOnly) {
|
||||
return;
|
||||
}
|
||||
props.onChange && props.onChange(e);
|
||||
radioGroup?.onChange && radioGroup.onChange(props.value);
|
||||
props.onChange?.(e);
|
||||
radioGroup?.onChange?.(props.value);
|
||||
},
|
||||
onClick: (e) => {
|
||||
if (readOnly) {
|
||||
|
||||
@@ -144,7 +144,7 @@ export const Search = forwardRef<HTMLInputElement, SearchProps>(
|
||||
(event: SearchClearEvent) => {
|
||||
onClear?.(event);
|
||||
handleChange("");
|
||||
searchRef.current && searchRef.current?.focus?.();
|
||||
searchRef.current?.focus?.();
|
||||
},
|
||||
[handleChange, onClear],
|
||||
);
|
||||
|
||||
@@ -122,7 +122,7 @@ export const Switch = forwardRef<HTMLInputElement, SwitchProps>(
|
||||
return;
|
||||
}
|
||||
setChecked(e.target.checked);
|
||||
props.onChange && props.onChange(e);
|
||||
props.onChange?.(e);
|
||||
}}
|
||||
onClick={(e) => {
|
||||
if (readOnly) {
|
||||
|
||||
@@ -49,7 +49,7 @@ Default.argTypes = {
|
||||
export const Open: StoryFn = () => {
|
||||
const ref = useRef<HTMLButtonElement | null>(null);
|
||||
useEffect(() => {
|
||||
ref.current && ref.current.click();
|
||||
ref.current?.click();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
||||
@@ -127,7 +127,7 @@ export {
|
||||
export { ErrorSummary, type ErrorSummaryProps } from "./form/error-summary";
|
||||
export { Fieldset, type FieldsetProps } from "./form/fieldset";
|
||||
export {
|
||||
UNSAFE_FileUpload,
|
||||
FileUpload,
|
||||
type FileAccepted,
|
||||
type FileItem,
|
||||
type FileMetadata,
|
||||
|
||||
@@ -63,7 +63,7 @@ export const PageBlock: OverridableComponent<PageBlockProps, HTMLDivElement> =
|
||||
{...rest}
|
||||
className={cl(
|
||||
"navds-pageblock",
|
||||
`navds-pageblock--${width}`,
|
||||
width && `navds-pageblock--${width}`,
|
||||
className,
|
||||
{ "navds-pageblock--gutters": gutters },
|
||||
)}
|
||||
|
||||
@@ -26,8 +26,8 @@ export type HStackProps = PrimitiveAsChildProps & Omit<StackProps, "direction">;
|
||||
* </HStack>
|
||||
*/
|
||||
export const HStack: OverridableComponent<HStackProps, HTMLDivElement> =
|
||||
forwardRef((props, ref) => {
|
||||
return <Stack {...props} ref={ref} direction="row" />;
|
||||
forwardRef(({ as = "div", ...rest }, ref) => {
|
||||
return <Stack as={as} {...rest} ref={ref} direction="row" />;
|
||||
});
|
||||
|
||||
export default HStack;
|
||||
@@ -27,8 +27,10 @@ export type VStackProps = PrimitiveAsChildProps &
|
||||
* </VStack>
|
||||
*/
|
||||
export const VStack: OverridableComponent<VStackProps, HTMLDivElement> =
|
||||
forwardRef((props, ref) => {
|
||||
return <Stack {...props} ref={ref} direction="column" wrap={false} />;
|
||||
forwardRef(({ as = "div", ...rest }, ref) => {
|
||||
return (
|
||||
<Stack as={as} {...rest} ref={ref} direction="column" wrap={false} />
|
||||
);
|
||||
});
|
||||
|
||||
export default VStack;
|
||||
@@ -1,15 +1,27 @@
|
||||
import cl from "clsx";
|
||||
import React, { forwardRef, useContext } from "react";
|
||||
import { BodyShort, Heading } from "../typography";
|
||||
import { useId } from "../util/hooks";
|
||||
import { BodyLong, BodyShort, Heading, HeadingProps } from "../typography";
|
||||
import { ListItem } from "./ListItem";
|
||||
import { ListContext } from "./context";
|
||||
import { ListProps } from "./types";
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
import type { ListItemProps, ListProps } from "./types";
|
||||
|
||||
const headingSizeMap: Record<
|
||||
Exclude<ListProps["size"], undefined>,
|
||||
HeadingProps["size"]
|
||||
> = {
|
||||
small: "xsmall",
|
||||
medium: "small",
|
||||
large: "medium",
|
||||
};
|
||||
|
||||
export interface ListComponent
|
||||
extends React.ForwardRefExoticComponent<
|
||||
ListProps & React.RefAttributes<HTMLDivElement>
|
||||
> {
|
||||
/**
|
||||
* @see 🏷️ {@link ListItemProps}
|
||||
*/
|
||||
Item: typeof ListItem;
|
||||
}
|
||||
|
||||
@@ -42,47 +54,31 @@ export const List = forwardRef<HTMLDivElement, ListProps>(
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const ariaId = useId();
|
||||
const { size: contextSize } = useContext(ListContext);
|
||||
|
||||
const { isNested, size: _size } = useContext(ListContext);
|
||||
|
||||
const listSize = size ?? _size ?? "medium";
|
||||
const listSize = size ?? contextSize;
|
||||
return (
|
||||
<ListContext.Provider
|
||||
value={{
|
||||
listType: ListTag,
|
||||
isNested: isNested === null ? false : true,
|
||||
size: listSize,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
<BodyLong
|
||||
as="div"
|
||||
{...rest}
|
||||
size={listSize}
|
||||
ref={ref}
|
||||
className={cl("navds-list", `navds-list--${listSize}`, className, {
|
||||
"navds-list--nested": isNested === null ? false : true,
|
||||
})}
|
||||
className={cl("navds-list", `navds-list--${listSize}`, className)}
|
||||
>
|
||||
{title && (
|
||||
<Heading
|
||||
id={`tittel-${ariaId}`}
|
||||
size={listSize === "medium" ? "small" : "xsmall"}
|
||||
as={headingTag}
|
||||
>
|
||||
<Heading size={headingSizeMap[listSize]} as={headingTag}>
|
||||
{title}
|
||||
</Heading>
|
||||
)}
|
||||
{description && (
|
||||
<BodyShort size={listSize} id={`description-${ariaId}`}>
|
||||
{description}
|
||||
</BodyShort>
|
||||
)}
|
||||
<ListTag
|
||||
aria-labelledby={title && `tittel-${ariaId}`}
|
||||
aria-describedby={description && `description-${ariaId}`}
|
||||
>
|
||||
{children}
|
||||
</ListTag>
|
||||
</div>
|
||||
{description && <BodyShort size={listSize}>{description}</BodyShort>}
|
||||
<ListTag role="list">{children}</ListTag>
|
||||
</BodyLong>
|
||||
</ListContext.Provider>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -2,7 +2,7 @@ import cl from "clsx";
|
||||
import React, { forwardRef, useContext } from "react";
|
||||
import { BodyLong } from "../typography";
|
||||
import { ListContext } from "./context";
|
||||
import { ListItemProps } from "./types";
|
||||
import type { ListItemProps } from "./types";
|
||||
|
||||
/**
|
||||
* @see 🏷️ {@link ListItemProps}
|
||||
@@ -21,7 +21,7 @@ export const ListItem = forwardRef<HTMLLIElement, ListItemProps>(
|
||||
<li {...rest} ref={ref} className={cl("navds-list__item", className)}>
|
||||
{listType === "ul" && (
|
||||
<div
|
||||
className={cl({
|
||||
className={cl("navds-list__item-marker", {
|
||||
"navds-list__item-marker--icon": icon,
|
||||
"navds-list__item-marker--bullet": !icon,
|
||||
})}
|
||||
@@ -30,8 +30,8 @@ export const ListItem = forwardRef<HTMLLIElement, ListItemProps>(
|
||||
icon
|
||||
) : (
|
||||
<svg
|
||||
width="6"
|
||||
height="6"
|
||||
width="0.375rem"
|
||||
height="0.375rem"
|
||||
viewBox="0 0 6 6"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -45,14 +45,14 @@ export const ListItem = forwardRef<HTMLLIElement, ListItemProps>(
|
||||
</div>
|
||||
)}
|
||||
|
||||
<BodyLong as="div" size={size} className="navds-list__item-content">
|
||||
<div>
|
||||
{title && (
|
||||
<BodyLong as="p" size={size} weight="semibold">
|
||||
{title}
|
||||
</BodyLong>
|
||||
)}
|
||||
{children}
|
||||
</BodyLong>
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -2,13 +2,11 @@ import { createContext } from "react";
|
||||
import { ListProps } from "./types";
|
||||
|
||||
interface ListContextProps {
|
||||
listType: ListProps["as"];
|
||||
isNested: boolean | null;
|
||||
size: ListProps["size"];
|
||||
listType: Exclude<ListProps["as"], undefined>;
|
||||
size: Exclude<ListProps["size"], undefined>;
|
||||
}
|
||||
|
||||
export const ListContext = createContext<ListContextProps>({
|
||||
listType: "ul",
|
||||
isNested: null,
|
||||
size: "medium",
|
||||
});
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
HeadHeartIcon,
|
||||
SplitHorizontalIcon,
|
||||
} from "@navikt/aksel-icons";
|
||||
import { Box } from "../layout/box";
|
||||
import { VStack } from "../layout/stack";
|
||||
import { BodyLong } from "../typography";
|
||||
import List from "./List";
|
||||
@@ -21,73 +22,97 @@ export default {
|
||||
|
||||
type Story = StoryObj<typeof List>;
|
||||
|
||||
export const Default = {
|
||||
export const Default: Story = {
|
||||
render: (props) => {
|
||||
return (
|
||||
<Box borderWidth="0 0 0 1" borderColor="border-subtle" maxWidth="500px">
|
||||
<List {...props}>
|
||||
<List.Item title="Lorem Ipsum Dolor Sit Amet">Beskrivelse.</List.Item>
|
||||
<List.Item title="Consectetur Adipiscing Elit">
|
||||
Beskrivelse på punktet i lista. Prøv å hold den kort og konsis.
|
||||
</List.Item>
|
||||
<List.Item>
|
||||
Lorem ipsum, dolor sit amet consectetur adipisicing elit. Iusto
|
||||
fugiat atque accusantium iure sunt, ipsum voluptas, impedit harum,
|
||||
minus rerum recusandae.
|
||||
</List.Item>
|
||||
<List.Item>
|
||||
Impedit nemo eos sit adipisci non dolores.
|
||||
<List>
|
||||
<List.Item title="Tempor Incididunt">
|
||||
Error assumenda officia
|
||||
</List.Item>
|
||||
<List.Item>Suscipit odit voluptatum</List.Item>
|
||||
</List>
|
||||
</List.Item>
|
||||
<List.Item>
|
||||
Lorem ipsum, dolor sit amet consectetur adipisicing elit. Iusto
|
||||
fugiat atque accusantium iure sunt, ipsum voluptas.
|
||||
<List as="ol">
|
||||
<List.Item title="Tempor Incididunt">
|
||||
Error assumenda officia
|
||||
</List.Item>
|
||||
<List.Item>Suscipit odit voluptatum</List.Item>
|
||||
</List>
|
||||
</List.Item>
|
||||
</List>
|
||||
</Box>
|
||||
);
|
||||
},
|
||||
args: {
|
||||
title: "",
|
||||
description: "",
|
||||
},
|
||||
argTypes: {
|
||||
size: { control: { type: "radio" }, options: ["small", "medium", "large"] },
|
||||
as: { control: { type: "radio" }, options: ["ul", "ol"] },
|
||||
},
|
||||
};
|
||||
|
||||
export const Ordered: Story = {
|
||||
render: () => {
|
||||
return (
|
||||
<List>
|
||||
<List.Item title="Lorem Ipsum Dolor Sit Amet">
|
||||
Lorem Ipsum Dolor Sit Amet
|
||||
</List.Item>
|
||||
<List.Item title="Consectetur Adipiscing Elit">
|
||||
Beskrivelse på punktet i lista. Prøv å hold den kort og konsis.
|
||||
</List.Item>
|
||||
<List.Item>
|
||||
Lorem ipsum, dolor sit amet consectetur adipisicing elit. Iusto fugiat
|
||||
atque accusantium iure sunt, ipsum voluptas, impedit harum, minus
|
||||
rerum recusandae. Consequuntur sint distinctio nulla reprehenderit eum
|
||||
suscipit quae libero.
|
||||
</List.Item>
|
||||
<List.Item>
|
||||
Lorem ipsum, dolor sit amet consectetur adipisicing elit. Iusto fugiat
|
||||
atque accusantium iure sunt, ipsum voluptas, impedit harum, minus
|
||||
rerum recusandae. Consequuntur sint distinctio nulla reprehenderit eum
|
||||
suscipit quae libero.
|
||||
</List.Item>
|
||||
</List>
|
||||
<Box borderWidth="0 0 0 1" borderColor="border-subtle" maxWidth="500px">
|
||||
<List as="ol">
|
||||
<List.Item title="Lorem Ipsum Dolor Sit Amet">Beskrivelse.</List.Item>
|
||||
<List.Item title="Consectetur Adipiscing Elit">
|
||||
Beskrivelse på punktet i lista. Prøv å hold den kort og konsis.
|
||||
</List.Item>
|
||||
<List.Item title="Quis Nostrud Exercitation Ullamco Eu Fugiat Nulla Pariatur Dolore">
|
||||
Beskrivelse på punktet i lista. Prøv å hold den kort og konsis. Men
|
||||
hvis den likevel blir lang, skal teksten brekke.
|
||||
</List.Item>
|
||||
<List.Item>Sed Do Eiusmod Tempor Incididunt</List.Item>
|
||||
<List.Item>Ut Labore Et Dolore Magna Aliqua</List.Item>
|
||||
<List.Item>Enim Ad Minim Veniam</List.Item>
|
||||
<List.Item>Laboris Nisi Ut Aliquip Ex Ea Commodo</List.Item>
|
||||
<List.Item>Duis Aute Irure Dolor In Reprehenderit</List.Item>
|
||||
<List.Item>
|
||||
Impedit nemo eos sit adipisci non dolores.
|
||||
<List as="ol">
|
||||
<List.Item title="Tempor Incididunt">
|
||||
Error assumenda officia
|
||||
</List.Item>
|
||||
<List.Item>Suscipit odit voluptatum</List.Item>
|
||||
</List>
|
||||
</List.Item>
|
||||
<List.Item title="Voluptate Velit Esse Cillum Dolore">
|
||||
Beskrivelse på punktet i lista. Prøv å hold den kort og konsis.
|
||||
<List>
|
||||
<List.Item title="Tempor Incididunt">
|
||||
Error assumenda officia
|
||||
</List.Item>
|
||||
<List.Item>Suscipit odit voluptatum</List.Item>
|
||||
</List>
|
||||
</List.Item>
|
||||
<List.Item>Eu Fugiat Nulla Pariatur</List.Item>
|
||||
</List>
|
||||
</Box>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const Ordered = {
|
||||
render: () => {
|
||||
return (
|
||||
<List as="ol">
|
||||
<List.Item title="Lorem Ipsum Dolor Sit Amet">
|
||||
Lorem Ipsum Dolor Sit Amet
|
||||
</List.Item>
|
||||
<List.Item title="Consectetur Adipiscing Elit">
|
||||
Beskrivelse på punktet i lista. Prøv å hold den kort og konsis.
|
||||
</List.Item>
|
||||
<List.Item title="Sed Do Eiusmod Tempor Incididunt">
|
||||
Beskrivelse på punktet i lista. Prøv å hold den kort og konsis.
|
||||
</List.Item>
|
||||
<List.Item title="Ut Labore Et Dolore Magna Aliqua">
|
||||
Beskrivelse på punktet i lista. Prøv å hold den kort og konsis.
|
||||
</List.Item>
|
||||
<List.Item title="Enim Ad Minim Veniam">
|
||||
Beskrivelse på punktet i lista. Prøv å hold den kort og konsis.
|
||||
</List.Item>
|
||||
<List.Item title="Quis Nostrud Exercitation Ullamco">
|
||||
Beskrivelse på punktet i lista. Prøv å hold den kort og konsis.
|
||||
</List.Item>
|
||||
<List.Item title="Laboris Nisi Ut Aliquip Ex Ea Commodo">
|
||||
Beskrivelse på punktet i lista. Prøv å hold den kort og konsis.
|
||||
</List.Item>
|
||||
<List.Item title="Duis Aute Irure Dolor In Reprehenderit">
|
||||
Beskrivelse på punktet i lista. Prøv å hold den kort og konsis.
|
||||
</List.Item>
|
||||
<List.Item title="Voluptate Velit Esse Cillum Dolore">
|
||||
Beskrivelse på punktet i lista. Prøv å hold den kort og konsis.
|
||||
</List.Item>
|
||||
<List.Item title="Eu Fugiat Nulla Pariatur">
|
||||
Beskrivelse på punktet i lista. Prøv å hold den kort og konsis.
|
||||
</List.Item>
|
||||
</List>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const WithHeading = {
|
||||
export const WithHeading: Story = {
|
||||
render: () => {
|
||||
return (
|
||||
<List
|
||||
@@ -108,10 +133,25 @@ export const WithHeading = {
|
||||
},
|
||||
};
|
||||
|
||||
export const SizesUl = {
|
||||
export const SizesUl: Story = {
|
||||
render: () => {
|
||||
return (
|
||||
<VStack gap="8">
|
||||
<List
|
||||
title="Large list"
|
||||
description="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam vel risus volutpat."
|
||||
size="large"
|
||||
>
|
||||
<List.Item title="Lorem Ipsum Dolor Sit Amet">
|
||||
Beskrivelsen på punkter er nærmere forklart <a href="/">her</a>
|
||||
</List.Item>
|
||||
<List.Item title="Consectetur Adipiscing Elit">
|
||||
Beskrivelsen på punkter er nærmere forklart <a href="/">her</a>
|
||||
</List.Item>
|
||||
<List.Item>
|
||||
Beskrivelsen på punkter er nærmere forklart <a href="/">her</a>
|
||||
</List.Item>
|
||||
</List>
|
||||
<List
|
||||
title="Medium list"
|
||||
description="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam vel risus volutpat."
|
||||
@@ -147,10 +187,26 @@ export const SizesUl = {
|
||||
},
|
||||
};
|
||||
|
||||
export const SizesOl = {
|
||||
export const SizesOl: Story = {
|
||||
render: () => {
|
||||
return (
|
||||
<VStack gap="8">
|
||||
<List
|
||||
title="Large list"
|
||||
description="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam vel risus volutpat."
|
||||
size="large"
|
||||
as="ol"
|
||||
>
|
||||
<List.Item title="Lorem Ipsum Dolor Sit Amet">
|
||||
Beskrivelsen på punkter er nærmere forklart <a href="/">her</a>
|
||||
</List.Item>
|
||||
<List.Item title="Consectetur Adipiscing Elit">
|
||||
Beskrivelsen på punkter er nærmere forklart <a href="/">her</a>
|
||||
</List.Item>
|
||||
<List.Item>
|
||||
Beskrivelsen på punkter er nærmere forklart <a href="/">her</a>
|
||||
</List.Item>
|
||||
</List>
|
||||
<List
|
||||
title="Medium list"
|
||||
description="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam vel risus volutpat."
|
||||
@@ -188,10 +244,31 @@ export const SizesOl = {
|
||||
},
|
||||
};
|
||||
|
||||
export const SizesIcons = {
|
||||
export const SizesIcons: Story = {
|
||||
render: () => {
|
||||
return (
|
||||
<VStack gap="8">
|
||||
<List
|
||||
title="Large list"
|
||||
description="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam vel risus volutpat."
|
||||
size="large"
|
||||
>
|
||||
<List.Item
|
||||
icon={<HeadHeartIcon aria-hidden />}
|
||||
title="Lorem Ipsum Dolor Sit Amet"
|
||||
>
|
||||
Beskrivelsen på punkter er nærmere forklart <a href="/">her</a>
|
||||
</List.Item>
|
||||
<List.Item
|
||||
icon={<HeadHeartIcon aria-hidden />}
|
||||
title="Consectetur Adipiscing Elit"
|
||||
>
|
||||
Beskrivelsen på punkter er nærmere forklart <a href="/">her</a>
|
||||
</List.Item>
|
||||
<List.Item icon={<HeadHeartIcon aria-hidden />}>
|
||||
Beskrivelsen på punkter er nærmere forklart <a href="/">her</a>
|
||||
</List.Item>
|
||||
</List>
|
||||
<List
|
||||
title="Medium list"
|
||||
description="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam vel risus volutpat."
|
||||
@@ -239,7 +316,7 @@ export const SizesIcons = {
|
||||
},
|
||||
};
|
||||
|
||||
export const Icons = {
|
||||
export const Icons: Story = {
|
||||
render: () => {
|
||||
return (
|
||||
<List title="Best title">
|
||||
@@ -266,39 +343,70 @@ export const Icons = {
|
||||
},
|
||||
};
|
||||
|
||||
export const Nested = {
|
||||
export const Tokens: Story = {
|
||||
render: () => {
|
||||
return (
|
||||
<List>
|
||||
<List.Item title="Sed Do Eiusmod Tempor Incididunt">
|
||||
Beskrivelsen på punkter er nærmere forklart <a href="/">her</a>
|
||||
<List>
|
||||
<List.Item title="Lorem Ipsum Dolor Sit Amet">
|
||||
Beskrivelsen på punkter er nærmere forklart <a href="/">her</a>
|
||||
</List.Item>
|
||||
<List.Item title="Consectetur Adipiscing Elit">
|
||||
Beskrivelsen på punkter er nærmere forklart <a href="/">her</a>
|
||||
</List.Item>
|
||||
<List.Item>
|
||||
Beskrivelsen på punkter er nærmere forklart <a href="/">her</a>
|
||||
</List.Item>
|
||||
<List.Item>
|
||||
Beskrivelsen på punkter er nærmere forklart <a href="/">her</a>
|
||||
<List style={{ "--ac-list-marker-color": "red" }}>
|
||||
<List.Item>Consectetur Adipiscing Elit</List.Item>
|
||||
<List.Item>
|
||||
Impedit nemo eos sit adipisci non dolores.
|
||||
<List
|
||||
style={{
|
||||
"--ac-list-marker-icon-color": "blue",
|
||||
"--ac-list-marker-ul-color": "pink",
|
||||
}}
|
||||
>
|
||||
<List.Item>Error assumenda officia</List.Item>
|
||||
<List.Item icon={<HeadHeartIcon aria-hidden />}>
|
||||
Suscipit odit voluptatum
|
||||
</List.Item>
|
||||
</List>
|
||||
</List.Item>
|
||||
<List.Item>
|
||||
Beskrivelsen på punkter er nærmere forklart <a href="/">her</a>
|
||||
<List.Item style={{ "--ac-list-marker-ol-color": "green" }}>
|
||||
Iusto fugiat atque accusantium iure sunt.
|
||||
<List as="ol">
|
||||
<List.Item>Error assumenda officia</List.Item>
|
||||
</List>
|
||||
<List>
|
||||
<List.Item>Suscipit odit voluptatum</List.Item>
|
||||
</List>
|
||||
</List.Item>
|
||||
</List>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const Spacing = {
|
||||
export const Spacing: Story = {
|
||||
render: () => {
|
||||
return (
|
||||
<>
|
||||
<h2>Large</h2>
|
||||
<BodyLong size="large" spacing>
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Sit minus
|
||||
aliquid, veniam quibusdam saepe autem quia odio? Tenetur dicta
|
||||
voluptates iste maiores perspiciatis? Sapiente in possimus iusto
|
||||
numquam? Esse, voluptatibus.
|
||||
</BodyLong>
|
||||
<List size="large">
|
||||
<List.Item>
|
||||
Lorem ipsum, dolor sit amet consectetur adipisicing elit. Iusto
|
||||
fugiat atque accusantium iure sunt, ipsum voluptas, impedit harum,
|
||||
minus rerum recusandae. Consequuntur sint distinctio nulla
|
||||
reprehenderit eum suscipit quae libero.
|
||||
</List.Item>
|
||||
<List.Item>
|
||||
Lorem ipsum, dolor sit amet consectetur adipisicing elit. Iusto
|
||||
fugiat atque accusantium iure sunt, ipsum voluptas, impedit harum,
|
||||
minus rerum recusandae. Consequuntur sint distinctio nulla
|
||||
reprehenderit eum suscipit quae libero.
|
||||
</List.Item>
|
||||
</List>
|
||||
<BodyLong size="large" spacing>
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Sit minus
|
||||
aliquid, veniam quibusdam saepe autem quia odio? Tenetur dicta
|
||||
voluptates iste maiores perspiciatis? Sapiente in possimus iusto
|
||||
numquam? Esse, voluptatibus.
|
||||
</BodyLong>
|
||||
<h2>Medium</h2>
|
||||
<BodyLong spacing>
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Sit minus
|
||||
@@ -359,43 +467,43 @@ export const Spacing = {
|
||||
};
|
||||
|
||||
export const Chromatic: Story = {
|
||||
render: () => (
|
||||
render: (...args) => (
|
||||
<VStack gap="2">
|
||||
<div>
|
||||
<h2>Default</h2>
|
||||
<Default.render />
|
||||
{Default.render?.(...args)}
|
||||
</div>
|
||||
<div>
|
||||
<h2>Ordered</h2>
|
||||
<Ordered.render />
|
||||
{Ordered.render?.(...args)}
|
||||
</div>
|
||||
<div>
|
||||
<h2>WithHeading</h2>
|
||||
<WithHeading.render />
|
||||
{WithHeading.render?.(...args)}
|
||||
</div>
|
||||
<div>
|
||||
<h2>SizesUl</h2>
|
||||
<SizesUl.render />
|
||||
{SizesUl.render?.(...args)}
|
||||
</div>
|
||||
<div>
|
||||
<h2>SizesOl</h2>
|
||||
<SizesOl.render />
|
||||
{SizesOl.render?.(...args)}
|
||||
</div>
|
||||
<div>
|
||||
<h2>SizesIcons</h2>
|
||||
<SizesIcons.render />
|
||||
{SizesIcons.render?.(...args)}
|
||||
</div>
|
||||
<div>
|
||||
<h2>Icons</h2>
|
||||
<Icons.render />
|
||||
{Icons.render?.(...args)}
|
||||
</div>
|
||||
<div>
|
||||
<h2>Nested</h2>
|
||||
<Nested.render />
|
||||
<h2>Tokens</h2>
|
||||
{Tokens.render?.(...args)}
|
||||
</div>
|
||||
<div>
|
||||
<h2>Spacing</h2>
|
||||
<Spacing.render />
|
||||
{Spacing.render?.(...args)}
|
||||
</div>
|
||||
</VStack>
|
||||
),
|
||||
|
||||
@@ -19,10 +19,10 @@ export interface ListProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
*/
|
||||
headingTag?: React.ElementType<any>;
|
||||
/**
|
||||
* Changes padding, height and font-size.
|
||||
* Changes margin-block on list and font size on items.
|
||||
* @default "medium"
|
||||
*/
|
||||
size?: "medium" | "small";
|
||||
size?: "small" | "medium" | "large";
|
||||
}
|
||||
|
||||
export interface ListItemProps extends React.HTMLAttributes<HTMLLIElement> {
|
||||
|
||||
@@ -127,7 +127,7 @@ export const Modal = forwardRef<HTMLDialogElement, ModalProps>(
|
||||
// We have to use JS because it doesn't work to set it with a prop (React bug?)
|
||||
// Currently doesn't seem to work in Chrome. See also Tooltip.tsx
|
||||
if (modalRef.current && portalNode) modalRef.current.autofocus = true;
|
||||
}, [modalRef, portalNode]);
|
||||
}, [portalNode]);
|
||||
|
||||
useEffect(() => {
|
||||
// We need to have this in a useEffect so that the content renders before the modal is displayed,
|
||||
@@ -140,7 +140,7 @@ export const Modal = forwardRef<HTMLDialogElement, ModalProps>(
|
||||
modalRef.current.close();
|
||||
}
|
||||
}
|
||||
}, [modalRef, portalNode, open]);
|
||||
}, [portalNode, open]);
|
||||
|
||||
useBodyScrollLock(modalRef, portalNode, isNested);
|
||||
|
||||
|
||||
@@ -337,6 +337,8 @@ const DismissableLayerNode = forwardRef<HTMLDivElement, DismissableLayerProps>(
|
||||
* If `disableOutsidePointerEvents` is true,
|
||||
* we want to disable pointer events on the body when the first layer is opened.
|
||||
*/
|
||||
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: Every time the descendants change, we want to update the body pointer events since we might have added or removed a layer.
|
||||
useEffect(() => {
|
||||
if (!node || !enabled || !disableOutsidePointerEvents) return;
|
||||
|
||||
@@ -362,6 +364,7 @@ const DismissableLayerNode = forwardRef<HTMLDivElement, DismissableLayerProps>(
|
||||
/**
|
||||
* To make sure pointerEvents are enabled for all parents and siblings when the layer is removed from the DOM
|
||||
*/
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: We explicitly want to run this on unmount, including every time the node updates to make sure we don't lock the application behind pointer-events: none.
|
||||
useEffect(() => {
|
||||
return () => descendants.values().forEach((x) => x.forceUpdate());
|
||||
}, [descendants, node]);
|
||||
|
||||
@@ -55,19 +55,19 @@ const RovingFocus = forwardRef<HTMLDivElement, RovingFocusProps>(
|
||||
|
||||
const nextItem = () => {
|
||||
const next = descendants.nextEnabled(idx, loop);
|
||||
next && next.node?.focus();
|
||||
next?.node?.focus();
|
||||
};
|
||||
const prevItem = () => {
|
||||
const prev = descendants.prevEnabled(idx, loop);
|
||||
prev && prev.node?.focus();
|
||||
prev?.node?.focus();
|
||||
};
|
||||
const firstItem = () => {
|
||||
const first = descendants.firstEnabled();
|
||||
first && first.node?.focus();
|
||||
first?.node?.focus();
|
||||
};
|
||||
const lastItem = () => {
|
||||
const last = descendants.lastEnabled();
|
||||
last && last.node?.focus();
|
||||
last?.node?.focus();
|
||||
};
|
||||
|
||||
const keyMap: Record<string, React.KeyboardEventHandler> = {
|
||||
|
||||
@@ -189,21 +189,13 @@ export const Pagination = forwardRef<HTMLElement, PaginationProps>(
|
||||
size={size}
|
||||
icon={
|
||||
<ChevronLeftIcon
|
||||
className="navds-pagination__prev-next-icon"
|
||||
{...(prevNextTexts
|
||||
? { "aria-hidden": true }
|
||||
: { title: "Forrige" })}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{prevNextTexts && (
|
||||
<BodyShort
|
||||
size={size === "xsmall" ? "small" : size}
|
||||
className="navds-pagination__prev-text"
|
||||
>
|
||||
Forrige
|
||||
</BodyShort>
|
||||
)}
|
||||
{prevNextTexts && `Forrige`}
|
||||
</Item>
|
||||
</li>
|
||||
{getSteps({ page, count, siblingCount, boundaryCount }).map(
|
||||
@@ -211,7 +203,10 @@ export const Pagination = forwardRef<HTMLElement, PaginationProps>(
|
||||
const n = Number(step);
|
||||
return Number.isNaN(n) ? (
|
||||
<li className="navds-pagination__ellipsis" key={`${step}${i}`}>
|
||||
<BodyShort size={size === "xsmall" ? "small" : size}>
|
||||
<BodyShort
|
||||
size={size === "xsmall" ? "small" : size}
|
||||
as="span"
|
||||
>
|
||||
...
|
||||
</BodyShort>
|
||||
</li>
|
||||
@@ -223,9 +218,7 @@ export const Pagination = forwardRef<HTMLElement, PaginationProps>(
|
||||
page={n}
|
||||
size={size}
|
||||
>
|
||||
<BodyShort size={size === "xsmall" ? "small" : size}>
|
||||
{n}
|
||||
</BodyShort>
|
||||
{n}
|
||||
</Item>
|
||||
</li>
|
||||
);
|
||||
@@ -243,7 +236,6 @@ export const Pagination = forwardRef<HTMLElement, PaginationProps>(
|
||||
size={size}
|
||||
icon={
|
||||
<ChevronRightIcon
|
||||
className="navds-pagination__prev-next-icon"
|
||||
{...(prevNextTexts
|
||||
? { "aria-hidden": true }
|
||||
: { title: "Neste" })}
|
||||
@@ -251,14 +243,7 @@ export const Pagination = forwardRef<HTMLElement, PaginationProps>(
|
||||
}
|
||||
iconPosition="right"
|
||||
>
|
||||
{prevNextTexts && (
|
||||
<BodyShort
|
||||
size={size === "xsmall" ? "small" : size}
|
||||
className="navds-pagination__next-text"
|
||||
>
|
||||
Neste
|
||||
</BodyShort>
|
||||
)}
|
||||
{prevNextTexts && `Neste`}
|
||||
</Item>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -11,9 +11,10 @@ export interface PaginationItemProps extends ButtonProps {
|
||||
*/
|
||||
selected?: boolean;
|
||||
/**
|
||||
* The page the item represents
|
||||
* Currently only sets `data-page` attribute.
|
||||
* @deprecated Use `data-page` instead if you need to access the items page number in the future.
|
||||
*/
|
||||
page: number;
|
||||
page?: number;
|
||||
/**
|
||||
* Changes padding, height and font-size
|
||||
* @default "medium"
|
||||
@@ -48,7 +49,7 @@ export const Item: PaginationItemType = forwardRef(
|
||||
"navds-pagination__item--selected": selected,
|
||||
})}
|
||||
data-page={page}
|
||||
/* TODO: Breaking change to remove. Add to future major version. */
|
||||
/* TODO: Breaking change to remove since it's in use by some applications. Add to future major version. */
|
||||
page={page}
|
||||
{...(Component === "button" && { type: "button" })}
|
||||
{...rest}
|
||||
|
||||
@@ -107,6 +107,7 @@ export const ProgressBar = forwardRef<HTMLDivElement, ProgressBarProps>(
|
||||
}, [simulated?.seconds]);
|
||||
|
||||
return (
|
||||
/* biome-ignore lint/a11y/useFocusableInteractive: Progressbar is not interactive. */
|
||||
<div
|
||||
ref={ref}
|
||||
className={cl(
|
||||
@@ -121,6 +122,7 @@ export const ProgressBar = forwardRef<HTMLDivElement, ProgressBarProps>(
|
||||
? `Fremdrift kan ikke beregnes, antatt tid er: ${simulated?.seconds} sekunder`
|
||||
: `${Math.round(value)} av ${Math.round(valueMax)}`
|
||||
}
|
||||
// biome-ignore lint/a11y/useAriaPropsForRole: We found that adding valueMin was not needed
|
||||
role="progressbar"
|
||||
aria-labelledby={ariaLabelledBy}
|
||||
aria-label={ariaLabel}
|
||||
|
||||
@@ -74,37 +74,36 @@ const AnimateHeight: React.FC<AnimateHeightProps> = ({
|
||||
const animationClassesTimeoutID = useRef<Timeout>();
|
||||
const timeoutID = useRef<Timeout>();
|
||||
|
||||
const initialHeight = useRef<Height>(height);
|
||||
const initialOverflow = useRef<Overflow>("visible");
|
||||
|
||||
const duration = prefersReducedMotion ? 0 : userDuration;
|
||||
|
||||
let initHeight: Height = height;
|
||||
let initOverflow: Overflow = "visible";
|
||||
|
||||
if (typeof initHeight === "number") {
|
||||
if (typeof initialHeight.current === "number") {
|
||||
// Reset negative height to 0
|
||||
if (typeof height !== "string") {
|
||||
initHeight = height < 0 ? 0 : height;
|
||||
initialHeight.current = height < 0 ? 0 : height;
|
||||
}
|
||||
initOverflow = "hidden";
|
||||
} else if (isPercentage(initHeight)) {
|
||||
initialOverflow.current = "hidden";
|
||||
} else if (isPercentage(initialHeight.current)) {
|
||||
// If value is string "0%" make sure we convert it to number 0
|
||||
initHeight = height === "0%" ? 0 : height;
|
||||
initOverflow = "hidden";
|
||||
initialHeight.current = height === "0%" ? 0 : height;
|
||||
initialOverflow.current = "hidden";
|
||||
}
|
||||
|
||||
const [currentHeight, setCurrentHeight] = useState<Height>(initHeight);
|
||||
const [overflow, setOverflow] = useState<Overflow>(initOverflow);
|
||||
const [currentHeight, setCurrentHeight] = useState<Height>(
|
||||
initialHeight.current,
|
||||
);
|
||||
const [overflow, setOverflow] = useState<Overflow>(initialOverflow.current);
|
||||
const [useTransitions, setUseTransitions] = useState<boolean>(false);
|
||||
|
||||
// ------------------ Did mount
|
||||
useEffect(() => {
|
||||
// Hide content if height is 0 (to prevent tabbing into it)
|
||||
hideContent(contentElement.current, currentHeight);
|
||||
|
||||
// This should be explicitly run only on mount
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
hideContent(contentElement.current, initialHeight.current);
|
||||
}, []);
|
||||
|
||||
// ------------------ Height update
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: This should be explicitly run only on height change
|
||||
useEffect(() => {
|
||||
if (height !== prevHeight.current && contentElement.current) {
|
||||
showContent(contentElement.current, prevHeight.current);
|
||||
|
||||
@@ -98,61 +98,6 @@ export const SizeSmall = () => <TableComponent size="small" />;
|
||||
|
||||
export const Buttons = () => <TableComponent size="small" button />;
|
||||
|
||||
export const WithDivs = () => {
|
||||
return (
|
||||
<div className="navds-table" role="table">
|
||||
<div className="navds-table__header" role="rowgroup">
|
||||
<div className="navds-table__row" role="row">
|
||||
<div className="navds-table__header-cell" role="columnheader">
|
||||
Fornavn
|
||||
</div>
|
||||
<div className="navds-table__header-cell" role="columnheader">
|
||||
Etternavn
|
||||
</div>
|
||||
<div className="navds-table__header-cell" role="columnheader">
|
||||
Rolle
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="navds-table__body" role="rowgroup">
|
||||
<div className="navds-table__row" role="row">
|
||||
<div className="navds-table__data-cell" role="cell">
|
||||
Jean-Luc
|
||||
</div>
|
||||
<div className="navds-table__data-cell" role="cell">
|
||||
Picard
|
||||
</div>
|
||||
<div className="navds-table__data-cell" role="cell">
|
||||
Kaptein
|
||||
</div>
|
||||
</div>
|
||||
<div className="navds-table__row" role="row">
|
||||
<div className="navds-table__data-cell" role="cell">
|
||||
William
|
||||
</div>
|
||||
<div className="navds-table__data-cell" role="cell">
|
||||
Riker
|
||||
</div>
|
||||
<div className="navds-table__data-cell" role="cell">
|
||||
Kommandør
|
||||
</div>
|
||||
</div>
|
||||
<div className="navds-table__row" role="row">
|
||||
<div className="navds-table__data-cell" role="cell">
|
||||
Geordi
|
||||
</div>
|
||||
<div className="navds-table__data-cell" role="cell">
|
||||
La Forge
|
||||
</div>
|
||||
<div className="navds-table__data-cell" role="cell">
|
||||
Sjefsingeniør
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const SelectionTable = ({ size = "medium" }: { size?: "small" | "medium" }) => {
|
||||
const useToggleList = (initialState) => {
|
||||
const [list, setList] = useState(initialState);
|
||||
@@ -259,10 +204,6 @@ export const Chromatic = {
|
||||
<h3>With Buttons</h3>
|
||||
<Buttons />
|
||||
</div>
|
||||
<div>
|
||||
<h3>Custom with divs</h3>
|
||||
<WithDivs />
|
||||
</div>
|
||||
<div>
|
||||
<h3>Selection</h3>
|
||||
<Selection />
|
||||
|
||||
@@ -41,7 +41,7 @@ export function useScrollButtons(listRef: React.RefObject<HTMLDivElement>) {
|
||||
|
||||
return () => {
|
||||
win.removeEventListener("resize", handleResize);
|
||||
resizeObserver && resizeObserver.disconnect();
|
||||
resizeObserver?.disconnect();
|
||||
updateScrollButtonState.clear();
|
||||
};
|
||||
}, [listRef, updateScrollButtonState]);
|
||||
|
||||
@@ -26,19 +26,19 @@ export function useTabList() {
|
||||
|
||||
const nextTab = () => {
|
||||
const next = descendants.nextEnabled(idx, loop);
|
||||
next && next.node?.focus();
|
||||
next?.node?.focus();
|
||||
};
|
||||
const prevTab = () => {
|
||||
const prev = descendants.prevEnabled(idx, loop);
|
||||
prev && prev.node?.focus();
|
||||
prev?.node?.focus();
|
||||
};
|
||||
const firstTab = () => {
|
||||
const first = descendants.firstEnabled();
|
||||
first && first.node?.focus();
|
||||
first?.node?.focus();
|
||||
};
|
||||
const lastTab = () => {
|
||||
const last = descendants.lastEnabled();
|
||||
last && last.node?.focus();
|
||||
last?.node?.focus();
|
||||
};
|
||||
|
||||
const keyMap: Record<string, React.KeyboardEventHandler> = {
|
||||
|
||||
@@ -91,27 +91,26 @@ export const TimelineRow = forwardRef<HTMLOListElement, TimelineRowProps>(
|
||||
}
|
||||
}}
|
||||
>
|
||||
{periods &&
|
||||
periods.map((period) => {
|
||||
return (
|
||||
<li key={`period-${period.id}`}>
|
||||
<PeriodContext.Provider
|
||||
value={{
|
||||
periodId: period.id,
|
||||
firstFocus: firstFocusable?.id === period.id,
|
||||
restProps: period?.restProps,
|
||||
}}
|
||||
>
|
||||
<Period
|
||||
start={period.start}
|
||||
end={period.endInclusive}
|
||||
icon={period.icon}
|
||||
ref={period?.ref}
|
||||
/>
|
||||
</PeriodContext.Provider>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
{periods?.map((period) => {
|
||||
return (
|
||||
<li key={`period-${period.id}`}>
|
||||
<PeriodContext.Provider
|
||||
value={{
|
||||
periodId: period.id,
|
||||
firstFocus: firstFocusable?.id === period.id,
|
||||
restProps: period?.restProps,
|
||||
}}
|
||||
>
|
||||
<Period
|
||||
start={period.start}
|
||||
end={period.endInclusive}
|
||||
icon={period.icon}
|
||||
ref={period?.ref}
|
||||
/>
|
||||
</PeriodContext.Provider>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ol>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -55,19 +55,19 @@ export function useToggleItem<P extends UseToggleItemProps>(
|
||||
|
||||
const nextTab = () => {
|
||||
const next = descendants.nextEnabled(idx, false);
|
||||
next && next.node?.focus();
|
||||
next?.node?.focus();
|
||||
};
|
||||
const prevTab = () => {
|
||||
const prev = descendants.prevEnabled(idx, false);
|
||||
prev && prev.node?.focus();
|
||||
prev?.node?.focus();
|
||||
};
|
||||
const firstTab = () => {
|
||||
const first = descendants.firstEnabled();
|
||||
first && first.node?.focus();
|
||||
first?.node?.focus();
|
||||
};
|
||||
const lastTab = () => {
|
||||
const last = descendants.lastEnabled();
|
||||
last && last.node?.focus();
|
||||
last?.node?.focus();
|
||||
};
|
||||
|
||||
const keyMap: Record<string, React.KeyboardEventHandler> = {
|
||||
|
||||
@@ -152,14 +152,14 @@ export const FontWeight: Story = {
|
||||
return (
|
||||
<>
|
||||
{fontWeights.map((weight) => (
|
||||
<>
|
||||
<React.Fragment key={weight}>
|
||||
<h2>{weight}</h2>
|
||||
<FontComponent
|
||||
key={weight}
|
||||
weight={weight}
|
||||
family="Source Sans 3"
|
||||
/>
|
||||
</>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -40,7 +40,7 @@ const checkState = (
|
||||
* https://github.com/mui/material-ui/blob/master/packages/mui-utils/src/ownerWindow/ownerWindow.ts
|
||||
*/
|
||||
const ownerWindow = (node: Node | undefined): Window => {
|
||||
const doc = (node && node.ownerDocument) || document;
|
||||
const doc = node?.ownerDocument || document;
|
||||
return doc.defaultView || window;
|
||||
};
|
||||
|
||||
@@ -205,6 +205,7 @@ const TextareaAutosize = forwardRef<HTMLTextAreaElement, TextareaAutosizeProps>(
|
||||
syncHeight();
|
||||
});
|
||||
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: Since value is an external prop, we want to reset the renders on every time it changes, even when it is undefined or empty.
|
||||
useEffect(() => {
|
||||
renders.current = 0;
|
||||
}, [value]);
|
||||
@@ -222,7 +223,7 @@ const TextareaAutosize = forwardRef<HTMLTextAreaElement, TextareaAutosizeProps>(
|
||||
};
|
||||
|
||||
const mainStyle: React.CSSProperties = {
|
||||
["--__ac-textarea-height"]: state.outerHeightStyle + "px",
|
||||
"--__ac-textarea-height": state.outerHeightStyle + "px",
|
||||
// Need a large enough difference to allow scrolling.
|
||||
// This prevents infinite rendering loop.
|
||||
overflow:
|
||||
|
||||
@@ -40,11 +40,12 @@ export function createContext<T>(options: CreateContextOptions<T> = {}) {
|
||||
* We use forwardRef to allow `ref` to be used as a regular context value
|
||||
* @see https://reactjs.org/docs/forwarding-refs.html#forwarding-refs-to-dom-components
|
||||
*/
|
||||
const Provider = forwardRef(
|
||||
({ children, ...context }: ProviderProps<T>, ref) => {
|
||||
const Provider = forwardRef<unknown, ProviderProps<T>>(
|
||||
({ children, ...context }, ref) => {
|
||||
// Only re-memoize when prop values change
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const value = React.useMemo(() => context, Object.values(context)) as T;
|
||||
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: Object.values(context) includes all dependencies.
|
||||
const value = React.useMemo(() => context, Object.values(context)) as T; // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
return (
|
||||
<Context.Provider value={ref ? { ...value, ref } : value}>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import * as React from "react";
|
||||
import { Box } from "../../../layout/box";
|
||||
import { HStack } from "../../../layout/stack";
|
||||
import { createDescendantContext } from "./useDescendant";
|
||||
|
||||
@@ -16,12 +15,16 @@ const [
|
||||
_useDescendantsContext,
|
||||
useDescendants,
|
||||
useDescendant,
|
||||
] = createDescendantContext<HTMLDivElement, { value?: string }>();
|
||||
] = createDescendantContext<
|
||||
HTMLButtonElement | HTMLInputElement,
|
||||
{ value?: string }
|
||||
>();
|
||||
|
||||
function Select({ children }: { children?: React.ReactNode }) {
|
||||
const descendants = useDescendants();
|
||||
const count = descendants.count();
|
||||
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: We want count to be a trigger for focusing the last descendant here.
|
||||
React.useEffect(() => {
|
||||
descendants.last()?.node.focus();
|
||||
}, [descendants, count]);
|
||||
@@ -37,9 +40,8 @@ function Option({ value, disabled }: { value?: string; disabled?: boolean }) {
|
||||
});
|
||||
|
||||
return (
|
||||
<Box
|
||||
<button
|
||||
ref={register}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
data-value={value}
|
||||
onKeyDown={(event) => {
|
||||
@@ -51,7 +53,7 @@ function Option({ value, disabled }: { value?: string; disabled?: boolean }) {
|
||||
}}
|
||||
>
|
||||
Option {index + 1}
|
||||
</Box>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ export class DescendantsManager<
|
||||
this.descendants.forEach((descendant) => {
|
||||
const index = descendants.indexOf(descendant.node);
|
||||
descendant.index = index;
|
||||
descendant.node.dataset["index"] = descendant.index.toString();
|
||||
descendant.node.dataset.index = descendant.index.toString();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ export function createDescendantContext<
|
||||
|
||||
useClientLayoutEffect(() => {
|
||||
if (!ref.current) return;
|
||||
const dataIndex = Number(ref.current.dataset["index"]);
|
||||
const dataIndex = Number(ref.current.dataset.index);
|
||||
if (index !== dataIndex && !Number.isNaN(dataIndex)) {
|
||||
setIndex(dataIndex);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# @navikt/ds-tailwind
|
||||
|
||||
## 7.2.1
|
||||
|
||||
## 7.2.0
|
||||
|
||||
## 7.1.2
|
||||
|
||||
## 7.1.1
|
||||
|
||||
## 7.1.0
|
||||
|
||||
## 7.0.1
|
||||
|
||||
## 7.0.0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-tailwind",
|
||||
"version": "7.0.1",
|
||||
"version": "7.2.1",
|
||||
"description": "Tailwind config based on @navikt/ds-tokens",
|
||||
"author": "Aksel | NAV designsystem team",
|
||||
"keywords": [
|
||||
@@ -15,7 +15,7 @@
|
||||
"tailwind.config.d.ts"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "ts-node ./src",
|
||||
"build": "yarn tsx ./src",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest watch"
|
||||
},
|
||||
@@ -25,11 +25,11 @@
|
||||
"directory": "@navikt/core/tailwind"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@navikt/ds-tokens": "^7.0.1",
|
||||
"@navikt/ds-tokens": "^7.2.1",
|
||||
"color": "4.2.3",
|
||||
"lodash": "^4.17.21",
|
||||
"tailwindcss": "^3.3.3",
|
||||
"ts-node": "^10.9.1",
|
||||
"tsx": "^4.19.1",
|
||||
"typescript": "^5.1.6",
|
||||
"vitest": "^1.2.2"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
figma-config.json
|
||||
plugin.js
|
||||
Loaded 100 of 266 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user