Compare commits

...
Author SHA1 Message Date
github-actions[bot]andgithub-actions[bot] bfee5a822e Version Packages (#2192)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-08-18 13:09:28 +02:00
Halvor Haugan 58ed3b5367 🏷️ Modal: Bedre type for width (#2191) 2023-08-18 12:51:56 +02:00
github-actions[bot]andgithub-actions[bot] 00b3dd7d59 Version Packages (#2190)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-08-18 12:20:30 +02:00
Ken 90db7dd0c1 📝 changeset 2023-08-18 12:16:01 +02:00
Ken b6d0304d5a Merge branch 'main' of https://github.com/navikt/aksel 2023-08-18 12:00:15 +02:00
Ken de0a797e09 ⚰️ Fjernet duplikat OG-bilde 2023-08-18 11:59:54 +02:00
github-actions[bot]andgithub-actions[bot] c4eaf0f350 Version Packages (#2186)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-08-18 11:41:26 +02:00
Ken fb9897e6a0 🐛 Fikset fokusrekkefølge på søk 2023-08-18 09:25:49 +02:00
Halvor HauganandKen 3438b5d7e7 ✨ Modal: støtte for header og footer (#2135)
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
2023-08-17 16:40:52 +02:00
Ken 3583062824 🎨 Oppdatert Action og Gray (#2092) 2023-08-17 16:14:42 +02:00
Ken 2993f809ad La til large-size for Table, oppdatert expand-knapp (#2178) 2023-08-17 16:14:24 +02:00
77 changed files with 2271 additions and 730 deletions

No files matched your search

+6
View File
@@ -1,5 +1,11 @@
# @navikt/aksel-icons
## 5.0.2
## 5.0.1
## 5.0.0
## 4.12.1
## 4.12.0
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/aksel-icons",
"version": "4.12.1",
"version": "5.0.2",
"author": "Aksel | NAV",
"homepage": "https://aksel.nav.no/ikoner",
"repository": {
+6
View File
@@ -1,5 +1,11 @@
# @navikt/aksel-stylelint
## 5.0.2
## 5.0.1
## 5.0.0
## 4.12.1
## 4.12.0
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/aksel-stylelint",
"version": "4.12.1",
"version": "5.0.2",
"author": "Aksel | NAV",
"homepage": "https://aksel.nav.no/grunnleggende/kode/stylelint",
"repository": {
@@ -35,8 +35,8 @@
"dev": "yarn watch:lint"
},
"devDependencies": {
"@navikt/ds-css": "^4.12.1",
"@navikt/ds-tokens": "^4.12.1",
"@navikt/ds-css": "^5.0.2",
"@navikt/ds-tokens": "^5.0.2",
"@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",
},
];
+21
View File
@@ -1,5 +1,26 @@
# @navikt/aksel
## 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
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/aksel",
"version": "4.12.1",
"version": "5.0.2",
"description": "Aksel command line interface. Handles css-imports, codemods and more",
"author": "Aksel | NAV designsystem team",
"license": "MIT",
@@ -29,7 +29,7 @@
},
"homepage": "https://aksel.nav.no/grunnleggende/kode/kommandolinje",
"dependencies": {
"@navikt/ds-css": "4.12.1",
"@navikt/ds-css": "5.0.2",
"axios": "1.3.6",
"chalk": "4.1.0",
"clipboardy": "^2.3.0",
+23
View File
@@ -1,5 +1,28 @@
# @navikt/ds-css
## 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
+1 -1
View File
@@ -121,7 +121,7 @@ const StyleMappings = {
{
component: "Modal",
main: "modal.css",
dependencies: ["button.css"],
dependencies: [typoCss, "button.css"],
},
{
component: "Pagination",
+5
View File
@@ -135,6 +135,11 @@
height: 1.25rem;
}
/* Tailwind sets all svg to block */
.navds-checkbox__icon > svg {
display: inline;
}
.navds-checkbox--small > .navds-checkbox__input:checked + .navds-checkbox__label::before {
background-position: 0.25rem center;
}
+127 -44
View File
@@ -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;
}
}
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-css",
"version": "4.12.1",
"version": "5.0.2",
"description": "CSS for NAV Designsystem",
"author": "Aksel | NAV designsystem team",
"keywords": [
@@ -27,7 +27,7 @@
"css:get-version": "node config/get-version.js"
},
"devDependencies": {
"@navikt/ds-tokens": "^4.12.1",
"@navikt/ds-tokens": "^5.0.2",
"cssnano": "6.0.0",
"fast-glob": "3.2.11",
"lodash": "4.17.21",
+58 -35
View File
@@ -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,7 +159,7 @@
transition: border-bottom-color 190ms cubic-bezier(0.6, 0.04, 0.98, 0.335);
}
.navds-table__expandable-row:not(.navds-table__expandable-row--expansion-disabled):hover {
.navds-table__expandable-row.navds-table__expandable-row--clickable:not(.navds-table__expandable-row--expansion-disabled):hover {
cursor: pointer;
}
@@ -171,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 {
@@ -235,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);
}
+4 -1
View File
@@ -196,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",
+35
View File
@@ -1,5 +1,40 @@
# @navikt/ds-react
## 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
+3 -5
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-react",
"version": "4.12.1",
"version": "5.0.2",
"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.12.1",
"@navikt/aksel-icons": "^5.0.2",
"@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",
@@ -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]
);
+172 -122
View File
@@ -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;
};
+3 -1
View File
@@ -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";
+148 -65
View File
@@ -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>
);
+6 -2
View File
@@ -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,
+1 -6
View File
@@ -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 -5
View File
@@ -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}
@@ -96,6 +96,7 @@ export const ExpandableRow: ExpandableRowType = forwardRef(
"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);
@@ -110,8 +111,8 @@ export const ExpandableRow: ExpandableRowType = forwardRef(
>
{!expansionDisabled && (
<button
type="button"
className="navds-table__toggle-expand-button"
type="button"
aria-controls={id}
aria-expanded={isOpen}
onClick={expansionHandler}
+1 -5
View File
@@ -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}
>
+3 -4
View File
@@ -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}
@@ -36,8 +36,44 @@ 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 = () => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const [open, setOpen] = useState(false);
return (
<Table size="small">
@@ -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 = () => {
+6
View File
@@ -1,5 +1,11 @@
# @navikt/ds-tailwind
## 5.0.2
## 5.0.1
## 5.0.0
## 4.12.1
## 4.12.0
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-tailwind",
"version": "4.12.1",
"version": "5.0.2",
"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.12.1",
"@navikt/ds-tokens": "^5.0.2",
"@types/jest": "^29.0.0",
"color": "4.2.3",
"jest": "^29.0.0",
+12
View File
@@ -1,5 +1,17 @@
# @navikt/ds-tokens
## 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
+60 -57
View File
@@ -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,9 +383,9 @@
{ "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)" },
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-tokens",
"version": "4.12.1",
"version": "5.0.2",
"description": "Design-tokens for NAV designsystem",
"author": "Aksel | NAV designsystem team",
"keywords": [
+20 -20
View File
@@ -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": {
+9 -9
View File
@@ -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": {
+47
View File
@@ -1,5 +1,52 @@
# Changelog
## 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-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))
### @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))
## 4.12.1
### @navikt/ds-css
+5 -5
View File
@@ -15,11 +15,11 @@
"website"
],
"dependencies": {
"@navikt/aksel-icons": "^4.12.1",
"@navikt/ds-css": "^4.12.1",
"@navikt/ds-react": "^4.12.1",
"@navikt/ds-tailwind": "^4.12.1",
"@navikt/ds-tokens": "^4.12.1"
"@navikt/aksel-icons": "^5.0.2",
"@navikt/ds-css": "^5.0.2",
"@navikt/ds-react": "^5.0.2",
"@navikt/ds-tailwind": "^5.0.2",
"@navikt/ds-tokens": "^5.0.2"
},
"devDependencies": {
"prettier-plugin-tailwindcss": "^0.2.3"
@@ -1,13 +1,11 @@
import { MenuHamburgerIcon, XMarkIcon } from "@navikt/aksel-icons";
import { MenuHamburgerIcon } from "@navikt/aksel-icons";
import { Button } from "@navikt/ds-react";
import { Modal } from "@navikt/ds-react";
import cl from "clsx";
import Link from "next/link";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import ReactModal from "react-modal";
import { useState } from "react";
import { logNav } from "../..";
import styles from "./header.module.css";
const LinkElement = ({ name, href, onClick }) => {
const { asPath } = useRouter();
@@ -41,14 +39,9 @@ const LinkElement = ({ name, href, onClick }) => {
export const Hamburger = () => {
const [open, setOpen] = useState(false);
useEffect(() => {
ReactModal.setAppElement("#__next");
}, []);
return (
<>
<div className="z-[1050] mr-0 grid h-full place-content-center">
<div className="mr-2 grid h-full place-content-center lg:hidden">
<button
onClick={() => setOpen(!open)}
aria-expanded={open}
@@ -60,25 +53,16 @@ export const Hamburger = () => {
aria-label="Åpne meny"
/>
</button>
<ReactModal
isOpen={open}
onRequestClose={() => setOpen(false)}
aria={{ modal: true }}
overlayClassName={styles.modalOverlay}
contentLabel="Meny"
className="bg-surface-default shadow-xlarge absolute left-4 right-4 top-0 block rounded px-11 py-14 sm:left-auto sm:right-6 sm:w-96 sm:max-w-[90%] lg:hidden"
>
</div>
<Modal
open={open}
onClose={() => setOpen(false)}
header={{ heading: "Meny" }}
width="small"
>
<Modal.Body>
<nav aria-label="hovedmeny">
<ul>
<Button
variant="tertiary"
onClick={() => setOpen(false)}
className={cl(
styles.akselTertiaryButton,
"absolute right-2 top-2"
)}
icon={<XMarkIcon title="lukk meny" fontSize="1.5rem" />}
/>
<LinkElement
onClick={() => setOpen(false)}
name="God praksis"
@@ -106,8 +90,8 @@ export const Hamburger = () => {
/>
</ul>
</nav>
</ReactModal>
</div>
</Modal.Body>
</Modal>
</>
);
};
@@ -114,9 +114,8 @@ export const Header = ({
<div className="z-[1050] ml-auto mr-4 flex justify-center lg:ml-0 lg:mr-0">
<GlobalSearch />
</div>
<div className="mr-2 h-full lg:hidden">
<Hamburger />
</div>
<Hamburger />
</div>
</header>
</>
@@ -1,14 +1,4 @@
/* stylelint-disable aksel/design-token-no-global-override */
.modalOverlay {
z-index: var(--a-z-index-modal);
@apply bg-surface-backdrop fixed inset-0 grid place-items-center lg:hidden;
}
.modalOverlaySearch {
z-index: 3000;
@apply bg-deepblue-900/20 fixed inset-0 grid place-items-center;
}
.akselTertiaryButton {
--a-text-action: var(--a-deepblue-800);
--a-text-action-on-action-subtle: var(--a-deepblue-800);
@@ -50,13 +50,7 @@ export const SearchForm = () => {
onSubmit={(e) => e.preventDefault()}
className="bg-surface-default grid w-full gap-2"
>
<div className="flex flex-row-reverse items-center gap-1 p-1">
<Button
variant="tertiary-neutral"
icon={<XMarkIcon title="Lukk søk" />}
onClick={close}
type="button"
/>
<div className="flex items-center gap-1 p-1">
<Search
label={
<span className="flex items-center gap-2">
@@ -83,6 +77,15 @@ export const SearchForm = () => {
placeholder="Søk gjennom hele aksel"
className="border-none pr-0 focus:shadow-none"
/>
<div className="grid h-full w-12 place-content-center">
<Button
variant="tertiary-neutral"
icon={<XMarkIcon title="Lukk søk" />}
onClick={close}
type="button"
size="small"
/>
</div>
</div>
{chipsToShow.length !== 0 && (
<Chips className="px-4 pb-4 md:px-10">
@@ -1,9 +1,6 @@
import { MagnifyingGlassIcon } from "@navikt/aksel-icons";
import { Button } from "@navikt/ds-react";
import cl from "clsx";
import { useContext, useEffect } from "react";
import ReactModal from "react-modal";
import styles from "../header.module.css";
import { Button, Modal } from "@navikt/ds-react";
import { useContext } from "react";
import { Error } from "./Error";
import { SearchForm } from "./Form";
import { Results } from "./Results";
@@ -13,10 +10,6 @@ export const Search = () => {
const { open, setOpen } = useContext(SearchContext);
const { close } = useContext(SearchNavigationContext);
useEffect(() => {
ReactModal.setAppElement("#__next");
}, []);
return (
<>
<Button
@@ -35,19 +28,17 @@ export const Search = () => {
>
Søk
</Button>
<ReactModal
isOpen={open}
onRequestClose={close}
aria={{ modal: true }}
contentLabel="Søk"
className="bg-surface-default focus-visible:shadow-focus animate-searchModal shadow-xlarge absolute inset-0 mx-auto my-auto flex h-[90%] max-h-[52rem] w-full max-w-[90%] flex-col overflow-x-hidden rounded-lg focus:outline-none md:h-[80%] lg:max-w-4xl"
overlayClassName={cl(styles.modalOverlaySearch, "backdrop-blur-[2px]")}
htmlOpenClassName="overflow-hidden"
<Modal
open={open}
onClose={close}
aria-label="Søk"
className="h-[90%] max-h-[52rem] "
width="medium"
>
<Error />
<SearchForm />
<Results />
</ReactModal>
</Modal>
</>
);
};
@@ -9,8 +9,7 @@ import { SuggestionBlock } from "components/website-modules/suggestionblock";
import Fuse from "fuse.js";
import Head from "next/head";
import Link from "next/link";
import { useRouter } from "next/router";
import { useEffect, useMemo, useRef, useState } from "react";
import { useMemo, useRef, useState } from "react";
import { categorizeIcons, getFillIcon } from "./utils";
import { IconSidebar } from "./Sidebar";
import { TitleLinks } from "./TitleLinks";
@@ -70,12 +69,6 @@ export const IconPage = ({ name }: { name: string }) => {
);
}, [toggle, query, strokeIcons, fillIcons]);
const router = useRouter();
useEffect(() => {
hideModal && Modal.setAppElement("#__next");
}, [hideModal]);
const subCategoryWordLength = Math.max(
...categories.map((cat) =>
Math.max(...cat.sub_categories.map((sub) => sub.sub_category.length))
@@ -260,15 +253,8 @@ export const IconPage = ({ name }: { name: string }) => {
{!hideModal && (
<Modal
open={!!name}
onClose={() =>
router.push(`/ikoner#${name}`, undefined, {
shallow: true,
})
}
closeButton={false}
aria-modal
aria-label={`${name} ikon`}
className="bg-surface-default focus-visible:shadow-focus z-modal absolute block h-full overflow-y-auto rounded px-2 py-6 focus:outline-none sm:px-6"
className="rounded px-2 py-6 sm:px-6"
>
{name && <IconSidebar name={name} focusRef={focusRef} />}
</Modal>
@@ -1,5 +1,5 @@
export const getImage = (key: string, variant: "OG" | "thumbnail") => {
const bloggOptions = 11;
const bloggOptions = 9;
const hash = Math.abs(
key.split("").reduce(function (a, b) {
-1
View File
@@ -54,7 +54,6 @@
"prismjs": "^1.27.0",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-modal": "3.15.1",
"react-responsive-masonry": "^2.1.7",
"sanity": "3.15.1",
"sanity-plugin-asset-source-unsplash": "^1.1.0",
@@ -1,44 +1,25 @@
import {
DatePicker,
useDatepicker,
Button,
Modal,
Heading,
} from "@navikt/ds-react";
import { DatePicker, useDatepicker, Button, Modal } from "@navikt/ds-react";
import { withDsExample } from "components/website-modules/examples/withDsExample";
import { useEffect, useState } from "react";
import { useRef } from "react";
import format from "date-fns/format";
import nbLocale from "date-fns/locale/nb";
const Example = () => {
const [open, setOpen] = useState(false);
const ref = useRef<HTMLDialogElement>(null);
const { datepickerProps, inputProps, selectedDay } = useDatepicker({
fromDate: new Date("Aug 23 2019"),
onDateChange: console.log,
});
useEffect(() => {
Modal.setAppElement("#__next");
}, []);
return (
<>
<Button onClick={() => setOpen(true)}>Åpne modal</Button>
<Button onClick={() => ref.current?.showModal()}>Åpne modal</Button>
<Modal
open={open}
aria-label="Modal demo"
onClose={() => setOpen((x) => !x)}
shouldCloseOnEsc={!datepickerProps.open}
aria-labelledby="modal-heading"
>
<Modal.Content className="min-w-96 max-w-full">
<Heading spacing level="1" size="large" id="modal-heading">
Heading
</Heading>
<div className="min-h-96">
<DatePicker {...datepickerProps} strategy="fixed">
<Modal ref={ref} header={{ heading: "Heading" }}>
<Modal.Body>
<div className="min-h-96 min-w-96 max-w-full">
<DatePicker {...datepickerProps}>
<DatePicker.Input {...inputProps} label="Velg dato" />
</DatePicker>
<div className="pt-4">
@@ -46,7 +27,7 @@ const Example = () => {
format(selectedDay, "dd.MM.yyyy", { locale: nbLocale })}
</div>
</div>
</Modal.Content>
</Modal.Body>
</Modal>
</>
);
@@ -61,5 +42,4 @@ export const Demo = {
export const args = {
index: 11,
desc: "Ved bruk av datepicker i Modal er det viktig at 'Escape' ikke lukker selve modalen hvis datepicker er åpen. Bruk 'shouldCloseOnEsc' for å toggle dette når datepicker er åpen.",
};
@@ -0,0 +1,47 @@
import { BodyLong, Button, Heading, Modal } from "@navikt/ds-react";
import { withDsExample } from "components/website-modules/examples/withDsExample";
import { useState } from "react";
const Example = () => {
const [open, setOpen] = useState(false);
return (
<>
<Button onClick={() => setOpen(true)}>Åpne modal</Button>
<Modal
open={open}
onClose={() => setOpen(false)}
aria-labelledby="modal-heading"
>
<Modal.Header>
<Heading level="1" size="large" id="modal-heading">
Laborum proident id ullamco
</Heading>
</Modal.Header>
<Modal.Body>
<BodyLong>
Culpa aliquip ut cupidatat laborum minim quis ex in aliqua. Qui
incididunt dolor do ad ut. Incididunt eiusmod nostrud deserunt duis
laborum. Proident aute culpa qui nostrud velit adipisicing minim.
Consequat aliqua aute dolor do sit Lorem nisi mollit velit. Aliqua
exercitation non minim minim pariatur sunt laborum ipsum.
Exercitation nostrud est laborum magna non non aliqua qui esse.
</BodyLong>
</Modal.Body>
</Modal>
</>
);
};
export default withDsExample(Example);
/* Storybook story */
export const Demo = {
render: Example,
};
export const args = {
index: 3,
desc: "Headeren tilbys som en egen komponent for de tilfellene du trenger mer fleksiblitet enn header-propen gir deg. Begge varianter har mulighet for å skjule lukkeknappen.",
};
@@ -1,31 +1,17 @@
import { BodyLong, Button, Heading, Modal } from "@navikt/ds-react";
import { BodyLong, Button, Modal } from "@navikt/ds-react";
import { withDsExample } from "components/website-modules/examples/withDsExample";
import { useEffect, useState } from "react";
import { useRef } from "react";
const Example = () => {
const [open, setOpen] = useState(false);
useEffect(() => {
Modal.setAppElement("#__next");
}, []);
const ref = useRef<HTMLDialogElement>(null);
return (
<>
<Button onClick={() => setOpen(true)}>Åpne modal</Button>
<Modal
open={open}
aria-label="Modal demo"
onClose={() => setOpen((x) => !x)}
aria-labelledby="modal-heading"
>
<Modal.Content>
<Heading spacing level="1" size="large" id="modal-heading">
Laborum proident id ullamco
</Heading>
<Heading spacing level="2" size="medium">
Excepteur labore nostrud incididunt exercitation.
</Heading>
<BodyLong spacing>
<div className="py-16">
<Button onClick={() => ref.current?.showModal()}>Åpne modal</Button>
<Modal ref={ref} header={{ heading: "Overskrift" }}>
<Modal.Body>
<BodyLong>
Culpa aliquip ut cupidatat laborum minim quis ex in aliqua. Qui
incididunt dolor do ad ut. Incididunt eiusmod nostrud deserunt duis
laborum. Proident aute culpa qui nostrud velit adipisicing minim.
@@ -33,9 +19,28 @@ const Example = () => {
exercitation non minim minim pariatur sunt laborum ipsum.
Exercitation nostrud est laborum magna non non aliqua qui esse.
</BodyLong>
</Modal.Content>
</Modal.Body>
<Modal.Footer>
<Button type="button" onClick={() => ref.current?.close()}>
Primær
</Button>
<Button
type="button"
variant="secondary"
onClick={() => ref.current?.close()}
>
Sekundær
</Button>
<Button
type="button"
variant="tertiary"
onClick={() => ref.current?.close()}
>
Tertiær
</Button>
</Modal.Footer>
</Modal>
</>
</div>
);
};
@@ -0,0 +1,43 @@
import { Button, Modal, TextField } from "@navikt/ds-react";
import { withDsExample } from "components/website-modules/examples/withDsExample";
import { useRef } from "react";
const Example = () => {
const ref = useRef<HTMLDialogElement>(null);
return (
<div className="py-12">
<Button onClick={() => ref.current?.showModal()}>Åpne modal</Button>
<Modal ref={ref} header={{ heading: "Skjema" }} width={400}>
<Modal.Body>
<form method="dialog" id="skjema" onSubmit={(e) => alert("onSubmit")}>
<TextField label="Har du noen tilbakemeldinger?" />
</form>
</Modal.Body>
<Modal.Footer>
<Button form="skjema">Send</Button>
<Button
type="button"
variant="secondary"
onClick={() => ref.current?.close()}
>
Avbryt
</Button>
</Modal.Footer>
</Modal>
</div>
);
};
export default withDsExample(Example);
/* Storybook story */
export const Demo = {
render: Example,
};
export const args = {
index: 4,
desc: 'Det er ikke støtte for å ha <form> som direkte barn av <Modal>. Legg <form> enten rundt hele modalen eller inni <Modal.Body>. Sistnevnte gjør det mulig å sette method="dialog", som gjør at modalen lukkes ved submit.',
};
@@ -1,40 +1,43 @@
import { BodyLong, Button, Heading, Modal } from "@navikt/ds-react";
import { BodyLong, Button, Modal } from "@navikt/ds-react";
import { withDsExample } from "components/website-modules/examples/withDsExample";
import { useEffect, useState } from "react";
import { useState } from "react";
const Example = () => {
const [open, setOpen] = useState(false);
useEffect(() => {
Modal.setAppElement("#__next");
}, []);
return (
<>
<Button onClick={() => setOpen(true)}>Åpne modal</Button>
<Modal
open={open}
aria-label="Modal demo"
onClose={() => setOpen((x) => !x)}
closeButton={false}
aria-labelledby="modal-heading"
onClose={() => setOpen(false)}
header={{
heading: "Er du sikker?",
size: "small",
closeButton: false,
}}
width="small"
>
<Modal.Content>
<Heading spacing level="1" size="large" id="modal-heading">
Laborum proident id ullamco
</Heading>
<Heading spacing level="2" size="medium">
Excepteur labore nostrud incididunt exercitation.
</Heading>
<BodyLong spacing>
<Modal.Body>
<BodyLong>
Culpa aliquip ut cupidatat laborum minim quis ex in aliqua. Qui
incididunt dolor do ad ut. Incididunt eiusmod nostrud deserunt duis
laborum. Proident aute culpa qui nostrud velit adipisicing minim.
Consequat aliqua aute dolor do sit Lorem nisi mollit velit. Aliqua
exercitation non minim minim pariatur sunt laborum ipsum.
Exercitation nostrud est laborum magna non non aliqua qui esse.
laborum.
</BodyLong>
</Modal.Content>
</Modal.Body>
<Modal.Footer>
<Button type="button" variant="danger" onClick={() => setOpen(false)}>
Ja, jeg er sikker
</Button>
<Button
type="button"
variant="secondary"
onClick={() => setOpen(false)}
>
Avbryt
</Button>
</Modal.Footer>
</Modal>
</>
);
@@ -48,5 +51,6 @@ export const Demo = {
};
export const args = {
index: 1,
index: 2,
desc: "Lukkeknappen i headeren kan skjules, men da må du ha en annen knapp som lukker modalen.",
};
@@ -0,0 +1,64 @@
import { FileIcon } from "@navikt/aksel-icons";
import { BodyLong, Button, Modal } from "@navikt/ds-react";
import { withDsExample } from "components/website-modules/examples/withDsExample";
import { useRef } from "react";
const Example = () => {
const ref = useRef<HTMLDialogElement>(null);
return (
<div className="py-16">
<Button onClick={() => ref.current?.showModal()}>Åpne modal</Button>
<Modal
ref={ref}
header={{
label: "Modal demo",
icon: <FileIcon aria-hidden />,
heading: "Overskrift",
}}
>
<Modal.Body>
<BodyLong>
Culpa aliquip ut cupidatat laborum minim quis ex in aliqua. Qui
incididunt dolor do ad ut. Incididunt eiusmod nostrud deserunt duis
laborum. Proident aute culpa qui nostrud velit adipisicing minim.
Consequat aliqua aute dolor do sit Lorem nisi mollit velit. Aliqua
exercitation non minim minim pariatur sunt laborum ipsum.
Exercitation nostrud est laborum magna non non aliqua qui esse.
</BodyLong>
</Modal.Body>
<Modal.Footer>
<Button type="button" onClick={() => ref.current?.close()}>
Primær
</Button>
<Button
type="button"
variant="secondary"
onClick={() => ref.current?.close()}
>
Sekundær
</Button>
<Button
type="button"
variant="tertiary"
onClick={() => ref.current?.close()}
>
Tertiær
</Button>
</Modal.Footer>
</Modal>
</div>
);
};
export default withDsExample(Example);
/* Storybook story */
export const Demo = {
render: Example,
};
export const args = {
index: 1,
};
Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 14 KiB

@@ -1,16 +0,0 @@
<svg width="1200" height="675" viewBox="0 0 1200 675" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1090_180341)">
<rect width="1200" height="675" fill="#8669C6"/>
<path d="M935 548L268 -119L932.999 -118.001L1600 549L935 548Z" stroke="#D6C3EE" stroke-width="8" stroke-linejoin="round"/>
<path d="M933.34 -117.66L266.68 549L933.34 1215.66L1600 549L933.34 -117.66Z" stroke="#D6C3EE" stroke-width="8" stroke-linejoin="round"/>
<path d="M267 -118L-400 549L266 549L933 -118L267 -118Z" stroke="#D6C3EE" stroke-width="8" stroke-linejoin="round"/>
<path d="M266.666 -117.666L-400 549L266.666 1215.67L933.332 549L266.666 -117.666Z" stroke="#D6C3EE" stroke-width="8" stroke-linejoin="round"/>
<path d="M934 548.999L267 1216L933 1216L1600 548.999L934 548.999Z" stroke="#D6C3EE" stroke-width="8" stroke-linejoin="round"/>
<path d="M267 1216L-400 549L265.999 548.999L933 1216L267 1216Z" stroke="#D6C3EE" stroke-width="8" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_1090_180341">
<rect width="1200" height="675" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

@@ -1,16 +0,0 @@
<svg width="1200" height="675" viewBox="0 0 1200 675" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1090_180382)">
<rect width="1200" height="675" fill="#EC4899"/>
<path d="M781.57 336.458L420.056 -25.056L780.486 -24.5145L1142 337L781.57 336.458Z" stroke="#F9A8D4" stroke-width="8" stroke-linejoin="round"/>
<path d="M780.67 -24.3296L419.34 337L780.67 698.33L1142 337L780.67 -24.3296Z" stroke="#F9A8D4" stroke-width="8" stroke-linejoin="round"/>
<path d="M419.514 -24.514L58.0001 337L418.972 337L780.486 -24.514L419.514 -24.514Z" stroke="#F9A8D4" stroke-width="8" stroke-linejoin="round"/>
<path d="M419.334 -24.333L58.001 337L419.334 698.333L780.667 337L419.334 -24.333Z" stroke="#F9A8D4" stroke-width="8" stroke-linejoin="round"/>
<path d="M781.028 337L419.514 698.514L780.486 698.514L1142 337L781.028 337Z" stroke="#F9A8D4" stroke-width="8" stroke-linejoin="round"/>
<path d="M419.514 698.514L58.0003 337L418.972 337L780.486 698.514L419.514 698.514Z" stroke="#F9A8D4" stroke-width="8" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_1090_180382">
<rect width="1200" height="675" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

@@ -1,12 +1,12 @@
<svg width="1200" height="675" viewBox="0 0 1200 675" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1090_180364)">
<rect width="1200" height="675" fill="#CCE2F0"/>
<path d="M1395.31 -431.082L965.724 -1.49527L1395.31 428.092L1824.9 -1.4952L1395.31 -431.082Z" stroke="#99C4DD" stroke-width="16" stroke-linejoin="round"/>
<path d="M542.92 -7.63999L113.331 421.949L542.276 421.949L971.865 -7.63995L542.92 -7.63999Z" stroke="#99C4DD" stroke-width="16" stroke-linejoin="round"/>
<path d="M119.473 415.805L-310.114 845.392L119.473 1274.98L549.059 845.392L119.473 415.805Z" stroke="#99C4DD" stroke-width="16" stroke-linejoin="round"/>
<g clip-path="url(#clip0_1090_180352)">
<rect width="1200" height="675" fill="#003453"/>
<path d="M547.789 -495.224L118.202 -65.6368L547.789 363.95L977.376 -65.6369L547.789 -495.224Z" stroke="#99F6E4" stroke-width="8" stroke-linejoin="round"/>
<path d="M548.279 363.463L118.69 793.052L547.635 793.052L977.223 363.463L548.279 363.463Z" stroke="#99F6E4" stroke-width="8" stroke-linejoin="round"/>
<path d="M-310.409 1222.15L-739.998 792.564L-311.054 792.563L118.536 1222.15L-310.409 1222.15Z" stroke="#99F6E4" stroke-width="8" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_1090_180364">
<clipPath id="clip0_1090_180352">
<rect width="1200" height="675" fill="white"/>
</clipPath>
</defs>

Before

Width:  |  Height:  |  Size: 774 B

After

Width:  |  Height:  |  Size: 773 B

@@ -1,12 +1,12 @@
<svg width="1200" height="675" viewBox="0 0 1200 675" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1090_180352)">
<rect width="1200" height="675" fill="#003453"/>
<path d="M547.789 -495.224L118.202 -65.6368L547.789 363.95L977.376 -65.6369L547.789 -495.224Z" stroke="#99F6E4" stroke-width="8" stroke-linejoin="round"/>
<path d="M548.279 363.463L118.69 793.052L547.635 793.052L977.223 363.463L548.279 363.463Z" stroke="#99F6E4" stroke-width="8" stroke-linejoin="round"/>
<path d="M-310.409 1222.15L-739.998 792.564L-311.054 792.563L118.536 1222.15L-310.409 1222.15Z" stroke="#99F6E4" stroke-width="8" stroke-linejoin="round"/>
<g clip-path="url(#clip0_1090_180360)">
<rect width="1200" height="675" fill="#FDF193"/>
<path d="M1485.82 -182.939L1056.23 -612.528L1485.18 -612.528L1914.77 -182.939L1485.82 -182.939Z" stroke="#9862DB" stroke-width="8" stroke-linejoin="round"/>
<path d="M1053.25 -180.596L623.665 248.991L1053.25 678.577L1482.84 248.991L1053.25 -180.596Z" stroke="#9862DB" stroke-width="8" stroke-linejoin="round"/>
<path d="M192.377 251.33L-237.211 680.919L191.733 680.919L621.322 251.33L192.377 251.33Z" stroke="#9862DB" stroke-width="8" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_1090_180352">
<clipPath id="clip0_1090_180360">
<rect width="1200" height="675" fill="white"/>
</clipPath>
</defs>

Before

Width:  |  Height:  |  Size: 773 B

After

Width:  |  Height:  |  Size: 772 B

@@ -1,12 +1,11 @@
<svg width="1200" height="675" viewBox="0 0 1200 675" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1090_180360)">
<rect width="1200" height="675" fill="#FDF193"/>
<path d="M1485.82 -182.939L1056.23 -612.528L1485.18 -612.528L1914.77 -182.939L1485.82 -182.939Z" stroke="#9862DB" stroke-width="8" stroke-linejoin="round"/>
<path d="M1053.25 -180.596L623.665 248.991L1053.25 678.577L1482.84 248.991L1053.25 -180.596Z" stroke="#9862DB" stroke-width="8" stroke-linejoin="round"/>
<path d="M192.377 251.33L-237.211 680.919L191.733 680.919L621.322 251.33L192.377 251.33Z" stroke="#9862DB" stroke-width="8" stroke-linejoin="round"/>
<g clip-path="url(#clip0_1090_180372)">
<rect width="1200" height="675" fill="#FBCFE8"/>
<path d="M2296.82 -94.5665L618.39 -94.5666L1456.35 743.392L3134.78 743.392L2296.82 -94.5665Z" stroke="#F472B6" stroke-width="16" stroke-linejoin="round"/>
<path d="M-238.001 -94.5665L-238.001 -1773L599.957 -935.043L599.957 743.392L-238.001 -94.5665Z" stroke="#F472B6" stroke-width="16" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_1090_180360">
<clipPath id="clip0_1090_180372">
<rect width="1200" height="675" fill="white"/>
</clipPath>
</defs>

Before

Width:  |  Height:  |  Size: 772 B

After

Width:  |  Height:  |  Size: 623 B

@@ -1,12 +1,15 @@
<svg width="1200" height="675" viewBox="0 0 1200 675" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1090_180348)">
<rect width="1200" height="675" fill="#99C4DD"/>
<path d="M1125.92 -317.637L811.81 -3.52571L1125.92 310.585L1440.03 -3.52576L1125.92 -317.637Z" stroke="#E6F1F8" stroke-width="16" stroke-linejoin="round"/>
<path d="M499.737 -5.09674L185.625 309.016L499.266 309.016L813.378 -5.09677L499.737 -5.09674Z" stroke="#E6F1F8" stroke-width="16" stroke-linejoin="round"/>
<path d="M187.193 307.446L-126.918 621.557L187.193 935.668L501.304 621.557L187.193 307.446Z" stroke="#E6F1F8" stroke-width="16" stroke-linejoin="round"/>
<g clip-path="url(#clip0_1090_180341)">
<rect width="1200" height="675" fill="#8669C6"/>
<path d="M935 548L268 -119L932.999 -118.001L1600 549L935 548Z" stroke="#D6C3EE" stroke-width="8" stroke-linejoin="round"/>
<path d="M933.34 -117.66L266.68 549L933.34 1215.66L1600 549L933.34 -117.66Z" stroke="#D6C3EE" stroke-width="8" stroke-linejoin="round"/>
<path d="M267 -118L-400 549L266 549L933 -118L267 -118Z" stroke="#D6C3EE" stroke-width="8" stroke-linejoin="round"/>
<path d="M266.666 -117.666L-400 549L266.666 1215.67L933.332 549L266.666 -117.666Z" stroke="#D6C3EE" stroke-width="8" stroke-linejoin="round"/>
<path d="M934 548.999L267 1216L933 1216L1600 548.999L934 548.999Z" stroke="#D6C3EE" stroke-width="8" stroke-linejoin="round"/>
<path d="M267 1216L-400 549L265.999 548.999L933 1216L267 1216Z" stroke="#D6C3EE" stroke-width="8" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_1090_180348">
<clipPath id="clip0_1090_180341">
<rect width="1200" height="675" fill="white"/>
</clipPath>
</defs>

Before

Width:  |  Height:  |  Size: 777 B

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -1,11 +1,15 @@
<svg width="1200" height="675" viewBox="0 0 1200 675" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1090_180372)">
<rect width="1200" height="675" fill="#FBCFE8"/>
<path d="M2296.82 -94.5665L618.39 -94.5666L1456.35 743.392L3134.78 743.392L2296.82 -94.5665Z" stroke="#F472B6" stroke-width="16" stroke-linejoin="round"/>
<path d="M-238.001 -94.5665L-238.001 -1773L599.957 -935.043L599.957 743.392L-238.001 -94.5665Z" stroke="#F472B6" stroke-width="16" stroke-linejoin="round"/>
<g clip-path="url(#clip0_1090_180382)">
<rect width="1200" height="675" fill="#EC4899"/>
<path d="M781.57 336.458L420.056 -25.056L780.486 -24.5145L1142 337L781.57 336.458Z" stroke="#F9A8D4" stroke-width="8" stroke-linejoin="round"/>
<path d="M780.67 -24.3296L419.34 337L780.67 698.33L1142 337L780.67 -24.3296Z" stroke="#F9A8D4" stroke-width="8" stroke-linejoin="round"/>
<path d="M419.514 -24.514L58.0001 337L418.972 337L780.486 -24.514L419.514 -24.514Z" stroke="#F9A8D4" stroke-width="8" stroke-linejoin="round"/>
<path d="M419.334 -24.333L58.001 337L419.334 698.333L780.667 337L419.334 -24.333Z" stroke="#F9A8D4" stroke-width="8" stroke-linejoin="round"/>
<path d="M781.028 337L419.514 698.514L780.486 698.514L1142 337L781.028 337Z" stroke="#F9A8D4" stroke-width="8" stroke-linejoin="round"/>
<path d="M419.514 698.514L58.0003 337L418.972 337L780.486 698.514L419.514 698.514Z" stroke="#F9A8D4" stroke-width="8" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_1090_180372">
<clipPath id="clip0_1090_180382">
<rect width="1200" height="675" fill="white"/>
</clipPath>
</defs>

Before

Width:  |  Height:  |  Size: 623 B

After

Width:  |  Height:  |  Size: 1.1 KiB

+1 -7
View File
@@ -1,6 +1,5 @@
html {
/* Remove horizontal jump caused by scrollbar presens on long or short pages */
overflow-y: scroll;
scrollbar-gutter: stable;
}
code,
@@ -58,11 +57,6 @@ a {
}
}
/* Stop content behind modal-scrolling */
.aksel.ReactModal__Body--open {
overflow: hidden;
}
.skiplink {
@apply z-modal bg-border-focus text-text-on-inverted absolute left-0 -translate-y-full p-4 no-underline focus-within:translate-y-0 focus-within:shadow-[inset_0_0_0_2px_white] focus-within:outline-none;
}
+1 -1
View File
@@ -42,7 +42,7 @@ const ModalWrapper = () => {
<>
<Button onClick={() => setIsOpen(true)}>Open modal</Button>
<Modal open={isOpen} onClose={() => setIsOpen(false)}>
<Modal.Content>modal content</Modal.Content>
<Modal.Body>modal content</Modal.Body>
</Modal>
</>
);
+2 -2
View File
@@ -10,8 +10,8 @@
"preview": "vite preview"
},
"dependencies": {
"@navikt/ds-css": "4.12.1",
"@navikt/ds-react": "4.12.1",
"@navikt/ds-css": "5.0.2",
"@navikt/ds-react": "5.0.2",
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
+19 -62
View File
@@ -3351,7 +3351,7 @@ __metadata:
languageName: node
linkType: hard
"@navikt/aksel-icons@^4.11.2, @navikt/aksel-icons@workspace:@navikt/aksel-icons":
"@navikt/aksel-icons@^5.0.1, @navikt/aksel-icons@workspace:@navikt/aksel-icons":
version: 0.0.0-use.local
resolution: "@navikt/aksel-icons@workspace:@navikt/aksel-icons"
dependencies:
@@ -3378,8 +3378,8 @@ __metadata:
version: 0.0.0-use.local
resolution: "@navikt/aksel-stylelint@workspace:@navikt/aksel-stylelint"
dependencies:
"@navikt/ds-css": ^4.11.2
"@navikt/ds-tokens": ^4.11.2
"@navikt/ds-css": ^5.0.1
"@navikt/ds-tokens": ^5.0.1
"@types/jest": ^29.0.0
concurrently: 7.2.1
copyfiles: 2.4.1
@@ -3396,7 +3396,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "@navikt/aksel@workspace:@navikt/aksel"
dependencies:
"@navikt/ds-css": 4.11.2
"@navikt/ds-css": 5.0.1
"@types/inquirer": ^9.0.3
"@types/jest": ^29.0.0
axios: 1.3.6
@@ -3420,11 +3420,11 @@ __metadata:
languageName: unknown
linkType: soft
"@navikt/ds-css@*, @navikt/ds-css@4.11.2, @navikt/ds-css@^4.11.2, @navikt/ds-css@workspace:@navikt/core/css":
"@navikt/ds-css@*, @navikt/ds-css@5.0.1, @navikt/ds-css@^5.0.1, @navikt/ds-css@workspace:@navikt/core/css":
version: 0.0.0-use.local
resolution: "@navikt/ds-css@workspace:@navikt/core/css"
dependencies:
"@navikt/ds-tokens": ^4.11.2
"@navikt/ds-tokens": ^5.0.1
cssnano: 6.0.0
fast-glob: 3.2.11
lodash: 4.17.21
@@ -3437,12 +3437,12 @@ __metadata:
languageName: unknown
linkType: soft
"@navikt/ds-react@*, @navikt/ds-react@4.11.2, @navikt/ds-react@^4.11.2, @navikt/ds-react@workspace:@navikt/core/react":
"@navikt/ds-react@*, @navikt/ds-react@5.0.1, @navikt/ds-react@^5.0.1, @navikt/ds-react@workspace:@navikt/core/react":
version: 0.0.0-use.local
resolution: "@navikt/ds-react@workspace:@navikt/core/react"
dependencies:
"@floating-ui/react": 0.24.1
"@navikt/aksel-icons": ^4.11.2
"@navikt/aksel-icons": ^5.0.1
"@radix-ui/react-tabs": 1.0.0
"@radix-ui/react-toggle-group": 1.0.0
"@testing-library/dom": 8.13.0
@@ -3451,7 +3451,6 @@ __metadata:
"@testing-library/user-event": ^14.2.0
"@types/faker": 5.5.8
"@types/jest": ^29.0.0
"@types/react-modal": ^3.13.1
clsx: ^1.2.1
concurrently: 7.2.1
copyfiles: ^2.4.1
@@ -3462,7 +3461,6 @@ __metadata:
jest-environment-jsdom: ^29.0.0
react-day-picker: 8.3.4
react-dom: ^18.0.0
react-modal: 3.15.1
react-router-dom: ^6.3.0
rimraf: 3.0.2
swr: ^1.1.2
@@ -3478,11 +3476,11 @@ __metadata:
languageName: unknown
linkType: soft
"@navikt/ds-tailwind@^4.11.2, @navikt/ds-tailwind@workspace:@navikt/core/tailwind":
"@navikt/ds-tailwind@^5.0.1, @navikt/ds-tailwind@workspace:@navikt/core/tailwind":
version: 0.0.0-use.local
resolution: "@navikt/ds-tailwind@workspace:@navikt/core/tailwind"
dependencies:
"@navikt/ds-tokens": ^4.11.2
"@navikt/ds-tokens": ^5.0.1
"@types/jest": ^29.0.0
color: 4.2.3
jest: ^29.0.0
@@ -3493,7 +3491,7 @@ __metadata:
languageName: unknown
linkType: soft
"@navikt/ds-tokens@^4.11.2, @navikt/ds-tokens@workspace:@navikt/core/tokens":
"@navikt/ds-tokens@^5.0.1, @navikt/ds-tokens@workspace:@navikt/core/tokens":
version: 0.0.0-use.local
resolution: "@navikt/ds-tokens@workspace:@navikt/core/tokens"
dependencies:
@@ -7281,15 +7279,6 @@ __metadata:
languageName: node
linkType: hard
"@types/react-modal@npm:^3.13.1":
version: 3.16.0
resolution: "@types/react-modal@npm:3.16.0"
dependencies:
"@types/react": "*"
checksum: 975c0238d81cea234a2318a7cfcb95a8e6beb7fc5341ca2311651535c6476881e8d2f2f2e1c679ca6e416c04505c748071a1fab0d3fb3e88b0a9e43060b012fe
languageName: node
linkType: hard
"@types/react-redux@npm:^7.1.20":
version: 7.1.25
resolution: "@types/react-redux@npm:7.1.25"
@@ -8044,11 +8033,11 @@ __metadata:
version: 0.0.0-use.local
resolution: "aksel.nav.no@workspace:aksel.nav.no"
dependencies:
"@navikt/aksel-icons": ^4.11.2
"@navikt/ds-css": ^4.11.2
"@navikt/ds-react": ^4.11.2
"@navikt/ds-tailwind": ^4.11.2
"@navikt/ds-tokens": ^4.11.2
"@navikt/aksel-icons": ^5.0.1
"@navikt/ds-css": ^5.0.1
"@navikt/ds-react": ^5.0.1
"@navikt/ds-tailwind": ^5.0.1
"@navikt/ds-tokens": ^5.0.1
prettier-plugin-tailwindcss: ^0.2.3
languageName: unknown
linkType: soft
@@ -12615,13 +12604,6 @@ __metadata:
languageName: node
linkType: hard
"exenv@npm:^1.2.0":
version: 1.2.2
resolution: "exenv@npm:1.2.2"
checksum: a894f3b60ab8419e0b6eec99c690a009c8276b4c90655ccaf7d28faba2de3a6b93b3d92210f9dc5efd36058d44f04098f6bbccef99859151104bfd49939904dc
languageName: node
linkType: hard
"exif-component@npm:^1.0.1":
version: 1.0.1
resolution: "exif-component@npm:1.0.1"
@@ -20619,28 +20601,13 @@ __metadata:
languageName: node
linkType: hard
"react-lifecycles-compat@npm:^3.0.0, react-lifecycles-compat@npm:^3.0.4":
"react-lifecycles-compat@npm:^3.0.4":
version: 3.0.4
resolution: "react-lifecycles-compat@npm:3.0.4"
checksum: a904b0fc0a8eeb15a148c9feb7bc17cec7ef96e71188280061fc340043fd6d8ee3ff233381f0e8f95c1cf926210b2c4a31f38182c8f35ac55057e453d6df204f
languageName: node
linkType: hard
"react-modal@npm:3.15.1":
version: 3.15.1
resolution: "react-modal@npm:3.15.1"
dependencies:
exenv: ^1.2.0
prop-types: ^15.7.2
react-lifecycles-compat: ^3.0.0
warning: ^4.0.3
peerDependencies:
react: ^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18
react-dom: ^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18
checksum: ee99ca312c35bcec9ef0868babf970ce03c52801731e29be336bb6bdc867a1ecf00a73e1fb5bc3b1b1ef66ceb0c9b4a0199fadb85b1b9829f731409951b018f0
languageName: node
linkType: hard
"react-photo-album@npm:2.2.2":
version: 2.2.2
resolution: "react-photo-album@npm:2.2.2"
@@ -22059,8 +22026,8 @@ __metadata:
version: 0.0.0-use.local
resolution: "shadow-dom@workspace:examples/shadow-dom"
dependencies:
"@navikt/ds-css": 4.11.2
"@navikt/ds-react": 4.11.2
"@navikt/ds-css": 5.0.1
"@navikt/ds-react": 5.0.1
"@types/react": ^18.0.0
"@types/react-dom": ^18.0.0
"@vitejs/plugin-react": ^3.1.0
@@ -24916,15 +24883,6 @@ __metadata:
languageName: node
linkType: hard
"warning@npm:^4.0.3":
version: 4.0.3
resolution: "warning@npm:4.0.3"
dependencies:
loose-envify: ^1.0.0
checksum: 4f2cb6a9575e4faf71ddad9ad1ae7a00d0a75d24521c193fa464f30e6b04027bd97aa5d9546b0e13d3a150ab402eda216d59c1d0f2d6ca60124d96cd40dfa35c
languageName: node
linkType: hard
"watchpack@npm:2.4.0, watchpack@npm:^2.2.0":
version: 2.4.0
resolution: "watchpack@npm:2.4.0"
@@ -25052,7 +25010,6 @@ __metadata:
prismjs: ^1.27.0
react: ^18.0.0
react-dom: ^18.0.0
react-modal: 3.15.1
react-responsive-masonry: ^2.1.7
sanity: 3.15.1
sanity-plugin-asset-source-unsplash: ^1.1.0