@navikt/ds-react 5.0.3 → 5.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +1 -1
  2. package/_docs.json +19 -0
  3. package/cjs/button/Button.js +10 -1
  4. package/cjs/date/DateInput.js +1 -1
  5. package/cjs/form/combobox/FilteredOptions/FilteredOptions.js +14 -3
  6. package/cjs/form/combobox/FilteredOptions/filteredOptionsContext.js +5 -1
  7. package/cjs/form/combobox/Input/Input.js +3 -1
  8. package/cjs/form/combobox/Input/inputContext.js +2 -1
  9. package/cjs/guide-panel/Illustration.js +5 -5
  10. package/cjs/modal/Modal.js +25 -12
  11. package/cjs/modal/ModalUtils.js +10 -9
  12. package/cjs/modal/dialog-polyfill.js +7 -33
  13. package/esm/button/Button.js +10 -1
  14. package/esm/button/Button.js.map +1 -1
  15. package/esm/date/DateInput.js +1 -1
  16. package/esm/date/DateInput.js.map +1 -1
  17. package/esm/form/combobox/FilteredOptions/FilteredOptions.js +14 -3
  18. package/esm/form/combobox/FilteredOptions/FilteredOptions.js.map +1 -1
  19. package/esm/form/combobox/FilteredOptions/filteredOptionsContext.d.ts +3 -1
  20. package/esm/form/combobox/FilteredOptions/filteredOptionsContext.js +6 -2
  21. package/esm/form/combobox/FilteredOptions/filteredOptionsContext.js.map +1 -1
  22. package/esm/form/combobox/Input/Input.js +3 -1
  23. package/esm/form/combobox/Input/Input.js.map +1 -1
  24. package/esm/form/combobox/Input/inputContext.js +3 -2
  25. package/esm/form/combobox/Input/inputContext.js.map +1 -1
  26. package/esm/guide-panel/Illustration.js +5 -5
  27. package/esm/modal/Modal.d.ts +5 -0
  28. package/esm/modal/Modal.js +25 -12
  29. package/esm/modal/Modal.js.map +1 -1
  30. package/esm/modal/ModalUtils.d.ts +2 -1
  31. package/esm/modal/ModalUtils.js +9 -8
  32. package/esm/modal/ModalUtils.js.map +1 -1
  33. package/esm/modal/dialog-polyfill.d.ts +1 -0
  34. package/esm/modal/dialog-polyfill.js +6 -33
  35. package/esm/modal/dialog-polyfill.js.map +1 -1
  36. package/package.json +2 -2
  37. package/src/button/Button.tsx +13 -1
  38. package/src/date/DateInput.tsx +1 -1
  39. package/src/form/combobox/FilteredOptions/FilteredOptions.tsx +16 -0
  40. package/src/form/combobox/FilteredOptions/filteredOptionsContext.tsx +10 -2
  41. package/src/form/combobox/Input/Input.tsx +3 -0
  42. package/src/form/combobox/Input/inputContext.tsx +2 -2
  43. package/src/form/combobox/combobox.stories.tsx +49 -0
  44. package/src/guide-panel/Illustration.tsx +5 -5
  45. package/src/loader/loader.stories.tsx +2 -2
  46. package/src/modal/Modal.test.tsx +54 -0
  47. package/src/modal/Modal.tsx +28 -11
  48. package/src/modal/ModalUtils.ts +9 -7
  49. package/src/modal/dialog-polyfill.ts +8 -40
  50. package/src/modal/modal.stories.tsx +2 -2
  51. package/src/timeline/timeline.stories.tsx +1 -1
@@ -6,12 +6,13 @@ import React, {
6
6
  useContext,
7
7
  useCallback,
8
8
  useRef,
9
- useLayoutEffect,
9
+ SetStateAction,
10
10
  } from "react";
11
11
  import cl from "clsx";
12
12
  import { useCustomOptionsContext } from "../customOptionsContext";
13
13
  import { useInputContext } from "../Input/inputContext";
14
14
  import usePrevious from "../../../util/usePrevious";
