mirror of
https://github.com/navikt/aksel.git
synced 2026-10-13 09:12:03 +00:00
Compare commits
63
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
265e105fd0 | ||
|
|
8d934e0931 | ||
|
|
6981fbccff | ||
|
|
bfee5a822e | ||
|
|
58ed3b5367 | ||
|
|
00b3dd7d59 | ||
|
|
90db7dd0c1 | ||
|
|
b6d0304d5a | ||
|
|
de0a797e09 | ||
|
|
c4eaf0f350 | ||
|
|
fb9897e6a0 | ||
|
|
3438b5d7e7 | ||
|
|
3583062824 | ||
|
|
2993f809ad | ||
|
|
fe25e1fdf2 | ||
|
|
5cda39ce19 | ||
|
|
6cb25e44de | ||
|
|
5812d6ee87 | ||
|
|
a402777c70 | ||
|
|
d4064bd461 | ||
|
|
1c482e03b8 | ||
|
|
608e1f3944 | ||
|
|
107d6f3d93 | ||
|
|
09d41e9b6d | ||
|
|
ebacbddd44 | ||
|
|
e58b072c84 | ||
|
|
e7cb3753a0 | ||
|
|
7486ba11f9 | ||
|
|
cf01a6e162 | ||
|
|
a67f95433f | ||
|
|
dd03078e33 | ||
|
|
72321e3846 | ||
|
|
1cd8ac93ed | ||
|
|
48d6f14103 | ||
|
|
44da7fcdc6 | ||
|
|
e68ab5cb2c | ||
|
|
c721b392f9 | ||
|
|
1d0d0ce665 | ||
|
|
9d02940543 | ||
|
|
7ff63a2c63 | ||
|
|
49f33a78d8 | ||
|
|
06aaec70f0 | ||
|
|
5fbf230e47 | ||
|
|
53265c247e | ||
|
|
d78f33df79 | ||
|
|
4375c55ab6 | ||
|
|
0de8238ecf | ||
|
|
647b14a238 | ||
|
|
64901adccb | ||
|
|
49ddb2094c | ||
|
|
8a5f209735 | ||
|
|
d3bd1202ed | ||
|
|
4bf6027518 | ||
|
|
c8e203c523 | ||
|
|
267799ca0e | ||
|
|
42934af3a1 | ||
|
|
eff8a902c5 | ||
|
|
d34a8f45ef | ||
|
|
d1ceb40439 | ||
|
|
e033b34c3b | ||
|
|
de3991d0f0 | ||
|
|
9641215506 | ||
|
|
8a82e1c1f6 |
No files matched your search
@@ -75,7 +75,7 @@ jobs:
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
comment-author: "github-actions[bot]"
|
||||
body-includes: github-comment-finder
|
||||
body-includes: Storybook demo
|
||||
|
||||
- name: Create or update comment
|
||||
if: steps.chromatic_tests.outcome == 'success' && !contains(github.event.head_commit.message, '[ci skip]') && github.event_name != 'push'
|
||||
@@ -84,7 +84,7 @@ jobs:
|
||||
comment-id: ${{ steps.fc.outputs.comment-id }}
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body: |
|
||||
### [<img id="github-comment-finder" src="https://raw.githubusercontent.com/storybookjs/brand/main/logo/logo-storybook-inverse.svg" height="32"/>](${{steps.storybook_url.outputs.replaced}})
|
||||
## [Storybook demo](${{steps.storybook_url.outputs.replaced}})
|
||||
|
||||
[${{ steps.vars.outputs.sha_short }}](https://github.com/navikt/aksel/commit/${{ steps.vars.outputs.sha_short }}) | ${{steps.chromatic_tests.outputs.componentCount}} komponenter | ${{steps.chromatic_tests.outputs.specCount}} stories
|
||||
edit-mode: replace
|
||||
@@ -96,7 +96,7 @@ jobs:
|
||||
comment-id: ${{ steps.fc.outputs.comment-id }}
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body: |
|
||||
### [<img id="github-comment-finder" src="https://raw.githubusercontent.com/storybookjs/brand/main/logo/logo-storybook-inverse.svg" height="32"/>](${{steps.storybook_url.outputs.replaced}})
|
||||
## [Storybook demo](${{steps.storybook_url.outputs.replaced}})
|
||||
|
||||
### [Endringer til review](${{steps.chromatic_tests.outputs.url}}): ${{steps.chromatic_tests.outputs.changeCount}}
|
||||
|
||||
|
||||
@@ -1,5 +1,35 @@
|
||||
# @navikt/aksel-icons
|
||||
|
||||
## 5.0.3
|
||||
|
||||
## 5.0.2
|
||||
|
||||
## 5.0.1
|
||||
|
||||
## 5.0.0
|
||||
|
||||
## 4.12.1
|
||||
|
||||
## 4.12.0
|
||||
|
||||
## 4.11.2
|
||||
|
||||
## 4.11.1
|
||||
|
||||
## 4.11.0
|
||||
|
||||
## 4.10.2
|
||||
|
||||
## 4.10.1
|
||||
|
||||
## 4.10.0
|
||||
|
||||
## 4.9.1
|
||||
|
||||
## 4.9.0
|
||||
|
||||
## 4.8.0
|
||||
|
||||
## 4.7.4
|
||||
|
||||
## 4.7.3
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
.icon-button {
|
||||
min-height: 3rem;
|
||||
min-width: 3rem;
|
||||
border-radius: 2px;
|
||||
border-radius: var(--a-border-radius-small);
|
||||
display: grid;
|
||||
place-content: center;
|
||||
background-color: var(--a-surface-default);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import meta from "@navikt/aksel-icons/metadata";
|
||||
|
||||
const subCategorizeIcons = (
|
||||
icons: typeof meta[1][]
|
||||
): { sub_category: string; icons: typeof meta[1][] }[] => {
|
||||
const categories: { sub_category: string; icons: typeof meta[1][] }[] = [];
|
||||
icons: (typeof meta)[1][]
|
||||
): { sub_category: string; icons: (typeof meta)[1][] }[] => {
|
||||
const categories: { sub_category: string; icons: (typeof meta)[1][] }[] = [];
|
||||
|
||||
for (const icon of icons) {
|
||||
const i = categories.findIndex(
|
||||
@@ -19,14 +19,14 @@ const subCategorizeIcons = (
|
||||
};
|
||||
|
||||
export const categorizeIcons = (
|
||||
icons: typeof meta[1][]
|
||||
icons: (typeof meta)[1][]
|
||||
): {
|
||||
category: string;
|
||||
sub_categories: { sub_category: string; icons: typeof meta[1][] }[];
|
||||
sub_categories: { sub_category: string; icons: (typeof meta)[1][] }[];
|
||||
}[] => {
|
||||
const categories: {
|
||||
category: string;
|
||||
icons: typeof meta[1][];
|
||||
icons: (typeof meta)[1][];
|
||||
}[] = [];
|
||||
|
||||
for (const icon of icons) {
|
||||
@@ -42,14 +42,14 @@ export const categorizeIcons = (
|
||||
.map((x) => ({ ...x, sub_categories: subCategorizeIcons(x.icons) }));
|
||||
};
|
||||
|
||||
const noFill = (icon: typeof meta[1], icons: typeof meta[1][]) => {
|
||||
const noFill = (icon: (typeof meta)[1], icons: (typeof meta)[1][]) => {
|
||||
const foundFill = icons.find(
|
||||
(x) => x.name.endsWith("Fill") && x.name.replace("Fill", "") === icon.name
|
||||
);
|
||||
return !foundFill;
|
||||
};
|
||||
|
||||
export const getFillIcon = (icons: typeof meta[1][]) => {
|
||||
export const getFillIcon = (icons: (typeof meta)[1][]) => {
|
||||
return icons.filter(
|
||||
(x, _, z) => x.variant.toLowerCase() === "fill" || noFill(x, z)
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/aksel-icons",
|
||||
"version": "4.7.4",
|
||||
"version": "5.0.3",
|
||||
"author": "Aksel | NAV",
|
||||
"homepage": "https://aksel.nav.no/ikoner",
|
||||
"repository": {
|
||||
|
||||
@@ -1,5 +1,35 @@
|
||||
# @navikt/aksel-stylelint
|
||||
|
||||
## 5.0.3
|
||||
|
||||
## 5.0.2
|
||||
|
||||
## 5.0.1
|
||||
|
||||
## 5.0.0
|
||||
|
||||
## 4.12.1
|
||||
|
||||
## 4.12.0
|
||||
|
||||
## 4.11.2
|
||||
|
||||
## 4.11.1
|
||||
|
||||
## 4.11.0
|
||||
|
||||
## 4.10.2
|
||||
|
||||
## 4.10.1
|
||||
|
||||
## 4.10.0
|
||||
|
||||
## 4.9.1
|
||||
|
||||
## 4.9.0
|
||||
|
||||
## 4.8.0
|
||||
|
||||
## 4.7.4
|
||||
|
||||
## 4.7.3
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/aksel-stylelint",
|
||||
"version": "4.7.4",
|
||||
"version": "5.0.3",
|
||||
"author": "Aksel | NAV",
|
||||
"homepage": "https://aksel.nav.no/grunnleggende/kode/stylelint",
|
||||
"repository": {
|
||||
@@ -35,8 +35,8 @@
|
||||
"dev": "yarn watch:lint"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@navikt/ds-css": "^4.7.4",
|
||||
"@navikt/ds-tokens": "^4.7.4",
|
||||
"@navikt/ds-css": "^5.0.3",
|
||||
"@navikt/ds-tokens": "^5.0.3",
|
||||
"@types/jest": "^29.0.0",
|
||||
"concurrently": "7.2.1",
|
||||
"copyfiles": "2.4.1",
|
||||
|
||||
@@ -25,4 +25,17 @@ export const deprecations: DeprecatedList = [
|
||||
message:
|
||||
"In v4.1.0 Chips. Toggle no longer handles special alignment for checkmark-icon, thus removing this class",
|
||||
},
|
||||
{
|
||||
classes: ["navds-modal__overlay"],
|
||||
message:
|
||||
"Removed in v5.0.0, use `.navds-modal::backdrop` and `.navds-modal--polyfilled + .backdrop` instead",
|
||||
},
|
||||
{
|
||||
classes: ["navds-modal__button--shake"],
|
||||
message: "Removed in v5.0.0",
|
||||
},
|
||||
{
|
||||
classes: ["navds-modal__content"],
|
||||
message: "Renamed to `.navds-modal__body` in v5.0.0",
|
||||
},
|
||||
];
|
||||
@@ -1,5 +1,110 @@
|
||||
# @navikt/aksel
|
||||
|
||||
## 5.0.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`8d934e093`](https://github.com/navikt/aksel/commit/8d934e0931caaa6f774a8f1b20fa34a19ece7218), [`8d934e093`](https://github.com/navikt/aksel/commit/8d934e0931caaa6f774a8f1b20fa34a19ece7218)]:
|
||||
- @navikt/ds-css@5.0.3
|
||||
|
||||
## 5.0.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/ds-css@5.0.2
|
||||
|
||||
## 5.0.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`90db7dd0c`](https://github.com/navikt/aksel/commit/90db7dd0c120c16a387d3169c05c1f33dc694323)]:
|
||||
- @navikt/ds-css@5.0.1
|
||||
|
||||
## 5.0.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`2993f809a`](https://github.com/navikt/aksel/commit/2993f809adbf26fe88237066fea7f3e1a21b6690), [`2993f809a`](https://github.com/navikt/aksel/commit/2993f809adbf26fe88237066fea7f3e1a21b6690), [`3438b5d7e`](https://github.com/navikt/aksel/commit/3438b5d7e75037889946d4f89e95cd1f91bbb2d0)]:
|
||||
- @navikt/ds-css@5.0.0
|
||||
|
||||
## 4.12.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`5cda39ce1`](https://github.com/navikt/aksel/commit/5cda39ce19fa3f25ae18a0b07dd85e97378edd26)]:
|
||||
- @navikt/ds-css@4.12.1
|
||||
|
||||
## 4.12.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`1c482e03b`](https://github.com/navikt/aksel/commit/1c482e03b845c4e6f6f1e174a58992fbeeabb52e), [`107d6f3d9`](https://github.com/navikt/aksel/commit/107d6f3d935cbc9bb94bc15cea63431cd15bbd61), [`09d41e9b6`](https://github.com/navikt/aksel/commit/09d41e9b6da5486a55faaf8088191b4a8374e420), [`a402777c7`](https://github.com/navikt/aksel/commit/a402777c70a18899ef4239b10a6335a671e26167)]:
|
||||
- @navikt/ds-css@4.12.0
|
||||
|
||||
## 4.11.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`72321e384`](https://github.com/navikt/aksel/commit/72321e3846f785318ebdba9f8e06cf13b09cd524)]:
|
||||
- @navikt/ds-css@4.11.2
|
||||
|
||||
## 4.11.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`e68ab5cb2`](https://github.com/navikt/aksel/commit/e68ab5cb2cd91c2f0234c864ee71821ed07a8203)]:
|
||||
- @navikt/ds-css@4.11.1
|
||||
|
||||
## 4.11.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`64901adcc`](https://github.com/navikt/aksel/commit/64901adccb8f19fbe06cdab4a70d47978e90e343), [`0de8238ec`](https://github.com/navikt/aksel/commit/0de8238ecf22e9f1fbc0e1b0cfe7a5cd7254901e), [`647b14a23`](https://github.com/navikt/aksel/commit/647b14a23888f82bacc4806d3afaacb5e39178fa)]:
|
||||
- @navikt/ds-css@4.11.0
|
||||
|
||||
## 4.10.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/ds-css@4.10.2
|
||||
|
||||
## 4.10.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`d3bd1202e`](https://github.com/navikt/aksel/commit/d3bd1202eddb23dd3f1f09c3e4086ff3b19737d6)]:
|
||||
- @navikt/ds-css@4.10.1
|
||||
|
||||
## 4.10.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`267799ca0`](https://github.com/navikt/aksel/commit/267799ca0ecc0c549e159f94124befb654e29331)]:
|
||||
- @navikt/ds-css@4.10.0
|
||||
|
||||
## 4.9.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`eff8a902c`](https://github.com/navikt/aksel/commit/eff8a902c50fd5ebc56616e866aa0a61b9eb5efc)]:
|
||||
- @navikt/ds-css@4.9.1
|
||||
|
||||
## 4.9.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`e033b34c3`](https://github.com/navikt/aksel/commit/e033b34c3b85fb98dd11dd0300bb20075e1b06e8)]:
|
||||
- @navikt/ds-css@4.9.0
|
||||
|
||||
## 4.8.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`8a82e1c1f`](https://github.com/navikt/aksel/commit/8a82e1c1f66ea38fca77b25d74b14926bd817dd9)]:
|
||||
- @navikt/ds-css@4.8.0
|
||||
|
||||
## 4.7.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/aksel",
|
||||
"version": "4.7.4",
|
||||
"version": "5.0.3",
|
||||
"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": "4.7.4",
|
||||
"@navikt/ds-css": "5.0.3",
|
||||
"axios": "1.3.6",
|
||||
"chalk": "4.1.0",
|
||||
"clipboardy": "^2.3.0",
|
||||
|
||||
@@ -1,5 +1,110 @@
|
||||
# @navikt/ds-css
|
||||
|
||||
## 5.0.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- ConfirmationPanel: Fikset opplesing av feilmelding ([#2199](https://github.com/navikt/aksel/pull/2199))
|
||||
|
||||
- Checkbox: Fikset checkbox-alignment ved bruk av tailwindcss ([#2199](https://github.com/navikt/aksel/pull/2199))
|
||||
|
||||
## 5.0.2
|
||||
|
||||
## 5.0.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Checkbox: Hotfix da checkmark ikke var sentrert hvis man brukte tailwind ([`90db7dd0c`](https://github.com/navikt/aksel/commit/90db7dd0c120c16a387d3169c05c1f33dc694323))
|
||||
|
||||
## 5.0.0
|
||||
|
||||
### Major Changes
|
||||
|
||||
- Oppdatert Modal - Se [Migrering](https://aksel.nav.no/grunnleggende/kode/migrering) ([#2135](https://github.com/navikt/aksel/pull/2135))
|
||||
|
||||
- :sparkles: Støtte for header og footer
|
||||
- :boom: Bruker nå native dialog i stedet for react-modal (med polyfill for eldre nettlesere)
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Table: ExpandableRow har oppdatert knapp for å matche Accordion og ExpansionCard ([#2178](https://github.com/navikt/aksel/pull/2178))
|
||||
|
||||
- Table: Alle størrelser har justert padding. Small-size table bruker nå også standard typografi-størrelse (18px). ([#2178](https://github.com/navikt/aksel/pull/2178))
|
||||
|
||||
## 4.12.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Added red border to Combobox in error state ([#2184](https://github.com/navikt/aksel/pull/2184))
|
||||
|
||||
## 4.12.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- CopyButton: Har nå prop 'iconPosition' for å høyre/venstre aligne ikon ([#2173](https://github.com/navikt/aksel/pull/2173))
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- List: Fikset sentrering, margins ([#2168](https://github.com/navikt/aksel/pull/2168))
|
||||
|
||||
- Checkbox: Checkmark er nå SVG-ikon og ikke Base64 ([#2171](https://github.com/navikt/aksel/pull/2171))
|
||||
|
||||
- Alert: Fikset alignment av status-ikon mot tekst ([#2179](https://github.com/navikt/aksel/pull/2179))
|
||||
|
||||
## 4.11.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- :lipstick: ToggleGroup: fjern semibold fra selected button ([#2167](https://github.com/navikt/aksel/pull/2167))
|
||||
|
||||
## 4.11.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Border-radius: Fikset hardkodet border-radius i Datepicker, ToggleGroup og Combobox. ([#2159](https://github.com/navikt/aksel/pull/2159))
|
||||
|
||||
## 4.11.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- HGrid: Ny komponent 'HGrid' er ute i Beta: [Dokumentasjon](https://aksel.nav.no/komponenter/core/hgrid) ([#1838](https://github.com/navikt/aksel/pull/1838))
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- List: Fikset alignment av ikoner ([#2149](https://github.com/navikt/aksel/pull/2149))
|
||||
|
||||
- Font: La til egen font for semibold italic for bedre skalering cross-browser ([#2150](https://github.com/navikt/aksel/pull/2150))
|
||||
|
||||
## 4.10.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Link: Fikset visited farger og inline SVG-reset ved bruk av tailwind
|
||||
|
||||
## 4.10.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Link: La til 'variant', 'underline' og 'inlineText'-prop ([#2093](https://github.com/navikt/aksel/pull/2093))
|
||||
|
||||
## 4.9.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Tweaks to combobox - updated example, small bugfixes, better affordance for selected options and added flag for "isAddedByUser" to onToggleSelected ([#2144](https://github.com/navikt/aksel/pull/2144))
|
||||
|
||||
## 4.9.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Table: ExpandableRow kan nå åpnes med 'expandOnRowClick'-prop ([#2127](https://github.com/navikt/aksel/pull/2127))
|
||||
|
||||
## 4.8.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Nye komponenter `VStack`, `HStack` og `Spacer` for å enklere kunne lage layout med flexbox og spacing-variabler. ([#2040](https://github.com/navikt/aksel/pull/2040))
|
||||
|
||||
## 4.7.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -24,10 +24,17 @@
|
||||
font-size: 1.5rem;
|
||||
align-self: flex-start;
|
||||
height: var(--a-font-line-height-xlarge);
|
||||
margin: var(--a-spacing-1);
|
||||
margin-inline: var(--a-spacing-1);
|
||||
margin-block: 0;
|
||||
margin-block-start: -1px;
|
||||
background: radial-gradient(circle, var(--a-surface-default) 50%, 0, transparent);
|
||||
}
|
||||
|
||||
.navds-alert--close-button > .navds-alert__icon,
|
||||
.navds-alert--small > .navds-alert__icon {
|
||||
margin-block-start: 0;
|
||||
}
|
||||
|
||||
.navds-alert--small > .navds-alert__icon {
|
||||
height: var(--a-font-line-height-large);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,13 @@
|
||||
font-display: swap;
|
||||
src: url("https://cdn.nav.no/aksel/fonts/SourceSans3-italic.woff2") format("woff2");
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Source Sans Pro";
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url("https://cdn.nav.no/aksel/fonts/SourceSans3-italic-latin-semibold") format("woff2");
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Source Sans Pro";
|
||||
font-style: normal;
|
||||
|
||||
@@ -57,7 +57,7 @@ const StyleMappings = {
|
||||
{
|
||||
component: "UNSAFE_Combobox",
|
||||
main: "combobox.css",
|
||||
dependencies: [typoCss],
|
||||
dependencies: [typoCss, "chips.css"],
|
||||
},
|
||||
{
|
||||
component: "ConfirmationPanel",
|
||||
@@ -99,6 +99,10 @@ const StyleMappings = {
|
||||
main: "help-text.css",
|
||||
dependencies: ["popover.css"],
|
||||
},
|
||||
{
|
||||
component: "HGrid",
|
||||
main: "hgrid.css",
|
||||
},
|
||||
{ component: "Ingress", main: typoCss },
|
||||
{
|
||||
component: "InternalHeader",
|
||||
@@ -117,7 +121,7 @@ const StyleMappings = {
|
||||
{
|
||||
component: "Modal",
|
||||
main: "modal.css",
|
||||
dependencies: ["button.css"],
|
||||
dependencies: [typoCss, "button.css"],
|
||||
},
|
||||
{
|
||||
component: "Pagination",
|
||||
@@ -134,6 +138,18 @@ const StyleMappings = {
|
||||
{ component: "Select", main: formCss, dependencies: [typoCss] },
|
||||
{ component: "Skeleton", main: "skeleton.css", dependencies: [] },
|
||||
{ component: "Stepper", main: "stepper.css", dependencies: [typoCss] },
|
||||
{
|
||||
component: "HStack",
|
||||
main: "stack.css",
|
||||
},
|
||||
{
|
||||
component: "VStack",
|
||||
main: "stack.css",
|
||||
},
|
||||
{
|
||||
component: "Spacer",
|
||||
main: "stack.css",
|
||||
},
|
||||
{ component: "Switch", main: formCss, dependencies: [typoCss] },
|
||||
{ component: "Table", main: "table.css", dependencies: [typoCss] },
|
||||
{ component: "Tabs", main: "tabs.css", dependencies: [typoCss] },
|
||||
|
||||
@@ -52,9 +52,16 @@
|
||||
.navds-copybutton__icon {
|
||||
font-size: 1.5rem;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.navds-copybutton__icon:first-of-type {
|
||||
margin-left: -0.25rem;
|
||||
}
|
||||
|
||||
.navds-copybutton__icon:last-of-type {
|
||||
margin-right: -0.25rem;
|
||||
}
|
||||
|
||||
.navds-copybutton__icon:only-child {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@@ -233,7 +233,7 @@
|
||||
text-decoration: none;
|
||||
border: none;
|
||||
background: none;
|
||||
border-radius: 3px;
|
||||
border-radius: calc(var(--a-border-radius-medium) - 1px);
|
||||
padding: var(--a-spacing-3);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
@@ -9,9 +9,10 @@
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
border: 1px solid var(--a-border-default);
|
||||
padding: calc(var(--a-spacing-2) - 2px) var(--a-spacing-2);
|
||||
}
|
||||
|
||||
.navds-combobox__wrapper-inner > :first-child {
|
||||
@@ -31,8 +32,19 @@
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.navds-combobox--error .navds-text-field__input:not(:hover):not(:disabled) {
|
||||
border-color: var(--ac-combobox-error-border, var(--a-border-danger));
|
||||
box-shadow: 0 0 0 1px var(--ac-combobox-error-border, var(--a-border-danger));
|
||||
}
|
||||
|
||||
.navds-combobox--error
|
||||
.navds-text-field__input:not(:hover):not(:disabled):not(.navds-combobox__wrapper-inner--virtually-unfocused):focus-within {
|
||||
box-shadow: 0 0 0 1px var(--ac-combobox-error-border, var(--a-border-danger)), var(--a-shadow-focus);
|
||||
}
|
||||
|
||||
.navds-combobox__selected-options {
|
||||
gap: var(--a-spacing-2);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.navds-combobox__selected-options > li {
|
||||
@@ -67,6 +79,7 @@
|
||||
margin: 0;
|
||||
min-width: 10ch;
|
||||
width: 100%;
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
.navds-combobox__input:focus-visible {
|
||||
@@ -85,7 +98,7 @@
|
||||
}
|
||||
|
||||
@supports not selector(:focus-visible) {
|
||||
.navds-combobox__input:focus-visible {
|
||||
.navds-combobox__input:focus {
|
||||
outline: none;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
@@ -101,6 +114,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
@supports not selector(:has) {
|
||||
.navds-combobox--focused .navds-combobox__wrapper-inner {
|
||||
box-shadow: 0 0 0 1px var(--a-surface-default) inset, var(--a-shadow-focus);
|
||||
box-shadow: var(--a-shadow-focus);
|
||||
}
|
||||
|
||||
.navds-combobox--focused .navds-combobox__wrapper-inner.navds-combobox__wrapper-inner--virtually-unfocused {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.navds-combobox__button-clear {
|
||||
border-radius: var(--a-border-radius-medium);
|
||||
color: var(--ac-combobox-clear-icon, var(--a-text-subtle));
|
||||
@@ -239,10 +263,19 @@
|
||||
padding-left: calc(var(--a-spacing-3) - 4px);
|
||||
}
|
||||
|
||||
.navds-form-field--small .navds-combobox__list-item--focus,
|
||||
.navds-form-field--small .navds-combobox__list-item:hover {
|
||||
padding-left: calc(var(--a-spacing-2) - 4px);
|
||||
}
|
||||
|
||||
.navds-combobox__list-item--selected {
|
||||
background-color: var(--ac-combobox-list-item-selected-bg, var(--a-surface-selected));
|
||||
}
|
||||
|
||||
.navds-combobox__list-item--selected p {
|
||||
font-weight: var(--a-font-weight-bold);
|
||||
}
|
||||
|
||||
.navds-combobox__list-item--selected.navds-combobox__list-item--focus,
|
||||
.navds-combobox__list-item--selected:hover {
|
||||
background-color: var(--ac-combobox-list-item-selected-hover-bg, var(--a-surface-action-subtle-hover));
|
||||
@@ -265,7 +298,7 @@
|
||||
|
||||
.navds-combobox__list-item__new-option--focus {
|
||||
box-shadow: var(--a-shadow-focus) inset, var(--a-border-action) 0 0 0 5px inset;
|
||||
border-radius: 3px;
|
||||
border-radius: calc(var(--a-border-radius-medium) - 1px);
|
||||
}
|
||||
|
||||
/* Mobile */
|
||||
|
||||
@@ -114,14 +114,32 @@
|
||||
}
|
||||
|
||||
.navds-checkbox__input:checked + .navds-checkbox__label::before {
|
||||
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMyAxMCI+ICAgIDxnPiAgICA8cGF0aCBmaWxsPSIjRkZGRkZGIiBkPSJNNCwxMGMtMC40LDAtMC44LTAuMS0xLjEtMC40TDAuNCw3LjFDMC4xLDYuOCwwLDYuNCwwLDZzMC4yLTAuOCwwLjUtMS4xQzEsNC40LDIsNC40LDIuNSw0LjlMNCw2LjRsNi40LTYgICAgQzEwLjgsMC4xLDExLjEsMCwxMS41LDBjMC40LDAsMC44LDAuMiwxLDAuNWMwLjYsMC42LDAuNSwxLjYtMC4xLDIuMXYwTDUsOS42QzQuNyw5LjksNC40LDEwLDQsMTB6IE0xMS44LDEuOUwxMS44LDEuOSAgICBDMTEuOCwxLjksMTEuOCwxLjksMTEuOCwxLjl6IE0xMS4yLDEuMUMxMS4yLDEuMSwxMS4yLDEuMSwxMS4yLDEuMUwxMS4yLDEuMXoiLz4gICAgPC9nPjwvc3ZnPg==);
|
||||
background-position: 0.375rem center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 0.8125rem;
|
||||
box-shadow: none;
|
||||
background-color: var(--ac-radio-checkbox-action, var(--a-surface-action));
|
||||
}
|
||||
|
||||
.navds-checkbox__input:where(:not(:checked)) + .navds-checkbox__label > .navds-checkbox__icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.navds-checkbox__input:checked + .navds-checkbox__label > .navds-checkbox__icon {
|
||||
color: var(--ac-radio-checkbox-bg, var(--a-surface-default));
|
||||
position: absolute;
|
||||
height: 1.5rem;
|
||||
transform: translate(0.375rem);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.navds-checkbox--small .navds-checkbox__input:checked + .navds-checkbox__label > .navds-checkbox__icon {
|
||||
transform: translate(0.25rem, -10%);
|
||||
}
|
||||
|
||||
/* Tailwind sets all svg to block */
|
||||
.navds-checkbox__icon > svg {
|
||||
display: inline;
|
||||
vertical-align: initial;
|
||||
}
|
||||
|
||||
.navds-checkbox--small > .navds-checkbox__input:checked + .navds-checkbox__label::before {
|
||||
background-position: 0.25rem center;
|
||||
}
|
||||
@@ -283,11 +301,14 @@
|
||||
}
|
||||
|
||||
.navds-checkbox--readonly > .navds-checkbox__input:checked + .navds-checkbox__label::before {
|
||||
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMyAxMCI+ICAgIDxnPiAgICA8cGF0aCBmaWxsPSJyZ2JhKDAsIDAsIDAsIDAuNjUpIiBkPSJNNCwxMGMtMC40LDAtMC44LTAuMS0xLjEtMC40TDAuNCw3LjFDMC4xLDYuOCwwLDYuNCwwLDZzMC4yLTAuOCwwLjUtMS4xQzEsNC40LDIsNC40LDIuNSw0LjlMNCw2LjRsNi40LTYgICAgQzEwLjgsMC4xLDExLjEsMCwxMS41LDBjMC40LDAsMC44LDAuMiwxLDAuNWMwLjYsMC42LDAuNSwxLjYtMC4xLDIuMXYwTDUsOS42QzQuNyw5LjksNC40LDEwLDQsMTB6IE0xMS44LDEuOUwxMS44LDEuOSAgICBDMTEuOCwxLjksMTEuOCwxLjksMTEuOCwxLjl6IE0xMS4yLDEuMUMxMS4yLDEuMSwxMS4yLDEuMSwxMS4yLDEuMUwxMS4yLDEuMXoiLz4gICAgPC9nPjwvc3ZnPg==);
|
||||
box-shadow: inset 0 0 0 2px var(--__ac-radio-checkbox-readonly-border);
|
||||
background-color: var(--__ac-radio-checkbox-readonly-bg);
|
||||
}
|
||||
|
||||
.navds-checkbox--readonly > .navds-checkbox__input:checked + .navds-checkbox__label > .navds-checkbox__icon {
|
||||
color: var(--a-icon-subtle);
|
||||
}
|
||||
|
||||
.navds-radio--readonly > .navds-radio__input:checked + .navds-radio__label::before {
|
||||
box-shadow: inset 0 0 0 2px var(--__ac-radio-checkbox-readonly-border), inset 0 0 0 4px var(--__ac-radio-checkbox-readonly-bg);
|
||||
background-color: var(--a-icon-subtle);
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
.navds-hgrid {
|
||||
--__ac-hgrid-columns-xs: initial;
|
||||
--__ac-hgrid-columns-sm: initial;
|
||||
--__ac-hgrid-columns-md: initial;
|
||||
--__ac-hgrid-columns-lg: initial;
|
||||
--__ac-hgrid-columns-xl: initial;
|
||||
--__ac-hgrid-columns: var(--__ac-hgrid-columns-xs);
|
||||
--__ac-hgrid-gap-xs: initial;
|
||||
--__ac-hgrid-gap-sm: initial;
|
||||
--__ac-hgrid-gap-md: initial;
|
||||
--__ac-hgrid-gap-lg: initial;
|
||||
--__ac-hgrid-gap-xl: initial;
|
||||
--__ac-hgrid-gap: var(--__ac-hgrid-gap-xs);
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: var(--__ac-hgrid-columns);
|
||||
gap: var(--__ac-hgrid-gap);
|
||||
}
|
||||
|
||||
@media (min-width: 480px) {
|
||||
.navds-hgrid {
|
||||
--__ac-hgrid-columns: var(--__ac-hgrid-columns-sm, var(--__ac-hgrid-columns-xs));
|
||||
--__ac-hgrid-gap: var(--__ac-hgrid-gap-sm, var(--__ac-hgrid-gap-xs));
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.navds-hgrid {
|
||||
--__ac-hgrid-columns: var(--__ac-hgrid-columns-md, var(--__ac-hgrid-columns-sm, var(--__ac-hgrid-columns-xs)));
|
||||
--__ac-hgrid-gap: var(--__ac-hgrid-gap-md, var(--__ac-hgrid-gap-sm, var(--__ac-hgrid-gap-xs)));
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.navds-hgrid {
|
||||
--__ac-hgrid-columns: var(
|
||||
--__ac-hgrid-columns-lg,
|
||||
var(--__ac-hgrid-columns-md, var(--__ac-hgrid-columns-sm, var(--__ac-hgrid-columns-xs)))
|
||||
);
|
||||
--__ac-hgrid-gap: var(--__ac-hgrid-gap-lg, var(--__ac-hgrid-gap-md, var(--__ac-hgrid-gap-sm, var(--__ac-hgrid-gap-xs))));
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1280px) {
|
||||
.navds-hgrid {
|
||||
--__ac-hgrid-columns: var(
|
||||
--__ac-hgrid-columns-xl,
|
||||
var(--__ac-hgrid-columns-lg, var(--__ac-hgrid-columns-md, var(--__ac-hgrid-columns-sm, var(--__ac-hgrid-columns-xs))))
|
||||
);
|
||||
--__ac-hgrid-gap: var(
|
||||
--__ac-hgrid-gap-xl,
|
||||
var(--__ac-hgrid-gap-lg, var(--__ac-hgrid-gap-md, var(--__ac-hgrid-gap-sm, var(--__ac-hgrid-gap-xs))))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@
|
||||
@import "guide-panel.css";
|
||||
@import "form/index.css";
|
||||
@import "help-text.css";
|
||||
@import "hgrid.css";
|
||||
@import "internalheader.css";
|
||||
@import "link.css";
|
||||
@import "loader.css";
|
||||
@@ -29,6 +30,7 @@
|
||||
@import "link-panel.css";
|
||||
@import "read-more.css";
|
||||
@import "skeleton.css";
|
||||
@import "stack.css";
|
||||
@import "stepper.css";
|
||||
@import "table.css";
|
||||
@import "tabs.css";
|
||||
|
||||
@@ -6,15 +6,35 @@
|
||||
gap: var(--a-spacing-1);
|
||||
}
|
||||
|
||||
.navds-alert .navds-link,
|
||||
.navds-confirmation-panel .navds-link {
|
||||
color: var(--a-text-default);
|
||||
.navds-link.navds-link--inline-text {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.navds-link.navds-link--inline-text > svg {
|
||||
/* stylelint-disable-next-line plugin/declaration-block-no-ignored-properties */
|
||||
margin: 0.1em 0.1em -0.1em;
|
||||
transform: translateY(0.05em);
|
||||
display: inline;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
.navds-link:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.navds-link.navds-link--remove-underline {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.navds-link.navds-link--remove-underline:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.navds-alert .navds-link,
|
||||
.navds-confirmation-panel .navds-link {
|
||||
color: var(--a-text-default);
|
||||
}
|
||||
|
||||
.navds-link:focus-visible {
|
||||
outline: none;
|
||||
color: var(--ac-link-focus-text, var(--a-text-on-action));
|
||||
@@ -45,3 +65,15 @@
|
||||
color: inherit;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.navds-link--action {
|
||||
color: var(--ac-link-action-text, var(--a-text-action));
|
||||
}
|
||||
|
||||
.navds-link--neutral {
|
||||
color: var(--ac-link-neutral-text, var(--a-text-default));
|
||||
}
|
||||
|
||||
.navds-link--subtle {
|
||||
color: var(--ac-link-subtle-text, var(--a-text-subtle));
|
||||
}
|
||||
@@ -1,14 +1,24 @@
|
||||
.navds-list ul,
|
||||
.navds-list ol {
|
||||
padding: 0;
|
||||
margin-block: var(--a-spacing-4);
|
||||
}
|
||||
|
||||
.navds-list--nested {
|
||||
margin: var(--a-spacing-4) 0 var(--a-spacing-8) 0;
|
||||
.navds-list--nested ul,
|
||||
.navds-list--nested ol {
|
||||
margin-block: var(--a-spacing-2);
|
||||
}
|
||||
|
||||
.navds-list li:not(.navds-list__item--noMargin) {
|
||||
margin-bottom: var(--a-spacing-5);
|
||||
.navds-list li {
|
||||
margin-block-end: var(--a-spacing-5);
|
||||
}
|
||||
|
||||
.navds-list--small li {
|
||||
margin-block-end: var(--a-spacing-2);
|
||||
}
|
||||
|
||||
.navds-list--nested li {
|
||||
margin-block-end: var(--a-spacing-3);
|
||||
}
|
||||
|
||||
.navds-list:last-child > ul > li:last-child,
|
||||
@@ -34,15 +44,30 @@
|
||||
|
||||
.navds-list .navds-list__item-marker--bullet {
|
||||
display: flex;
|
||||
margin-top: 0.375rem;
|
||||
align-items: center;
|
||||
height: var(--a-font-line-height-large);
|
||||
grid-column: 1 / 2;
|
||||
/* stylelint-disable-next-line csstools/value-no-unknown-custom-properties */
|
||||
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-medium);
|
||||
}
|
||||
|
||||
.navds-list__item-marker--icon {
|
||||
/* stylelint-disable-next-line csstools/value-no-unknown-custom-properties */
|
||||
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-large);
|
||||
padding-block-end: 1px;
|
||||
}
|
||||
|
||||
.navds-list--small .navds-list__item-marker--icon {
|
||||
height: var(--a-font-line-height-medium);
|
||||
}
|
||||
|
||||
.navds-list__item-marker--icon svg {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.navds-list ul .navds-list__item-content {
|
||||
@@ -51,6 +76,7 @@
|
||||
|
||||
.navds-list ol {
|
||||
padding-left: var(--a-spacing-2);
|
||||
list-style: decimal;
|
||||
}
|
||||
|
||||
.navds-list ol li {
|
||||
@@ -60,7 +86,12 @@
|
||||
|
||||
.navds-list ol li::marker {
|
||||
font-weight: var(--a-font-weight-bold);
|
||||
font-size: var(--a-font-size-medium);
|
||||
/* stylelint-disable-next-line csstools/value-no-unknown-custom-properties */
|
||||
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-large);
|
||||
}
|
||||
|
||||
.navds-list--small ol li::marker {
|
||||
font-size: var(--a-font-size-small);
|
||||
line-height: var(--a-font-line-height-medium);
|
||||
}
|
||||
+127
-44
@@ -1,69 +1,152 @@
|
||||
.ReactModal__Body--open {
|
||||
.navds-modal__document-body {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.navds-modal {
|
||||
background-color: var(--ac-modal-bg, var(--a-surface-default));
|
||||
border-radius: var(--a-border-radius-medium);
|
||||
display: block;
|
||||
position: relative;
|
||||
overflow: auto;
|
||||
max-height: 100%;
|
||||
border: 1px solid var(--ac-modal-border, var(--a-border-subtle));
|
||||
border-radius: var(--a-border-radius-large);
|
||||
box-shadow: var(--a-shadow-xlarge);
|
||||
padding: 0;
|
||||
position: fixed;
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.navds-modal__content {
|
||||
padding: var(--a-spacing-4);
|
||||
.navds-modal[open] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
animation: akselModalFadeIn 0.35s cubic-bezier(0.15, 1, 0.3, 1);
|
||||
}
|
||||
|
||||
.navds-modal:focus-visible,
|
||||
.navds-modal--focus {
|
||||
box-shadow: var(--a-shadow-focus);
|
||||
outline: none;
|
||||
.navds-modal--polyfilled {
|
||||
top: 50%;
|
||||
transform: translate(0, -50%);
|
||||
overflow: auto;
|
||||
|
||||
/* From polyfill (dialog-polyfill/dist/dialog-polyfill.css): */
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: fit-content;
|
||||
height: fit-content;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
@supports not selector(:focus-visible) {
|
||||
.navds-modal:focus,
|
||||
.navds-modal--focus {
|
||||
box-shadow: var(--a-shadow-focus);
|
||||
outline: none;
|
||||
.navds-modal--polyfilled:not([open]) {
|
||||
display: none; /* from polyfill */
|
||||
}
|
||||
|
||||
._dialog_overlay {
|
||||
/* From polyfill */
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.navds-modal--small {
|
||||
width: var(--ac-modal-width-small, 450px);
|
||||
}
|
||||
|
||||
.navds-modal--medium {
|
||||
width: var(--ac-modal-width-medium, 700px);
|
||||
}
|
||||
|
||||
@media (min-width: 480px) {
|
||||
.navds-modal {
|
||||
max-width: calc((100% - 6px) - 2em);
|
||||
max-height: calc((100% - 6px) - 2em);
|
||||
}
|
||||
|
||||
.navds-modal--autowidth {
|
||||
max-width: min(700px, calc((100% - 6px) - 2em));
|
||||
}
|
||||
}
|
||||
|
||||
.navds-modal__overlay {
|
||||
.navds-modal::backdrop {
|
||||
/*
|
||||
* Cannot use --a-surface-backdrop because ::backdrop does not inherit from anything but itself.
|
||||
* But consumers can do `::backdrop { --ac-modal-backdrop: white; }`
|
||||
* We can add ::backdrop to tokens.css when we remove the polyfill.
|
||||
*/
|
||||
background: var(--ac-modal-backdrop, rgb(2 20 49 / 0.49));
|
||||
animation: akselModalBackdropFadeIn 0.35s cubic-bezier(0.15, 1, 0.3, 1);
|
||||
}
|
||||
|
||||
.navds-modal--polyfilled + .backdrop /* Cannot be combined with ::backdrop selector */ {
|
||||
background: var(--ac-modal-backdrop, rgb(2 20 49 / 0.49));
|
||||
|
||||
/* From polyfill: */
|
||||
position: fixed;
|
||||
z-index: var(--a-z-index-modal);
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: var(--ac-modal-backdrop, var(--a-surface-backdrop));
|
||||
padding: var(--a-spacing-4);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.navds-modal__button--shake {
|
||||
transform: rotate(0deg);
|
||||
transition: transform 100ms cubic-bezier(0.82, 2, 1, 0.3);
|
||||
}
|
||||
|
||||
/* prettier-ignore */
|
||||
.navds-modal__overlay:active > .navds-modal:not(:active) > .navds-modal__button--shake {
|
||||
transform: rotate(10deg);
|
||||
transition: transform 100ms cubic-bezier(0.82, -2, 1, 0.3);
|
||||
}
|
||||
|
||||
.navds-modal__button {
|
||||
position: absolute;
|
||||
top: var(--a-spacing-4);
|
||||
right: var(--a-spacing-4);
|
||||
display: flex;
|
||||
padding: var(--a-spacing-2);
|
||||
margin-left: var(--a-spacing-4);
|
||||
float: right;
|
||||
}
|
||||
|
||||
.navds-modal__button svg {
|
||||
height: 1.5rem;
|
||||
width: 1.5rem;
|
||||
.navds-modal__header {
|
||||
padding: var(--a-spacing-6) var(--a-spacing-6) var(--a-spacing-4) var(--a-spacing-6);
|
||||
}
|
||||
|
||||
.navds-modal__header-icon svg {
|
||||
display: inline;
|
||||
vertical-align: -0.25rem;
|
||||
margin-right: var(--a-spacing-1);
|
||||
}
|
||||
|
||||
.navds-modal__label {
|
||||
font-weight: 600;
|
||||
color: var(--a-text-subtle);
|
||||
}
|
||||
|
||||
.navds-modal__body {
|
||||
padding: var(--a-spacing-6);
|
||||
overflow: auto;
|
||||
overscroll-behavior: contain;
|
||||
}
|
||||
|
||||
.navds-modal__header + .navds-modal__body {
|
||||
padding-top: var(--a-spacing-0);
|
||||
}
|
||||
|
||||
.navds-modal__footer {
|
||||
display: flex;
|
||||
flex-flow: row-reverse wrap;
|
||||
gap: var(--a-spacing-4);
|
||||
padding: var(--a-spacing-4) var(--a-spacing-6) var(--a-spacing-6) var(--a-spacing-6);
|
||||
}
|
||||
|
||||
.navds-modal__footer :nth-of-type(2) {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
@keyframes akselModalFadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translate3d(0, calc(5% + 4px), 0);
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
to {
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes akselModalBackdropFadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-css",
|
||||
"version": "4.7.4",
|
||||
"version": "5.0.3",
|
||||
"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": "^4.7.4",
|
||||
"@navikt/ds-tokens": "^5.0.3",
|
||||
"cssnano": "6.0.0",
|
||||
"fast-glob": "3.2.11",
|
||||
"lodash": "4.17.21",
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
.navds-stack {
|
||||
--__ac-stack-align: initial;
|
||||
--__ac-stack-justify: initial;
|
||||
--__ac-stack-direction: initial;
|
||||
--__ac-stack-wrap: initial;
|
||||
--__ac-stack-gap-xs: initial;
|
||||
--__ac-stack-gap-sm: initial;
|
||||
--__ac-stack-gap-md: initial;
|
||||
--__ac-stack-gap-lg: initial;
|
||||
--__ac-stack-gap-xl: initial;
|
||||
--__ac-stack-gap: var(--__ac-stack-gap-xs);
|
||||
|
||||
gap: var(--__ac-stack-gap);
|
||||
display: flex;
|
||||
align-items: var(--__ac-stack-align);
|
||||
justify-content: var(--__ac-stack-justify);
|
||||
flex-flow: var(--__ac-stack-direction) var(--__ac-stack-wrap);
|
||||
}
|
||||
|
||||
.navds-stack__spacer {
|
||||
flex: 1;
|
||||
justify-self: stretch;
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
.navds-stack > .navds-stack__spacer {
|
||||
margin-block-start: calc(var(--__ac-stack-gap) * -1);
|
||||
}
|
||||
|
||||
.navds-hstack > .navds-stack__spacer {
|
||||
margin-inline-start: calc(var(--__ac-stack-gap) * -1);
|
||||
}
|
||||
|
||||
@media (min-width: 480px) {
|
||||
.navds-stack {
|
||||
--__ac-stack-gap: var(--__ac-stack-gap-sm, var(--__ac-stack-gap-xs));
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.navds-stack {
|
||||
--__ac-stack-gap: var(--__ac-stack-gap-md, var(--__ac-stack-gap-sm, var(--__ac-stack-gap-xs)));
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.navds-stack {
|
||||
--__ac-stack-gap: var(--__ac-stack-gap-lg, var(--__ac-stack-gap-md, var(--__ac-stack-gap-sm, var(--__ac-stack-gap-xs))));
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1280px) {
|
||||
.navds-stack {
|
||||
--__ac-stack-gap: var(
|
||||
--__ac-stack-gap-xl,
|
||||
var(--__ac-stack-gap-lg, var(--__ac-stack-gap-md, var(--__ac-stack-gap-sm, var(--__ac-stack-gap-xs))))
|
||||
);
|
||||
}
|
||||
}
|
||||
+62
-35
@@ -16,7 +16,7 @@
|
||||
display: table-row;
|
||||
}
|
||||
|
||||
.navds-table__body .navds-table__row--shade-on-hover:hover {
|
||||
.navds-table__body .navds-table__row--shade-on-hover:not(.navds-table__expandable-row--expansion-disabled):hover {
|
||||
background-color: var(--ac-table-row-hover, var(--a-bg-subtle));
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
.navds-table__header-cell,
|
||||
.navds-table__data-cell {
|
||||
display: table-cell;
|
||||
padding: var(--a-spacing-4) var(--a-spacing-3);
|
||||
padding: var(--a-spacing-3) var(--a-spacing-2);
|
||||
border-bottom: 1px solid var(--ac-table-row-border, var(--a-border-default));
|
||||
text-align: left;
|
||||
}
|
||||
@@ -74,9 +74,14 @@
|
||||
border-bottom-color: transparent;
|
||||
}
|
||||
|
||||
.navds-table--large .navds-table__header-cell,
|
||||
.navds-table--large .navds-table__data-cell {
|
||||
padding: var(--a-spacing-4) var(--a-spacing-2);
|
||||
}
|
||||
|
||||
.navds-table--small .navds-table__header-cell,
|
||||
.navds-table--small .navds-table__data-cell {
|
||||
padding: var(--a-spacing-2) var(--a-spacing-3);
|
||||
padding: var(--a-spacing-1) var(--a-spacing-2);
|
||||
}
|
||||
|
||||
.navds-table .navds-checkbox .navds-checkbox__input {
|
||||
@@ -154,6 +159,10 @@
|
||||
transition: border-bottom-color 190ms cubic-bezier(0.6, 0.04, 0.98, 0.335);
|
||||
}
|
||||
|
||||
.navds-table__expandable-row.navds-table__expandable-row--clickable:not(.navds-table__expandable-row--expansion-disabled):hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.navds-table__expandable-row--open .navds-table__data-cell {
|
||||
border-bottom-color: transparent;
|
||||
}
|
||||
@@ -167,55 +176,73 @@
|
||||
}
|
||||
|
||||
.navds-table__toggle-expand-cell {
|
||||
padding: 0;
|
||||
width: 3.5rem;
|
||||
padding: 0 var(--a-spacing-2);
|
||||
position: relative;
|
||||
width: 2.5rem;
|
||||
}
|
||||
|
||||
.navds-table--large .navds-table__toggle-expand-cell {
|
||||
padding: 0 var(--a-spacing-2);
|
||||
}
|
||||
|
||||
.navds-table--small .navds-table__toggle-expand-cell {
|
||||
padding: 0;
|
||||
width: 2.25rem;
|
||||
padding: var(--a-spacing-1) var(--a-spacing-2);
|
||||
}
|
||||
|
||||
.navds-table__toggle-expand-button {
|
||||
all: unset;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
display: grid;
|
||||
place-content: center;
|
||||
cursor: pointer;
|
||||
padding: var(--a-spacing-4);
|
||||
margin: 0;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: var(--a-border-radius-medium);
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.navds-table--small .navds-table__toggle-expand-button {
|
||||
padding: var(--a-spacing-2);
|
||||
.navds-table__expandable-icon {
|
||||
transition: transform 150ms ease-in-out;
|
||||
}
|
||||
|
||||
.navds-table__toggle-expand-button:hover,
|
||||
.navds-table__expandable-row--clickable:hover .navds-table__toggle-expand-button {
|
||||
background-color: var(--a-surface-hover);
|
||||
}
|
||||
|
||||
.navds-table__toggle-expand-cell--open > :where(.navds-table__toggle-expand-button) :where(.navds-table__expandable-icon) {
|
||||
transform: translateY(0) rotate(180deg);
|
||||
}
|
||||
|
||||
.navds-table__toggle-expand-button:hover > :where(.navds-table__expandable-icon) {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
.navds-table__toggle-expand-cell--open > .navds-table__toggle-expand-button:hover > :where(.navds-table__expandable-icon) {
|
||||
transform: translateY(-1px) rotate(180deg);
|
||||
}
|
||||
|
||||
.navds-table__toggle-expand-button:focus-visible {
|
||||
box-shadow: inset var(--a-shadow-focus);
|
||||
outline: none;
|
||||
box-shadow: var(--a-shadow-focus);
|
||||
}
|
||||
|
||||
@supports not selector(:focus-visible) {
|
||||
.navds-table__toggle-expand-button:focus {
|
||||
box-shadow: inset var(--a-shadow-focus);
|
||||
outline: none;
|
||||
box-shadow: var(--a-shadow-focus);
|
||||
}
|
||||
}
|
||||
|
||||
.navds-table__expandable-icon {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.navds-table--small .navds-table__expandable-icon {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.navds-table__toggle-expand-button:hover .navds-table__expandable-icon {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
.navds-table__toggle-expand-cell--open .navds-table__expandable-icon {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.navds-table__toggle-expand-cell--open .navds-table__toggle-expand-button:hover .navds-table__expandable-icon {
|
||||
transform: translateY(-1px) rotate(180deg);
|
||||
.navds-table__toggle-expand-button::after {
|
||||
inset: 0;
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.navds-table__expanded-row-cell {
|
||||
@@ -231,9 +258,9 @@
|
||||
}
|
||||
|
||||
.navds-table__expanded-row-content {
|
||||
padding: var(--a-spacing-4) 4.25rem;
|
||||
padding: var(--a-spacing-4) calc(var(--a-spacing-2) + 3rem);
|
||||
}
|
||||
|
||||
.navds-table--small .navds-table__expanded-row-content {
|
||||
padding: var(--a-spacing-2) var(--a-spacing-12);
|
||||
padding: var(--a-spacing-2) calc(var(--a-spacing-2) + 3rem);
|
||||
}
|
||||
@@ -34,7 +34,7 @@
|
||||
cursor: pointer;
|
||||
background-color: var(--ac-toggle-group-button-bg, var(--a-surface-transparent));
|
||||
color: var(--ac-toggle-group-button-text, var(--a-text-default));
|
||||
border-radius: 2px;
|
||||
border-radius: var(--a-border-radius-small);
|
||||
min-width: fit-content;
|
||||
font-weight: var(--a-font-weight-regular);
|
||||
}
|
||||
@@ -113,7 +113,6 @@
|
||||
.navds-toggle-group__button[aria-checked="true"] {
|
||||
background-color: var(--ac-toggle-group-selected-bg, var(--a-surface-action-selected));
|
||||
color: var(--ac-toggle-group-selected-text, var(--a-text-on-action));
|
||||
font-weight: var(--a-font-weight-bold);
|
||||
}
|
||||
|
||||
.navds-toggle-group--neutral > .navds-toggle-group__button[aria-pressed="true"],
|
||||
|
||||
@@ -175,7 +175,10 @@
|
||||
"--ac-link-focus-border": "--a-border-focus",
|
||||
"--ac-link-active-text": "--a-text-on-action",
|
||||
"--ac-link-active-bg": "--a-border-focus",
|
||||
"--ac-link-active-border": "--a-border-focus"
|
||||
"--ac-link-active-border": "--a-border-focus",
|
||||
"--ac-link-action-text": "--a-text-action",
|
||||
"--ac-link-neutral-text": "--a-text-default",
|
||||
"--ac-link-subtle-text": "--a-text-subtle"
|
||||
},
|
||||
"list": {
|
||||
"--ac-list-marker-ul-color": "--ac-list-marker-color",
|
||||
@@ -193,7 +196,10 @@
|
||||
},
|
||||
"modal": {
|
||||
"--ac-modal-bg": "--a-surface-default",
|
||||
"--ac-modal-backdrop": "--a-surface-backdrop"
|
||||
"--ac-modal-border": "--a-border-subtle",
|
||||
"--ac-modal-backdrop": "--a-surface-backdrop",
|
||||
"--ac-modal-width-small": "450px",
|
||||
"--ac-modal-width-medium": "700px"
|
||||
},
|
||||
"pagination": {
|
||||
"--ac-pagination-text": "--a-text-default",
|
||||
@@ -385,7 +391,8 @@
|
||||
"--ac-combobox-list-item-selected-hover-bg": "--a-surface-action-subtle-hover",
|
||||
"--ac-combobox-list-item-loading-bg": "--a-surface-default",
|
||||
"--ac-combobox-list-item-hover-border-left": "--a-border-strong",
|
||||
"--ac-combobox-list-item-selected-hover-border-left": "--a-border-focus"
|
||||
"--ac-combobox-list-item-selected-hover-border-left": "--a-border-focus",
|
||||
"--ac-combobox-error-border": "--a-border-danger"
|
||||
},
|
||||
"select": {
|
||||
"--ac-select-bg": "--a-surface-default",
|
||||
|
||||
@@ -1,5 +1,170 @@
|
||||
# @navikt/ds-react
|
||||
|
||||
## 5.0.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- ConfirmationPanel: Fikset opplesing av feilmelding ([#2199](https://github.com/navikt/aksel/pull/2199))
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/aksel-icons@5.0.3
|
||||
|
||||
## 5.0.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- :label: Modal: Bedre type for `width` ([#2191](https://github.com/navikt/aksel/pull/2191))
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/aksel-icons@5.0.2
|
||||
|
||||
## 5.0.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/aksel-icons@5.0.1
|
||||
|
||||
## 5.0.0
|
||||
|
||||
### Major Changes
|
||||
|
||||
- Oppdatert Modal - Se [Migrering](https://aksel.nav.no/grunnleggende/kode/migrering) ([#2135](https://github.com/navikt/aksel/pull/2135))
|
||||
|
||||
- :sparkles: Støtte for header og footer
|
||||
- :boom: Bruker nå native dialog i stedet for react-modal (med polyfill for eldre nettlesere)
|
||||
- :boom: Provider: `appElement` er fjernet
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Table: ExpandableRow har oppdatert knapp for å matche Accordion og ExpansionCard ([#2178](https://github.com/navikt/aksel/pull/2178))
|
||||
|
||||
- Table: Har lagt til ny size: 'large'. ([#2178](https://github.com/navikt/aksel/pull/2178))
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/aksel-icons@5.0.0
|
||||
|
||||
## 4.12.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Added red border to Combobox in error state ([#2184](https://github.com/navikt/aksel/pull/2184))
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/aksel-icons@4.12.1
|
||||
|
||||
## 4.12.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Combobox: La til støtte for feilmeldinger i Combobox ([#2182](https://github.com/navikt/aksel/pull/2182))
|
||||
|
||||
- CopyButton: Har nå prop 'iconPosition' for å høyre/venstre aligne ikon ([#2173](https://github.com/navikt/aksel/pull/2173))
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- List: Fikset sentrering, margins ([#2168](https://github.com/navikt/aksel/pull/2168))
|
||||
|
||||
- Combobox: Kjører nå 'onChange' + 'onClear' når input blir reset programmatisk ([#2183](https://github.com/navikt/aksel/pull/2183))
|
||||
|
||||
- Combobox: Fikset custom-options i singleselect ([#2180](https://github.com/navikt/aksel/pull/2180))
|
||||
|
||||
- Combobox: Fjernet unødvendige 'onClear'-calls når man velger verdier ([#2170](https://github.com/navikt/aksel/pull/2170))
|
||||
|
||||
- Checkbox: Checkmark er nå SVG-ikon og ikke Base64 ([#2171](https://github.com/navikt/aksel/pull/2171))
|
||||
|
||||
- Combobox: Lukker nå nedtrekksmeny hvis man legger til ny option i singleselect ([#2177](https://github.com/navikt/aksel/pull/2177))
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/aksel-icons@4.12.0
|
||||
|
||||
## 4.11.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/aksel-icons@4.11.2
|
||||
|
||||
## 4.11.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/aksel-icons@4.11.1
|
||||
|
||||
## 4.11.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- HGrid: Ny komponent 'HGrid' er ute i Beta: [Dokumentasjon](https://aksel.nav.no/komponenter/core/hgrid) ([#1838](https://github.com/navikt/aksel/pull/1838))
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Fixes bug where combobox list could not be closed after clicking a chip ([#2155](https://github.com/navikt/aksel/pull/2155))
|
||||
|
||||
- Grid: Markert som deprecated. Bruk nye 'HGrid' ([#2153](https://github.com/navikt/aksel/pull/2153))
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/aksel-icons@4.11.0
|
||||
|
||||
## 4.10.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/aksel-icons@4.10.2
|
||||
|
||||
## 4.10.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/aksel-icons@4.10.1
|
||||
|
||||
## 4.10.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Link: La til 'variant', 'underline' og 'inlineText'-prop ([#2093](https://github.com/navikt/aksel/pull/2093))
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/aksel-icons@4.10.0
|
||||
|
||||
## 4.9.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Button: Fikset aria-live bug der knapp alltid ble lest opp av skjermleser ved render ([#2143](https://github.com/navikt/aksel/pull/2143))
|
||||
|
||||
- Tweaks to combobox - updated example, small bugfixes, better affordance for selected options and added flag for "isAddedByUser" to onToggleSelected ([#2144](https://github.com/navikt/aksel/pull/2144))
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/aksel-icons@4.9.1
|
||||
|
||||
## 4.9.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Table: ExpandableRow kan nå åpnes med 'expandOnRowClick'-prop ([#2127](https://github.com/navikt/aksel/pull/2127))
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/aksel-icons@4.9.0
|
||||
|
||||
## 4.8.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- Nye komponenter `VStack`, `HStack` og `Spacer` for å enklere kunne lage layout med flexbox og spacing-variabler. ([#2040](https://github.com/navikt/aksel/pull/2040))
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @navikt/aksel-icons@4.8.0
|
||||
|
||||
## 4.7.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-react",
|
||||
"version": "4.7.4",
|
||||
"version": "5.0.3",
|
||||
"description": "Aksel react-components for NAV designsystem",
|
||||
"author": "Aksel | NAV designsystem team",
|
||||
"license": "MIT",
|
||||
@@ -38,13 +38,12 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@floating-ui/react": "0.24.1",
|
||||
"@navikt/aksel-icons": "^4.7.4",
|
||||
"@navikt/aksel-icons": "^5.0.3",
|
||||
"@radix-ui/react-tabs": "1.0.0",
|
||||
"@radix-ui/react-toggle-group": "1.0.0",
|
||||
"clsx": "^1.2.1",
|
||||
"date-fns": "2.29.3",
|
||||
"react-day-picker": "8.3.4",
|
||||
"react-modal": "3.15.1"
|
||||
"react-day-picker": "8.3.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/dom": "8.13.0",
|
||||
@@ -53,7 +52,6 @@
|
||||
"@testing-library/user-event": "^14.2.0",
|
||||
"@types/faker": "5.5.8",
|
||||
"@types/jest": "^29.0.0",
|
||||
"@types/react-modal": "^3.13.1",
|
||||
"concurrently": "7.2.1",
|
||||
"copyfiles": "^2.4.1",
|
||||
"faker": "5.5.3",
|
||||
|
||||
@@ -102,6 +102,7 @@ export const Alert = forwardRef<HTMLDivElement, AlertProps>(
|
||||
{
|
||||
"navds-alert--full-width": fullWidth,
|
||||
"navds-alert--inline": inline,
|
||||
"navds-alert--close-button": closeButton,
|
||||
}
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -185,6 +185,16 @@ export const WithCloseButton: Story = {
|
||||
</BodyLong>
|
||||
<Link href="#">Id elit esse enim reprehenderit</Link>
|
||||
</AlertWithCloseButton>
|
||||
<AlertWithCloseButton>
|
||||
<DsHeading spacing size="small" level="3">
|
||||
Aliquip duis est in commodo pariatur
|
||||
</DsHeading>
|
||||
<BodyLong>
|
||||
Ullamco ullamco laborum et commodo sint culpa cupidatat culpa qui
|
||||
laboris ex. Labore ex occaecat proident qui qui fugiat magna. Fugiat
|
||||
sint commodo consequat eu aute.
|
||||
</BodyLong>
|
||||
</AlertWithCloseButton>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
@@ -197,6 +207,6 @@ export const WithCloseButton: Story = {
|
||||
});
|
||||
|
||||
const buttonsAfter = canvas.getAllByTitle("Lukk Alert");
|
||||
expect(buttonsAfter.length).toBe(1);
|
||||
expect(buttonsAfter.length).toBe(2);
|
||||
},
|
||||
};
|
||||
@@ -126,11 +126,7 @@ export const Button: OverridableComponent<ButtonProps, HTMLButtonElement> =
|
||||
<span className="navds-button__icon">{icon}</span>
|
||||
)}
|
||||
{children && (
|
||||
<Label
|
||||
as="span"
|
||||
size={size === "medium" ? "medium" : "small"}
|
||||
aria-live="polite"
|
||||
>
|
||||
<Label as="span" size={size === "medium" ? "medium" : "small"}>
|
||||
{children}
|
||||
</Label>
|
||||
)}
|
||||
|
||||
@@ -148,8 +148,9 @@ export const Loading = {
|
||||
</div>
|
||||
),
|
||||
|
||||
args: { chromatic: { disableSnapshot: true } },
|
||||
chromatic: { disableSnapshot: true },
|
||||
parameters: {
|
||||
chromatic: { disableSnapshot: true },
|
||||
},
|
||||
};
|
||||
|
||||
export const Icon = () => (
|
||||
@@ -275,6 +276,7 @@ export const LoadingWithAs = {
|
||||
</div>
|
||||
),
|
||||
|
||||
args: { chromatic: { disableSnapshot: true } },
|
||||
chromatic: { disableSnapshot: true },
|
||||
parameters: {
|
||||
chromatic: { disableSnapshot: true },
|
||||
},
|
||||
};
|
||||
@@ -63,6 +63,11 @@ export interface CopyButtonProps
|
||||
* @default 'Kopiert'
|
||||
*/
|
||||
activeTitle?: string;
|
||||
/**
|
||||
* Icon position in Button
|
||||
* @default "left"
|
||||
*/
|
||||
iconPosition?: "left" | "right";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -91,6 +96,7 @@ export const CopyButton = forwardRef<HTMLButtonElement, CopyButtonProps>(
|
||||
activeDuration = 2000,
|
||||
title = "Kopier",
|
||||
activeTitle = "Kopiert",
|
||||
iconPosition = "left",
|
||||
...rest
|
||||
},
|
||||
ref
|
||||
@@ -119,6 +125,25 @@ export const CopyButton = forwardRef<HTMLButtonElement, CopyButtonProps>(
|
||||
}, activeDuration);
|
||||
};
|
||||
|
||||
const CopyIcon = () => {
|
||||
return active ? (
|
||||
<span className="navds-copybutton__icon">
|
||||
{activeIcon ?? (
|
||||
<CheckmarkIcon
|
||||
aria-hidden={!!text}
|
||||
title={text ? undefined : activeTitle}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
) : (
|
||||
<span className="navds-copybutton__icon">
|
||||
{icon ?? (
|
||||
<FilesIcon aria-hidden={!!text} title={text ? undefined : title} />
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
ref={ref}
|
||||
@@ -138,25 +163,7 @@ export const CopyButton = forwardRef<HTMLButtonElement, CopyButtonProps>(
|
||||
onClick={handleClick}
|
||||
>
|
||||
<span className="navds-copybutton__content">
|
||||
{active ? (
|
||||
<span className="navds-copybutton__icon">
|
||||
{activeIcon ?? (
|
||||
<CheckmarkIcon
|
||||
aria-hidden={!!text}
|
||||
title={text ? undefined : activeTitle}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
) : (
|
||||
<span className="navds-copybutton__icon">
|
||||
{icon ?? (
|
||||
<FilesIcon
|
||||
aria-hidden={!!text}
|
||||
title={text ? undefined : title}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
{iconPosition === "left" && <CopyIcon />}
|
||||
|
||||
{text &&
|
||||
(active ? (
|
||||
@@ -176,6 +183,7 @@ export const CopyButton = forwardRef<HTMLButtonElement, CopyButtonProps>(
|
||||
{text}
|
||||
</Label>
|
||||
))}
|
||||
{iconPosition === "right" && <CopyIcon />}
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
|
||||
@@ -60,6 +60,16 @@ export const Variants = {
|
||||
),
|
||||
};
|
||||
|
||||
export const IconPosition = {
|
||||
render: () => (
|
||||
<div className="colgap">
|
||||
<CopyButton copyText="3.14" iconPosition="left" text="Kopier" />
|
||||
|
||||
<CopyButton copyText="3.14" iconPosition="right" text="Kopier" />
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
export const Sizes = {
|
||||
render: () => (
|
||||
<div className="colgap">
|
||||
|
||||
@@ -109,9 +109,9 @@ export interface DatePickerDefaultProps
|
||||
onOpenToggle?: () => void;
|
||||
/**
|
||||
* Avoid using if possible!
|
||||
* Changes what CSS position property to use
|
||||
* Changes what CSS position property to use.
|
||||
* You want to use "fixed" if parent wrapper has position relative, but you want popover to escape
|
||||
* @default "absolute"
|
||||
* @default See Popover
|
||||
*/
|
||||
strategy?: "absolute" | "fixed";
|
||||
/**
|
||||
@@ -184,7 +184,7 @@ export const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>(
|
||||
open: _open,
|
||||
onClose,
|
||||
onOpenToggle,
|
||||
strategy = "absolute",
|
||||
strategy,
|
||||
bubbleEscape = false,
|
||||
...rest
|
||||
},
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useCallback, useEffect } from "react";
|
||||
import { useCallback, useEffect, RefObject } from "react";
|
||||
|
||||
export const useEscape = (
|
||||
open: boolean,
|
||||
setOpen: (openState: boolean) => void,
|
||||
focusRef: any
|
||||
focusRef: RefObject<HTMLElement>
|
||||
) => {
|
||||
const handleClose = useCallback(() => {
|
||||
setOpen(false);
|
||||
@@ -11,7 +11,12 @@ export const useEscape = (
|
||||
}, [focusRef, setOpen]);
|
||||
|
||||
const escape = useCallback(
|
||||
(e) => open && e.key === "Escape" && handleClose(),
|
||||
(event: KeyboardEvent) => {
|
||||
if (open && event.key === "Escape") {
|
||||
event.preventDefault(); // This prevents modal from closing when using datepicker inside modal
|
||||
handleClose();
|
||||
}
|
||||
},
|
||||
[handleClose, open]
|
||||
);
|
||||
|
||||
|
||||
@@ -88,12 +88,7 @@ export const ConfirmationPanel = forwardRef<
|
||||
{label}
|
||||
</Checkbox>
|
||||
</div>
|
||||
<div
|
||||
className="navds-form-field__error"
|
||||
id={errorId}
|
||||
aria-relevant="additions removals"
|
||||
aria-live="polite"
|
||||
>
|
||||
<div className="navds-form-field__error" id={errorId} role="alert">
|
||||
{showErrorMsg && <ErrorMessage size={size}>{props.error}</ErrorMessage>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -86,6 +86,23 @@ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
|
||||
}}
|
||||
/>
|
||||
<label htmlFor={inputProps.id} className="navds-checkbox__label">
|
||||
<span className="navds-checkbox__icon">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="13"
|
||||
height="10"
|
||||
viewBox="0 0 13 10"
|
||||
fill="none"
|
||||
focusable={false}
|
||||
role="img"
|
||||
aria-hidden
|
||||
>
|
||||
<path
|
||||
d="M4.03524 6.41478L10.4752 0.404669C11.0792 -0.160351 12.029 -0.130672 12.5955 0.47478C13.162 1.08027 13.1296 2.03007 12.5245 2.59621L5.02111 9.59934C4.74099 9.85904 4.37559 10 4.00025 10C3.60651 10 3.22717 9.84621 2.93914 9.56111L0.439143 7.06111C-0.146381 6.47558 -0.146381 5.52542 0.439143 4.93989C1.02467 4.35437 1.97483 4.35437 2.56036 4.93989L4.03524 6.41478Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span
|
||||
className={cl("navds-checkbox__content", {
|
||||
"navds-sr-only": props.hideLabel,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import cl from "clsx";
|
||||
import React, { forwardRef, useMemo, useRef } from "react";
|
||||
import { BodyShort, Label, mergeRefs } from "../..";
|
||||
import { BodyShort, ErrorMessage, Label, mergeRefs } from "../..";
|
||||
import ClearButton from "./ClearButton";
|
||||
import FilteredOptions from "./FilteredOptions/FilteredOptions";
|
||||
import { useFilteredOptionsContext } from "./FilteredOptions/filteredOptionsContext";
|
||||
@@ -39,12 +39,15 @@ export const Combobox = forwardRef<
|
||||
|
||||
const {
|
||||
clearInput,
|
||||
error,
|
||||
errorId,
|
||||
focusInput,
|
||||
hasError,
|
||||
inputDescriptionId,
|
||||
inputProps,
|
||||
inputRef,
|
||||
value,
|
||||
showErrorMsg,
|
||||
size = "medium",
|
||||
} = useInputContext();
|
||||
|
||||
@@ -129,6 +132,14 @@ export const Combobox = forwardRef<
|
||||
</div>
|
||||
<FilteredOptions />
|
||||
</div>
|
||||
<div
|
||||
className="navds-form-field__error"
|
||||
id={errorId}
|
||||
aria-relevant="additions removals"
|
||||
aria-live="polite"
|
||||
>
|
||||
{showErrorMsg && <ErrorMessage size={size}>{error}</ErrorMessage>}
|
||||
</div>
|
||||
</ComboboxWrapper>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import cl from "clsx";
|
||||
import React, { useRef } from "react";
|
||||
import React, { useRef, useState } from "react";
|
||||
|
||||
type ComboboxWrapperProps = {
|
||||
children: any;
|
||||
@@ -23,6 +23,7 @@ const ComboboxWrapper = ({
|
||||
toggleListButtonRef,
|
||||
}: ComboboxWrapperProps) => {
|
||||
const wrapperRef = useRef<HTMLDivElement | null>(null);
|
||||
const [hasFocusWithin, setHasFocusWithin] = useState(false);
|
||||
|
||||
function onFocusInsideWrapper(e) {
|
||||
if (
|
||||
@@ -30,12 +31,14 @@ const ComboboxWrapper = ({
|
||||
toggleListButtonRef?.current !== e.target
|
||||
) {
|
||||
toggleIsListOpen(true);
|
||||
setHasFocusWithin(true);
|
||||
}
|
||||
}
|
||||
|
||||
function onBlurWrapper(e) {
|
||||
if (!wrapperRef.current?.contains(e.relatedTarget)) {
|
||||
toggleIsListOpen(false);
|
||||
setHasFocusWithin(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,14 +49,15 @@ const ComboboxWrapper = ({
|
||||
className,
|
||||
"navds-form-field",
|
||||
`navds-form-field--${inputSize}`,
|
||||
"navds-search",
|
||||
{
|
||||
"navds-search--error": hasError,
|
||||
"navds-search--disabled": !!inputProps.disabled,
|
||||
"navds-combobox--error": hasError,
|
||||
"navds-combobox--disabled": !!inputProps.disabled,
|
||||
"navds-combobox--focused": hasFocusWithin,
|
||||
}
|
||||
)}
|
||||
onFocus={onFocusInsideWrapper}
|
||||
onBlur={onBlurWrapper}
|
||||
tabIndex={-1}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@@ -48,7 +48,11 @@ const FilteredOptions = () => {
|
||||
{isValueNew && allowNewValues && (
|
||||
<li
|
||||
tabIndex={-1}
|
||||
onPointerUp={(event) => toggleOption(value, event)}
|
||||
onPointerUp={(event) => {
|
||||
toggleOption(value, event);
|
||||
if (!isMultiSelect && !selectedOptions.includes(value))
|
||||
toggleIsListOpen(false);
|
||||
}}
|
||||
id={`${id}-combobox-new-option`}
|
||||
className={cl("navds-combobox__list-item__new-option", {
|
||||
"navds-combobox__list-item__new-option--focus":
|
||||
@@ -88,9 +92,8 @@ const FilteredOptions = () => {
|
||||
tabIndex={-1}
|
||||
onPointerUp={(event) => {
|
||||
toggleOption(option, event);
|
||||
if (!isMultiSelect) {
|
||||
if (!isMultiSelect && !selectedOptions.includes(option))
|
||||
toggleIsListOpen(false);
|
||||
}
|
||||
}}
|
||||
role="option"
|
||||
aria-selected={selectedOptions.includes(option)}
|
||||
|
||||
@@ -8,6 +8,7 @@ import React, {
|
||||
useRef,
|
||||
useLayoutEffect,
|
||||
} from "react";
|
||||
import cl from "clsx";
|
||||
import { useCustomOptionsContext } from "../customOptionsContext";
|
||||
import { useInputContext } from "../Input/inputContext";
|
||||
import usePrevious from "../../../util/usePrevious";
|
||||
@@ -58,7 +59,7 @@ export const FilteredOptionsProvider = ({ children, value: props }) => {
|
||||
} = props;
|
||||
const filteredOptionsRef = useRef<HTMLUListElement | null>(null);
|
||||
const {
|
||||
inputProps: { id },
|
||||
inputProps: { "aria-describedby": partialAriaDescribedBy, id },
|
||||
value,
|
||||
searchTerm,
|
||||
setValue,
|
||||
@@ -124,18 +125,26 @@ export const FilteredOptionsProvider = ({ children, value: props }) => {
|
||||
}, [allowNewValues, isValueNew]);
|
||||
|
||||
const ariaDescribedBy = useMemo(() => {
|
||||
let activeOption;
|
||||
if (!isLoading && filteredOptions.length === 0) {
|
||||
return `${id}-no-hits`;
|
||||
activeOption = `${id}-no-hits`;
|
||||
} else if ((value && value !== "") || isLoading) {
|
||||
if (shouldAutocomplete && filteredOptions[0]) {
|
||||
return `${id}-option-${filteredOptions[0].replace(" ", "-")}`;
|
||||
} else if (isLoading) {
|
||||
return `${id}-is-loading`;
|
||||
activeOption = `${id}-option-${filteredOptions[0].replace(" ", "-")}`;
|
||||
} else if (isListOpen && isLoading) {
|
||||
activeOption = `${id}-is-loading`;
|
||||
}
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}, [isLoading, value, shouldAutocomplete, filteredOptions, id]);
|
||||
return cl(activeOption, partialAriaDescribedBy) || undefined;
|
||||
}, [
|
||||
isListOpen,
|
||||
isLoading,
|
||||
value,
|
||||
partialAriaDescribedBy,
|
||||
shouldAutocomplete,
|
||||
filteredOptions,
|
||||
id,
|
||||
]);
|
||||
|
||||
const currentOption = useMemo(() => {
|
||||
if (filteredOptionsIndex == null) {
|
||||
|
||||
@@ -22,8 +22,12 @@ interface InputProps
|
||||
const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
({ inputClassName, error, errorId, ...rest }, ref) => {
|
||||
const { clearInput, inputProps, onChange, size, value } = useInputContext();
|
||||
const { selectedOptions, removeSelectedOption, toggleOption } =
|
||||
useSelectedOptionsContext();
|
||||
const {
|
||||
selectedOptions,
|
||||
removeSelectedOption,
|
||||
toggleOption,
|
||||
isMultiSelect,
|
||||
} = useSelectedOptionsContext();
|
||||
const {
|
||||
activeDecendantId,
|
||||
allowNewValues,
|
||||
@@ -37,6 +41,7 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
ariaDescribedBy,
|
||||
moveFocusToInput,
|
||||
moveFocusToEnd,
|
||||
setFilteredOptionsIndex,
|
||||
shouldAutocomplete,
|
||||
} = useFilteredOptionsContext();
|
||||
|
||||
@@ -46,7 +51,8 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
event.preventDefault();
|
||||
// Selecting a value from the dropdown / FilteredOptions
|
||||
toggleOption(currentOption, event);
|
||||
clearInput(event);
|
||||
if (!isMultiSelect && !selectedOptions.includes(currentOption))
|
||||
toggleIsListOpen(false);
|
||||
} else if (shouldAutocomplete && selectedOptions.includes(value)) {
|
||||
event.preventDefault();
|
||||
// Trying to set the same value that is already set, so just clearing the input
|
||||
@@ -55,15 +61,18 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
event.preventDefault();
|
||||
// Autocompleting or adding a new value
|
||||
toggleOption(value, event);
|
||||
clearInput(event);
|
||||
if (!isMultiSelect && !selectedOptions.includes(value))
|
||||
toggleIsListOpen(false);
|
||||
}
|
||||
},
|
||||
[
|
||||
allowNewValues,
|
||||
clearInput,
|
||||
currentOption,
|
||||
isMultiSelect,
|
||||
selectedOptions,
|
||||
shouldAutocomplete,
|
||||
toggleIsListOpen,
|
||||
toggleOption,
|
||||
value,
|
||||
]
|
||||
@@ -139,6 +148,10 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
[filteredOptions.length, onChange, toggleIsListOpen]
|
||||
);
|
||||
|
||||
const onBlur = () => {
|
||||
setFilteredOptionsIndex(-1);
|
||||
};
|
||||
|
||||
return (
|
||||
<input
|
||||
{...rest}
|
||||
@@ -148,6 +161,7 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
onChange={onChangeHandler}
|
||||
type="text"
|
||||
role="combobox"
|
||||
onBlur={onBlur}
|
||||
onKeyUp={handleKeyUp}
|
||||
onKeyDown={handleKeyDown}
|
||||
aria-controls={`${inputProps.id}-filtered-options`}
|
||||
@@ -156,6 +170,7 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
aria-autocomplete={shouldAutocomplete ? "both" : "list"}
|
||||
aria-activedescendant={activeDecendantId}
|
||||
aria-describedby={ariaDescribedBy}
|
||||
aria-invalid={inputProps["aria-invalid"]}
|
||||
className={cl(
|
||||
inputClassName,
|
||||
"navds-combobox__input",
|
||||
|
||||
@@ -13,6 +13,7 @@ import { useFormField, FormFieldType } from "../../useFormField";
|
||||
|
||||
interface InputContextType extends FormFieldType {
|
||||
clearInput: (event: React.PointerEvent | React.KeyboardEvent) => void;
|
||||
error?: string;
|
||||
focusInput: () => void;
|
||||
inputRef: React.RefObject<HTMLInputElement>;
|
||||
value: string;
|
||||
@@ -80,10 +81,11 @@ export const InputContextProvider = ({ children, value: props }) => {
|
||||
const clearInput = useCallback(
|
||||
(event: React.PointerEvent | React.KeyboardEvent) => {
|
||||
onClear?.(event);
|
||||
externalOnChange?.(null, "");
|
||||
setValue("");
|
||||
setSearchTerm("");
|
||||
},
|
||||
[onClear, setSearchTerm, setValue]
|
||||
[externalOnChange, onClear, setValue]
|
||||
);
|
||||
|
||||
const focusInput = useCallback(() => {
|
||||
@@ -101,6 +103,7 @@ export const InputContextProvider = ({ children, value: props }) => {
|
||||
value={{
|
||||
...formFieldProps,
|
||||
clearInput,
|
||||
error,
|
||||
focusInput,
|
||||
inputRef,
|
||||
value,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from "react";
|
||||
import { Chips } from "../../..";
|
||||
import { useSelectedOptionsContext } from "./selectedOptionsContext";
|
||||
import { useInputContext } from "../Input/inputContext";
|
||||
|
||||
interface SelectedOptionsProps {
|
||||
selectedOptions?: string[];
|
||||
@@ -10,10 +11,12 @@ interface SelectedOptionsProps {
|
||||
|
||||
const Option = ({ option }: { option: string }) => {
|
||||
const { isMultiSelect, removeSelectedOption } = useSelectedOptionsContext();
|
||||
const { focusInput } = useInputContext();
|
||||
|
||||
const onClick = (e) => {
|
||||
e.stopPropagation();
|
||||
removeSelectedOption(option);
|
||||
focusInput();
|
||||
};
|
||||
|
||||
if (!isMultiSelect) {
|
||||
|
||||
@@ -42,8 +42,12 @@ export const SelectedOptionsProvider = ({
|
||||
>;
|
||||
}) => {
|
||||
const { clearInput, focusInput } = useInputContext();
|
||||
const { customOptions, removeCustomOption, addCustomOption } =
|
||||
useCustomOptionsContext();
|
||||
const {
|
||||
customOptions,
|
||||
removeCustomOption,
|
||||
addCustomOption,
|
||||
setCustomOptions,
|
||||
} = useCustomOptionsContext();
|
||||
const {
|
||||
allowNewValues,
|
||||
isMultiSelect,
|
||||
@@ -60,12 +64,12 @@ export const SelectedOptionsProvider = ({
|
||||
|
||||
const addSelectedOption = useCallback(
|
||||
(option: string) => {
|
||||
if (
|
||||
!options
|
||||
.map((opt) => opt.toLowerCase())
|
||||
.includes(option?.toLowerCase?.())
|
||||
) {
|
||||
const isCustomOption = !options
|
||||
.map((opt) => opt.toLowerCase())
|
||||
.includes(option?.toLowerCase?.());
|
||||
if (isCustomOption) {
|
||||
allowNewValues && addCustomOption(option);
|
||||
!isMultiSelect && setSelectedOptions([]);
|
||||
} else if (isMultiSelect) {
|
||||
setSelectedOptions((prevSelectedOptions) => [
|
||||
...prevSelectedOptions,
|
||||
@@ -73,15 +77,24 @@ export const SelectedOptionsProvider = ({
|
||||
]);
|
||||
} else {
|
||||
setSelectedOptions([option]);
|
||||
setCustomOptions([]);
|
||||
}
|
||||
onToggleSelected?.(option, true);
|
||||
onToggleSelected?.(option, true, isCustomOption);
|
||||
},
|
||||
[addCustomOption, allowNewValues, isMultiSelect, onToggleSelected, options]
|
||||
[
|
||||
addCustomOption,
|
||||
allowNewValues,
|
||||
isMultiSelect,
|
||||
onToggleSelected,
|
||||
options,
|
||||
setCustomOptions,
|
||||
]
|
||||
);
|
||||
|
||||
const removeSelectedOption = useCallback(
|
||||
(option: string) => {
|
||||
if (customOptions.includes(option)) {
|
||||
const isCustomOption = customOptions.includes(option);
|
||||
if (isCustomOption) {
|
||||
removeCustomOption(option);
|
||||
} else {
|
||||
setSelectedOptions((prevSelectedOptions) =>
|
||||
@@ -90,7 +103,7 @@ export const SelectedOptionsProvider = ({
|
||||
)
|
||||
);
|
||||
}
|
||||
onToggleSelected?.(option, false);
|
||||
onToggleSelected?.(option, false, isCustomOption);
|
||||
},
|
||||
[customOptions, onToggleSelected, removeCustomOption]
|
||||
);
|
||||
@@ -102,16 +115,13 @@ export const SelectedOptionsProvider = ({
|
||||
} else {
|
||||
addSelectedOption(option);
|
||||
}
|
||||
if (!isMultiSelect) {
|
||||
clearInput(event);
|
||||
}
|
||||
clearInput(event);
|
||||
focusInput();
|
||||
},
|
||||
[
|
||||
addSelectedOption,
|
||||
clearInput,
|
||||
focusInput,
|
||||
isMultiSelect,
|
||||
removeSelectedOption,
|
||||
selectedOptions,
|
||||
]
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Meta } from "@storybook/react";
|
||||
import React, { useState, useId, useMemo } from "react";
|
||||
import { userEvent, within } from "@storybook/testing-library";
|
||||
import { Chips, UNSAFE_Combobox, TextField } from "../../index";
|
||||
import { expect } from "@storybook/jest";
|
||||
import { expect, jest } from "@storybook/jest";
|
||||
|
||||
export default {
|
||||
title: "ds-react/Combobox",
|
||||
@@ -82,6 +82,7 @@ export function MultiSelect(props) {
|
||||
label="Komboboks - velg flere"
|
||||
options={props.options}
|
||||
isMultiSelect={props.isMultiSelect}
|
||||
size={props.size}
|
||||
/>
|
||||
</DemoContainer>
|
||||
);
|
||||
@@ -90,6 +91,28 @@ export function MultiSelect(props) {
|
||||
MultiSelect.args = {
|
||||
options,
|
||||
isMultiSelect: true,
|
||||
size: "medium",
|
||||
};
|
||||
|
||||
export function WithAddNewOptions(props) {
|
||||
const id = useId();
|
||||
return (
|
||||
<DemoContainer dataTheme={props.darkMode}>
|
||||
<UNSAFE_Combobox
|
||||
id={id}
|
||||
label="Komboboks med mulighet for å legge til nye verdier"
|
||||
options={props.options}
|
||||
allowNewValues={props.allowNewValues}
|
||||
shouldAutocomplete={props.shouldAutoComplete}
|
||||
/>
|
||||
</DemoContainer>
|
||||
);
|
||||
}
|
||||
|
||||
WithAddNewOptions.args = {
|
||||
options,
|
||||
allowNewValues: true,
|
||||
shouldAutoComplete: true,
|
||||
};
|
||||
|
||||
export function MultiSelectWithAddNewOptions(props) {
|
||||
@@ -99,7 +122,7 @@ export function MultiSelectWithAddNewOptions(props) {
|
||||
<UNSAFE_Combobox
|
||||
id={id}
|
||||
isMultiSelect={props.isMultiSelect}
|
||||
label="Komboboks (med mulighet for å legge til nye verdier)"
|
||||
label="Multiselect komboboks med mulighet for å legge til nye verdier"
|
||||
options={props.options}
|
||||
allowNewValues={props.allowNewValues}
|
||||
/>
|
||||
@@ -293,6 +316,33 @@ ComboboxSizes.args = {
|
||||
options,
|
||||
};
|
||||
|
||||
export const WithError = {
|
||||
args: {
|
||||
error: "Du må velge en favorittfrukt.",
|
||||
isLoading: true,
|
||||
},
|
||||
render: (props) => {
|
||||
const [hasSelectedValue, setHasSelectedValue] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
return (
|
||||
<DemoContainer dataTheme={props.darkMode}>
|
||||
<UNSAFE_Combobox
|
||||
filteredOptions={isLoading ? [] : undefined}
|
||||
options={options}
|
||||
label="Hva er dine favorittfrukter?"
|
||||
error={!hasSelectedValue && props.error}
|
||||
isLoading={isLoading}
|
||||
onChange={() => {
|
||||
setIsLoading(true);
|
||||
setTimeout(() => setIsLoading(false), 2000);
|
||||
}}
|
||||
onToggleSelected={(_, isSelected) => setHasSelectedValue(isSelected)}
|
||||
/>
|
||||
</DemoContainer>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
function sleep(ms: number) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
@@ -322,6 +372,7 @@ export const CancelInputTest = {
|
||||
userEvent.keyboard("{Escape}");
|
||||
await sleep(1000);
|
||||
userEvent.keyboard("{ArrowDown}");
|
||||
await sleep(500);
|
||||
const banana = canvas.getByText("banana");
|
||||
userEvent.click(banana);
|
||||
},
|
||||
@@ -363,12 +414,20 @@ export const RemoveSelectedMultiSelectTest = {
|
||||
await sleep(250);
|
||||
userEvent.keyboard("{Enter}");
|
||||
await sleep(250);
|
||||
userEvent.keyboard("{Escape}");
|
||||
await sleep(250);
|
||||
|
||||
const appleSlett = canvas.getByLabelText("apple slett");
|
||||
appleSlett.focus();
|
||||
await sleep(250);
|
||||
userEvent.click(appleSlett);
|
||||
await sleep(250);
|
||||
const appleOption = canvas.getByRole("option", {
|
||||
name: "apple",
|
||||
selected: false,
|
||||
});
|
||||
expect(appleOption).toBeVisible();
|
||||
userEvent.keyboard("{Escape}");
|
||||
await sleep(250);
|
||||
expect(appleOption).not.toBeVisible();
|
||||
|
||||
const bananaSlett = canvas.getByLabelText("banana slett");
|
||||
expect(bananaSlett).toBeInTheDocument();
|
||||
@@ -411,3 +470,42 @@ export const AddWhenAddNewDisabledTest = {
|
||||
expect(invalidSelect).not.toBeInTheDocument();
|
||||
},
|
||||
};
|
||||
|
||||
export const TestThatCallbacksOnlyFireWhenExpected = {
|
||||
args: {
|
||||
onChange: jest.fn(),
|
||||
onClear: jest.fn(),
|
||||
onToggleSelected: jest.fn(),
|
||||
},
|
||||
render: (props) => {
|
||||
return (
|
||||
<DemoContainer dataTheme={props.darkMode}>
|
||||
<UNSAFE_Combobox
|
||||
options={options}
|
||||
label="Hva er dine favorittfrukter?"
|
||||
{...props}
|
||||
/>
|
||||
</DemoContainer>
|
||||
);
|
||||
},
|
||||
play: async ({ canvasElement, args }) => {
|
||||
args.onToggleSelected.mockClear();
|
||||
args.onClear.mockClear();
|
||||
args.onChange.mockClear();
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
const input = canvas.getByLabelText("Hva er dine favorittfrukter?");
|
||||
const searchWord = "tangerine";
|
||||
|
||||
userEvent.click(input);
|
||||
await userEvent.type(input, searchWord, { delay: 200 });
|
||||
await sleep(250);
|
||||
userEvent.keyboard("{ArrowDown}");
|
||||
await sleep(250);
|
||||
userEvent.keyboard("{Enter}");
|
||||
await sleep(250);
|
||||
expect(args.onClear.mock.calls).toHaveLength(1);
|
||||
expect(args.onToggleSelected.mock.calls).toHaveLength(1);
|
||||
expect(args.onChange.mock.calls).toHaveLength(searchWord.length + 1);
|
||||
},
|
||||
};
|
||||
@@ -1,10 +1,12 @@
|
||||
import React, { useState, useCallback, createContext, useContext } from "react";
|
||||
import { useInputContext } from "./Input/inputContext";
|
||||
import { useSelectedOptionsContext } from "./SelectedOptions/selectedOptionsContext";
|
||||
|
||||
type CustomOptionsContextType = {
|
||||
customOptions: string[];
|
||||
removeCustomOption: (option: string) => void;
|
||||
addCustomOption: (option: string) => void;
|
||||
setCustomOptions: React.Dispatch<React.SetStateAction<string[]>>;
|
||||
};
|
||||
|
||||
const CustomOptionsContext = createContext<CustomOptionsContextType>(
|
||||
@@ -14,6 +16,7 @@ const CustomOptionsContext = createContext<CustomOptionsContextType>(
|
||||
export const CustomOptionsProvider = ({ children }) => {
|
||||
const [customOptions, setCustomOptions] = useState<string[]>([]);
|
||||
const { focusInput } = useInputContext();
|
||||
const { isMultiSelect } = useSelectedOptionsContext();
|
||||
|
||||
const removeCustomOption = useCallback(
|
||||
(option) => {
|
||||
@@ -27,16 +30,21 @@ export const CustomOptionsProvider = ({ children }) => {
|
||||
|
||||
const addCustomOption = useCallback(
|
||||
(option) => {
|
||||
setCustomOptions((prevOptions) => [...prevOptions, option]);
|
||||
if (isMultiSelect) {
|
||||
setCustomOptions((prevOptions) => [...prevOptions, option]);
|
||||
} else {
|
||||
setCustomOptions([option]);
|
||||
}
|
||||
focusInput();
|
||||
},
|
||||
[focusInput, setCustomOptions]
|
||||
[focusInput, isMultiSelect, setCustomOptions]
|
||||
);
|
||||
|
||||
const customOptionsState = {
|
||||
customOptions,
|
||||
removeCustomOption,
|
||||
addCustomOption,
|
||||
setCustomOptions,
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -66,7 +66,10 @@ export interface ComboboxProps
|
||||
* @param event
|
||||
* @returns
|
||||
*/
|
||||
onChange?: (event: ChangeEvent<HTMLInputElement>) => void;
|
||||
onChange?: (
|
||||
event: ChangeEvent<HTMLInputElement> | null,
|
||||
value?: string
|
||||
) => void;
|
||||
/**
|
||||
* Callback function triggered whenever the input field is cleared
|
||||
*
|
||||
@@ -78,10 +81,15 @@ export interface ComboboxProps
|
||||
* Callback function triggered whenever an option is selected or de-selected
|
||||
*
|
||||
* @param option
|
||||
* @param isSelected
|
||||
* @param isSelected - Whether the option has been selected or unselected
|
||||
* @param isCustomOption - Whether the option comes from user input, instead of from the list
|
||||
* @returns
|
||||
*/
|
||||
onToggleSelected?: (option: string, isSelected: boolean) => void;
|
||||
onToggleSelected?: (
|
||||
option: string,
|
||||
isSelected: boolean,
|
||||
isCustomOption: boolean
|
||||
) => void;
|
||||
/**
|
||||
* List of selected options.
|
||||
*
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React, { useState } from "react";
|
||||
import { ConfirmationPanel } from "../index";
|
||||
import { Meta } from "@storybook/react";
|
||||
import React, { useState } from "react";
|
||||
import { Link } from "../..";
|
||||
import { ConfirmationPanel } from "../index";
|
||||
|
||||
export default {
|
||||
title: "ds-react/ConfirmationPanel",
|
||||
component: ConfirmationPanel,
|
||||
|
||||
@@ -25,6 +25,10 @@ export interface CellProps extends HTMLAttributes<HTMLDivElement> {
|
||||
lg?: Column;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use <HGrid /> instead. <Grid /> will be removed in the future.
|
||||
* @see [📝 Documentation](https://aksel.nav.no/komponenter/core/hgrid)
|
||||
*/
|
||||
export const Cell = forwardRef<HTMLDivElement, CellProps>(
|
||||
({ children, xs, sm, md, lg, className, ...rest }, ref) => {
|
||||
return (
|
||||
|
||||
@@ -8,6 +8,10 @@ export interface GridProps extends HTMLAttributes<HTMLDivElement> {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use <HGrid /> instead. <Grid /> will be removed in the future.
|
||||
* @see [📝 Documentation](https://aksel.nav.no/komponenter/core/hgrid)
|
||||
*/
|
||||
export const Grid = forwardRef<HTMLDivElement, GridProps>(
|
||||
({ children, className, ...rest }, ref) => (
|
||||
<div ref={ref} className={cl("navds-grid", className)} {...rest}>
|
||||
|
||||
@@ -1,6 +1,2 @@
|
||||
export { default as Cell, type CellProps } from "./Cell";
|
||||
export { default as Grid, type GridProps } from "./Grid";
|
||||
export {
|
||||
default as ContentContainer,
|
||||
type ContentContainerProps,
|
||||
} from "./ContentContainer";
|
||||
@@ -32,3 +32,6 @@ export * from "./toggle-group";
|
||||
export * from "./tooltip";
|
||||
export * from "./typography";
|
||||
export * from "./util";
|
||||
export * from "./layout/stack";
|
||||
export * from "./layout/grid";
|
||||
export * from "./layout/content-container";
|
||||
File renamed without changes.
@@ -0,0 +1,4 @@
|
||||
export {
|
||||
default as ContentContainer,
|
||||
type ContentContainerProps,
|
||||
} from "./ContentContainer";
|
||||
@@ -0,0 +1,101 @@
|
||||
import React, { forwardRef, HTMLAttributes } from "react";
|
||||
import cl from "clsx";
|
||||
import {
|
||||
getResponsiveProps,
|
||||
getResponsiveValue,
|
||||
ResponsiveProp,
|
||||
SpacingScale,
|
||||
} from "../utilities/css";
|
||||
|
||||
export interface HGridProps extends HTMLAttributes<HTMLDivElement> {
|
||||
children: React.ReactNode;
|
||||
/**
|
||||
* Number of columns to display. Can be a number, a string with a unit or tokens for spesific breakpoints.
|
||||
* Sets `grid-template-columns`, so `fr`, `minmax` etc. works.
|
||||
* @example
|
||||
* columns={{ sm: 1, md: 1, lg: "1fr auto", xl: "1fr auto"}}
|
||||
* @example
|
||||
* columns={3}
|
||||
* @example
|
||||
* columns="repeat(3, minmax(0, 1fr))"
|
||||
*/
|
||||
columns?: ResponsiveProp<number | string>;
|
||||
/** Spacing between columns. Based on spacing-tokens.
|
||||
* @example
|
||||
* gap="6"
|
||||
* gap={{ sm: "2", md: "2", lg: "6", xl: "6"}}
|
||||
*/
|
||||
gap?: ResponsiveProp<SpacingScale>;
|
||||
}
|
||||
/**
|
||||
* Horizontal Grid Primitive with dynamic columns and gap based on breakpoints.
|
||||
*
|
||||
* @see [📝 Documentation](https://aksel.nav.no/komponenter/core/hgrid)
|
||||
* @see 🏷️ {@link HGridProps}
|
||||
*
|
||||
* @example
|
||||
* <HGrid gap="6" columns={3}>
|
||||
* <div />
|
||||
* <div />
|
||||
* <div />
|
||||
* </HGrid>
|
||||
* @example
|
||||
* <HGrid gap={{xs: "2", md: "6"}} columns={3}>
|
||||
* <div />
|
||||
* <div />
|
||||
* <div />
|
||||
* </HGrid>
|
||||
* @example
|
||||
* <HGrid gap="6" columns={{ sm: 1, md: 1, lg: "1fr auto", xl: "1fr auto"}}>
|
||||
* <div />
|
||||
* <div />
|
||||
* <div />
|
||||
* </HGrid>
|
||||
*/
|
||||
export const HGrid = forwardRef<HTMLDivElement, HGridProps>(
|
||||
({ className, columns, gap, style, ...rest }, ref) => {
|
||||
const styles: React.CSSProperties = {
|
||||
...style,
|
||||
...getResponsiveProps(`hgrid`, "gap", "spacing", gap),
|
||||
...getResponsiveValue(`hgrid`, "columns", formatGrid(columns)),
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
{...rest}
|
||||
ref={ref}
|
||||
className={cl("navds-hgrid", className)}
|
||||
style={styles}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
function formatGrid(
|
||||
props?: ResponsiveProp<number | string>
|
||||
): ResponsiveProp<number | string> {
|
||||
if (!props) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (typeof props === "string" || typeof props === "number") {
|
||||
return getColumnValue(props);
|
||||
}
|
||||
|
||||
return Object.fromEntries(
|
||||
Object.entries(props).map(([breakpointToken, columnValue]) => [
|
||||
breakpointToken,
|
||||
getColumnValue(columnValue),
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
const getColumnValue = (prop: number | string) => {
|
||||
if (typeof prop === "number") {
|
||||
return `repeat(${prop}, minmax(0, 1fr))`;
|
||||
}
|
||||
|
||||
return prop;
|
||||
};
|
||||
|
||||
export default HGrid;
|
||||
@@ -0,0 +1,102 @@
|
||||
import React from "react";
|
||||
import { HGrid } from ".";
|
||||
|
||||
const columnsVariants = {
|
||||
Number: "columnNumber",
|
||||
String: "columnString",
|
||||
Object: "columnObject",
|
||||
};
|
||||
|
||||
export default {
|
||||
title: "ds-react/HGrid",
|
||||
component: HGrid,
|
||||
parameters: {
|
||||
layout: "fullscreen",
|
||||
},
|
||||
argTypes: {
|
||||
columnsType: {
|
||||
defaultValue: Object.keys(columnsVariants)[0],
|
||||
options: Object.keys(columnsVariants),
|
||||
control: { type: "radio" },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/* const getColumnsProp = () */
|
||||
|
||||
export const Default = {
|
||||
render: (props) => (
|
||||
<HGrid
|
||||
gap={props?.gap ?? 4}
|
||||
columns={props[columnsVariants[props.columnsType]]}
|
||||
>
|
||||
<Placeholder text="1" />
|
||||
<Placeholder text="2" />
|
||||
<Placeholder text="3" />
|
||||
<Placeholder text="4" />
|
||||
</HGrid>
|
||||
),
|
||||
args: {
|
||||
columnNumber: 4,
|
||||
columnObject: { xs: 1, md: 4 },
|
||||
columnString: "repeat(3, minmax(0, 1fr))",
|
||||
gap: "4",
|
||||
},
|
||||
};
|
||||
|
||||
export const Gap = {
|
||||
render: () => (
|
||||
<HGrid gap="6">
|
||||
<Placeholder text="1" />
|
||||
<Placeholder text="2" />
|
||||
<Placeholder text="3" />
|
||||
<Placeholder text="4" />
|
||||
</HGrid>
|
||||
),
|
||||
};
|
||||
|
||||
export const DynamicGap = {
|
||||
render: () => (
|
||||
<HGrid gap={{ xs: "2", md: "8" }}>
|
||||
<Placeholder text="1" />
|
||||
<Placeholder text="2" />
|
||||
<Placeholder text="3" />
|
||||
<Placeholder text="4" />
|
||||
</HGrid>
|
||||
),
|
||||
};
|
||||
|
||||
export const Columns = {
|
||||
render: () => (
|
||||
<HGrid gap="4" columns={2}>
|
||||
<Placeholder text="1" />
|
||||
<Placeholder text="2" />
|
||||
<Placeholder text="3" />
|
||||
<Placeholder text="4" />
|
||||
</HGrid>
|
||||
),
|
||||
};
|
||||
|
||||
export const DynamicColumns = {
|
||||
render: () => (
|
||||
<HGrid gap="4" columns={{ sm: "1fr 5fr", md: "2fr 2fr" }}>
|
||||
<Placeholder text="1" />
|
||||
<Placeholder text="2" />
|
||||
</HGrid>
|
||||
),
|
||||
};
|
||||
|
||||
function Placeholder({ text }) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
background: "var(--a-deepblue-900)",
|
||||
height: "5rem",
|
||||
width: "auto",
|
||||
color: "white",
|
||||
}}
|
||||
>
|
||||
{text}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default as HGrid, type HGridProps } from "./HGrid";
|
||||
@@ -0,0 +1,30 @@
|
||||
import React, { forwardRef } from "react";
|
||||
import { OverridableComponent } from "../../util/OverridableComponent";
|
||||
import { StackProps, Stack } from "./Stack";
|
||||
|
||||
export type HStackProps = Omit<StackProps, "direction">;
|
||||
|
||||
/**
|
||||
* Layout-primitive for horizontal flexbox
|
||||
*
|
||||
* @see [📝 Documentation](https://aksel.nav.no/komponenter/core/Stack)
|
||||
* @see 🏷️ {@link HStackProps}
|
||||
* @see [🤖 OverridableComponent](https://aksel.nav.no/grunnleggende/kode/overridablecomponent) support
|
||||
*
|
||||
* @example
|
||||
* <HStack gap="8">
|
||||
* <MyComponent />
|
||||
* <MyComponent />
|
||||
* </HStack>
|
||||
*
|
||||
* @example
|
||||
* // Responsive gap
|
||||
* <HStack gap={{xs: "2", md: "6"}}>
|
||||
* <MyComponent />
|
||||
* <MyComponent />
|
||||
* </HStack>
|
||||
*/
|
||||
export const HStack: OverridableComponent<HStackProps, HTMLDivElement> =
|
||||
forwardRef((props, ref) => {
|
||||
return <Stack {...props} ref={ref} direction="row" />;
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
import React from "react";
|
||||
|
||||
/**
|
||||
* Layout-primitive for auto-spacing between elements
|
||||
*
|
||||
* @see [📝 Documentation](https://aksel.nav.no/komponenter/core/Stack)
|
||||
*
|
||||
* @example
|
||||
* <HStack gap="8">
|
||||
* <MyComponent />
|
||||
* <Spacer />
|
||||
* <MyComponent />
|
||||
* </HStack>
|
||||
*/
|
||||
export const Spacer = () => <div className="navds-stack__spacer" />;
|
||||
@@ -0,0 +1,76 @@
|
||||
import cl from "clsx";
|
||||
import React, { forwardRef, HTMLAttributes } from "react";
|
||||
import { OverridableComponent } from "../../util/OverridableComponent";
|
||||
import {
|
||||
getResponsiveProps,
|
||||
ResponsiveProp,
|
||||
SpacingScale,
|
||||
} from "../utilities/css";
|
||||
|
||||
export interface StackProps extends HTMLAttributes<HTMLDivElement> {
|
||||
children: React.ReactNode;
|
||||
/**
|
||||
* Justify-content
|
||||
*/
|
||||
justify?:
|
||||
| "start"
|
||||
| "center"
|
||||
| "end"
|
||||
| "space-around"
|
||||
| "space-between"
|
||||
| "space-evenly";
|
||||
/**
|
||||
* Align-items
|
||||
*/
|
||||
align?: "start" | "center" | "end" | "baseline" | "stretch";
|
||||
/**
|
||||
* flex-wrap
|
||||
*/
|
||||
wrap?: boolean;
|
||||
/**
|
||||
* @example
|
||||
* gap='4'
|
||||
* gap={{xs: '2', sm: '3', md: '4', lg: '5', xl: '6'}}
|
||||
*/
|
||||
gap?: ResponsiveProp<SpacingScale>;
|
||||
direction: "row" | "column";
|
||||
}
|
||||
|
||||
export const Stack: OverridableComponent<StackProps, HTMLDivElement> =
|
||||
forwardRef(
|
||||
(
|
||||
{
|
||||
as: Component = "div",
|
||||
className,
|
||||
align,
|
||||
justify,
|
||||
wrap = true,
|
||||
gap,
|
||||
style: _style,
|
||||
direction,
|
||||
...rest
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const style = {
|
||||
..._style,
|
||||
"--__ac-stack-direction": direction,
|
||||
"--__ac-stack-align": align,
|
||||
"--__ac-stack-justify": justify,
|
||||
"--__ac-stack-wrap": wrap ? "wrap" : "nowrap",
|
||||
...getResponsiveProps(`stack`, "gap", "spacing", gap),
|
||||
} as React.CSSProperties;
|
||||
|
||||
return (
|
||||
<Component
|
||||
{...rest}
|
||||
ref={ref}
|
||||
style={style}
|
||||
className={cl("navds-stack", className, {
|
||||
"navds-vstack": direction === "column",
|
||||
"navds-hstack": direction === "row",
|
||||
})}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,30 @@
|
||||
import React, { forwardRef } from "react";
|
||||
import { OverridableComponent } from "../../util/OverridableComponent";
|
||||
import { StackProps, Stack } from "./Stack";
|
||||
|
||||
export type VStackProps = Omit<StackProps, "direction" | "wrap">;
|
||||
|
||||
/**
|
||||
* Layout-primitive for vetical flexbox
|
||||
*
|
||||
* @see [📝 Documentation](https://aksel.nav.no/komponenter/core/Stack)
|
||||
* @see 🏷️ {@link VStackProps}
|
||||
* @see [🤖 OverridableComponent](https://aksel.nav.no/grunnleggende/kode/overridablecomponent) support
|
||||
*
|
||||
* @example
|
||||
* <VStack gap="8">
|
||||
* <MyComponent />
|
||||
* <MyComponent />
|
||||
* </VStack>
|
||||
*
|
||||
* @example
|
||||
* // Responsive gap
|
||||
* <VStack gap={{xs: "2", md: "6"}}>
|
||||
* <MyComponent />
|
||||
* <MyComponent />
|
||||
* </VStack>
|
||||
*/
|
||||
export const VStack: OverridableComponent<VStackProps, HTMLDivElement> =
|
||||
forwardRef((props, ref) => {
|
||||
return <Stack {...props} ref={ref} direction="column" wrap={false} />;
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
export { HStack, type HStackProps } from "./HStack";
|
||||
export { VStack, type VStackProps } from "./VStack";
|
||||
export { Spacer } from "./Spacer";
|
||||
@@ -0,0 +1,161 @@
|
||||
import React from "react";
|
||||
import type { Meta } from "@storybook/react";
|
||||
import { HStack, VStack, Spacer } from ".";
|
||||
|
||||
export default {
|
||||
title: "ds-react/Stack",
|
||||
component: HStack,
|
||||
} satisfies Meta<typeof HStack>;
|
||||
|
||||
export const Horizontal = {
|
||||
render: () => (
|
||||
<HStack gap="4">
|
||||
<Placeholders count={4} />
|
||||
</HStack>
|
||||
),
|
||||
};
|
||||
|
||||
export const Spacing = {
|
||||
render: () => (
|
||||
<div style={{ height: "80vh", display: "flex" }}>
|
||||
<VStack gap="8">
|
||||
<Spacer />
|
||||
<HStack gap="4">
|
||||
<Placeholders count={1} />
|
||||
<Spacer />
|
||||
<Placeholders count={1} />
|
||||
</HStack>
|
||||
<HStack gap="4">
|
||||
<Placeholders count={1} />
|
||||
<Placeholders count={1} />
|
||||
</HStack>
|
||||
<HStack gap="4">
|
||||
<Placeholders count={2} />
|
||||
</HStack>
|
||||
</VStack>
|
||||
</div>
|
||||
),
|
||||
parameters: {
|
||||
layout: "fullscreen",
|
||||
},
|
||||
};
|
||||
|
||||
export const Vertical = {
|
||||
render: () => (
|
||||
<VStack gap="4">
|
||||
<Placeholders count={4} />
|
||||
</VStack>
|
||||
),
|
||||
};
|
||||
|
||||
export const VerticalDemo = {
|
||||
render: () => (
|
||||
<VStack gap="2">
|
||||
<VStack>
|
||||
<Placeholders count={4} />
|
||||
</VStack>
|
||||
<Placeholders count={4} />
|
||||
<VStack>
|
||||
<Placeholders count={4} />
|
||||
</VStack>
|
||||
</VStack>
|
||||
),
|
||||
};
|
||||
|
||||
export const VerticalAlign = {
|
||||
render: () => (
|
||||
<VStack gap="4">
|
||||
<VStack align="start">
|
||||
<Placeholders count={2} />
|
||||
</VStack>
|
||||
<VStack align="center">
|
||||
<Placeholders count={2} />
|
||||
</VStack>
|
||||
<VStack align="end">
|
||||
<Placeholders count={2} />
|
||||
</VStack>
|
||||
</VStack>
|
||||
),
|
||||
parameters: {
|
||||
layout: "fullscreen",
|
||||
},
|
||||
};
|
||||
|
||||
export const OverrideComponent = {
|
||||
render: () => (
|
||||
<VStack gap="4" as="form" onSubmit={(e) => e.preventDefault()}>
|
||||
<Placeholders count={4} />
|
||||
</VStack>
|
||||
),
|
||||
};
|
||||
|
||||
export const Responsive = {
|
||||
render: () => (
|
||||
<VStack gap={{ xs: "1", sm: "3", md: "6", lg: "10", xl: "16" }}>
|
||||
<Placeholders count={4} />
|
||||
</VStack>
|
||||
),
|
||||
};
|
||||
|
||||
export const Nested = {
|
||||
render: () => (
|
||||
<VStack gap="16">
|
||||
<Placeholders count={2}>
|
||||
<VStack gap="4">
|
||||
<Placeholders count={2} color="gray" />
|
||||
</VStack>
|
||||
</Placeholders>
|
||||
</VStack>
|
||||
),
|
||||
};
|
||||
|
||||
export const DividerDemo = {
|
||||
render: () => (
|
||||
<div style={{ height: "80vh", width: "40rem" }}>
|
||||
<VStack gap={{ xs: "2", md: "6", lg: "12" }}>
|
||||
<HStack gap={{ xs: "2", md: "6", lg: "12" }}>
|
||||
<Placeholders count={1} />
|
||||
<Spacer />
|
||||
<Placeholders count={1} />
|
||||
</HStack>
|
||||
<hr
|
||||
style={{
|
||||
border: "none",
|
||||
borderBottom: "1px solid var(--a-border-divider)",
|
||||
margin: 0,
|
||||
}}
|
||||
/>
|
||||
<HStack gap={{ xs: "2", md: "6", lg: "12" }}>
|
||||
<Placeholders count={2} />
|
||||
</HStack>
|
||||
</VStack>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
function Placeholders({
|
||||
count,
|
||||
children,
|
||||
color,
|
||||
}: {
|
||||
count: number;
|
||||
children?: React.ReactNode;
|
||||
color?: string;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
{Array.from({ length: count }, (_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
style={{
|
||||
backgroundColor: color ?? "var(--a-purple-200)",
|
||||
height: children ? "" : "3rem",
|
||||
width: children ? "" : "3rem",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
export type BreakpointsAlias = "xs" | "sm" | "md" | "lg" | "xl";
|
||||
export type SpacingScale =
|
||||
| "0"
|
||||
| "05"
|
||||
| "1"
|
||||
| "2"
|
||||
| "3"
|
||||
| "4"
|
||||
| "5"
|
||||
| "6"
|
||||
| "7"
|
||||
| "8"
|
||||
| "9"
|
||||
| "10"
|
||||
| "11"
|
||||
| "12"
|
||||
| "14"
|
||||
| "16"
|
||||
| "18"
|
||||
| "20"
|
||||
| "24"
|
||||
| "32";
|
||||
|
||||
type ResponsivePropConfig<T = string> = {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
[Breakpoint in BreakpointsAlias]?: T;
|
||||
};
|
||||
|
||||
export type ResponsiveProp<T> = T | ResponsivePropConfig<T>;
|
||||
|
||||
export type ResponsiveValue<T = string> = undefined | ResponsiveProp<T>;
|
||||
|
||||
export function getResponsiveProps<T = string>(
|
||||
componentName: string,
|
||||
componentProp: string,
|
||||
tokenSubgroup: string,
|
||||
responsiveProp?: ResponsiveProp<T>
|
||||
) {
|
||||
if (!responsiveProp) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (typeof responsiveProp === "string") {
|
||||
return {
|
||||
[`--__ac-${componentName}-${componentProp}-xs`]: `var(--a-${tokenSubgroup}-${responsiveProp})`,
|
||||
};
|
||||
}
|
||||
|
||||
return Object.fromEntries(
|
||||
Object.entries(responsiveProp).map(([breakpointAlias, aliasOrScale]) => [
|
||||
`--__ac-${componentName}-${componentProp}-${breakpointAlias}`,
|
||||
`var(--a-${tokenSubgroup}-${aliasOrScale})`,
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
export function getResponsiveValue<T = string>(
|
||||
componentName: string,
|
||||
componentProp: string,
|
||||
responsiveProp?: ResponsiveValue<T>
|
||||
) {
|
||||
if (!responsiveProp) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (typeof responsiveProp === "string") {
|
||||
return {
|
||||
[`--__ac-${componentName}-${componentProp}-xs`]: responsiveProp,
|
||||
};
|
||||
}
|
||||
|
||||
return Object.fromEntries(
|
||||
Object.entries(responsiveProp).map(([breakpointAlias, responsiveValue]) => [
|
||||
`--__ac-${componentName}-${componentProp}-${breakpointAlias}`,
|
||||
responsiveValue,
|
||||
])
|
||||
);
|
||||
}
|
||||
@@ -4,6 +4,23 @@ import { OverridableComponent } from "../util/OverridableComponent";
|
||||
|
||||
export interface LinkProps
|
||||
extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
|
||||
/**
|
||||
* Inverts when the underline appears. If this is false,
|
||||
* the underline does not appear by default, but does appear when the link is hovered.
|
||||
* This makes it more suitable for use when inlined in text.
|
||||
* @default true
|
||||
*/
|
||||
underline?: boolean;
|
||||
/**
|
||||
* Renders link with `display: inline` for better wrapping in text.
|
||||
* @default false
|
||||
*/
|
||||
inlineText?: boolean;
|
||||
/**
|
||||
* Variant of the component to use.
|
||||
* @default "action"
|
||||
*/
|
||||
variant?: "action" | "neutral" | "subtle";
|
||||
/**
|
||||
* Link text
|
||||
*/
|
||||
@@ -22,10 +39,37 @@ export interface LinkProps
|
||||
* <Link href="#">lenke til ny side</Link>
|
||||
* <Link as={ReactRouter.Link} to="#">lenke til ny side</Link>
|
||||
* ```
|
||||
* @example
|
||||
* ```jsx
|
||||
* <BodyLong>
|
||||
* ...
|
||||
* <Link href="#" inlineText>lenke til ny side</Link>
|
||||
* ...
|
||||
* </BodyLong>
|
||||
* ```
|
||||
*/
|
||||
export const Link: OverridableComponent<LinkProps, HTMLAnchorElement> =
|
||||
forwardRef(({ as: Component = "a", className, ...rest }, ref) => (
|
||||
<Component {...rest} ref={ref} className={cl("navds-link", className)} />
|
||||
));
|
||||
forwardRef(
|
||||
(
|
||||
{
|
||||
as: Component = "a",
|
||||
className,
|
||||
underline = true,
|
||||
variant = "action",
|
||||
inlineText = false,
|
||||
...rest
|
||||
},
|
||||
ref
|
||||
) => (
|
||||
<Component
|
||||
{...rest}
|
||||
ref={ref}
|
||||
className={cl("navds-link", className, `navds-link--${variant}`, {
|
||||
"navds-link--remove-underline": !underline,
|
||||
"navds-link--inline-text": inlineText,
|
||||
})}
|
||||
/>
|
||||
)
|
||||
);
|
||||
|
||||
export default Link;
|
||||
@@ -6,29 +6,76 @@ import {
|
||||
ConfirmationPanel as DsConfirmationPanel,
|
||||
Link,
|
||||
} from "..";
|
||||
import { RandomIcon } from "../util/RandomIcon";
|
||||
|
||||
export default {
|
||||
title: "ds-react/Link",
|
||||
component: Link,
|
||||
};
|
||||
|
||||
const LinkWrapper = ({
|
||||
children = "Ex aliqua incididunt",
|
||||
iconRight = false,
|
||||
iconLeft = false,
|
||||
variant = "action",
|
||||
underline = false,
|
||||
inlineText = true,
|
||||
}) => (
|
||||
<>
|
||||
{" "}
|
||||
<Link
|
||||
href="#"
|
||||
underline={underline}
|
||||
variant={variant as "action" | "neutral" | "subtle"}
|
||||
inlineText={inlineText}
|
||||
>
|
||||
{iconLeft && (
|
||||
<>
|
||||
<RandomIcon />{" "}
|
||||
</>
|
||||
)}
|
||||
{children}
|
||||
{iconRight && (
|
||||
<>
|
||||
{" "}
|
||||
<RandomIcon />
|
||||
</>
|
||||
)}
|
||||
</Link>{" "}
|
||||
</>
|
||||
);
|
||||
|
||||
export const Default = {
|
||||
render: (props) => {
|
||||
render: ({ icon, inline }) => {
|
||||
const LinkD = () => (
|
||||
<Link href="#">
|
||||
{props.icon && <PlusCircleFillIcon />}Ex aliqua incididunt{" "}
|
||||
{props.icon && <PlusCircleFillIcon />}
|
||||
</Link>
|
||||
<>
|
||||
{" "}
|
||||
<Link href="#" underline={!inline} inlineText={inline}>
|
||||
{icon && <PlusCircleFillIcon />}Ex aliqua incididunt
|
||||
{icon && <PlusCircleFillIcon />}
|
||||
</Link>{" "}
|
||||
</>
|
||||
);
|
||||
|
||||
if (props.inline) {
|
||||
if (inline) {
|
||||
return (
|
||||
<BodyLong>
|
||||
Incididunt laborum nisi nisi Lorem <LinkD /> in. Laborum aute fugiat
|
||||
officia adipisicing non veniam dolor nulla non ex consectetur fugiat
|
||||
eiusmod aute. Culpa sit aute est duis minim in in voluptate velit
|
||||
fugiat. Laboris est id deserunt ut ea Lorem eu. Esse elit laboris aute
|
||||
commodo sint laborum fugiat aliqua.
|
||||
</BodyLong>
|
||||
<div
|
||||
className="colgap"
|
||||
style={{
|
||||
width: "800px",
|
||||
border: "1px solid black",
|
||||
borderRadius: "8px",
|
||||
}}
|
||||
>
|
||||
<BodyLong>
|
||||
Incididunt laborum nisi nisi Lorem
|
||||
<LinkD />
|
||||
in. Laborum aute fugiat officia adipisicing non veniam dolor nulla
|
||||
non ex consectetur fugiat eiusmod aute. Culpa sit aute est duis
|
||||
minim in in voluptate velit fugiat. Laboris est id deserunt ut ea
|
||||
Lorem eu. Esse elit laboris aute commodo sint laborum fugiat aliqua.
|
||||
</BodyLong>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return <LinkD />;
|
||||
@@ -40,6 +87,49 @@ export const Default = {
|
||||
},
|
||||
};
|
||||
|
||||
export const InlineInsideBodyLong = {
|
||||
render: ({ iconLeft, iconRight }) => {
|
||||
return (
|
||||
<div
|
||||
className="colgap"
|
||||
style={{
|
||||
width: "800px",
|
||||
border: "1px solid black",
|
||||
borderRadius: "8px",
|
||||
}}
|
||||
>
|
||||
<style>{`.storybook-custom-spacing { white-space: pre;}`}</style>
|
||||
<BodyLong>
|
||||
<LinkWrapper underline iconLeft={iconLeft} iconRight={iconRight} />
|
||||
Eiusmod aute.
|
||||
<LinkWrapper underline iconLeft={iconLeft} iconRight={iconRight} />
|
||||
Culpa sit aute est duis minim in in voluptate velit Incididunt laborum
|
||||
nisi nisi Lorem officia adipisicing non veniam
|
||||
<LinkWrapper underline iconLeft={iconLeft} iconRight={iconRight}>
|
||||
blah blah blah blah blah blah blah blah blah blah blah blah blah
|
||||
blah blah blah blah blah blah blah blah
|
||||
</LinkWrapper>
|
||||
dolor eu. Esse elit laboris aute commodo sint laborum fugiat aliqua.
|
||||
<LinkWrapper underline iconLeft={iconLeft} iconRight={iconRight}>
|
||||
Link
|
||||
</LinkWrapper>
|
||||
</BodyLong>
|
||||
<BodyLong className="storybook-custom-spacing">
|
||||
Custom{" "}
|
||||
<LinkWrapper underline iconLeft={iconLeft} iconRight={iconRight}>
|
||||
link
|
||||
</LinkWrapper>
|
||||
{" "}spacing.
|
||||
</BodyLong>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
args: {
|
||||
iconLeft: false,
|
||||
iconRight: false,
|
||||
},
|
||||
};
|
||||
|
||||
const DemoLink = () => (
|
||||
<Link href="#">
|
||||
<PlusCircleFillIcon aria-hidden /> Ex aliqua incididunt{" "}
|
||||
@@ -83,3 +173,43 @@ export const ConfirmationPanel = () => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Variants = {
|
||||
render: ({ iconLeft, iconRight }) => {
|
||||
return (
|
||||
<div className="colgap">
|
||||
{["action", "neutral", "subtle"].map((variant) => (
|
||||
<>
|
||||
<div>
|
||||
<LinkWrapper
|
||||
iconLeft={iconLeft}
|
||||
iconRight={iconRight}
|
||||
variant={variant}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
args: {
|
||||
iconLeft: false,
|
||||
iconRight: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const InlineLink = {
|
||||
render: () => (
|
||||
<BodyLong>
|
||||
Officia incididunt Culpa sit aute est duis minim in in voluptate velit
|
||||
Incididunt laborum nisi nisi Lorem officia adipisicing non veniam{" "}
|
||||
<Link href="#" inlineText={true}>
|
||||
lenke til ny side
|
||||
<PlusCircleFillIcon aria-hidden />
|
||||
</Link>{" "}
|
||||
Culpa sit aute est duis minim in in voluptate velit Incididunt laborum
|
||||
nisi nisi Lorem officia adipisicing non veniam occaecat commodo id ad
|
||||
aliquip.
|
||||
</BodyLong>
|
||||
),
|
||||
};
|
||||
@@ -24,6 +24,11 @@ export interface ListProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
* @default "h3"
|
||||
*/
|
||||
headingTag?: React.ElementType<any>;
|
||||
/**
|
||||
* Changes padding, height and font-size
|
||||
* @default medium
|
||||
*/
|
||||
size?: "medium" | "small";
|
||||
}
|
||||
|
||||
export interface ListComponent
|
||||
@@ -37,13 +42,15 @@ export interface ListComponent
|
||||
}
|
||||
|
||||
interface ListContextProps {
|
||||
listType: "ul" | "ol";
|
||||
listType: ListProps["as"];
|
||||
isNested: boolean | null;
|
||||
size: ListProps["size"];
|
||||
}
|
||||
|
||||
export const ListContext = createContext<ListContextProps>({
|
||||
listType: "ul",
|
||||
isNested: null,
|
||||
size: "medium",
|
||||
});
|
||||
|
||||
export const List = forwardRef<HTMLDivElement, ListProps>(
|
||||
@@ -55,35 +62,44 @@ export const List = forwardRef<HTMLDivElement, ListProps>(
|
||||
title,
|
||||
description,
|
||||
headingTag = "h3",
|
||||
size,
|
||||
...rest
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const ariaId = useId();
|
||||
|
||||
const { isNested } = useContext(ListContext);
|
||||
const { isNested, size: _size } = useContext(ListContext);
|
||||
|
||||
const listSize = size ?? _size ?? "medium";
|
||||
return (
|
||||
<ListContext.Provider
|
||||
value={{
|
||||
listType: ListTag,
|
||||
isNested: isNested === null ? false : true,
|
||||
size: listSize,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
{...rest}
|
||||
ref={ref}
|
||||
className={cl("navds-list", className, {
|
||||
className={cl("navds-list", `navds-list--${listSize}`, className, {
|
||||
"navds-list--nested": isNested === null ? false : true,
|
||||
})}
|
||||
>
|
||||
{title && (
|
||||
<Heading id={`tittel-${ariaId}`} size="small" as={headingTag}>
|
||||
<Heading
|
||||
id={`tittel-${ariaId}`}
|
||||
size={listSize === "medium" ? "small" : "xsmall"}
|
||||
as={headingTag}
|
||||
>
|
||||
{title}
|
||||
</Heading>
|
||||
)}
|
||||
{description && (
|
||||
<BodyShort id={`description-${ariaId}`}>{description}</BodyShort>
|
||||
<BodyShort size={listSize} id={`description-${ariaId}`}>
|
||||
{description}
|
||||
</BodyShort>
|
||||
)}
|
||||
<ListTag
|
||||
aria-labelledby={title && `tittel-${ariaId}`}
|
||||
|
||||
@@ -20,7 +20,7 @@ export interface ListItemProps extends React.HTMLAttributes<HTMLLIElement> {
|
||||
|
||||
export const ListItem = forwardRef<HTMLLIElement, ListItemProps>(
|
||||
({ className, children, title, icon, ...rest }, ref) => {
|
||||
const { listType, isNested } = useContext(ListContext);
|
||||
const { listType, size } = useContext(ListContext);
|
||||
|
||||
if (listType === "ol" && icon) {
|
||||
console.warn(
|
||||
@@ -29,13 +29,7 @@ export const ListItem = forwardRef<HTMLLIElement, ListItemProps>(
|
||||
}
|
||||
|
||||
return (
|
||||
<li
|
||||
{...rest}
|
||||
ref={ref}
|
||||
className={cl("navds-list__item", className, {
|
||||
"navds-list__item--noMargin": isNested,
|
||||
})}
|
||||
>
|
||||
<li {...rest} ref={ref} className={cl("navds-list__item", className)}>
|
||||
{listType === "ul" && (
|
||||
<div
|
||||
className={cl({
|
||||
@@ -62,9 +56,9 @@ export const ListItem = forwardRef<HTMLLIElement, ListItemProps>(
|
||||
</div>
|
||||
)}
|
||||
|
||||
<BodyShort as="div" size="small" className="navds-list__item-content">
|
||||
<BodyShort as="div" size={size} className="navds-list__item-content">
|
||||
{title && (
|
||||
<Label as="p" size="small">
|
||||
<Label as="p" size={size}>
|
||||
{title}
|
||||
</Label>
|
||||
)}
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
} from "@navikt/aksel-icons";
|
||||
import { Meta } from "@storybook/react";
|
||||
import React from "react";
|
||||
import { List } from "..";
|
||||
import { List, VStack } from "..";
|
||||
|
||||
export default {
|
||||
title: "ds-react/List",
|
||||
@@ -91,6 +91,137 @@ export const WithHeading = {
|
||||
},
|
||||
};
|
||||
|
||||
export const SizesUl = {
|
||||
render: () => {
|
||||
return (
|
||||
<VStack gap="8">
|
||||
<List
|
||||
title="Medium list"
|
||||
description="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam vel risus volutpat."
|
||||
size="medium"
|
||||
>
|
||||
<List.Item title="Lorem Ipsum Dolor Sit Amet">
|
||||
Beskrivelsen på punkter er nærmere forklart <a href="/">her</a>
|
||||
</List.Item>
|
||||
<List.Item title="Consectetur Adipiscing Elit">
|
||||
Beskrivelsen på punkter er nærmere forklart <a href="/">her</a>
|
||||
</List.Item>
|
||||
<List.Item>
|
||||
Beskrivelsen på punkter er nærmere forklart <a href="/">her</a>
|
||||
</List.Item>
|
||||
</List>
|
||||
<List
|
||||
title="Small list"
|
||||
description="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam vel risus volutpat."
|
||||
size="small"
|
||||
>
|
||||
<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>
|
||||
</VStack>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const SizesOl = {
|
||||
render: () => {
|
||||
return (
|
||||
<VStack gap="8">
|
||||
<List
|
||||
title="Medium list"
|
||||
description="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam vel risus volutpat."
|
||||
size="medium"
|
||||
as="ol"
|
||||
>
|
||||
<List.Item title="Lorem Ipsum Dolor Sit Amet">
|
||||
Beskrivelsen på punkter er nærmere forklart <a href="/">her</a>
|
||||
</List.Item>
|
||||
<List.Item title="Consectetur Adipiscing Elit">
|
||||
Beskrivelsen på punkter er nærmere forklart <a href="/">her</a>
|
||||
</List.Item>
|
||||
<List.Item>
|
||||
Beskrivelsen på punkter er nærmere forklart <a href="/">her</a>
|
||||
</List.Item>
|
||||
</List>
|
||||
<List
|
||||
title="Small list"
|
||||
description="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam vel risus volutpat."
|
||||
size="small"
|
||||
as="ol"
|
||||
>
|
||||
<List.Item title="Lorem Ipsum Dolor Sit Amet">
|
||||
Beskrivelsen på punkter er nærmere forklart <a href="/">her</a>
|
||||
</List.Item>
|
||||
<List.Item title="Consectetur Adipiscing Elit">
|
||||
Beskrivelsen på punkter er nærmere forklart <a href="/">her</a>
|
||||
</List.Item>
|
||||
<List.Item>
|
||||
Beskrivelsen på punkter er nærmere forklart <a href="/">her</a>
|
||||
</List.Item>
|
||||
</List>
|
||||
</VStack>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const SizesIcons = {
|
||||
render: () => {
|
||||
return (
|
||||
<VStack gap="8">
|
||||
<List
|
||||
title="Medium list"
|
||||
description="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam vel risus volutpat."
|
||||
size="medium"
|
||||
>
|
||||
<List.Item
|
||||
icon={<HeadHeartIcon aria-hidden />}
|
||||
title="Lorem Ipsum Dolor Sit Amet"
|
||||
>
|
||||
Beskrivelsen på punkter er nærmere forklart <a href="/">her</a>
|
||||
</List.Item>
|
||||
<List.Item
|
||||
icon={<HeadHeartIcon aria-hidden />}
|
||||
title="Consectetur Adipiscing Elit"
|
||||
>
|
||||
Beskrivelsen på punkter er nærmere forklart <a href="/">her</a>
|
||||
</List.Item>
|
||||
<List.Item icon={<HeadHeartIcon aria-hidden />}>
|
||||
Beskrivelsen på punkter er nærmere forklart <a href="/">her</a>
|
||||
</List.Item>
|
||||
</List>
|
||||
<List
|
||||
title="Small list"
|
||||
description="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam vel risus volutpat."
|
||||
size="small"
|
||||
>
|
||||
<List.Item
|
||||
icon={<HeadHeartIcon aria-hidden />}
|
||||
title="Lorem Ipsum Dolor Sit Amet"
|
||||
>
|
||||
Beskrivelsen på punkter er nærmere forklart <a href="/">her</a>
|
||||
</List.Item>
|
||||
<List.Item
|
||||
icon={<HeadHeartIcon aria-hidden />}
|
||||
title="Consectetur Adipiscing Elit"
|
||||
>
|
||||
Beskrivelsen på punkter er nærmere forklart <a href="/">her</a>
|
||||
</List.Item>
|
||||
<List.Item icon={<HeadHeartIcon aria-hidden />}>
|
||||
Beskrivelsen på punkter er nærmere forklart <a href="/">her</a>
|
||||
</List.Item>
|
||||
</List>
|
||||
</VStack>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const Icons = {
|
||||
render: () => {
|
||||
return (
|
||||
|
||||
@@ -1,76 +1,91 @@
|
||||
import React, { forwardRef, useEffect, useMemo, useRef } from "react";
|
||||
import React, {
|
||||
forwardRef,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
} from "react";
|
||||
import cl from "clsx";
|
||||
import ReactModal from "react-modal";
|
||||
import dialogPolyfill from "./dialog-polyfill";
|
||||
import { Detail, Heading, mergeRefs, useId } from "..";
|
||||
import ModalBody from "./ModalBody";
|
||||
import ModalHeader from "./ModalHeader";
|
||||
import ModalFooter from "./ModalFooter";
|
||||
import { getCloseHandler, useBodyScrollLock } from "./ModalUtils";
|
||||
import { ModalContext } from "./ModalContext";
|
||||
|
||||
import { Button, mergeRefs, useProvider } from "..";
|
||||
import ModalContent, { ModalContentType } from "./ModalContent";
|
||||
import { XMarkIcon } from "@navikt/aksel-icons";
|
||||
const needPolyfill =
|
||||
typeof window !== "undefined" && window.HTMLDialogElement === undefined;
|
||||
|
||||
export interface ModalProps {
|
||||
export interface ModalProps
|
||||
extends React.DialogHTMLAttributes<HTMLDialogElement> {
|
||||
/**
|
||||
* Content for the header. Alteratively you can use <Modal.Header> instead for more control,
|
||||
* but then you have to set `aria-label` or `aria-labelledby` on the modal manually.
|
||||
*/
|
||||
header?: {
|
||||
label?: string;
|
||||
icon?: React.ReactNode;
|
||||
heading: string;
|
||||
/**
|
||||
* Heading size
|
||||
* @default "medium"
|
||||
* */
|
||||
size?: "medium" | "small";
|
||||
/**
|
||||
* Removes close-button (X) when false
|
||||
* @default true
|
||||
*/
|
||||
closeButton?: boolean;
|
||||
};
|
||||
/**
|
||||
* Modal content
|
||||
*/
|
||||
children: React.ReactNode;
|
||||
/**
|
||||
* Open state for modal
|
||||
* Whether the modal should be visible or not.
|
||||
* Remember to use the `onClose` callback to keep your local state in sync.
|
||||
* You can also use `ref.current.openModal()` and `ref.current.close()`.
|
||||
*/
|
||||
open: boolean;
|
||||
open?: boolean;
|
||||
/**
|
||||
* Callback for modal wanting to close
|
||||
* Called when the modal has been closed
|
||||
*/
|
||||
onClose: () => void;
|
||||
onClose?: React.ReactEventHandler<HTMLDialogElement>;
|
||||
/**
|
||||
* If modal should close on overlay click (click outside Modal)
|
||||
* @default true
|
||||
* Called when the user wants to close the modal (clicked the close button or pressed Esc).
|
||||
* @returns Whether to close the modal
|
||||
*/
|
||||
shouldCloseOnOverlayClick?: boolean;
|
||||
onBeforeClose?: () => boolean | void;
|
||||
/**
|
||||
* Called when the user presses the Esc key, unless `onBeforeClose()` returns `false`.
|
||||
*/
|
||||
onCancel?: React.ReactEventHandler<HTMLDialogElement>;
|
||||
/**
|
||||
* @default fit-content (up to 700px)
|
||||
* */
|
||||
width?: "medium" | "small" | number | `${number}${string}`;
|
||||
/**
|
||||
* User defined classname for modal
|
||||
*/
|
||||
className?: string;
|
||||
/**
|
||||
* User defined classname for modal
|
||||
* Sets aria-labelledby on modal.
|
||||
* No need to set this manually if the `header` prop is used. A reference to `header.heading` will be created automatically.
|
||||
* @warning If not using `header`, you should set either `aria-labelledby` or `aria-label`.
|
||||
*/
|
||||
overlayClassName?: string;
|
||||
/**
|
||||
* Removes close-button(X) when false
|
||||
* @default true
|
||||
*/
|
||||
closeButton?: boolean;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
shouldCloseOnEsc?: boolean;
|
||||
/**
|
||||
* Allows custom styling of ReactModal, in accordance with their typing
|
||||
*/
|
||||
style?: ReactModal.Styles;
|
||||
/**
|
||||
* Callback for setting parent element modal will attach to
|
||||
*/
|
||||
parentSelector?(): HTMLElement;
|
||||
"aria-labelledby"?: string;
|
||||
"aria-describedby"?: string;
|
||||
"aria-modal"?: boolean;
|
||||
/**
|
||||
* Sets aria-label on modal
|
||||
* @warning This should be set if not using 'aria-labelledby' or 'aria-describedby'
|
||||
*/
|
||||
"aria-label"?: string;
|
||||
}
|
||||
|
||||
interface ModalComponent
|
||||
extends ModalLifecycle,
|
||||
React.ForwardRefExoticComponent<
|
||||
ModalProps & React.RefAttributes<ReactModal>
|
||||
> {
|
||||
Content: ModalContentType;
|
||||
extends React.ForwardRefExoticComponent<
|
||||
ModalProps & React.RefAttributes<HTMLDialogElement>
|
||||
> {
|
||||
Header: typeof ModalHeader;
|
||||
Body: typeof ModalBody;
|
||||
Footer: typeof ModalFooter;
|
||||
}
|
||||
|
||||
type ModalLifecycle = {
|
||||
setAppElement: (element: any) => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* A component that displays a modal dialog.
|
||||
*
|
||||
@@ -78,112 +93,147 @@ type ModalLifecycle = {
|
||||
* @see 🏷️ {@link ModalProps}
|
||||
*
|
||||
* @example
|
||||
* State change with `useRef`
|
||||
* ```jsx
|
||||
* const ref = useRef<HTMLDialogElement>(null);
|
||||
* <Button onClick={() => ref.current?.showModal()}>Open modal</Button>
|
||||
* <Modal
|
||||
* ref={ref}
|
||||
* header={{
|
||||
* label: "Optional label",
|
||||
* icon: <FileIcon aria-hidden />,
|
||||
* heading: "My heading",
|
||||
* }}
|
||||
* >
|
||||
* <Modal.Body>
|
||||
* <BodyLong>Hello world</BodyLong>
|
||||
* </Modal.Body>
|
||||
* <Modal.Footer>
|
||||
* <Button>Save</Button>
|
||||
* <Button type="button" variant="tertiary" onClick={() => ref.current?.close()}>Close</Button>
|
||||
* </Modal.Footer>
|
||||
* </Modal>
|
||||
* ```
|
||||
* @example
|
||||
* State change with `useState`
|
||||
* ```jsx
|
||||
* const [open, setOpen] = useState(false);
|
||||
*
|
||||
* <Modal
|
||||
* open={open}
|
||||
* aria-label="Modal demo"
|
||||
* onClose={() => setOpen((x) => !x)}
|
||||
* onClose={() => setOpen(false)}
|
||||
* aria-labelledby="modal-heading"
|
||||
* >
|
||||
* <Modal.Content>
|
||||
* <Heading spacing level="1" size="large" id="modal-heading">
|
||||
* Viktig info
|
||||
* </Heading>
|
||||
* <BodyLong spacing>
|
||||
* Hallo!
|
||||
* </BodyLong>
|
||||
* </Modal.Content>
|
||||
* <Modal.Header>
|
||||
* <Heading level="1" size="large" id="modal-heading">My heading</Heading>
|
||||
* </Modal.Header>
|
||||
* <Modal.Body>
|
||||
* <BodyLong>Hello world</BodyLong>
|
||||
* </Modal.Body>
|
||||
* </Modal>
|
||||
* ```
|
||||
*/
|
||||
export const Modal = forwardRef<ReactModal, ModalProps>(
|
||||
export const Modal = forwardRef<HTMLDialogElement, ModalProps>(
|
||||
(
|
||||
{
|
||||
header,
|
||||
children,
|
||||
open,
|
||||
onClose,
|
||||
onBeforeClose,
|
||||
onCancel,
|
||||
width,
|
||||
className,
|
||||
overlayClassName,
|
||||
shouldCloseOnOverlayClick = true,
|
||||
shouldCloseOnEsc = true,
|
||||
closeButton = true,
|
||||
"aria-describedby": ariaDescribedBy,
|
||||
"aria-labelledby": ariaLabelledBy,
|
||||
"aria-modal": ariaModal,
|
||||
"aria-label": contentLabel,
|
||||
"aria-labelledby": ariaLabelledby,
|
||||
style,
|
||||
parentSelector,
|
||||
...rest
|
||||
},
|
||||
}: ModalProps,
|
||||
ref
|
||||
) => {
|
||||
const modalRef = useRef<ReactModal | null>(null);
|
||||
const modalRef = useRef<HTMLDialogElement>(null);
|
||||
const mergedRef = useMemo(() => mergeRefs([modalRef, ref]), [ref]);
|
||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||
const rootElement = useProvider()?.rootElement;
|
||||
const appElement = useProvider()?.appElement;
|
||||
const ariaLabelId = useId();
|
||||
|
||||
if (useContext(ModalContext)) {
|
||||
console.error("Modals should not be nested");
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
appElement && Modal.setAppElement(appElement);
|
||||
}, [appElement]);
|
||||
|
||||
const onModalCloseRequest = (e) => {
|
||||
if (shouldCloseOnOverlayClick || e.type === "keydown") {
|
||||
onClose();
|
||||
} else if (buttonRef.current) {
|
||||
buttonRef.current.focus();
|
||||
if (needPolyfill && modalRef.current) {
|
||||
dialogPolyfill.registerDialog(modalRef.current);
|
||||
}
|
||||
};
|
||||
}, [modalRef]);
|
||||
|
||||
const getParentSelector = () => {
|
||||
if (parentSelector) {
|
||||
return parentSelector;
|
||||
useEffect(() => {
|
||||
// We need to have this in a useEffect so that the content renders before the modal is displayed,
|
||||
// and in case `open` is true initially.
|
||||
if (modalRef.current && open !== undefined) {
|
||||
if (open && !modalRef.current.open) {
|
||||
modalRef.current.showModal();
|
||||
} else if (!open && modalRef.current.open) {
|
||||
modalRef.current.close();
|
||||
}
|
||||
}
|
||||
return rootElement !== undefined
|
||||
? () => rootElement as HTMLElement
|
||||
: undefined;
|
||||
};
|
||||
}, [modalRef, open]);
|
||||
|
||||
useBodyScrollLock(modalRef, "navds-modal__document-body");
|
||||
|
||||
const isWidthPreset =
|
||||
typeof width === "string" && ["small", "medium"].includes(width);
|
||||
|
||||
return (
|
||||
<ReactModal
|
||||
{...rest}
|
||||
parentSelector={getParentSelector()}
|
||||
style={style}
|
||||
isOpen={open}
|
||||
<dialog
|
||||
ref={mergedRef}
|
||||
className={cl("navds-modal", className)}
|
||||
overlayClassName={cl("navds-modal__overlay", overlayClassName)}
|
||||
shouldCloseOnOverlayClick={shouldCloseOnOverlayClick}
|
||||
shouldCloseOnEsc={shouldCloseOnEsc}
|
||||
onRequestClose={(e) => onModalCloseRequest(e)}
|
||||
aria={{
|
||||
describedby: ariaDescribedBy,
|
||||
labelledby: ariaLabelledBy,
|
||||
modal: ariaModal,
|
||||
className={cl("navds-modal", className, {
|
||||
"navds-modal--polyfilled": needPolyfill,
|
||||
"navds-modal--autowidth": !width,
|
||||
[`navds-modal--${width}`]: isWidthPreset,
|
||||
})}
|
||||
style={{
|
||||
...style,
|
||||
...(!isWidthPreset ? { width } : {}),
|
||||
}}
|
||||
contentLabel={contentLabel}
|
||||
onCancel={(event) => {
|
||||
// FYI: onCancel fires when you press Esc
|
||||
if (onBeforeClose && onBeforeClose() === false) {
|
||||
event.preventDefault();
|
||||
} else if (onCancel) onCancel(event);
|
||||
}}
|
||||
aria-labelledby={
|
||||
!ariaLabelledby && !rest["aria-label"] && header
|
||||
? ariaLabelId
|
||||
: ariaLabelledby
|
||||
}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
{closeButton && (
|
||||
<Button
|
||||
className={cl("navds-modal__button", {
|
||||
"navds-modal__button--shake": shouldCloseOnOverlayClick,
|
||||
})}
|
||||
size="small"
|
||||
variant="tertiary-neutral"
|
||||
ref={buttonRef}
|
||||
onClick={onClose}
|
||||
icon={<XMarkIcon title="Lukk modalvindu" />}
|
||||
/>
|
||||
)}
|
||||
</ReactModal>
|
||||
<ModalContext.Provider
|
||||
value={{
|
||||
closeHandler: getCloseHandler(modalRef, header, onBeforeClose),
|
||||
}}
|
||||
>
|
||||
{header && (
|
||||
<ModalHeader>
|
||||
{header.label && (
|
||||
<Detail className="navds-modal__label">{header.label}</Detail>
|
||||
)}
|
||||
<Heading
|
||||
size={header.size ?? "medium"}
|
||||
level="1"
|
||||
id={ariaLabelId}
|
||||
>
|
||||
<span className="navds-modal__header-icon">{header.icon}</span>
|
||||
{header.heading}
|
||||
</Heading>
|
||||
</ModalHeader>
|
||||
)}
|
||||
|
||||
{children}
|
||||
</ModalContext.Provider>
|
||||
</dialog>
|
||||
);
|
||||
}
|
||||
) as ModalComponent;
|
||||
|
||||
Modal.setAppElement = (element) => ReactModal.setAppElement(element);
|
||||
Modal.Content = ModalContent;
|
||||
Modal.Header = ModalHeader;
|
||||
Modal.Body = ModalBody;
|
||||
Modal.Footer = ModalFooter;
|
||||
|
||||
export default Modal;
|
||||
@@ -0,0 +1,14 @@
|
||||
import React, { forwardRef } from "react";
|
||||
import cl from "clsx";
|
||||
|
||||
export interface ModalBodyProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const ModalBody = forwardRef<HTMLDivElement, ModalBodyProps>(
|
||||
({ className, ...rest }, ref) => (
|
||||
<div {...rest} ref={ref} className={cl("navds-modal__body", className)} />
|
||||
)
|
||||
);
|
||||
|
||||
export default ModalBody;
|
||||
@@ -1,26 +0,0 @@
|
||||
import React, { forwardRef } from "react";
|
||||
import cl from "clsx";
|
||||
|
||||
export interface ModalContentProps
|
||||
extends React.HTMLAttributes<HTMLDivElement> {
|
||||
/**
|
||||
* Modal.Content content
|
||||
*/
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export type ModalContentType = React.ForwardRefExoticComponent<
|
||||
ModalContentProps & React.RefAttributes<HTMLDivElement>
|
||||
>;
|
||||
|
||||
const ModalContent: ModalContentType = forwardRef(
|
||||
({ className, ...rest }, ref) => (
|
||||
<div
|
||||
{...rest}
|
||||
ref={ref}
|
||||
className={cl("navds-modal__content", className)}
|
||||
/>
|
||||
)
|
||||
);
|
||||
|
||||
export default ModalContent;
|
||||
@@ -0,0 +1,6 @@
|
||||
import React from "react";
|
||||
|
||||
interface ModalContextProps {
|
||||
closeHandler?: React.MouseEventHandler<HTMLButtonElement>;
|
||||
}
|
||||
export const ModalContext = React.createContext<ModalContextProps | null>(null);
|
||||
@@ -0,0 +1,14 @@
|
||||
import React, { forwardRef } from "react";
|
||||
import cl from "clsx";
|
||||
|
||||
export interface ModalFooterProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const ModalFooter = forwardRef<HTMLDivElement, ModalFooterProps>(
|
||||
({ className, ...rest }, ref) => (
|
||||
<div {...rest} ref={ref} className={cl("navds-modal__footer", className)} />
|
||||
)
|
||||
);
|
||||
|
||||
export default ModalFooter;
|
||||
@@ -0,0 +1,42 @@
|
||||
import React, { forwardRef, useContext } from "react";
|
||||
import cl from "clsx";
|
||||
import { XMarkIcon } from "@navikt/aksel-icons";
|
||||
import { Button } from "../button";
|
||||
import { ModalContext } from "./ModalContext";
|
||||
|
||||
export interface ModalHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
children?: React.ReactNode;
|
||||
/**
|
||||
* Removes close-button (X) when false
|
||||
* @default true
|
||||
*/
|
||||
closeButton?: boolean;
|
||||
}
|
||||
|
||||
const ModalHeader = forwardRef<HTMLDivElement, ModalHeaderProps>(
|
||||
({ children, className, closeButton = true, ...rest }, ref) => {
|
||||
const context = useContext(ModalContext);
|
||||
if (context === null) {
|
||||
console.error("<Modal.Header> has to be used within a <Modal>");
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div {...rest} ref={ref} className={cl("navds-modal__header", className)}>
|
||||
{context.closeHandler && closeButton && (
|
||||
<Button
|
||||
type="button"
|
||||
className="navds-modal__button"
|
||||
size="small"
|
||||
variant="tertiary-neutral"
|
||||
onClick={context.closeHandler}
|
||||
icon={<XMarkIcon title="Lukk modalvindu" />}
|
||||
/>
|
||||
)}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default ModalHeader;
|
||||
@@ -0,0 +1,37 @@
|
||||
import React from "react";
|
||||
import type { ModalProps } from "./Modal";
|
||||
|
||||
export function getCloseHandler(
|
||||
modalRef: React.RefObject<HTMLDialogElement>,
|
||||
header: ModalProps["header"],
|
||||
onBeforeClose: ModalProps["onBeforeClose"]
|
||||
) {
|
||||
if (header && header.closeButton === false) return undefined;
|
||||
if (onBeforeClose) {
|
||||
return () => onBeforeClose() !== false && modalRef.current?.close();
|
||||
}
|
||||
return () => modalRef.current?.close();
|
||||
}
|
||||
|
||||
export function useBodyScrollLock(
|
||||
modalRef: React.RefObject<HTMLDialogElement>,
|
||||
bodyClass: string
|
||||
) {
|
||||
React.useEffect(() => {
|
||||
if (!modalRef.current) return;
|
||||
if (modalRef.current.open) document.body.classList.add(bodyClass); // In case `open` is true initially
|
||||
|
||||
const observer = new MutationObserver(() => {
|
||||
if (modalRef.current?.open) document.body.classList.add(bodyClass);
|
||||
else document.body.classList.remove(bodyClass);
|
||||
});
|
||||
observer.observe(modalRef.current, {
|
||||
attributes: true,
|
||||
attributeFilter: ["open"],
|
||||
});
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
document.body.classList.remove(bodyClass); // In case modal is unmounted before it's closed
|
||||
};
|
||||
}, [modalRef, bodyClass]);
|
||||
}
|
||||
@@ -0,0 +1,980 @@
|
||||
// @ts-nocheck
|
||||
|
||||
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
/**
|
||||
* Dispatches the passed event to both an "on<type>" handler as well as via the
|
||||
* normal dispatch operation. Does not bubble.
|
||||
*
|
||||
* @param {!EventTarget} target
|
||||
* @param {!Event} event
|
||||
* @return {boolean}
|
||||
*/
|
||||
function safeDispatchEvent(target, event) {
|
||||
var check = "on" + event.type.toLowerCase();
|
||||
if (typeof target[check] === "function") {
|
||||
target[check](event);
|
||||
}
|
||||
return target.dispatchEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Element} el to check for stacking context
|
||||
* @return {boolean} whether this el or its parents creates a stacking context
|
||||
*/
|
||||
function createsStackingContext(el) {
|
||||
while (el && el !== document.body) {
|
||||
var s = window.getComputedStyle(el);
|
||||
var invalid = function (k, ok) {
|
||||
return !(s[k] === undefined || s[k] === ok);
|
||||
};
|
||||
|
||||
if (
|
||||
s.opacity < 1 ||
|
||||
invalid("zIndex", "auto") ||
|
||||
invalid("transform", "none") ||
|
||||
invalid("mixBlendMode", "normal") ||
|
||||
invalid("filter", "none") ||
|
||||
invalid("perspective", "none") ||
|
||||
s["isolation"] === "isolate" ||
|
||||
s.position === "fixed" ||
|
||||
s.webkitOverflowScrolling === "touch"
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
el = el.parentElement;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the nearest <dialog> from the passed element.
|
||||
*
|
||||
* @param {Element} el to search from
|
||||
* @return {HTMLDialogElement} dialog found
|
||||
*/
|
||||
function findNearestDialog(el) {
|
||||
while (el) {
|
||||
if (el.localName === "dialog") {
|
||||
return /** @type {HTMLDialogElement} */ el;
|
||||
}
|
||||
if (el.parentElement) {
|
||||
el = el.parentElement;
|
||||
} else if (el.parentNode) {
|
||||
el = el.parentNode.host;
|
||||
} else {
|
||||
el = null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Blur the specified element, as long as it's not the HTML body element.
|
||||
* This works around an IE9/10 bug - blurring the body causes Windows to
|
||||
* blur the whole application.
|
||||
*
|
||||
* @param {Element} el to blur
|
||||
*/
|
||||
function safeBlur(el) {
|
||||
// Find the actual focused element when the active element is inside a shadow root
|
||||
while (el && el.shadowRoot && el.shadowRoot.activeElement) {
|
||||
el = el.shadowRoot.activeElement;
|
||||
}
|
||||
|
||||
if (el && el.blur && el !== document.body) {
|
||||
el.blur();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!NodeList} nodeList to search
|
||||
* @param {Node} node to find
|
||||
* @return {boolean} whether node is inside nodeList
|
||||
*/
|
||||
function inNodeList(nodeList, node) {
|
||||
for (var i = 0; i < nodeList.length; ++i) {
|
||||
if (nodeList[i] === node) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLFormElement} el to check
|
||||
* @return {boolean} whether this form has method="dialog"
|
||||
*/
|
||||
function isFormMethodDialog(el) {
|
||||
if (!el || !el.hasAttribute("method")) {
|
||||
return false;
|
||||
}
|
||||
return el.getAttribute("method").toLowerCase() === "dialog";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!DocumentFragment|!Element} hostElement
|
||||
* @return {?Element}
|
||||
*/
|
||||
function findFocusableElementWithin(hostElement) {
|
||||
// Note that this is 'any focusable area'. This list is probably not exhaustive, but the
|
||||
// alternative involves stepping through and trying to focus everything.
|
||||
var opts = ["button", "input", "keygen", "select", "textarea"];
|
||||
var query = opts.map(function (el) {
|
||||
return el + ":not([disabled])";
|
||||
});
|
||||
// TODO(samthor): tabindex values that are not numeric are not focusable.
|
||||
query.push('[tabindex]:not([disabled]):not([tabindex=""])'); // tabindex != "", not disabled
|
||||
var target = hostElement.querySelector(query.join(", "));
|
||||
|
||||
if (!target && "attachShadow" in Element.prototype) {
|
||||
// If we haven't found a focusable target, see if the host element contains an element
|
||||
// which has a shadowRoot.
|
||||
// Recursively search for the first focusable item in shadow roots.
|
||||
var elems = hostElement.querySelectorAll("*");
|
||||
for (var i = 0; i < elems.length; i++) {
|
||||
if (elems[i].tagName && elems[i].shadowRoot) {
|
||||
target = findFocusableElementWithin(elems[i].shadowRoot);
|
||||
if (target) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if an element is attached to the DOM.
|
||||
* @param {Element} element to check
|
||||
* @return {boolean} whether the element is in DOM
|
||||
*/
|
||||
function isConnected(element) {
|
||||
return element.isConnected || document.body.contains(element);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Event} event
|
||||
* @return {?Element}
|
||||
*/
|
||||
function findFormSubmitter(event) {
|
||||
if (event.submitter) {
|
||||
return event.submitter;
|
||||
}
|
||||
|
||||
var form = event.target;
|
||||
if (!(form instanceof HTMLFormElement)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var submitter = dialogPolyfill.formSubmitter;
|
||||
if (!submitter) {
|
||||
var target = event.target;
|
||||
var root = ("getRootNode" in target && target.getRootNode()) || document;
|
||||
submitter = root.activeElement;
|
||||
}
|
||||
|
||||
if (!submitter || submitter.form !== form) {
|
||||
return null;
|
||||
}
|
||||
return submitter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Event} event
|
||||
*/
|
||||
function maybeHandleSubmit(event) {
|
||||
if (event.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
var form = /** @type {!HTMLFormElement} */ event.target;
|
||||
|
||||
// We'd have a value if we clicked on an imagemap.
|
||||
var value = dialogPolyfill.imagemapUseValue;
|
||||
var submitter = findFormSubmitter(event);
|
||||
if (value === null && submitter) {
|
||||
value = submitter.value;
|
||||
}
|
||||
|
||||
// There should always be a dialog as this handler is added specifically on them, but check just
|
||||
// in case.
|
||||
var dialog = findNearestDialog(form);
|
||||
if (!dialog) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Prefer formmethod on the button.
|
||||
var formmethod =
|
||||
(submitter && submitter.getAttribute("formmethod")) ||
|
||||
form.getAttribute("method");
|
||||
if (formmethod !== "dialog") {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
|
||||
if (value != null) {
|
||||
// nb. we explicitly check against null/undefined
|
||||
dialog.close(value);
|
||||
} else {
|
||||
dialog.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!HTMLDialogElement} dialog to upgrade
|
||||
* @constructor
|
||||
*/
|
||||
function dialogPolyfillInfo(dialog) {
|
||||
this.dialog_ = dialog;
|
||||
this.replacedStyleTop_ = false;
|
||||
this.openAsModal_ = false;
|
||||
|
||||
// Set a11y role. Browsers that support dialog implicitly know this already.
|
||||
if (!dialog.hasAttribute("role")) {
|
||||
dialog.setAttribute("role", "dialog");
|
||||
}
|
||||
|
||||
dialog.show = this.show.bind(this);
|
||||
dialog.showModal = this.showModal.bind(this);
|
||||
dialog.close = this.close.bind(this);
|
||||
|
||||
dialog.addEventListener("submit", maybeHandleSubmit, false);
|
||||
|
||||
if (!("returnValue" in dialog)) {
|
||||
dialog.returnValue = "";
|
||||
}
|
||||
|
||||
if ("MutationObserver" in window) {
|
||||
var mo = new MutationObserver(this.maybeHideModal.bind(this));
|
||||
mo.observe(dialog, { attributes: true, attributeFilter: ["open"] });
|
||||
} else {
|
||||
// IE10 and below support. Note that DOMNodeRemoved etc fire _before_ removal. They also
|
||||
// seem to fire even if the element was removed as part of a parent removal. Use the removed
|
||||
// events to force downgrade (useful if removed/immediately added).
|
||||
var removed = false;
|
||||
var cb = function () {
|
||||
removed ? this.downgradeModal() : this.maybeHideModal();
|
||||
removed = false;
|
||||
}.bind(this);
|
||||
var timeout;
|
||||
var delayModel = function (ev) {
|
||||
if (ev.target !== dialog) {
|
||||
return;
|
||||
} // not for a child element
|
||||
var cand = "DOMNodeRemoved";
|
||||
removed |= ev.type.substr(0, cand.length) === cand;
|
||||
window.clearTimeout(timeout);
|
||||
timeout = window.setTimeout(cb, 0);
|
||||
};
|
||||
["DOMAttrModified", "DOMNodeRemoved", "DOMNodeRemovedFromDocument"].forEach(
|
||||
function (name) {
|
||||
dialog.addEventListener(name, delayModel);
|
||||
}
|
||||
);
|
||||
}
|
||||
// Note that the DOM is observed inside DialogManager while any dialog
|
||||
// is being displayed as a modal, to catch modal removal from the DOM.
|
||||
|
||||
Object.defineProperty(dialog, "open", {
|
||||
set: this.setOpen.bind(this),
|
||||
get: dialog.hasAttribute.bind(dialog, "open"),
|
||||
});
|
||||
|
||||
this.backdrop_ = document.createElement("div");
|
||||
this.backdrop_.className = "backdrop";
|
||||
this.backdrop_.addEventListener(
|
||||
"mouseup",
|
||||
this.backdropMouseEvent_.bind(this)
|
||||
);
|
||||
this.backdrop_.addEventListener(
|
||||
"mousedown",
|
||||
this.backdropMouseEvent_.bind(this)
|
||||
);
|
||||
this.backdrop_.addEventListener("click", this.backdropMouseEvent_.bind(this));
|
||||
}
|
||||
|
||||
dialogPolyfillInfo.prototype = /** @type {HTMLDialogElement.prototype} */ {
|
||||
get dialog() {
|
||||
return this.dialog_;
|
||||
},
|
||||
|
||||
/**
|
||||
* Maybe remove this dialog from the modal top layer. This is called when
|
||||
* a modal dialog may no longer be tenable, e.g., when the dialog is no
|
||||
* longer open or is no longer part of the DOM.
|
||||
*/
|
||||
maybeHideModal: function () {
|
||||
if (this.dialog_.hasAttribute("open") && isConnected(this.dialog_)) {
|
||||
return;
|
||||
}
|
||||
this.downgradeModal();
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove this dialog from the modal top layer, leaving it as a non-modal.
|
||||
*/
|
||||
downgradeModal: function () {
|
||||
if (!this.openAsModal_) {
|
||||
return;
|
||||
}
|
||||
this.openAsModal_ = false;
|
||||
this.dialog_.style.zIndex = "";
|
||||
|
||||
// This won't match the native <dialog> exactly because if the user set top on a centered
|
||||
// polyfill dialog, that top gets thrown away when the dialog is closed. Not sure it's
|
||||
// possible to polyfill this perfectly.
|
||||
if (this.replacedStyleTop_) {
|
||||
this.dialog_.style.top = "";
|
||||
this.replacedStyleTop_ = false;
|
||||
}
|
||||
|
||||
// Clear the backdrop and remove from the manager.
|
||||
this.backdrop_.parentNode &&
|
||||
this.backdrop_.parentNode.removeChild(this.backdrop_);
|
||||
dialogPolyfill.dm.removeDialog(this);
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {boolean} value whether to open or close this dialog
|
||||
*/
|
||||
setOpen: function (value) {
|
||||
if (value) {
|
||||
this.dialog_.hasAttribute("open") ||
|
||||
this.dialog_.setAttribute("open", "");
|
||||
} else {
|
||||
this.dialog_.removeAttribute("open");
|
||||
this.maybeHideModal(); // nb. redundant with MutationObserver
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles mouse events ('mouseup', 'mousedown', 'click') on the fake .backdrop element, redirecting them as if
|
||||
* they were on the dialog itself.
|
||||
*
|
||||
* @param {!Event} e to redirect
|
||||
*/
|
||||
backdropMouseEvent_: function (e) {
|
||||
if (!this.dialog_.hasAttribute("tabindex")) {
|
||||
// Clicking on the backdrop should move the implicit cursor, even if dialog cannot be
|
||||
// focused. Create a fake thing to focus on. If the backdrop was _before_ the dialog, this
|
||||
// would not be needed - clicks would move the implicit cursor there.
|
||||
var fake = document.createElement("div");
|
||||
this.dialog_.insertBefore(fake, this.dialog_.firstChild);
|
||||
fake.tabIndex = -1;
|
||||
fake.focus();
|
||||
this.dialog_.removeChild(fake);
|
||||
} else {
|
||||
this.dialog_.focus();
|
||||
}
|
||||
|
||||
var redirectedEvent = document.createEvent("MouseEvents");
|
||||
redirectedEvent.initMouseEvent(
|
||||
e.type,
|
||||
e.bubbles,
|
||||
e.cancelable,
|
||||
window,
|
||||
e.detail,
|
||||
e.screenX,
|
||||
e.screenY,
|
||||
e.clientX,
|
||||
e.clientY,
|
||||
e.ctrlKey,
|
||||
e.altKey,
|
||||
e.shiftKey,
|
||||
e.metaKey,
|
||||
e.button,
|
||||
e.relatedTarget
|
||||
);
|
||||
this.dialog_.dispatchEvent(redirectedEvent);
|
||||
e.stopPropagation();
|
||||
},
|
||||
|
||||
/**
|
||||
* Focuses on the first focusable element within the dialog. This will always blur the current
|
||||
* focus, even if nothing within the dialog is found.
|
||||
*/
|
||||
focus_: function () {
|
||||
// Find element with `autofocus` attribute, or fall back to the first form/tabindex control.
|
||||
var target = this.dialog_.querySelector("[autofocus]:not([disabled])");
|
||||
if (!target && this.dialog_.tabIndex >= 0) {
|
||||
target = this.dialog_;
|
||||
}
|
||||
if (!target) {
|
||||
target = findFocusableElementWithin(this.dialog_);
|
||||
}
|
||||
safeBlur(document.activeElement);
|
||||
target && target.focus();
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the zIndex for the backdrop and dialog.
|
||||
*
|
||||
* @param {number} dialogZ
|
||||
* @param {number} backdropZ
|
||||
*/
|
||||
updateZIndex: function (dialogZ, backdropZ) {
|
||||
if (dialogZ < backdropZ) {
|
||||
throw new Error("dialogZ should never be < backdropZ");
|
||||
}
|
||||
this.dialog_.style.zIndex = dialogZ;
|
||||
this.backdrop_.style.zIndex = backdropZ;
|
||||
},
|
||||
|
||||
/**
|
||||
* Shows the dialog. If the dialog is already open, this does nothing.
|
||||
*/
|
||||
show: function () {
|
||||
if (!this.dialog_.open) {
|
||||
this.setOpen(true);
|
||||
this.focus_();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Show this dialog modally.
|
||||
*/
|
||||
showModal: function () {
|
||||
if (this.dialog_.hasAttribute("open")) {
|
||||
throw new Error(
|
||||
"Failed to execute 'showModal' on dialog: The element is already open, and therefore cannot be opened modally."
|
||||
);
|
||||
}
|
||||
if (!isConnected(this.dialog_)) {
|
||||
throw new Error(
|
||||
"Failed to execute 'showModal' on dialog: The element is not in a Document."
|
||||
);
|
||||
}
|
||||
if (!dialogPolyfill.dm.pushDialog(this)) {
|
||||
throw new Error(
|
||||
"Failed to execute 'showModal' on dialog: There are too many open modal dialogs."
|
||||
);
|
||||
}
|
||||
|
||||
if (createsStackingContext(this.dialog_.parentElement)) {
|
||||
console.warn(
|
||||
"A dialog is being shown inside a stacking context. " +
|
||||
"This may cause it to be unusable. For more information, see this link: " +
|
||||
"https://github.com/GoogleChrome/dialog-polyfill/#stacking-context"
|
||||
);
|
||||
}
|
||||
|
||||
this.setOpen(true);
|
||||
this.openAsModal_ = true;
|
||||
|
||||
// Optionally center vertically, relative to the current viewport.
|
||||
if (dialogPolyfill.needsCentering(this.dialog_)) {
|
||||
dialogPolyfill.reposition(this.dialog_);
|
||||
this.replacedStyleTop_ = true;
|
||||
} else {
|
||||
this.replacedStyleTop_ = false;
|
||||
}
|
||||
|
||||
// Insert backdrop.
|
||||
this.dialog_.parentNode.insertBefore(
|
||||
this.backdrop_,
|
||||
this.dialog_.nextSibling
|
||||
);
|
||||
|
||||
// Focus on whatever inside the dialog.
|
||||
this.focus_();
|
||||
},
|
||||
|
||||
/**
|
||||
* Closes this HTMLDialogElement. This is optional vs clearing the open
|
||||
* attribute, however this fires a 'close' event.
|
||||
*
|
||||
* @param {string=} opt_returnValue to use as the returnValue
|
||||
*/
|
||||
close: function (opt_returnValue) {
|
||||
if (!this.dialog_.hasAttribute("open")) {
|
||||
throw new Error(
|
||||
"Failed to execute 'close' on dialog: The element does not have an 'open' attribute, and therefore cannot be closed."
|
||||
);
|
||||
}
|
||||
this.setOpen(false);
|
||||
|
||||
// Leave returnValue untouched in case it was set directly on the element
|
||||
if (opt_returnValue !== undefined) {
|
||||
this.dialog_.returnValue = opt_returnValue;
|
||||
}
|
||||
|
||||
// Triggering "close" event for any attached listeners on the <dialog>.
|
||||
var closeEvent = new window.CustomEvent("close", {
|
||||
bubbles: false,
|
||||
cancelable: false,
|
||||
});
|
||||
safeDispatchEvent(this.dialog_, closeEvent);
|
||||
},
|
||||
};
|
||||
|
||||
var dialogPolyfill = {};
|
||||
|
||||
dialogPolyfill.reposition = function (element) {
|
||||
var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
|
||||
var topValue = scrollTop + (window.innerHeight - element.offsetHeight) / 2;
|
||||
element.style.top = Math.max(scrollTop, topValue) + "px";
|
||||
};
|
||||
|
||||
dialogPolyfill.isInlinePositionSetByStylesheet = function (element) {
|
||||
for (var i = 0; i < document.styleSheets.length; ++i) {
|
||||
var styleSheet = document.styleSheets[i];
|
||||
var cssRules = null;
|
||||
// Some browsers throw on cssRules.
|
||||
try {
|
||||
cssRules = styleSheet.cssRules;
|
||||
} catch (e) {
|
||||
/* empty */
|
||||
}
|
||||
if (!cssRules) {
|
||||
continue;
|
||||
}
|
||||
for (var j = 0; j < cssRules.length; ++j) {
|
||||
var rule = cssRules[j];
|
||||
var selectedNodes = null;
|
||||
// Ignore errors on invalid selector texts.
|
||||
try {
|
||||
selectedNodes = document.querySelectorAll(rule.selectorText);
|
||||
} catch (e) {
|
||||
/* empty */
|
||||
}
|
||||
if (!selectedNodes || !inNodeList(selectedNodes, element)) {
|
||||
continue;
|
||||
}
|
||||
var cssTop = rule.style.getPropertyValue("top");
|
||||
var cssBottom = rule.style.getPropertyValue("bottom");
|
||||
if (
|
||||
(cssTop && cssTop !== "auto") ||
|
||||
(cssBottom && cssBottom !== "auto")
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
dialogPolyfill.needsCentering = function (dialog) {
|
||||
var computedStyle = window.getComputedStyle(dialog);
|
||||
if (computedStyle.position !== "absolute") {
|
||||
return false;
|
||||
}
|
||||
|
||||
// We must determine whether the top/bottom specified value is non-auto. In
|
||||
// WebKit/Blink, checking computedStyle.top == 'auto' is sufficient, but
|
||||
// Firefox returns the used value. So we do this crazy thing instead: check
|
||||
// the inline style and then go through CSS rules.
|
||||
if (
|
||||
(dialog.style.top !== "auto" && dialog.style.top !== "") ||
|
||||
(dialog.style.bottom !== "auto" && dialog.style.bottom !== "")
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return !dialogPolyfill.isInlinePositionSetByStylesheet(dialog);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {!Element} element to force upgrade
|
||||
*/
|
||||
dialogPolyfill.forceRegisterDialog = function (element) {
|
||||
if (window.HTMLDialogElement || element.showModal) {
|
||||
console.warn(
|
||||
"This browser already supports <dialog>, the polyfill " +
|
||||
"may not work correctly",
|
||||
element
|
||||
);
|
||||
}
|
||||
if (element.localName !== "dialog") {
|
||||
throw new Error("Failed to register dialog: The element is not a dialog.");
|
||||
}
|
||||
new dialogPolyfillInfo(/** @type {!HTMLDialogElement} */ element);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {!Element} element to upgrade, if necessary
|
||||
*/
|
||||
dialogPolyfill.registerDialog = function (element) {
|
||||
if (!element.showModal) {
|
||||
dialogPolyfill.forceRegisterDialog(element);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*/
|
||||
dialogPolyfill.DialogManager = function () {
|
||||
/** @type {!Array<!dialogPolyfillInfo>} */
|
||||
this.pendingDialogStack = [];
|
||||
|
||||
var checkDOM = this.checkDOM_.bind(this);
|
||||
|
||||
// The overlay is used to simulate how a modal dialog blocks the document.
|
||||
// The blocking dialog is positioned on top of the overlay, and the rest of
|
||||
// the dialogs on the pending dialog stack are positioned below it. In the
|
||||
// actual implementation, the modal dialog stacking is controlled by the
|
||||
// top layer, where z-index has no effect.
|
||||
this.overlay = document.createElement("div");
|
||||
this.overlay.className = "_dialog_overlay";
|
||||
this.overlay.addEventListener(
|
||||
"click",
|
||||
function (e) {
|
||||
this.forwardTab_ = undefined;
|
||||
e.stopPropagation();
|
||||
checkDOM([]); // sanity-check DOM
|
||||
}.bind(this)
|
||||
);
|
||||
|
||||
this.handleKey_ = this.handleKey_.bind(this);
|
||||
this.handleFocus_ = this.handleFocus_.bind(this);
|
||||
|
||||
this.zIndexLow_ = 100000;
|
||||
this.zIndexHigh_ = 100000 + 150;
|
||||
|
||||
this.forwardTab_ = undefined;
|
||||
|
||||
if ("MutationObserver" in window) {
|
||||
this.mo_ = new MutationObserver(function (records) {
|
||||
var removed = [];
|
||||
records.forEach(function (rec) {
|
||||
for (var i = 0, c; (c = rec.removedNodes[i]); ++i) {
|
||||
if (!(c instanceof Element)) {
|
||||
continue;
|
||||
} else if (c.localName === "dialog") {
|
||||
removed.push(c);
|
||||
}
|
||||
removed = removed.concat(c.querySelectorAll("dialog"));
|
||||
}
|
||||
});
|
||||
removed.length && checkDOM(removed);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Called on the first modal dialog being shown. Adds the overlay and related
|
||||
* handlers.
|
||||
*/
|
||||
dialogPolyfill.DialogManager.prototype.blockDocument = function () {
|
||||
document.documentElement.addEventListener("focus", this.handleFocus_, true);
|
||||
document.addEventListener("keydown", this.handleKey_);
|
||||
this.mo_ && this.mo_.observe(document, { childList: true, subtree: true });
|
||||
};
|
||||
|
||||
/**
|
||||
* Called on the first modal dialog being removed, i.e., when no more modal
|
||||
* dialogs are visible.
|
||||
*/
|
||||
dialogPolyfill.DialogManager.prototype.unblockDocument = function () {
|
||||
document.documentElement.removeEventListener(
|
||||
"focus",
|
||||
this.handleFocus_,
|
||||
true
|
||||
);
|
||||
document.removeEventListener("keydown", this.handleKey_);
|
||||
this.mo_ && this.mo_.disconnect();
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the stacking of all known dialogs.
|
||||
*/
|
||||
dialogPolyfill.DialogManager.prototype.updateStacking = function () {
|
||||
var zIndex = this.zIndexHigh_;
|
||||
|
||||
for (var i = 0, dpi; (dpi = this.pendingDialogStack[i]); ++i) {
|
||||
dpi.updateZIndex(--zIndex, --zIndex);
|
||||
if (i === 0) {
|
||||
this.overlay.style.zIndex = --zIndex;
|
||||
}
|
||||
}
|
||||
|
||||
// Make the overlay a sibling of the dialog itself.
|
||||
var last = this.pendingDialogStack[0];
|
||||
if (last) {
|
||||
var p = last.dialog.parentNode || document.body;
|
||||
p.appendChild(this.overlay);
|
||||
} else if (this.overlay.parentNode) {
|
||||
this.overlay.parentNode.removeChild(this.overlay);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Element} candidate to check if contained or is the top-most modal dialog
|
||||
* @return {boolean} whether candidate is contained in top dialog
|
||||
*/
|
||||
dialogPolyfill.DialogManager.prototype.containedByTopDialog_ = function (
|
||||
candidate
|
||||
) {
|
||||
while ((candidate = findNearestDialog(candidate))) {
|
||||
for (var i = 0, dpi; (dpi = this.pendingDialogStack[i]); ++i) {
|
||||
if (dpi.dialog === candidate) {
|
||||
return i === 0; // only valid if top-most
|
||||
}
|
||||
}
|
||||
candidate = candidate.parentElement;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
dialogPolyfill.DialogManager.prototype.handleFocus_ = function (event) {
|
||||
var target = event.composedPath ? event.composedPath()[0] : event.target;
|
||||
|
||||
if (this.containedByTopDialog_(target)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (document.activeElement === document.documentElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
safeBlur(/** @type {Element} */ target);
|
||||
|
||||
if (this.forwardTab_ === undefined) {
|
||||
return;
|
||||
} // move focus only from a tab key
|
||||
|
||||
var dpi = this.pendingDialogStack[0];
|
||||
var dialog = dpi.dialog;
|
||||
var position = dialog.compareDocumentPosition(target);
|
||||
if (position & Node.DOCUMENT_POSITION_PRECEDING) {
|
||||
if (this.forwardTab_) {
|
||||
// forward
|
||||
dpi.focus_();
|
||||
} else if (target !== document.documentElement) {
|
||||
// backwards if we're not already focused on <html>
|
||||
document.documentElement.focus();
|
||||
}
|
||||
} else {
|
||||
// TODO: Focus after the dialog, is ignored.
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
dialogPolyfill.DialogManager.prototype.handleKey_ = function (event) {
|
||||
this.forwardTab_ = undefined;
|
||||
if (event.keyCode === 27) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
var cancelEvent = new window.CustomEvent("cancel", {
|
||||
bubbles: false,
|
||||
cancelable: true,
|
||||
});
|
||||
var dpi = this.pendingDialogStack[0];
|
||||
if (dpi && safeDispatchEvent(dpi.dialog, cancelEvent)) {
|
||||
dpi.dialog.close();
|
||||
}
|
||||
} else if (event.keyCode === 9) {
|
||||
this.forwardTab_ = !event.shiftKey;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds and downgrades any known modal dialogs that are no longer displayed. Dialogs that are
|
||||
* removed and immediately readded don't stay modal, they become normal.
|
||||
*
|
||||
* @param {!Array<!HTMLDialogElement>} removed that have definitely been removed
|
||||
*/
|
||||
dialogPolyfill.DialogManager.prototype.checkDOM_ = function (removed) {
|
||||
// This operates on a clone because it may cause it to change. Each change also calls
|
||||
// updateStacking, which only actually needs to happen once. But who removes many modal dialogs
|
||||
// at a time?!
|
||||
var clone = this.pendingDialogStack.slice();
|
||||
clone.forEach(function (dpi) {
|
||||
if (removed.indexOf(dpi.dialog) !== -1) {
|
||||
dpi.downgradeModal();
|
||||
} else {
|
||||
dpi.maybeHideModal();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {!dialogPolyfillInfo} dpi
|
||||
* @return {boolean} whether the dialog was allowed
|
||||
*/
|
||||
dialogPolyfill.DialogManager.prototype.pushDialog = function (dpi) {
|
||||
var allowed = (this.zIndexHigh_ - this.zIndexLow_) / 2 - 1;
|
||||
if (this.pendingDialogStack.length >= allowed) {
|
||||
return false;
|
||||
}
|
||||
if (this.pendingDialogStack.unshift(dpi) === 1) {
|
||||
this.blockDocument();
|
||||
}
|
||||
this.updateStacking();
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {!dialogPolyfillInfo} dpi
|
||||
*/
|
||||
dialogPolyfill.DialogManager.prototype.removeDialog = function (dpi) {
|
||||
var index = this.pendingDialogStack.indexOf(dpi);
|
||||
if (index === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.pendingDialogStack.splice(index, 1);
|
||||
if (this.pendingDialogStack.length === 0) {
|
||||
this.unblockDocument();
|
||||
}
|
||||
this.updateStacking();
|
||||
};
|
||||
|
||||
if (typeof window !== "undefined" && window.HTMLDialogElement === undefined) {
|
||||
dialogPolyfill.dm = new dialogPolyfill.DialogManager();
|
||||
dialogPolyfill.formSubmitter = null;
|
||||
dialogPolyfill.imagemapUseValue = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs global handlers, such as click listers and native method overrides. These are needed
|
||||
* even if a no dialog is registered, as they deal with <form method="dialog">.
|
||||
*/
|
||||
if (typeof window !== "undefined" && window.HTMLDialogElement === undefined) {
|
||||
/**
|
||||
* If HTMLFormElement translates method="DIALOG" into 'get', then replace the descriptor with
|
||||
* one that returns the correct value.
|
||||
*/
|
||||
var testForm = document.createElement("form");
|
||||
testForm.setAttribute("method", "dialog");
|
||||
if (testForm.method !== "dialog") {
|
||||
var methodDescriptor = Object.getOwnPropertyDescriptor(
|
||||
HTMLFormElement.prototype,
|
||||
"method"
|
||||
);
|
||||
if (methodDescriptor) {
|
||||
// nb. Some older iOS and older PhantomJS fail to return the descriptor. Don't do anything
|
||||
// and don't bother to update the element.
|
||||
var realGet = methodDescriptor.get;
|
||||
methodDescriptor.get = function () {
|
||||
if (isFormMethodDialog(this)) {
|
||||
return "dialog";
|
||||
}
|
||||
return realGet.call(this);
|
||||
};
|
||||
var realSet = methodDescriptor.set;
|
||||
/** @this {HTMLElement} */
|
||||
methodDescriptor.set = function (v) {
|
||||
if (typeof v === "string" && v.toLowerCase() === "dialog") {
|
||||
return this.setAttribute("method", v);
|
||||
}
|
||||
return realSet.call(this, v);
|
||||
};
|
||||
Object.defineProperty(
|
||||
HTMLFormElement.prototype,
|
||||
"method",
|
||||
methodDescriptor
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Global 'click' handler, to capture the <input type="submit"> or <button> element which has
|
||||
* submitted a <form method="dialog">. Needed as Safari and others don't report this inside
|
||||
* document.activeElement.
|
||||
*/
|
||||
document.addEventListener(
|
||||
"click",
|
||||
function (ev) {
|
||||
dialogPolyfill.formSubmitter = null;
|
||||
dialogPolyfill.imagemapUseValue = null;
|
||||
if (ev.defaultPrevented) {
|
||||
return;
|
||||
} // e.g. a submit which prevents default submission
|
||||
|
||||
var target = /** @type {Element} */ ev.target;
|
||||
if ("composedPath" in ev) {
|
||||
var path = ev.composedPath();
|
||||
target = path.shift() || target;
|
||||
}
|
||||
if (!target || !isFormMethodDialog(target.form)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var valid =
|
||||
target.type === "submit" &&
|
||||
["button", "input"].indexOf(target.localName) > -1;
|
||||
if (!valid) {
|
||||
if (!(target.localName === "input" && target.type === "image")) {
|
||||
return;
|
||||
}
|
||||
// this is a <input type="image">, which can submit forms
|
||||
dialogPolyfill.imagemapUseValue = ev.offsetX + "," + ev.offsetY;
|
||||
}
|
||||
|
||||
var dialog = findNearestDialog(target);
|
||||
if (!dialog) {
|
||||
return;
|
||||
}
|
||||
|
||||
dialogPolyfill.formSubmitter = target;
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
/**
|
||||
* Global 'submit' handler. This handles submits of `method="dialog"` which are invalid, i.e.,
|
||||
* outside a dialog. They get prevented.
|
||||
*/
|
||||
document.addEventListener("submit", function (ev) {
|
||||
var form = ev.target;
|
||||
var dialog = findNearestDialog(form);
|
||||
if (dialog) {
|
||||
return; // ignore, handle there
|
||||
}
|
||||
|
||||
var submitter = findFormSubmitter(ev);
|
||||
var formmethod =
|
||||
(submitter && submitter.getAttribute("formmethod")) ||
|
||||
form.getAttribute("method");
|
||||
if (formmethod === "dialog") {
|
||||
ev.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Replace the native HTMLFormElement.submit() method, as it won't fire the
|
||||
* submit event and give us a chance to respond.
|
||||
*/
|
||||
var nativeFormSubmit = HTMLFormElement.prototype.submit;
|
||||
var replacementFormSubmit = function () {
|
||||
if (!isFormMethodDialog(this)) {
|
||||
return nativeFormSubmit.call(this);
|
||||
}
|
||||
var dialog = findNearestDialog(this);
|
||||
dialog && dialog.close();
|
||||
};
|
||||
HTMLFormElement.prototype.submit = replacementFormSubmit;
|
||||
}
|
||||
|
||||
export default dialogPolyfill as {
|
||||
registerDialog(dialog: HTMLDialogElement): void;
|
||||
forceRegisterDialog(dialog: HTMLDialogElement): void;
|
||||
};
|
||||
@@ -1,2 +1,4 @@
|
||||
export { default as Modal, type ModalProps } from "./Modal";
|
||||
export { type ModalContentProps } from "./ModalContent";
|
||||
export { type ModalHeaderProps } from "./ModalHeader";
|
||||
export { type ModalBodyProps } from "./ModalBody";
|
||||
export { type ModalFooterProps } from "./ModalFooter";
|
||||
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable react-hooks/rules-of-hooks */
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useRef, useState } from "react";
|
||||
import { FileIcon } from "@navikt/aksel-icons";
|
||||
import { BodyLong, Button, Heading } from "..";
|
||||
import Modal from "./Modal";
|
||||
|
||||
@@ -7,78 +7,161 @@ export default {
|
||||
title: "ds-react/Modal",
|
||||
component: Modal,
|
||||
parameters: {
|
||||
chromatic: { delay: 1000 },
|
||||
chromatic: { pauseAnimationAtEnd: true },
|
||||
},
|
||||
};
|
||||
|
||||
export const Default = {
|
||||
render: (props) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
Modal.setAppElement?.("#root");
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button onClick={() => setOpen(true)}>Open Modal</Button>
|
||||
<Modal
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
aria-labelledby="header123"
|
||||
{...props}
|
||||
>
|
||||
<Modal.Content>
|
||||
<Heading spacing id="header123" level="1" size="large">
|
||||
Header
|
||||
</Heading>
|
||||
<Heading spacing level="2" size="medium">
|
||||
Header
|
||||
</Heading>
|
||||
<BodyLong>
|
||||
Voluptate laboris mollit dolore qui. Magna elit.
|
||||
</BodyLong>
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
},
|
||||
|
||||
args: {
|
||||
shouldCloseOnOverlayClick: true,
|
||||
closeButton: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const ParentSelector = () => {
|
||||
const [open, setOpen] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
Modal.setAppElement?.("#root");
|
||||
}, []);
|
||||
|
||||
const parentEl = document.getElementById("custom-container");
|
||||
export const WithUseRef = () => {
|
||||
const ref = useRef<HTMLDialogElement>(null);
|
||||
const ref2 = useRef<HTMLDialogElement>(null);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div id="custom-container" />
|
||||
<Button onClick={() => setOpen(true)}>Open Modal</Button>
|
||||
<Button onClick={() => ref.current?.showModal()}>Open Modal</Button>
|
||||
<Modal
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
aria-labelledby="header123"
|
||||
parentSelector={parentEl ? () => parentEl : undefined}
|
||||
open={ref.current ? undefined : true /* initially open */}
|
||||
ref={ref}
|
||||
header={{
|
||||
label: "Optional label",
|
||||
icon: <FileIcon aria-hidden />,
|
||||
heading: "Title",
|
||||
size: "small",
|
||||
}}
|
||||
>
|
||||
<Modal.Content>
|
||||
<Heading spacing id="header123" level="1" size="large">
|
||||
Header
|
||||
</Heading>
|
||||
<Heading spacing level="2" size="medium">
|
||||
Header
|
||||
</Heading>
|
||||
<BodyLong>Voluptate laboris mollit dolore qui. Magna elit.</BodyLong>
|
||||
</Modal.Content>
|
||||
<Modal.Body>
|
||||
<BodyLong spacing>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
|
||||
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim
|
||||
ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
|
||||
aliquip ex ea commodo consequat. Duis aute irure dolor in
|
||||
reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
|
||||
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
|
||||
culpa qui officia deserunt mollit anim id est laborum.
|
||||
</BodyLong>
|
||||
|
||||
{/* Nested modal */}
|
||||
<Modal
|
||||
ref={ref2}
|
||||
onBeforeClose={() =>
|
||||
window.confirm("Are you sure you want to close the modal?")
|
||||
}
|
||||
aria-labelledby="heading123"
|
||||
>
|
||||
<Modal.Header>
|
||||
<Heading size="medium" level="2" id="heading123">
|
||||
Custom header
|
||||
</Heading>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
Nesting modals is not recommended, but works in most cases.
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button onClick={() => ref2.current?.close()}>
|
||||
Close w/o confirm
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
|
||||
<BodyLong>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
|
||||
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim
|
||||
ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
|
||||
aliquip ex ea commodo consequat. Duis aute irure dolor in
|
||||
reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
|
||||
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
|
||||
culpa qui officia deserunt mollit anim id est laborum.
|
||||
</BodyLong>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button>Primary</Button>
|
||||
<Button variant="secondary" onClick={() => ref2.current?.showModal()}>
|
||||
Secondary
|
||||
</Button>
|
||||
<Button variant="tertiary">Tertiary</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
WithUseRef.storyName = "With useRef";
|
||||
|
||||
export const WithUseState = () => {
|
||||
const [open, setOpen] = useState(true);
|
||||
const [open2, setOpen2] = useState(false);
|
||||
|
||||
return (
|
||||
<div style={{ minHeight: "1000px", display: "flex", alignItems: "center" }}>
|
||||
<div style={{ display: "flex", gap: "1em" }}>
|
||||
<Button variant="secondary">Button</Button>
|
||||
<Button onClick={() => setOpen(true)}>Open Modal</Button>
|
||||
<Button variant="secondary">Button</Button>
|
||||
</div>
|
||||
<Modal
|
||||
header={{ heading: "Simple header" }}
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
width={400}
|
||||
>
|
||||
<Modal.Body>
|
||||
This modal has width set to 400.
|
||||
{/* Nested modal */}
|
||||
<Modal
|
||||
open={open2}
|
||||
onClose={(e) => {
|
||||
e.stopPropagation(); // onClose wil propagate to parent modal if not stopped
|
||||
setOpen2(false);
|
||||
}}
|
||||
aria-label="Nested modal"
|
||||
width={800}
|
||||
>
|
||||
<Modal.Body>
|
||||
<BodyLong spacing>
|
||||
Nesting modals is not recommended, but works in most cases. This
|
||||
one does not have header or footer, but is bigger than the
|
||||
parent modal.
|
||||
</BodyLong>
|
||||
<BodyLong spacing>Width is set to 800.</BodyLong>
|
||||
<Button onClick={() => setOpen2(false)}>Close</Button>
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button onClick={() => setOpen2(true)}>Primary</Button>
|
||||
<Button variant="secondary" onClick={() => setOpen(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
WithUseState.storyName = "With useState";
|
||||
|
||||
export const EmptyHeader = () => (
|
||||
<div style={{ minWidth: "800px", minHeight: "600px" } /* For Chromatic */}>
|
||||
<Modal open>
|
||||
<Modal.Header />
|
||||
<Modal.Body>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
|
||||
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
|
||||
veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
|
||||
commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
|
||||
velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
|
||||
occaecat cupidatat non proident, sunt in culpa qui officia deserunt
|
||||
mollit anim id est laborum.
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
|
||||
export const Small = () => (
|
||||
<Modal open width="small" header={{ heading: "Simple header" }}>
|
||||
<Modal.Body>Lorem ipsum dolor sit amet.</Modal.Body>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
export const Medium = () => (
|
||||
<Modal open width="medium" header={{ heading: "Simple header" }}>
|
||||
<Modal.Body>Lorem ipsum dolor sit amet.</Modal.Body>
|
||||
</Modal>
|
||||
);
|
||||
@@ -16,10 +16,12 @@ import React, {
|
||||
useCallback,
|
||||
useMemo,
|
||||
useRef,
|
||||
useContext,
|
||||
} from "react";
|
||||
import { mergeRefs } from "..";
|
||||
import { useClientLayoutEffect, useEventListener } from "../util";
|
||||
import PopoverContent, { PopoverContentType } from "./PopoverContent";
|
||||
import { ModalContext } from "../modal/ModalContext";
|
||||
|
||||
export interface PopoverProps extends HTMLAttributes<HTMLDivElement> {
|
||||
/**
|
||||
@@ -117,13 +119,15 @@ export const Popover = forwardRef<HTMLDivElement, PopoverProps>(
|
||||
onClose,
|
||||
placement = "top",
|
||||
offset,
|
||||
strategy: userStrategy = "absolute",
|
||||
strategy: userStrategy,
|
||||
bubbleEscape = false,
|
||||
...rest
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const arrowRef = useRef<HTMLDivElement | null>(null);
|
||||
const isInModal = useContext(ModalContext) !== null;
|
||||
const chosenStrategy = userStrategy ?? (isInModal ? "fixed" : "absolute");
|
||||
|
||||
const {
|
||||
x,
|
||||
@@ -135,7 +139,7 @@ export const Popover = forwardRef<HTMLDivElement, PopoverProps>(
|
||||
placement: flPlacement,
|
||||
middlewareData: { arrow: { x: arrowX, y: arrowY } = {} },
|
||||
} = useFloating({
|
||||
strategy: userStrategy,
|
||||
strategy: chosenStrategy,
|
||||
placement,
|
||||
open: open,
|
||||
onOpenChange: onClose,
|
||||
|
||||
@@ -2,13 +2,9 @@ import React, { createContext, useContext } from "react";
|
||||
|
||||
export interface ProviderContextType {
|
||||
/**
|
||||
* Global root-element to attach portals to (Modal, Tooltip)
|
||||
* Global root-element to attach portals to (Tooltip)
|
||||
*/
|
||||
rootElement?: HTMLElement;
|
||||
/**
|
||||
* Global config for appElement (Modal)
|
||||
*/
|
||||
appElement?: HTMLElement;
|
||||
}
|
||||
|
||||
export const ProviderContext = createContext<ProviderContextType | undefined>(
|
||||
@@ -18,7 +14,6 @@ export const ProviderContext = createContext<ProviderContextType | undefined>(
|
||||
export interface ProviderProps {
|
||||
children?: React.ReactNode;
|
||||
rootElement?: HTMLElement;
|
||||
appElement?: HTMLElement;
|
||||
}
|
||||
|
||||
export const useProvider = () => useContext(ProviderContext);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React, { forwardRef, useContext } from "react";
|
||||
import React, { forwardRef } from "react";
|
||||
import cl from "clsx";
|
||||
import { BodyShort } from "..";
|
||||
import { TableContext } from "./Table";
|
||||
|
||||
export interface DataCellProps
|
||||
extends React.TdHTMLAttributes<HTMLTableCellElement> {
|
||||
@@ -19,8 +18,6 @@ export interface DataCellType
|
||||
|
||||
export const DataCell: DataCellType = forwardRef(
|
||||
({ className, children = "", align, ...rest }, ref) => {
|
||||
const context = useContext(TableContext);
|
||||
|
||||
return (
|
||||
<BodyShort
|
||||
as="td"
|
||||
@@ -28,7 +25,6 @@ export const DataCell: DataCellType = forwardRef(
|
||||
className={cl("navds-table__data-cell", className, {
|
||||
[`navds-table__data-cell--align-${align}`]: align,
|
||||
})}
|
||||
size={context?.size}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -31,10 +31,15 @@ export interface ExpandableRowProps extends Omit<RowProps, "content"> {
|
||||
*/
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
/**
|
||||
* Disable expansio
|
||||
* Disable expansion. shadeOnHover will not be visible.
|
||||
* @default false
|
||||
*/
|
||||
expansionDisabled?: boolean;
|
||||
/**
|
||||
* Makes the whole row clickable
|
||||
* @default false
|
||||
*/
|
||||
expandOnRowClick?: boolean;
|
||||
/**
|
||||
* The width of the expanded row's internal cell
|
||||
* @default 999
|
||||
@@ -58,6 +63,7 @@ export const ExpandableRow: ExpandableRowType = forwardRef(
|
||||
open,
|
||||
onOpenChange,
|
||||
expansionDisabled = false,
|
||||
expandOnRowClick = false,
|
||||
colSpan = 999,
|
||||
...rest
|
||||
},
|
||||
@@ -65,9 +71,22 @@ export const ExpandableRow: ExpandableRowType = forwardRef(
|
||||
) => {
|
||||
const [internalOpen, setInternalOpen] = useState<boolean>(defaultOpen);
|
||||
const id = useId();
|
||||
|
||||
const isOpen = open ?? internalOpen;
|
||||
|
||||
const expansionHandler = (e) => {
|
||||
onOpenChange?.(!isOpen);
|
||||
if (open === undefined) {
|
||||
setInternalOpen((open) => !open);
|
||||
}
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
const onRowClick = (e) => {
|
||||
if (e.target.nodeName === "TD" || e.target.nodeName === "TH") {
|
||||
expansionHandler(e);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Row
|
||||
@@ -75,7 +94,14 @@ export const ExpandableRow: ExpandableRowType = forwardRef(
|
||||
ref={ref}
|
||||
className={cl("navds-table__expandable-row", className, {
|
||||
"navds-table__expandable-row--open": isOpen,
|
||||
"navds-table__expandable-row--expansion-disabled":
|
||||
expansionDisabled,
|
||||
"navds-table__expandable-row--clickable": expandOnRowClick,
|
||||
})}
|
||||
onClick={(e) => {
|
||||
!expansionDisabled && expandOnRowClick && onRowClick(e);
|
||||
rest?.onClick?.(e);
|
||||
}}
|
||||
>
|
||||
{togglePlacement === "right" && children}
|
||||
<DataCell
|
||||
@@ -85,16 +111,11 @@ export const ExpandableRow: ExpandableRowType = forwardRef(
|
||||
>
|
||||
{!expansionDisabled && (
|
||||
<button
|
||||
type="button"
|
||||
className="navds-table__toggle-expand-button"
|
||||
type="button"
|
||||
aria-controls={id}
|
||||
aria-expanded={isOpen}
|
||||
onClick={() => {
|
||||
onOpenChange?.(!isOpen);
|
||||
if (open === undefined) {
|
||||
setInternalOpen((open) => !open);
|
||||
}
|
||||
}}
|
||||
onClick={expansionHandler}
|
||||
>
|
||||
<ChevronDownIcon
|
||||
className="navds-table__expandable-icon"
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { forwardRef, useContext } from "react";
|
||||
import cl from "clsx";
|
||||
import { TableContext } from "./Table";
|
||||
import React, { forwardRef } from "react";
|
||||
|
||||
export interface HeaderCellProps
|
||||
extends React.ThHTMLAttributes<HTMLTableCellElement> {
|
||||
@@ -19,14 +18,11 @@ export interface HeaderCellType
|
||||
|
||||
export const HeaderCell: HeaderCellType = forwardRef(
|
||||
({ className, children, align, ...rest }, ref) => {
|
||||
const context = useContext(TableContext);
|
||||
|
||||
return (
|
||||
<th
|
||||
ref={ref}
|
||||
className={cl("navds-table__header-cell", "navds-label", className, {
|
||||
[`navds-table__header-cell--align-${align}`]: align,
|
||||
"navds-label--small": context?.size === "small",
|
||||
})}
|
||||
{...rest}
|
||||
>
|
||||
|
||||
@@ -16,10 +16,10 @@ export interface SortState {
|
||||
export interface TableProps
|
||||
extends React.TableHTMLAttributes<HTMLTableElement> {
|
||||
/**
|
||||
* Changes padding
|
||||
* Changes padding around Cells
|
||||
* @default "medium"
|
||||
*/
|
||||
size?: "medium" | "small";
|
||||
size?: "large" | "medium" | "small";
|
||||
/**
|
||||
* Zebra striped table
|
||||
* @default false
|
||||
@@ -49,7 +49,6 @@ export interface TableType
|
||||
}
|
||||
|
||||
export interface TableContextProps {
|
||||
size: "medium" | "small";
|
||||
onSortChange?: (sortKey: string) => void;
|
||||
sort?: SortState;
|
||||
}
|
||||
@@ -100,7 +99,7 @@ export const Table = forwardRef(
|
||||
},
|
||||
ref
|
||||
) => (
|
||||
<TableContext.Provider value={{ size, onSortChange, sort }}>
|
||||
<TableContext.Provider value={{ onSortChange, sort }}>
|
||||
<table
|
||||
{...rest}
|
||||
ref={ref}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState } from "react";
|
||||
import { Table } from "..";
|
||||
import { Link } from "../..";
|
||||
import { Button, Checkbox, Link } from "../..";
|
||||
|
||||
export default {
|
||||
title: "ds-react/Table",
|
||||
@@ -36,6 +36,43 @@ export const Expandable = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export const ExpandableLarge = () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
return (
|
||||
<Table size="large">
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell />
|
||||
{columns.map(({ key, name }) => (
|
||||
<Table.HeaderCell key={key}>{name}</Table.HeaderCell>
|
||||
))}
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{data.slice(0, 1).map((data) => (
|
||||
<Table.ExpandableRow content={data.content} key={data.name}>
|
||||
{columns.map(({ key }) => (
|
||||
<Table.DataCell key={key}>{data[key]}</Table.DataCell>
|
||||
))}
|
||||
</Table.ExpandableRow>
|
||||
))}
|
||||
{data.slice(1, 2).map((data) => (
|
||||
<Table.ExpandableRow
|
||||
content={data.content}
|
||||
key={data.name}
|
||||
open={open}
|
||||
onOpenChange={(open) => setOpen(open)}
|
||||
>
|
||||
{columns.map(({ key }) => (
|
||||
<Table.DataCell key={key}>{data[key]}</Table.DataCell>
|
||||
))}
|
||||
</Table.ExpandableRow>
|
||||
))}
|
||||
</Table.Body>
|
||||
</Table>
|
||||
);
|
||||
};
|
||||
|
||||
export const ExpandableSmall = () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
return (
|
||||
@@ -227,3 +264,63 @@ export const ExpandableOpen = () => {
|
||||
</Table>
|
||||
);
|
||||
};
|
||||
|
||||
export const ClickableRow = {
|
||||
render: () => {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const [isRowOpen1, setIsRowOpen1] = useState(false);
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const [isRowOpen2, setIsRowOpen2] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Table zebraStripes>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>Navn</Table.HeaderCell>
|
||||
<Table.HeaderCell>Info</Table.HeaderCell>
|
||||
<Table.HeaderCell aria-hidden />
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.ExpandableRow
|
||||
content={<div>placeholder row 1</div>}
|
||||
togglePlacement="right"
|
||||
onOpenChange={setIsRowOpen1}
|
||||
data-testid="row1"
|
||||
open={isRowOpen1}
|
||||
expandOnRowClick
|
||||
>
|
||||
<Table.DataCell>Ola</Table.DataCell>
|
||||
<Table.DataCell>
|
||||
<Button
|
||||
size="xsmall"
|
||||
onClick={(e) => {
|
||||
alert("Mer info");
|
||||
}}
|
||||
>
|
||||
Mer info
|
||||
</Button>
|
||||
</Table.DataCell>
|
||||
</Table.ExpandableRow>
|
||||
<Table.ExpandableRow
|
||||
content={<div>placeholder row 2</div>}
|
||||
togglePlacement="right"
|
||||
onOpenChange={setIsRowOpen2}
|
||||
data-testid="row2"
|
||||
open={isRowOpen2}
|
||||
expandOnRowClick
|
||||
>
|
||||
<Table.DataCell>Hans</Table.DataCell>
|
||||
<Table.DataCell>
|
||||
<Checkbox hideLabel size="small">
|
||||
Sett
|
||||
</Checkbox>
|
||||
</Table.DataCell>
|
||||
</Table.ExpandableRow>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
</>
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -77,7 +77,10 @@ export const NoShadeOnHover = () => <TableComponent shadeOnHover={false} />;
|
||||
export const ZebraNoShadeOnHover = () => (
|
||||
<TableComponent zebraStripes shadeOnHover={false} />
|
||||
);
|
||||
export const Small = () => <TableComponent size="small" />;
|
||||
export const SizeLarge = () => <TableComponent size="large" />;
|
||||
export const SizeMedium = () => <TableComponent size="medium" />;
|
||||
export const SizeSmall = () => <TableComponent size="small" />;
|
||||
|
||||
export const Buttons = () => <TableComponent size="small" button />;
|
||||
|
||||
export const WithDivs = () => {
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import * as AllIcons from "@navikt/aksel-icons";
|
||||
import React from "react";
|
||||
|
||||
const icons = Object.keys(AllIcons).map((key) => AllIcons[key]);
|
||||
|
||||
export const RandomIcon = () => {
|
||||
const Icon = icons[Math.floor(Math.random() * icons.length)];
|
||||
return <Icon />;
|
||||
};
|
||||
@@ -1,5 +1,35 @@
|
||||
# @navikt/ds-tailwind
|
||||
|
||||
## 5.0.3
|
||||
|
||||
## 5.0.2
|
||||
|
||||
## 5.0.1
|
||||
|
||||
## 5.0.0
|
||||
|
||||
## 4.12.1
|
||||
|
||||
## 4.12.0
|
||||
|
||||
## 4.11.2
|
||||
|
||||
## 4.11.1
|
||||
|
||||
## 4.11.0
|
||||
|
||||
## 4.10.2
|
||||
|
||||
## 4.10.1
|
||||
|
||||
## 4.10.0
|
||||
|
||||
## 4.9.1
|
||||
|
||||
## 4.9.0
|
||||
|
||||
## 4.8.0
|
||||
|
||||
## 4.7.4
|
||||
|
||||
## 4.7.3
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-tailwind",
|
||||
"version": "4.7.4",
|
||||
"version": "5.0.3",
|
||||
"description": "Tailwind config based on @navikt/ds-tokens",
|
||||
"author": "Aksel | NAV designsystem team",
|
||||
"keywords": [
|
||||
@@ -22,7 +22,7 @@
|
||||
"directory": "@navikt/core/tailwind"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@navikt/ds-tokens": "^4.7.4",
|
||||
"@navikt/ds-tokens": "^5.0.3",
|
||||
"@types/jest": "^29.0.0",
|
||||
"color": "4.2.3",
|
||||
"jest": "^29.0.0",
|
||||
|
||||
@@ -1,5 +1,47 @@
|
||||
# @navikt/ds-tokens
|
||||
|
||||
## 5.0.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- ConfirmationPanel: Fikset opplesing av feilmelding ([#2199](https://github.com/navikt/aksel/pull/2199))
|
||||
|
||||
- Tokens: `--a-icon-alt-3` er nå i synk med figma ([#2199](https://github.com/navikt/aksel/pull/2199))
|
||||
|
||||
## 5.0.2
|
||||
|
||||
## 5.0.1
|
||||
|
||||
## 5.0.0
|
||||
|
||||
### Major Changes
|
||||
|
||||
- Gray: Gråfarger er nå mindre varme ([#2092](https://github.com/navikt/aksel/pull/2092))
|
||||
|
||||
- Action: Selected-varianter av action er nå 'blue' ('deepblue' før) ([#2092](https://github.com/navikt/aksel/pull/2092))
|
||||
|
||||
## 4.12.1
|
||||
|
||||
## 4.12.0
|
||||
|
||||
## 4.11.2
|
||||
|
||||
## 4.11.1
|
||||
|
||||
## 4.11.0
|
||||
|
||||
## 4.10.2
|
||||
|
||||
## 4.10.1
|
||||
|
||||
## 4.10.0
|
||||
|
||||
## 4.9.1
|
||||
|
||||
## 4.9.0
|
||||
|
||||
## 4.8.0
|
||||
|
||||
## 4.7.4
|
||||
|
||||
## 4.7.3
|
||||
|
||||
@@ -75,28 +75,28 @@
|
||||
{ "name": "--a-nav-red", "value": "rgba(195, 0, 0, 1)" }
|
||||
],
|
||||
"global-gray": [
|
||||
{ "name": "--a-gray-50", "value": "rgba(247, 247, 247, 1)" },
|
||||
{ "name": "--a-gray-100", "value": "rgba(241, 241, 241, 1)" },
|
||||
{ "name": "--a-gray-200", "value": "rgba(229, 229, 229, 1)" },
|
||||
{ "name": "--a-gray-300", "value": "rgba(207, 207, 207, 1)" },
|
||||
{ "name": "--a-gray-400", "value": "rgba(176, 176, 176, 1)" },
|
||||
{ "name": "--a-gray-500", "value": "rgba(143, 143, 143, 1)" },
|
||||
{ "name": "--a-gray-600", "value": "rgba(112, 112, 112, 1)" },
|
||||
{ "name": "--a-gray-700", "value": "rgba(89, 89, 89, 1)" },
|
||||
{ "name": "--a-gray-800", "value": "rgba(64, 64, 64, 1)" },
|
||||
{ "name": "--a-gray-900", "value": "rgba(38, 38, 38, 1)" }
|
||||
{ "name": "--a-gray-50", "value": "rgba(242, 243, 245, 1)" },
|
||||
{ "name": "--a-gray-100", "value": "rgba(236, 238, 240, 1)" },
|
||||
{ "name": "--a-gray-200", "value": "rgba(224, 227, 230, 1)" },
|
||||
{ "name": "--a-gray-300", "value": "rgba(203, 207, 213, 1)" },
|
||||
{ "name": "--a-gray-400", "value": "rgba(170, 176, 186, 1)" },
|
||||
{ "name": "--a-gray-500", "value": "rgba(131, 140, 154, 1)" },
|
||||
{ "name": "--a-gray-600", "value": "rgba(103, 111, 123, 1)" },
|
||||
{ "name": "--a-gray-700", "value": "rgba(82, 89, 98, 1)" },
|
||||
{ "name": "--a-gray-800", "value": "rgba(66, 71, 79, 1)" },
|
||||
{ "name": "--a-gray-900", "value": "rgba(35, 38, 42, 1)" }
|
||||
],
|
||||
"global-grayalpha": [
|
||||
{ "name": "--a-grayalpha-50", "value": "rgba(0, 0, 0, 0.03)" },
|
||||
{ "name": "--a-grayalpha-100", "value": "rgba(0, 0, 0, 0.05)" },
|
||||
{ "name": "--a-grayalpha-200", "value": "rgba(0, 0, 0, 0.1)" },
|
||||
{ "name": "--a-grayalpha-300", "value": "rgba(0, 0, 0, 0.19)" },
|
||||
{ "name": "--a-grayalpha-400", "value": "rgba(0, 0, 0, 0.31)" },
|
||||
{ "name": "--a-grayalpha-500", "value": "rgba(0, 0, 0, 0.44)" },
|
||||
{ "name": "--a-grayalpha-600", "value": "rgba(0, 0, 0, 0.56)" },
|
||||
{ "name": "--a-grayalpha-700", "value": "rgba(0, 0, 0, 0.65)" },
|
||||
{ "name": "--a-grayalpha-800", "value": "rgba(0, 0, 0, 0.75)" },
|
||||
{ "name": "--a-grayalpha-900", "value": "rgba(0, 0, 0, 0.85)" }
|
||||
{ "name": "--a-grayalpha-50", "value": "rgba(38, 55, 89, 0.06)" },
|
||||
{ "name": "--a-grayalpha-100", "value": "rgba(18, 43, 68, 0.08)" },
|
||||
{ "name": "--a-grayalpha-200", "value": "rgba(17, 41, 64, 0.13)" },
|
||||
{ "name": "--a-grayalpha-300", "value": "rgba(7, 26, 54, 0.21)" },
|
||||
{ "name": "--a-grayalpha-400", "value": "rgba(5, 23, 51, 0.34)" },
|
||||
{ "name": "--a-grayalpha-500", "value": "rgba(2, 20, 49, 0.49)" },
|
||||
{ "name": "--a-grayalpha-600", "value": "rgba(2, 15, 34, 0.6)" },
|
||||
{ "name": "--a-grayalpha-700", "value": "rgba(1, 11, 24, 0.68)" },
|
||||
{ "name": "--a-grayalpha-800", "value": "rgba(3, 11, 22, 0.75)" },
|
||||
{ "name": "--a-grayalpha-900", "value": "rgba(2, 5, 9, 0.87)" }
|
||||
],
|
||||
"global-blue": [
|
||||
{ "name": "--a-blue-50", "value": "rgba(230, 240, 255, 1)" },
|
||||
@@ -196,17 +196,17 @@
|
||||
],
|
||||
|
||||
"semantic-border-default": [
|
||||
{ "name": "--a-border-default", "value": "rgba(0, 0, 0, 0.44)" },
|
||||
{ "name": "--a-border-strong", "value": "rgba(0, 0, 0, 0.65)" },
|
||||
{ "name": "--a-border-divider", "value": "rgba(0, 0, 0, 0.19)" },
|
||||
{ "name": "--a-border-subtle", "value": "rgba(0, 0, 0, 0.1)" },
|
||||
{ "name": "--a-border-subtle-hover", "value": "rgba(0, 0, 0, 0.19)" },
|
||||
{ "name": "--a-border-on-inverted", "value": "rgba(229, 229, 229, 1)" },
|
||||
{ "name": "--a-border-on-inverted-subtle", "value": "rgba(89, 89, 89, 1)" }
|
||||
{ "name": "--a-border-default", "value": "rgba(2, 20, 49, 0.49)" },
|
||||
{ "name": "--a-border-strong", "value": "rgba(1, 11, 24, 0.68)" },
|
||||
{ "name": "--a-border-divider", "value": "rgba(7, 26, 54, 0.21)" },
|
||||
{ "name": "--a-border-subtle", "value": "rgba(7, 26, 54, 0.21)" },
|
||||
{ "name": "--a-border-subtle-hover", "value": "rgba(5, 23, 51, 0.34)" },
|
||||
{ "name": "--a-border-on-inverted", "value": "rgba(224, 227, 230, 1)" },
|
||||
{ "name": "--a-border-on-inverted-subtle", "value": "rgba(82, 89, 98, 1)" }
|
||||
],
|
||||
"semantic-border-action": [
|
||||
{ "name": "--a-border-action", "value": "rgba(0, 103, 197, 1)" },
|
||||
{ "name": "--a-border-action-selected", "value": "rgba(0, 91, 130, 1)" },
|
||||
{ "name": "--a-border-action-selected", "value": "rgba(0, 69, 156, 1)" },
|
||||
{ "name": "--a-border-selected", "value": "rgba(0, 103, 197, 1)" }
|
||||
],
|
||||
"semantic-border-status": [
|
||||
@@ -228,19 +228,19 @@
|
||||
{ "name": "--a-border-alt-3", "value": "rgba(0, 91, 130, 1)" }
|
||||
],
|
||||
"semantic-text": [
|
||||
{ "name": "--a-text-default", "value": "rgba(38, 38, 38, 1)" },
|
||||
{ "name": "--a-text-subtle", "value": "rgba(0, 0, 0, 0.65)" },
|
||||
{ "name": "--a-text-default", "value": "rgba(35, 38, 42, 1)" },
|
||||
{ "name": "--a-text-subtle", "value": "rgba(1, 11, 24, 0.68)" },
|
||||
{ "name": "--a-text-visited", "value": "rgba(99, 70, 137, 1)" },
|
||||
{ "name": "--a-text-danger", "value": "rgba(195, 0, 0, 1)" },
|
||||
{ "name": "--a-text-action", "value": "rgba(0, 103, 197, 1)" },
|
||||
{ "name": "--a-text-action-selected", "value": "rgba(0, 91, 130, 1)" },
|
||||
{ "name": "--a-text-action-selected", "value": "rgba(0, 69, 156, 1)" },
|
||||
{
|
||||
"name": "--a-text-action-on-action-subtle",
|
||||
"value": "rgba(0, 86, 180, 1)"
|
||||
},
|
||||
{ "name": "--a-text-on-warning", "value": "rgba(38, 38, 38, 1)" },
|
||||
{ "name": "--a-text-on-info", "value": "rgba(38, 38, 38, 1)" },
|
||||
{ "name": "--a-text-on-alt-2", "value": "rgba(38, 38, 38, 1)" },
|
||||
{ "name": "--a-text-on-warning", "value": "rgba(35, 38, 42, 1)" },
|
||||
{ "name": "--a-text-on-info", "value": "rgba(35, 38, 42, 1)" },
|
||||
{ "name": "--a-text-on-alt-2", "value": "rgba(35, 38, 42, 1)" },
|
||||
{ "name": "--a-text-on-inverted", "value": "rgba(255, 255, 255, 1)" },
|
||||
{ "name": "--a-text-on-neutral", "value": "rgba(255, 255, 255, 1)" },
|
||||
{ "name": "--a-text-on-action", "value": "rgba(255, 255, 255, 1)" },
|
||||
@@ -251,15 +251,15 @@
|
||||
],
|
||||
"semantic-surface-default": [
|
||||
{ "name": "--a-surface-default", "value": "rgba(255, 255, 255, 1)" },
|
||||
{ "name": "--a-surface-hover", "value": "rgba(0, 0, 0, 0.05)" },
|
||||
{ "name": "--a-surface-active", "value": "rgba(0, 0, 0, 0.1)" },
|
||||
{ "name": "--a-surface-hover", "value": "rgba(18, 43, 68, 0.08)" },
|
||||
{ "name": "--a-surface-active", "value": "rgba(17, 41, 64, 0.13)" },
|
||||
{ "name": "--a-surface-selected", "value": "rgba(230, 240, 255, 1)" },
|
||||
{ "name": "--a-surface-subtle", "value": "rgba(247, 247, 247, 1)" },
|
||||
{ "name": "--a-surface-subtle", "value": "rgba(242, 243, 245, 1)" },
|
||||
{ "name": "--a-surface-transparent", "value": "rgba(255, 255, 255, 0)" },
|
||||
{ "name": "--a-surface-backdrop", "value": "rgba(0, 0, 0, 0.65)" },
|
||||
{ "name": "--a-surface-inverted-hover", "value": "rgba(64, 64, 64, 1)" },
|
||||
{ "name": "--a-surface-inverted-active", "value": "rgba(89, 89, 89, 1)" },
|
||||
{ "name": "--a-surface-inverted", "value": "rgba(38, 38, 38, 1)" }
|
||||
{ "name": "--a-surface-backdrop", "value": "rgba(2, 20, 49, 0.49)" },
|
||||
{ "name": "--a-surface-inverted-hover", "value": "rgba(66, 71, 79, 1)" },
|
||||
{ "name": "--a-surface-inverted-active", "value": "rgba(82, 89, 98, 1)" },
|
||||
{ "name": "--a-surface-inverted", "value": "rgba(35, 38, 42, 1)" }
|
||||
],
|
||||
"semantic-surface-action": [
|
||||
{ "name": "--a-surface-action-subtle", "value": "rgba(230, 240, 255, 1)" },
|
||||
@@ -269,24 +269,27 @@
|
||||
},
|
||||
{ "name": "--a-surface-action", "value": "rgba(0, 103, 197, 1)" },
|
||||
{ "name": "--a-surface-action-hover", "value": "rgba(0, 86, 180, 1)" },
|
||||
{ "name": "--a-surface-action-active", "value": "rgba(0, 91, 130, 1)" },
|
||||
{ "name": "--a-surface-action-selected", "value": "rgba(0, 91, 130, 1)" },
|
||||
{ "name": "--a-surface-action-active", "value": "rgba(0, 69, 156, 1)" },
|
||||
{ "name": "--a-surface-action-selected", "value": "rgba(0, 69, 156, 1)" },
|
||||
{
|
||||
"name": "--a-surface-action-selected-hover",
|
||||
"value": "rgba(0, 80, 119, 1)"
|
||||
"value": "rgba(0, 52, 125, 1)"
|
||||
}
|
||||
],
|
||||
"semantic-surface-neutral": [
|
||||
{ "name": "--a-surface-neutral-subtle", "value": "rgba(0, 0, 0, 0.05)" },
|
||||
{ "name": "--a-surface-neutral-subtle", "value": "rgba(18, 43, 68, 0.08)" },
|
||||
{
|
||||
"name": "--a-surface-neutral-subtle-hover",
|
||||
"value": "rgba(0, 0, 0, 0.1)"
|
||||
"value": "rgba(17, 41, 64, 0.13)"
|
||||
},
|
||||
{ "name": "--a-surface-neutral-moderate", "value": "rgba(0, 0, 0, 0.1)" },
|
||||
{ "name": "--a-surface-neutral", "value": "rgba(89, 89, 89, 1)" },
|
||||
{ "name": "--a-surface-neutral-hover", "value": "rgba(64, 64, 64, 1)" },
|
||||
{ "name": "--a-surface-neutral-selected", "value": "rgba(38, 38, 38, 1)" },
|
||||
{ "name": "--a-surface-neutral-active", "value": "rgba(38, 38, 38, 1)" }
|
||||
{
|
||||
"name": "--a-surface-neutral-moderate",
|
||||
"value": "rgba(17, 41, 64, 0.13)"
|
||||
},
|
||||
{ "name": "--a-surface-neutral", "value": "rgba(82, 89, 98, 1)" },
|
||||
{ "name": "--a-surface-neutral-hover", "value": "rgba(66, 71, 79, 1)" },
|
||||
{ "name": "--a-surface-neutral-selected", "value": "rgba(35, 38, 42, 1)" },
|
||||
{ "name": "--a-surface-neutral-active", "value": "rgba(35, 38, 42, 1)" }
|
||||
],
|
||||
"semantic-surface-success": [
|
||||
{ "name": "--a-surface-success-subtle", "value": "rgba(204, 241, 214, 1)" },
|
||||
@@ -356,18 +359,18 @@
|
||||
},
|
||||
{
|
||||
"name": "--a-bg-subtle",
|
||||
"value": "rgba(241, 241, 241, 1)"
|
||||
"value": "rgba(236, 238, 240, 1)"
|
||||
}
|
||||
],
|
||||
"semantic-icon-defaults": [
|
||||
{ "name": "--a-icon-default", "value": "rgba(38, 38, 38, 1)" },
|
||||
{ "name": "--a-icon-subtle", "value": "rgba(0, 0, 0, 0.65)" },
|
||||
{ "name": "--a-icon-default", "value": "rgba(35, 38, 42, 1)" },
|
||||
{ "name": "--a-icon-subtle", "value": "rgba(1, 11, 24, 0.68)" },
|
||||
{ "name": "--a-icon-on-neutral", "value": "rgba(255, 255, 255, 1)" },
|
||||
{ "name": "--a-icon-on-inverted", "value": "rgba(255, 255, 255, 1)" }
|
||||
],
|
||||
"semantic-icon-action": [
|
||||
{ "name": "--a-icon-action", "value": "rgba(0, 103, 197, 1)" },
|
||||
{ "name": "--a-icon-action-selected", "value": "rgba(0, 91, 130, 1)" },
|
||||
{ "name": "--a-icon-action-selected", "value": "rgba(0, 69, 156, 1)" },
|
||||
{
|
||||
"name": "--a-icon-action-on-action-subtle",
|
||||
"value": "rgba(0, 86, 180, 1)"
|
||||
@@ -380,14 +383,14 @@
|
||||
{ "name": "--a-icon-danger", "value": "rgba(195, 0, 0, 1)" },
|
||||
{ "name": "--a-icon-on-danger", "value": "rgba(255, 255, 255, 1)" },
|
||||
{ "name": "--a-icon-warning", "value": "rgba(199, 115, 0, 1)" },
|
||||
{ "name": "--a-icon-on-warning", "value": "rgba(38, 38, 38, 1)" },
|
||||
{ "name": "--a-icon-on-warning", "value": "rgba(35, 38, 42, 1)" },
|
||||
{ "name": "--a-icon-info", "value": "rgba(35, 107, 125, 1)" },
|
||||
{ "name": "--a-icon-on-info", "value": "rgba(38, 38, 38, 1)" }
|
||||
{ "name": "--a-icon-on-info", "value": "rgba(35, 38, 42, 1)" }
|
||||
],
|
||||
"semantic-icon-alt": [
|
||||
{ "name": "--a-icon-alt-1", "value": "rgba(99, 70, 137, 1)" },
|
||||
{ "name": "--a-icon-alt-2", "value": "rgba(127, 137, 0, 1)" },
|
||||
{ "name": "--a-icon-alt-3", "value": "rgba(51, 128, 165, 1)" }
|
||||
{ "name": "--a-icon-alt-3", "value": "rgba(0, 91, 130, 1)" }
|
||||
],
|
||||
|
||||
"semantic-data-surface": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@navikt/ds-tokens",
|
||||
"version": "4.7.4",
|
||||
"version": "5.0.3",
|
||||
"description": "Design-tokens for NAV designsystem",
|
||||
"author": "Aksel | NAV designsystem team",
|
||||
"keywords": [
|
||||
|
||||
@@ -13,66 +13,66 @@
|
||||
},
|
||||
"gray": {
|
||||
"50": {
|
||||
"value": "rgba(247, 247, 247, 1)"
|
||||
"value": "rgba(242, 243, 245, 1)"
|
||||
},
|
||||
"100": {
|
||||
"value": "rgba(241, 241, 241, 1)"
|
||||
"value": "rgba(236, 238, 240, 1)"
|
||||
},
|
||||
"200": {
|
||||
"value": "rgba(229, 229, 229, 1)"
|
||||
"value": "rgba(224, 227, 230, 1)"
|
||||
},
|
||||
"300": {
|
||||
"value": "rgba(207, 207, 207, 1)"
|
||||
"value": "rgba(203, 207, 213, 1)"
|
||||
},
|
||||
"400": {
|
||||
"value": "rgba(176, 176, 176, 1)"
|
||||
"value": "rgba(170, 176, 186, 1)"
|
||||
},
|
||||
"500": {
|
||||
"value": "rgba(143, 143, 143, 1)"
|
||||
"value": "rgba(131, 140, 154, 1)"
|
||||
},
|
||||
"600": {
|
||||
"value": "rgba(112, 112, 112, 1)"
|
||||
"value": "rgba(103, 111, 123, 1)"
|
||||
},
|
||||
"700": {
|
||||
"value": "rgba(89, 89, 89, 1)"
|
||||
"value": "rgba(82, 89, 98, 1)"
|
||||
},
|
||||
"800": {
|
||||
"value": "rgba(64, 64, 64, 1)"
|
||||
"value": "rgba(66, 71, 79, 1)"
|
||||
},
|
||||
"900": {
|
||||
"value": "rgba(38, 38, 38, 1)"
|
||||
"value": "rgba(35, 38, 42, 1)"
|
||||
}
|
||||
},
|
||||
"grayalpha": {
|
||||
"50": {
|
||||
"value": "rgba(0, 0, 0, 0.03)"
|
||||
"value": "rgba(38, 55, 89, 0.06)"
|
||||
},
|
||||
"100": {
|
||||
"value": "rgba(0, 0, 0, 0.05)"
|
||||
"value": "rgba(18, 43, 68, 0.08)"
|
||||
},
|
||||
"200": {
|
||||
"value": "rgba(0, 0, 0, 0.1)"
|
||||
"value": "rgba(17, 41, 64, 0.13)"
|
||||
},
|
||||
"300": {
|
||||
"value": "rgba(0, 0, 0, 0.19)"
|
||||
"value": "rgba(7, 26, 54, 0.21)"
|
||||
},
|
||||
"400": {
|
||||
"value": "rgba(0, 0, 0, 0.31)"
|
||||
"value": "rgba(5, 23, 51, 0.34)"
|
||||
},
|
||||
"500": {
|
||||
"value": "rgba(0, 0, 0, 0.44)"
|
||||
"value": "rgba(2, 20, 49, 0.49)"
|
||||
},
|
||||
"600": {
|
||||
"value": "rgba(0, 0, 0, 0.56)"
|
||||
"value": "rgba(2, 15, 34, 0.6)"
|
||||
},
|
||||
"700": {
|
||||
"value": "rgba(0, 0, 0, 0.65)"
|
||||
"value": "rgba(1, 11, 24, 0.68)"
|
||||
},
|
||||
"800": {
|
||||
"value": "rgba(0, 0, 0, 0.75)"
|
||||
"value": "rgba(3, 11, 22, 0.75)"
|
||||
},
|
||||
"900": {
|
||||
"value": "rgba(0, 0, 0, 0.85)"
|
||||
"value": "rgba(2, 5, 9, 0.87)"
|
||||
}
|
||||
},
|
||||
"blue": {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
},
|
||||
"action": {
|
||||
"selected": {
|
||||
"value": "{a.deepblue.500.value}"
|
||||
"value": "{a.blue.700.value}"
|
||||
},
|
||||
"on": {
|
||||
"action": {
|
||||
@@ -89,7 +89,7 @@
|
||||
"value": "{a.transparent.value}"
|
||||
},
|
||||
"backdrop": {
|
||||
"value": "{a.grayalpha.700.value}"
|
||||
"value": "{a.grayalpha.500.value}"
|
||||
},
|
||||
"inverted": {
|
||||
"hover": {
|
||||
@@ -115,13 +115,13 @@
|
||||
"value": "{a.blue.600.value}"
|
||||
},
|
||||
"active": {
|
||||
"value": "{a.deepblue.500.value}"
|
||||
"value": "{a.blue.700.value}"
|
||||
},
|
||||
"selected": {
|
||||
"hover": {
|
||||
"value": "{a.deepblue.600.value}"
|
||||
"value": "{a.blue.800.value}"
|
||||
},
|
||||
"@": { "value": "{a.deepblue.500.value}" }
|
||||
"@": { "value": "{a.blue.700.value}" }
|
||||
},
|
||||
"@": {
|
||||
"value": "{a.blue.500.value}"
|
||||
@@ -233,11 +233,11 @@
|
||||
"value": "{a.grayalpha.300.value}"
|
||||
},
|
||||
"subtle": {
|
||||
"hover": { "value": "{a.grayalpha.300.value}" },
|
||||
"@": { "value": "{a.grayalpha.200.value}" }
|
||||
"hover": { "value": "{a.grayalpha.400.value}" },
|
||||
"@": { "value": "{a.grayalpha.300.value}" }
|
||||
},
|
||||
"action": {
|
||||
"selected": { "value": "{a.deepblue.500.value}" },
|
||||
"selected": { "value": "{a.blue.700.value}" },
|
||||
"@": { "value": "{a.blue.500.value}" }
|
||||
},
|
||||
"selected": {
|
||||
@@ -287,7 +287,7 @@
|
||||
"value": "{a.grayalpha.700.value}"
|
||||
},
|
||||
"action": {
|
||||
"selected": { "value": "{a.deepblue.500.value}" },
|
||||
"selected": { "value": "{a.blue.700.value}" },
|
||||
"on": {
|
||||
"action": {
|
||||
"subtle": {
|
||||
@@ -316,7 +316,7 @@
|
||||
"value": "{a.limegreen.700.value}"
|
||||
},
|
||||
"alt3": {
|
||||
"value": "{a.deepblue.400.value}"
|
||||
"value": "{a.deepblue.500.value}"
|
||||
},
|
||||
"on": {
|
||||
"neutral": {
|
||||
|
||||
+301
-110
@@ -1,15 +1,206 @@
|
||||
# Changelog
|
||||
|
||||
## 4.7.4
|
||||
## 5.0.3
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- ConfirmationPanel: Fikset opplesing av feilmelding ([#2199](https://github.com/navikt/aksel/pull/2199))
|
||||
|
||||
### @navikt/ds-tokens
|
||||
|
||||
- ConfirmationPanel: Fikset opplesing av feilmelding ([#2199](https://github.com/navikt/aksel/pull/2199))
|
||||
|
||||
- Tokens: `--a-icon-alt-3` er nå i synk med figma ([#2199](https://github.com/navikt/aksel/pull/2199))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Oppdatert bruk av REM i komponenter for forbedret utrykk ved font-scaling i browser ([#2126](https://github.com/navikt/aksel/pull/2126))
|
||||
- ConfirmationPanel: Fikset opplesing av feilmelding ([#2199](https://github.com/navikt/aksel/pull/2199))
|
||||
|
||||
- Checkbox: Fikset checkbox-alignment ved bruk av tailwindcss ([#2199](https://github.com/navikt/aksel/pull/2199))
|
||||
|
||||
## 5.0.2
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- :label: Modal: Bedre type for `width` ([#2191](https://github.com/navikt/aksel/pull/2191))
|
||||
|
||||
## 5.0.1
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Checkbox: Hotfix da checkmark ikke var sentrert hvis man brukte tailwind ([`90db7dd0c`](https://github.com/navikt/aksel/commit/90db7dd0c120c16a387d3169c05c1f33dc694323))
|
||||
|
||||
## 5.0.0
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Oppdatert Modal - Se [Migrering](https://aksel.nav.no/grunnleggende/kode/migrering) ([#2135](https://github.com/navikt/aksel/pull/2135))
|
||||
|
||||
- :sparkles: Støtte for header og footer
|
||||
- :boom: Bruker nå native dialog i stedet for react-modal (med polyfill for eldre nettlesere)
|
||||
- :boom: Provider: `appElement` er fjernet
|
||||
|
||||
<!---->
|
||||
|
||||
- Table: ExpandableRow har oppdatert knapp for å matche Accordion og ExpansionCard ([#2178](https://github.com/navikt/aksel/pull/2178))
|
||||
|
||||
- Table: Har lagt til ny size: 'large'. ([#2178](https://github.com/navikt/aksel/pull/2178))
|
||||
|
||||
### @navikt/ds-tokens
|
||||
|
||||
- Gray: Gråfarger er nå mindre varme ([#2092](https://github.com/navikt/aksel/pull/2092))
|
||||
|
||||
- Action: Selected-varianter av action er nå 'blue' ('deepblue' før) ([#2092](https://github.com/navikt/aksel/pull/2092))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Oppdatert Modal - Se [Migrering](https://aksel.nav.no/grunnleggende/kode/migrering) ([#2135](https://github.com/navikt/aksel/pull/2135))
|
||||
|
||||
- :sparkles: Støtte for header og footer
|
||||
- :boom: Bruker nå native dialog i stedet for react-modal (med polyfill for eldre nettlesere)
|
||||
|
||||
<!---->
|
||||
|
||||
- Table: ExpandableRow har oppdatert knapp for å matche Accordion og ExpansionCard ([#2178](https://github.com/navikt/aksel/pull/2178))
|
||||
|
||||
- Table: Alle størrelser har justert padding. Small-size table bruker nå også standard typografi-størrelse (18px). ([#2178](https://github.com/navikt/aksel/pull/2178))
|
||||
|
||||
## 4.12.1
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Added red border to Combobox in error state ([#2184](https://github.com/navikt/aksel/pull/2184))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Added red border to Combobox in error state ([#2184](https://github.com/navikt/aksel/pull/2184))
|
||||
|
||||
## 4.12.0
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Combobox: La til støtte for feilmeldinger i Combobox ([#2182](https://github.com/navikt/aksel/pull/2182))
|
||||
|
||||
- CopyButton: Har nå prop 'iconPosition' for å høyre/venstre aligne ikon ([#2173](https://github.com/navikt/aksel/pull/2173))
|
||||
|
||||
<!---->
|
||||
|
||||
- List: Fikset sentrering, margins ([#2168](https://github.com/navikt/aksel/pull/2168))
|
||||
|
||||
- Combobox: Kjører nå 'onChange' + 'onClear' når input blir reset programmatisk ([#2183](https://github.com/navikt/aksel/pull/2183))
|
||||
|
||||
- Combobox: Fikset custom-options i singleselect ([#2180](https://github.com/navikt/aksel/pull/2180))
|
||||
|
||||
- Combobox: Fjernet unødvendige 'onClear'-calls når man velger verdier ([#2170](https://github.com/navikt/aksel/pull/2170))
|
||||
|
||||
- Checkbox: Checkmark er nå SVG-ikon og ikke Base64 ([#2171](https://github.com/navikt/aksel/pull/2171))
|
||||
|
||||
- Combobox: Lukker nå nedtrekksmeny hvis man legger til ny option i singleselect ([#2177](https://github.com/navikt/aksel/pull/2177))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- CopyButton: Har nå prop 'iconPosition' for å høyre/venstre aligne ikon ([#2173](https://github.com/navikt/aksel/pull/2173))
|
||||
|
||||
<!---->
|
||||
|
||||
- List: Fikset sentrering, margins ([#2168](https://github.com/navikt/aksel/pull/2168))
|
||||
|
||||
- Checkbox: Checkmark er nå SVG-ikon og ikke Base64 ([#2171](https://github.com/navikt/aksel/pull/2171))
|
||||
|
||||
- Alert: Fikset alignment av status-ikon mot tekst ([#2179](https://github.com/navikt/aksel/pull/2179))
|
||||
|
||||
## 4.11.2
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- :lipstick: ToggleGroup: fjern semibold fra selected button ([#2167](https://github.com/navikt/aksel/pull/2167))
|
||||
|
||||
## 4.11.1
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Border-radius: Fikset hardkodet border-radius i Datepicker, ToggleGroup og Combobox. ([#2159](https://github.com/navikt/aksel/pull/2159))
|
||||
|
||||
## 4.11.0
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- HGrid: Ny komponent 'HGrid' er ute i Beta: [Dokumentasjon](https://aksel.nav.no/komponenter/core/hgrid) ([#1838](https://github.com/navikt/aksel/pull/1838))
|
||||
|
||||
<!---->
|
||||
|
||||
- Fixes bug where combobox list could not be closed after clicking a chip ([#2155](https://github.com/navikt/aksel/pull/2155))
|
||||
|
||||
- Grid: Markert som deprecated. Bruk nye 'HGrid' ([#2153](https://github.com/navikt/aksel/pull/2153))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- HGrid: Ny komponent 'HGrid' er ute i Beta: [Dokumentasjon](https://aksel.nav.no/komponenter/core/hgrid) ([#1838](https://github.com/navikt/aksel/pull/1838))
|
||||
|
||||
<!---->
|
||||
|
||||
- List: Fikset alignment av ikoner ([#2149](https://github.com/navikt/aksel/pull/2149))
|
||||
|
||||
- Font: La til egen font for semibold italic for bedre skalering cross-browser ([#2150](https://github.com/navikt/aksel/pull/2150))
|
||||
|
||||
## 4.10.2
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Link: Fikset visited farger og inline SVG-reset ved bruk av tailwind
|
||||
|
||||
## 4.10.0
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Link: La til 'variant', 'underline' og 'inlineText'-prop ([#2093](https://github.com/navikt/aksel/pull/2093))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Link: La til 'variant', 'underline' og 'inlineText'-prop ([#2093](https://github.com/navikt/aksel/pull/2093))
|
||||
|
||||
## 4.9.1
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Button: Fikset aria-live bug der knapp alltid ble lest opp av skjermleser ved render ([#2143](https://github.com/navikt/aksel/pull/2143))
|
||||
|
||||
- Tweaks to combobox - updated example, small bugfixes, better affordance for selected options and added flag for "isAddedByUser" to onToggleSelected ([#2144](https://github.com/navikt/aksel/pull/2144))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Tweaks to combobox - updated example, small bugfixes, better affordance for selected options and added flag for "isAddedByUser" to onToggleSelected ([#2144](https://github.com/navikt/aksel/pull/2144))
|
||||
|
||||
## 4.9.0
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Table: ExpandableRow kan nå åpnes med 'expandOnRowClick'-prop ([#2127](https://github.com/navikt/aksel/pull/2127))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Table: ExpandableRow kan nå åpnes med 'expandOnRowClick'-prop ([#2127](https://github.com/navikt/aksel/pull/2127))
|
||||
|
||||
## 4.8.0
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Nye komponenter `VStack`, `HStack` og `Spacer` for å enklere kunne lage layout med flexbox og spacing-variabler. ([#2040](https://github.com/navikt/aksel/pull/2040))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Nye komponenter `VStack`, `HStack` og `Spacer` for å enklere kunne lage layout med flexbox og spacing-variabler. ([#2040](https://github.com/navikt/aksel/pull/2040))
|
||||
|
||||
## 4.7.4
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Textfield: La til type 'time' som tilgjengelig option ([#2137](https://github.com/navikt/aksel/pull/2137))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Oppdatert bruk av REM i komponenter for forbedret utrykk ved font-scaling i browser ([#2126](https://github.com/navikt/aksel/pull/2126))
|
||||
|
||||
## 4.7.3
|
||||
|
||||
### @navikt/ds-react
|
||||
@@ -18,11 +209,11 @@
|
||||
|
||||
## 4.7.2
|
||||
|
||||
### @navikt/ds-css
|
||||
### @navikt/ds-react
|
||||
|
||||
- Stepper: Fikset `hotizontal`-bug når step var `completed` ([#2116](https://github.com/navikt/aksel/pull/2116))
|
||||
|
||||
### @navikt/ds-react
|
||||
### @navikt/ds-css
|
||||
|
||||
- Stepper: Fikset `hotizontal`-bug når step var `completed` ([#2116](https://github.com/navikt/aksel/pull/2116))
|
||||
|
||||
@@ -34,10 +225,6 @@
|
||||
|
||||
## 4.7.0
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Ny komponent Combobox! ([#1868](https://github.com/navikt/aksel/pull/1868))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Ny komponent Combobox! ([#1868](https://github.com/navikt/aksel/pull/1868))
|
||||
@@ -48,28 +235,36 @@
|
||||
|
||||
- Combobox post-release tweaks ([#2112](https://github.com/navikt/aksel/pull/2112))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Ny komponent Combobox! ([#1868](https://github.com/navikt/aksel/pull/1868))
|
||||
|
||||
## 4.6.1
|
||||
|
||||
### @navikt/ds-css
|
||||
### @navikt/ds-react
|
||||
|
||||
- Skjema: Labels og Legends bruker nå inline-flex når readOnly er satt ([#2089](https://github.com/navikt/aksel/pull/2089))
|
||||
|
||||
### @navikt/ds-react
|
||||
### @navikt/ds-css
|
||||
|
||||
- Skjema: Labels og Legends bruker nå inline-flex når readOnly er satt ([#2089](https://github.com/navikt/aksel/pull/2089))
|
||||
|
||||
## 4.6.0
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Skjema: De fleste skjemakomponenter har nå styling for `readOnly`-state ([#2080](https://github.com/navikt/aksel/pull/2080))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Skjema: De fleste skjemakomponenter støtter nå `readOnly`-state ([#2080](https://github.com/navikt/aksel/pull/2080))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Skjema: De fleste skjemakomponenter har nå styling for `readOnly`-state ([#2080](https://github.com/navikt/aksel/pull/2080))
|
||||
|
||||
## 4.5.0
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- - Alert: La til `closeButton`-prop ([#2079](https://github.com/navikt/aksel/pull/2079))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- - Alert: La til `closeButton`-prop ([#2079](https://github.com/navikt/aksel/pull/2079))
|
||||
@@ -78,10 +273,6 @@
|
||||
|
||||
- - Button: Fikset outline-bug i tertiary-variant ved `:active`-state ([#2079](https://github.com/navikt/aksel/pull/2079))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- - Alert: La til `closeButton`-prop ([#2079](https://github.com/navikt/aksel/pull/2079))
|
||||
|
||||
## 4.4.2
|
||||
|
||||
### @navikt/ds-react
|
||||
@@ -96,25 +287,29 @@
|
||||
|
||||
## 4.4.0
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Fikset klassenavn brukt for popover i Datepicker og Monthpicker ([PR](https://github.com/navikt/aksel/pull/2041))
|
||||
|
||||
### @navikt/ds-tokens
|
||||
|
||||
- Shadow-tokens er oppdatert til mer tydeligere varianter ([PR](https://github.com/navikt/aksel/pull/2041))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Shadow-tokens er oppdatert: Datepicker har nå ikke border, Modal bruker shadow-xlarge, LinkCard bruker shadow-xsmall ([PR](https://github.com/navikt/aksel/pull/2041))
|
||||
|
||||
### @navikt/ds-tailwind
|
||||
|
||||
- Shadow-tokens er oppdatert ([PR](https://github.com/navikt/aksel/pull/2041))
|
||||
|
||||
- Oppdatert text-subtle og icon-subtle tokens til 700-skala (tidligere 600). ([PR](https://github.com/navikt/aksel/pull/2036))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Shadow-tokens er oppdatert: Datepicker har nå ikke border, Modal bruker shadow-xlarge, LinkCard bruker shadow-xsmall ([PR](https://github.com/navikt/aksel/pull/2041))
|
||||
## 4.3.0
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Fikset klassenavn brukt for popover i Datepicker og Monthpicker ([PR](https://github.com/navikt/aksel/pull/2041))
|
||||
|
||||
## 4.3.0
|
||||
- Popover og Helptext har nå luft mot siden av skjerm på mindre flater ([PR](https://github.com/navikt/aksel/pull/2069))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
@@ -124,20 +319,16 @@
|
||||
|
||||
- ExpansionCard: Ved nesting av komponetene fikk man styling fra parent ([PR](https://github.com/navikt/aksel/pull/2067))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Popover og Helptext har nå luft mot siden av skjerm på mindre flater ([PR](https://github.com/navikt/aksel/pull/2069))
|
||||
|
||||
## 4.2.0
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Chat: `small`-size, innebygde varianter for farge og oppdatert utseende. ([PR](https://github.com/navikt/aksel/pull/2048))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Oppdatert Chat: `size` og `variant`-prop, optional `avatar`, uu og ui-forbedringer ([PR](https://github.com/navikt/aksel/pull/2048))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Chat: `small`-size, innebygde varianter for farge og oppdatert utseende. ([PR](https://github.com/navikt/aksel/pull/2048))
|
||||
|
||||
## 4.1.7
|
||||
|
||||
### @navikt/ds-react
|
||||
@@ -154,16 +345,16 @@
|
||||
|
||||
## 4.1.5
|
||||
|
||||
### @navikt/aksel-icons
|
||||
|
||||
- Nye ikoner `ChevronRightLast` og `ChevronLeftLast` ([PR](https://github.com/navikt/aksel/pull/2054))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- `OverridableComponent` fungerer nå med komponenter som allerede bruker 'as'-prop. ([PR](https://github.com/navikt/aksel/pull/2051))
|
||||
|
||||
- Popover: `bubbleEscape`-prop tilbyr muligheten for escape-events til å sendes opp gjennom dom-treet. ([PR](https://github.com/navikt/aksel/pull/2052))
|
||||
|
||||
### @navikt/aksel-icons
|
||||
|
||||
- Nye ikoner `ChevronRightLast` og `ChevronLeftLast` ([PR](https://github.com/navikt/aksel/pull/2054))
|
||||
|
||||
## 4.1.4
|
||||
|
||||
### @navikt/ds-css
|
||||
@@ -194,22 +385,6 @@
|
||||
|
||||
- Deprecated klassenavn `navds-chips--icon-left` ([PR](https://github.com/navikt/aksel/pull/2035))
|
||||
|
||||
### @navikt/ds-tokens
|
||||
|
||||
- :tada: Fargetokens for datavisualisering. ([PR](https://github.com/navikt/aksel/pull/2032))
|
||||
|
||||
- Oppdatert text-subtle og icon-subtle tokens til 700-skala (tidligere 600). ([PR](https://github.com/navikt/aksel/pull/2036))
|
||||
|
||||
### @navikt/ds-tailwind
|
||||
|
||||
- :tada: Fargetokens for datavisualisering. ([PR](https://github.com/navikt/aksel/pull/2032))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Chips: `neutral` og `action`-varianter av Chips.Toggle. Opt-out mulighet for Checkmark. Oppdatert checkmark-ikon ([PR](https://github.com/navikt/aksel/pull/2035))
|
||||
|
||||
- Alle description-felter på fieldsets har nå `text-subtle` som farge. ([PR](https://github.com/navikt/aksel/pull/2036))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Chips: `neutral` og `action`-varianter for Chips.Toggle. `checkmark`-prop for Chips.Toggle ([PR](https://github.com/navikt/aksel/pull/2035))
|
||||
@@ -222,13 +397,29 @@
|
||||
|
||||
- Accordion: La til `indent`-prop ([PR](https://github.com/navikt/aksel/pull/2027))
|
||||
|
||||
### @navikt/ds-tokens
|
||||
|
||||
- :tada: Fargetokens for datavisualisering. ([PR](https://github.com/navikt/aksel/pull/2032))
|
||||
|
||||
- Oppdatert text-subtle og icon-subtle tokens til 700-skala (tidligere 600). ([PR](https://github.com/navikt/aksel/pull/2036))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Chips: `neutral` og `action`-varianter av Chips.Toggle. Opt-out mulighet for Checkmark. Oppdatert checkmark-ikon ([PR](https://github.com/navikt/aksel/pull/2035))
|
||||
|
||||
- Alle description-felter på fieldsets har nå `text-subtle` som farge. ([PR](https://github.com/navikt/aksel/pull/2036))
|
||||
|
||||
### @navikt/ds-tailwind
|
||||
|
||||
- :tada: Fargetokens for datavisualisering. ([PR](https://github.com/navikt/aksel/pull/2032))
|
||||
|
||||
## 4.0.0
|
||||
|
||||
### @navikt/aksel
|
||||
### @navikt/ds-react
|
||||
|
||||
- Codemod for migrering av Datepicker/Monthpicker ut av Beta. `npx @navikt/aksel codemod v4-date` ([PR](https://github.com/navikt/aksel/pull/2026))
|
||||
- Datepicker og Monthpicker er ute av beta. [Guide](https://aksel.nav.no/grunnleggende/kode/migrering#h4ef68ae743b0) ([PR](https://github.com/navikt/aksel/pull/2026))
|
||||
|
||||
- Codemods for migrering fra `@navikt/ds-react-internal` til `@navikt/ds-react` [Guide](https://aksel.nav.no/grunnleggende/kode/migrering#h3b8538e1981d) ([PR](https://github.com/navikt/aksel/pull/2026))
|
||||
- Komponenter i `@navikt/ds-react-internal` er flyttet over til `@navikt/ds-react`. [Guide](https://aksel.nav.no/grunnleggende/kode/migrering#h3b8538e1981d) ([PR](https://github.com/navikt/aksel/pull/2026))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
@@ -240,11 +431,11 @@
|
||||
|
||||
- Styling for flyttede komponenter finnes nå på CDN [Guide](https://aksel.nav.no/grunnleggende/kode/css-import)
|
||||
|
||||
### @navikt/ds-react
|
||||
### @navikt/aksel
|
||||
|
||||
- Datepicker og Monthpicker er ute av beta. [Guide](https://aksel.nav.no/grunnleggende/kode/migrering#h4ef68ae743b0) ([PR](https://github.com/navikt/aksel/pull/2026))
|
||||
- Codemod for migrering av Datepicker/Monthpicker ut av Beta. `npx @navikt/aksel codemod v4-date` ([PR](https://github.com/navikt/aksel/pull/2026))
|
||||
|
||||
- Komponenter i `@navikt/ds-react-internal` er flyttet over til `@navikt/ds-react`. [Guide](https://aksel.nav.no/grunnleggende/kode/migrering#h3b8538e1981d) ([PR](https://github.com/navikt/aksel/pull/2026))
|
||||
- Codemods for migrering fra `@navikt/ds-react-internal` til `@navikt/ds-react` [Guide](https://aksel.nav.no/grunnleggende/kode/migrering#h3b8538e1981d) ([PR](https://github.com/navikt/aksel/pull/2026))
|
||||
|
||||
## 3.4.2
|
||||
|
||||
@@ -264,11 +455,11 @@
|
||||
|
||||
- Stylelint-pakke for Aksel ([PR](https://github.com/navikt/aksel/pull/1973))
|
||||
|
||||
### @navikt/ds-css
|
||||
### @navikt/ds-react
|
||||
|
||||
- Tag: `moderate`-variant ([PR](https://github.com/navikt/aksel/pull/2010))
|
||||
|
||||
### @navikt/ds-react
|
||||
### @navikt/ds-css
|
||||
|
||||
- Tag: `moderate`-variant ([PR](https://github.com/navikt/aksel/pull/2010))
|
||||
|
||||
@@ -292,11 +483,9 @@
|
||||
|
||||
## 3.2.3
|
||||
|
||||
### @navikt/ds-css
|
||||
### @navikt/aksel-icons
|
||||
|
||||
- Datepicker: Fikset small-variant av datepicker-input. ([PR](https://github.com/navikt/aksel/pull/1993))
|
||||
|
||||
- CopyButton: `xsmall`-size for bruk i tabeller ([PR](https://github.com/navikt/aksel/pull/1994))
|
||||
- Nye ikoner `FileParagraph` og `FilePlus` ([PR](https://github.com/navikt/aksel/pull/1998))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
@@ -304,9 +493,11 @@
|
||||
|
||||
- CopyButton: `xsmall`-variant for bruk i tabeller ([PR](https://github.com/navikt/aksel/pull/1994))
|
||||
|
||||
### @navikt/aksel-icons
|
||||
### @navikt/ds-css
|
||||
|
||||
- Nye ikoner `FileParagraph` og `FilePlus` ([PR](https://github.com/navikt/aksel/pull/1998))
|
||||
- Datepicker: Fikset small-variant av datepicker-input. ([PR](https://github.com/navikt/aksel/pull/1993))
|
||||
|
||||
- CopyButton: `xsmall`-size for bruk i tabeller ([PR](https://github.com/navikt/aksel/pull/1994))
|
||||
|
||||
## 3.2.2
|
||||
|
||||
@@ -328,6 +519,10 @@
|
||||
|
||||
## 3.1.0
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Oppdatert Label og Description spacing for alle skjemakomponenter ([PR](https://github.com/navikt/aksel/pull/1967))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Alle komponenter bruker nå default `:focus-visible` for fokusmarkering. Medfølger også fallback for `:focus` ([PR](https://github.com/navikt/aksel/pull/1966))
|
||||
@@ -336,10 +531,6 @@
|
||||
|
||||
- Oppdatert Label og Description spacing for skjemakomponenter ([PR](https://github.com/navikt/aksel/pull/1967))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Oppdatert Label og Description spacing for alle skjemakomponenter ([PR](https://github.com/navikt/aksel/pull/1967))
|
||||
|
||||
## 3.0.1
|
||||
|
||||
### @navikt/ds-react
|
||||
@@ -350,16 +541,16 @@
|
||||
|
||||
## 3.0.0
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Accordion: Chevron er left-aligned, deler av styling er refaktorert, `size`-props og `neutral`-variant ([PR](https://github.com/navikt/aksel/pull/1964))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Accordion: left-aligner chevron, `neutral`-variant, `size`-prop ([PR](https://github.com/navikt/aksel/pull/1964))
|
||||
|
||||
- Bruker nå nå `@navikt/aksel-icons` for interne ikoner ([PR](https://github.com/navikt/aksel/pull/1964))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Accordion: Chevron er left-aligned, deler av styling er refaktorert, `size`-props og `neutral`-variant ([PR](https://github.com/navikt/aksel/pull/1964))
|
||||
|
||||
## 2.9.8
|
||||
|
||||
### @navikt/aksel-icons
|
||||
@@ -454,14 +645,14 @@
|
||||
|
||||
## 2.8.7
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Button: token for tertiary
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Fikset typografi-bruk for `Radio` og `Checkbox`.
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Button: token for tertiary
|
||||
|
||||
## 2.8.6
|
||||
|
||||
### @navikt/ds-css
|
||||
@@ -490,14 +681,14 @@
|
||||
|
||||
## 2.8.2
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- ExpansioCard: Oppdatert typografi ([PR](https://github.com/navikt/aksel/pull/1870))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- ExpansioCard: Oppdatert typografibruk ([PR](https://github.com/navikt/aksel/pull/1870))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- ExpansioCard: Oppdatert typografi ([PR](https://github.com/navikt/aksel/pull/1870))
|
||||
|
||||
## 2.8.1
|
||||
|
||||
### @navikt/ds-react
|
||||
@@ -576,14 +767,14 @@
|
||||
|
||||
## 2.4.2
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- List: La til støtte for nesting ([PR](https://github.com/navikt/aksel/pull/1823))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- List: la til støtte for nestede lister ([PR](https://github.com/navikt/aksel/pull/1823))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- List: La til støtte for nesting ([PR](https://github.com/navikt/aksel/pull/1823))
|
||||
|
||||
## 2.4.1
|
||||
|
||||
### @navikt/ds-react
|
||||
@@ -614,6 +805,12 @@
|
||||
|
||||
## 2.2.0
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- ToggleGroup: `neutral`-variant ([PR](https://github.com/navikt/aksel/pull/1789))
|
||||
|
||||
- Button: `neutral`-variant ([PR](https://github.com/navikt/aksel/pull/1789))
|
||||
|
||||
### @navikt/ds-tokens
|
||||
|
||||
- Justeringer av semantiske fargetokens, statusfarger nå mer tydelig ([PR](https://github.com/navikt/aksel/pull/1787))
|
||||
@@ -626,12 +823,6 @@
|
||||
|
||||
- Button: `Neutral`-variant.
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- ToggleGroup: `neutral`-variant ([PR](https://github.com/navikt/aksel/pull/1789))
|
||||
|
||||
- Button: `neutral`-variant ([PR](https://github.com/navikt/aksel/pull/1789))
|
||||
|
||||
## 2.1.7
|
||||
|
||||
### @navikt/ds-react
|
||||
@@ -676,14 +867,14 @@
|
||||
|
||||
## 2.1.0
|
||||
|
||||
### @navikt/ds-tokens
|
||||
|
||||
- Fikset feil danger-hover token ([PR](https://github.com/navikt/aksel/pull/1665))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Datepicker/Monthpicker: år med 2 siffer i input fungerer nå
|
||||
|
||||
### @navikt/ds-tokens
|
||||
|
||||
- Fikset feil danger-hover token ([PR](https://github.com/navikt/aksel/pull/1665))
|
||||
|
||||
## 2.0.18
|
||||
|
||||
### @navikt/ds-react
|
||||
@@ -746,23 +937,23 @@
|
||||
|
||||
## 2.0.0
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Fonter blir bruk i all typografi blir nå lastet fra CDN
|
||||
|
||||
### @navikt/ds-tokens
|
||||
|
||||
- Alle token er oppdatert til nytt format. ([Guide](https://aksel.nav.no/grunnleggende/kode/migrering#h76f47744d112))
|
||||
|
||||
### @navikt/ds-tailwind
|
||||
|
||||
- Alle token er oppdatert til nytt format. ([Guide](https://aksel.nav.no/grunnleggende/kode/migrering#h76f47744d112))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Fontlasting: Fonter lastes nå fra NAV-CDN
|
||||
|
||||
- Tokens: Alle komponenter bruker nå semantiske tokens for som standard, med innebygd støtte for komponent-spesifikke tokens.
|
||||
|
||||
### @navikt/ds-react
|
||||
### @navikt/ds-tailwind
|
||||
|
||||
- Fonter blir bruk i all typografi blir nå lastet fra CDN
|
||||
- Alle token er oppdatert til nytt format. ([Guide](https://aksel.nav.no/grunnleggende/kode/migrering#h76f47744d112))
|
||||
|
||||
## 1.5.10
|
||||
|
||||
@@ -808,16 +999,16 @@
|
||||
|
||||
## 1.5.0
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Tag: `filled`-varianter ([PR](https://github.com/navikt/aksel/pull/1684))
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- Tag: `filles`-varianter ([PR](https://github.com/navikt/aksel/pull/1684))
|
||||
|
||||
- Ny komponent Chips! ([PR](https://github.com/navikt/aksel/pull/1668))
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Tag: `filled`-varianter ([PR](https://github.com/navikt/aksel/pull/1684))
|
||||
|
||||
## 1.4.4
|
||||
|
||||
### @navikt/ds-react
|
||||
@@ -844,10 +1035,6 @@
|
||||
|
||||
## 1.3.39
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Fikset bruk av `:watch` for diverse parsere (less, parcel)
|
||||
|
||||
### @navikt/ds-react
|
||||
|
||||
- useId-bug fikset ved bruk i både react 17 og 18
|
||||
@@ -870,6 +1057,10 @@
|
||||
|
||||
- Stepper: `interactive` og `completed`-props for wizard-løsninger
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
- Fikset bruk av `:watch` for diverse parsere (less, parcel)
|
||||
|
||||
## 1.3.24
|
||||
|
||||
### @navikt/ds-css
|
||||
|
||||
Loaded 100 of 192 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user