From 4c40e815b81b160aa166893a66579943d6d9a097 Mon Sep 17 00:00:00 2001 From: "Imad A. Bakir" Date: Tue, 8 Oct 2024 11:00:15 +0200 Subject: [PATCH 01/13] PLAT-3591 WIP --- src/assets/icons/_icons.ts | 1 + src/assets/icons/close-x.svg | 6 + .../overlays/Modal/Modal.module.scss | 56 +++-- .../overlays/Modal/Modal.stories.tsx | 5 +- src/components/overlays/Modal/Modal.tsx | 192 +++++++++++------- 5 files changed, 166 insertions(+), 94 deletions(-) create mode 100644 src/assets/icons/close-x.svg diff --git a/src/assets/icons/_icons.ts b/src/assets/icons/_icons.ts index a065710..b811d7d 100644 --- a/src/assets/icons/_icons.ts +++ b/src/assets/icons/_icons.ts @@ -50,4 +50,5 @@ export const icons = { 'user': {}, 'warning': {}, 'workflows': {}, + 'close-x': {}, } as const satisfies Record; diff --git a/src/assets/icons/close-x.svg b/src/assets/icons/close-x.svg new file mode 100644 index 0000000..0df5d96 --- /dev/null +++ b/src/assets/icons/close-x.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/components/overlays/Modal/Modal.module.scss b/src/components/overlays/Modal/Modal.module.scss index 480717a..fb7c633 100644 --- a/src/components/overlays/Modal/Modal.module.scss +++ b/src/components/overlays/Modal/Modal.module.scss @@ -13,28 +13,41 @@ /* --bk-modal-background-color: color-mix(in srgb, var(--bk-panel-background-color) 80%, transparent); */ --bk-modal-background-color: var(--bk-panel-background-color); - - --bk-modal-inset: calc(var(--bk-sizing-3) + var(--bk-sizing-2)); - + margin: auto; padding: 0; - width: var(--bk-sizing-9); - max-width: calc(100% - 6px - 2em); height: fit-content; - min-height: 60%; - max-height: calc(100% - 6px - 2em); + min-height: bk.$spacing-16; + max-width: calc(100% - bk.$spacing-6); + max-height: calc(100% - bk.$spacing-6); box-shadow: 0 8px 10px 1px rgba(0 0 0 / 14%), 0 3px 14px 2px rgba(0 0 0 / 12%), 0 5px 5px -3px rgba(0 0 0 / 3%); background-color: var(--bk-modal-background-color); - border-radius: var(--bk-sizing-2); - + border-radius: bk.$sizing-2; + display: none; /* flex */ flex-direction: column; - + &.bk-modal-small { + width: 34.5rem; + } + &.bk-modal-medium { + width: 48.8rem; + + } + &.bk-modal-large { + width: 56rem; + } + &.bk-modal-x-large { + width: 64.7rem; + } + &.bk-modal-fullscreen { + width: calc(100% - bk.$spacing-6); + height: calc(100% - bk.$spacing-6); + } .bk-modal__header { position: sticky; top: 0; - padding: var(--bk-sizing-2) var(--bk-modal-inset); + padding: 0; background: var(--bk-modal-background-color); @@ -56,15 +69,23 @@ :nth-child(1 of :global(.action)) { margin-left: auto; } - :global(.action) { - align-self: center; - } + + } + .bk-modal__close { + position: absolute; + right: bk.$spacing-4; + top: bk.$spacing-4; + } + .bk-modal__container { + padding: bk.$spacing-4 bk.$spacing-4 bk.$spacing-7 bk.$spacing-4; + flex-direction: column; + overflow: hidden; + display: flex; + flex: 1; } - .bk-modal__content { flex: 1; /* Make sure we cover all available space */ - padding: var(--bk-modal-inset); - padding-top: var(--bk-sizing-3); + overflow: auto; } @@ -161,6 +182,7 @@ display: flex; justify-content: center; align-items: center; + overflow: hidden; } } } diff --git a/src/components/overlays/Modal/Modal.stories.tsx b/src/components/overlays/Modal/Modal.stories.tsx index 84ba506..cc1ef7a 100644 --- a/src/components/overlays/Modal/Modal.stories.tsx +++ b/src/components/overlays/Modal/Modal.stories.tsx @@ -5,6 +5,7 @@ import * as React from 'react'; import type { Meta, StoryObj } from '@storybook/react'; +import { userEvent, within } from '@storybook/test'; import { ModalClassNames as cl } from './Modal.tsx'; import { OverflowTester } from '../../../util/storybook/OverflowTester.tsx'; @@ -45,9 +46,11 @@ export const Interactive: Story = { render: () => (
+

Title

This is a modal

+

Title

This is a submodal

@@ -76,7 +79,7 @@ const ModalWithSpinnerTrigger = ({ triggerLabel = 'Open modal with spinner (it w return ( <> - + ); }; diff --git a/src/components/overlays/Modal/Modal.tsx b/src/components/overlays/Modal/Modal.tsx index 245ca15..9eda768 100644 --- a/src/components/overlays/Modal/Modal.tsx +++ b/src/components/overlays/Modal/Modal.tsx @@ -1,12 +1,13 @@ -/* Copyright (c) Fortanix, Inc. -|* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of -|* the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { classNames as cx, type ClassNameArgument } from '../../../util/componentUtil.ts'; -import * as React from 'react'; +import { + classNames as cx, + type ClassNameArgument, +} from "../../../util/componentUtil.ts"; +import * as React from "react"; -import cl from './Modal.module.scss'; +import { Icon } from "../../graphics/Icon/Icon.tsx"; +import cl from "./Modal.module.scss"; export { cl as ModalClassNames }; @@ -41,80 +42,119 @@ const useClickOutside = (ref: React.RefObject, callbac }; */ - export type ModalProps = React.PropsWithChildren<{ - unstyled?: boolean, - active: boolean, - className?: ClassNameArgument, - onClose: () => void, - closeable?: boolean, + unstyled?: boolean, + size?: "small" | "medium" | "large" | "x-large" | "fullscreen", + active: boolean, + className?: ClassNameArgument, + onClose: () => void, + closeable?: boolean, }>; /** * Modal component. */ -export const Modal = ({ children, unstyled, className, active, onClose, closeable = true }: ModalProps) => { - const dialogRef = React.useRef(null); - - // Sync the `active` flag with the DOM dialog - React.useEffect(() => { - const dialog = dialogRef.current; - if (dialog === null) { return; } - - if (active && !dialog.open) { - dialog.showModal(); - } else if (!active && dialog.open) { - dialog.close(); - } - }, [active]); - - // Sync the dialog close event with the `active` flag - const handleCloseEvent = React.useCallback((event: Event): void => { - const dialog = dialogRef.current; - if (dialog === null) { return; } - - if (active && event.target === dialog) { - onClose(); - } - }, [active, onClose]); - React.useEffect(() => { - const dialog = dialogRef.current; - if (dialog === null) { return; } - - dialog.addEventListener('close', handleCloseEvent); - return () => { dialog.removeEventListener('close', handleCloseEvent); }; - }, [handleCloseEvent]); - - const close = React.useCallback(() => { - onClose(); - }, [onClose]); - - const handleDialogClick = React.useCallback((event: React.MouseEvent) => { - const dialog = dialogRef.current; - if (dialog !== null && event.target === dialog && closeable) { - // Note: clicking the backdrop just results in an event where the target is the `` element. In order to - // distinguish between the backdrop and the modal content, we assume that the `` is fully covered by - // another element. In our case, `bk-modal__content` must cover the whole `` otherwise this will not work. - close(); - } - }, [close]); - - return ( - -
-

Title

- {closeable && ()} -
- -
- {children} -
-
- ); +export const Modal = ({ + children, + unstyled, + className, + size = "medium", + closeable = true, + active, + onClose, +}: ModalProps) => { + const dialogRef = React.useRef(null); + + // Sync the `active` flag with the DOM dialog + React.useEffect(() => { + const dialog = dialogRef.current; + if (dialog === null) { + return; + } + + if (active && !dialog.open) { + dialog.showModal(); + } else if (!active && dialog.open) { + dialog.close(); + } + }, [active]); + + // Sync the dialog close event with the `active` flag + const handleCloseEvent = React.useCallback( + (event: Event): void => { + const dialog = dialogRef.current; + if (dialog === null) { + return; + } + + if (active && event.target === dialog) { + onClose(); + } + }, + [active, onClose], + ); + React.useEffect(() => { + const dialog = dialogRef.current; + if (dialog === null) { + return; + } + + dialog.addEventListener("close", handleCloseEvent); + return () => { + dialog.removeEventListener("close", handleCloseEvent); + }; + }, [handleCloseEvent]); + + const close = React.useCallback(() => { + onClose(); + }, [onClose]); + + const handleDialogClick = React.useCallback( + (event: React.MouseEvent) => { + const dialog = dialogRef.current; + if (dialog !== null && event.target === dialog) { + // Note: clicking the backdrop just results in an event where the target is the `` element. In order to + // distinguish between the backdrop and the modal content, we assume that the `` is fully covered by + // another element. In our case, `bk-modal__content` must cover the whole `` otherwise this will not work. + close(); + } + }, + [close], + ); + + return ( + // biome-ignore lint/a11y/useKeyWithClickEvents: + + {closeable && ( + + )} +
+
+ {children} +
+
+
+ ); }; From 908fc6e5ce1c7a9fa54e1374ae9c175c4e7b1168 Mon Sep 17 00:00:00 2001 From: Arlindo Pereira Date: Tue, 8 Oct 2024 16:22:20 +0200 Subject: [PATCH 02/13] Modal Component * Correct sizes, spacings etc. * Additional helper components ModalHeader and ModalContent * Fullscreen variant --- .../overlays/Modal/Modal.module.scss | 79 ++++++++--------- .../overlays/Modal/Modal.stories.tsx | 86 ++++++++++++++----- src/components/overlays/Modal/Modal.tsx | 62 +++++++++++-- 3 files changed, 158 insertions(+), 69 deletions(-) diff --git a/src/components/overlays/Modal/Modal.module.scss b/src/components/overlays/Modal/Modal.module.scss index fb7c633..b5639f0 100644 --- a/src/components/overlays/Modal/Modal.module.scss +++ b/src/components/overlays/Modal/Modal.module.scss @@ -8,19 +8,19 @@ @layer baklava.components { .bk-modal { @include bk.component-base(bk-modal); - + @include Panel.bk-panel; - + /* --bk-modal-background-color: color-mix(in srgb, var(--bk-panel-background-color) 80%, transparent); */ --bk-modal-background-color: var(--bk-panel-background-color); - + margin: auto; padding: 0; height: fit-content; min-height: bk.$spacing-16; max-width: calc(100% - bk.$spacing-6); max-height: calc(100% - bk.$spacing-6); - + box-shadow: 0 8px 10px 1px rgba(0 0 0 / 14%), 0 3px 14px 2px rgba(0 0 0 / 12%), 0 5px 5px -3px rgba(0 0 0 / 3%); background-color: var(--bk-modal-background-color); border-radius: bk.$sizing-2; @@ -28,56 +28,55 @@ display: none; /* flex */ flex-direction: column; &.bk-modal-small { - width: 34.5rem; + // 484px + width: calc(484 * bk.$size-1); } &.bk-modal-medium { - width: 48.8rem; - + // 684px + width: calc(684 * bk.$size-1); } &.bk-modal-large { - width: 56rem; + // 784px + width: calc(784 * bk.$size-1); } &.bk-modal-x-large { - width: 64.7rem; + // 906px + width: calc(906 * bk.$size-1); } &.bk-modal-fullscreen { - width: calc(100% - bk.$spacing-6); - height: calc(100% - bk.$spacing-6); + width: calc(100% - bk.$spacing-3); + height: calc(100% - bk.$spacing-3); } .bk-modal__header { position: sticky; top: 0; padding: 0; - + background: var(--bk-modal-background-color); - - --header-shadow-size: calc(var(--bk-sizing-1) / 2); - box-shadow: 0 var(--header-shadow-size) 0 0 rgba(0 0 0 / 12%); - /* Clip everything except the bottom shadow (-1px for weird clipping behavior with scroll) */ - clip-path: inset(-1px -1px calc(-1 * var(--header-shadow-size)) -1px); - + display: flex; flex-direction: row; align-items: baseline; - + margin-bottom: bk.$spacing-7; + h1 { - font-size: 1.4rem; - font-weight: 300; - text-transform: uppercase; + font-size: 16px; // do not match bk variable sizes + font-weight: bk.$font-weight-semibold; } - + :nth-child(1 of :global(.action)) { margin-left: auto; } - } .bk-modal__close { position: absolute; - right: bk.$spacing-4; - top: bk.$spacing-4; + right: bk.$spacing-8; + top: bk.$spacing-8; + z-index: 1; } .bk-modal__container { - padding: bk.$spacing-4 bk.$spacing-4 bk.$spacing-7 bk.$spacing-4; + padding: bk.$spacing-8; + padding-bottom: bk.$spacing-9; flex-direction: column; overflow: hidden; display: flex; @@ -87,8 +86,7 @@ flex: 1; /* Make sure we cover all available space */ overflow: auto; } - - + /* Variant: slide out */ --modal-slide-out-inset: var(--bk-sizing-3); &:is(.bk-modal--slide-out-left, .bk-modal--slide-out-right) { @@ -98,7 +96,7 @@ width: 60vw; height: auto; max-height: 100%; - + &.bk-modal--slide-out-left { right: auto; transform-origin: center left; @@ -111,26 +109,25 @@ border-top-right-radius: 0; border-bottom-right-radius: 0; } - + opacity: 1; transform: scaleX(0); &:modal { transform: scaleX(1); - + @starting-style { opacity: 1; transform: scaleX(0); } } - + .bk-modal__content { overflow: auto; } } - - + /* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog#animating_dialogs */ - + opacity: 0; scale: 0.98 0.98; transition: @@ -139,21 +136,21 @@ overlay 1ms ease-out allow-discrete, display 1ms ease-out allow-discrete; transition-duration: 200ms; /* Exit transition duration */ - + &:modal { display: flex; opacity: 1; scale: 1 1; transition-duration: 120ms; /* Enter transition duration */ transition-timing-function: ease-in; - + @starting-style { opacity: 0; scale: 1.05 1.05; } } } - + /* Note: `::backdrop` cannot be nested/scoped (at least in Chrome v120) */ .bk-modal::backdrop { --transition-time: 200ms; @@ -172,12 +169,12 @@ background-color: rgb(0 0 0 / 0%); } } - + .bk-modal-spinner { .bk-modal__header { display: none; } - + .bk-modal__content { display: flex; justify-content: center; diff --git a/src/components/overlays/Modal/Modal.stories.tsx b/src/components/overlays/Modal/Modal.stories.tsx index cc1ef7a..ac9bfc2 100644 --- a/src/components/overlays/Modal/Modal.stories.tsx +++ b/src/components/overlays/Modal/Modal.stories.tsx @@ -5,11 +5,10 @@ import * as React from 'react'; import type { Meta, StoryObj } from '@storybook/react'; -import { userEvent, within } from '@storybook/test'; import { ModalClassNames as cl } from './Modal.tsx'; import { OverflowTester } from '../../../util/storybook/OverflowTester.tsx'; -import { Modal } from './Modal.tsx'; +import { Modal, ModalHeader, ModalContent } from './Modal.tsx'; import { Button } from '../../actions/Button/Button.tsx'; import { Spinner } from '../../graphics/Spinner/Spinner.tsx'; @@ -42,26 +41,67 @@ const ModalWithTrigger = ({ triggerLabel = 'Open modal', ...modalProps }: ModalW ); }; -export const Interactive: Story = { +const reusableModalChildren: React.JSX.Element = ( + <> + +

Modal title

+
+ +

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do iusmod tempor incididunt ut labore et dolore magna aliqua.

+ + + +

Submodal title

+
+ +

This is a submodal

+ +
+
+ + +
+ +); + +export const ModalSizeSmall: Story = { render: () => ( - -
-

Title

-

This is a modal

- - -

Title

-
-

This is a submodal

- -
-
- - -
+ + {reusableModalChildren} + + ), +}; + +export const ModalSizeMedium: Story = { + render: () => ( + + {reusableModalChildren} + + ), +}; + +export const ModalSizeLarge: Story = { + render: () => ( + + {reusableModalChildren} + + ), +}; + +export const ModalSizeXLarge: Story = { + render: () => ( + + {reusableModalChildren} + + ), +}; + +export const ModalSizeFullScreen: Story = { + render: () => ( + + {reusableModalChildren} ), - play: async ({ canvasElement }) => {}, }; type ModalWithSpinnerTriggerProps = Omit, 'active' | 'onClose'> & { @@ -79,15 +119,17 @@ const ModalWithSpinnerTrigger = ({ triggerLabel = 'Open modal with spinner (it w return ( <> - + ); }; export const ModalWithSpinner: Story = { render: () => ( - - + + + + ), play: async ({ canvasElement }) => {}, diff --git a/src/components/overlays/Modal/Modal.tsx b/src/components/overlays/Modal/Modal.tsx index 9eda768..217fce6 100644 --- a/src/components/overlays/Modal/Modal.tsx +++ b/src/components/overlays/Modal/Modal.tsx @@ -1,3 +1,6 @@ +/* Copyright (c) Fortanix, Inc. +|* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of +|* the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { classNames as cx, @@ -22,12 +25,12 @@ const useClickOutside = (ref: React.RefObject, callbac } } }, [ref]); - + React.useEffect(() => { if (!window.PointerEvent) { return; } - + document.addEventListener('pointerdown', handleEvent); - + return () => { if (window.PointerEvent) { document.removeEventListener('pointerdown', handleEvent); @@ -42,6 +45,55 @@ const useClickOutside = (ref: React.RefObject, callbac }; */ +export type ModalHeaderProps = React.PropsWithChildren<{ + unstyled?: boolean, + className?: ClassNameArgument, +}>; + +/* Modal Header component */ +export const ModalHeader = ({ + children, + unstyled, + className, +}: ModalHeaderProps) => ( +
+ {children} +
+); + +export type ModalContentProps = React.PropsWithChildren<{ + unstyled?: boolean, + className?: ClassNameArgument, +}>; + +/* Modal Content component */ +export const ModalContent = ({ + children, + unstyled, + className, +}: ModalContentProps) => ( +
+ {children} +
+); + export type ModalProps = React.PropsWithChildren<{ unstyled?: boolean, size?: "small" | "medium" | "large" | "x-large" | "fullscreen", @@ -151,9 +203,7 @@ export const Modal = ({ )}
-
- {children} -
+ {children}
); From 275cbd58683f819604bae4307a0346dd21602857 Mon Sep 17 00:00:00 2001 From: Arlindo Pereira Date: Tue, 8 Oct 2024 16:26:26 +0200 Subject: [PATCH 03/13] Fix indentation --- src/components/overlays/Modal/Modal.tsx | 280 ++++++++++++------------ 1 file changed, 140 insertions(+), 140 deletions(-) diff --git a/src/components/overlays/Modal/Modal.tsx b/src/components/overlays/Modal/Modal.tsx index 217fce6..15888f6 100644 --- a/src/components/overlays/Modal/Modal.tsx +++ b/src/components/overlays/Modal/Modal.tsx @@ -3,8 +3,8 @@ |* the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { - classNames as cx, - type ClassNameArgument, + classNames as cx, + type ClassNameArgument, } from "../../../util/componentUtil.ts"; import * as React from "react"; @@ -46,165 +46,165 @@ const useClickOutside = (ref: React.RefObject, callbac */ export type ModalHeaderProps = React.PropsWithChildren<{ - unstyled?: boolean, - className?: ClassNameArgument, + unstyled?: boolean, + className?: ClassNameArgument, }>; /* Modal Header component */ export const ModalHeader = ({ - children, - unstyled, - className, + children, + unstyled, + className, }: ModalHeaderProps) => ( -
- {children} -
+
+ {children} +
); export type ModalContentProps = React.PropsWithChildren<{ - unstyled?: boolean, - className?: ClassNameArgument, + unstyled?: boolean, + className?: ClassNameArgument, }>; /* Modal Content component */ export const ModalContent = ({ - children, - unstyled, - className, + children, + unstyled, + className, }: ModalContentProps) => ( -
- {children} -
+
+ {children} +
); export type ModalProps = React.PropsWithChildren<{ - unstyled?: boolean, - size?: "small" | "medium" | "large" | "x-large" | "fullscreen", - active: boolean, - className?: ClassNameArgument, - onClose: () => void, - closeable?: boolean, + unstyled?: boolean, + size?: "small" | "medium" | "large" | "x-large" | "fullscreen", + active: boolean, + className?: ClassNameArgument, + onClose: () => void, + closeable?: boolean, }>; /** * Modal component. */ export const Modal = ({ - children, - unstyled, - className, - size = "medium", - closeable = true, - active, - onClose, + children, + unstyled, + className, + size = "medium", + closeable = true, + active, + onClose, }: ModalProps) => { - const dialogRef = React.useRef(null); - - // Sync the `active` flag with the DOM dialog - React.useEffect(() => { - const dialog = dialogRef.current; - if (dialog === null) { - return; - } - - if (active && !dialog.open) { - dialog.showModal(); - } else if (!active && dialog.open) { - dialog.close(); - } - }, [active]); - - // Sync the dialog close event with the `active` flag - const handleCloseEvent = React.useCallback( - (event: Event): void => { - const dialog = dialogRef.current; - if (dialog === null) { - return; - } - - if (active && event.target === dialog) { - onClose(); - } - }, - [active, onClose], - ); - React.useEffect(() => { - const dialog = dialogRef.current; - if (dialog === null) { - return; - } - - dialog.addEventListener("close", handleCloseEvent); - return () => { - dialog.removeEventListener("close", handleCloseEvent); - }; - }, [handleCloseEvent]); - - const close = React.useCallback(() => { - onClose(); - }, [onClose]); - - const handleDialogClick = React.useCallback( - (event: React.MouseEvent) => { - const dialog = dialogRef.current; - if (dialog !== null && event.target === dialog) { - // Note: clicking the backdrop just results in an event where the target is the `` element. In order to - // distinguish between the backdrop and the modal content, we assume that the `` is fully covered by - // another element. In our case, `bk-modal__content` must cover the whole `` otherwise this will not work. - close(); - } - }, - [close], - ); - - return ( - // biome-ignore lint/a11y/useKeyWithClickEvents: - - {closeable && ( - - )} -
- {children} -
-
- ); + const dialogRef = React.useRef(null); + + // Sync the `active` flag with the DOM dialog + React.useEffect(() => { + const dialog = dialogRef.current; + if (dialog === null) { + return; + } + + if (active && !dialog.open) { + dialog.showModal(); + } else if (!active && dialog.open) { + dialog.close(); + } + }, [active]); + + // Sync the dialog close event with the `active` flag + const handleCloseEvent = React.useCallback( + (event: Event): void => { + const dialog = dialogRef.current; + if (dialog === null) { + return; + } + + if (active && event.target === dialog) { + onClose(); + } + }, + [active, onClose], + ); + React.useEffect(() => { + const dialog = dialogRef.current; + if (dialog === null) { + return; + } + + dialog.addEventListener("close", handleCloseEvent); + return () => { + dialog.removeEventListener("close", handleCloseEvent); + }; + }, [handleCloseEvent]); + + const close = React.useCallback(() => { + onClose(); + }, [onClose]); + + const handleDialogClick = React.useCallback( + (event: React.MouseEvent) => { + const dialog = dialogRef.current; + if (dialog !== null && event.target === dialog) { + // Note: clicking the backdrop just results in an event where the target is the `` element. In order to + // distinguish between the backdrop and the modal content, we assume that the `` is fully covered by + // another element. In our case, `bk-modal__content` must cover the whole `` otherwise this will not work. + close(); + } + }, + [close], + ); + + return ( + // biome-ignore lint/a11y/useKeyWithClickEvents: + + {closeable && ( + + )} +
+ {children} +
+
+ ); }; From 64a3ed89996c55e7684bf271cf52c982b9e3c4f6 Mon Sep 17 00:00:00 2001 From: Arlindo Pereira Date: Wed, 9 Oct 2024 13:03:17 +0200 Subject: [PATCH 04/13] Modal Footer --- .../overlays/Modal/Modal.stories.tsx | 3 ++- src/components/overlays/Modal/Modal.tsx | 24 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/components/overlays/Modal/Modal.stories.tsx b/src/components/overlays/Modal/Modal.stories.tsx index ac9bfc2..1c7bb06 100644 --- a/src/components/overlays/Modal/Modal.stories.tsx +++ b/src/components/overlays/Modal/Modal.stories.tsx @@ -8,7 +8,7 @@ import type { Meta, StoryObj } from '@storybook/react'; import { ModalClassNames as cl } from './Modal.tsx'; import { OverflowTester } from '../../../util/storybook/OverflowTester.tsx'; -import { Modal, ModalHeader, ModalContent } from './Modal.tsx'; +import { Modal, ModalHeader, ModalContent, ModalFooter } from './Modal.tsx'; import { Button } from '../../actions/Button/Button.tsx'; import { Spinner } from '../../graphics/Spinner/Spinner.tsx'; @@ -61,6 +61,7 @@ const reusableModalChildren: React.JSX.Element = ( + This is a modal footer with eventual action buttons ); diff --git a/src/components/overlays/Modal/Modal.tsx b/src/components/overlays/Modal/Modal.tsx index 15888f6..45360ad 100644 --- a/src/components/overlays/Modal/Modal.tsx +++ b/src/components/overlays/Modal/Modal.tsx @@ -94,6 +94,30 @@ export const ModalContent = ({ ); +export type ModalFooterProps = React.PropsWithChildren<{ + unstyled?: boolean, + className?: ClassNameArgument, +}>; + +/* Modal Footer component */ +export const ModalFooter = ({ + children, + unstyled, + className, +}: ModalFooterProps) => ( +
+ {children} +
+); + export type ModalProps = React.PropsWithChildren<{ unstyled?: boolean, size?: "small" | "medium" | "large" | "x-large" | "fullscreen", From e12c24cad1fb9113edad33a1f70d52d25450cb44 Mon Sep 17 00:00:00 2001 From: Arlindo Pereira Date: Wed, 9 Oct 2024 13:29:20 +0200 Subject: [PATCH 05/13] Fix uncloseable dialogs - clicking on the back will not close them --- src/components/overlays/Modal/Modal.stories.tsx | 8 ++++++++ src/components/overlays/Modal/Modal.tsx | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/components/overlays/Modal/Modal.stories.tsx b/src/components/overlays/Modal/Modal.stories.tsx index 1c7bb06..63afc21 100644 --- a/src/components/overlays/Modal/Modal.stories.tsx +++ b/src/components/overlays/Modal/Modal.stories.tsx @@ -105,6 +105,14 @@ export const ModalSizeFullScreen: Story = { ), }; +export const ModalUncloseable: Story = { + render: () => ( + + {reusableModalChildren} + + ), +}; + type ModalWithSpinnerTriggerProps = Omit, 'active' | 'onClose'> & { triggerLabel?: string, }; diff --git a/src/components/overlays/Modal/Modal.tsx b/src/components/overlays/Modal/Modal.tsx index 45360ad..db60fe0 100644 --- a/src/components/overlays/Modal/Modal.tsx +++ b/src/components/overlays/Modal/Modal.tsx @@ -188,7 +188,7 @@ export const Modal = ({ const handleDialogClick = React.useCallback( (event: React.MouseEvent) => { const dialog = dialogRef.current; - if (dialog !== null && event.target === dialog) { + if (dialog !== null && event.target === dialog && closeable) { // Note: clicking the backdrop just results in an event where the target is the `` element. In order to // distinguish between the backdrop and the modal content, we assume that the `` is fully covered by // another element. In our case, `bk-modal__content` must cover the whole `` otherwise this will not work. From 47b675e919c3bd975d72e9e8019aa14e798d50a7 Mon Sep 17 00:00:00 2001 From: Arlindo Pereira Date: Wed, 9 Oct 2024 13:46:31 +0200 Subject: [PATCH 06/13] Fix Modal Spinner to not have a background other than the backdrop --- src/components/overlays/Modal/Modal.module.scss | 7 ++----- src/components/overlays/Modal/Modal.stories.tsx | 5 ++--- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/components/overlays/Modal/Modal.module.scss b/src/components/overlays/Modal/Modal.module.scss index b5639f0..76060df 100644 --- a/src/components/overlays/Modal/Modal.module.scss +++ b/src/components/overlays/Modal/Modal.module.scss @@ -160,7 +160,8 @@ overlay var(--transition-time) allow-discrete, background-color var(--transition-time); } - .bk-modal:modal::backdrop { + .bk-modal:modal::backdrop, + .bk-modal-spinner::backdrop { background-color: rgb(0 0 0 / 20%); backdrop-filter: blur(5px); /* Should be in px, not rem (blur effect should be constant) */ } @@ -171,10 +172,6 @@ } .bk-modal-spinner { - .bk-modal__header { - display: none; - } - .bk-modal__content { display: flex; justify-content: center; diff --git a/src/components/overlays/Modal/Modal.stories.tsx b/src/components/overlays/Modal/Modal.stories.tsx index 63afc21..26d605b 100644 --- a/src/components/overlays/Modal/Modal.stories.tsx +++ b/src/components/overlays/Modal/Modal.stories.tsx @@ -128,18 +128,17 @@ const ModalWithSpinnerTrigger = ({ triggerLabel = 'Open modal with spinner (it w return ( <> - + ); }; export const ModalWithSpinner: Story = { render: () => ( - + ), - play: async ({ canvasElement }) => {}, }; From 3500a11be6be10c3f1e6c22fdeb661a6a67fe3b8 Mon Sep 17 00:00:00 2001 From: Arlindo Pereira Date: Wed, 9 Oct 2024 16:45:22 +0200 Subject: [PATCH 07/13] Use dot notation for Modal subcomponents --- .../overlays/Modal/Modal.stories.tsx | 24 ++++----- src/components/overlays/Modal/Modal.tsx | 52 +++++-------------- 2 files changed, 26 insertions(+), 50 deletions(-) diff --git a/src/components/overlays/Modal/Modal.stories.tsx b/src/components/overlays/Modal/Modal.stories.tsx index 26d605b..e4d0608 100644 --- a/src/components/overlays/Modal/Modal.stories.tsx +++ b/src/components/overlays/Modal/Modal.stories.tsx @@ -8,7 +8,7 @@ import type { Meta, StoryObj } from '@storybook/react'; import { ModalClassNames as cl } from './Modal.tsx'; import { OverflowTester } from '../../../util/storybook/OverflowTester.tsx'; -import { Modal, ModalHeader, ModalContent, ModalFooter } from './Modal.tsx'; +import { Modal } from './Modal.tsx'; import { Button } from '../../actions/Button/Button.tsx'; import { Spinner } from '../../graphics/Spinner/Spinner.tsx'; @@ -43,25 +43,25 @@ const ModalWithTrigger = ({ triggerLabel = 'Open modal', ...modalProps }: ModalW const reusableModalChildren: React.JSX.Element = ( <> - +

Modal title

-
- + +

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do iusmod tempor incididunt ut labore et dolore magna aliqua.

- +

Submodal title

-
- + +

This is a submodal

-
+
-
- This is a modal footer with eventual action buttons + + This is a modal footer with eventual action buttons ); @@ -136,9 +136,9 @@ const ModalWithSpinnerTrigger = ({ triggerLabel = 'Open modal with spinner (it w export const ModalWithSpinner: Story = { render: () => ( - + - + ), }; diff --git a/src/components/overlays/Modal/Modal.tsx b/src/components/overlays/Modal/Modal.tsx index db60fe0..050253f 100644 --- a/src/components/overlays/Modal/Modal.tsx +++ b/src/components/overlays/Modal/Modal.tsx @@ -14,44 +14,14 @@ import cl from "./Modal.module.scss"; export { cl as ModalClassNames }; -/* -const useClickOutside = (ref: React.RefObject, callback: () => void) => { - const handleEvent = React.useCallback((event: React.PointerEvent) => { - if (ref && ref.current) { - if (ref.current.contains(event.target as Node)) { - React.setState({ hasClickedOutside: false }); - } else { - React.setState({ hasClickedOutside: true }); - } - } - }, [ref]); - - React.useEffect(() => { - if (!window.PointerEvent) { return; } - document.addEventListener('pointerdown', handleEvent); - - return () => { - if (window.PointerEvent) { - document.removeEventListener('pointerdown', handleEvent); - } else { - document.removeEventListener('mousedown', handleEvent); - document.removeEventListener('touchstart', handleEvent); - } - } - }, []); - - return [ref, hasClickedOutside]; -}; -*/ - -export type ModalHeaderProps = React.PropsWithChildren<{ +type ModalHeaderProps = React.PropsWithChildren<{ unstyled?: boolean, className?: ClassNameArgument, }>; /* Modal Header component */ -export const ModalHeader = ({ +const ModalHeader = ({ children, unstyled, className, @@ -69,13 +39,13 @@ export const ModalHeader = ({ ); -export type ModalContentProps = React.PropsWithChildren<{ +type ModalContentProps = React.PropsWithChildren<{ unstyled?: boolean, className?: ClassNameArgument, }>; /* Modal Content component */ -export const ModalContent = ({ +const ModalContent = ({ children, unstyled, className, @@ -94,13 +64,13 @@ export const ModalContent = ({ ); -export type ModalFooterProps = React.PropsWithChildren<{ +type ModalFooterProps = React.PropsWithChildren<{ unstyled?: boolean, className?: ClassNameArgument, }>; /* Modal Footer component */ -export const ModalFooter = ({ +const ModalFooter = ({ children, unstyled, className, @@ -118,7 +88,7 @@ export const ModalFooter = ({ ); -export type ModalProps = React.PropsWithChildren<{ +type ModalProps = React.PropsWithChildren<{ unstyled?: boolean, size?: "small" | "medium" | "large" | "x-large" | "fullscreen", active: boolean, @@ -130,7 +100,7 @@ export type ModalProps = React.PropsWithChildren<{ /** * Modal component. */ -export const Modal = ({ +const Modal = ({ children, unstyled, className, @@ -232,3 +202,9 @@ export const Modal = ({
); }; + +Modal.Content = ModalContent; +Modal.Header = ModalHeader; +Modal.Footer = ModalFooter; + +export { Modal }; From 8d13f25309692930e06b60dd04a6a4a8883bde3f Mon Sep 17 00:00:00 2001 From: Arlindo Pereira Date: Thu, 10 Oct 2024 16:59:34 +0200 Subject: [PATCH 08/13] Prevent dialog closing with Esc key --- src/components/overlays/Modal/Modal.tsx | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/components/overlays/Modal/Modal.tsx b/src/components/overlays/Modal/Modal.tsx index 050253f..dc74fbc 100644 --- a/src/components/overlays/Modal/Modal.tsx +++ b/src/components/overlays/Modal/Modal.tsx @@ -168,6 +168,22 @@ const Modal = ({ [close], ); + // prevent closing dialog with Esc key + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key !== 'Escape') { + return; + } + if (closeable) { + e.preventDefault(); + } + }; + React.useEffect(() => { + document.addEventListener('keydown', handleKeyDown); + return () => { + document.removeEventListener('keydown', handleKeyDown); + }; + }, [closeable]); + return ( // biome-ignore lint/a11y/useKeyWithClickEvents: Date: Fri, 11 Oct 2024 12:15:49 +0200 Subject: [PATCH 09/13] Lint fixes --- src/components/overlays/Modal/Modal.tsx | 79 ++++++++++--------------- 1 file changed, 32 insertions(+), 47 deletions(-) diff --git a/src/components/overlays/Modal/Modal.tsx b/src/components/overlays/Modal/Modal.tsx index dc74fbc..71c4792 100644 --- a/src/components/overlays/Modal/Modal.tsx +++ b/src/components/overlays/Modal/Modal.tsx @@ -2,35 +2,28 @@ |* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of |* the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { - classNames as cx, - type ClassNameArgument, -} from "../../../util/componentUtil.ts"; -import * as React from "react"; +import { type ClassNameArgument, classNames as cx } from '../../../util/componentUtil.ts'; +import * as React from 'react'; -import { Icon } from "../../graphics/Icon/Icon.tsx"; +import { Icon } from '../../graphics/Icon/Icon.tsx'; -import cl from "./Modal.module.scss"; +import cl from './Modal.module.scss'; export { cl as ModalClassNames }; type ModalHeaderProps = React.PropsWithChildren<{ - unstyled?: boolean, - className?: ClassNameArgument, + unstyled?: boolean; + className?: ClassNameArgument; }>; /* Modal Header component */ -const ModalHeader = ({ - children, - unstyled, - className, -}: ModalHeaderProps) => ( +const ModalHeader = ({ children, unstyled, className }: ModalHeaderProps) => (
; /* Modal Content component */ -const ModalContent = ({ - children, - unstyled, - className, -}: ModalContentProps) => ( +const ModalContent = ({ children, unstyled, className }: ModalContentProps) => (
; /* Modal Footer component */ -const ModalFooter = ({ - children, - unstyled, - className, -}: ModalFooterProps) => ( +const ModalFooter = ({ children, unstyled, className }: ModalFooterProps) => (
void, - closeable?: boolean, + unstyled?: boolean; + size?: 'small' | 'medium' | 'large' | 'x-large' | 'fullscreen'; + active: boolean; + className?: ClassNameArgument; + onClose: () => void; + closeable?: boolean; }>; /** @@ -145,9 +130,9 @@ const Modal = ({ return; } - dialog.addEventListener("close", handleCloseEvent); + dialog.addEventListener('close', handleCloseEvent); return () => { - dialog.removeEventListener("close", handleCloseEvent); + dialog.removeEventListener('close', handleCloseEvent); }; }, [handleCloseEvent]); @@ -191,12 +176,12 @@ const Modal = ({ className={cx( { bk: true, - [cl["bk-modal-small"] as string]: size === "small", - [cl["bk-modal-medium"] as string]: size === "medium", - [cl["bk-modal-large"] as string]: size === "large", - [cl["bk-modal-x-large"] as string]: size === "x-large", - [cl["bk-modal-fullscreen"] as string]: size === "fullscreen", - [cl["bk-modal"] as string]: !unstyled, + [cl['bk-modal-small'] as string]: size === 'small', + [cl['bk-modal-medium'] as string]: size === 'medium', + [cl['bk-modal-large'] as string]: size === 'large', + [cl['bk-modal-x-large'] as string]: size === 'x-large', + [cl['bk-modal-fullscreen'] as string]: size === 'fullscreen', + [cl['bk-modal'] as string]: !unstyled, }, className, )} @@ -205,14 +190,14 @@ const Modal = ({ {closeable && ( )} -
+
{children}
From 8673f4ac6d99929650762745b0e6465e11171e05 Mon Sep 17 00:00:00 2001 From: Arlindo Pereira Date: Fri, 11 Oct 2024 14:27:36 +0200 Subject: [PATCH 10/13] Prevent default border when trying to close modal spinner --- src/components/overlays/Modal/Modal.module.scss | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/overlays/Modal/Modal.module.scss b/src/components/overlays/Modal/Modal.module.scss index 76060df..6f1f092 100644 --- a/src/components/overlays/Modal/Modal.module.scss +++ b/src/components/overlays/Modal/Modal.module.scss @@ -27,6 +27,7 @@ display: none; /* flex */ flex-direction: column; + &.bk-modal-small { // 484px width: calc(484 * bk.$size-1); @@ -172,6 +173,8 @@ } .bk-modal-spinner { + outline: none !important; // prevent blue border on Esc pressing + .bk-modal__content { display: flex; justify-content: center; From 37697deb593ab2bf8b89663ec369bf2380d91f3c Mon Sep 17 00:00:00 2001 From: Arlindo Pereira Date: Fri, 11 Oct 2024 15:17:53 +0200 Subject: [PATCH 11/13] Move handleKeyDown to a useCallback; fix other lint issues --- src/components/overlays/Modal/Modal.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/overlays/Modal/Modal.tsx b/src/components/overlays/Modal/Modal.tsx index 71c4792..97dbd08 100644 --- a/src/components/overlays/Modal/Modal.tsx +++ b/src/components/overlays/Modal/Modal.tsx @@ -89,7 +89,7 @@ const Modal = ({ children, unstyled, className, - size = "medium", + size = 'medium', closeable = true, active, onClose, @@ -150,24 +150,24 @@ const Modal = ({ close(); } }, - [close], + [close, closeable], ); // prevent closing dialog with Esc key - const handleKeyDown = (e: KeyboardEvent) => { - if (e.key !== 'Escape') { + const handleKeyDown = React.useCallback((event: KeyboardEvent) => { + if (event.key !== 'Escape') { return; } if (closeable) { - e.preventDefault(); + event.preventDefault(); } - }; + }, [closeable]); React.useEffect(() => { document.addEventListener('keydown', handleKeyDown); return () => { document.removeEventListener('keydown', handleKeyDown); }; - }, [closeable]); + }, [handleKeyDown]); return ( // biome-ignore lint/a11y/useKeyWithClickEvents: From 05284f0c3d32a6a5f2ddcd188dd215c26d14a8ae Mon Sep 17 00:00:00 2001 From: mkrause Date: Fri, 11 Oct 2024 15:22:59 +0200 Subject: [PATCH 12/13] Linter fixes. --- src/components/overlays/Modal/Modal.stories.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/overlays/Modal/Modal.stories.tsx b/src/components/overlays/Modal/Modal.stories.tsx index e4d0608..7423285 100644 --- a/src/components/overlays/Modal/Modal.stories.tsx +++ b/src/components/overlays/Modal/Modal.stories.tsx @@ -32,7 +32,7 @@ type ModalWithTriggerProps = Omit, 'active' | }; const ModalWithTrigger = ({ triggerLabel = 'Open modal', ...modalProps }: ModalWithTriggerProps) => { const [active, setActive] = React.useState(false); - const onClose = React.useCallback(() => { setActive(false); }, [setActive]); + const onClose = React.useCallback(() => { setActive(false); }, []); return ( <> @@ -124,7 +124,7 @@ const ModalWithSpinnerTrigger = ({ triggerLabel = 'Open modal with spinner (it w setActive(false); }, 5000); } - const onClose = React.useCallback(() => { setActive(false); }, [setActive]); + const onClose = React.useCallback(() => { setActive(false); }, []); return ( <> From b05f73e84f5a43c78e67f9012bc65faf772805bb Mon Sep 17 00:00:00 2001 From: mkrause Date: Fri, 11 Oct 2024 15:29:17 +0200 Subject: [PATCH 13/13] Update VSCode settings. --- .vscode/settings.json | 19 +++++++- .../overlays/Modal/Modal.module.scss | 44 +++++++++---------- .../overlays/Modal/Modal.stories.tsx | 4 +- src/components/overlays/Modal/Modal.tsx | 4 +- 4 files changed, 44 insertions(+), 27 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index d2adc91..895524a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,22 @@ { // https://github.com/Microsoft/vscode-css-languageservice/blob/main/docs/customData.md // https://stackoverflow.com/questions/42520229/vs-code-and-intellisense-for-css-grid-and-css-modules - "css.customData": [".vscode/custom.css-data.json"] + "css.customData": [".vscode/custom.css-data.json"], + + // Editor (code) + "editor.insertSpaces": true, // Insert spaces when pressing Tab + "editor.tabSize": 2, + "editor.detectIndentation": true, // Detect tabSize/insertSpaces automatically when opening a file + "editor.renderWhitespace": "selection", // Render whitespace as visible when selecting text + "editor.wordWrap": "wordWrapColumn", + "editor.wordWrapColumn": 120, + "editor.rulers": [120], + "editor.formatOnSave": false, // Disable auto-format + "editor.formatOnPaste": false, + "editor.comments.ignoreEmptyLines": false, + "files.insertFinalNewline": true, // Insert a newline at the end of the file when saving + "files.trimTrailingWhitespace": false, // Do not trim trailing whitespace + "editor.trimAutoWhitespace": false, + "files.eol": "\n", + "javascript.preferences.quoteStyle": "single", } diff --git a/src/components/overlays/Modal/Modal.module.scss b/src/components/overlays/Modal/Modal.module.scss index 6f1f092..289fb43 100644 --- a/src/components/overlays/Modal/Modal.module.scss +++ b/src/components/overlays/Modal/Modal.module.scss @@ -8,26 +8,26 @@ @layer baklava.components { .bk-modal { @include bk.component-base(bk-modal); - + @include Panel.bk-panel; - + /* --bk-modal-background-color: color-mix(in srgb, var(--bk-panel-background-color) 80%, transparent); */ --bk-modal-background-color: var(--bk-panel-background-color); - + margin: auto; padding: 0; height: fit-content; min-height: bk.$spacing-16; max-width: calc(100% - bk.$spacing-6); max-height: calc(100% - bk.$spacing-6); - + box-shadow: 0 8px 10px 1px rgba(0 0 0 / 14%), 0 3px 14px 2px rgba(0 0 0 / 12%), 0 5px 5px -3px rgba(0 0 0 / 3%); background-color: var(--bk-modal-background-color); border-radius: bk.$sizing-2; - + display: none; /* flex */ flex-direction: column; - + &.bk-modal-small { // 484px width: calc(484 * bk.$size-1); @@ -52,19 +52,19 @@ position: sticky; top: 0; padding: 0; - + background: var(--bk-modal-background-color); - + display: flex; flex-direction: row; align-items: baseline; margin-bottom: bk.$spacing-7; - + h1 { font-size: 16px; // do not match bk variable sizes font-weight: bk.$font-weight-semibold; } - + :nth-child(1 of :global(.action)) { margin-left: auto; } @@ -87,7 +87,7 @@ flex: 1; /* Make sure we cover all available space */ overflow: auto; } - + /* Variant: slide out */ --modal-slide-out-inset: var(--bk-sizing-3); &:is(.bk-modal--slide-out-left, .bk-modal--slide-out-right) { @@ -97,7 +97,7 @@ width: 60vw; height: auto; max-height: 100%; - + &.bk-modal--slide-out-left { right: auto; transform-origin: center left; @@ -110,25 +110,25 @@ border-top-right-radius: 0; border-bottom-right-radius: 0; } - + opacity: 1; transform: scaleX(0); &:modal { transform: scaleX(1); - + @starting-style { opacity: 1; transform: scaleX(0); } } - + .bk-modal__content { overflow: auto; } } - + /* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog#animating_dialogs */ - + opacity: 0; scale: 0.98 0.98; transition: @@ -137,21 +137,21 @@ overlay 1ms ease-out allow-discrete, display 1ms ease-out allow-discrete; transition-duration: 200ms; /* Exit transition duration */ - + &:modal { display: flex; opacity: 1; scale: 1 1; transition-duration: 120ms; /* Enter transition duration */ transition-timing-function: ease-in; - + @starting-style { opacity: 0; scale: 1.05 1.05; } } } - + /* Note: `::backdrop` cannot be nested/scoped (at least in Chrome v120) */ .bk-modal::backdrop { --transition-time: 200ms; @@ -171,10 +171,10 @@ background-color: rgb(0 0 0 / 0%); } } - + .bk-modal-spinner { outline: none !important; // prevent blue border on Esc pressing - + .bk-modal__content { display: flex; justify-content: center; diff --git a/src/components/overlays/Modal/Modal.stories.tsx b/src/components/overlays/Modal/Modal.stories.tsx index 7423285..1bc4af3 100644 --- a/src/components/overlays/Modal/Modal.stories.tsx +++ b/src/components/overlays/Modal/Modal.stories.tsx @@ -48,7 +48,7 @@ const reusableModalChildren: React.JSX.Element = (

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do iusmod tempor incididunt ut labore et dolore magna aliqua.

- +

Submodal title

@@ -58,7 +58,7 @@ const reusableModalChildren: React.JSX.Element = (
- + This is a modal footer with eventual action buttons diff --git a/src/components/overlays/Modal/Modal.tsx b/src/components/overlays/Modal/Modal.tsx index 97dbd08..7cb6817 100644 --- a/src/components/overlays/Modal/Modal.tsx +++ b/src/components/overlays/Modal/Modal.tsx @@ -117,7 +117,7 @@ const Modal = ({ if (dialog === null) { return; } - + if (active && event.target === dialog) { onClose(); } @@ -129,7 +129,7 @@ const Modal = ({ if (dialog === null) { return; } - + dialog.addEventListener('close', handleCloseEvent); return () => { dialog.removeEventListener('close', handleCloseEvent);