mirror of
https://github.com/navikt/aksel.git
synced 2026-10-13 01:02:02 +00:00
Compare commits
47
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf167064eb | ||
|
|
73a6ae1bf1 | ||
|
|
95c84e1d01 | ||
|
|
4675857e8f | ||
|
|
2a6e416b5c | ||
|
|
d3c3cafc97 | ||
|
|
678fa6692b | ||
|
|
87aeb94c64 | ||
|
|
4714a5ff32 | ||
|
|
ca1d9be88f | ||
|
|
2654da8b64 | ||
|
|
8d6418a594 | ||
|
|
8269091fb8 | ||
|
|
fd82184057 | ||
|
|
39f2cb8614 | ||
|
|
4bf298f363 | ||
|
|
3a239e4e17 | ||
|
|
4fcfaf3deb | ||
|
|
5dbc66daaa | ||
|
|
555a46b5da | ||
|
|
73b2bca156 | ||
|
|
01411dc11f | ||
|
|
b1b0d23863 | ||
|
|
cf59ce81bf | ||
|
|
00c0ff31e1 | ||
|
|
d278060e59 | ||
|
|
e5d5b67262 | ||
|
|
246f25cbaa | ||
|
|
b2a22438ad | ||
|
|
8b8b724e40 | ||
|
|
9f9bdcc645 | ||
|
|
82bdf8075a | ||
|
|
410e543825 | ||
|
|
40308e0e77 | ||
|
|
fff309b573 | ||
|
|
fe7c2de8fa | ||
|
|
6fedea0ba4 | ||
|
|
9811327509 | ||
|
|
cf2034f914 | ||
|
|
9e5e7f8fdc | ||
|
|
e9dc171d74 | ||
|
|
544b80eeae | ||
|
|
4358b53095 | ||
|
|
de57099b09 | ||
|
|
5a97cb422d | ||
|
|
67fb3bb3a1 | ||
|
|
7bdd5cb748 |
No files matched your search
@@ -156,5 +156,6 @@ module.exports = {
|
||||
"**/codemod/**/*.js",
|
||||
"!.storybook",
|
||||
"**/playwright-report/**",
|
||||
"**/tokens/**/plugin.js",
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
name: "Update remote token config for figma variables plugin"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
paths:
|
||||
- "@navikt/core/tokens/**"
|
||||
|
||||
jobs:
|
||||
upload:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: "read"
|
||||
id-token: "write"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: yarn
|
||||
- name: set-env for yarn install to @navikt scope
|
||||
shell: bash
|
||||
run: echo "NPM_AUTH_TOKEN=${{ secrets.READER_TOKEN }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn
|
||||
|
||||
- name: Build tokens
|
||||
run: yarn workspace @navikt/ds-tokens build
|
||||
|
||||
- id: upload
|
||||
uses: nais/deploy/actions/cdn-upload/v2@master
|
||||
with:
|
||||
team: designsystem
|
||||
source: "./@navikt/core/tokens/figma-config.json"
|
||||
destination: "./@navikt/tokens"
|
||||
project_id: ${{ vars.NAIS_MANAGEMENT_PROJECT_ID }}
|
||||
identity_provider: ${{ secrets.NAIS_WORKLOAD_IDENTITY_PROVIDER }}
|
||||
source_keep_parent_name: false
|
||||
no_cache_paths: "@navikt/tokens/figma-config.json"
|
||||
|
||||
- run: echo uploaded file ${{ steps.upload.outputs.uploaded }}
|
||||
shell: bash
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
NPM_AUTH_TOKEN: ${{ secrets.READER_TOKEN }}
|
||||
- run: yarn boot
|
||||
- run: cd ./examples/referansesider && yarn && yarn build
|
||||
- uses: navikt/frontend/actions/spa-deploy/v2@main
|
||||
- uses: nais/deploy/actions/spa-deploy/v2@master
|
||||
with:
|
||||
app: aksel-referansesider
|
||||
team: designsystem
|
||||
|
||||
@@ -5,24 +5,11 @@ jobs:
|
||||
execute:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
packages: "write"
|
||||
contents: "read"
|
||||
id-token: "write"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- uses: denoland/setup-deno@v1
|
||||
with:
|
||||
deno-version: v1.x
|
||||
- name: Restore cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: "**/node_modules"
|
||||
key: ${{ runner.os }}-build-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-
|
||||
|
||||
- uses: nais/docker-build-push@v0
|
||||
id: docker-push
|
||||
with:
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# @navikt/aksel-icons
|
||||
|
||||
## 7.1.2
|
||||
|
||||
## 7.1.1
|
||||
|
||||
## 7.1.0
|
||||
|
||||
## 7.0.1
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/aksel-icons",
|
||||
"version": "7.1.0",
|
||||
"version": "7.1.2",
|
||||
"author": "Aksel | NAV",
|
||||
"homepage": "https://aksel.nav.no/ikoner",
|
||||
"repository": {
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @navikt/aksel-stylelint
|
||||
|
||||
## 7.1.2
|
||||
|
||||
## 7.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- List: :boom: Removed class names "navds-list--nested" and "navds-list\_\_item-content" ([#3184](https://github.com/navikt/aksel/pull/3184))
|
||||
|
||||
## 7.1.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/aksel-stylelint",
|
||||
"version": "7.1.0",
|
||||
"version": "7.1.2",
|
||||
"author": "Aksel | NAV",
|
||||
"homepage": "https://aksel.nav.no/grunnleggende/kode/stylelint",
|
||||
"repository": {
|
||||
@@ -35,8 +35,8 @@
|
||||
"dev": "yarn watch:lint"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@navikt/ds-css": "^7.1.0",
|
||||
"@navikt/ds-tokens": "^7.1.0",
|
||||
"@navikt/ds-css": "^7.1.2",
|
||||
"@navikt/ds-tokens": "^7.1.2",
|
||||
"concurrently": "7.2.1",
|
||||
"postcss-selector-parser": "^6.0.13",
|
||||
"postcss-value-parser": "^4.2.0",
|
||||
|
||||
@@ -68,4 +68,8 @@ export const deprecations: DeprecatedList = [
|
||||
],
|
||||
message: "These classes had no effect and were removed in v7.1.0",
|
||||
},
|
||||
{
|
||||
classes: ["navds-list--nested", "navds-list__item-content"],
|
||||
message: "Removed in v7.1.1",
|
||||
},
|
||||
];
|
||||
@@ -1,5 +1,19 @@
|
||||
# @navikt/aksel
|
||||
|
||||
## 7.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`95c84e1d0`](https://github.com/navikt/aksel/commit/95c84e1d014d880bc22eb0053a875771e8399540)]:
|
||||
- @navikt/ds-css@7.1.2
|
||||
|
||||
## 7.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`ca1d9be88`](https://github.com/navikt/aksel/commit/ca1d9be88f45f936ee8546af146f8783d8c43db2), [`73b2bca15`](https://github.com/navikt/aksel/commit/73b2bca156072c945c2fc4e4e849f50a00398eb6), [`d278060e5`](https://github.com/navikt/aksel/commit/d278060e59b17638cbda22dc194e89b01dc2113e)]:
|
||||
- @navikt/ds-css@7.1.1
|
||||
|
||||
## 7.1.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/aksel",
|
||||
"version": "7.1.0",
|
||||
"version": "7.1.2",
|
||||
"description": "Aksel command line interface. Handles css-imports, codemods and more",
|
||||
"author": "Aksel | NAV designsystem team",
|
||||
"license": "MIT",
|
||||
@@ -29,7 +29,7 @@
|
||||
},
|
||||
"homepage": "https://aksel.nav.no/grunnleggende/kode/kommandolinje",
|
||||
"dependencies": {
|
||||
"@navikt/ds-css": "7.1.0",
|
||||
"@navikt/ds-css": "7.1.2",
|
||||
"axios": "1.7.4",
|
||||
"chalk": "4.1.0",
|
||||
"clipboardy": "^2.3.0",
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# @navikt/ds-css
|
||||
|
||||
## 7.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- List: Explicitly set list-style on ol to override reset-CSS ([#3202](https://github.com/navikt/aksel/pull/3202))
|
||||
|
||||
## 7.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- List: Add indentation and remove overly verbose aria attributes ([#3184](https://github.com/navikt/aksel/pull/3184))
|
||||
|
||||
- Datepicker/Select: :lipstick: dim non-interactable icons when readonly is set. ([#3187](https://github.com/navikt/aksel/pull/3187))
|
||||
|
||||
- Combobox: :bug: implement missing readonly and disabled ([#3180](https://github.com/navikt/aksel/pull/3180))
|
||||
|
||||
## 7.1.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -281,6 +281,7 @@
|
||||
/* Readonly */
|
||||
.navds-date__field--readonly .navds-date__field-button {
|
||||
cursor: default;
|
||||
color: var(--a-gray-500);
|
||||
}
|
||||
|
||||
.navds-date__caption-button {
|
||||
|
||||
@@ -20,6 +20,36 @@
|
||||
--__ac-combobox-input-height: 1.5rem;
|
||||
}
|
||||
|
||||
.navds-combobox--disabled {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.navds-combobox--disabled .navds-combobox__wrapper *:hover {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.navds-combobox--disabled .navds-text-field__input {
|
||||
border: 1px solid var(--a-border-default);
|
||||
}
|
||||
|
||||
.navds-combobox--disabled .navds-combobox__selected-options {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.navds-combobox--readonly {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.navds-combobox--readonly .navds-combobox__button-toggle-list {
|
||||
color: var(--a-gray-500);
|
||||
}
|
||||
|
||||
.navds-combobox--readonly .navds-text-field__input,
|
||||
.navds-combobox--readonly .navds-combobox__input {
|
||||
background-color: var(--a-surface-subtle);
|
||||
border-color: var(--a-border-subtle);
|
||||
}
|
||||
|
||||
.navds-combobox__button-clear svg,
|
||||
.navds-combobox__button-toggle-list svg,
|
||||
.navds-combobox__list svg {
|
||||
|
||||
@@ -109,3 +109,7 @@
|
||||
border-color: var(--a-border-subtle);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.navds-select--readonly .navds-select__chevron {
|
||||
color: var(--a-gray-500);
|
||||
}
|
||||
+60
-71
@@ -4,92 +4,81 @@
|
||||
margin-block: var(--a-spacing-4);
|
||||
}
|
||||
|
||||
.navds-list--nested ul,
|
||||
.navds-list--nested ol {
|
||||
margin-block: var(--a-spacing-2);
|
||||
}
|
||||
|
||||
.navds-list--small ul,
|
||||
.navds-list--small ol {
|
||||
margin-block: var(--a-spacing-3);
|
||||
}
|
||||
|
||||
.navds-list li {
|
||||
margin-block-end: var(--a-spacing-2);
|
||||
}
|
||||
|
||||
.navds-list:last-child > ul > li:last-child,
|
||||
.navds-list:last-child > ol > li:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.navds-list ul .navds-list__item {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: var(--a-spacing-3);
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.navds-list ul .navds-list__item::marker {
|
||||
content: none;
|
||||
}
|
||||
|
||||
.navds-list ol .navds-list__item-marker--icon {
|
||||
grid-column: 1 / 2;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.navds-list .navds-list__item-marker--bullet {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: var(--a-font-line-height-xlarge);
|
||||
grid-column: 1 / 2;
|
||||
color: var(--ac-list-marker-ul-color, var(--ac-list-marker-color, var(--a-icon-default)));
|
||||
}
|
||||
|
||||
.navds-list--small .navds-list__item-marker--bullet {
|
||||
height: var(--a-font-line-height-large);
|
||||
}
|
||||
|
||||
.navds-list__item-marker--icon {
|
||||
color: var(--ac-list-marker-icon-color, var(--ac-list-marker-color, var(--a-icon-default)));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: var(--a-font-line-height-xlarge);
|
||||
padding-block-end: 1px;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.navds-list--small .navds-list__item-marker--icon {
|
||||
height: var(--a-font-line-height-large);
|
||||
}
|
||||
|
||||
.navds-list__item-marker--icon svg {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.navds-list ul .navds-list__item-content {
|
||||
grid-column: 2 / 3;
|
||||
.navds-list .navds-list :where(ul, ol) {
|
||||
margin-block: var(--a-spacing-2) 0;
|
||||
}
|
||||
|
||||
.navds-list ol {
|
||||
padding-left: var(--a-spacing-2);
|
||||
list-style: decimal;
|
||||
list-style: decimal; /* This is the default value, but some frameworks have `ol,ul { list-style:none }` */
|
||||
padding-left: 1.7rem;
|
||||
}
|
||||
|
||||
.navds-list ol li {
|
||||
padding-left: var(--a-spacing-1);
|
||||
margin-left: var(--a-spacing-2);
|
||||
.navds-list ol > .navds-list__item {
|
||||
padding-left: 0.3rem;
|
||||
}
|
||||
|
||||
/* SAFARI HACK START */
|
||||
/* stylelint-disable selector-type-no-unknown */
|
||||
|
||||
_::-webkit-full-page-media,
|
||||
_:future,
|
||||
:root .navds-list ol {
|
||||
padding-left: 2.1rem;
|
||||
}
|
||||
|
||||
_::-webkit-full-page-media,
|
||||
_:future,
|
||||
:root .navds-list ol .navds-list__item {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
/* SAFARI HACK END */
|
||||
/* stylelint-enable selector-type-no-unknown */
|
||||
|
||||
.navds-list__item {
|
||||
margin-block-end: var(--a-spacing-2);
|
||||
}
|
||||
|
||||
.navds-list__item:last-child {
|
||||
margin-block-end: 0;
|
||||
}
|
||||
|
||||
.navds-list ul > .navds-list__item {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: var(--a-spacing-2);
|
||||
}
|
||||
|
||||
.navds-list ol li::marker {
|
||||
font-weight: var(--a-font-weight-bold);
|
||||
font-size: var(--a-font-size-large);
|
||||
color: var(--ac-list-marker-ol-color, var(--ac-list-marker-color, var(--a-icon-default)));
|
||||
line-height: var(--a-font-line-height-xlarge);
|
||||
}
|
||||
|
||||
.navds-list--small ol li::marker {
|
||||
font-size: var(--a-font-size-small);
|
||||
line-height: var(--a-font-line-height-large);
|
||||
.navds-list__item-marker {
|
||||
width: 1.5rem;
|
||||
}
|
||||
|
||||
.navds-list ul > li > .navds-list__item-marker {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: var(--a-font-line-height-xlarge);
|
||||
}
|
||||
|
||||
.navds-list--small ul > li > .navds-list__item-marker {
|
||||
height: var(--a-font-line-height-large);
|
||||
}
|
||||
|
||||
.navds-list__item-marker--bullet {
|
||||
padding-left: 0.8rem;
|
||||
color: var(--ac-list-marker-ul-color, var(--ac-list-marker-color, var(--a-icon-default)));
|
||||
}
|
||||
|
||||
.navds-list__item-marker--icon {
|
||||
font-size: 1.5rem;
|
||||
color: var(--ac-list-marker-icon-color, var(--ac-list-marker-color, var(--a-icon-default)));
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-css",
|
||||
"version": "7.1.0",
|
||||
"version": "7.1.2",
|
||||
"description": "CSS for NAV Designsystem",
|
||||
"author": "Aksel | NAV designsystem team",
|
||||
"keywords": [
|
||||
@@ -27,7 +27,7 @@
|
||||
"css:get-version": "node config/get-version.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@navikt/ds-tokens": "^7.1.0",
|
||||
"@navikt/ds-tokens": "^7.1.2",
|
||||
"cssnano": "6.0.0",
|
||||
"fast-glob": "3.2.11",
|
||||
"lodash": "4.17.21",
|
||||
|
||||
@@ -1,5 +1,25 @@
|
||||
# @navikt/ds-react
|
||||
|
||||
## 7.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/ds-tokens@7.1.2
|
||||
- @navikt/aksel-icons@7.1.2
|
||||
|
||||
## 7.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- List: Add indentation and remove overly verbose aria attributes ([#3184](https://github.com/navikt/aksel/pull/3184))
|
||||
|
||||
- Combobox: :bug: implement missing readonly and disabled ([#3180](https://github.com/navikt/aksel/pull/3180))
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/ds-tokens@7.1.1
|
||||
- @navikt/aksel-icons@7.1.1
|
||||
|
||||
## 7.1.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-react",
|
||||
"version": "7.1.0",
|
||||
"version": "7.1.2",
|
||||
"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.1.0",
|
||||
"@navikt/ds-tokens": "^7.1.0",
|
||||
"@navikt/aksel-icons": "^7.1.2",
|
||||
"@navikt/ds-tokens": "^7.1.2",
|
||||
"clsx": "^2.1.0",
|
||||
"date-fns": "^3.0.0",
|
||||
"react-day-picker": "8.10.0"
|
||||
|
||||
@@ -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> = ({
|
||||
|
||||
@@ -67,6 +67,9 @@ Default.argTypes = {
|
||||
disabled: {
|
||||
control: { type: "boolean" },
|
||||
},
|
||||
readOnly: {
|
||||
control: { type: "boolean" },
|
||||
},
|
||||
isListOpen: {
|
||||
control: { type: "boolean" },
|
||||
},
|
||||
@@ -484,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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -525,6 +566,8 @@ export const Chromatic: StoryFn = () => {
|
||||
<WithError />
|
||||
<H2>Disabled</H2>
|
||||
<Disabled />
|
||||
<H2>Readonly</H2>
|
||||
<Readonly />
|
||||
</VStack>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -2,7 +2,7 @@ import cl from "clsx";
|
||||
import React, { forwardRef, useContext } from "react";
|
||||
import { BodyLong } from "../typography";
|
||||
import { ListContext } from "./context";
|
||||
import { ListItemProps } from "./types";
|
||||
import type { ListItemProps } from "./types";
|
||||
|
||||
/**
|
||||
* @see 🏷️ {@link ListItemProps}
|
||||
@@ -21,7 +21,7 @@ export const ListItem = forwardRef<HTMLLIElement, ListItemProps>(
|
||||
<li {...rest} ref={ref} className={cl("navds-list__item", className)}>
|
||||
{listType === "ul" && (
|
||||
<div
|
||||
className={cl({
|
||||
className={cl("navds-list__item-marker", {
|
||||
"navds-list__item-marker--icon": icon,
|
||||
"navds-list__item-marker--bullet": !icon,
|
||||
})}
|
||||
@@ -30,8 +30,8 @@ export const ListItem = forwardRef<HTMLLIElement, ListItemProps>(
|
||||
icon
|
||||
) : (
|
||||
<svg
|
||||
width="6"
|
||||
height="6"
|
||||
width="0.375rem"
|
||||
height="0.375rem"
|
||||
viewBox="0 0 6 6"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -45,14 +45,14 @@ export const ListItem = forwardRef<HTMLLIElement, ListItemProps>(
|
||||
</div>
|
||||
)}
|
||||
|
||||
<BodyLong as="div" size={size} className="navds-list__item-content">
|
||||
<div>
|
||||
{title && (
|
||||
<BodyLong as="p" size={size} weight="semibold">
|
||||
{title}
|
||||
</BodyLong>
|
||||
)}
|
||||
{children}
|
||||
</BodyLong>
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -2,13 +2,11 @@ import { createContext } from "react";
|
||||
import { ListProps } from "./types";
|
||||
|
||||
interface ListContextProps {
|
||||
listType: ListProps["as"];
|
||||
isNested: boolean | null;
|
||||
size: ListProps["size"];
|
||||
listType: Exclude<ListProps["as"], undefined>;
|
||||
size: Exclude<ListProps["size"], undefined>;
|
||||
}
|
||||
|
||||
export const ListContext = createContext<ListContextProps>({
|
||||
listType: "ul",
|
||||
isNested: null,
|
||||
size: "medium",
|
||||
});
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
HeadHeartIcon,
|
||||
SplitHorizontalIcon,
|
||||
} from "@navikt/aksel-icons";
|
||||
import { Box } from "../layout/box";
|
||||
import { VStack } from "../layout/stack";
|
||||
import { BodyLong } from "../typography";
|
||||
import List from "./List";
|
||||
@@ -21,73 +22,97 @@ export default {
|
||||
|
||||
type Story = StoryObj<typeof List>;
|
||||
|
||||
export const Default = {
|
||||
export const Default: Story = {
|
||||
render: (props) => {
|
||||
return (
|
||||
<Box borderWidth="0 0 0 1" borderColor="border-subtle" maxWidth="500px">
|
||||
<List {...props}>
|
||||
<List.Item title="Lorem Ipsum Dolor Sit Amet">Beskrivelse.</List.Item>
|
||||
<List.Item title="Consectetur Adipiscing Elit">
|
||||
Beskrivelse på punktet i lista. Prøv å hold den kort og konsis.
|
||||
</List.Item>
|
||||
<List.Item>
|
||||
Lorem ipsum, dolor sit amet consectetur adipisicing elit. Iusto
|
||||
fugiat atque accusantium iure sunt, ipsum voluptas, impedit harum,
|
||||
minus rerum recusandae.
|
||||
</List.Item>
|
||||
<List.Item>
|
||||
Impedit nemo eos sit adipisci non dolores.
|
||||
<List>
|
||||
<List.Item title="Tempor Incididunt">
|
||||
Error assumenda officia
|
||||
</List.Item>
|
||||
<List.Item>Suscipit odit voluptatum</List.Item>
|
||||
</List>
|
||||
</List.Item>
|
||||
<List.Item>
|
||||
Lorem ipsum, dolor sit amet consectetur adipisicing elit. Iusto
|
||||
fugiat atque accusantium iure sunt, ipsum voluptas.
|
||||
<List as="ol">
|
||||
<List.Item title="Tempor Incididunt">
|
||||
Error assumenda officia
|
||||
</List.Item>
|
||||
<List.Item>Suscipit odit voluptatum</List.Item>
|
||||
</List>
|
||||
</List.Item>
|
||||
</List>
|
||||
</Box>
|
||||
);
|
||||
},
|
||||
args: {
|
||||
title: "",
|
||||
description: "",
|
||||
},
|
||||
argTypes: {
|
||||
size: { control: { type: "radio" }, options: ["small", "medium"] },
|
||||
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>
|
||||
),
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# @navikt/ds-tailwind
|
||||
|
||||
## 7.1.2
|
||||
|
||||
## 7.1.1
|
||||
|
||||
## 7.1.0
|
||||
|
||||
## 7.0.1
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-tailwind",
|
||||
"version": "7.1.0",
|
||||
"version": "7.1.2",
|
||||
"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.1.0",
|
||||
"@navikt/ds-tokens": "^7.1.2",
|
||||
"color": "4.2.3",
|
||||
"lodash": "^4.17.21",
|
||||
"tailwindcss": "^3.3.3",
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
figma-config.json
|
||||
plugin.js
|
||||
@@ -1,5 +1,9 @@
|
||||
# @navikt/ds-tokens
|
||||
|
||||
## 7.1.2
|
||||
|
||||
## 7.1.1
|
||||
|
||||
## 7.1.0
|
||||
|
||||
## 7.0.1
|
||||
|
||||
@@ -1,29 +1,11 @@
|
||||
import { getGlobalScaleForColor } from "./leonardo";
|
||||
import { contrastTokenConfig } from "./tokens/contrast";
|
||||
import { neutralTokenConfig } from "./tokens/neutral";
|
||||
import { globalColorDarkModeConfig } from "./tokens/global-dark";
|
||||
import { globalColorLightModeConfig } from "./tokens/global-light";
|
||||
import { radiusTokenConfig } from "./tokens/radius";
|
||||
import { semanticTokenConfig } from "./tokens/semantic";
|
||||
import { semanticTokensForAllRolesConfig } from "./tokens/semantic-roles";
|
||||
import { spacingTokenConfig } from "./tokens/spacing";
|
||||
import {
|
||||
ColorTheme,
|
||||
GlobalColorVariable,
|
||||
globalColorRoles,
|
||||
mergeConfigs,
|
||||
tokensWithPrefix,
|
||||
} from "./util";
|
||||
|
||||
export const globalScale = (
|
||||
theme: ColorTheme,
|
||||
): Record<string, GlobalColorVariable> => {
|
||||
return globalColorRoles.reduce(
|
||||
(acc, role) => {
|
||||
acc[role] = getGlobalScaleForColor(role, theme);
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, GlobalColorVariable>,
|
||||
);
|
||||
};
|
||||
import { mergeConfigs, tokensWithPrefix } from "./util";
|
||||
|
||||
/**
|
||||
* Collection of configs for:
|
||||
@@ -36,9 +18,8 @@ export const lightModeTokens = () => {
|
||||
mergeConfigs([
|
||||
semanticTokensForAllRolesConfig(),
|
||||
contrastTokenConfig(),
|
||||
semanticTokenConfig(),
|
||||
neutralTokenConfig("light"),
|
||||
globalScale("light"),
|
||||
semanticTokenConfig("light"),
|
||||
globalColorLightModeConfig,
|
||||
]),
|
||||
);
|
||||
};
|
||||
@@ -49,7 +30,12 @@ export const lightModeTokens = () => {
|
||||
*/
|
||||
export const darkModeTokens = () => {
|
||||
return tokensWithPrefix(
|
||||
mergeConfigs([globalScale("dark"), neutralTokenConfig("dark")]),
|
||||
mergeConfigs([
|
||||
semanticTokensForAllRolesConfig(),
|
||||
contrastTokenConfig(),
|
||||
semanticTokenConfig("dark"),
|
||||
globalColorDarkModeConfig,
|
||||
]),
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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,30 @@
|
||||
import { StyleDictionaryToken, TokenTypes } from "../util";
|
||||
|
||||
export type FigmaToken = Omit<StyleDictionaryToken<TokenTypes>, "value"> & {
|
||||
name: string;
|
||||
alias?: string;
|
||||
code: Variable["codeSyntax"];
|
||||
value: string | number;
|
||||
figmaType: VariableResolvedDataType;
|
||||
scopes: VariableScope[];
|
||||
};
|
||||
|
||||
export type FigmaConfigEntry = {
|
||||
name: string;
|
||||
hideFromPublishing: boolean;
|
||||
tokens: FigmaToken[];
|
||||
};
|
||||
|
||||
export type FigmaTokenConfig = {
|
||||
version: string;
|
||||
timestamp: string;
|
||||
colors: { light: ColorThemeEntry; dark: ColorThemeEntry };
|
||||
radius: FigmaConfigEntry;
|
||||
spacing: FigmaConfigEntry;
|
||||
};
|
||||
|
||||
type ColorThemeEntry = {
|
||||
name: "light" | "dark";
|
||||
global: FigmaConfigEntry;
|
||||
semantic: FigmaConfigEntry;
|
||||
};
|
||||
@@ -0,0 +1,77 @@
|
||||
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",
|
||||
}),
|
||||
|
||||
colors: {
|
||||
light: {
|
||||
name: "light",
|
||||
global: {
|
||||
name: "Global colors light",
|
||||
hideFromPublishing: true,
|
||||
tokens: lightTokens.filter(isGlobalColor),
|
||||
},
|
||||
semantic: {
|
||||
name: "Semantic colors",
|
||||
hideFromPublishing: false,
|
||||
tokens: lightTokens.filter(isSemanticColor),
|
||||
},
|
||||
},
|
||||
dark: {
|
||||
name: "dark",
|
||||
global: {
|
||||
name: "Global colors dark",
|
||||
hideFromPublishing: true,
|
||||
tokens: darkTokens.filter(isGlobalColor),
|
||||
},
|
||||
semantic: {
|
||||
name: "Semantic colors",
|
||||
hideFromPublishing: false,
|
||||
tokens: darkTokens.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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
import _config from "../../../figma-config.json";
|
||||
import { FigmaConfigEntry, FigmaTokenConfig } from "../figma-config.types";
|
||||
import { FigmaPluginInterface } from "./FigmaPluginInterface";
|
||||
|
||||
type ScopedFigmaTokenConfig = Omit<FigmaTokenConfig, "version" | "timestamp">;
|
||||
|
||||
export class AkselVariablesInterface {
|
||||
private Figma: FigmaPluginInterface;
|
||||
private config: FigmaTokenConfig;
|
||||
private meta: Pick<FigmaTokenConfig, "version" | "timestamp">;
|
||||
private remoteConfigURL =
|
||||
"https://cdn.nav.no/designsystem/@navikt/tokens/figma-config.json";
|
||||
|
||||
constructor() {
|
||||
const config = _config as FigmaTokenConfig;
|
||||
this.config = config;
|
||||
this.meta = { timestamp: config.timestamp, version: config.version };
|
||||
this.Figma = new FigmaPluginInterface();
|
||||
}
|
||||
|
||||
/**
|
||||
* Because of "dynamic-page" loading in Figma,
|
||||
* we need to initialize the plugin trough async methods.
|
||||
* This method should be called before any other methods.
|
||||
*/
|
||||
async init(): Promise<void> {
|
||||
await this.Figma.init();
|
||||
}
|
||||
|
||||
async useRemoteConfig(): Promise<void> {
|
||||
const newConfig = await fetch(this.remoteConfigURL)
|
||||
.then((res) => res.json())
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
throw new Error("Error fetching config from CDN 😱");
|
||||
});
|
||||
this.meta = { timestamp: newConfig.timestamp, version: newConfig.version };
|
||||
|
||||
this.config = newConfig;
|
||||
}
|
||||
|
||||
updateVariables(): void {
|
||||
this.updateGlobalColorCollection(this.config.colors.light.global);
|
||||
this.updateGlobalColorCollection(this.config.colors.dark.global);
|
||||
this.updateSemanticColorCollection(this.config.colors);
|
||||
this.updateScaleCollection(this.config.radius);
|
||||
this.updateScaleCollection(this.config.spacing);
|
||||
console.info("Variables updated!");
|
||||
}
|
||||
|
||||
private updateGlobalColorCollection(
|
||||
globalColorTheme: FigmaConfigEntry,
|
||||
): void {
|
||||
const collection =
|
||||
this.Figma.getCollection(globalColorTheme.name) ??
|
||||
this.Figma.createCollection(globalColorTheme.name);
|
||||
|
||||
/**
|
||||
* Correctly sorts the token-scale for global colors
|
||||
* 000 - 100 - 200... etc.
|
||||
*/
|
||||
const getLastNumber = (name: string) => {
|
||||
const matches = name.match(/\d+/g);
|
||||
return matches ? parseInt(matches[matches.length - 1], 10) : 0;
|
||||
};
|
||||
const sortedTokens = globalColorTheme.tokens.sort(
|
||||
(a, b) => getLastNumber(a.name) - getLastNumber(b.name),
|
||||
);
|
||||
|
||||
for (const token of sortedTokens) {
|
||||
/* Color values can only be defined by strings */
|
||||
if (typeof token.value !== "string") {
|
||||
throw new Error(`Token value is not a string: ${token}`);
|
||||
}
|
||||
|
||||
const variable =
|
||||
this.Figma.getVariable(token.name, collection.id) ??
|
||||
this.Figma.createVariable(token.name, collection, token.figmaType);
|
||||
|
||||
this.Figma.setVariableValue(
|
||||
variable,
|
||||
figma.util.rgba(token.value),
|
||||
collection.defaultModeId,
|
||||
);
|
||||
|
||||
this.Figma.setVariableMetadata(variable, {
|
||||
codeSyntax: token.code,
|
||||
description: token.comment ?? "",
|
||||
hiddenFromPublishing: collection.hiddenFromPublishing,
|
||||
scopes: token.scopes,
|
||||
});
|
||||
}
|
||||
|
||||
console.info("Updated collection:", collection.name);
|
||||
}
|
||||
|
||||
private updateScaleCollection(
|
||||
globalScale:
|
||||
| ScopedFigmaTokenConfig["radius"]
|
||||
| ScopedFigmaTokenConfig["spacing"],
|
||||
): void {
|
||||
const collection =
|
||||
this.Figma.getCollection(globalScale.name) ??
|
||||
this.Figma.createCollection(globalScale.name);
|
||||
|
||||
const sortedTokens = globalScale.tokens.sort((a, b) => {
|
||||
if (typeof a.value === "number" && typeof b.value === "number") {
|
||||
return a.value - b.value;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
for (const token of sortedTokens) {
|
||||
const variable =
|
||||
this.Figma.getVariable(token.name, collection.id) ??
|
||||
this.Figma.createVariable(token.name, collection, token.figmaType);
|
||||
|
||||
this.Figma.setVariableValue(
|
||||
variable,
|
||||
token.value,
|
||||
collection.defaultModeId,
|
||||
);
|
||||
|
||||
this.Figma.setVariableMetadata(variable, {
|
||||
codeSyntax: token.code,
|
||||
description: token.comment ?? "",
|
||||
hiddenFromPublishing: collection.hiddenFromPublishing,
|
||||
scopes: token.scopes,
|
||||
});
|
||||
}
|
||||
|
||||
console.info("Updated collection:", collection.name);
|
||||
}
|
||||
|
||||
private updateSemanticColorCollection(
|
||||
colorsConfig: ScopedFigmaTokenConfig["colors"],
|
||||
): void {
|
||||
const semanticCollectionName = colorsConfig.light.semantic.name;
|
||||
|
||||
const collection =
|
||||
this.Figma.getCollection(semanticCollectionName) ??
|
||||
this.Figma.createCollection(semanticCollectionName);
|
||||
|
||||
for (const colorEntry of Object.values(colorsConfig)) {
|
||||
const modeName = colorEntry.name;
|
||||
|
||||
const globalCollection = this.Figma.getCollection(
|
||||
colorsConfig[modeName].global.name,
|
||||
);
|
||||
|
||||
if (!globalCollection) {
|
||||
throw new Error(
|
||||
`Global collection not found for: ${colorsConfig[modeName].global.name}`,
|
||||
);
|
||||
}
|
||||
|
||||
const mode =
|
||||
this.Figma.getModeWithName(modeName, collection) ??
|
||||
this.Figma.createMode(modeName, collection);
|
||||
|
||||
for (const token of colorsConfig[modeName].semantic.tokens) {
|
||||
const variable =
|
||||
this.Figma.getVariable(token.name, collection.id) ??
|
||||
this.Figma.createVariable(token.name, collection, token.figmaType);
|
||||
|
||||
if (!token.alias) {
|
||||
if (typeof token.value !== "string") {
|
||||
throw new Error(
|
||||
`Semantic tokens without alias requires value to be string: ${token}`,
|
||||
);
|
||||
}
|
||||
|
||||
this.Figma.setVariableValue(
|
||||
variable,
|
||||
figma.util.rgba(token.value),
|
||||
mode.modeId,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const globalVariable = this.Figma.getVariable(
|
||||
token.alias,
|
||||
globalCollection.id,
|
||||
);
|
||||
|
||||
if (!globalVariable) {
|
||||
throw new Error(
|
||||
`Global variable not found for alias: ${token.alias}`,
|
||||
);
|
||||
}
|
||||
|
||||
this.Figma.setVariableValue(
|
||||
variable,
|
||||
this.Figma.createVariableAlias(globalVariable),
|
||||
mode.modeId,
|
||||
);
|
||||
|
||||
this.Figma.setVariableMetadata(variable, {
|
||||
codeSyntax: token.code,
|
||||
description: token.comment ?? "",
|
||||
hiddenFromPublishing: collection.hiddenFromPublishing,
|
||||
scopes: token.scopes,
|
||||
});
|
||||
}
|
||||
|
||||
/* Make sure to remove "default" modes if they exist */
|
||||
this.Figma.removeNonMatchingModes(
|
||||
Object.values(colorsConfig).map((colorConfig) => colorConfig.name),
|
||||
collection,
|
||||
);
|
||||
}
|
||||
|
||||
console.info("Updated collection:", collection.name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all local collections in the Figma file.
|
||||
* @important Use with caution! If the current variables are not published in figma,
|
||||
* they will be lost forever!
|
||||
*/
|
||||
resetVariables(): void {
|
||||
this.Figma.resetVariables();
|
||||
}
|
||||
|
||||
exitWithMessage(message: string): void {
|
||||
this.Figma.exit(message);
|
||||
}
|
||||
|
||||
exit(): void {
|
||||
this.Figma.exit(
|
||||
`Finished updating variables for version ${this.meta.version}, last updated at ${this.meta.timestamp}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
/**
|
||||
* Acts as a wrapper for interfacing with the Figma PluginAPI
|
||||
* @see https://www.figma.com/plugin-docs/api/api-reference/
|
||||
*/
|
||||
export class FigmaPluginInterface {
|
||||
private collections: VariableCollection[];
|
||||
private variables: Variable[];
|
||||
|
||||
constructor() {
|
||||
this.collections = [];
|
||||
this.variables = [];
|
||||
}
|
||||
|
||||
async init() {
|
||||
console.info("Initializing plugin...");
|
||||
const collections =
|
||||
await figma.variables.getLocalVariableCollectionsAsync();
|
||||
const variables: Variable[] = [];
|
||||
|
||||
for (const collection of collections) {
|
||||
for (const id of collection.variableIds) {
|
||||
const variable = await figma.variables.getVariableByIdAsync(id);
|
||||
if (!variable) {
|
||||
throw new Error(
|
||||
`Variable with id ${id} not found in collection ${collection.name}`,
|
||||
);
|
||||
}
|
||||
variables.push(variable);
|
||||
}
|
||||
}
|
||||
|
||||
this.collections = collections;
|
||||
this.variables = variables;
|
||||
console.info("Plugin initialized!");
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Collection handling */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
getCollection(name: string): VariableCollection | undefined {
|
||||
return this.collections.find((col) => col.name === name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new collection if it doesn't already exist.
|
||||
* If it does exist, it will return the existing collection.
|
||||
*/
|
||||
createCollection(name: string): VariableCollection {
|
||||
const existingCollection = this.getCollection(name);
|
||||
if (existingCollection) {
|
||||
console.info("Collection already exists, skipping creation: ", name);
|
||||
return existingCollection;
|
||||
}
|
||||
|
||||
const collection = figma.variables.createVariableCollection(name);
|
||||
this.collections.push(collection);
|
||||
console.info("Creating new collection: ", name);
|
||||
|
||||
return collection;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Variable handling */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/**
|
||||
* Returns a variable if it exists in the specified collection.
|
||||
* Since variables can have the same name in different collections, the collectionId is required.
|
||||
*/
|
||||
getVariable(name: string, collectionId: string): Variable | undefined {
|
||||
return this.variables.find(
|
||||
(figmaVariable) =>
|
||||
figmaVariable.name === name &&
|
||||
figmaVariable.variableCollectionId === collectionId,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new variable if it doesn't already exist.
|
||||
* If it does exist, it will return the existing variable.
|
||||
*/
|
||||
createVariable(
|
||||
name: string,
|
||||
collection: VariableCollection,
|
||||
type: VariableResolvedDataType,
|
||||
): Variable {
|
||||
const existingVariable = this.getVariable(name, collection.id);
|
||||
if (existingVariable) {
|
||||
console.info("Variable already exists, skipping creation: ", name);
|
||||
return existingVariable;
|
||||
}
|
||||
|
||||
const variable = figma.variables.createVariable(name, collection, type);
|
||||
this.variables.push(variable);
|
||||
return variable;
|
||||
}
|
||||
|
||||
createVariableAlias(variable: Variable) {
|
||||
return figma.variables.createVariableAlias(variable);
|
||||
}
|
||||
|
||||
setVariableValue(
|
||||
variable: Variable,
|
||||
value: VariableValue,
|
||||
modeId: string,
|
||||
): void {
|
||||
variable.setValueForMode(modeId, value);
|
||||
}
|
||||
|
||||
setVariableMetadata(
|
||||
variable: Variable,
|
||||
metadata: Pick<
|
||||
Variable,
|
||||
"scopes" | "codeSyntax" | "hiddenFromPublishing" | "description"
|
||||
>,
|
||||
): void {
|
||||
const platforms: CodeSyntaxPlatform[] = ["WEB", "ANDROID", "iOS"];
|
||||
platforms.forEach((platform: CodeSyntaxPlatform) => {
|
||||
if (metadata.codeSyntax?.[platform]) {
|
||||
variable.setVariableCodeSyntax(platform, metadata.codeSyntax[platform]);
|
||||
}
|
||||
});
|
||||
|
||||
variable.scopes = metadata.scopes;
|
||||
variable.description = metadata.description ?? "";
|
||||
variable.hiddenFromPublishing = metadata.hiddenFromPublishing;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Mode handling */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
getModeWithName<T extends string>(
|
||||
name: T,
|
||||
collection: VariableCollection,
|
||||
):
|
||||
| {
|
||||
modeId: string;
|
||||
name: T;
|
||||
}
|
||||
| undefined {
|
||||
const foundMode = collection.modes.find((mode) => mode.name === name);
|
||||
|
||||
return foundMode ? { modeId: foundMode.modeId, name } : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new mode if it doesn't already exist.
|
||||
* If it does exist, it will return the existing mode.
|
||||
*/
|
||||
createMode<T extends string>(
|
||||
name: T,
|
||||
collection: VariableCollection,
|
||||
): {
|
||||
modeId: string;
|
||||
name: T;
|
||||
} {
|
||||
const existingMode = collection.modes.find((mode) => mode.name === name);
|
||||
if (existingMode) {
|
||||
console.info("Mode already exists, skipping creation: ", name);
|
||||
return { modeId: existingMode.modeId, name };
|
||||
}
|
||||
|
||||
return {
|
||||
modeId: collection.addMode(name),
|
||||
name,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all modes that are not in the provided list.
|
||||
* Usefull for removing "default" modes, while preserving the custom modes.
|
||||
*/
|
||||
removeNonMatchingModes(
|
||||
modes: string[],
|
||||
collection: VariableCollection,
|
||||
): {
|
||||
modeId: string;
|
||||
name: string;
|
||||
}[] {
|
||||
const existingModes = collection.modes;
|
||||
|
||||
for (const mode of existingModes) {
|
||||
if (!modes.find((m) => m === mode.name)) {
|
||||
collection.removeMode(mode.modeId);
|
||||
}
|
||||
}
|
||||
|
||||
return collection.modes;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Utilities */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/**
|
||||
* Resets all variables and collections in the document. Use with caution!
|
||||
*/
|
||||
resetVariables() {
|
||||
this.collections.forEach((collection) => {
|
||||
console.info("Deleting collection: ", collection.name);
|
||||
collection.remove();
|
||||
});
|
||||
this.collections = [];
|
||||
this.variables = [];
|
||||
}
|
||||
|
||||
exit(message?: string) {
|
||||
console.info("Exiting plugin...");
|
||||
figma.closePlugin(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "Aksel tokens to variables",
|
||||
"id": "1416051171368451170",
|
||||
"api": "1.0.0",
|
||||
"main": "plugin.js",
|
||||
"capabilities": [],
|
||||
"enableProposedApi": false,
|
||||
"editorType": ["figma"],
|
||||
"documentAccess": "dynamic-page",
|
||||
"networkAccess": {
|
||||
"allowedDomains": ["https://cdn.nav.no"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { AkselVariablesInterface } from "./AkselVariablesInterface";
|
||||
|
||||
const plugin = new AkselVariablesInterface();
|
||||
|
||||
main().catch((err) =>
|
||||
plugin.exitWithMessage(`Error updating local variables: ${err.message}`),
|
||||
);
|
||||
|
||||
async function main() {
|
||||
await plugin.init();
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
await plugin.useRemoteConfig();
|
||||
}
|
||||
|
||||
plugin.updateVariables();
|
||||
plugin.exit();
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
import {
|
||||
BackgroundColor,
|
||||
Color,
|
||||
ContrastColor,
|
||||
CssColor,
|
||||
Theme,
|
||||
} from "@adobe/leonardo-contrast-colors";
|
||||
import {
|
||||
ColorTheme,
|
||||
GlobalColorRoles,
|
||||
GlobalColorScale,
|
||||
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.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,
|
||||
},
|
||||
};
|
||||
|
||||
// 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,
|
||||
theme: ColorTheme,
|
||||
): CssColor => {
|
||||
/* We have to remove 'ContrastColorBackground' from first position */
|
||||
const leonardoTheme = (
|
||||
theme === "light" ? leonardoLightTheme : leonardoDarkTheme
|
||||
).slice(1) as ContrastColor[];
|
||||
|
||||
const colorContrastRole = leonardoTheme.find((c) => c.name === role);
|
||||
|
||||
if (!colorContrastRole) {
|
||||
throw new Error(`Color ${role} not found in leonardo config`);
|
||||
}
|
||||
|
||||
/**
|
||||
* @example colorContrastRole format
|
||||
* {
|
||||
* name: 'dataTwo',
|
||||
* values: [
|
||||
* { name: 'dataTwo100', contrast: 1.08, value: '#200500' },
|
||||
* { name: 'dataTwo200', contrast: 1.16, value: '#2b0f00' },
|
||||
* ...
|
||||
* ]
|
||||
*}
|
||||
*/
|
||||
const color = colorContrastRole.values.find(
|
||||
(value) => value.name === `${role}${scale}`,
|
||||
);
|
||||
|
||||
if (!color || !color.value) {
|
||||
throw new Error(`Color ${role}.${scale} not found in leonardo config`);
|
||||
}
|
||||
|
||||
return color.value;
|
||||
};
|
||||
|
||||
type GlobalColor = {
|
||||
value: string;
|
||||
type: "global-color";
|
||||
group: GlobalColorRoles;
|
||||
};
|
||||
|
||||
export const getGlobalScaleForColor = (
|
||||
role: GlobalColorRoles,
|
||||
theme: ColorTheme,
|
||||
): Record<GlobalColorScale, GlobalColor> => {
|
||||
return globalColorScales.reduce(
|
||||
(acc, scale) => {
|
||||
acc[scale] = {
|
||||
value: getColorScaleFromLeonardo(role, scale, theme),
|
||||
type: "global-color",
|
||||
group: role,
|
||||
};
|
||||
return acc;
|
||||
},
|
||||
{} as Record<GlobalColorScale, GlobalColor>,
|
||||
);
|
||||
};
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -8,60 +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",
|
||||
value: "{a.neutral.000.value}",
|
||||
type: "color",
|
||||
group: "contrast",
|
||||
},
|
||||
dataTwo: {
|
||||
value: "white",
|
||||
value: "{a.neutral.000.value}",
|
||||
type: "color",
|
||||
group: "contrast",
|
||||
},
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
import { GlobalConfigT } from "../util";
|
||||
|
||||
export const globalColorDarkModeConfig: GlobalConfigT = {
|
||||
neutral: {
|
||||
"000": {
|
||||
value: "black",
|
||||
type: "global-color",
|
||||
group: "neutral",
|
||||
},
|
||||
"100": { value: "#1a1d21", type: "global-color", group: "neutral" },
|
||||
"200": { value: "#202327", type: "global-color", group: "neutral" },
|
||||
"300": { value: "#282b2f", type: "global-color", group: "neutral" },
|
||||
"400": { value: "#33363a", type: "global-color", group: "neutral" },
|
||||
"500": { value: "#666d77", type: "global-color", group: "neutral" },
|
||||
"600": { value: "#787e89", type: "global-color", group: "neutral" },
|
||||
"700": { value: "#8c929d", type: "global-color", group: "neutral" },
|
||||
"800": { value: "#959ca5", type: "global-color", group: "neutral" },
|
||||
"900": { value: "#a6acb4", type: "global-color", group: "neutral" },
|
||||
"1000": { value: "#c5c9ce", type: "global-color", group: "neutral" },
|
||||
},
|
||||
accent: {
|
||||
"100": { value: "#101d2d", type: "global-color", group: "accent" },
|
||||
"200": { value: "#152436", type: "global-color", group: "accent" },
|
||||
"300": { value: "#1a2c41", type: "global-color", group: "accent" },
|
||||
"400": { value: "#23374f", type: "global-color", group: "accent" },
|
||||
"500": { value: "#2e6db8", type: "global-color", group: "accent" },
|
||||
"600": { value: "#447fc9", type: "global-color", group: "accent" },
|
||||
"700": { value: "#5f94d8", type: "global-color", group: "accent" },
|
||||
"800": { value: "#6d9ddd", type: "global-color", group: "accent" },
|
||||
"900": { value: "#84aee6", type: "global-color", group: "accent" },
|
||||
"1000": { value: "#b0caf1", type: "global-color", group: "accent" },
|
||||
},
|
||||
success: {
|
||||
"100": { value: "#00230b", type: "global-color", group: "success" },
|
||||
"200": { value: "#032911", type: "global-color", group: "success" },
|
||||
"300": { value: "#0b3118", type: "global-color", group: "success" },
|
||||
"400": { value: "#183d24", type: "global-color", group: "success" },
|
||||
"500": { value: "#207c41", type: "global-color", group: "success" },
|
||||
"600": { value: "#368e52", type: "global-color", group: "success" },
|
||||
"700": { value: "#4ca265", type: "global-color", group: "success" },
|
||||
"800": { value: "#5eab72", type: "global-color", group: "success" },
|
||||
"900": { value: "#78ba87", type: "global-color", group: "success" },
|
||||
"1000": { value: "#a4d4ad", type: "global-color", group: "success" },
|
||||
},
|
||||
warning: {
|
||||
"100": { value: "#371100", type: "global-color", group: "warning" },
|
||||
"200": { value: "#3e1704", type: "global-color", group: "warning" },
|
||||
"300": { value: "#471f0c", type: "global-color", group: "warning" },
|
||||
"400": { value: "#542b18", type: "global-color", group: "warning" },
|
||||
"500": { value: "#af5122", type: "global-color", group: "warning" },
|
||||
"600": { value: "#c46233", type: "global-color", group: "warning" },
|
||||
"700": { value: "#de753c", type: "global-color", group: "warning" },
|
||||
"800": { value: "#e78040", type: "global-color", group: "warning" },
|
||||
"900": { value: "#ed974b", type: "global-color", group: "warning" },
|
||||
"1000": { value: "#f5bf6d", type: "global-color", group: "warning" },
|
||||
},
|
||||
danger: {
|
||||
"100": { value: "#410409", type: "global-color", group: "danger" },
|
||||
"200": { value: "#490b10", type: "global-color", group: "danger" },
|
||||
"300": { value: "#521517", type: "global-color", group: "danger" },
|
||||
"400": { value: "#5f2021", type: "global-color", group: "danger" },
|
||||
"500": { value: "#c8343f", type: "global-color", group: "danger" },
|
||||
"600": { value: "#dd4a50", type: "global-color", group: "danger" },
|
||||
"700": { value: "#eb6772", type: "global-color", group: "danger" },
|
||||
"800": { value: "#ee7685", type: "global-color", group: "danger" },
|
||||
"900": { value: "#f28ea2", type: "global-color", group: "danger" },
|
||||
"1000": { value: "#f6b7cd", type: "global-color", group: "danger" },
|
||||
},
|
||||
/* Info is currently just a copy of brandThree */
|
||||
info: {
|
||||
"100": { value: "#131e26", type: "global-color", group: "brandThree" },
|
||||
"200": { value: "#18242c", type: "global-color", group: "brandThree" },
|
||||
"300": { value: "#1e2c35", type: "global-color", group: "brandThree" },
|
||||
"400": { value: "#273843", type: "global-color", group: "brandThree" },
|
||||
"500": { value: "#43718d", type: "global-color", group: "brandThree" },
|
||||
"600": { value: "#5a839b", type: "global-color", group: "brandThree" },
|
||||
"700": { value: "#7396ab", type: "global-color", group: "brandThree" },
|
||||
"800": { value: "#7f9fb3", type: "global-color", group: "brandThree" },
|
||||
"900": { value: "#93afc0", type: "global-color", group: "brandThree" },
|
||||
"1000": { value: "#b7cbd7", type: "global-color", group: "brandThree" },
|
||||
},
|
||||
brandOne: {
|
||||
"100": { value: "#321220", type: "global-color", group: "brandOne" },
|
||||
"200": { value: "#3a1827", type: "global-color", group: "brandOne" },
|
||||
"300": { value: "#42202e", type: "global-color", group: "brandOne" },
|
||||
"400": { value: "#4e2a39", type: "global-color", group: "brandOne" },
|
||||
"500": { value: "#a35276", type: "global-color", group: "brandOne" },
|
||||
"600": { value: "#b06886", type: "global-color", group: "brandOne" },
|
||||
"700": { value: "#be7f99", type: "global-color", group: "brandOne" },
|
||||
"800": { value: "#c58aa2", type: "global-color", group: "brandOne" },
|
||||
"900": { value: "#cf9eb1", type: "global-color", group: "brandOne" },
|
||||
"1000": { value: "#e1c0cc", type: "global-color", group: "brandOne" },
|
||||
},
|
||||
brandTwo: {
|
||||
"100": { value: "#2d160e", type: "global-color", group: "brandTwo" },
|
||||
"200": { value: "#351d15", type: "global-color", group: "brandTwo" },
|
||||
"300": { value: "#3d241c", type: "global-color", group: "brandTwo" },
|
||||
"400": { value: "#493027", type: "global-color", group: "brandTwo" },
|
||||
"500": { value: "#975e4a", type: "global-color", group: "brandTwo" },
|
||||
"600": { value: "#ab6f5b", type: "global-color", group: "brandTwo" },
|
||||
"700": { value: "#c0836e", type: "global-color", group: "brandTwo" },
|
||||
"800": { value: "#c78e79", type: "global-color", group: "brandTwo" },
|
||||
"900": { value: "#d1a08c", type: "global-color", group: "brandTwo" },
|
||||
"1000": { value: "#e3c2b1", type: "global-color", group: "brandTwo" },
|
||||
},
|
||||
brandThree: {
|
||||
"100": { value: "#131e26", type: "global-color", group: "brandThree" },
|
||||
"200": { value: "#18242c", type: "global-color", group: "brandThree" },
|
||||
"300": { value: "#1e2c35", type: "global-color", group: "brandThree" },
|
||||
"400": { value: "#273843", type: "global-color", group: "brandThree" },
|
||||
"500": { value: "#43718d", type: "global-color", group: "brandThree" },
|
||||
"600": { value: "#5a839b", type: "global-color", group: "brandThree" },
|
||||
"700": { value: "#7396ab", type: "global-color", group: "brandThree" },
|
||||
"800": { value: "#7f9fb3", type: "global-color", group: "brandThree" },
|
||||
"900": { value: "#93afc0", type: "global-color", group: "brandThree" },
|
||||
"1000": { value: "#b7cbd7", type: "global-color", group: "brandThree" },
|
||||
},
|
||||
dataOne: {
|
||||
"100": { value: "#221a2c", type: "global-color", group: "dataOne" },
|
||||
"200": { value: "#272032", type: "global-color", group: "dataOne" },
|
||||
"300": { value: "#2f273a", type: "global-color", group: "dataOne" },
|
||||
"400": { value: "#3a3245", type: "global-color", group: "dataOne" },
|
||||
"500": { value: "#776491", type: "global-color", group: "dataOne" },
|
||||
"600": { value: "#8876a1", type: "global-color", group: "dataOne" },
|
||||
"700": { value: "#9b8bb0", type: "global-color", group: "dataOne" },
|
||||
"800": { value: "#a495b7", type: "global-color", group: "dataOne" },
|
||||
"900": { value: "#b2a7c3", type: "global-color", group: "dataOne" },
|
||||
"1000": { value: "#cdc5d9", type: "global-color", group: "dataOne" },
|
||||
},
|
||||
dataTwo: {
|
||||
"100": { value: "#1c1f00", type: "global-color", group: "dataTwo" },
|
||||
"200": { value: "#222500", type: "global-color", group: "dataTwo" },
|
||||
"300": { value: "#292d00", type: "global-color", group: "dataTwo" },
|
||||
"400": { value: "#343900", type: "global-color", group: "dataTwo" },
|
||||
"500": { value: "#6a7200", type: "global-color", group: "dataTwo" },
|
||||
"600": { value: "#7c8400", type: "global-color", group: "dataTwo" },
|
||||
"700": { value: "#8f9800", type: "global-color", group: "dataTwo" },
|
||||
"800": { value: "#99a200", type: "global-color", group: "dataTwo" },
|
||||
"900": { value: "#a9b325", type: "global-color", group: "dataTwo" },
|
||||
"1000": { value: "#c5d14c", type: "global-color", group: "dataTwo" },
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,141 @@
|
||||
import { GlobalConfigT } from "../util";
|
||||
|
||||
export const globalColorLightModeConfig: GlobalConfigT = {
|
||||
neutral: {
|
||||
"000": {
|
||||
value: "white",
|
||||
type: "global-color",
|
||||
group: "neutral",
|
||||
},
|
||||
"100": { value: "#f5f6f7", type: "global-color", group: "neutral" },
|
||||
"200": { value: "#ecedef", type: "global-color", group: "neutral" },
|
||||
"300": { value: "#e1e3e7", type: "global-color", group: "neutral" },
|
||||
"400": { value: "#cfd3d8", type: "global-color", group: "neutral" },
|
||||
"500": { value: "#818997", type: "global-color", group: "neutral" },
|
||||
"600": { value: "#6f7785", type: "global-color", group: "neutral" },
|
||||
"700": { value: "#5d6573", type: "global-color", group: "neutral" },
|
||||
"800": { value: "#555d6a", type: "global-color", group: "neutral" },
|
||||
"900": { value: "#49515e", type: "global-color", group: "neutral" },
|
||||
"1000": { value: "#202733", type: "global-color", group: "neutral" },
|
||||
},
|
||||
accent: {
|
||||
"100": { value: "#f1f7ff", type: "global-color", group: "accent" },
|
||||
"200": { value: "#e4eeff", type: "global-color", group: "accent" },
|
||||
"300": { value: "#d4e5fd", type: "global-color", group: "accent" },
|
||||
"400": { value: "#bad5fb", type: "global-color", group: "accent" },
|
||||
"500": { value: "#428ae3", type: "global-color", group: "accent" },
|
||||
"600": { value: "#2277d5", type: "global-color", group: "accent" },
|
||||
"700": { value: "#0063c1", type: "global-color", group: "accent" },
|
||||
"800": { value: "#005bb6", type: "global-color", group: "accent" },
|
||||
"900": { value: "#004ea3", type: "global-color", group: "accent" },
|
||||
"1000": { value: "#002459", type: "global-color", group: "accent" },
|
||||
},
|
||||
success: {
|
||||
"100": { value: "#e2fde8", type: "global-color", group: "success" },
|
||||
"200": { value: "#d5f6db", type: "global-color", group: "success" },
|
||||
"300": { value: "#c4edcd", type: "global-color", group: "success" },
|
||||
"400": { value: "#a8dfb4", type: "global-color", group: "success" },
|
||||
"500": { value: "#199d4f", type: "global-color", group: "success" },
|
||||
"600": { value: "#00893c", type: "global-color", group: "success" },
|
||||
"700": { value: "#007629", type: "global-color", group: "success" },
|
||||
"800": { value: "#006c1f", type: "global-color", group: "success" },
|
||||
"900": { value: "#005e0f", type: "global-color", group: "success" },
|
||||
"1000": { value: "#002e00", type: "global-color", group: "success" },
|
||||
},
|
||||
warning: {
|
||||
"100": { value: "#fff5e4", type: "global-color", group: "warning" },
|
||||
"200": { value: "#ffebc7", type: "global-color", group: "warning" },
|
||||
"300": { value: "#ffdea5", type: "global-color", group: "warning" },
|
||||
"400": { value: "#ffcb6f", type: "global-color", group: "warning" },
|
||||
"500": { value: "#e75e01", type: "global-color", group: "warning" },
|
||||
"600": { value: "#c95100", type: "global-color", group: "warning" },
|
||||
"700": { value: "#ac4400", type: "global-color", group: "warning" },
|
||||
"800": { value: "#a03e00", type: "global-color", group: "warning" },
|
||||
"900": { value: "#8c3500", type: "global-color", group: "warning" },
|
||||
"1000": { value: "#481700", type: "global-color", group: "warning" },
|
||||
},
|
||||
danger: {
|
||||
"100": { value: "#fff3f7", type: "global-color", group: "danger" },
|
||||
"200": { value: "#ffe9f0", type: "global-color", group: "danger" },
|
||||
"300": { value: "#ffdae6", type: "global-color", group: "danger" },
|
||||
"400": { value: "#ffc2d7", type: "global-color", group: "danger" },
|
||||
"500": { value: "#fa4256", type: "global-color", group: "danger" },
|
||||
"600": { value: "#e81c3b", type: "global-color", group: "danger" },
|
||||
"700": { value: "#cc0023", type: "global-color", group: "danger" },
|
||||
"800": { value: "#bd0015", type: "global-color", group: "danger" },
|
||||
"900": { value: "#a60000", type: "global-color", group: "danger" },
|
||||
"1000": { value: "#560000", type: "global-color", group: "danger" },
|
||||
},
|
||||
info: {
|
||||
"100": { value: "#eef6fc", type: "global-color", group: "info" },
|
||||
"200": { value: "#e3eff7", type: "global-color", group: "info" },
|
||||
"300": { value: "#d7e6f0", type: "global-color", group: "info" },
|
||||
"400": { value: "#c0d6e4", type: "global-color", group: "info" },
|
||||
"500": { value: "#5a8fae", type: "global-color", group: "info" },
|
||||
"600": { value: "#417da0", type: "global-color", group: "info" },
|
||||
"700": { value: "#246b91", type: "global-color", group: "info" },
|
||||
"800": { value: "#156389", type: "global-color", group: "info" },
|
||||
"900": { value: "#00557d", type: "global-color", group: "info" },
|
||||
"1000": { value: "#002942", type: "global-color", group: "info" },
|
||||
},
|
||||
brandOne: {
|
||||
"100": { value: "#fbf3f6", type: "global-color", group: "brandOne" },
|
||||
"200": { value: "#f8eaef", type: "global-color", group: "brandOne" },
|
||||
"300": { value: "#f3dde5", type: "global-color", group: "brandOne" },
|
||||
"400": { value: "#eccad6", type: "global-color", group: "brandOne" },
|
||||
"500": { value: "#c26f92", type: "global-color", group: "brandOne" },
|
||||
"600": { value: "#b65781", type: "global-color", group: "brandOne" },
|
||||
"700": { value: "#a93d70", type: "global-color", group: "brandOne" },
|
||||
"800": { value: "#a33069", type: "global-color", group: "brandOne" },
|
||||
"900": { value: "#98185d", type: "global-color", group: "brandOne" },
|
||||
"1000": { value: "#52002b", type: "global-color", group: "brandOne" },
|
||||
},
|
||||
brandTwo: {
|
||||
"100": { value: "#fff4ee", type: "global-color", group: "brandTwo" },
|
||||
"200": { value: "#fdebe0", type: "global-color", group: "brandTwo" },
|
||||
"300": { value: "#fcddcd", type: "global-color", group: "brandTwo" },
|
||||
"400": { value: "#f8c8b1", type: "global-color", group: "brandTwo" },
|
||||
"500": { value: "#c0765d", type: "global-color", group: "brandTwo" },
|
||||
"600": { value: "#a9654e", type: "global-color", group: "brandTwo" },
|
||||
"700": { value: "#915541", type: "global-color", group: "brandTwo" },
|
||||
"800": { value: "#874e3b", type: "global-color", group: "brandTwo" },
|
||||
"900": { value: "#764332", type: "global-color", group: "brandTwo" },
|
||||
"1000": { value: "#3c1f15", type: "global-color", group: "brandTwo" },
|
||||
},
|
||||
brandThree: {
|
||||
"100": { value: "#eef6fc", type: "global-color", group: "brandThree" },
|
||||
"200": { value: "#e3eff7", type: "global-color", group: "brandThree" },
|
||||
"300": { value: "#d7e6f0", type: "global-color", group: "brandThree" },
|
||||
"400": { value: "#c0d6e4", type: "global-color", group: "brandThree" },
|
||||
"500": { value: "#5a8fae", type: "global-color", group: "brandThree" },
|
||||
"600": { value: "#417da0", type: "global-color", group: "brandThree" },
|
||||
"700": { value: "#246b91", type: "global-color", group: "brandThree" },
|
||||
"800": { value: "#156389", type: "global-color", group: "brandThree" },
|
||||
"900": { value: "#00557d", type: "global-color", group: "brandThree" },
|
||||
"1000": { value: "#002942", type: "global-color", group: "brandThree" },
|
||||
},
|
||||
dataOne: {
|
||||
"100": { value: "#f7f5f9", type: "global-color", group: "dataOne" },
|
||||
"200": { value: "#f0edf4", type: "global-color", group: "dataOne" },
|
||||
"300": { value: "#e6e1ed", type: "global-color", group: "dataOne" },
|
||||
"400": { value: "#d7cfe3", type: "global-color", group: "dataOne" },
|
||||
"500": { value: "#9580b1", type: "global-color", group: "dataOne" },
|
||||
"600": { value: "#846ca4", type: "global-color", group: "dataOne" },
|
||||
"700": { value: "#725a91", type: "global-color", group: "dataOne" },
|
||||
"800": { value: "#6a5289", type: "global-color", group: "dataOne" },
|
||||
"900": { value: "#5e467c", type: "global-color", group: "dataOne" },
|
||||
"1000": { value: "#331a4d", type: "global-color", group: "dataOne" },
|
||||
},
|
||||
dataTwo: {
|
||||
"100": { value: "#f4f9d1", type: "global-color", group: "dataTwo" },
|
||||
"200": { value: "#ebf4a9", type: "global-color", group: "dataTwo" },
|
||||
"300": { value: "#e0ec6b", type: "global-color", group: "dataTwo" },
|
||||
"400": { value: "#d0dc00", type: "global-color", group: "dataTwo" },
|
||||
"500": { value: "#878f00", type: "global-color", group: "dataTwo" },
|
||||
"600": { value: "#757c00", type: "global-color", group: "dataTwo" },
|
||||
"700": { value: "#646900", type: "global-color", group: "dataTwo" },
|
||||
"800": { value: "#5c6100", type: "global-color", group: "dataTwo" },
|
||||
"900": { value: "#515400", type: "global-color", group: "dataTwo" },
|
||||
"1000": { value: "#2a2800", type: "global-color", group: "dataTwo" },
|
||||
},
|
||||
};
|
||||
@@ -1,23 +0,0 @@
|
||||
import { ColorTheme, StyleDictionaryTokenConfig } from "../util";
|
||||
|
||||
const NeutralScaleLight = (): StyleDictionaryTokenConfig<"global-color"> => ({
|
||||
"000": {
|
||||
value: "white",
|
||||
type: "global-color",
|
||||
group: "neutral",
|
||||
},
|
||||
});
|
||||
|
||||
const NeutralScaleDark = (): StyleDictionaryTokenConfig<"global-color"> => ({
|
||||
"000": {
|
||||
value: "black",
|
||||
type: "global-color",
|
||||
group: "neutral",
|
||||
},
|
||||
});
|
||||
|
||||
export const neutralTokenConfig = (theme: ColorTheme) => {
|
||||
return {
|
||||
neutral: theme === "light" ? NeutralScaleLight() : NeutralScaleDark(),
|
||||
};
|
||||
};
|
||||
@@ -34,14 +34,14 @@ const configForRole = (
|
||||
type: "color",
|
||||
group: `background.${role}`,
|
||||
},
|
||||
[`${role}-moderate-active`]: {
|
||||
[`${role}-moderate-pressed`]: {
|
||||
value: `{a.${role}.400.value}`,
|
||||
type: "color",
|
||||
group: `background.${role}`,
|
||||
},
|
||||
/* Should match role-strong */
|
||||
[`${role}-moderate-selected`]: {
|
||||
value: `{a.${role}.600.value}`,
|
||||
value: `{a.${role}.700.value}`,
|
||||
type: "color",
|
||||
group: `background.${role}`,
|
||||
},
|
||||
@@ -55,7 +55,7 @@ const configForRole = (
|
||||
type: "color",
|
||||
group: `background.${role}`,
|
||||
},
|
||||
[`${role}-strong-active`]: {
|
||||
[`${role}-strong-pressed`]: {
|
||||
value: `{a.${role}.800.value}`,
|
||||
type: "color",
|
||||
group: `background.${role}`,
|
||||
@@ -73,12 +73,12 @@ const configForRole = (
|
||||
},
|
||||
text: {
|
||||
[role]: {
|
||||
value: `{a.${role}.900.value}`,
|
||||
value: `{a.${role}.700.value}`,
|
||||
type: "color",
|
||||
group: `text.${role}`,
|
||||
},
|
||||
[`${role}-subtle`]: {
|
||||
value: `{a.${role}.700.value}`,
|
||||
[`${role}-strong`]: {
|
||||
value: `{a.${role}.900.value}`,
|
||||
type: "color",
|
||||
group: `text.${role}`,
|
||||
},
|
||||
@@ -90,7 +90,7 @@ const configForRole = (
|
||||
},
|
||||
border: {
|
||||
[role]: {
|
||||
value: `{a.${role}.600.value}`,
|
||||
value: `{a.${role}.500.value}`,
|
||||
type: "color",
|
||||
group: `border.${role}`,
|
||||
},
|
||||
@@ -100,7 +100,7 @@ const configForRole = (
|
||||
group: `border.${role}`,
|
||||
},
|
||||
[`${role}-strong`]: {
|
||||
value: `{a.${role}.700.value}`,
|
||||
value: `{a.${role}.600.value}`,
|
||||
type: "color",
|
||||
group: `border.${role}`,
|
||||
},
|
||||
|
||||
@@ -1,75 +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: "#0E151F",
|
||||
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.600.value}",
|
||||
type: "color",
|
||||
group: "border",
|
||||
},
|
||||
subtle: {
|
||||
value: "{a.neutral.400.value}",
|
||||
type: "color",
|
||||
group: "border",
|
||||
},
|
||||
strong: {
|
||||
value: "{a.neutral.700.value}",
|
||||
type: "color",
|
||||
group: "border",
|
||||
},
|
||||
/**
|
||||
* TODO: Need to verify this value
|
||||
* - Discuss with brand
|
||||
*/
|
||||
focus: {
|
||||
value: "{a.accent.700.value}",
|
||||
type: "color",
|
||||
group: "border",
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -1,5 +1,16 @@
|
||||
import _ from "lodash";
|
||||
|
||||
type GlobalColorEntryT = {
|
||||
value: string;
|
||||
type: "global-color";
|
||||
group: GlobalColorRoles;
|
||||
};
|
||||
|
||||
export type GlobalConfigT = Record<
|
||||
GlobalColorRoles,
|
||||
Record<GlobalColorScale, GlobalColorEntryT> & { "000"?: GlobalColorEntryT }
|
||||
>;
|
||||
|
||||
export const colorThemes = ["light", "dark"] as const;
|
||||
export type ColorTheme = (typeof colorThemes)[number];
|
||||
|
||||
@@ -34,17 +45,29 @@ 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 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<
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-tokens",
|
||||
"version": "7.1.0",
|
||||
"version": "7.1.2",
|
||||
"description": "Design-tokens for NAV designsystem",
|
||||
"author": "Aksel | NAV designsystem team",
|
||||
"keywords": [
|
||||
@@ -19,7 +19,10 @@
|
||||
],
|
||||
"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 && yarn build:plugin",
|
||||
"build:figma-config": "tsx ./darkside/figma",
|
||||
"build:plugin-dev": "esbuild darkside/figma/plugin/plugin.ts --target=es2016 --bundle --define:process.env.NODE_ENV=\\\"development\\\" --outfile='darkside/figma/plugin/plugin.js'",
|
||||
"build:plugin": "esbuild darkside/figma/plugin/plugin.ts --target=es2016 --bundle --define:process.env.NODE_ENV=\\\"production\\\" --outfile='darkside/figma/plugin/plugin.js'",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest watch"
|
||||
},
|
||||
@@ -29,8 +32,9 @@
|
||||
"directory": "@navikt/core/tokens"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@adobe/leonardo-contrast-colors": "^1.0.0",
|
||||
"@figma/plugin-typings": "^1.100.2",
|
||||
"color": "4.2.3",
|
||||
"esbuild": "^0.24.0",
|
||||
"lightningcss": "^1.27.0",
|
||||
"lodash": "^4.17.21",
|
||||
"style-dictionary": "^4.1.1",
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
+18
-4
@@ -4,18 +4,32 @@
|
||||
|
||||
### @navikt/aksel-stylelint
|
||||
|
||||
- Pagination: :boom: Added some classNames to list of deprecated (removed) classes. ([#3164](https://github.com/navikt/aksel/pull/3164))
|
||||
- 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
|
||||
|
||||
- Pagination: :lipstick: Updated font-weight to 600 and marked 'page'-prop on 'Item' as deprecated. ([#3164](https://github.com/navikt/aksel/pull/3164))
|
||||
- List: Add indentation and remove overly verbose aria attributes ([#3184](https://github.com/navikt/aksel/pull/3184))
|
||||
|
||||
* Combobox: :label: Better typing for maxSelected prop ([#3169](https://github.com/navikt/aksel/pull/3169))
|
||||
- Combobox: :bug: implement missing readonly and disabled ([#3180](https://github.com/navikt/aksel/pull/3180))
|
||||
|
||||
* ErrorSummary: Remove aria-attributes. ([#3171](https://github.com/navikt/aksel/pull/3171))
|
||||
* 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: Explicitly set list-style on ol to override reset-CSS ([#3202](https://github.com/navikt/aksel/pull/3202))
|
||||
|
||||
* 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
|
||||
|
||||
@@ -15,10 +15,10 @@
|
||||
"playroom"
|
||||
],
|
||||
"dependencies": {
|
||||
"@navikt/aksel-icons": "^7.1.0",
|
||||
"@navikt/ds-css": "^7.1.0",
|
||||
"@navikt/ds-react": "^7.1.0",
|
||||
"@navikt/ds-tailwind": "^7.1.0",
|
||||
"@navikt/ds-tokens": "^7.1.0"
|
||||
"@navikt/aksel-icons": "^7.1.2",
|
||||
"@navikt/ds-css": "^7.1.2",
|
||||
"@navikt/ds-react": "^7.1.2",
|
||||
"@navikt/ds-tailwind": "^7.1.2",
|
||||
"@navikt/ds-tokens": "^7.1.2"
|
||||
}
|
||||
}
|
||||
@@ -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" />
|
||||
|
||||
@@ -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,12 +38,12 @@
|
||||
"@portabletext/react": "^3.1.0",
|
||||
"@sanity/client": "^6.18.0",
|
||||
"@sanity/code-input": "4.1.4",
|
||||
"@sanity/color-input": "^3.1.1",
|
||||
"@sanity/color-input": "^4.0.1",
|
||||
"@sanity/image-url": "1.0.2",
|
||||
"@sanity/locale-nb-no": "^1.1.11",
|
||||
"@sanity/preview-url-secret": "^1.6.20",
|
||||
"@sanity/locale-nb-no": "^1.1.14",
|
||||
"@sanity/preview-url-secret": "^1.6.21",
|
||||
"@sanity/table": "1.1.2",
|
||||
"@sanity/vision": "3.55.0",
|
||||
"@sanity/vision": "3.59.0",
|
||||
"@slack/web-api": "^7.0.2",
|
||||
"boring-avatars": "1.10.1",
|
||||
"clsx": "^2.1.0",
|
||||
@@ -55,7 +55,7 @@
|
||||
"lz-string": "^1.5.0",
|
||||
"next": "^14.2.12",
|
||||
"next-logger": "^4.0.0",
|
||||
"next-sanity": "9.4.5",
|
||||
"next-sanity": "9.5.0",
|
||||
"node-cache": "^5.1.2",
|
||||
"pino": "^8.21.0",
|
||||
"prism-react-renderer": "^2.0.0",
|
||||
@@ -66,7 +66,7 @@
|
||||
"react-responsive-masonry": "2.2.0",
|
||||
"react-rx": "^2.1.3",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"sanity": "3.55.0",
|
||||
"sanity": "3.59.0",
|
||||
"sanity-plugin-media": "^2.3.2",
|
||||
"sharp": "0.32.6",
|
||||
"styled-components": "^6.0.0",
|
||||
@@ -81,8 +81,8 @@
|
||||
"@next/bundle-analyzer": "^14.2.3",
|
||||
"@next/eslint-plugin-next": "^14.2.3",
|
||||
"@playwright/test": "1.35.1",
|
||||
"@sanity/block-tools": "3.55.0",
|
||||
"@sanity/schema": "3.55.0",
|
||||
"@sanity/block-tools": "3.59.0",
|
||||
"@sanity/schema": "3.59.0",
|
||||
"@types/jscodeshift": "^0.11.11",
|
||||
"@types/react": "^18.0.0",
|
||||
"@types/react-dom": "^18.0.0",
|
||||
@@ -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).",
|
||||
};
|
||||
File renamed without changes.
@@ -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.",
|
||||
};
|
||||
@@ -15,7 +15,7 @@ export const ArticleViews = defineType({
|
||||
prepare: (selected) => ({
|
||||
title: selected.title,
|
||||
subtitle: selected.url,
|
||||
media: LineGraphIcon,
|
||||
media: () => <LineGraphIcon aria-hidden />,
|
||||
}),
|
||||
},
|
||||
orderings: [
|
||||
|
||||
@@ -19,7 +19,7 @@ export const BloggLandingSide = defineType({
|
||||
preview: {
|
||||
prepare: () => ({
|
||||
title: "Landingsside produktbloggen",
|
||||
media: HouseIcon,
|
||||
media: () => <HouseIcon aria-hidden />,
|
||||
}),
|
||||
},
|
||||
});
|
||||
@@ -43,7 +43,7 @@ export const GrunnleggendeLandingSide = defineType({
|
||||
preview: {
|
||||
prepare: () => ({
|
||||
title: "Landingsside grunnleggende",
|
||||
media: HouseIcon,
|
||||
media: () => <HouseIcon aria-hidden />,
|
||||
}),
|
||||
},
|
||||
});
|
||||
@@ -43,7 +43,7 @@ export const KomponentLandingSide = defineType({
|
||||
preview: {
|
||||
prepare: () => ({
|
||||
title: "Landingsside komponenter",
|
||||
media: HouseIcon,
|
||||
media: () => <HouseIcon aria-hidden />,
|
||||
}),
|
||||
},
|
||||
});
|
||||
@@ -35,7 +35,10 @@ export const artikkelPreview = (_type: string, threshold: number = 12) => {
|
||||
? `${(tema ?? kategori) && "/ "}${tema ?? kategori ?? ``}`
|
||||
: ""
|
||||
}`,
|
||||
media: isAfter(updateInfo, threshold) && HourglassBottomFilledIcon,
|
||||
media: () =>
|
||||
isAfter(updateInfo, threshold) ? (
|
||||
<HourglassBottomFilledIcon aria-hidden />
|
||||
) : undefined,
|
||||
};
|
||||
}
|
||||
return {
|
||||
|
||||
@@ -15,7 +15,7 @@ export const PrinsipperLandingSide = defineType({
|
||||
preview: {
|
||||
prepare: () => ({
|
||||
title: "Landingsside prinsipper",
|
||||
media: HouseIcon,
|
||||
media: () => <HouseIcon aria-hidden />,
|
||||
}),
|
||||
},
|
||||
});
|
||||
@@ -43,7 +43,7 @@ export const TemplatesLandingSide = defineType({
|
||||
preview: {
|
||||
prepare: () => ({
|
||||
title: "Landingsside Mønster og Maler",
|
||||
media: HouseIcon,
|
||||
media: () => <HouseIcon aria-hidden />,
|
||||
}),
|
||||
},
|
||||
});
|
||||
@@ -5,7 +5,7 @@ export const KodeEksempler = defineType({
|
||||
title: "Komponent-eksempel",
|
||||
name: "kode_eksempler",
|
||||
type: "object",
|
||||
icon: ComponentIcon,
|
||||
icon: () => <ComponentIcon aria-hidden />,
|
||||
fields: [
|
||||
defineField({
|
||||
title: "tittel",
|
||||
@@ -38,7 +38,7 @@ export const KodeEksempler = defineType({
|
||||
return {
|
||||
title: "Komponent-demo",
|
||||
subtitle: `${variant} | ${dir}`,
|
||||
media: ComponentIcon,
|
||||
media: () => <ComponentIcon aria-hidden />,
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
@@ -5,7 +5,7 @@ export const PropsSeksjon = defineType({
|
||||
title: "Props",
|
||||
name: "props_seksjon",
|
||||
type: "object",
|
||||
icon: BulletListIcon,
|
||||
icon: () => <BulletListIcon aria-hidden />,
|
||||
fields: [
|
||||
defineField({
|
||||
title: "Tittel (h2)",
|
||||
@@ -51,7 +51,7 @@ export const PropsSeksjon = defineType({
|
||||
prepare() {
|
||||
return {
|
||||
title: "Props",
|
||||
media: BulletListIcon,
|
||||
media: () => <BulletListIcon aria-hidden />,
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
@@ -5,7 +5,7 @@ export const TastaturUU = defineType({
|
||||
title: "Tastatur",
|
||||
name: "tastatur_modul",
|
||||
type: "object",
|
||||
icon: KeyHorizontalIcon,
|
||||
icon: () => <KeyHorizontalIcon aria-hidden />,
|
||||
fields: [
|
||||
defineField({
|
||||
type: "array",
|
||||
|
||||
@@ -7,5 +7,5 @@ export const TokenTabell = defineType({
|
||||
type: "reference",
|
||||
to: [{ type: "token_kategori" }],
|
||||
validation: (Rule) => Rule.required(),
|
||||
icon: TokenIcon,
|
||||
icon: () => <TokenIcon aria-hidden />,
|
||||
});
|
||||
@@ -6,7 +6,7 @@ export const HeroBilde = defineType({
|
||||
title: "Hero bilde",
|
||||
name: "herobilde",
|
||||
type: "image",
|
||||
icon: ImageIcon,
|
||||
icon: () => <ImageIcon aria-hidden />,
|
||||
options: {
|
||||
hotspot: true,
|
||||
},
|
||||
|
||||
@@ -8,7 +8,7 @@ export const Accordion = defineType({
|
||||
description:
|
||||
"Accordion brukes hvis man har flere seksjoner man ønsker å skjule. Bruke ExpansionCard om du bare skal vise en seksjon",
|
||||
type: "object",
|
||||
icon: Density3Icon,
|
||||
icon: () => <Density3Icon aria-hidden />,
|
||||
fields: [
|
||||
defineField({
|
||||
title: "Accordion-liste",
|
||||
@@ -69,7 +69,11 @@ export const Accordion = defineType({
|
||||
list: "list",
|
||||
},
|
||||
prepare({ ...props }) {
|
||||
return { title: "Accordion", media: Density3Icon, list: props?.list };
|
||||
return {
|
||||
title: "Accordion",
|
||||
media: () => <Density3Icon aria-hidden />,
|
||||
list: props?.list,
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -6,7 +6,7 @@ export const Alert = defineType({
|
||||
name: "alert",
|
||||
title: "Alert",
|
||||
type: "object",
|
||||
icon: ExclamationmarkTriangleIcon,
|
||||
icon: () => <ExclamationmarkTriangleIcon aria-hidden />,
|
||||
fields: [
|
||||
defineField({
|
||||
title: "Variant",
|
||||
@@ -62,7 +62,7 @@ export const Alert = defineType({
|
||||
return {
|
||||
title: toPlainText(selection?.body),
|
||||
subtitle: `Alert - ${selection.variant}`,
|
||||
media: ExclamationmarkTriangleIcon,
|
||||
media: () => <ExclamationmarkTriangleIcon aria-hidden />,
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
@@ -5,7 +5,7 @@ export const Attachment = defineType({
|
||||
title: "Vedlegg med nedlastning",
|
||||
name: "attachment",
|
||||
type: "file",
|
||||
icon: DownloadIcon,
|
||||
icon: () => <DownloadIcon aria-hidden />,
|
||||
fields: [
|
||||
defineField({
|
||||
title: "Tittel",
|
||||
@@ -34,7 +34,7 @@ export const Attachment = defineType({
|
||||
return {
|
||||
title: selection.title,
|
||||
subtitle: "Vedlegg med nedlastning",
|
||||
media: DownloadIcon,
|
||||
media: () => <DownloadIcon aria-hidden />,
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
@@ -5,7 +5,7 @@ export const Bilde = defineType({
|
||||
title: "Bilde",
|
||||
name: "bilde",
|
||||
type: "image",
|
||||
icon: ImageIcon,
|
||||
icon: () => <ImageIcon aria-hidden />,
|
||||
options: {
|
||||
hotspot: true,
|
||||
},
|
||||
|
||||
@@ -6,7 +6,7 @@ export const DoDont = defineType({
|
||||
title: "Do / Dont",
|
||||
name: "do_dont",
|
||||
type: "object",
|
||||
icon: CheckmarkCircleIcon,
|
||||
icon: () => <CheckmarkCircleIcon aria-hidden />,
|
||||
fields: [
|
||||
defineField({
|
||||
type: "array",
|
||||
|
||||
@@ -16,7 +16,7 @@ export const ExpansionCard = defineType({
|
||||
description:
|
||||
"ExpansionCard brukes hvis man har en seksjon man ønsker å skjule. Bruke Accordion om du trenger flere seksjoner etter hverandre",
|
||||
type: "object",
|
||||
icon: ChevronDownIcon,
|
||||
icon: () => <ChevronDownIcon aria-hidden />,
|
||||
fields: [
|
||||
defineField({
|
||||
title: "Heading",
|
||||
@@ -65,7 +65,7 @@ export const ExpansionCard = defineType({
|
||||
return {
|
||||
title: selection?.heading,
|
||||
subtitle: `ExpansionCard`,
|
||||
media: ChevronDownIcon,
|
||||
media: () => <ChevronDownIcon aria-hidden />,
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
@@ -6,7 +6,7 @@ export const Kode = defineType({
|
||||
title: "Kode",
|
||||
name: "kode",
|
||||
type: "object",
|
||||
icon: TerminalIcon,
|
||||
icon: () => <TerminalIcon aria-hidden />,
|
||||
fields: [
|
||||
defineField({
|
||||
name: "code",
|
||||
|
||||
@@ -8,7 +8,7 @@ export const RelatertInnhold = defineType({
|
||||
title: "Relaterte lenker",
|
||||
description: "Liste med relevante lenker til innholdet",
|
||||
type: "object",
|
||||
icon: LinkIcon,
|
||||
icon: () => <LinkIcon aria-hidden />,
|
||||
fields: [
|
||||
defineField({
|
||||
title: "Tittel",
|
||||
|
||||
@@ -228,7 +228,7 @@ export const RiktekstStandard = {
|
||||
name: "riktekst_standard",
|
||||
type: "array",
|
||||
of: Riktekst("standard"),
|
||||
icon: FileTextIcon,
|
||||
icon: () => <FileTextIcon aria-hidden />,
|
||||
};
|
||||
|
||||
export const RiktekstPrinsipp = {
|
||||
@@ -236,7 +236,7 @@ export const RiktekstPrinsipp = {
|
||||
name: "riktekst_prinsipp",
|
||||
type: "array",
|
||||
of: Riktekst("prinsipp"),
|
||||
icon: FileTextIcon,
|
||||
icon: () => <FileTextIcon aria-hidden />,
|
||||
};
|
||||
|
||||
export const RiktekstGrunnleggende = {
|
||||
@@ -244,7 +244,7 @@ export const RiktekstGrunnleggende = {
|
||||
name: "riktekst_grunnleggende",
|
||||
type: "array",
|
||||
of: Riktekst("grunnleggende"),
|
||||
icon: FileTextIcon,
|
||||
icon: () => <FileTextIcon aria-hidden />,
|
||||
};
|
||||
|
||||
export const RiktekstKomponent = {
|
||||
@@ -252,7 +252,7 @@ export const RiktekstKomponent = {
|
||||
name: "riktekst_komponent",
|
||||
type: "array",
|
||||
of: Riktekst("komponent"),
|
||||
icon: FileTextIcon,
|
||||
icon: () => <FileTextIcon aria-hidden />,
|
||||
};
|
||||
|
||||
export const RiktekstEnkel = {
|
||||
@@ -260,7 +260,7 @@ export const RiktekstEnkel = {
|
||||
name: "riktekst_enkel",
|
||||
type: "array",
|
||||
of: [block],
|
||||
icon: FileTextIcon,
|
||||
icon: () => <FileTextIcon aria-hidden />,
|
||||
};
|
||||
|
||||
export const RiktekstAccordion = {
|
||||
@@ -268,7 +268,7 @@ export const RiktekstAccordion = {
|
||||
name: "riktekst_accordion",
|
||||
type: "array",
|
||||
of: Riktekst("accordion"),
|
||||
icon: FileTextIcon,
|
||||
icon: () => <FileTextIcon aria-hidden />,
|
||||
};
|
||||
|
||||
export const RiktekstStandalone = {
|
||||
@@ -276,7 +276,7 @@ export const RiktekstStandalone = {
|
||||
name: "riktekst_standalone",
|
||||
type: "array",
|
||||
of: Riktekst("standalone"),
|
||||
icon: FileTextIcon,
|
||||
icon: () => <FileTextIcon aria-hidden />,
|
||||
};
|
||||
|
||||
export const RiktekstTemplates = {
|
||||
@@ -284,5 +284,5 @@ export const RiktekstTemplates = {
|
||||
name: "riktekst_templates",
|
||||
type: "array",
|
||||
of: Riktekst("templates"),
|
||||
icon: FileTextIcon,
|
||||
icon: () => <FileTextIcon aria-hidden />,
|
||||
};
|
||||
@@ -5,5 +5,5 @@ export const Tabell = defineType({
|
||||
title: "Tabell",
|
||||
name: "tabell_v2",
|
||||
type: "table",
|
||||
icon: TableIcon,
|
||||
icon: () => <TableIcon aria-hidden />,
|
||||
});
|
||||
@@ -5,7 +5,7 @@ export const Tips = defineType({
|
||||
name: "tips",
|
||||
title: "Tips",
|
||||
type: "object",
|
||||
icon: LightBulbIcon,
|
||||
icon: () => <LightBulbIcon aria-hidden />,
|
||||
fields: [
|
||||
defineField({
|
||||
title: "Innhold",
|
||||
|
||||
@@ -6,7 +6,7 @@ export const UnikSidemodul = defineType({
|
||||
title: "Unik Sidemodul",
|
||||
type: "object",
|
||||
name: "spesial_seksjon",
|
||||
icon: StarIcon,
|
||||
icon: () => <StarIcon aria-hidden />,
|
||||
validation: (Rule) => Rule.required(),
|
||||
fields: [
|
||||
defineField({
|
||||
|
||||
@@ -28,7 +28,7 @@ export const Video = defineType({
|
||||
name: "video",
|
||||
title: "Video",
|
||||
type: "object",
|
||||
icon: VideoplayerIcon,
|
||||
icon: () => <VideoplayerIcon aria-hidden />,
|
||||
fields: [
|
||||
defineField({
|
||||
name: "webm",
|
||||
|
||||
@@ -17,7 +17,7 @@ export const ExampletextBlock = defineType({
|
||||
name: "exampletext_block",
|
||||
title: "Eksempel/Standard tekst",
|
||||
type: "object",
|
||||
icon: Chat2Icon,
|
||||
icon: () => <Chat2Icon aria-hidden />,
|
||||
fields: [
|
||||
defineField({
|
||||
title: "Tittel",
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<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" />
|
||||
<!-- fonts -->
|
||||
|
||||
Loaded 100 of 123 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user