From e9d32fa52f3b4cf4375f4fc5f163cecebe6cca73 Mon Sep 17 00:00:00 2001 From: Halvor Haugan Date: Tue, 17 Oct 2023 15:22:01 +0200 Subject: [PATCH 1/6] =?UTF-8?q?:sparkles:=20Modal:=20St=C3=B8tte=20for=20?= =?UTF-8?q?=C3=A5=20lukke=20ved=20klikk=20utenfor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/slimy-cameras-reflect.md | 5 +++++ @navikt/core/react/src/modal/Modal.tsx | 10 ++++++++++ @navikt/core/react/src/modal/modal.stories.tsx | 5 ++++- @navikt/core/react/src/modal/types.ts | 7 ++++++- 4 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 .changeset/slimy-cameras-reflect.md diff --git a/.changeset/slimy-cameras-reflect.md b/.changeset/slimy-cameras-reflect.md new file mode 100644 index 00000000000..a99b48f3e88 --- /dev/null +++ b/.changeset/slimy-cameras-reflect.md @@ -0,0 +1,5 @@ +--- +"@navikt/ds-react": minor +--- + +:sparkles: Modal: Støtte for å lukke ved klikk utenfor diff --git a/@navikt/core/react/src/modal/Modal.tsx b/@navikt/core/react/src/modal/Modal.tsx index 2729aa761bd..a00d18c73ac 100644 --- a/@navikt/core/react/src/modal/Modal.tsx +++ b/@navikt/core/react/src/modal/Modal.tsx @@ -82,6 +82,7 @@ export const Modal = forwardRef( open, onBeforeClose, onCancel, + closeOnClickOutside, width, portal, className, @@ -129,6 +130,7 @@ export const Modal = forwardRef( typeof width === "string" && ["small", "medium"].includes(width); const component = ( + // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-noninteractive-element-interactions ( event.preventDefault(); } else if (onCancel) onCancel(event); }} + onClick={ + closeOnClickOutside + ? (event) => + event.target === modalRef.current && + (!onBeforeClose || onBeforeClose() !== false) && + modalRef.current.close() + : undefined + } aria-labelledby={ !ariaLabelledby && !rest["aria-label"] && header ? ariaLabelId diff --git a/@navikt/core/react/src/modal/modal.stories.tsx b/@navikt/core/react/src/modal/modal.stories.tsx index 8f25e6b26bc..25931d68da1 100644 --- a/@navikt/core/react/src/modal/modal.stories.tsx +++ b/@navikt/core/react/src/modal/modal.stories.tsx @@ -1,5 +1,5 @@ -import React, { useRef, useState } from "react"; import { FileIcon } from "@navikt/aksel-icons"; +import React, { useRef, useState } from "react"; import { BodyLong, Button, Heading } from ".."; import Modal from "./Modal"; @@ -27,6 +27,7 @@ export const WithUseRef = () => { heading: "Title", size: "small", }} + closeOnClickOutside > @@ -45,6 +46,7 @@ export const WithUseRef = () => { onBeforeClose={() => window.confirm("Are you sure you want to close the modal?") } + closeOnClickOutside aria-labelledby="heading123" > @@ -111,6 +113,7 @@ export const WithUseState = () => { e.stopPropagation(); // onClose wil propagate to parent modal if not stopped setOpen2(false); }} + closeOnClickOutside aria-label="Nested modal" width={800} > diff --git a/@navikt/core/react/src/modal/types.ts b/@navikt/core/react/src/modal/types.ts index 4e84c52ad13..82c21e93056 100644 --- a/@navikt/core/react/src/modal/types.ts +++ b/@navikt/core/react/src/modal/types.ts @@ -1,5 +1,5 @@ export interface ModalProps - extends React.DialogHTMLAttributes { + extends Omit, "onClick"> { /** * Content for the header. Alteratively you can use instead for more control, * but then you have to set `aria-label` or `aria-labelledby` on the modal manually. @@ -42,6 +42,11 @@ export interface ModalProps * Called when the user presses the Esc key, unless `onBeforeClose()` returns `false`. */ onCancel?: React.ReactEventHandler; + /** + * Whether to close when clicking on the backdrop. *** TODO RULES *** + * @default false + */ + closeOnClickOutside?: boolean; /** * @default fit-content (up to 700px) * */ From dd51cbc1588dfab8e32a637824d704018a3d2537 Mon Sep 17 00:00:00 2001 From: Halvor Haugan Date: Wed, 25 Oct 2023 16:49:26 +0200 Subject: [PATCH 2/6] jsdoc og eksempel --- @navikt/core/react/src/modal/types.ts | 4 +- .../modal/close-on-click-outside.tsx | 38 +++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 aksel.nav.no/website/pages/eksempler/modal/close-on-click-outside.tsx diff --git a/@navikt/core/react/src/modal/types.ts b/@navikt/core/react/src/modal/types.ts index 82c21e93056..d7e847bc556 100644 --- a/@navikt/core/react/src/modal/types.ts +++ b/@navikt/core/react/src/modal/types.ts @@ -43,7 +43,9 @@ export interface ModalProps */ onCancel?: React.ReactEventHandler; /** - * Whether to close when clicking on the backdrop. *** TODO RULES *** + * Whether to close when clicking on the backdrop. + * + * **WARNING:** Users may click outside by accident. Don't use if closing causes data loss, or the modal contains important info. * @default false */ closeOnClickOutside?: boolean; diff --git a/aksel.nav.no/website/pages/eksempler/modal/close-on-click-outside.tsx b/aksel.nav.no/website/pages/eksempler/modal/close-on-click-outside.tsx new file mode 100644 index 00000000000..e75fb12aa06 --- /dev/null +++ b/aksel.nav.no/website/pages/eksempler/modal/close-on-click-outside.tsx @@ -0,0 +1,38 @@ +import { BodyLong, Button, Modal } from "@navikt/ds-react"; +import { withDsExample } from "components/website-modules/examples/withDsExample"; +import { useRef } from "react"; + +const Example = () => { + const ref = useRef(null); + + return ( +
+ + + + + + Culpa aliquip ut cupidatat laborum minim quis ex in aliqua. Qui + incididunt dolor do ad ut. Incididunt eiusmod nostrud deserunt duis + laborum. Proident aute culpa qui nostrud velit adipisicing minim. + Consequat aliqua aute dolor do sit Lorem nisi mollit velit. Aliqua + exercitation non minim minim pariatur sunt laborum ipsum. + Exercitation nostrud est laborum magna non non aliqua qui esse. + + + +
+ ); +}; + +export default withDsExample(Example); + +/* Storybook story */ +export const Demo = { + render: Example, +}; + +export const args = { + index: 5, + desc: "Husk at det er lett å klikke utenfor ved et uhell. Ikke bruk 'closeOnClickOutside' hvis det kan føre til at brukeren mister data eller går glipp av viktig informasjon.", +}; From 9f5d4e68cd33190c0addee54c4161e7526a2e925 Mon Sep 17 00:00:00 2001 From: Halvor Haugan Date: Thu, 26 Oct 2023 16:13:13 +0200 Subject: [PATCH 3/6] justeringer --- @navikt/core/react/src/modal/Modal.tsx | 71 +++++++++++-------- .../core/react/src/modal/modal.stories.tsx | 6 +- @navikt/core/react/src/modal/types.ts | 4 +- ...utside.tsx => close-on-backdrop-click.tsx} | 4 +- 4 files changed, 49 insertions(+), 36 deletions(-) rename aksel.nav.no/website/pages/eksempler/modal/{close-on-click-outside.tsx => close-on-backdrop-click.tsx} (90%) diff --git a/@navikt/core/react/src/modal/Modal.tsx b/@navikt/core/react/src/modal/Modal.tsx index a00d18c73ac..c91788f4189 100644 --- a/@navikt/core/react/src/modal/Modal.tsx +++ b/@navikt/core/react/src/modal/Modal.tsx @@ -82,12 +82,13 @@ export const Modal = forwardRef( open, onBeforeClose, onCancel, - closeOnClickOutside, + closeOnBackdropClick, width, portal, className, "aria-labelledby": ariaLabelledby, style, + onClick, ...rest }: ModalProps, ref @@ -129,39 +130,51 @@ export const Modal = forwardRef( const isWidthPreset = typeof width === "string" && ["small", "medium"].includes(width); + const mergedClassName = cl("navds-modal", className, { + "navds-modal--polyfilled": needPolyfill, + "navds-modal--autowidth": !width, + [`navds-modal--${width}`]: isWidthPreset, + }); + + const mergedStyle = { + ...style, + ...(!isWidthPreset ? { width } : {}), + }; + + const mergedOnCancel: React.DialogHTMLAttributes["onCancel"] = + (event) => { + if (onBeforeClose && onBeforeClose() === false) { + event.preventDefault(); + } else if (onCancel) onCancel(event); + }; + + const mergedOnClick = closeOnBackdropClick + ? (event: React.MouseEvent) => { + if (onClick) onClick(event); + if ( + event.target === modalRef.current && + (!onBeforeClose || onBeforeClose() !== false) + ) { + modalRef.current?.close(); + } + } + : onClick; + + const mergedAriaLabelledBy = + !ariaLabelledby && !rest["aria-label"] && header + ? ariaLabelId + : ariaLabelledby; + const component = ( // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-noninteractive-element-interactions { - // FYI: onCancel fires when you press Esc - if (onBeforeClose && onBeforeClose() === false) { - event.preventDefault(); - } else if (onCancel) onCancel(event); - }} - onClick={ - closeOnClickOutside - ? (event) => - event.target === modalRef.current && - (!onBeforeClose || onBeforeClose() !== false) && - modalRef.current.close() - : undefined - } - aria-labelledby={ - !ariaLabelledby && !rest["aria-label"] && header - ? ariaLabelId - : ariaLabelledby - } + className={mergedClassName} + style={mergedStyle} + onCancel={mergedOnCancel} // FYI: onCancel fires when you press Esc + onClick={mergedOnClick} + aria-labelledby={mergedAriaLabelledBy} > { heading: "Title", size: "small", }} - closeOnClickOutside + closeOnBackdropClick > @@ -46,7 +46,7 @@ export const WithUseRef = () => { onBeforeClose={() => window.confirm("Are you sure you want to close the modal?") } - closeOnClickOutside + closeOnBackdropClick aria-labelledby="heading123" > @@ -113,7 +113,7 @@ export const WithUseState = () => { e.stopPropagation(); // onClose wil propagate to parent modal if not stopped setOpen2(false); }} - closeOnClickOutside + closeOnBackdropClick aria-label="Nested modal" width={800} > diff --git a/@navikt/core/react/src/modal/types.ts b/@navikt/core/react/src/modal/types.ts index d7e847bc556..5522ba11697 100644 --- a/@navikt/core/react/src/modal/types.ts +++ b/@navikt/core/react/src/modal/types.ts @@ -1,5 +1,5 @@ export interface ModalProps - extends Omit, "onClick"> { + extends React.DialogHTMLAttributes { /** * Content for the header. Alteratively you can use instead for more control, * but then you have to set `aria-label` or `aria-labelledby` on the modal manually. @@ -48,7 +48,7 @@ export interface ModalProps * **WARNING:** Users may click outside by accident. Don't use if closing causes data loss, or the modal contains important info. * @default false */ - closeOnClickOutside?: boolean; + closeOnBackdropClick?: boolean; /** * @default fit-content (up to 700px) * */ diff --git a/aksel.nav.no/website/pages/eksempler/modal/close-on-click-outside.tsx b/aksel.nav.no/website/pages/eksempler/modal/close-on-backdrop-click.tsx similarity index 90% rename from aksel.nav.no/website/pages/eksempler/modal/close-on-click-outside.tsx rename to aksel.nav.no/website/pages/eksempler/modal/close-on-backdrop-click.tsx index e75fb12aa06..aefadfc2fc1 100644 --- a/aksel.nav.no/website/pages/eksempler/modal/close-on-click-outside.tsx +++ b/aksel.nav.no/website/pages/eksempler/modal/close-on-backdrop-click.tsx @@ -9,7 +9,7 @@ const Example = () => {
- + Culpa aliquip ut cupidatat laborum minim quis ex in aliqua. Qui @@ -34,5 +34,5 @@ export const Demo = { export const args = { index: 5, - desc: "Husk at det er lett å klikke utenfor ved et uhell. Ikke bruk 'closeOnClickOutside' hvis det kan føre til at brukeren mister data eller går glipp av viktig informasjon.", + desc: "Husk at det er lett å klikke utenfor ved et uhell. Ikke bruk 'closeOnBackdropClick' hvis det kan føre til at brukeren mister data eller går glipp av viktig informasjon.", }; From 4e64d4447b827fd8b112e6499fe5402213d273c8 Mon Sep 17 00:00:00 2001 From: Halvor Haugan Date: Thu, 26 Oct 2023 16:15:38 +0200 Subject: [PATCH 4/6] justering 2 --- @navikt/core/react/src/modal/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/@navikt/core/react/src/modal/types.ts b/@navikt/core/react/src/modal/types.ts index 5522ba11697..3ad4e5e026b 100644 --- a/@navikt/core/react/src/modal/types.ts +++ b/@navikt/core/react/src/modal/types.ts @@ -45,7 +45,7 @@ export interface ModalProps /** * Whether to close when clicking on the backdrop. * - * **WARNING:** Users may click outside by accident. Don't use if closing causes data loss, or the modal contains important info. + * **WARNING:** Users may click outside by accident. Don't use if closing can cause data loss, or the modal contains important info. * @default false */ closeOnBackdropClick?: boolean; From 9b4c34266ce4fb96b96b728b9b5dd8ba483ca452 Mon Sep 17 00:00:00 2001 From: Halvor Haugan Date: Fri, 27 Oct 2023 09:24:13 +0200 Subject: [PATCH 5/6] no clickoutside when polyfill --- @navikt/core/react/src/modal/Modal.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/@navikt/core/react/src/modal/Modal.tsx b/@navikt/core/react/src/modal/Modal.tsx index c91788f4189..4a381d65a53 100644 --- a/@navikt/core/react/src/modal/Modal.tsx +++ b/@navikt/core/react/src/modal/Modal.tsx @@ -148,7 +148,8 @@ export const Modal = forwardRef( } else if (onCancel) onCancel(event); }; - const mergedOnClick = closeOnBackdropClick + const mergedOnClick = + closeOnBackdropClick && !needPolyfill // closeOnBackdropClick has issues on polyfill when nesting modals (DatePicker) ? (event: React.MouseEvent) => { if (onClick) onClick(event); if ( From 5473d45f74b728ad9a48380fa95cfce3b740b81d Mon Sep 17 00:00:00 2001 From: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com> Date: Fri, 27 Oct 2023 12:22:03 +0200 Subject: [PATCH 6/6] Apply suggestions from code review --- @navikt/core/react/src/modal/Modal.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/@navikt/core/react/src/modal/Modal.tsx b/@navikt/core/react/src/modal/Modal.tsx index 4a381d65a53..f4e4eac9f64 100644 --- a/@navikt/core/react/src/modal/Modal.tsx +++ b/@navikt/core/react/src/modal/Modal.tsx @@ -151,12 +151,12 @@ export const Modal = forwardRef( const mergedOnClick = closeOnBackdropClick && !needPolyfill // closeOnBackdropClick has issues on polyfill when nesting modals (DatePicker) ? (event: React.MouseEvent) => { - if (onClick) onClick(event); + onClick && onClick(event); if ( event.target === modalRef.current && (!onBeforeClose || onBeforeClose() !== false) ) { - modalRef.current?.close(); + modalRef.current.close(); } } : onClick;