@entur/modal 1.8.15 → 2.0.0-next.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.
package/dist/Drawer.d.ts CHANGED
@@ -25,4 +25,4 @@ export type DrawerProps = {
25
25
  /** Legger på et overlay over resten av siden */
26
26
  overlay?: boolean;
27
27
  };
28
- export declare const Drawer: React.FC<DrawerProps>;
28
+ export declare const Drawer: ({ children, className, closeLabel, contrast, open, onDismiss, title, style, overlay, }: DrawerProps) => import("react/jsx-runtime").JSX.Element | null;
package/dist/Modal.d.ts CHANGED
@@ -4,12 +4,16 @@ export type ModalProps = {
4
4
  children: React.ReactNode;
5
5
  /** Skjermleser-label til lukk-knappen */
6
6
  closeLabel?: string;
7
- /** En ref til elementet som skal være fokusert når modalen åpnes. Defaulter til lukkeknappen */
7
+ /** En ref til elementet som skal være fokusert når modalen åpnes. Defaulter til tittelen, ellers første interaktive element */
8
8
  initialFocusRef?: React.RefObject<HTMLElement>;
9
- /** Flagg som sier om modalen er åpen */
9
+ /** Styrer om modalen er synlig */
10
10
  open?: boolean;
11
- /** Callback som kalles når brukeren ber om å lukke modalen */
12
- onDismiss?: () => void;
11
+ /** Callback som kalles når brukeren ber om å lukke modalen. Påkrevd: Esc og klikk utenfor lukker alltid (nettleserens innebygde atferd for native dialog) */
12
+ onDismiss: () => void;
13
+ /** Om lukkeknappen skal vises i øvre høyre hjørne
14
+ * @default true
15
+ */
16
+ showCloseButton?: boolean;
13
17
  /** Størrelsen på modalen */
14
18
  size: 'extraSmall' | 'small' | 'medium' | 'large' | 'extraLarge';
15
19
  /** Hvordan innholdet skal plasseres i modalen
@@ -18,10 +22,11 @@ export type ModalProps = {
18
22
  align?: 'start' | 'center' | 'end';
19
23
  /** Tittelen som vises i modalen */
20
24
  title?: React.ReactNode;
25
+ /** Tilgjengelig navn for modalen når title ikke er satt */
26
+ 'aria-label'?: string;
21
27
  /** Om modalen skal lukkes når man klikker på utsiden av den
22
28
  * @default true
23
29
  */
24
30
  closeOnClickOutside?: boolean;
25
- [key: string]: any;
26
- };
27
- export declare const Modal: React.FC<ModalProps>;
31
+ } & Omit<React.HTMLAttributes<HTMLDivElement>, 'title' | 'aria-label'>;
32
+ export declare const Modal: ({ children, closeLabel, initialFocusRef, open, onDismiss, size, align, title, closeOnClickOutside, showCloseButton, "aria-label": ariaLabel, ...rest }: ModalProps) => import("react/jsx-runtime").JSX.Element;
@@ -12,8 +12,7 @@ export type ModalContentProps = {
12
12
  * @default 'start'
13
13
  */
14
14
  align?: 'start' | 'center' | 'end';
15
- [key: string]: any;
16
- };
15
+ } & Omit<React.HTMLAttributes<HTMLDivElement>, 'title'>;
17
16
  export declare const headingsMap: {
18
17
  extraSmall: <E extends React.ElementType = "h4">({ margin, children, as, ...rest }: import('../../typography').Heading4Props<E>) => JSX.Element;
19
18
  small: <E extends React.ElementType = "h3">({ margin, children, as, ...rest }: import('../../typography').Heading3Props<E>) => JSX.Element;
@@ -21,4 +20,4 @@ export declare const headingsMap: {
21
20
  large: <E extends React.ElementType = "h2">({ margin, children, as, ...rest }: import('../../typography').Heading2Props<E>) => JSX.Element;
22
21
  extraLarge: <E extends React.ElementType = "h2">({ margin, children, as, ...rest }: import('../../typography').Heading2Props<E>) => JSX.Element;
23
22
  };
24
- export declare const ModalContent: React.FC<ModalContentProps>;
23
+ export declare const ModalContent: ({ children, className, size, title, align, ...rest }: ModalContentProps) => import("react/jsx-runtime").JSX.Element;
@@ -10,6 +10,9 @@ export type ModalOverlayProps = {
10
10
  className?: string;
11
11
  /** En ref til elementet som skal være fokusert når modalen åpnes. Defaulter til lukkeknappen */
12
12
  initialFocusRef?: React.RefObject<HTMLElement>;
13
- [key: string]: any;
14
- };
15
- export declare const ModalOverlay: React.FC<ModalOverlayProps>;
13
+ /** Om modalen skal lukkes når man klikker utenfor den
14
+ * @default true
15
+ */
16
+ closeOnClickOutside?: boolean;
17
+ } & Omit<React.DialogHTMLAttributes<HTMLDialogElement>, 'open' | 'onClick'>;
18
+ export declare const ModalOverlay: ({ className, open, onDismiss, initialFocusRef, closeOnClickOutside, children, ...rest }: ModalOverlayProps) => React.ReactPortal | null;
package/dist/modal.cjs.js CHANGED
@@ -2,26 +2,69 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const utils = require("@entur/utils");
4
4
  const jsxRuntime = require("react/jsx-runtime");
5
+ const react = require("react");
5
6
  const icons = require("@entur/icons");
6
7
  const button = require("@entur/button");
7
8
  const typography = require("@entur/typography");
9
+ const reactDom = require("react-dom");
8
10
  const classNames = require("classnames");
9
- const dialog = require("@reach/dialog");
10
- const React = require("react");
11
- const reactFocusLock = require("react-focus-lock");
12
11
  const layout = require("@entur/layout");
