Compare commits

..
Author SHA1 Message Date
github-actions[bot]andgithub-actions[bot] 4714a5ff32 Version Packages (#3181)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-10-03 16:01:01 +02:00
Halvor HauganandKen ca1d9be88f List: Add indentation and remove aria attributes (#3184)
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
2024-10-03 15:06:06 +02:00
Halvor Haugan 2654da8b64 update-article-views: Use latest docker image + remove reduntant stuff in workflow (#3189) 2024-10-03 08:52:48 +02:00
Julian Nymark 8d6418a594 Referansesider: 💄 use correct tokens 2024-10-02 14:24:10 +02:00
Julian Nymark 8269091fb8 Referansesider: 💄 outline offset for focus 2024-10-02 14:15:06 +02:00
Julian Nymark fd82184057 Referansesider: 🎨 aktivitetsplan buttons & select 2024-10-02 14:11:31 +02:00
Julian Nymark 39f2cb8614 Referansesider: 🎨 aktivitetsplan main card 2024-10-02 13:57:07 +02:00
Julian Nymark 4bf298f363 Referansesider: 🎨 Tag to own component, flesh out cards for aktivitetsplan 2024-10-02 12:55:54 +02:00
Halvor HauganandKen 3a239e4e17 [aksel.nav.no] Show breakpoints in GuidePanel examples (#3192)
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
2024-10-02 12:23:10 +02:00
Halvor Haugan 4fcfaf3deb [aksel.nav.no] Storybook: Change to Vite framework (#3185) 2024-10-02 10:14:17 +02:00
Julian Nymark 5dbc66daaa Referansesider: 🚧 Aktivitetsplan 2024-10-02 09:53:17 +02:00
Halvor Haugan 555a46b5da Adjust examples of Switch (#3188) 2024-10-01 13:55:03 +02:00
Julian Nymark 73b2bca156 Combobox: 💄 tweak the visibility of chevron (#3187)
* Combobox: 💄 tweak the visibility of chevron

* Combobox: 💄 dim visibility of icons for readonly datepicker, select, and combobox

* Combobox: 💄 dim visibility of icons for readonly datepicker, select, and combobox

* changeset
2024-10-01 13:47:53 +02:00
Julian Nymark 01411dc11f referansesider: 💄 sykepenger++ 2024-10-01 12:02:38 +02:00
Julian Nymark b1b0d23863 Referansesider: 💄 use more tokens 2024-09-30 14:56:17 +02:00
Julian Nymark cf59ce81bf Combobox disabled fixes (#3183)
* Combobox: 💄 remove hover border color when disabled

* Combobox: 💄 remove hover effect on chips when disabled
2024-09-30 13:53:48 +02:00
Julian Nymark 00c0ff31e1 Combobox: 💄 remove hover border color when disabled (#3182) 2024-09-30 13:38:22 +02:00
Julian Nymark d278060e59 Combobox: 🐛 implement Readonly + disabled (#3180)
* 🐛 [Combobox] implement disabled

* 💄 [Combobox] disabled

* ✨ [Combobox] implement readOnly

* 📝 [Combobox] add examples

* 📝 [Combobox] changeset

* 📝 [Combobox] changeset++

* 📝 [Combobox] stories (review fixes)

* 📝 [Combobox] stories (review fixes++)

* Combobox: 🎨 remove ToggleListButton when readonly or disabled

* Combobox: ♿ combobox input does not support native readonly, use nativeReadOnly={false}

* Revert "Combobox: 🎨 remove ToggleListButton when readonly or disabled"

This reverts commit e54a2eca32.

* Combobox: remove unused code
2024-09-30 11:24:47 +02:00
Ken e5d5b67262 [Referansesider] Migrated to new tokens 2024-09-27 14:13:54 +02:00
Ken 246f25cbaa misc: Update darkmode config for tokens 2024-09-27 14:01:51 +02:00
Ken b2a22438ad bug: Fixed CDN-location for figma config 2024-09-27 12:58:30 +02:00
KenandHalvor Haugan 8b8b724e40 [Darkside] Generate Token config to be consumed by Figma plugin (#3172)
* feat: Build figma config

* bug: Fix ignored gitignore config

* refactor: Rename collection -> name key

* feat: pre-update value to figma config

* feat: Re-resolve name with grouping

* feat: Better handling of relative units

* feat: Added better typings, workflow file

* feat: Updated name-parsing for pregen of config

* feat: Removed Unused eslint-disable directive

* feat: Extract figma scope when creating variables

* refactor: Simplified code by extracting logic

* feat: Updated workflow naming

* bug: re-add missing group for semantic color

* feat: Add metadata to figma config

* bug: Added missing 'contrast declaration

* bug: Calculate correct radius values

* refactor: Simplified config structure by unwrapping scopes

* refactor: Extracted logic for group check

* refactor: Only allow string or number as figma value in config

* feat: Added tests for value conversions

* refactor: Renamed figma config file

* update: Lock update

* refactor: Avoid using format as a workaround to extract token list

* Update @navikt/core/tokens/darkside/figma/index.ts

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

* Update @navikt/core/tokens/darkside/figma/create-tokens.ts

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

* Update @navikt/core/tokens/darkside/util.ts

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

* refactor: inline filter calls

* refactor: Better typing for as group declaration

* refactor: Avoid using objects as type definintions

* refactor: Inline semantic token check for roles

* refactor: date -> timestamp and NO locale

* refactor: token -> tokens

* refactor: removed redundant redaclaration of token name in test

* refactor: Avoid extra tests for same check

---------

Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
2024-09-27 12:35:42 +02:00
Ken 9f9bdcc645 bug: Fixed old test for global tokens 2024-09-26 19:13:38 +02:00
Ken 82bdf8075a [Tokens]: Update darkmode tokens and separate config support
feat: Updated darkmode tokens
feat: Split global color config into separate files
2024-09-26 19:09:01 +02:00
Julian Nymark 410e543825 🎨 [referansesider] minside eslint 2024-09-26 15:47:29 +02:00
Julian Nymark 40308e0e77 🎨 [referansesider] minside dokumentarkiv 2024-09-26 15:39:02 +02:00
Julian Nymark fff309b573 💄 [referansesider] minside NotificationsPanel lightmode match figma 2024-09-26 14:44:44 +02:00
Julian Nymark fe7c2de8fa 💄 [referansesider] minside PassiveCards++ 2024-09-26 14:37:42 +02:00
Julian Nymark 6fedea0ba4 💄 [referansesider] minside PassiveCards 2024-09-25 15:02:05 +02:00
Halvor HauganandKen 9811327509 [aksel.nav.no] Adjust examples of Switch (#3175)
---------

Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
2024-09-25 14:21:19 +02:00
Ken cf2034f914 [Aksel.nav.no] Open all Accordions when searched (#3179)
* feat: Open all Accordions when searched

* refactor: Type node as possibly undefined
2024-09-25 14:19:15 +02:00
dependabot[bot] 9e5e7f8fdc build(deps): Bump rollup from 3.29.4 to 3.29.5 (#3178)
Bumps [rollup](https://github.com/rollup/rollup) from 3.29.4 to 3.29.5.
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v3.29.4...v3.29.5)

---
updated-dependencies:
- dependency-name: rollup
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-25 13:49:55 +02:00
Ken e9dc171d74 Referansesider: Dynamic route-list generation for header and homepage 2024-09-25 13:25:43 +02:00
Julian Nymark 544b80eeae 💄 [referansesider] Minside Cards 2024-09-25 13:17:10 +02:00
Julian Nymark 4358b53095 🎨 [referansesider] move Styled components to top layer++ 2024-09-25 12:07:57 +02:00
Julian Nymark de57099b09 🎨 [referansesider] move Styled components to top layer 2024-09-25 11:59:57 +02:00
Ken 5a97cb422d feat: Add cache-control to index.html metadata referansesider 2024-09-25 11:16:03 +02:00
Ken 67fb3bb3a1 feat: Added theme switcher for referansesider 2024-09-25 10:52:03 +02:00
Ken 7bdd5cb748 bug: Fixes darkmode cascading for darkside tokens 2024-09-25 10:10:24 +02:00
github-actions[bot]andgithub-actions[bot] 62bcab1ab0 Version Packages (#3170)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-25 09:36:07 +02:00
Halvor Haugan 61e60d9556 Modify changeset text for Accordion change 2024-09-24 17:17:18 +02:00
Halvor Haugan 9c1eba4450 Accordion: Remove position:relative to fix popover clipping 2024-09-24 16:53:46 +02:00
Julian Nymark 63986d9872 💄 [referansesider] remove overflow 2024-09-24 16:43:04 +02:00
Ken 761c8ef64f refactor: Update tokens use in Piktogram for sykepenger page 2024-09-24 16:31:30 +02:00
Ken 4b86b555d8 feat: Referansesider now properly handles nested routing 2024-09-24 16:10:58 +02:00
Julian Nymark 8e1e294fda 💄 [referansesider] minside add icons 2024-09-24 15:51:55 +02:00
Halvor Haugan f71594e477 ErrorSummary: Remove aria-attributes. (#3171) 2024-09-24 15:41:49 +02:00
Julian Nymark d2a89851cd 💄 [referansesider] responsive design fix 2024-09-24 15:31:25 +02:00
Ken d27da71304 feat: Refactored to use more tokens in referansesider 2024-09-24 15:26:19 +02:00
Julian Nymark fcf17fc750 💄 [referansesider] minside card CSS 2024-09-24 15:24:41 +02:00
Julian Nymark e1b091358b 🎨 [referansesider] add content to minside 2024-09-24 15:15:15 +02:00
dependabot[bot] 988ad39ad1 build(deps): Bump rollup in /examples/referansesider (#3173)
Bumps [rollup](https://github.com/rollup/rollup) from 4.21.3 to 4.22.4.
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.21.3...v4.22.4)

---
updated-dependencies:
- dependency-name: rollup
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-24 14:00:52 +02:00
Ken 524c87e3bd feat: Update referansesider header fontsize 2024-09-24 13:54:48 +02:00
Ken 8cf6123656 feat: Created /komponenter route for component-testing, refactored button-component
feat: Use func for sykepenger function
2024-09-24 13:32:24 +02:00
Julian Nymark 866e6a168c 💄 [referansesider] make sykepenger more responsive 2024-09-24 13:16:47 +02:00
Ken bc2be15c55 Update referansesider
- feat: Refactor to use aksel-icons package
- refactor: use google import for font instead of inline files
2024-09-24 13:06:55 +02:00
Julian Nymark 4e6fc073fe 🐛 [referansesider] only have tanstack router devtools in dev mode 2024-09-24 12:59:56 +02:00
Ken e895c691ce [Referansesider] SPA deploy to aksel-referansesider.ansatt.dev.nav.no (#3174)
* bug: Fix build-script

* feat: Workflow for SPA deploy of referansesider

* feat: Add workflow dispatch to SPA deploy
2024-09-24 12:40:08 +02:00
Halvor Haugan 186c0fec07 🆙 Upgrade some packages (#3165) 2024-09-23 17:24:04 +02:00
Ken 746a58b663 feat: Updated darkside tokens with figma 2024-09-23 16:30:13 +02:00
Julian Nymark d767aadde3 🎨 [referansesider] add crude dekoratør 2024-09-23 14:02:30 +02:00
KenandHalvor Haugan 4359428b40 [Pagination] Deprecated page-prop, synced design with Figma (#3164)
* feat: Mark page-prop as deprecated in pagination

* feat: Mark page-prop as deprecated in pagination with better comment

* feat: Synced pagination with Figma

* memo: Changeset

* memo: Add classes to deprecationlist in stylelint config

* memo: changeset for stylelint

* memo: changeset for stylelint

* Update @navikt/aksel-stylelint/src/deprecations.ts

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

* Update .changeset/thirty-snakes-doubt.md

---------

Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
2024-09-23 12:28:52 +02:00
Halvor Haugan d583276428 Combobox: 🏷️ Better typing for maxSelected prop (#3169) 2024-09-23 10:07:06 +02:00
139 changed files with 8387 additions and 9202 deletions

No files matched your search

+5 -2
View File
@@ -73,8 +73,11 @@ module.exports = {
},
{
files: ["**/*.test.*", "**/__tests__/*"],
plugins: ["vitest"],
extends: ["plugin:vitest/recommended", "plugin:testing-library/react"],
plugins: ["@vitest"],
extends: [
"plugin:@vitest/legacy-recommended",
"plugin:testing-library/react",
],
},
{
+49
View File
@@ -0,0 +1,49 @@
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
- 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: navikt/frontend/actions/spa-deploy/v2@main
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:
+4
View File
@@ -1,5 +1,9 @@
# @navikt/aksel-icons
## 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";
@@ -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"
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/aksel-icons",
"version": "7.0.1",
"version": "7.1.1",
"author": "Aksel | NAV",
"homepage": "https://aksel.nav.no/ikoner",
"repository": {
+12
View File
@@ -1,5 +1,17 @@
# @navikt/aksel-stylelint
## 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
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/aksel-stylelint",
"version": "7.0.1",
"version": "7.1.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.1.1",
"@navikt/ds-tokens": "^7.1.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",
},
];
+14
View File
@@ -1,5 +1,19 @@
# @navikt/aksel
## 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
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/aksel",
"version": "7.0.1",
"version": "7.1.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.1.1",
"axios": "1.7.4",
"chalk": "4.1.0",
"clipboardy": "^2.3.0",
+1
View File
@@ -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";
+16
View File
@@ -1,5 +1,21 @@
# @navikt/ds-css
## 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
-4
View File
@@ -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 *
*************************/
+1
View File
@@ -281,6 +281,7 @@
/* Readonly */
.navds-date__field--readonly .navds-date__field-button {
cursor: default;
color: var(--a-gray-500);
}
.navds-date__caption-button {
+30
View File
@@ -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 {
+4
View File
@@ -109,3 +109,7 @@
border-color: var(--a-border-subtle);
cursor: default;
}
.navds-select--readonly .navds-select__chevron {
color: var(--a-gray-500);
}
+59 -71
View File
@@ -4,92 +4,80 @@
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;
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)));
}
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-css",
"version": "7.0.1",
"version": "7.1.1",
"description": "CSS for NAV Designsystem",
"author": "Aksel | NAV designsystem team",
"keywords": [
@@ -27,7 +27,7 @@
"css:get-version": "node config/get-version.js"
},
"devDependencies": {
"@navikt/ds-tokens": "^7.0.1",
"@navikt/ds-tokens": "^7.1.1",
"cssnano": "6.0.0",
"fast-glob": "3.2.11",
"lodash": "4.17.21",
+28
View File
@@ -1,5 +1,33 @@
# @navikt/ds-react
## 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
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-react",
"version": "7.0.1",
"version": "7.1.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",
@@ -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.1.1",
"@navikt/ds-tokens": "^7.1.1",
"clsx": "^2.1.0",
"date-fns": "^3.0.0",
"react-day-picker": "8.10.0"
@@ -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();
@@ -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,
@@ -63,6 +64,7 @@ const ComboboxProvider = forwardRef<HTMLInputElement, ComboboxProps>(
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}
@@ -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,6 +137,9 @@ const FilteredOptionsProvider = ({
const toggleIsListOpen = useCallback(
(newState?: boolean) => {
if (disabled || readOnly) {
return;
}
virtualFocus.moveFocusToTop();
if (newState ?? !isInternalListOpen) {
setHideCaret(!!maxSelected?.isLimitReached);
@@ -147,6 +151,8 @@ const FilteredOptionsProvider = ({
maxSelected?.isLimitReached,
isInternalListOpen,
setHideCaret,
disabled,
readOnly,
],
);
@@ -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,
@@ -49,6 +49,7 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
setValue,
hideCaret,
setHideCaret,
readOnly,
} = useInputContext();
const {
selectedOptions,
@@ -149,6 +150,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 =
@@ -214,6 +218,7 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
setValue,
searchTerm,
shouldShowSelectedOptions,
readOnly,
],
);
@@ -262,6 +267,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,
@@ -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>
);
};
+10 -12
View File
@@ -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;
@@ -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;
@@ -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;
+15 -25
View File
@@ -1,15 +1,18 @@
import cl from "clsx";
import React, { forwardRef, useContext } from "react";
import { BodyShort, Heading } from "../typography";
import { useId } from "../util/hooks";
import { BodyLong, BodyShort, Heading } 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";
export interface ListComponent
extends React.ForwardRefExoticComponent<
ListProps & React.RefAttributes<HTMLDivElement>
> {
/**
* @see 🏷️ {@link ListItemProps}
*/
Item: typeof ListItem;
}
@@ -42,47 +45,34 @@ 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={size}
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}
>
{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>
);
},
+6 -6
View File
@@ -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 -4
View File
@@ -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",
});
+124 -95
View File
@@ -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"] },
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,7 +133,7 @@ export const WithHeading = {
},
};
export const SizesUl = {
export const SizesUl: Story = {
render: () => {
return (
<VStack gap="8">
@@ -147,7 +172,7 @@ export const SizesUl = {
},
};
export const SizesOl = {
export const SizesOl: Story = {
render: () => {
return (
<VStack gap="8">
@@ -188,7 +213,7 @@ export const SizesOl = {
},
};
export const SizesIcons = {
export const SizesIcons: Story = {
render: () => {
return (
<VStack gap="8">
@@ -239,7 +264,7 @@ export const SizesIcons = {
},
};
export const Icons = {
export const Icons: Story = {
render: () => {
return (
<List title="Best title">
@@ -266,36 +291,40 @@ 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 (
<>
@@ -359,43 +388,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>
),
+1 -1
View File
@@ -19,7 +19,7 @@ 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";
@@ -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}
@@ -40,8 +40,8 @@ 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;
+4
View File
@@ -1,5 +1,9 @@
# @navikt/ds-tailwind
## 7.1.1
## 7.1.0
## 7.0.1
## 7.0.0
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-tailwind",
"version": "7.0.1",
"version": "7.1.1",
"description": "Tailwind config based on @navikt/ds-tokens",
"author": "Aksel | NAV designsystem team",
"keywords": [
@@ -25,7 +25,7 @@
"directory": "@navikt/core/tailwind"
},
"devDependencies": {
"@navikt/ds-tokens": "^7.0.1",
"@navikt/ds-tokens": "^7.1.1",
"color": "4.2.3",
"lodash": "^4.17.21",
"tailwindcss": "^3.3.3",
+1
View File
@@ -0,0 +1 @@
figma-config.json
+4
View File
@@ -1,5 +1,9 @@
# @navikt/ds-tokens
## 7.1.1
## 7.1.0
## 7.0.1
## 7.0.0
@@ -36,7 +36,7 @@ export const lightModeTokens = () => {
mergeConfigs([
semanticTokensForAllRolesConfig(),
contrastTokenConfig(),
semanticTokenConfig(),
semanticTokenConfig("light"),
neutralTokenConfig("light"),
globalScale("light"),
]),
@@ -49,7 +49,13 @@ export const lightModeTokens = () => {
*/
export const darkModeTokens = () => {
return tokensWithPrefix(
mergeConfigs([globalScale("dark"), neutralTokenConfig("dark")]),
mergeConfigs([
semanticTokensForAllRolesConfig(),
contrastTokenConfig(),
semanticTokenConfig("dark"),
neutralTokenConfig("dark"),
globalScale("dark"),
]),
);
};
@@ -0,0 +1,235 @@
import lodash from "lodash";
import StyleDictionary from "style-dictionary";
import { Dictionary, TransformedToken } from "style-dictionary/types";
import { createPropertyFormatter, getReferences } from "style-dictionary/utils";
import {
darkModeTokens,
lightModeTokens,
scaleTokens,
} from "../create-configuration";
import {
type SemanticTokenGroups,
type StyleDictionaryToken,
type TokenTypes,
} from "../util";
import type { FigmaToken } from "./figma-config.types";
const config = {
light: lightModeTokens(),
dark: darkModeTokens(),
scale: scaleTokens(),
};
const createStyleDictionaryForCollection = (
collection: keyof typeof config,
) => {
return new StyleDictionary({
tokens: config[collection],
platforms: {
[collection]: {
transformGroup: "css",
files: [],
},
},
});
};
export const getTokensForCollection = async (
collection: keyof typeof config,
) => {
const SD = createStyleDictionaryForCollection(collection);
await SD.hasInitialized;
const dictionary = await SD.getPlatformTokens(collection);
return dictionary.allTokens.map((token) => {
const reference = getReferences(token.original, dictionary.tokens);
/*
* Currently only supports 1 level of references.
*/
return reference.length > 0
? prepareToken(
{ ...token, alias: createTokenName(reference[0]) },
dictionary,
)
: prepareToken(token, dictionary);
});
};
function prepareToken(
token: TransformedToken,
dictionary: Dictionary,
): FigmaToken {
const formatter = createPropertyFormatter({
dictionary,
format: "css",
});
const cssVariable = formatter(token);
return {
name: createTokenName(token),
/* We can assume this since each config is typed with 'StyleDictionaryToken' */
type: token.type as TokenTypes,
value: figmaValue(token),
alias: token.alias,
comment: token.comment,
group: token.group,
code: {
web: `var(${cssVariable.trim().split(": ")[0]})`,
},
...figmaSettings(token),
};
}
/**
* @see https://www.figma.com/plugin-docs/api/VariableValue
*
* We do not need to handle the 'color' type since we can use Figma built-in for this
* @see https://www.figma.com/plugin-docs/api/properties/figma-util-rgba
*/
export function figmaValue(token: TransformedToken): string | number {
if (isRadiusToken(token) || isSpacingToken(token)) {
const float = parseFloat(token.value.replace("px", "").replace("rem", ""));
/*
* Figma does not support relative units, so we need to convert rem to px.
*/
if (token.value.includes("rem")) {
return float * 16;
}
return float;
}
return token.value;
}
/**
* Scopes allows us to define where in Figma the token can be used.
* @see https://www.figma.com/plugin-docs/api/VariableScope
*/
function figmaSettings(token: TransformedToken): {
figmaType: VariableResolvedDataType;
scopes: VariableScope[];
} {
if (isGlobalColor(token)) {
return createFigmaSettings("COLOR", ["ALL_FILLS", "STROKE_COLOR"]);
}
if (isTokenOfSemanticColorGroup(token, "background")) {
return createFigmaSettings("COLOR", ["FRAME_FILL", "SHAPE_FILL"]);
}
if (isTokenOfSemanticColorGroup(token, "border")) {
return createFigmaSettings("COLOR", ["STROKE_COLOR"]);
}
if (
isTokenOfSemanticColorGroup(token, "contrast") ||
isTokenOfSemanticColorGroup(token, "text")
) {
return createFigmaSettings("COLOR", ["SHAPE_FILL", "TEXT_FILL"]);
}
if (isRadiusToken(token)) {
return createFigmaSettings("FLOAT", ["CORNER_RADIUS"]);
}
if (isSpacingToken(token)) {
return createFigmaSettings("FLOAT", ["GAP"]);
}
console.warn(`No fitting type or scope found for token: ${token.name}`);
return createFigmaSettings("STRING", []);
}
function createFigmaSettings(
figmaType: VariableResolvedDataType,
scopes: VariableScope[],
): {
figmaType: VariableResolvedDataType;
scopes: VariableScope[];
} {
return { figmaType, scopes };
}
/**
* Allows us to extract the token name from the token object and create the correct grouping for Figma.
*/
export function createTokenName(token: TransformedToken) {
if (!token.attributes?.item || typeof token.attributes.item !== "string") {
throw new Error(`No item attribute found on token: ${token.name}`);
}
/*
* Because of the `StyleDictionaryTokenConfig`-type, we can assume that attributes.item will always exists.
*/
let name = lodash.startCase(token.attributes.item);
if (token.group) {
name = createGroupName(token.group) + name;
}
/*
* In the case we have a token "Text/Accent/Accent", we want to add a suffix to the name.
*/
const nameKeys = new Set(name.split("/"));
if (nameKeys.size !== name.split("/").length) {
name += " Default";
}
/*
* For pure value tokens, we want to add a prefix to the name to make it more readable in Figma.
*/
if (isRadiusToken(token)) {
return "Radius " + name;
}
if (isSpacingToken(token)) {
return "Spacing " + name;
}
return name;
}
/**
* By splitting the group by ".", we can create a more readable group name in Figma.
* @param group "background.primary"
* @returns "Background/Primary/"
* @see https://help.figma.com/hc/en-us/articles/14506821864087-Overview-of-variables-collections-and-modes#h_01H9V3QSVH2T1EYNXP7RNXZ8MV
*/
function createGroupName(group: string): string {
return (
group
.split(".")
.map((g) => lodash.startCase(g))
.join("/") + "/"
);
}
function isTokenOfSemanticColorGroup(
token: TransformedToken | FigmaToken,
groupType: SemanticTokenGroups,
): boolean {
const group = token.group as StyleDictionaryToken<TokenTypes>["group"];
if (!isSemanticColor(token)) {
return false;
}
return group?.includes(groupType) ?? false;
}
export function isGlobalColor(token: TransformedToken | FigmaToken): boolean {
const type = token.type as TokenTypes;
return type === "global-color";
}
export function isSemanticColor(token: TransformedToken | FigmaToken): boolean {
const type = token.type as TokenTypes;
return type === "color";
}
export function isRadiusToken(token: TransformedToken | FigmaToken): boolean {
const type = token.type as TokenTypes;
return type === "global-radius";
}
export function isSpacingToken(token: TransformedToken | FigmaToken): boolean {
const type = token.type as TokenTypes;
return type === "global-spacing";
}
@@ -0,0 +1,28 @@
import { StyleDictionaryToken, TokenTypes } from "../util";
export type FigmaToken = Omit<StyleDictionaryToken<TokenTypes>, "value"> & {
name: string;
alias?: string;
code: {
web: string;
};
value: string | number;
figmaType: VariableResolvedDataType;
scopes: VariableScope[];
};
type FigmaConfigEntry = {
name: string;
hideFromPublishing: boolean;
tokens: FigmaToken[];
};
export type FigmaTokenConfig = {
version: string;
timestamp: string;
globalLight: FigmaConfigEntry;
globalDark: FigmaConfigEntry;
semanticColors: FigmaConfigEntry;
radius: FigmaConfigEntry;
spacing: FigmaConfigEntry;
};
@@ -0,0 +1,63 @@
import fs from "fs";
import { version } from "../../package.json";
import {
getTokensForCollection,
isGlobalColor,
isRadiusToken,
isSemanticColor,
isSpacingToken,
} from "./create-tokens";
import { FigmaTokenConfig } from "./figma-config.types";
buildFigmaConfig();
/**
* Builds a configuration JSON that can be used by Figma-plugins to
* generate 'variables' in a Figma-file based on our tokens.
*/
async function buildFigmaConfig() {
try {
const lightTokens = await getTokensForCollection("light");
const darkTokens = await getTokensForCollection("dark");
const scaleTokens = await getTokensForCollection("scale");
const preparedConfig: FigmaTokenConfig = {
version,
timestamp: new Date().toLocaleString("no-NO", {
timeZone: "Europe/Oslo",
}),
globalLight: {
name: "Global colors light",
hideFromPublishing: true,
tokens: lightTokens.filter(isGlobalColor),
},
globalDark: {
name: "Global colors dark",
hideFromPublishing: true,
tokens: darkTokens.filter(isGlobalColor),
},
semanticColors: {
name: "Semantic colors",
hideFromPublishing: false,
tokens: lightTokens.filter(isSemanticColor),
},
radius: {
name: "Radius",
hideFromPublishing: false,
tokens: scaleTokens.filter(isRadiusToken),
},
spacing: {
name: "Spacing",
hideFromPublishing: false,
tokens: scaleTokens.filter(isSpacingToken),
},
};
fs.writeFileSync(
"./figma-config.json",
JSON.stringify(preparedConfig, null, 2),
);
} catch (error) {
console.error("Error building Figma config:", error);
}
}
+21 -120
View File
@@ -1,126 +1,15 @@
import {
BackgroundColor,
Color,
ContrastColor,
CssColor,
Theme,
} from "@adobe/leonardo-contrast-colors";
import { ContrastColor, CssColor } from "@adobe/leonardo-contrast-colors";
import { globalDarkThemeConfig } from "./tokens/global-dark";
import { globalLightThemeConfig } from "./tokens/global-light";
import {
ColorTheme,
GlobalColorRoles,
GlobalColorScale,
GlobalColorVariable,
globalColorRoles,
globalColorScales,
} from "./util";
const background = new BackgroundColor({
name: "white",
colorKeys: ["#fff"],
ratios: [1],
});
export const globalColorConfig: Record<
GlobalColorRoles,
{ colorKeys: CssColor[]; ratios: number[]; smooth: boolean }
> = {
/* Core */
neutral: {
colorKeys: ["#838c9a"],
ratios: [1.08, 1.16, 1.28, 1.5, 3.5, 4.19, 4.89, 5.75, 7.2, 15],
smooth: false,
},
accent: {
colorKeys: ["#0090ff"],
ratios: [1.08, 1.16, 1.28, 1.5, 3.5, 4.19, 4.89, 5.75, 7.2, 15],
smooth: false,
},
/* Status */
success: {
colorKeys: ["#2aa77d", "#e6ffeb"],
ratios: [1.08, 1.16, 1.28, 1.5, 3.5, 4.19, 4.89, 5.75, 7.2, 15],
smooth: false,
},
info: {
colorKeys: ["#ebfcff", "#66cbec", "#144852"],
ratios: [1.08, 1.16, 1.28, 1.5, 3.5, 4.19, 4.89, 5.75, 7.2, 15],
smooth: false,
},
warning: {
colorKeys: ["#ffeccc", "#ff9100", "#c77300", "#ffcc66"],
ratios: [1.08, 1.16, 1.28, 1.5, 3.5, 4.19, 4.89, 5.75, 7.2, 15],
smooth: true,
},
danger: {
colorKeys: ["#ffb3cd", "#fa4456"],
ratios: [1.08, 1.16, 1.28, 1.5, 3.5, 4.19, 4.89, 5.75, 7.2, 15],
smooth: false,
},
/* Brand */
brandOne: {
colorKeys: ["#99185e", "#7a134b"],
ratios: [1.08, 1.16, 1.28, 1.5, 3.5, 4.19, 4.89, 5.75, 7.2, 15],
smooth: false,
},
brandTwo: {
colorKeys: ["#cc7566", "#fff3ec"],
ratios: [1.08, 1.16, 1.28, 1.5, 3.5, 4.19, 4.89, 5.75, 7.2, 15],
smooth: true,
},
brandThree: {
colorKeys: ["#005b82", "#e6f1f8", "#00243a"],
ratios: [1.08, 1.16, 1.28, 1.5, 3.5, 4.19, 4.89, 5.75, 7.2, 15],
smooth: true,
},
/* Data */
dataOne: {
colorKeys: ["#8269a2"],
ratios: [1.08, 1.16, 1.28, 1.5, 3.5, 4.19, 4.89, 5.75, 7.2, 15],
smooth: false,
},
dataTwo: {
colorKeys: ["#d9e61e"],
ratios: [1.08, 1.16, 1.28, 1.5, 3.5, 4.19, 4.89, 5.75, 7.2, 15],
smooth: false,
},
dataThree: {
colorKeys: ["#ffe45c"],
ratios: [1.08, 1.16, 1.28, 1.5, 3.5, 4.19, 4.89, 5.75, 7.2, 15],
smooth: false,
},
};
// Convert globalColorConfig to Color instances
const colorInstances = Object.entries(globalColorConfig).map(
([role, config]) =>
new Color({
name: role,
colorKeys: config.colorKeys,
ratios: config.ratios,
colorspace: "OKLCH",
smooth: config.smooth,
}),
);
const leonardoLightTheme = new Theme({
colors: colorInstances,
backgroundColor: background,
lightness: 100,
contrast: 1,
saturation: 100,
output: "HEX",
formula: "wcag2",
}).contrastColors;
const leonardoDarkTheme = new Theme({
colors: colorInstances,
backgroundColor: background,
lightness: 0,
contrast: 1,
saturation: 100,
output: "HEX",
formula: "wcag2",
}).contrastColors;
const getColorScaleFromLeonardo = (
role: GlobalColorRoles,
scale: GlobalColorScale,
@@ -128,7 +17,7 @@ const getColorScaleFromLeonardo = (
): CssColor => {
/* We have to remove 'ContrastColorBackground' from first position */
const leonardoTheme = (
theme === "light" ? leonardoLightTheme : leonardoDarkTheme
theme === "light" ? globalLightThemeConfig : globalDarkThemeConfig
).slice(1) as ContrastColor[];
const colorContrastRole = leonardoTheme.find((c) => c.name === role);
@@ -140,10 +29,10 @@ const getColorScaleFromLeonardo = (
/**
* @example colorContrastRole format
* {
* name: 'dataThree',
* name: 'dataTwo',
* values: [
* { name: 'dataThree100', contrast: 1.08, value: '#200500' },
* { name: 'dataThree200', contrast: 1.16, value: '#2b0f00' },
* { name: 'dataTwo100', contrast: 1.08, value: '#200500' },
* { name: 'dataTwo200', contrast: 1.16, value: '#2b0f00' },
* ...
* ]
*}
@@ -181,3 +70,15 @@ export const getGlobalScaleForColor = (
{} as Record<GlobalColorScale, GlobalColor>,
);
};
export const globalScale = (
theme: ColorTheme,
): Record<string, GlobalColorVariable> => {
return globalColorRoles.reduce(
(acc, role) => {
acc[role] = getGlobalScaleForColor(role, theme);
return acc;
},
{} as Record<string, GlobalColorVariable>,
);
};
@@ -0,0 +1,89 @@
import { TransformedToken } from "style-dictionary/types";
import { describe, expect, test } from "vitest";
import { createTokenName, figmaValue } from "../figma/create-tokens";
const baseToken: TransformedToken = {
name: "tokenName",
attributes: { item: "primary" },
path: ["color", "background"],
original: { value: "#000000" },
filePath: "path/to/file.css",
isSource: true,
};
describe("createTokenName", () => {
test("should extract name from token with item attribute", () => {
const token: TransformedToken = baseToken;
expect(createTokenName({ ...token, attributes: { item: "primary" } })).toBe(
"Primary",
);
expect(
createTokenName({ ...token, attributes: { item: "bg-strong-hover" } }),
).toBe("Bg Strong Hover");
});
test("should create correct grouping for token with group attribute", () => {
const token: TransformedToken = {
...baseToken,
attributes: { item: "primary" },
group: "color.background",
};
expect(createTokenName(token)).toBe("Color/Background/Primary");
});
test("should add 'Default' suffix for tokens with duplicate names", () => {
const token: TransformedToken = {
...baseToken,
attributes: { item: "accent" },
group: "text.accent",
};
expect(createTokenName(token)).toBe("Text/Accent/Accent Default");
});
test("should add 'Radius' prefix for global-radius type tokens", () => {
const token: TransformedToken = {
...baseToken,
attributes: { item: "small" },
type: "global-radius",
};
expect(createTokenName(token)).toBe("Radius Small");
});
test("should add 'Spacing' prefix for global-spacing type tokens", () => {
const token: TransformedToken = {
...baseToken,
attributes: { item: "4" },
type: "global-spacing",
};
expect(createTokenName(token)).toBe("Spacing 4");
});
});
describe("extracting figma value for token", () => {
test("should convert rem to px for global-radius type tokens", () => {
const token: TransformedToken = {
...baseToken,
value: "0.25rem",
type: "global-radius",
};
expect(figmaValue(token)).toBe(4);
});
test("should convert px to number for global-spacing type tokens", () => {
const token: TransformedToken = {
...baseToken,
value: "32px",
type: "global-spacing",
};
expect(figmaValue(token)).toBe(32);
});
test("should return the original value for non-radius and non-spacing type tokens", () => {
const token: TransformedToken = {
...baseToken,
type: "color",
value: "#111",
};
expect(figmaValue(token)).toBe("#111");
});
});
@@ -1,11 +1,23 @@
import { describe, expect, test } from "vitest";
import { globalColorConfig } from "../leonardo";
import { config as darkModeConfig } from "../tokens/global-dark";
import { config as lightModeConfig } from "../tokens/global-light";
describe("leonardo token setup", () => {
Object.entries(globalColorConfig).forEach(([role, { colorKeys, ratios }]) => {
test(`${role} should have correct colorKeys and ratios for each role`, () => {
expect(colorKeys.length).toBeGreaterThan(0);
expect(ratios.length).toEqual(10);
describe("Validate global token config", () => {
describe("Lightmode should have correct colorKeys and ratios for each role", () => {
Object.entries(lightModeConfig).forEach(([role, { colorKeys, ratios }]) => {
test(`${role} should have correct colorKeys and ratios for each role`, () => {
expect(colorKeys.length).toBeGreaterThan(0);
expect(ratios.length).toEqual(10);
});
});
});
describe("Darkmode should have correct colorKeys and ratios for each role", () => {
Object.entries(darkModeConfig).forEach(([role, { colorKeys, ratios }]) => {
test(`${role} should have correct colorKeys and ratios for each role`, () => {
expect(colorKeys.length).toBeGreaterThan(0);
expect(ratios.length).toEqual(10);
});
});
});
});
+11 -16
View File
@@ -8,65 +8,60 @@ export const contrastTokenConfig = (): {
contrast: {
/* core */
accent: {
value: "white",
value: "{a.neutral.000.value}",
type: "color",
group: "contrast",
},
neutral: {
value: "white",
value: "{a.neutral.000.value}",
type: "color",
group: "contrast",
},
/* Status */
danger: {
value: "white",
value: "{a.neutral.000.value}",
type: "color",
group: "contrast",
},
info: {
value: "white",
value: "{a.neutral.000.value}",
type: "color",
group: "contrast",
},
success: {
value: "white",
value: "{a.neutral.000.value}",
type: "color",
group: "contrast",
},
warning: {
value: "white",
value: "{a.neutral.000.value}",
type: "color",
group: "contrast",
},
/* Brand */
brandOne: {
value: "white",
value: "{a.neutral.000.value}",
type: "color",
group: "contrast",
},
brandTwo: {
value: "white",
value: "{a.neutral.000.value}",
type: "color",
group: "contrast",
},
brandThree: {
value: "white",
value: "{a.neutral.000.value}",
type: "color",
group: "contrast",
},
/* Data */
dataOne: {
value: "white",
type: "color",
group: "contrast",
},
dataThree: {
value: "white",
value: "{a.neutral.000.value}",
type: "color",
group: "contrast",
},
dataTwo: {
value: "white",
value: "{a.neutral.000.value}",
type: "color",
group: "contrast",
},
@@ -0,0 +1,98 @@
import { BackgroundColor, Color, Theme } from "@adobe/leonardo-contrast-colors";
import { GlobalColorConfig } from "../util";
export const config: GlobalColorConfig = {
/* Core */
neutral: {
colorKeys: ["#838c9a"],
ratios: [1.08, 1.16, 1.28, 1.5, 3.5, 4.5, 5.85, 6.6, 8, 15],
smooth: false,
},
accent: {
colorKeys: ["#e6f0ff", "#0067c5"],
ratios: [1.08, 1.16, 1.28, 1.5, 3.5, 4.5, 5.85, 6.6, 8, 15],
smooth: true,
},
/* Status */
success: {
colorKeys: ["#e6ffeb", "#2aa758"],
ratios: [1.08, 1.16, 1.28, 1.5, 3.5, 4.5, 5.85, 6.6, 8, 15],
smooth: false,
},
/**
* TODO: This is currently just a copy of brandThree
* - Should info still exist as a global color?
* - If not, should the semantic colors be generated for info based on brandThree?
*/
info: {
colorKeys: ["#005b82", "#e6f1f8", "#00243a"],
ratios: [1.08, 1.16, 1.28, 1.5, 3.5, 4.5, 5.85, 6.6, 8, 15],
smooth: true,
},
warning: {
colorKeys: ["#ffbe4d", "#f56e00", "#cc5200"],
ratios: [1.08, 1.16, 1.28, 1.5, 3.5, 4.5, 5.85, 6.6, 8, 15],
smooth: false,
},
danger: {
colorKeys: ["#ffb3cd", "#f93448"],
ratios: [1.08, 1.16, 1.28, 1.5, 3.5, 4.5, 5.85, 6.6, 8, 15],
smooth: false,
},
/* Brand */
brandOne: {
colorKeys: ["#99185e", "#7a134b"],
ratios: [1.08, 1.16, 1.28, 1.5, 3.5, 4.5, 5.85, 6.6, 8, 15],
smooth: false,
},
brandTwo: {
colorKeys: ["#fff3ec", "#cc8066"],
ratios: [1.08, 1.16, 1.28, 1.5, 3.5, 4.5, 5.85, 6.6, 8, 15],
smooth: false,
},
brandThree: {
colorKeys: ["#e6f1f8", "#005b82", "#00243a"],
ratios: [1.08, 1.16, 1.28, 1.5, 3.5, 4.5, 5.85, 6.6, 8, 15],
smooth: false,
},
/* Data */
dataOne: {
colorKeys: ["#8269a2"],
ratios: [1.08, 1.16, 1.28, 1.5, 3.5, 4.5, 5.85, 6.6, 8, 15],
smooth: false,
},
dataTwo: {
colorKeys: ["#d9e61e"],
ratios: [1.08, 1.16, 1.28, 1.5, 3.5, 4.5, 5.85, 6.6, 8, 15],
smooth: false,
},
};
const colors = Object.entries(config).map(
([role, color]) =>
new Color({
name: role,
colorKeys: color.colorKeys,
ratios: color.ratios,
colorspace: "OKLCH",
smooth: color.smooth,
}),
);
const background = new BackgroundColor({
name: "neutral",
colorKeys: config.neutral.colorKeys,
ratios: config.neutral.ratios,
colorspace: "OKLCH",
smooth: config.neutral.smooth,
});
export const globalDarkThemeConfig = new Theme({
colors,
backgroundColor: background,
lightness: 13,
contrast: 1,
saturation: 100,
output: "HEX",
formula: "wcag2",
}).contrastColors;
@@ -0,0 +1,96 @@
import { BackgroundColor, Color, Theme } from "@adobe/leonardo-contrast-colors";
import { GlobalColorConfig } from "../util";
export const config: GlobalColorConfig = {
/* Core */
neutral: {
colorKeys: ["#838c9a"],
ratios: [1.08, 1.16, 1.28, 1.5, 3.5, 4.5, 5.85, 6.6, 8, 15],
smooth: false,
},
accent: {
colorKeys: ["#e6f0ff", "#0067c5"],
ratios: [1.08, 1.16, 1.28, 1.5, 3.5, 4.5, 5.85, 6.6, 8, 15],
smooth: true,
},
/* Status */
success: {
colorKeys: ["#2aa758", "#e6ffeb"],
ratios: [1.08, 1.16, 1.28, 1.5, 3.5, 4.5, 5.85, 6.6, 8, 15],
smooth: false,
},
/**
* TODO: This is currently just a copy of brandThree
* - Should info still exist as a global color?
* - If not, should the semantic colors be generated for info based on brandThree?
*/
info: {
colorKeys: ["#005b82", "#e6f1f8", "#00243a"],
ratios: [1.08, 1.16, 1.28, 1.5, 3.5, 4.5, 5.85, 6.6, 8, 15],
smooth: true,
},
warning: {
colorKeys: ["#ffbe4d", "#f56e00", "#cc5200"],
ratios: [1.08, 1.16, 1.28, 1.5, 3.5, 4.5, 5.85, 6.6, 8, 15],
smooth: true,
},
danger: {
colorKeys: ["#f93448", "#ffb3cd"],
ratios: [1.08, 1.16, 1.28, 1.5, 3.5, 4.5, 5.85, 6.6, 8, 15],
smooth: false,
},
/* Brand */
brandOne: {
colorKeys: ["#99185e", "#7a134b"],
ratios: [1.08, 1.16, 1.28, 1.5, 3.5, 4.5, 5.85, 6.6, 8, 15],
smooth: false,
},
brandTwo: {
colorKeys: ["#cc8066", "#fff3ec"],
ratios: [1.08, 1.16, 1.28, 1.5, 3.5, 4.5, 5.85, 6.6, 8, 15],
smooth: true,
},
brandThree: {
colorKeys: ["#005b82", "#e6f1f8", "#00243a"],
ratios: [1.08, 1.16, 1.28, 1.5, 3.5, 4.5, 5.85, 6.6, 8, 15],
smooth: true,
},
/* Data */
dataOne: {
colorKeys: ["#8269a2"],
ratios: [1.08, 1.16, 1.28, 1.5, 3.5, 4.5, 5.85, 6.6, 8, 15],
smooth: false,
},
dataTwo: {
colorKeys: ["#d9e61e"],
ratios: [1.08, 1.16, 1.28, 1.5, 3.5, 4.5, 5.85, 6.6, 8, 15],
smooth: false,
},
};
const background = new BackgroundColor({
name: "white",
colorKeys: ["#fff"],
ratios: [1],
});
const colors = Object.entries(config).map(
([role, color]) =>
new Color({
name: role,
colorKeys: color.colorKeys,
ratios: color.ratios,
colorspace: "OKLCH",
smooth: color.smooth,
}),
);
export const globalLightThemeConfig = new Theme({
colors,
backgroundColor: background,
lightness: 100,
contrast: 1,
saturation: 100,
output: "HEX",
formula: "wcag2",
}).contrastColors;
+20 -16
View File
@@ -1,23 +1,27 @@
import { ColorTheme, StyleDictionaryTokenConfig } from "../util";
const NeutralScaleLight = (): StyleDictionaryTokenConfig<"global-color"> => ({
"000": {
value: "white",
type: "global-color",
group: "neutral",
const NeutralScaleLight = {
neutral: {
"000": {
value: "white",
type: "global-color",
group: "neutral",
},
},
});
} as const;
const NeutralScaleDark = (): StyleDictionaryTokenConfig<"global-color"> => ({
"000": {
value: "black",
type: "global-color",
group: "neutral",
const NeutralScaleDark = {
neutral: {
"000": {
value: "black",
type: "global-color",
group: "neutral",
},
},
});
} as const;
export const neutralTokenConfig = (theme: ColorTheme) => {
return {
neutral: theme === "light" ? NeutralScaleLight() : NeutralScaleDark(),
};
export const neutralTokenConfig = (
theme: ColorTheme,
): StyleDictionaryTokenConfig<"global-color"> => {
return theme === "light" ? NeutralScaleLight : NeutralScaleDark;
};
@@ -34,7 +34,7 @@ const configForRole = (
type: "color",
group: `background.${role}`,
},
[`${role}-moderate-active`]: {
[`${role}-moderate-pressed`]: {
value: `{a.${role}.400.value}`,
type: "color",
group: `background.${role}`,
@@ -46,17 +46,17 @@ const configForRole = (
group: `background.${role}`,
},
[`${role}-strong`]: {
value: `{a.${role}.700.value}`,
value: `{a.${role}.600.value}`,
type: "color",
group: `background.${role}`,
},
[`${role}-strong-hover`]: {
value: `{a.${role}.800.value}`,
value: `{a.${role}.700.value}`,
type: "color",
group: `background.${role}`,
},
[`${role}-strong-active`]: {
value: `{a.${role}.900.value}`,
[`${role}-strong-pressed`]: {
value: `{a.${role}.800.value}`,
type: "color",
group: `background.${role}`,
},
@@ -73,11 +73,11 @@ const configForRole = (
},
text: {
[role]: {
value: `{a.${role}.1000.value}`,
value: `{a.${role}.700.value}`,
type: "color",
group: `text.${role}`,
},
[`${role}-subtle`]: {
[`${role}-strong`]: {
value: `{a.${role}.900.value}`,
type: "color",
group: `text.${role}`,
+100 -71
View File
@@ -1,76 +1,105 @@
import { StyleDictionaryTokenConfig } from "../util";
import { ColorTheme, StyleDictionaryTokenConfig } from "../util";
export const semanticTokenConfig = (): StyleDictionaryTokenConfig<"color"> => ({
text: {
default: {
value: "{a.neutral.1000.value}",
type: "color",
group: "text",
export function semanticTokenConfig(theme: ColorTheme) {
return theme === "light" ? semanticTokensLight() : semanticTokensDark();
}
function semanticTokensDark(): StyleDictionaryTokenConfig<"color"> {
const baseline = semanticTokensLight();
return {
...baseline,
bg: {
...baseline.bg,
default: {
value: "#141b26",
type: "color",
group: "background",
},
raised: {
value: "{a.neutral.200.value}",
type: "color",
group: "background",
},
sunken: {
value: "#07090D",
type: "color",
group: "background",
},
},
subtle: {
value: "{a.neutral.900.value}",
type: "color",
group: "text",
};
}
function semanticTokensLight(): StyleDictionaryTokenConfig<"color"> {
return {
text: {
default: {
value: "{a.neutral.1000.value}",
type: "color",
group: "text",
},
subtle: {
value: "{a.neutral.900.value}",
type: "color",
group: "text",
},
icon: {
value: "{a.neutral.600.value}",
type: "color",
group: "text",
},
},
icon: {
value: "{a.neutral.600.value}",
type: "color",
group: "text",
bg: {
default: {
value: "{a.neutral.000.value}",
type: "color",
group: "background",
},
input: {
value: "{a.neutral.000.value}",
type: "color",
group: "background",
},
raised: {
value: "{a.neutral.000.value}",
type: "color",
group: "background",
},
sunken: {
value: "{a.neutral.200.value}",
type: "color",
group: "background",
},
overlay: {
value: "rgba(2, 20, 49 , 0.49)",
type: "color",
group: "background",
},
},
},
bg: {
default: {
value: "{a.neutral.000.value}",
type: "color",
group: "background",
border: {
default: {
value: "{a.neutral.500.value}",
type: "color",
group: "border",
},
subtle: {
value: "{a.neutral.400.value}",
type: "color",
group: "border",
},
strong: {
value: "{a.neutral.600.value}",
type: "color",
group: "border",
},
/**
* TODO: Need to verify this value
* - Discuss with brand
*/
focus: {
value: "{a.accent.700.value}",
type: "color",
group: "border",
},
},
input: {
value: "{a.neutral.000.value}",
type: "color",
group: "background",
},
raised: {
value: "{a.neutral.000.value}",
type: "color",
group: "background",
},
sunken: {
value: "{a.neutral.200.value}",
type: "color",
group: "background",
},
overlay: {
/* TODO: Update alpha here, consier not having the token itself? */
value: "rgb(2 20 49 / 0.49)",
type: "color",
group: "background",
},
},
border: {
default: {
value: "{a.neutral.500.value}",
type: "color",
group: "border",
},
subtle: {
value: "{a.neutral.400.value}",
type: "color",
group: "border",
},
strong: {
/* TODO: Consider 700 scale here? */
value: "{a.neutral.600.value}",
type: "color",
group: "border",
},
/**
* TODO: Need to verify this value
* - Discuss with brand
*/
focus: {
value: "{a.accent.700.value}",
type: "color",
group: "border",
},
},
});
};
}
+21 -4
View File
@@ -1,3 +1,4 @@
import { CssColor } from "@adobe/leonardo-contrast-colors";
import _ from "lodash";
export const colorThemes = ["light", "dark"] as const;
@@ -15,7 +16,6 @@ export const globalColorRoles = [
"brandThree",
"dataOne",
"dataTwo",
"dataThree",
] as const;
export type GlobalColorRoles = (typeof globalColorRoles)[number];
@@ -35,17 +35,34 @@ export const globalColorScales = [
export type GlobalColorScale = (typeof globalColorScales)[number];
type TokenTypes = "color" | "global-color" | "global-radius" | "global-spacing";
export type TokenTypes =
| "color"
| "global-color"
| "global-radius"
| "global-spacing";
export type SemanticTokenGroups = "background" | "border" | "text" | "contrast";
type SemanticTokenGroupsWithRoles = Exclude<SemanticTokenGroups, "contrast">;
export type TokenGroup =
| GlobalColorRoles
| SemanticTokenGroups
| `${SemanticTokenGroupsWithRoles}.${GlobalColorRoles}`;
export type GlobalColorConfig = Record<
GlobalColorRoles,
{ colorKeys: CssColor[]; ratios: number[]; smooth: boolean }
>;
export type StyleDictionaryToken<T extends TokenTypes> = {
value: string;
type: T;
group?: string;
group?: TokenGroup;
comment?: string;
};
export type StyleDictionaryTokenConfig<T extends TokenTypes> = {
[key: string]: StyleDictionaryToken<T> | StyleDictionaryTokenConfig<T>;
[key: string]: Record<string, StyleDictionaryToken<T>>;
};
export type GlobalColorVariable = Record<
+4 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-tokens",
"version": "7.0.1",
"version": "7.1.1",
"description": "Design-tokens for NAV designsystem",
"author": "Aksel | NAV designsystem team",
"keywords": [
@@ -19,7 +19,8 @@
],
"scripts": {
"build": "tsx ./config/build.ts > /dev/null && tsx ./config/version-tag.ts && yarn build:darkside",
"build:darkside": "tsx ./darkside",
"build:darkside": "tsx ./darkside && yarn build:figma-config",
"build:figma-config": "tsx ./darkside/figma",
"test": "vitest run",
"test:watch": "vitest watch"
},
@@ -30,6 +31,7 @@
},
"devDependencies": {
"@adobe/leonardo-contrast-colors": "^1.0.0",
"@figma/plugin-typings": "^1.100.2",
"color": "4.2.3",
"lightningcss": "^1.27.0",
"lodash": "^4.17.21",
+5 -2
View File
@@ -3,7 +3,10 @@
"jsx": "react",
"esModuleInterop": true,
"resolveJsonModule": true,
"strict": true
"strict": true,
"types": ["@figma/plugin-typings"],
"moduleResolution": "Bundler",
"module": "Preserve"
},
"include": ["./stories"]
"include": ["./stories", "./darkside/figma"]
}
+30
View File
@@ -1,5 +1,35 @@
# Changelog
## 7.1
### @navikt/aksel-stylelint
- List: :boom: Removed class names "navds-list--nested" and "navds-list\_\_item-content" ([#3184](https://github.com/navikt/aksel/pull/3184))
* Pagination: :boom: Added some classNames to list of deprecated (removed) classes. ([#3164](https://github.com/navikt/aksel/pull/3164))
### @navikt/ds-react
- 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))
* Pagination: :lipstick: Updated font-weight to 600 and marked 'page'-prop on 'Item' as deprecated. ([#3164](https://github.com/navikt/aksel/pull/3164))
- 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))
### @navikt/ds-css
- 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))
* 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
### @navikt/aksel-icons
+5 -5
View File
@@ -15,10 +15,10 @@
"playroom"
],
"dependencies": {
"@navikt/aksel-icons": "^7.0.1",
"@navikt/ds-css": "^7.0.1",
"@navikt/ds-react": "^7.0.1",
"@navikt/ds-tailwind": "^7.0.1",
"@navikt/ds-tokens": "^7.0.1"
"@navikt/aksel-icons": "^7.1.1",
"@navikt/ds-css": "^7.1.1",
"@navikt/ds-react": "^7.1.1",
"@navikt/ds-tailwind": "^7.1.1",
"@navikt/ds-tokens": "^7.1.1"
}
}
+2 -1
View File
@@ -1,4 +1,5 @@
public/sandbox
test-results/
tsconfig.tsbuildinfo
___temp
___temp
next-env.d.ts
+7 -15
View File
@@ -1,8 +1,7 @@
import { loadCsf } from "@storybook/csf-tools";
import type { StorybookConfig } from "@storybook/nextjs";
import type { StorybookConfig } from "@storybook/experimental-nextjs-vite";
import { readFileSync } from "fs";
import { resolve } from "path";
import TsconfigPathsPlugin from "tsconfig-paths-webpack-plugin";
import TsconfigPathsPlugin from "vite-tsconfig-paths";
const indexRegex = /export const args = {\s+index: (\d+),/;
@@ -60,20 +59,13 @@ const sbConfig: StorybookConfig = {
"@storybook/addon-interactions",
"@storybook/addon-a11y",
],
framework: "@storybook/nextjs",
framework: "@storybook/experimental-nextjs-vite",
docs: {
autodocs: "tag",
},
webpackFinal: (async (config) => {
if (!config?.resolve) {
return config;
}
config.resolve.plugins = [
new TsconfigPathsPlugin({
configFile: resolve(__dirname, "../tsconfig.json"),
}),
];
return config;
}) satisfies StorybookConfig["webpackFinal"],
viteFinal: async (config) => {
const { mergeConfig } = await import("vite");
return mergeConfig(config, { plugins: [TsconfigPathsPlugin()] });
},
};
export default sbConfig;
@@ -1,13 +1,35 @@
import { useEffect, useState } from "react";
import { Accordion as DsAccordion } from "@navikt/ds-react";
import ErrorBoundary from "@/error-boundary";
import { SanityBlockContent } from "@/sanity-block";
import { AccordionT } from "@/types";
type AccordionProps = {
node: AccordionT;
node?: AccordionT;
};
const Accordion = ({ node }: AccordionProps) => {
const [openAccordions, setOpenAccordions] = useState<number[]>([]);
useEffect(() => {
const listener = (event: KeyboardEvent) => {
if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === "f") {
setOpenAccordions((node?.list ?? []).map((_, i) => i));
}
};
document.addEventListener("keydown", listener);
return () => document.removeEventListener("keydown", listener);
}, [node?.list]);
const handleOpenChange = (index: number) => {
if (openAccordions.includes(index)) {
setOpenAccordions(openAccordions.filter((i) => i !== index));
} else {
setOpenAccordions([...openAccordions, index]);
}
};
if (!node || node.list.length === 0) {
return null;
}
@@ -15,8 +37,12 @@ const Accordion = ({ node }: AccordionProps) => {
return (
<div className="mb-8">
<DsAccordion>
{node.list.map((el) => (
<DsAccordion.Item key={el._key}>
{node.list.map((el, index) => (
<DsAccordion.Item
key={el._key}
open={openAccordions.includes(index)}
onOpenChange={() => handleOpenChange(index)}
>
<DsAccordion.Header>{el.title}</DsAccordion.Header>
<DsAccordion.Content>
<SanityBlockContent blocks={el.content} className="toc-ignore" />
@@ -1,4 +1,4 @@
import { getParameters } from "codesandbox/lib/api/define";
import { getParameters } from "codesandbox-import-utils/lib/api/define";
import { Button } from "@navikt/ds-react";
import { CodeSandboxLogo } from "@/assets/Icons";
@@ -7,7 +7,7 @@ import {
MonitorIcon,
TabletIcon,
} from "@navikt/aksel-icons";
import { HStack } from "@navikt/ds-react";
import { Box, HStack } from "@navikt/ds-react";
import styles from "./examples.module.css";
type withDsT = {
@@ -70,6 +70,9 @@ export const withDsExample = (
style={{ background: getBg(background) }}
>
<Icon aria-hidden fontSize="1.5rem" /> {`${breakpoint}`}
<Box marginInline="2 0" className="text-text-subtle">
{width}px
</Box>
</HStack>
);
};
@@ -38,7 +38,12 @@ type MenuListProps = {
export function MenuUl({ children, id, className }: MenuListProps) {
return (
<ul className={cl(styles.menuList, className)} id={id}>
// eslint-disable-next-line jsx-a11y/no-redundant-roles
<ul
className={cl(styles.menuList, className)}
id={id}
role="list" // WebKit browsers remove list semantics when list-style-type is none
>
{children}
</ul>
);
+5 -1
View File
@@ -8,7 +8,11 @@ test.describe("Axe a11y", () => {
await page.goto(`http://localhost:3000${url}`);
await page.waitForLoadState("domcontentloaded");
const accessibilityScanResults = await new AxeBuilder({ page })
.disableRules(["definition-list", "scrollable-region-focusable"])
.disableRules([
"definition-list",
"scrollable-region-focusable",
"landmark-complementary-is-top-level",
])
.exclude("iframe")
.exclude("#aksel-expansioncard")
.exclude("#toc-scroll")
-5
View File
@@ -1,5 +0,0 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
+2 -3
View File
@@ -47,13 +47,13 @@
"@slack/web-api": "^7.0.2",
"boring-avatars": "1.10.1",
"clsx": "^2.1.0",
"codesandbox": "^2.2.3",
"codesandbox-import-utils": "^2.2.3",
"date-fns": "^3.0.0",
"fuse.js": "^7.0.0",
"jscodeshift": "^0.15.1",
"lodash": "^4.17.21",
"lz-string": "^1.5.0",
"next": "^14.2.3",
"next": "^14.2.12",
"next-logger": "^4.0.0",
"next-sanity": "9.4.5",
"node-cache": "^5.1.2",
@@ -101,7 +101,6 @@
"showdown": "^2.1.0",
"start-server-and-test": "^2.0.3",
"ts-node": "^10.9.1",
"tsconfig-paths-webpack-plugin": "^4.1.0",
"typescript": "^5.1.6",
"vitest": "^1.2.2"
}
@@ -0,0 +1,46 @@
import { UNSAFE_Combobox } from "@navikt/ds-react";
import { withDsExample } from "@/web/examples/withDsExample";
const Example = () => {
const selectedOptions = ["napoleonskake", "donut"];
return (
<div>
<UNSAFE_Combobox
options={initialOptions}
label="Hva er dine favorittfrukter?"
description="Bare de fruktene som du spiser minst 5 av om dagen teller vi som en favorittfrukt."
selectedOptions={selectedOptions}
isMultiSelect
readOnly
/>
</div>
);
};
const initialOptions = [
"banana",
"apple",
"tangerine",
"pear",
"grape",
"kiwi",
"mango",
"passion fruit",
"pineapple",
"strawberry",
"watermelon",
"grape fruit",
];
// EXAMPLES DO NOT INCLUDE CONTENT BELOW THIS LINE
export default withDsExample(Example, { variant: "static" });
/* Storybook story */
export const Demo = {
render: Example,
};
export const args = {
index: 9,
};
@@ -12,7 +12,7 @@ const Example = () => {
};
// EXAMPLES DO NOT INCLUDE CONTENT BELOW THIS LINE
export default withDsExample(Example);
export default withDsExample(Example, { showBreakpoints: true });
/* Storybook story */
export const Demo = {
@@ -12,7 +12,7 @@ const Example = () => {
};
// EXAMPLES DO NOT INCLUDE CONTENT BELOW THIS LINE
export default withDsExample(Example);
export default withDsExample(Example, { showBreakpoints: true });
/* Storybook story */
export const Demo = {
@@ -1,8 +1,15 @@
import { Switch } from "@navikt/ds-react";
import { Switch, VStack } from "@navikt/ds-react";
import { withDsExample } from "@/web/examples/withDsExample";
const Example = () => {
return <Switch disabled>Varsle med SMS</Switch>;
return (
<VStack gap="4">
<Switch disabled>Varsle med SMS</Switch>
<Switch disabled checked>
Varsle med SMS
</Switch>
</VStack>
);
};
// EXAMPLES DO NOT INCLUDE CONTENT BELOW THIS LINE
@@ -15,4 +22,5 @@ export const Demo = {
export const args = {
index: 99,
desc: "Vi fraråder bruk av disabled state. Vurder om du trenger å vise feltet i det hele tatt, om du heller kan bruke 'readOnly', eller bare kan skrive det ut i ren tekst.",
};
@@ -15,4 +15,5 @@ export const Demo = {
export const args = {
index: 4,
desc: "Label må være meningsfull selv om den skjules, siden den fortsatt leses av skjermlesere.",
};
@@ -1,8 +1,15 @@
import { Switch } from "@navikt/ds-react";
import { Switch, VStack } from "@navikt/ds-react";
import { withDsExample } from "@/web/examples/withDsExample";
const Example = () => {
return <Switch loading>Varsle med SMS</Switch>;
return (
<VStack gap="4">
<Switch loading>Varsle med SMS</Switch>
<Switch loading checked>
Varsle med SMS
</Switch>
</VStack>
);
};
// EXAMPLES DO NOT INCLUDE CONTENT BELOW THIS LINE
@@ -15,4 +22,5 @@ export const Demo = {
export const args = {
index: 8,
desc: "'loading'-prop bør bare brukes for korte lastetider. Ved lengre lastetid bør brukeren [informeres om hva som foregår](https://aksel.nav.no/komponenter/core/loader#ba471ad4389c).",
};
@@ -1,14 +1,14 @@
import { Switch } from "@navikt/ds-react";
import { Switch, VStack } from "@navikt/ds-react";
import { withDsExample } from "@/web/examples/withDsExample";
const Example = () => {
return (
<div className="grid gap-4">
<VStack gap="4">
<Switch readOnly>Slå på notifikasjoner</Switch>
<Switch readOnly checked>
Slå på notifikasjoner
</Switch>
</div>
</VStack>
);
};
@@ -22,4 +22,5 @@ export const Demo = {
export const args = {
index: 98,
desc: "Readonly-attributtet gjør at tilstanden ikke kan endres. Til forskjell fra 'disabled' vil brukere fortsatt kunne tabbe til feltet, og verdien vil inkluderes når skjemaet sendes inn.",
};
@@ -15,4 +15,5 @@ export const Demo = {
export const args = {
index: 1,
desc: "Small brukes på interne flater der det er behov for et mer komprimert grensesnitt. Small bør brukes sparsomt på sider som brukes fra mobil, da det er viktig at touch-flaten er stor nok.",
};
@@ -2,6 +2,7 @@ import { namedTypes } from "ast-types";
import fs from "fs";
import jscodeshift, { ASTPath, Collection } from "jscodeshift";
import path from "path";
// eslint-disable-next-line import/default
import prettier from "prettier";
const j = jscodeshift.withParser("tsx");
+4 -4
View File
@@ -10,15 +10,15 @@
"astro": "astro"
},
"dependencies": {
"@astrojs/check": "^0.4.1",
"@astrojs/node": "^8.1.0",
"@astrojs/react": "^3.0.9",
"@astrojs/check": "^0.9.3",
"@astrojs/node": "^8.3.4",
"@astrojs/react": "^3.6.2",
"@navikt/aksel-icons": "file:./../../@navikt/aksel-icons",
"@navikt/ds-css": "file:./../../@navikt/core/css",
"@navikt/ds-react": "file:./../../@navikt/core/react",
"@types/react": "^18.2.48",
"@types/react-dom": "^18.2.18",
"astro": "^4.2.6",
"astro": "^4.15.8",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"typescript": "^5.3.3"
+2781 -2892
View File
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -13,7 +13,7 @@
"@navikt/ds-css": "file:./../../@navikt/core/css",
"@navikt/ds-react": "file:./../../@navikt/core/react",
"@next/bundle-analyzer": "^14.1.0",
"next": "14.1.0",
"next": "14.2.12",
"react": "^18",
"react-dom": "^18"
},
File diff suppressed because it is too large. Load diff
+14 -2
View File
@@ -2,9 +2,21 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta
http-equiv="Cache-control"
content="no-cache, no-store, must-revalidate"
/>
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
<!-- fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Source+Sans+3:ital,wght@0,200..900;1,200..900&display=swap"
rel="stylesheet"
/>
<title>Aksel darkside (referansesider)</title>
</head>
<body>
<div id="root"></div>
+4 -2
View File
@@ -5,11 +5,12 @@
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"build": "vite build && tsc -b",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@navikt/aksel-icons": "../../@navikt/aksel-icons",
"@navikt/ds-css": "../../@navikt/core/css",
"@navikt/ds-tokens": "../../@navikt/core/tokens",
"@tanstack/react-router": "^1.57.17",
@@ -26,6 +27,7 @@
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react-swc": "^3.5.0",
"@vitest/eslint-plugin": "^1.1.4",
"autoprefixer": "^10.4.20",
"eslint": "^9.9.0",
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
@@ -35,6 +37,6 @@
"tailwindcss": "^3.4.11",
"typescript": "^5.5.3",
"typescript-eslint": "^8.0.1",
"vite": "^5.4.6"
"vite": "^5.4.8"
}
}
@@ -0,0 +1,34 @@
<svg
width="32"
height="32"
viewBox="0 0 32 32"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<style>
path[data-path="1"] {
fill: #00243a;
}
path[data-path="2"] {
fill: #ffd799;
}
@media (prefers-color-scheme: dark) {
path[data-path="1"] {
fill: #00243a;
}
path[data-path="2"] {
fill: #ffd799;
}
}
</style>
<path
data-path="1"
d="M0 4C0 1.79086 1.79086 0 4 0H28C30.2091 0 32 1.79086 32 4V28C32 30.2091 30.2091 32 28 32H4C1.79086 32 0 30.2091 0 28V4Z"
/>
<path
data-path="2"
fill-rule="evenodd"
clip-rule="evenodd"
d="M4.43934 9.93931C4.1476 10.2311 4.00115 10.6131 4.00001 10.9955C3.99885 11.3808 4.14527 11.7666 4.43929 12.0606C5.02505 12.6464 5.9748 12.6465 6.56061 12.0607L11.3112 7.31056C11.7029 6.93797 12.3226 6.94391 12.7071 7.3284L24.6716 19.2929C25.0621 19.6834 25.0621 20.3166 24.6716 20.7071L19.9393 25.4393C19.3536 26.0251 19.3536 26.9748 19.9393 27.5606C20.2083 27.8296 20.5539 27.975 20.9058 27.997C21.3206 28.0231 21.7442 27.8776 22.0611 27.5607L26.8289 22.7929C27.5791 22.0427 28.0005 21.0253 28.0005 19.9644L28.0001 7.99987C28 5.79078 26.2092 4 24.0001 4H12.036C10.9753 4 9.95813 4.42124 9.20803 5.17108C9.19582 5.18298 9.18367 5.19498 9.17157 5.20708L4.43934 9.93931ZM25.0003 15.379L16.6214 7H24.0001C24.5524 7 25.0001 7.4477 25.0001 7.99997L25.0003 15.379ZM14.5607 19.5606C15.1464 18.9749 15.1464 18.0251 14.5607 17.4393C13.9749 16.8535 13.0251 16.8535 12.4393 17.4393L4.43934 25.4393C3.85355 26.0251 3.85355 26.9749 4.43934 27.5606C5.02513 28.1464 5.97487 28.1464 6.56066 27.5606L14.5607 19.5606Z"
/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

@@ -1,21 +0,0 @@
import { twMerge } from "tailwind-merge";
export const Ikon = ({
className,
colorStroke = "stroke-black",
colorFill = "fill-black",
}: {
className?: string;
colorStroke?: string;
colorFill?: string;
}) => {
return (
<span className={twMerge(`w-4 h-4 block`, className)} aria-hidden="true">
<svg className={twMerge(colorFill, colorStroke)} viewBox="0 0 24 24">
<path d="M6.5 11.25c-.69 0-1.25-.56-1.25-1.25V4a.75.75 0 0 0-1.5 0v6a2.75 2.75 0 0 0 2.75 2.75h10.69l-3.22 3.22a.75.75 0 1 0 1.06 1.06l4.5-4.5a.75.75 0 0 0 0-1.06l-4.5-4.5a.75.75 0 0 0-1.06 1.06l3.22 3.22z"></path>
</svg>
</span>
);
};
export default Ikon;
@@ -1,31 +0,0 @@
import { twMerge } from "tailwind-merge";
export const Ikon = ({
className,
colorStroke = "stroke-black",
colorFill = "fill-black",
}: {
className?: string;
colorStroke?: string;
colorFill?: string;
}) => {
return (
<span className={twMerge(`w-5 h-5 block`, className)} aria-hidden="true">
<svg
className={twMerge(colorFill, colorStroke)}
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
focusable="false"
role="img"
aria-hidden="true"
fontSize="1.25rem"
>
<path d="M14.087 6.874a.752.752 0 0 0-.117 1.156l3.22 3.22H5a.75.75 0 0 0 0 1.5h12.19l-3.22 3.22a.75.75 0 0 0 1.06 1.06l4.5-4.5a.75.75 0 0 0 0-1.06l-4.5-4.5a.75.75 0 0 0-.943-.096"></path>
</svg>
</span>
);
};
export default Ikon;
Loaded 100 of 139 files, more files were not shown because too many files have changed in this diff. Show more