15
+ import { useClientLayoutEffect } from "../../../util";
15
16
 
16
17
  const normalizeText = (text: string): string =>
17
18
  typeof text === "string" ? `${text}`.toLowerCase().trim() : "";
@@ -35,6 +36,8 @@ type FilteredOptionsContextType = {
35
36
  isListOpen: boolean;
36
37
  isLoading?: boolean;
37
38
  filteredOptions: string[];
39
+ isMouseLastUsedInputDevice: boolean;
40
+ setIsMouseLastUsedInputDevice: React.Dispatch<SetStateAction<boolean>>;
38
41
  isValueNew: boolean;
39
42
  toggleIsListOpen: (newState?: boolean) => void;
40
43
  currentOption: string | null;
@@ -84,7 +87,10 @@ export const FilteredOptionsProvider = ({ children, value: props }) => {
84
87
 
85
88
  const previousSearchTerm = usePrevious(searchTerm);
86
89
 
87
- useLayoutEffect(() => {
90
+ const [isMouseLastUsedInputDevice, setIsMouseLastUsedInputDevice] =
91
+ useState(false);
92
+
93
+ useClientLayoutEffect(() => {
88
94
  if (
89
95
  shouldAutocomplete &&
90
96
  normalizeText(searchTerm) !== "" &&
@@ -246,6 +252,8 @@ export const FilteredOptionsProvider = ({ children, value: props }) => {
246
252
  isListOpen,
247
253
  isLoading,
248
254
  filteredOptions,
255
+ isMouseLastUsedInputDevice,
256
+ setIsMouseLastUsedInputDevice,
249
257
  isValueNew,
250
258
  toggleIsListOpen,
251
259
  currentOption,
@@ -42,6 +42,7 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
42
42
  moveFocusToInput,
43
43
  moveFocusToEnd,
44
44
  setFilteredOptionsIndex,
45
+ setIsMouseLastUsedInputDevice,
45
46
  shouldAutocomplete,
46
47
  } = useFilteredOptionsContext();
47
48
 
@@ -102,6 +103,7 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
102
103
 
103
104
  const handleKeyDown = useCallback(
104
105
  (e) => {
106
+ setIsMouseLastUsedInputDevice(false);
105
107
  if (e.key === "Backspace") {
106
108
  if (value === "") {
107
109
  const lastSelectedOption =
@@ -132,6 +134,7 @@ const Input = forwardRef<HTMLInputElement, InputProps>(
132
134
  isListOpen,
133
135
  filteredOptionsIndex,
134
136
  moveFocusUp,
137
+ setIsMouseLastUsedInputDevice,
135
138
  ]
136
139
  );
137
140
 
@@ -4,12 +4,12 @@ import React, {
4
4
  createContext,
5
5
  useCallback,
6
6
  useContext,
7
- useLayoutEffect,
8
7
  useMemo,
9
8
  useRef,
10
9
  useState,
11
10
  } from "react";
12
11
  import { useFormField, FormFieldType } from "../../useFormField";
12
+ import { useClientLayoutEffect } from "../../../util";
13
13
 
14
14
  interface InputContextType extends FormFieldType {
15
15
  clearInput: (event: React.PointerEvent | React.KeyboardEvent) => void;
@@ -92,7 +92,7 @@ export const InputContextProvider = ({ children, value: props }) => {
92
92
  inputRef.current?.focus?.();
93
93
  }, []);
94
94
 
95
- useLayoutEffect(() => {
95
+ useClientLayoutEffect(() => {
96
96
  if (shouldAutocomplete && inputRef && value !== searchTerm) {
97
97
  inputRef.current?.setSelectionRange?.(searchTerm.length, value.length);
98
98
  }
@@ -509,3 +509,52 @@ export const TestThatCallbacksOnlyFireWhenExpected = {
509
509
  expect(args.onChange.mock.calls).toHaveLength(searchWord.length + 1);
510
510
  },
511
511
  };
512
+
513
+ export const TestHoverAndFocusSwitching = {
514
+ render: (props) => {
515
+ return (
516
+ <DemoContainer dataTheme={props.darkMode}>
517
+ <UNSAFE_Combobox
518
+ options={options}
519
+ label="Hva er dine favorittfrukter?"
520
+ {...props}
521
+ />
522
+ </DemoContainer>
523
+ );
524
+ },
525
+ play: async ({ canvasElement }) => {
526
+ const canvas = within(canvasElement);
527
+
528
+ await sleep(500);
529
+
530
+ const getInput = () =>
531
+ canvas.getByRole("combobox", {
532
+ name: "Hva er dine favorittfrukter?",
533
+ });
534
+
535
+ userEvent.click(getInput());
536
+ expect(getInput().getAttribute("aria-expanded")).toEqual("false");
537
+ expect(getInput().getAttribute("aria-activedescendant")).toBeNull();
538
+
539
+ await sleep(250);
540
+ userEvent.keyboard("{ArrowDown}");
541
+ await sleep(250);
542
+ const bananaOption = canvas.getByRole("option", { name: "banana" });
543
+ expect(getInput().getAttribute("aria-activedescendant")).toBe(
544
+ bananaOption.getAttribute("id")
545
+ );
546
+
547
+ userEvent.keyboard("{ArrowDown}");
548
+ await sleep(250);
549
+ const appleOption = canvas.getByRole("option", { name: "apple" });
550
+ expect(getInput().getAttribute("aria-activedescendant")).toBe(
551
+ appleOption.getAttribute("id")
552
+ );
553
+
554
+ userEvent.hover(bananaOption);
555
+ await sleep(250);
556
+ expect(getInput().getAttribute("aria-activedescendant")).toBe(
557
+ bananaOption.getAttribute("id")
558
+ );
559
+ },
560
+ };
@@ -54,21 +54,21 @@ export const DefaultIllustration: DefaultIllustrationType = ({
54
54
  fillRule="evenodd"
55
55
  clipRule="evenodd"
56
56
  d="M16.584 26.9976C15.7859 27.062 15.5625 25.8029 15.803 24.9807C15.8482 24.8249 16.1124 24.1154 16.5802 24.1154C17.0473 24.1154 17.2536 24.5032 17.2823 24.5699C17.6259 25.3715 17.4571 26.9268 16.584 26.9976"
57
- fill="#262626"
57
+ fill="#23262a"
58
58
  />
59
59
  <path
60
60
  fillRule="evenodd"
61
61
  clipRule="evenodd"
62
62
  d="M25.8405 26.9976C26.6386 27.062 26.862 25.8029 26.6215 24.9807C26.5763 24.8249 26.3121 24.1154 25.8444 24.1154C25.3772 24.1154 25.171 24.5032 25.1423 24.5699C24.7987 25.3715 24.9674 26.9268 25.8405 26.9976"
63
- fill="#262626"
63
+ fill="#23262a"
64
64
  />
65
65
  <path
66
66
  d="M21.5081 28.2384C21.9854 28.157 22.3113 28.2081 22.428 28.3669C22.8687 28.9672 22.7277 29.6023 21.9718 30.1237C21.5744 30.3977 21.0273 30.4942 20.7377 30.3521C20.596 30.2826 20.4304 30.3536 20.3677 30.5106C20.3051 30.6676 20.3691 30.8512 20.5107 30.9207C20.9894 31.1555 21.7255 31.0257 22.268 30.6517C23.2953 29.9431 23.5304 28.8837 22.863 27.9743C22.5805 27.59 22.0806 27.5116 21.4228 27.6239C21.2697 27.65 21.1647 27.8088 21.1883 27.9784C21.2118 28.1481 21.355 28.2645 21.5081 28.2384Z"
67
- fill="#262626"
67
+ fill="#23262a"
68
68
  />
69
69
  <path
70
70
  d="M24.9595 32.3642C24.9315 32.4234 24.8672 32.5367 24.7639 32.686C24.589 32.9386 24.3694 33.1919 24.1027 33.4281C23.3079 34.1319 22.2735 34.5389 20.9568 34.5017C19.673 34.4654 18.6432 34.0647 17.8358 33.4185C17.5393 33.1813 17.2946 32.9272 17.0989 32.6739C16.9836 32.5246 16.9115 32.4114 16.88 32.3523C16.8043 32.2104 16.618 32.152 16.464 32.2218C16.31 32.2917 16.2466 32.4634 16.3224 32.6053C16.3681 32.6908 16.4569 32.8304 16.5927 33.0062C16.8156 33.2948 17.0928 33.5826 17.4283 33.8511C18.3398 34.5805 19.5046 35.0338 20.9378 35.0743C22.4302 35.1165 23.6233 34.6471 24.5333 33.8412C24.8376 33.5717 25.0877 33.2832 25.2878 32.9941C25.4095 32.8183 25.4887 32.6787 25.5291 32.5934C25.5977 32.4485 25.5259 32.2796 25.3686 32.2163C25.2113 32.153 25.0282 32.2192 24.9595 32.3642Z"
71
- fill="#262626"
71
+ fill="#23262a"
72
72
  />
73
73
  <path
74
74
  fillRule="evenodd"
@@ -80,7 +80,7 @@ export const DefaultIllustration: DefaultIllustrationType = ({
80
80
  fillRule="evenodd"
81
81
  clipRule="evenodd"
82
82
  d="M27.6207 51.8307H26.5502C26.4709 51.8307 26.407 51.7671 26.407 51.6882V51.5086C26.407 51.4303 26.4709 51.3661 26.5502 51.3661H27.6207C27.7 51.3661 27.764 51.4303 27.764 51.5086V51.6882C27.764 51.7671 27.7 51.8307 27.6207 51.8307"
83
- fill="#262626"
83
+ fill="#23262a"
84
84
  />
85
85
  <path
86
86
  fillRule="evenodd"
@@ -57,7 +57,7 @@ export const Variant = () => (
57
57
  <Loader size="3xlarge" variant="inverted" />
58
58
  <Loader size="3xlarge" variant="interaction" />
59
59
  </div>
60
- <div style={{ backgroundColor: "#262626" }}>
60
+ <div style={{ backgroundColor: "#23262a" }}>
61
61
  <Loader size="3xlarge" variant="neutral" />
62
62
  <Loader size="3xlarge" variant="inverted" />
63
63
  <Loader size="3xlarge" variant="interaction" />
@@ -72,7 +72,7 @@ export const Transparent = () => (
72
72
  <Loader size="3xlarge" transparent variant="inverted" />
73
73
  <Loader size="3xlarge" transparent variant="interaction" />
74
74
  </div>
75
- <div style={{ backgroundColor: "#262626" }}>
75
+ <div style={{ backgroundColor: "#23262a" }}>
76
76
  <Loader size="3xlarge" transparent variant="neutral" />
77
77
  <Loader size="3xlarge" transparent variant="inverted" />
78
78
  <Loader size="3xlarge" transparent variant="interaction" />
@@ -0,0 +1,54 @@
1
+ import React, { useState } from "react";
2
+ import { fireEvent, render, screen, waitFor } from "@testing-library/react";
3
+ import { Button, Modal } from "..";
4
+ import { BODY_CLASS } from "./ModalUtils";
5
+
6
+ const Test = () => {
7
+ const [open, setOpen] = useState(true);
8
+
9
+ return (
10
+ <Modal open={open}>
11
+ <Modal.Body>
12
+ <p>Foobar</p>
13
+ <Button onClick={() => setOpen(false)}>Close</Button>
14
+ </Modal.Body>
15
+ </Modal>
16
+ );
17
+ };
18
+
19
+ describe("Modal", () => {
20
+ test("should be visible", () => {
21
+ render(<Test />);
22
+ expect(screen.getByText("Foobar")).toBeVisible();
23
+ });
24
+
25
+ test("should be hidden after setting 'open' to false", async () => {
26
+ render(<Test />);
27
+ fireEvent.click(screen.getByText("Close"));
28
+ expect(screen.getByText("Foobar")).not.toBeVisible();
29
+ });
30
+
31
+ test("should toggle body class", async () => {
32
+ render(<Test />);
33
+ expect(document.body.classList).toContain(BODY_CLASS);
34
+
35
+ fireEvent.click(screen.getByText("Close"));
36
+ await waitFor(() =>
37
+ expect(document.body.classList).not.toContain(BODY_CLASS)
38
+ );
39
+ });
40
+
41
+ test("should toggle body class when using portal", async () => {
42
+ render(
43
+ <Modal portal open>
44
+ <Modal.Header />
45
+ </Modal>
46
+ );
47
+ expect(document.body.classList).toContain(BODY_CLASS);
48
+
49
+ fireEvent.click(screen.getByRole("button"));
50
+ await waitFor(() =>
51
+ expect(document.body.classList).not.toContain(BODY_CLASS)
52
+ );
53
+ });
54
+ });
@@ -5,18 +5,17 @@ import React, {
5
5
  useMemo,
6
6
  useRef,
7
7
  } from "react";
8
+ import { createPortal } from "react-dom";
9
+ import { useFloatingPortalNode } from "@floating-ui/react";
8
10
  import cl from "clsx";
9
- import dialogPolyfill from "./dialog-polyfill";
10
- import { Detail, Heading, mergeRefs, useId } from "..";
11
+ import dialogPolyfill, { needPolyfill } from "./dialog-polyfill";
12
+ import { Detail, Heading, mergeRefs, useId, useProvider } from "..";
11
13
  import ModalBody from "./ModalBody";
12
14
  import ModalHeader from "./ModalHeader";
13
15
  import ModalFooter from "./ModalFooter";
14
16
  import { getCloseHandler, useBodyScrollLock } from "./ModalUtils";
15
17
  import { ModalContext } from "./ModalContext";
16
18
 
17
- const needPolyfill =
18
- typeof window !== "undefined" && window.HTMLDialogElement === undefined;
19
-
20
19
  export interface ModalProps
21
20
  extends React.DialogHTMLAttributes<HTMLDialogElement> {
22
21
  /**
@@ -65,6 +64,11 @@ export interface ModalProps
65
64
  * @default fit-content (up to 700px)
66
65
  * */
67
66
  width?: "medium" | "small" | number | `${number}${string}`;
67
+ /**
68
+ * Lets you render the modal into a different part of the DOM.
69
+ * Will use `rootElement` from `Provider` if defined, otherwise `document.body`.
70
+ */
71
+ portal?: boolean;
68
72
  /**
69
73
  * User defined classname for modal
70
74
  */
@@ -141,6 +145,7 @@ export const Modal = forwardRef<HTMLDialogElement, ModalProps>(
141
145
  onBeforeClose,
142
146
  onCancel,
143
147
  width,
148
+ portal,
144
149
  className,
145
150
  "aria-labelledby": ariaLabelledby,
146
151
  style,
@@ -151,35 +156,41 @@ export const Modal = forwardRef<HTMLDialogElement, ModalProps>(
151
156
  const modalRef = useRef<HTMLDialogElement>(null);
152
157
  const mergedRef = useMemo(() => mergeRefs([modalRef, ref]), [ref]);
153
158
  const ariaLabelId = useId();
159
+ const rootElement = useProvider()?.rootElement;
160
+ const portalNode = useFloatingPortalNode({ root: rootElement });
154
161
 
155
162
  if (useContext(ModalContext)) {
156
163
  console.error("Modals should not be nested");
157
164
  }
158
165
 
159
166
  useEffect(() => {
160
- if (needPolyfill && modalRef.current) {
167
+ // If using portal, modalRef.current will not be set before portalNode is set.
168
+ // If not using portal, modalRef.current is available first.
169
+ // We check both to avoid activating polyfill twice when not using portal.
170
+ if (needPolyfill && modalRef.current && portalNode) {
161
171
  dialogPolyfill.registerDialog(modalRef.current);
162
172
  }
163
- }, [modalRef]);
173
+ }, [modalRef, portalNode]);
164
174
 
165
175
  useEffect(() => {
166
176
  // We need to have this in a useEffect so that the content renders before the modal is displayed,
167
177
  // and in case `open` is true initially.
168
- if (modalRef.current && open !== undefined) {
178
+ // We need to check both modalRef.current and portalNode to make sure the polyfill has been activated.
179
+ if (modalRef.current && portalNode && open !== undefined) {
169
180
  if (open && !modalRef.current.open) {
170
181
  modalRef.current.showModal();
171
182
  } else if (!open && modalRef.current.open) {
172
183
  modalRef.current.close();
173
184
  }
174
185
  }
175
- }, [modalRef, open]);
186
+ }, [modalRef, portalNode, open]);
176
187
 
177
- useBodyScrollLock(modalRef, "navds-modal__document-body");
188
+ useBodyScrollLock(modalRef, portalNode);
178
189
 
179
190
  const isWidthPreset =
180
191
  typeof width === "string" && ["small", "medium"].includes(width);
181
192
 
182
- return (
193
+ const component = (
183
194
  <dialog
184
195
  ref={mergedRef}
185
196
  className={cl("navds-modal", className, {
@@ -229,6 +240,12 @@ export const Modal = forwardRef<HTMLDialogElement, ModalProps>(
229
240
  </ModalContext.Provider>
230
241
  </dialog>
231
242
  );
243
+
244
+ if (portal) {
245
+ if (portalNode) return createPortal(component, portalNode);
246
+ return null;
247
+ }
248
+ return component;
232
249
  }
233
250
  ) as ModalComponent;
234
251
 
@@ -13,17 +13,19 @@ export function getCloseHandler(
13
13
  return () => modalRef.current?.close();
14
14
  }
15
15
 
16
+ export const BODY_CLASS = "navds-modal__document-body";
17
+
16
18
  export function useBodyScrollLock(
17
19
  modalRef: React.RefObject<HTMLDialogElement>,
18
- bodyClass: string
20
+ portalNode: HTMLElement | null
19
21
  ) {
20
22
  React.useEffect(() => {
21
- if (!modalRef.current) return;
22
- if (modalRef.current.open) document.body.classList.add(bodyClass); // In case `open` is true initially
23
+ if (!modalRef.current || !portalNode) return; // We check both to avoid running this twice when not using portal
24
+ if (modalRef.current.open) document.body.classList.add(BODY_CLASS); // In case `open` is true initially
23
25
 
24
26
  const observer = new MutationObserver(() => {
25
- if (modalRef.current?.open) document.body.classList.add(bodyClass);
26
- else document.body.classList.remove(bodyClass);
27
+ if (modalRef.current?.open) document.body.classList.add(BODY_CLASS);
28
+ else document.body.classList.remove(BODY_CLASS);
27
29
  });
28
30
  observer.observe(modalRef.current, {
29
31
  attributes: true,
@@ -31,7 +33,7 @@ export function useBodyScrollLock(
31
33
  });
32
34
  return () => {
33
35
  observer.disconnect();
34
- document.body.classList.remove(bodyClass); // In case modal is unmounted before it's closed
36
+ document.body.classList.remove(BODY_CLASS); // In case modal is unmounted before it's closed
35
37
  };
36
- }, [modalRef, bodyClass]);
38
+ }, [modalRef, portalNode]);
37
39
  }
@@ -1,5 +1,10 @@
1
1
  // @ts-nocheck
2
2
 
3
+ export const needPolyfill =
4
+ typeof window !== "undefined" &&
5
+ (window.HTMLDialogElement === undefined ||
6
+ navigator.userAgent.includes("jsdom"));
7
+
3
8
  // Copyright (c) 2013 The Chromium Authors. All rights reserved.
4
9
  //
5
10
  // Redistribution and use in source and binary forms, with or without
@@ -44,35 +49,6 @@ function safeDispatchEvent(target, event) {
44
49
  return target.dispatchEvent(event);
45
50
  }
46
51
 
47
- /**
48
- * @param {Element} el to check for stacking context
49
- * @return {boolean} whether this el or its parents creates a stacking context
50
- */
51
- function createsStackingContext(el) {
52
- while (el && el !== document.body) {
53
- var s = window.getComputedStyle(el);
54
- var invalid = function (k, ok) {
55
- return !(s[k] === undefined || s[k] === ok);
56
- };
57
-
58
- if (
59
- s.opacity < 1 ||
60
- invalid("zIndex", "auto") ||
61
- invalid("transform", "none") ||
62
- invalid("mixBlendMode", "normal") ||
63
- invalid("filter", "none") ||
64
- invalid("perspective", "none") ||
65
- s["isolation"] === "isolate" ||
66
- s.position === "fixed" ||
67
- s.webkitOverflowScrolling === "touch"
68
- ) {
69
- return true;
70
- }
71
- el = el.parentElement;
72
- }
73
- return false;
74
- }
75
-
76
52
  /**
77
53
  * Finds the nearest <dialog> from the passed element.
78
54
  *
@@ -476,14 +452,6 @@ dialogPolyfillInfo.prototype = /** @type {HTMLDialogElement.prototype} */ {
476
452
  );
477
453
  }
478
454
 
479
- if (createsStackingContext(this.dialog_.parentElement)) {
480
- console.warn(
481
- "A dialog is being shown inside a stacking context. " +
482
- "This may cause it to be unusable. For more information, see this link: " +
483
- "https://github.com/GoogleChrome/dialog-polyfill/#stacking-context"
484
- );
485
- }
486
-
487
455
  this.setOpen(true);
488
456
  this.openAsModal_ = true;
489
457
 
@@ -602,7 +570,7 @@ dialogPolyfill.needsCentering = function (dialog) {
602
570
  * @param {!Element} element to force upgrade
603
571
  */
604
572
  dialogPolyfill.forceRegisterDialog = function (element) {
605
- if (window.HTMLDialogElement || element.showModal) {
573
+ if (element.showModal) {
606
574
  console.warn(
607
575
  "This browser already supports <dialog>, the polyfill " +
608
576
  "may not work correctly",
@@ -847,7 +815,7 @@ dialogPolyfill.DialogManager.prototype.removeDialog = function (dpi) {
847
815
  this.updateStacking();
848
816
  };
849
817
 
850
- if (typeof window !== "undefined" && window.HTMLDialogElement === undefined) {
818
+ if (needPolyfill) {
851
819
  dialogPolyfill.dm = new dialogPolyfill.DialogManager();
852
820
  dialogPolyfill.formSubmitter = null;
853
821
  dialogPolyfill.imagemapUseValue = null;
@@ -857,7 +825,7 @@ if (typeof window !== "undefined" && window.HTMLDialogElement === undefined) {
857
825
  * Installs global handlers, such as click listers and native method overrides. These are needed
858
826
  * even if a no dialog is registered, as they deal with <form method="dialog">.
859
827
  */
860
- if (typeof window !== "undefined" && window.HTMLDialogElement === undefined) {
828
+ if (needPolyfill) {
861
829
  /**
862
830
  * If HTMLFormElement translates method="DIALOG" into 'get', then replace the descriptor with
863
831
  * one that returns the correct value.
@@ -160,8 +160,8 @@ export const Small = () => (
160
160
  </Modal>
161
161
  );
162
162
 
163
- export const Medium = () => (
164
- <Modal open width="medium" header={{ heading: "Simple header" }}>
163
+ export const MediumWithPortal = () => (
164
+ <Modal open portal width="medium" header={{ heading: "Simple header" }}>
165
165
  <Modal.Body>Lorem ipsum dolor sit amet.</Modal.Body>
166
166
  </Modal>
167
167
  );
@@ -1,8 +1,8 @@
1
1
  import { CheckmarkCircleFillIcon } from "@navikt/aksel-icons";
2
- import { useState } from "@storybook/addons";
3
2
  import { Meta } from "@storybook/react";
4
3
  import * as React from "react";
5
4
  import Timeline from "./Timeline";
5
+ import { useState } from "react";
6
6
 
7
7
  export default {
8
8
  title: "ds-react/Timeline",