13
12
  const ModalOverlay = ({
14
13
  className,
15
14
  open,
15
+ onDismiss,
16
+ initialFocusRef,
17
+ closeOnClickOutside = true,
18
+ children,
16
19
  ...rest
17
- }) => /* @__PURE__ */ jsxRuntime.jsx(
18
- dialog.DialogOverlay,
19
- {
20
- className: classNames("eds-modal__overlay", className),
21
- isOpen: open,
22
- ...rest
23
- }
24
- );
20
+ }) => {
21
+ const dialogRef = react.useRef(null);
22
+ react.useEffect(() => {
23
+ const dialog = dialogRef.current;
24
+ if (!dialog) return;
25
+ if (open && !dialog.open) {
26
+ dialog.showModal();
27
+ const target = initialFocusRef?.current ?? dialog.querySelector("[data-autofocus]") ?? dialog;
28
+ target.focus();
29
+ } else if (!open && dialog.open) {
30
+ dialog.close();
31
+ }
32
+ }, [open, initialFocusRef]);
33
+ react.useEffect(
34
+ function syncCancelEvent() {
35
+ const dialog = dialogRef.current;
36
+ if (!dialog || !onDismiss) return;
37
+ const handleCancel = (e) => {
38
+ e.preventDefault();
39
+ onDismiss();
40
+ };
41
+ dialog.addEventListener("cancel", handleCancel);
42
+ return () => dialog.removeEventListener("cancel", handleCancel);
43
+ },
44
+ [onDismiss]
45
+ );
46
+ const handleClick = react.useCallback(
47
+ (e) => {
48
+ if (!closeOnClickOutside) return;
49
+ if (e.target === dialogRef.current) onDismiss?.();
50
+ },
51
+ [onDismiss, closeOnClickOutside]
52
+ );
53
+ if (typeof document === "undefined") return null;
54
+ return reactDom.createPortal(
55
+ /* @__PURE__ */ jsxRuntime.jsx(layout.ContrastContext.Provider, { value: false, children: /* @__PURE__ */ jsxRuntime.jsx(
56
+ "dialog",
57
+ {
58
+ ref: dialogRef,
59
+ className: classNames("eds-modal__overlay", className),
60
+ ...rest,
61
+ onClick: handleClick,
62
+ children: open && children
63
+ }
64
+ ) }),
65
+ document.body
66
+ );
67
+ };
25
68
  const headingsMap = {
26
69
  extraSmall: typography.Heading4,
27
70
  small: typography.Heading3,
@@ -38,9 +81,9 @@ const ModalContent = ({
38
81
  ...rest
39
82
  }) => {
40
83
  const Heading = headingsMap[size] || typography.Heading2;
41
- const randomId = utils.useRandomId("eds-modal");
84
+ const randomId = react.useId();
42
85
  return /* @__PURE__ */ jsxRuntime.jsxs(
43
- dialog.DialogContent,
86
+ "div",
44
87
  {
45
88
  className: classNames(
46
89
  "eds-modal__content",
@@ -48,7 +91,7 @@ const ModalContent = ({
48
91
  `eds-modal__content--align-${align}`,
49
92
  className
50
93
  ),
51
- "aria-labelledby": randomId,
94
+ "aria-labelledby": title ? randomId : void 0,
52
95
  ...rest,
53
96
  children: [
54
97
  title && /* @__PURE__ */ jsxRuntime.jsx(Heading, { margin: "bottom", as: "h2", id: randomId, children: title }),
@@ -67,22 +110,23 @@ const Modal = ({
67
110
  align = "start",
68
111
  title,
69
112
  closeOnClickOutside = true,
113
+ showCloseButton = true,
114
+ "aria-label": ariaLabel,
70
115
  ...rest
71
116
  }) => {
72
- const randomId = utils.useRandomId("eds-modal");
117
+ const randomId = react.useId();
73
118
  const Heading = headingsMap[size] || typography.Heading2;
74
- const showCloseButton = ["medium", "large", "extraLarge"].includes(size);
75
- let handleOnDismiss;
76
- if (onDismiss && closeOnClickOutside) {
77
- handleOnDismiss = onDismiss;
78
- }
79
- return /* @__PURE__ */ jsxRuntime.jsx(
119
+ return /* @__PURE__ */ jsxRuntime.jsxs(
80
120
  ModalOverlay,
81
121
  {
82
122
  open,
83
- onDismiss: handleOnDismiss,
123
+ onDismiss,
124
+ closeOnClickOutside,
84
125
  initialFocusRef,
85
- children: /* @__PURE__ */ jsxRuntime.jsxs(ModalContent, { size, align, ...rest, children: [
126
+ className: `eds-modal__overlay--size-${size}`,
127
+ "aria-labelledby": title ? randomId : void 0,
128
+ "aria-label": !title ? ariaLabel : void 0,
129
+ children: [
86
130
  showCloseButton && /* @__PURE__ */ jsxRuntime.jsx(
87
131
  button.IconButton,
88
132
  {
@@ -93,9 +137,21 @@ const Modal = ({
93
137
  children: /* @__PURE__ */ jsxRuntime.jsx(icons.CloseIcon, {})
94
138
  }
95
139
  ),
96
- title && /* @__PURE__ */ jsxRuntime.jsx(Heading, { margin: "bottom", as: "h2", id: randomId, children: title }),
97
- children
98
- ] })
140
+ /* @__PURE__ */ jsxRuntime.jsxs(ModalContent, { size, align, ...rest, children: [
141
+ title && /* @__PURE__ */ jsxRuntime.jsx(
142
+ Heading,
143
+ {
144
+ margin: "bottom",
145
+ as: "h2",
146
+ id: randomId,
147
+ tabIndex: -1,
148
+ "data-autofocus": initialFocusRef ? void 0 : "",
149
+ children: title
150
+ }
151
+ ),
152
+ children
153
+ ] })
154
+ ]
99
155
  }
100
156
  );
101
157
  };
@@ -110,31 +166,58 @@ const Drawer = ({
110
166
  style,
111
167
  overlay = false
112
168
  }) => {
113
- const titleId = utils.useRandomId("eds-drawer");
114
- if (!open) {
115
- return null;
116
- }
117
- const handleKeyDown = (e) => {
169
+ const titleId = react.useId();
170
+ const drawerRef = react.useRef(null);
171
+ const previouslyFocusedRef = react.useRef(null);
172
+ react.useEffect(
173
+ function handleFocus() {
174
+ if (!open || overlay) return;
175
+ previouslyFocusedRef.current = document.activeElement;
176
+ drawerRef.current?.querySelector("[data-autofocus]")?.focus();
177
+ return () => {
178
+ if (previouslyFocusedRef.current instanceof HTMLElement) {
179
+ previouslyFocusedRef.current.focus();
180
+ }
181
+ };
182
+ },
183
+ [open, overlay]
184
+ );
185
+ const shouldRemoveFromDOM = !open && !overlay;
186
+ if (shouldRemoveFromDOM) return null;
187
+ function handleKeyDown(e) {
118
188
  if (e.key === "Escape") {
119
189
  e.stopPropagation();
120
190
  onDismiss();
121
191
  }
192
+ }
193
+ const Wrapper = contrast ? layout.Contrast : "div";
194
+ const nonModalDialogProps = {
195
+ role: "dialog",
196
+ "aria-modal": false,
197
+ "aria-labelledby": titleId
122
198
  };
123
- const Wrapper = contrast ? layout.Contrast : React.Fragment;
124
- const ContentContainer = overlay ? dialog.DialogContent : "div";
125
199
  return /* @__PURE__ */ jsxRuntime.jsx(
126
- ConditionalWrapper,
200
+ utils.ConditionalWrapper,
127
201
  {
128
202
  condition: overlay,
129
- wrapper: (children2) => /* @__PURE__ */ jsxRuntime.jsx(ModalOverlay, { open, onDismiss, children: children2 }),
130
- children: /* @__PURE__ */ jsxRuntime.jsx(Wrapper, { children: /* @__PURE__ */ jsxRuntime.jsx(
131
- ContentContainer,
203
+ wrapper: (children2) => /* @__PURE__ */ jsxRuntime.jsx(
204
+ ModalOverlay,
132
205
  {
206
+ open,
207
+ onDismiss,
133
208
  "aria-labelledby": titleId,
209
+ children: children2
210
+ }
211
+ ),
212
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
213
+ Wrapper,
214
+ {
215
+ ref: drawerRef,
216
+ ...!overlay && nonModalDialogProps,
134
217
  className: classNames("eds-drawer", className),
135
218
  onKeyDown: handleKeyDown,
136
219
  style,
137
- children: /* @__PURE__ */ jsxRuntime.jsxs(reactFocusLock.MoveFocusInside, { children: [
220
+ children: [
138
221
  /* @__PURE__ */ jsxRuntime.jsx(
139
222
  button.IconButton,
140
223
  {
@@ -146,16 +229,15 @@ const Drawer = ({
146
229
  }
147
230
  ),
148
231
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "eds-drawer__content", children: [
149
- /* @__PURE__ */ jsxRuntime.jsx(typography.Heading3, { as: "h2", id: titleId, children: title }),
232
+ /* @__PURE__ */ jsxRuntime.jsx(typography.Heading3, { as: "h2", id: titleId, tabIndex: -1, "data-autofocus": "", children: title }),
150
233
  children
151
234
  ] })
152
- ] })
235
+ ]
153
236
  }
154
- ) })
237
+ )
155
238
  }
156
239
  );
157
240
  };
158
- const ConditionalWrapper = ({ condition, wrapper, children }) => condition ? wrapper(children) : children;
159
241
  utils.warnAboutMissingStyles("modal", "icons", "typography", "a11y", "button");
160
242
  exports.Drawer = Drawer;
161
243
  exports.Modal = Modal;
@@ -1 +1 @@
1
- {"version":3,"file":"modal.cjs.js","sources":["../src/ModalOverlay.tsx","../src/ModalContent.tsx","../src/Modal.tsx","../src/Drawer.tsx","../src/index.tsx"],"sourcesContent":["import React from 'react';\nimport classNames from 'classnames';\nimport { DialogOverlay } from '@reach/dialog';\n\nexport type ModalOverlayProps = {\n /** Flagg som sier om modalen er åpen */\n open?: boolean;\n /** Callback som kalles når brukeren ber om å lukke modalen */\n onDismiss?: () => void;\n /** Innholdet i modalen */\n children: React.ReactNode;\n /** Ekstra klassenavn */\n className?: string;\n /** En ref til elementet som skal være fokusert når modalen åpnes. Defaulter til lukkeknappen */\n initialFocusRef?: React.RefObject<HTMLElement>;\n [key: string]: any;\n};\n\nexport const ModalOverlay: React.FC<ModalOverlayProps> = ({\n className,\n open,\n ...rest\n}) => (\n <DialogOverlay\n className={classNames('eds-modal__overlay', className)}\n isOpen={open}\n {...rest}\n />\n);\n","import React from 'react';\nimport classNames from 'classnames';\nimport { DialogContent } from '@reach/dialog';\nimport { Heading2, Heading3, Heading4 } from '@entur/typography';\nimport { useRandomId } from '@entur/utils';\n\nexport type ModalContentProps = {\n /** Innholdet i modalen */\n children: React.ReactNode;\n /** Ekstra klassenavn */\n className?: string;\n /** Størrelsen på modalen */\n size: 'extraSmall' | 'small' | 'medium' | 'large' | 'extraLarge';\n /** Tittelen som vises i modalen */\n title?: React.ReactNode;\n /** Hvordan innholdet skal plasseres i modalen\n * @default 'start'\n */\n align?: 'start' | 'center' | 'end';\n [key: string]: any;\n};\n\nexport const headingsMap = {\n extraSmall: Heading4,\n small: Heading3,\n medium: Heading2,\n large: Heading2,\n extraLarge: Heading2,\n};\n\nexport const ModalContent: React.FC<ModalContentProps> = ({\n children,\n className,\n size,\n title,\n align = 'start',\n ...rest\n}) => {\n const Heading: React.ElementType = headingsMap[size] || Heading2;\n const randomId = useRandomId('eds-modal');\n return (\n <DialogContent\n className={classNames(\n 'eds-modal__content',\n `eds-modal__content--size-${size}`,\n `eds-modal__content--align-${align}`,\n className,\n )}\n aria-labelledby={randomId}\n {...rest}\n >\n {title && (\n <Heading margin=\"bottom\" as=\"h2\" id={randomId}>\n {title}\n </Heading>\n )}\n {children}\n </DialogContent>\n );\n};\n","import React from 'react';\n\nimport { CloseIcon } from '@entur/icons';\nimport { IconButton } from '@entur/button';\nimport { Heading2 } from '@entur/typography';\nimport { useRandomId } from '@entur/utils';\n\nimport { ModalOverlay } from './ModalOverlay';\nimport { ModalContent, headingsMap } from './ModalContent';\n\nimport './Modal.scss';\n\nexport type ModalProps = {\n /** Innholdet i modalen */\n children: React.ReactNode;\n /** Skjermleser-label til lukk-knappen */\n closeLabel?: string;\n /** En ref til elementet som skal være fokusert når modalen åpnes. Defaulter til lukkeknappen */\n initialFocusRef?: React.RefObject<HTMLElement>;\n /** Flagg som sier om modalen er åpen */\n open?: boolean;\n /** Callback som kalles når brukeren ber om å lukke modalen */\n onDismiss?: () => void;\n /** Størrelsen på modalen */\n size: 'extraSmall' | 'small' | 'medium' | 'large' | 'extraLarge';\n /** Hvordan innholdet skal plasseres i modalen\n * @default 'start'\n */\n align?: 'start' | 'center' | 'end';\n /** Tittelen som vises i modalen */\n title?: React.ReactNode;\n /** Om modalen skal lukkes når man klikker på utsiden av den\n * @default true\n */\n closeOnClickOutside?: boolean;\n [key: string]: any;\n};\n\nexport const Modal: React.FC<ModalProps> = ({\n children,\n closeLabel = 'Lukk',\n initialFocusRef,\n open,\n onDismiss,\n size,\n align = 'start',\n title,\n closeOnClickOutside = true,\n ...rest\n}) => {\n const randomId = useRandomId('eds-modal');\n const Heading: React.ElementType = headingsMap[size] || Heading2;\n const showCloseButton = ['medium', 'large', 'extraLarge'].includes(size);\n\n let handleOnDismiss;\n if (onDismiss && closeOnClickOutside) {\n handleOnDismiss = onDismiss;\n }\n return (\n <ModalOverlay\n open={open}\n onDismiss={handleOnDismiss}\n initialFocusRef={initialFocusRef}\n >\n <ModalContent size={size} align={align} {...rest}>\n {showCloseButton && (\n <IconButton\n className=\"eds-modal__close\"\n aria-label={closeLabel}\n onClick={onDismiss}\n type=\"button\"\n >\n <CloseIcon />\n </IconButton>\n )}\n {title && (\n <Heading margin=\"bottom\" as=\"h2\" id={randomId}>\n {title}\n </Heading>\n )}\n {children}\n </ModalContent>\n </ModalOverlay>\n );\n};\n","import React from 'react';\nimport classNames from 'classnames';\nimport { MoveFocusInside } from 'react-focus-lock';\nimport { Contrast } from '@entur/layout';\nimport { CloseIcon } from '@entur/icons';\nimport { Heading3 } from '@entur/typography';\nimport { useRandomId } from '@entur/utils';\nimport { IconButton } from '@entur/button';\n\nimport './Drawer.scss';\nimport { ModalOverlay } from './ModalOverlay';\nimport { DialogContent } from '@reach/dialog';\n\nexport type DrawerProps = {\n /** Innholdet. Typisk tekst, lenker eller knapper */\n children: React.ReactNode;\n /** Ekstra klassenavn */\n className?: string;\n /** Tekst som beskriver lukkeknappen for skjermlesere\n * @default 'Lukk skuff'\n */\n closeLabel?: string;\n /** Om draweren skal vises i mørk variant\n * @default false\n */\n contrast?: boolean;\n /** Callback som kalles når brukeren ønsker å lukke draweren */\n onDismiss: () => void;\n /** Om draweren er åpen eller ikke\n * @default true\n */\n open?: boolean;\n /** Tittel på toppen av draweren */\n title: string;\n /** Styling som sendes til Drawer */\n style?: React.CSSProperties;\n /** Legger på et overlay over resten av siden */\n overlay?: boolean;\n};\n\nexport const Drawer: React.FC<DrawerProps> = ({\n children,\n className,\n closeLabel = 'Lukk skuff',\n contrast = false,\n open = true,\n onDismiss,\n title,\n style,\n overlay = false,\n}) => {\n const titleId = useRandomId('eds-drawer');\n\n if (!open) {\n return null;\n }\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (e.key === 'Escape') {\n e.stopPropagation();\n onDismiss();\n }\n };\n\n const Wrapper = contrast ? Contrast : React.Fragment;\n const ContentContainer = overlay ? DialogContent : 'div';\n return (\n <ConditionalWrapper\n condition={overlay}\n wrapper={(children: React.ReactNode) => (\n <ModalOverlay open={open} onDismiss={onDismiss}>\n {children}\n </ModalOverlay>\n )}\n >\n <Wrapper>\n <ContentContainer\n aria-labelledby={titleId}\n className={classNames('eds-drawer', className)}\n onKeyDown={handleKeyDown}\n style={style}\n >\n <MoveFocusInside>\n <IconButton\n className=\"eds-drawer__close-button\"\n onClick={onDismiss}\n type=\"button\"\n aria-label={closeLabel}\n >\n <CloseIcon aria-hidden />\n </IconButton>\n <div className=\"eds-drawer__content\">\n <Heading3 as=\"h2\" id={titleId}>\n {title}\n </Heading3>\n {children}\n </div>\n </MoveFocusInside>\n </ContentContainer>\n </Wrapper>\n </ConditionalWrapper>\n );\n};\n\nconst ConditionalWrapper: React.FC<{\n condition: boolean;\n wrapper: (child: JSX.Element) => JSX.Element;\n children: React.ReactElement;\n}> = ({ condition, wrapper, children }) =>\n condition ? wrapper(children) : children;\n","import { warnAboutMissingStyles } from '@entur/utils';\nimport './index.scss';\n\nwarnAboutMissingStyles('modal', 'icons', 'typography', 'a11y', 'button');\n\nexport * from './Modal';\nexport * from './ModalOverlay';\nexport * from './ModalContent';\nexport * from './Drawer';\n"],"names":["jsx","DialogOverlay","Heading4","Heading3","Heading2","useRandomId","jsxs","DialogContent","IconButton","CloseIcon","Contrast","children","MoveFocusInside","warnAboutMissingStyles"],"mappings":"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"}
1
+ {"version":3,"file":"modal.cjs.js","sources":["../src/ModalOverlay.tsx","../src/ModalContent.tsx","../src/Modal.tsx","../src/Drawer.tsx","../src/index.tsx"],"sourcesContent":["import React, { useCallback, useEffect, useRef } from 'react';\nimport { createPortal } from 'react-dom';\nimport classNames from 'classnames';\nimport { ContrastContext } from '@entur/layout';\n\nexport type ModalOverlayProps = {\n /** Flagg som sier om modalen er åpen */\n open?: boolean;\n /** Callback som kalles når brukeren ber om å lukke modalen */\n onDismiss?: () => void;\n /** Innholdet i modalen */\n children: React.ReactNode;\n /** Ekstra klassenavn */\n className?: string;\n /** En ref til elementet som skal være fokusert når modalen åpnes. Defaulter til lukkeknappen */\n initialFocusRef?: React.RefObject<HTMLElement>;\n /** Om modalen skal lukkes når man klikker utenfor den\n * @default true\n */\n closeOnClickOutside?: boolean;\n} & Omit<React.DialogHTMLAttributes<HTMLDialogElement>, 'open' | 'onClick'>;\n\nexport const ModalOverlay = ({\n className,\n open,\n onDismiss,\n initialFocusRef,\n closeOnClickOutside = true,\n children,\n ...rest\n}: ModalOverlayProps) => {\n const dialogRef = useRef<HTMLDialogElement>(null);\n\n // Toggle native dialog open state. Always stays in DOM so the browser can\n // manage initial focus storage + restore on close.\n useEffect(() => {\n const dialog = dialogRef.current;\n if (!dialog) return;\n if (open && !dialog.open) {\n dialog.showModal();\n const target =\n initialFocusRef?.current ??\n dialog.querySelector<HTMLElement>('[data-autofocus]') ??\n dialog;\n target.focus();\n } else if (!open && dialog.open) {\n dialog.close();\n }\n }, [open, initialFocusRef]);\n\n // Esc fires cancel — preventDefault so we route through onDismiss + state,\n // which triggers the effect above to call close() (native restore runs).\n useEffect(\n function syncCancelEvent() {\n const dialog = dialogRef.current;\n if (!dialog || !onDismiss) return;\n const handleCancel = (e: Event) => {\n e.preventDefault();\n onDismiss();\n };\n dialog.addEventListener('cancel', handleCancel);\n return () => dialog.removeEventListener('cancel', handleCancel);\n },\n [onDismiss],\n );\n\n const handleClick = useCallback(\n (e: React.MouseEvent<HTMLDialogElement>) => {\n if (!closeOnClickOutside) return;\n if (e.target === dialogRef.current) onDismiss?.();\n },\n [onDismiss, closeOnClickOutside],\n );\n\n if (typeof document === 'undefined') return null;\n\n return createPortal(\n <ContrastContext.Provider value={false}>\n <dialog\n ref={dialogRef}\n className={classNames('eds-modal__overlay', className)}\n {...rest}\n onClick={handleClick}\n >\n {open && children}\n </dialog>\n </ContrastContext.Provider>,\n document.body,\n );\n};\n","import React, { useId } from 'react';\nimport classNames from 'classnames';\nimport { Heading4, Heading3, Heading2 } from '@entur/typography';\n\nexport type ModalContentProps = {\n /** Innholdet i modalen */\n children: React.ReactNode;\n /** Ekstra klassenavn */\n className?: string;\n /** Størrelsen på modalen */\n size: 'extraSmall' | 'small' | 'medium' | 'large' | 'extraLarge';\n /** Tittelen som vises i modalen */\n title?: React.ReactNode;\n /** Hvordan innholdet skal plasseres i modalen\n * @default 'start'\n */\n align?: 'start' | 'center' | 'end';\n} & Omit<React.HTMLAttributes<HTMLDivElement>, 'title'>;\n\nexport const headingsMap = {\n extraSmall: Heading4,\n small: Heading3,\n medium: Heading2,\n large: Heading2,\n extraLarge: Heading2,\n};\n\nexport const ModalContent = ({\n children,\n className,\n size,\n title,\n align = 'start',\n ...rest\n}: ModalContentProps) => {\n const Heading: React.ElementType = headingsMap[size] || Heading2;\n const randomId = useId();\n return (\n <div\n className={classNames(\n 'eds-modal__content',\n `eds-modal__content--size-${size}`,\n `eds-modal__content--align-${align}`,\n className,\n )}\n aria-labelledby={title ? randomId : undefined}\n {...rest}\n >\n {title && (\n <Heading margin=\"bottom\" as=\"h2\" id={randomId}>\n {title}\n </Heading>\n )}\n {children}\n </div>\n );\n};\n","import React, { useId } from 'react';\n\nimport { CloseIcon } from '@entur/icons';\nimport { IconButton } from '@entur/button';\nimport { Heading2 } from '@entur/typography';\n\nimport { ModalOverlay } from './ModalOverlay';\nimport { ModalContent, headingsMap } from './ModalContent';\n\nimport './Modal.scss';\n\nexport type ModalProps = {\n /** Innholdet i modalen */\n children: React.ReactNode;\n /** Skjermleser-label til lukk-knappen */\n closeLabel?: string;\n /** En ref til elementet som skal være fokusert når modalen åpnes. Defaulter til tittelen, ellers første interaktive element */\n initialFocusRef?: React.RefObject<HTMLElement>;\n /** Styrer om modalen er synlig */\n open?: boolean;\n /** Callback som kalles når brukeren ber om å lukke modalen. Påkrevd: Esc og klikk utenfor lukker alltid (nettleserens innebygde atferd for native dialog) */\n onDismiss: () => void;\n /** Om lukkeknappen skal vises i øvre høyre hjørne\n * @default true\n */\n showCloseButton?: boolean;\n /** Størrelsen på modalen */\n size: 'extraSmall' | 'small' | 'medium' | 'large' | 'extraLarge';\n /** Hvordan innholdet skal plasseres i modalen\n * @default 'start'\n */\n align?: 'start' | 'center' | 'end';\n /** Tittelen som vises i modalen */\n title?: React.ReactNode;\n /** Tilgjengelig navn for modalen når title ikke er satt */\n 'aria-label'?: string;\n /** Om modalen skal lukkes når man klikker på utsiden av den\n * @default true\n */\n closeOnClickOutside?: boolean;\n} & Omit<React.HTMLAttributes<HTMLDivElement>, 'title' | 'aria-label'>;\n\nexport const Modal = ({\n children,\n closeLabel = 'Lukk',\n initialFocusRef,\n open,\n onDismiss,\n size,\n align = 'start',\n title,\n closeOnClickOutside = true,\n showCloseButton = true,\n 'aria-label': ariaLabel,\n ...rest\n}: ModalProps) => {\n const randomId = useId();\n const Heading: React.ElementType = headingsMap[size] || Heading2;\n\n return (\n <ModalOverlay\n open={open}\n onDismiss={onDismiss}\n closeOnClickOutside={closeOnClickOutside}\n initialFocusRef={initialFocusRef}\n className={`eds-modal__overlay--size-${size}`}\n aria-labelledby={title ? randomId : undefined}\n aria-label={!title ? ariaLabel : undefined}\n >\n {showCloseButton && (\n <IconButton\n className=\"eds-modal__close\"\n aria-label={closeLabel}\n onClick={onDismiss}\n type=\"button\"\n >\n <CloseIcon />\n </IconButton>\n )}\n <ModalContent size={size} align={align} {...rest}>\n {title && (\n <Heading\n margin=\"bottom\"\n as=\"h2\"\n id={randomId}\n tabIndex={-1}\n data-autofocus={initialFocusRef ? undefined : ''}\n >\n {title}\n </Heading>\n )}\n {children}\n </ModalContent>\n </ModalOverlay>\n );\n};\n","import React, { useEffect, useId, useRef } from 'react';\nimport classNames from 'classnames';\nimport { Contrast } from '@entur/layout';\nimport { CloseIcon } from '@entur/icons';\nimport { Heading3 } from '@entur/typography';\nimport { IconButton } from '@entur/button';\nimport { ConditionalWrapper } from '@entur/utils';\nimport { ModalOverlay } from './ModalOverlay';\n\nimport './Drawer.scss';\n\nexport type DrawerProps = {\n /** Innholdet. Typisk tekst, lenker eller knapper */\n children: React.ReactNode;\n /** Ekstra klassenavn */\n className?: string;\n /** Tekst som beskriver lukkeknappen for skjermlesere\n * @default 'Lukk skuff'\n */\n closeLabel?: string;\n /** Om draweren skal vises i mørk variant\n * @default false\n */\n contrast?: boolean;\n /** Callback som kalles når brukeren ønsker å lukke draweren */\n onDismiss: () => void;\n /** Om draweren er åpen eller ikke\n * @default true\n */\n open?: boolean;\n /** Tittel på toppen av draweren */\n title: string;\n /** Styling som sendes til Drawer */\n style?: React.CSSProperties;\n /** Legger på et overlay over resten av siden */\n overlay?: boolean;\n};\n\nexport const Drawer = ({\n children,\n className,\n closeLabel = 'Lukk skuff',\n contrast = false,\n open = true,\n onDismiss,\n title,\n style,\n overlay = false,\n}: DrawerProps) => {\n const titleId = useId();\n const drawerRef = useRef<HTMLDivElement>(null);\n const previouslyFocusedRef = useRef<Element | null>(null);\n\n useEffect(\n function handleFocus() {\n if (!open || overlay) return;\n previouslyFocusedRef.current = document.activeElement;\n drawerRef.current\n ?.querySelector<HTMLElement>('[data-autofocus]')\n ?.focus();\n\n return () => {\n if (previouslyFocusedRef.current instanceof HTMLElement) {\n previouslyFocusedRef.current.focus();\n }\n };\n },\n [open, overlay],\n );\n\n const shouldRemoveFromDOM = !open && !overlay;\n if (shouldRemoveFromDOM) return null;\n\n function handleKeyDown(e: React.KeyboardEvent) {\n if (e.key === 'Escape') {\n e.stopPropagation();\n onDismiss();\n }\n }\n\n const Wrapper = contrast ? Contrast : 'div';\n const nonModalDialogProps = {\n role: 'dialog' as const,\n 'aria-modal': false,\n 'aria-labelledby': titleId,\n };\n\n return (\n <ConditionalWrapper\n condition={overlay}\n wrapper={(children: React.ReactNode) => (\n <ModalOverlay\n open={open}\n onDismiss={onDismiss}\n aria-labelledby={titleId}\n >\n {children}\n </ModalOverlay>\n )}\n >\n <Wrapper\n ref={drawerRef}\n {...(!overlay && nonModalDialogProps)}\n className={classNames('eds-drawer', className)}\n onKeyDown={handleKeyDown}\n style={style}\n >\n <IconButton\n className=\"eds-drawer__close-button\"\n onClick={onDismiss}\n type=\"button\"\n aria-label={closeLabel}\n >\n <CloseIcon aria-hidden />\n </IconButton>\n <div className=\"eds-drawer__content\">\n <Heading3 as=\"h2\" id={titleId} tabIndex={-1} data-autofocus=\"\">\n {title}\n </Heading3>\n {children}\n </div>\n </Wrapper>\n </ConditionalWrapper>\n );\n};\n","import { warnAboutMissingStyles } from '@entur/utils';\nimport './index.scss';\n\nwarnAboutMissingStyles('modal', 'icons', 'typography', 'a11y', 'button');\n\nexport * from './Modal';\nexport * from './ModalOverlay';\nexport * from './ModalContent';\nexport * from './Drawer';\n"],"names":["useRef","useEffect","useCallback","createPortal","jsx","ContrastContext","Heading4","Heading3","Heading2","useId","jsxs","IconButton","CloseIcon","Contrast","ConditionalWrapper","children","warnAboutMissingStyles"],"mappings":"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"}
package/dist/modal.esm.js CHANGED
@@ -1,25 +1,68 @@
1
- import { useRandomId, warnAboutMissingStyles } from "@entur/utils";
1
+ import { ConditionalWrapper, warnAboutMissingStyles } from "@entur/utils";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useRef, useEffect, useCallback, useId } from "react";
3
4
  import { CloseIcon } from "@entur/icons";
4
5
  import { IconButton } from "@entur/button";
5
6
  import { Heading2, Heading3, Heading4 } from "@entur/typography";
7
+ import { createPortal } from "react-dom";
6
8
  import classNames from "classnames";
7
- import { DialogOverlay, DialogContent } from "@reach/dialog";
8
- import React from "react";
9
- import { MoveFocusInside } from "react-focus-lock";
10
- import { Contrast } from "@entur/layout";
9
+ import { ContrastContext, Contrast } from "@entur/layout";
11
10
  const ModalOverlay = ({
12
11
  className,
13
12
  open,
13
+ onDismiss,
14
+ initialFocusRef,
15
+ closeOnClickOutside = true,
16
+ children,
14
17
  ...rest
15
- }) => /* @__PURE__ */ jsx(
16
- DialogOverlay,
17
- {
18
- className: classNames("eds-modal__overlay", className),
19
- isOpen: open,
20
- ...rest
21
- }
22
- );
18
+ }) => {
19
+ const dialogRef = useRef(null);
20
+ useEffect(() => {
21
+ const dialog = dialogRef.current;
22
+ if (!dialog) return;
23
+ if (open && !dialog.open) {
24
+ dialog.showModal();
25
+ const target = initialFocusRef?.current ?? dialog.querySelector("[data-autofocus]") ?? dialog;
26
+ target.focus();
27
+ } else if (!open && dialog.open) {
28
+ dialog.close();
29
+ }
30
+ }, [open, initialFocusRef]);
31
+ useEffect(
32
+ function syncCancelEvent() {
33
+ const dialog = dialogRef.current;
34
+ if (!dialog || !onDismiss) return;
35
+ const handleCancel = (e) => {
36
+ e.preventDefault();
37
+ onDismiss();
38
+ };
39
+ dialog.addEventListener("cancel", handleCancel);
40
+ return () => dialog.removeEventListener("cancel", handleCancel);
41
+ },
42
+ [onDismiss]
43
+ );
44
+ const handleClick = useCallback(
45
+ (e) => {
46
+ if (!closeOnClickOutside) return;
47
+ if (e.target === dialogRef.current) onDismiss?.();
48
+ },
49
+ [onDismiss, closeOnClickOutside]
50
+ );
51
+ if (typeof document === "undefined") return null;
52
+ return createPortal(
53
+ /* @__PURE__ */ jsx(ContrastContext.Provider, { value: false, children: /* @__PURE__ */ jsx(
54
+ "dialog",
55
+ {
56
+ ref: dialogRef,
57
+ className: classNames("eds-modal__overlay", className),
58
+ ...rest,
59
+ onClick: handleClick,
60
+ children: open && children
61
+ }
62
+ ) }),
63
+ document.body
64
+ );
65
+ };
23
66
  const headingsMap = {
24
67
  extraSmall: Heading4,
25
68
  small: Heading3,
@@ -36,9 +79,9 @@ const ModalContent = ({
36
79
  ...rest
37
80
  }) => {
38
81
  const Heading = headingsMap[size] || Heading2;
39
- const randomId = useRandomId("eds-modal");
82
+ const randomId = useId();
40
83
  return /* @__PURE__ */ jsxs(
41
- DialogContent,
84
+ "div",
42
85
  {
43
86
  className: classNames(
44
87
  "eds-modal__content",
@@ -46,7 +89,7 @@ const ModalContent = ({
46
89
  `eds-modal__content--align-${align}`,
47
90
  className
48
91
  ),
49
- "aria-labelledby": randomId,
92
+ "aria-labelledby": title ? randomId : void 0,
50
93
  ...rest,
51
94
  children: [
52
95
  title && /* @__PURE__ */ jsx(Heading, { margin: "bottom", as: "h2", id: randomId, children: title }),
@@ -65,22 +108,23 @@ const Modal = ({
65
108
  align = "start",
66
109
  title,
67
110
  closeOnClickOutside = true,
111
+ showCloseButton = true,
112
+ "aria-label": ariaLabel,
68
113
  ...rest
69
114
  }) => {
70
- const randomId = useRandomId("eds-modal");
115
+ const randomId = useId();
71
116
  const Heading = headingsMap[size] || Heading2;
72
- const showCloseButton = ["medium", "large", "extraLarge"].includes(size);
73
- let handleOnDismiss;
74
- if (onDismiss && closeOnClickOutside) {
75
- handleOnDismiss = onDismiss;
76
- }
77
- return /* @__PURE__ */ jsx(
117
+ return /* @__PURE__ */ jsxs(
78
118
  ModalOverlay,
79
119
  {
80
120
  open,
81
- onDismiss: handleOnDismiss,
121
+ onDismiss,
122
+ closeOnClickOutside,
82
123
  initialFocusRef,
83
- children: /* @__PURE__ */ jsxs(ModalContent, { size, align, ...rest, children: [
124
+ className: `eds-modal__overlay--size-${size}`,
125
+ "aria-labelledby": title ? randomId : void 0,
126
+ "aria-label": !title ? ariaLabel : void 0,
127
+ children: [
84
128
  showCloseButton && /* @__PURE__ */ jsx(
85
129
  IconButton,
86
130
  {
@@ -91,9 +135,21 @@ const Modal = ({
91
135
  children: /* @__PURE__ */ jsx(CloseIcon, {})
92
136
  }
93
137
  ),
94
- title && /* @__PURE__ */ jsx(Heading, { margin: "bottom", as: "h2", id: randomId, children: title }),
95
- children
96
- ] })
138
+ /* @__PURE__ */ jsxs(ModalContent, { size, align, ...rest, children: [
139
+ title && /* @__PURE__ */ jsx(
140
+ Heading,
141
+ {
142
+ margin: "bottom",
143
+ as: "h2",
144
+ id: randomId,
145
+ tabIndex: -1,
146
+ "data-autofocus": initialFocusRef ? void 0 : "",
147
+ children: title
148
+ }
149
+ ),
150
+ children
151
+ ] })
152
+ ]
97
153
  }
98
154
  );
99
155
  };
@@ -108,31 +164,58 @@ const Drawer = ({
108
164
  style,
109
165
  overlay = false
110
166
  }) => {
111
- const titleId = useRandomId("eds-drawer");
112
- if (!open) {
113
- return null;
114
- }
115
- const handleKeyDown = (e) => {
167
+ const titleId = useId();
168
+ const drawerRef = useRef(null);
169
+ const previouslyFocusedRef = useRef(null);
170
+ useEffect(
171
+ function handleFocus() {
172
+ if (!open || overlay) return;
173
+ previouslyFocusedRef.current = document.activeElement;
174
+ drawerRef.current?.querySelector("[data-autofocus]")?.focus();
175
+ return () => {
176
+ if (previouslyFocusedRef.current instanceof HTMLElement) {
177
+ previouslyFocusedRef.current.focus();
178
+ }
179
+ };
180
+ },
181
+ [open, overlay]
182
+ );
183
+ const shouldRemoveFromDOM = !open && !overlay;
184
+ if (shouldRemoveFromDOM) return null;
185
+ function handleKeyDown(e) {
116
186
  if (e.key === "Escape") {
117
187
  e.stopPropagation();
118
188
  onDismiss();
119
189
  }
190
+ }
191
+ const Wrapper = contrast ? Contrast : "div";
192
+ const nonModalDialogProps = {
193
+ role: "dialog",
194
+ "aria-modal": false,
195
+ "aria-labelledby": titleId
120
196
  };
121
- const Wrapper = contrast ? Contrast : React.Fragment;
122
- const ContentContainer = overlay ? DialogContent : "div";
123
197
  return /* @__PURE__ */ jsx(
124
198
  ConditionalWrapper,
125
199
  {
126
200
  condition: overlay,
127
- wrapper: (children2) => /* @__PURE__ */ jsx(ModalOverlay, { open, onDismiss, children: children2 }),
128
- children: /* @__PURE__ */ jsx(Wrapper, { children: /* @__PURE__ */ jsx(
129
- ContentContainer,
201
+ wrapper: (children2) => /* @__PURE__ */ jsx(
202
+ ModalOverlay,
130
203
  {
204
+ open,
205
+ onDismiss,
131
206
  "aria-labelledby": titleId,
207
+ children: children2
208
+ }
209
+ ),
210
+ children: /* @__PURE__ */ jsxs(
211
+ Wrapper,
212
+ {
213
+ ref: drawerRef,
214
+ ...!overlay && nonModalDialogProps,
132
215
  className: classNames("eds-drawer", className),
133
216
  onKeyDown: handleKeyDown,
134
217
  style,
135
- children: /* @__PURE__ */ jsxs(MoveFocusInside, { children: [
218
+ children: [
136
219
  /* @__PURE__ */ jsx(
137
220
  IconButton,
138
221
  {
@@ -144,16 +227,15 @@ const Drawer = ({
144
227
  }
145
228
  ),
146
229
  /* @__PURE__ */ jsxs("div", { className: "eds-drawer__content", children: [
147
- /* @__PURE__ */ jsx(Heading3, { as: "h2", id: titleId, children: title }),
230
+ /* @__PURE__ */ jsx(Heading3, { as: "h2", id: titleId, tabIndex: -1, "data-autofocus": "", children: title }),
148
231
  children
149
232
  ] })
150
- ] })
233
+ ]
151
234
  }
152
- ) })
235
+ )
153
236
  }
154
237
  );
155
238
  };
156
- const ConditionalWrapper = ({ condition, wrapper, children }) => condition ? wrapper(children) : children;
157
239
  warnAboutMissingStyles("modal", "icons", "typography", "a11y", "button");
158
240
  export {
159
241
  Drawer,
@@ -1 +1 @@
1
- {"version":3,"file":"modal.esm.js","sources":["../src/ModalOverlay.tsx","../src/ModalContent.tsx","../src/Modal.tsx","../src/Drawer.tsx","../src/index.tsx"],"sourcesContent":["import React from 'react';\nimport classNames from 'classnames';\nimport { DialogOverlay } from '@reach/dialog';\n\nexport type ModalOverlayProps = {\n /** Flagg som sier om modalen er åpen */\n open?: boolean;\n /** Callback som kalles når brukeren ber om å lukke modalen */\n onDismiss?: () => void;\n /** Innholdet i modalen */\n children: React.ReactNode;\n /** Ekstra klassenavn */\n className?: string;\n /** En ref til elementet som skal være fokusert når modalen åpnes. Defaulter til lukkeknappen */\n initialFocusRef?: React.RefObject<HTMLElement>;\n [key: string]: any;\n};\n\nexport const ModalOverlay: React.FC<ModalOverlayProps> = ({\n className,\n open,\n ...rest\n}) => (\n <DialogOverlay\n className={classNames('eds-modal__overlay', className)}\n isOpen={open}\n {...rest}\n />\n);\n","import React from 'react';\nimport classNames from 'classnames';\nimport { DialogContent } from '@reach/dialog';\nimport { Heading2, Heading3, Heading4 } from '@entur/typography';\nimport { useRandomId } from '@entur/utils';\n\nexport type ModalContentProps = {\n /** Innholdet i modalen */\n children: React.ReactNode;\n /** Ekstra klassenavn */\n className?: string;\n /** Størrelsen på modalen */\n size: 'extraSmall' | 'small' | 'medium' | 'large' | 'extraLarge';\n /** Tittelen som vises i modalen */\n title?: React.ReactNode;\n /** Hvordan innholdet skal plasseres i modalen\n * @default 'start'\n */\n align?: 'start' | 'center' | 'end';\n [key: string]: any;\n};\n\nexport const headingsMap = {\n extraSmall: Heading4,\n small: Heading3,\n medium: Heading2,\n large: Heading2,\n extraLarge: Heading2,\n};\n\nexport const ModalContent: React.FC<ModalContentProps> = ({\n children,\n className,\n size,\n title,\n align = 'start',\n ...rest\n}) => {\n const Heading: React.ElementType = headingsMap[size] || Heading2;\n const randomId = useRandomId('eds-modal');\n return (\n <DialogContent\n className={classNames(\n 'eds-modal__content',\n `eds-modal__content--size-${size}`,\n `eds-modal__content--align-${align}`,\n className,\n )}\n aria-labelledby={randomId}\n {...rest}\n >\n {title && (\n <Heading margin=\"bottom\" as=\"h2\" id={randomId}>\n {title}\n </Heading>\n )}\n {children}\n </DialogContent>\n );\n};\n","import React from 'react';\n\nimport { CloseIcon } from '@entur/icons';\nimport { IconButton } from '@entur/button';\nimport { Heading2 } from '@entur/typography';\nimport { useRandomId } from '@entur/utils';\n\nimport { ModalOverlay } from './ModalOverlay';\nimport { ModalContent, headingsMap } from './ModalContent';\n\nimport './Modal.scss';\n\nexport type ModalProps = {\n /** Innholdet i modalen */\n children: React.ReactNode;\n /** Skjermleser-label til lukk-knappen */\n closeLabel?: string;\n /** En ref til elementet som skal være fokusert når modalen åpnes. Defaulter til lukkeknappen */\n initialFocusRef?: React.RefObject<HTMLElement>;\n /** Flagg som sier om modalen er åpen */\n open?: boolean;\n /** Callback som kalles når brukeren ber om å lukke modalen */\n onDismiss?: () => void;\n /** Størrelsen på modalen */\n size: 'extraSmall' | 'small' | 'medium' | 'large' | 'extraLarge';\n /** Hvordan innholdet skal plasseres i modalen\n * @default 'start'\n */\n align?: 'start' | 'center' | 'end';\n /** Tittelen som vises i modalen */\n title?: React.ReactNode;\n /** Om modalen skal lukkes når man klikker på utsiden av den\n * @default true\n */\n closeOnClickOutside?: boolean;\n [key: string]: any;\n};\n\nexport const Modal: React.FC<ModalProps> = ({\n children,\n closeLabel = 'Lukk',\n initialFocusRef,\n open,\n onDismiss,\n size,\n align = 'start',\n title,\n closeOnClickOutside = true,\n ...rest\n}) => {\n const randomId = useRandomId('eds-modal');\n const Heading: React.ElementType = headingsMap[size] || Heading2;\n const showCloseButton = ['medium', 'large', 'extraLarge'].includes(size);\n\n let handleOnDismiss;\n if (onDismiss && closeOnClickOutside) {\n handleOnDismiss = onDismiss;\n }\n return (\n <ModalOverlay\n open={open}\n onDismiss={handleOnDismiss}\n initialFocusRef={initialFocusRef}\n >\n <ModalContent size={size} align={align} {...rest}>\n {showCloseButton && (\n <IconButton\n className=\"eds-modal__close\"\n aria-label={closeLabel}\n onClick={onDismiss}\n type=\"button\"\n >\n <CloseIcon />\n </IconButton>\n )}\n {title && (\n <Heading margin=\"bottom\" as=\"h2\" id={randomId}>\n {title}\n </Heading>\n )}\n {children}\n </ModalContent>\n </ModalOverlay>\n );\n};\n","import React from 'react';\nimport classNames from 'classnames';\nimport { MoveFocusInside } from 'react-focus-lock';\nimport { Contrast } from '@entur/layout';\nimport { CloseIcon } from '@entur/icons';\nimport { Heading3 } from '@entur/typography';\nimport { useRandomId } from '@entur/utils';\nimport { IconButton } from '@entur/button';\n\nimport './Drawer.scss';\nimport { ModalOverlay } from './ModalOverlay';\nimport { DialogContent } from '@reach/dialog';\n\nexport type DrawerProps = {\n /** Innholdet. Typisk tekst, lenker eller knapper */\n children: React.ReactNode;\n /** Ekstra klassenavn */\n className?: string;\n /** Tekst som beskriver lukkeknappen for skjermlesere\n * @default 'Lukk skuff'\n */\n closeLabel?: string;\n /** Om draweren skal vises i mørk variant\n * @default false\n */\n contrast?: boolean;\n /** Callback som kalles når brukeren ønsker å lukke draweren */\n onDismiss: () => void;\n /** Om draweren er åpen eller ikke\n * @default true\n */\n open?: boolean;\n /** Tittel på toppen av draweren */\n title: string;\n /** Styling som sendes til Drawer */\n style?: React.CSSProperties;\n /** Legger på et overlay over resten av siden */\n overlay?: boolean;\n};\n\nexport const Drawer: React.FC<DrawerProps> = ({\n children,\n className,\n closeLabel = 'Lukk skuff',\n contrast = false,\n open = true,\n onDismiss,\n title,\n style,\n overlay = false,\n}) => {\n const titleId = useRandomId('eds-drawer');\n\n if (!open) {\n return null;\n }\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (e.key === 'Escape') {\n e.stopPropagation();\n onDismiss();\n }\n };\n\n const Wrapper = contrast ? Contrast : React.Fragment;\n const ContentContainer = overlay ? DialogContent : 'div';\n return (\n <ConditionalWrapper\n condition={overlay}\n wrapper={(children: React.ReactNode) => (\n <ModalOverlay open={open} onDismiss={onDismiss}>\n {children}\n </ModalOverlay>\n )}\n >\n <Wrapper>\n <ContentContainer\n aria-labelledby={titleId}\n className={classNames('eds-drawer', className)}\n onKeyDown={handleKeyDown}\n style={style}\n >\n <MoveFocusInside>\n <IconButton\n className=\"eds-drawer__close-button\"\n onClick={onDismiss}\n type=\"button\"\n aria-label={closeLabel}\n >\n <CloseIcon aria-hidden />\n </IconButton>\n <div className=\"eds-drawer__content\">\n <Heading3 as=\"h2\" id={titleId}>\n {title}\n </Heading3>\n {children}\n </div>\n </MoveFocusInside>\n </ContentContainer>\n </Wrapper>\n </ConditionalWrapper>\n );\n};\n\nconst ConditionalWrapper: React.FC<{\n condition: boolean;\n wrapper: (child: JSX.Element) => JSX.Element;\n children: React.ReactElement;\n}> = ({ condition, wrapper, children }) =>\n condition ? wrapper(children) : children;\n","import { warnAboutMissingStyles } from '@entur/utils';\nimport './index.scss';\n\nwarnAboutMissingStyles('modal', 'icons', 'typography', 'a11y', 'button');\n\nexport * from './Modal';\nexport * from './ModalOverlay';\nexport * from './ModalContent';\nexport * from './Drawer';\n"],"names":["children"],"mappings":"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"}
1
+ {"version":3,"file":"modal.esm.js","sources":["../src/ModalOverlay.tsx","../src/ModalContent.tsx","../src/Modal.tsx","../src/Drawer.tsx","../src/index.tsx"],"sourcesContent":["import React, { useCallback, useEffect, useRef } from 'react';\nimport { createPortal } from 'react-dom';\nimport classNames from 'classnames';\nimport { ContrastContext } from '@entur/layout';\n\nexport type ModalOverlayProps = {\n /** Flagg som sier om modalen er åpen */\n open?: boolean;\n /** Callback som kalles når brukeren ber om å lukke modalen */\n onDismiss?: () => void;\n /** Innholdet i modalen */\n children: React.ReactNode;\n /** Ekstra klassenavn */\n className?: string;\n /** En ref til elementet som skal være fokusert når modalen åpnes. Defaulter til lukkeknappen */\n initialFocusRef?: React.RefObject<HTMLElement>;\n /** Om modalen skal lukkes når man klikker utenfor den\n * @default true\n */\n closeOnClickOutside?: boolean;\n} & Omit<React.DialogHTMLAttributes<HTMLDialogElement>, 'open' | 'onClick'>;\n\nexport const ModalOverlay = ({\n className,\n open,\n onDismiss,\n initialFocusRef,\n closeOnClickOutside = true,\n children,\n ...rest\n}: ModalOverlayProps) => {\n const dialogRef = useRef<HTMLDialogElement>(null);\n\n // Toggle native dialog open state. Always stays in DOM so the browser can\n // manage initial focus storage + restore on close.\n useEffect(() => {\n const dialog = dialogRef.current;\n if (!dialog) return;\n if (open && !dialog.open) {\n dialog.showModal();\n const target =\n initialFocusRef?.current ??\n dialog.querySelector<HTMLElement>('[data-autofocus]') ??\n dialog;\n target.focus();\n } else if (!open && dialog.open) {\n dialog.close();\n }\n }, [open, initialFocusRef]);\n\n // Esc fires cancel — preventDefault so we route through onDismiss + state,\n // which triggers the effect above to call close() (native restore runs).\n useEffect(\n function syncCancelEvent() {\n const dialog = dialogRef.current;\n if (!dialog || !onDismiss) return;\n const handleCancel = (e: Event) => {\n e.preventDefault();\n onDismiss();\n };\n dialog.addEventListener('cancel', handleCancel);\n return () => dialog.removeEventListener('cancel', handleCancel);\n },\n [onDismiss],\n );\n\n const handleClick = useCallback(\n (e: React.MouseEvent<HTMLDialogElement>) => {\n if (!closeOnClickOutside) return;\n if (e.target === dialogRef.current) onDismiss?.();\n },\n [onDismiss, closeOnClickOutside],\n );\n\n if (typeof document === 'undefined') return null;\n\n return createPortal(\n <ContrastContext.Provider value={false}>\n <dialog\n ref={dialogRef}\n className={classNames('eds-modal__overlay', className)}\n {...rest}\n onClick={handleClick}\n >\n {open && children}\n </dialog>\n </ContrastContext.Provider>,\n document.body,\n );\n};\n","import React, { useId } from 'react';\nimport classNames from 'classnames';\nimport { Heading4, Heading3, Heading2 } from '@entur/typography';\n\nexport type ModalContentProps = {\n /** Innholdet i modalen */\n children: React.ReactNode;\n /** Ekstra klassenavn */\n className?: string;\n /** Størrelsen på modalen */\n size: 'extraSmall' | 'small' | 'medium' | 'large' | 'extraLarge';\n /** Tittelen som vises i modalen */\n title?: React.ReactNode;\n /** Hvordan innholdet skal plasseres i modalen\n * @default 'start'\n */\n align?: 'start' | 'center' | 'end';\n} & Omit<React.HTMLAttributes<HTMLDivElement>, 'title'>;\n\nexport const headingsMap = {\n extraSmall: Heading4,\n small: Heading3,\n medium: Heading2,\n large: Heading2,\n extraLarge: Heading2,\n};\n\nexport const ModalContent = ({\n children,\n className,\n size,\n title,\n align = 'start',\n ...rest\n}: ModalContentProps) => {\n const Heading: React.ElementType = headingsMap[size] || Heading2;\n const randomId = useId();\n return (\n <div\n className={classNames(\n 'eds-modal__content',\n `eds-modal__content--size-${size}`,\n `eds-modal__content--align-${align}`,\n className,\n )}\n aria-labelledby={title ? randomId : undefined}\n {...rest}\n >\n {title && (\n <Heading margin=\"bottom\" as=\"h2\" id={randomId}>\n {title}\n </Heading>\n )}\n {children}\n </div>\n );\n};\n","import React, { useId } from 'react';\n\nimport { CloseIcon } from '@entur/icons';\nimport { IconButton } from '@entur/button';\nimport { Heading2 } from '@entur/typography';\n\nimport { ModalOverlay } from './ModalOverlay';\nimport { ModalContent, headingsMap } from './ModalContent';\n\nimport './Modal.scss';\n\nexport type ModalProps = {\n /** Innholdet i modalen */\n children: React.ReactNode;\n /** Skjermleser-label til lukk-knappen */\n closeLabel?: string;\n /** En ref til elementet som skal være fokusert når modalen åpnes. Defaulter til tittelen, ellers første interaktive element */\n initialFocusRef?: React.RefObject<HTMLElement>;\n /** Styrer om modalen er synlig */\n open?: boolean;\n /** Callback som kalles når brukeren ber om å lukke modalen. Påkrevd: Esc og klikk utenfor lukker alltid (nettleserens innebygde atferd for native dialog) */\n onDismiss: () => void;\n /** Om lukkeknappen skal vises i øvre høyre hjørne\n * @default true\n */\n showCloseButton?: boolean;\n /** Størrelsen på modalen */\n size: 'extraSmall' | 'small' | 'medium' | 'large' | 'extraLarge';\n /** Hvordan innholdet skal plasseres i modalen\n * @default 'start'\n */\n align?: 'start' | 'center' | 'end';\n /** Tittelen som vises i modalen */\n title?: React.ReactNode;\n /** Tilgjengelig navn for modalen når title ikke er satt */\n 'aria-label'?: string;\n /** Om modalen skal lukkes når man klikker på utsiden av den\n * @default true\n */\n closeOnClickOutside?: boolean;\n} & Omit<React.HTMLAttributes<HTMLDivElement>, 'title' | 'aria-label'>;\n\nexport const Modal = ({\n children,\n closeLabel = 'Lukk',\n initialFocusRef,\n open,\n onDismiss,\n size,\n align = 'start',\n title,\n closeOnClickOutside = true,\n showCloseButton = true,\n 'aria-label': ariaLabel,\n ...rest\n}: ModalProps) => {\n const randomId = useId();\n const Heading: React.ElementType = headingsMap[size] || Heading2;\n\n return (\n <ModalOverlay\n open={open}\n onDismiss={onDismiss}\n closeOnClickOutside={closeOnClickOutside}\n initialFocusRef={initialFocusRef}\n className={`eds-modal__overlay--size-${size}`}\n aria-labelledby={title ? randomId : undefined}\n aria-label={!title ? ariaLabel : undefined}\n >\n {showCloseButton && (\n <IconButton\n className=\"eds-modal__close\"\n aria-label={closeLabel}\n onClick={onDismiss}\n type=\"button\"\n >\n <CloseIcon />\n </IconButton>\n )}\n <ModalContent size={size} align={align} {...rest}>\n {title && (\n <Heading\n margin=\"bottom\"\n as=\"h2\"\n id={randomId}\n tabIndex={-1}\n data-autofocus={initialFocusRef ? undefined : ''}\n >\n {title}\n </Heading>\n )}\n {children}\n </ModalContent>\n </ModalOverlay>\n );\n};\n","import React, { useEffect, useId, useRef } from 'react';\nimport classNames from 'classnames';\nimport { Contrast } from '@entur/layout';\nimport { CloseIcon } from '@entur/icons';\nimport { Heading3 } from '@entur/typography';\nimport { IconButton } from '@entur/button';\nimport { ConditionalWrapper } from '@entur/utils';\nimport { ModalOverlay } from './ModalOverlay';\n\nimport './Drawer.scss';\n\nexport type DrawerProps = {\n /** Innholdet. Typisk tekst, lenker eller knapper */\n children: React.ReactNode;\n /** Ekstra klassenavn */\n className?: string;\n /** Tekst som beskriver lukkeknappen for skjermlesere\n * @default 'Lukk skuff'\n */\n closeLabel?: string;\n /** Om draweren skal vises i mørk variant\n * @default false\n */\n contrast?: boolean;\n /** Callback som kalles når brukeren ønsker å lukke draweren */\n onDismiss: () => void;\n /** Om draweren er åpen eller ikke\n * @default true\n */\n open?: boolean;\n /** Tittel på toppen av draweren */\n title: string;\n /** Styling som sendes til Drawer */\n style?: React.CSSProperties;\n /** Legger på et overlay over resten av siden */\n overlay?: boolean;\n};\n\nexport const Drawer = ({\n children,\n className,\n closeLabel = 'Lukk skuff',\n contrast = false,\n open = true,\n onDismiss,\n title,\n style,\n overlay = false,\n}: DrawerProps) => {\n const titleId = useId();\n const drawerRef = useRef<HTMLDivElement>(null);\n const previouslyFocusedRef = useRef<Element | null>(null);\n\n useEffect(\n function handleFocus() {\n if (!open || overlay) return;\n previouslyFocusedRef.current = document.activeElement;\n drawerRef.current\n ?.querySelector<HTMLElement>('[data-autofocus]')\n ?.focus();\n\n return () => {\n if (previouslyFocusedRef.current instanceof HTMLElement) {\n previouslyFocusedRef.current.focus();\n }\n };\n },\n [open, overlay],\n );\n\n const shouldRemoveFromDOM = !open && !overlay;\n if (shouldRemoveFromDOM) return null;\n\n function handleKeyDown(e: React.KeyboardEvent) {\n if (e.key === 'Escape') {\n e.stopPropagation();\n onDismiss();\n }\n }\n\n const Wrapper = contrast ? Contrast : 'div';\n const nonModalDialogProps = {\n role: 'dialog' as const,\n 'aria-modal': false,\n 'aria-labelledby': titleId,\n };\n\n return (\n <ConditionalWrapper\n condition={overlay}\n wrapper={(children: React.ReactNode) => (\n <ModalOverlay\n open={open}\n onDismiss={onDismiss}\n aria-labelledby={titleId}\n >\n {children}\n </ModalOverlay>\n )}\n >\n <Wrapper\n ref={drawerRef}\n {...(!overlay && nonModalDialogProps)}\n className={classNames('eds-drawer', className)}\n onKeyDown={handleKeyDown}\n style={style}\n >\n <IconButton\n className=\"eds-drawer__close-button\"\n onClick={onDismiss}\n type=\"button\"\n aria-label={closeLabel}\n >\n <CloseIcon aria-hidden />\n </IconButton>\n <div className=\"eds-drawer__content\">\n <Heading3 as=\"h2\" id={titleId} tabIndex={-1} data-autofocus=\"\">\n {title}\n </Heading3>\n {children}\n </div>\n </Wrapper>\n </ConditionalWrapper>\n );\n};\n","import { warnAboutMissingStyles } from '@entur/utils';\nimport './index.scss';\n\nwarnAboutMissingStyles('modal', 'icons', 'typography', 'a11y', 'button');\n\nexport * from './Modal';\nexport * from './ModalOverlay';\nexport * from './ModalContent';\nexport * from './Drawer';\n"],"names":["children"],"mappings":";;;;;;;;;AAsBO,MAAM,eAAe,CAAC;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,sBAAsB;AAAA,EACtB;AAAA,EACA,GAAG;AACL,MAAyB;AACvB,QAAM,YAAY,OAA0B,IAAI;AAIhD,YAAU,MAAM;AACd,UAAM,SAAS,UAAU;AACzB,QAAI,CAAC,OAAQ;AACb,QAAI,QAAQ,CAAC,OAAO,MAAM;AACxB,aAAO,UAAA;AACP,YAAM,SACJ,iBAAiB,WACjB,OAAO,cAA2B,kBAAkB,KACpD;AACF,aAAO,MAAA;AAAA,IACT,WAAW,CAAC,QAAQ,OAAO,MAAM;AAC/B,aAAO,MAAA;AAAA,IACT;AAAA,EACF,GAAG,CAAC,MAAM,eAAe,CAAC;AAI1B;AAAA,IACE,SAAS,kBAAkB;AACzB,YAAM,SAAS,UAAU;AACzB,UAAI,CAAC,UAAU,CAAC,UAAW;AAC3B,YAAM,eAAe,CAAC,MAAa;AACjC,UAAE,eAAA;AACF,kBAAA;AAAA,MACF;AACA,aAAO,iBAAiB,UAAU,YAAY;AAC9C,aAAO,MAAM,OAAO,oBAAoB,UAAU,YAAY;AAAA,IAChE;AAAA,IACA,CAAC,SAAS;AAAA,EAAA;AAGZ,QAAM,cAAc;AAAA,IAClB,CAAC,MAA2C;AAC1C,UAAI,CAAC,oBAAqB;AAC1B,UAAI,EAAE,WAAW,UAAU,QAAS,aAAA;AAAA,IACtC;AAAA,IACA,CAAC,WAAW,mBAAmB;AAAA,EAAA;AAGjC,MAAI,OAAO,aAAa,YAAa,QAAO;AAE5C,SAAO;AAAA,IACL,oBAAC,gBAAgB,UAAhB,EAAyB,OAAO,OAC/B,UAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,KAAK;AAAA,QACL,WAAW,WAAW,sBAAsB,SAAS;AAAA,QACpD,GAAG;AAAA,QACJ,SAAS;AAAA,QAER,UAAA,QAAQ;AAAA,MAAA;AAAA,IAAA,GAEb;AAAA,IACA,SAAS;AAAA,EAAA;AAEb;ACtEO,MAAM,cAAc;AAAA,EACzB,YAAY;AAAA,EACZ,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,YAAY;AACd;AAEO,MAAM,eAAe,CAAC;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR,GAAG;AACL,MAAyB;AACvB,QAAM,UAA6B,YAAY,IAAI,KAAK;AACxD,QAAM,WAAW,MAAA;AACjB,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA,4BAA4B,IAAI;AAAA,QAChC,6BAA6B,KAAK;AAAA,QAClC;AAAA,MAAA;AAAA,MAEF,mBAAiB,QAAQ,WAAW;AAAA,MACnC,GAAG;AAAA,MAEH,UAAA;AAAA,QAAA,SACC,oBAAC,WAAQ,QAAO,UAAS,IAAG,MAAK,IAAI,UAClC,UAAA,MAAA,CACH;AAAA,QAED;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGP;ACdO,MAAM,QAAQ,CAAC;AAAA,EACpB;AAAA,EACA,aAAa;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR;AAAA,EACA,sBAAsB;AAAA,EACtB,kBAAkB;AAAA,EAClB,cAAc;AAAA,EACd,GAAG;AACL,MAAkB;AAChB,QAAM,WAAW,MAAA;AACjB,QAAM,UAA6B,YAAY,IAAI,KAAK;AAExD,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,WAAW,4BAA4B,IAAI;AAAA,MAC3C,mBAAiB,QAAQ,WAAW;AAAA,MACpC,cAAY,CAAC,QAAQ,YAAY;AAAA,MAEhC,UAAA;AAAA,QAAA,mBACC;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,WAAU;AAAA,YACV,cAAY;AAAA,YACZ,SAAS;AAAA,YACT,MAAK;AAAA,YAEL,8BAAC,WAAA,CAAA,CAAU;AAAA,UAAA;AAAA,QAAA;AAAA,QAGf,qBAAC,cAAA,EAAa,MAAY,OAAe,GAAG,MACzC,UAAA;AAAA,UAAA,SACC;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,QAAO;AAAA,cACP,IAAG;AAAA,cACH,IAAI;AAAA,cACJ,UAAU;AAAA,cACV,kBAAgB,kBAAkB,SAAY;AAAA,cAE7C,UAAA;AAAA,YAAA;AAAA,UAAA;AAAA,UAGJ;AAAA,QAAA,EAAA,CACH;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGN;ACzDO,MAAM,SAAS,CAAC;AAAA,EACrB;AAAA,EACA;AAAA,EACA,aAAa;AAAA,EACb,WAAW;AAAA,EACX,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AACZ,MAAmB;AACjB,QAAM,UAAU,MAAA;AAChB,QAAM,YAAY,OAAuB,IAAI;AAC7C,QAAM,uBAAuB,OAAuB,IAAI;AAExD;AAAA,IACE,SAAS,cAAc;AACrB,UAAI,CAAC,QAAQ,QAAS;AACtB,2BAAqB,UAAU,SAAS;AACxC,gBAAU,SACN,cAA2B,kBAAkB,GAC7C,MAAA;AAEJ,aAAO,MAAM;AACX,YAAI,qBAAqB,mBAAmB,aAAa;AACvD,+BAAqB,QAAQ,MAAA;AAAA,QAC/B;AAAA,MACF;AAAA,IACF;AAAA,IACA,CAAC,MAAM,OAAO;AAAA,EAAA;AAGhB,QAAM,sBAAsB,CAAC,QAAQ,CAAC;AACtC,MAAI,oBAAqB,QAAO;AAEhC,WAAS,cAAc,GAAwB;AAC7C,QAAI,EAAE,QAAQ,UAAU;AACtB,QAAE,gBAAA;AACF,gBAAA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,UAAU,WAAW,WAAW;AACtC,QAAM,sBAAsB;AAAA,IAC1B,MAAM;AAAA,IACN,cAAc;AAAA,IACd,mBAAmB;AAAA,EAAA;AAGrB,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAW;AAAA,MACX,SAAS,CAACA,cACR;AAAA,QAAC;AAAA,QAAA;AAAA,UACC;AAAA,UACA;AAAA,UACA,mBAAiB;AAAA,UAEhB,UAAAA;AAAAA,QAAA;AAAA,MAAA;AAAA,MAIL,UAAA;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,KAAK;AAAA,UACJ,GAAI,CAAC,WAAW;AAAA,UACjB,WAAW,WAAW,cAAc,SAAS;AAAA,UAC7C,WAAW;AAAA,UACX;AAAA,UAEA,UAAA;AAAA,YAAA;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,WAAU;AAAA,gBACV,SAAS;AAAA,gBACT,MAAK;AAAA,gBACL,cAAY;AAAA,gBAEZ,UAAA,oBAAC,WAAA,EAAU,eAAW,KAAA,CAAC;AAAA,cAAA;AAAA,YAAA;AAAA,YAEzB,qBAAC,OAAA,EAAI,WAAU,uBACb,UAAA;AAAA,cAAA,oBAAC,UAAA,EAAS,IAAG,MAAK,IAAI,SAAS,UAAU,IAAI,kBAAe,IACzD,UAAA,MAAA,CACH;AAAA,cACC;AAAA,YAAA,EAAA,CACH;AAAA,UAAA;AAAA,QAAA;AAAA,MAAA;AAAA,IACF;AAAA,EAAA;AAGN;ACzHA,uBAAuB,SAAS,SAAS,cAAc,QAAQ,QAAQ;"}
package/dist/styles.css CHANGED
@@ -199,35 +199,71 @@
199
199
 
200
200
  :root{
201
201
  --eds-modal:1;
202
- }:root{
203
- --reach-dialog:1;
202
+ }html:has(dialog.eds-modal__overlay[open]),
203
+ html:has(dialog.eds-modal__overlay[open]) body{
204
+ overflow:hidden;
204
205
  }
205
206
 
206
207
  .eds-modal__overlay{
207
- background:rgba(0, 0, 0, 0.5);
208
- bottom:0;
209
- display:-webkit-box;
210
- display:-webkit-flex;
211
- display:-moz-box;
212
- display:flex;
213
- left:0;
214
- overflow:hidden;
215
- position:fixed;
216
- right:0;
217
- top:0;
208
+ border:none;
209
+ padding:0;
210
+ background:transparent;
211
+ color:inherit;
212
+ }
213
+ .eds-modal__overlay[open]{
218
214
  z-index:30;
219
- -webkit-animation:fadeInOverlay;
220
- -moz-animation:fadeInOverlay;
221
- -o-animation:fadeInOverlay;
222
- animation:fadeInOverlay;
223
- -webkit-animation-duration:0.2s;
224
- -moz-animation-duration:0.2s;
225
- -o-animation-duration:0.2s;
226
- animation-duration:0.2s;
227
- -webkit-animation-timing-function:ease-in-out;
228
- -moz-animation-timing-function:ease-in-out;
229
- -o-animation-timing-function:ease-in-out;
230
- animation-timing-function:ease-in-out;
215
+ width:100%;
216
+ overflow:hidden;
217
+ -ms-scroll-chaining:none;
218
+ overscroll-behavior:contain;
219
+ -webkit-animation:fadeInOverlay 0.2s ease-in-out;
220
+ -moz-animation:fadeInOverlay 0.2s ease-in-out;
221
+ -o-animation:fadeInOverlay 0.2s ease-in-out;
222
+ animation:fadeInOverlay 0.2s ease-in-out;
223
+ }
224
+ .eds-modal__overlay[open]::-webkit-backdrop{
225
+ background:rgba(0, 0, 0, 0.5);
226
+ -webkit-animation:fadeInOverlay 0.2s ease-in-out;
227
+ animation:fadeInOverlay 0.2s ease-in-out;
228
+ }
229
+ .eds-modal__overlay[open]::-ms-backdrop{
230
+ background:rgba(0, 0, 0, 0.5);
231
+ animation:fadeInOverlay 0.2s ease-in-out;
232
+ }
233
+ .eds-modal__overlay[open]::backdrop{
234
+ background:rgba(0, 0, 0, 0.5);
235
+ -webkit-animation:fadeInOverlay 0.2s ease-in-out;
236
+ -moz-animation:fadeInOverlay 0.2s ease-in-out;
237
+ -o-animation:fadeInOverlay 0.2s ease-in-out;
238
+ animation:fadeInOverlay 0.2s ease-in-out;
239
+ }
240
+ .eds-modal__overlay--size-extraSmall{
241
+ max-width:21rem;
242
+ }
243
+ .eds-modal__overlay--size-small{
244
+ max-width:28.125rem;
245
+ }
246
+ .eds-modal__overlay--size-medium{
247
+ max-width:45rem;
248
+ }
249
+ .eds-modal__overlay--size-large{
250
+ max-width:56.25rem;
251
+ }
252
+ .eds-modal__overlay--size-extraLarge{
253
+ max-width:78.75rem;
254
+ }
255
+ .eds-modal__close{
256
+ position:absolute;
257
+ top:1rem;
258
+ right:1rem;
259
+ color:var(--components-modal-modal-standard-icon);
260
+ z-index:-webkit-calc(40 + 1);
261
+ z-index:-moz-calc(40 + 1);
262
+ z-index:calc(40 + 1);
263
+ -webkit-animation:slideInContent 0.2s ease-in-out;
264
+ -moz-animation:slideInContent 0.2s ease-in-out;
265
+ -o-animation:slideInContent 0.2s ease-in-out;
266
+ animation:slideInContent 0.2s ease-in-out;
231
267
  }
232
268
  .eds-modal__content{
233
269
  background:var(--components-modal-modal-standard-fill);
@@ -236,25 +272,14 @@
236
272
  -moz-border-radius:0.75rem;
237
273
  border-radius:0.75rem;
238
274
  color:var(--components-modal-modal-standard-text);
239
- margin:auto;
240
275
  padding:1.5rem;
241
- position:relative;
242
276
  width:100%;
243
277
  max-height:90vh;
244
278
  overflow:auto;
245
- z-index:40;
246
- -webkit-animation:slideInContent;
247
- -moz-animation:slideInContent;
248
- -o-animation:slideInContent;
249
- animation:slideInContent;
250
- -webkit-animation-duration:0.2s;
251
- -moz-animation-duration:0.2s;
252
- -o-animation-duration:0.2s;
253
- animation-duration:0.2s;
254
- -webkit-animation-timing-function:ease-in-out;
255
- -moz-animation-timing-function:ease-in-out;
256
- -o-animation-timing-function:ease-in-out;
257
- animation-timing-function:ease-in-out;
279
+ -webkit-animation:slideInContent 0.2s ease-in-out;
280
+ -moz-animation:slideInContent 0.2s ease-in-out;
281
+ -o-animation:slideInContent 0.2s ease-in-out;
282
+ animation:slideInContent 0.2s ease-in-out;
258
283
  }
259
284
  .eds-modal__content--align-center{
260
285
  display:-webkit-box;
@@ -296,18 +321,22 @@
296
321
  .eds-modal__content--size-small{
297
322
  max-width:28.125rem;
298
323
  padding:2.5rem;
324
+ padding-top:2rem;
299
325
  }
300
326
  .eds-modal__content--size-medium{
301
327
  max-width:45rem;
302
328
  padding:3rem;
329
+ padding-top:2rem;
303
330
  }
304
331
  .eds-modal__content--size-large{
305
332
  max-width:56.25rem;
306
333
  padding:3rem;
334
+ padding-top:2rem;
307
335
  }
308
336
  .eds-modal__content--size-extraLarge{
309
337
  max-width:78.75rem;
310
338
  padding:3rem;
339
+ padding-top:2rem;
311
340
  }
312
341
  .eds-modal__content::-webkit-scrollbar{
313
342
  width:16px;
@@ -325,12 +354,6 @@
325
354
  .eds-modal__content::-webkit-scrollbar-thumb:hover{
326
355
  background-color:grey;
327
356
  }
328
- .eds-modal__close{
329
- position:absolute;
330
- top:1rem;
331
- right:1rem;
332
- color:var(--components-modal-modal-standard-icon);
333
- }
334
357
 
335
358
  @-webkit-keyframes fadeInOverlay{
336
359
  from{
@@ -369,43 +392,73 @@
369
392
  }
370
393
  @-webkit-keyframes slideInContent{
371
394
  from{
372
- top:5rem;
395
+ -webkit-transform:translateY(0.5rem);
396
+ transform:translateY(0.5rem);
373
397
  opacity:0;
374
398
  }
375
399
  to{
376
- top:0%;
400
+ -webkit-transform:translateY(0);
401
+ transform:translateY(0);
377
402
  opacity:1;
378
403
  }
379
404
  }
380
405
  @-moz-keyframes slideInContent{
381
406
  from{
382
- top:5rem;
407
+ -moz-transform:translateY(0.5rem);
408
+ transform:translateY(0.5rem);
383
409
  opacity:0;
384
410
  }
385
411
  to{
386
- top:0%;
412
+ -moz-transform:translateY(0);
413
+ transform:translateY(0);
387
414
  opacity:1;
388
415
  }
389
416
  }
390
417
  @-o-keyframes slideInContent{
391
418
  from{
392
- top:5rem;
419
+ -o-transform:translateY(0.5rem);
420
+ transform:translateY(0.5rem);
393
421
  opacity:0;
394
422
  }
395
423
  to{
396
- top:0%;
424
+ -o-transform:translateY(0);
425
+ transform:translateY(0);
397
426
  opacity:1;
398
427
  }
399
428
  }
400
429
  @keyframes slideInContent{
401
430
  from{
402
- top:5rem;
431
+ -webkit-transform:translateY(0.5rem);
432
+ -moz-transform:translateY(0.5rem);
433
+ -o-transform:translateY(0.5rem);
434
+ transform:translateY(0.5rem);
403
435
  opacity:0;
404
436
  }
405
437
  to{
406
- top:0%;
438
+ -webkit-transform:translateY(0);
439
+ -moz-transform:translateY(0);
440
+ -o-transform:translateY(0);
441
+ transform:translateY(0);
407
442
  opacity:1;
408
443
  }
444
+ }
445
+ @media (prefers-reduced-motion: reduce){
446
+ .eds-modal__overlay::-webkit-backdrop{
447
+ -webkit-animation:none;
448
+ animation:none;
449
+ }
450
+ .eds-modal__overlay::-ms-backdrop{
451
+ animation:none;
452
+ }
453
+ .eds-modal__overlay,
454
+ .eds-modal__overlay::backdrop,
455
+ .eds-modal__content,
456
+ .eds-modal__close{
457
+ -webkit-animation:none;
458
+ -moz-animation:none;
459
+ -o-animation:none;
460
+ animation:none;
461
+ }
409
462
  }.eds-drawer{
410
463
  -webkit-animation:slideFromRight forwards ease-out 0.1s;
411
464
  -moz-animation:slideFromRight forwards ease-out 0.1s;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@entur/modal",
3
- "version": "1.8.15",
3
+ "version": "2.0.0-next.0",
4
4
  "license": "EUPL-1.2",
5
5
  "main": "dist/modal.cjs.js",
6
6
  "module": "dist/modal.esm.js",
@@ -22,24 +22,22 @@
22
22
  "test": "jest"
23
23
  },
24
24
  "peerDependencies": {
25
- "react": ">=16.8.0",
26
- "react-dom": ">=16.8.0"
25
+ "react": ">=18.0.0",
26
+ "react-dom": ">=18.0.0"
27
27
  },
28
28
  "dependencies": {
29
- "@entur/a11y": "^0.2.112",
30
- "@entur/button": "^4.0.10",
31
- "@entur/icons": "^9.0.3",
32
- "@entur/layout": "^3.7.4",
33
- "@entur/tokens": "^3.24.0",
34
- "@entur/typography": "^2.1.11",
35
- "@entur/utils": "^0.13.5",
36
- "@reach/dialog": "^0.16.2",
37
- "classnames": "^2.5.1",
38
- "react-focus-lock": "^2.13.6"
29
+ "@entur/a11y": "^1.0.0-next.0",
30
+ "@entur/button": "^5.0.0-next.0",
31
+ "@entur/icons": "^10.0.0-next.0",
32
+ "@entur/layout": "^4.0.0-next.0",
33
+ "@entur/tokens": "^3.24.1-next.0",
34
+ "@entur/typography": "^3.0.0-next.0",
35
+ "@entur/utils": "^1.0.0-next.0",
36
+ "classnames": "^2.5.1"
39
37
  },
40
38
  "devDependencies": {
41
39
  "@testing-library/jest-dom": "^5.17.0",
42
- "@testing-library/react": "^10.4.9",
40
+ "@testing-library/react": "^16.3.0",
43
41
  "@testing-library/user-event": "14.6.1",
44
42
  "@vitejs/plugin-react": "^5.0.1",
45
43
  "jest": "^29.0.0",
@@ -49,5 +47,5 @@
49
47
  "vite": "^7.3.2",
50
48
  "vite-plugin-dts": "^4.5.4"
51
49
  },
52
- "gitHead": "ac37425c6b442481549814941b317886e3a7defb"
50
+ "gitHead": "0f435bdeec98cc3cea1db7434b13b1f771a39a18"
53
51
  }