From e406bdebe6a1b23947c8befb40df923a14151438 Mon Sep 17 00:00:00 2001 From: pa-lem Date: Mon, 23 Sep 2024 10:48:40 +0200 Subject: [PATCH 01/12] add context and functions to display confirm modal --- .../app/src/components/display/slide-over.tsx | 106 +++++++++++------- .../src/components/modals/modal-confirm.tsx | 4 +- frontend/app/src/components/ui/form.tsx | 26 +++++ 3 files changed, 91 insertions(+), 45 deletions(-) diff --git a/frontend/app/src/components/display/slide-over.tsx b/frontend/app/src/components/display/slide-over.tsx index 84718f705d..22223ddc6c 100644 --- a/frontend/app/src/components/display/slide-over.tsx +++ b/frontend/app/src/components/display/slide-over.tsx @@ -1,11 +1,12 @@ import { Dialog, Transition } from "@headlessui/react"; -import React, { Fragment, useRef } from "react"; +import React, { Fragment, useRef, useState } from "react"; import { Badge } from "@/components/ui/badge"; import { Icon } from "@iconify-icon/react"; import { ObjectHelpButton } from "@/components/menu/object-help-button"; import { useAtomValue } from "jotai/index"; import { currentBranchAtom } from "@/state/atoms/branches.atom"; import { IModelSchema } from "@/state/atoms/schema.atom"; +import ModalConfirm from "../modals/modal-confirm"; interface Props { open: boolean; @@ -15,9 +16,16 @@ interface Props { offset?: number; } +interface SlideOverContextProps { + setPreventClose?: (value: boolean) => void; +} + +export const SlideOverContext = React.createContext({}); + export default function SlideOver(props: Props) { const { open, setOpen, title, offset = 0 } = props; const initialFocusRef = useRef(null); + const [preventClose, setPreventClose] = useState(false); // Need to define full classes so tailwind can compile the css const panelWidth = "w-[400px]"; @@ -28,51 +36,63 @@ export default function SlideOver(props: Props) { }; return ( - - - -
- - -
-
-
-
- {props.children} - - + {props.children} + + +
- -
-
+ + + + setOpen(true)} + onConfirm={() => setPreventClose(false)} + open={!open && preventClose} + setOpen={() => setPreventClose(false)} + /> + ); } diff --git a/frontend/app/src/components/modals/modal-confirm.tsx b/frontend/app/src/components/modals/modal-confirm.tsx index a8747eafa0..fcee449c14 100644 --- a/frontend/app/src/components/modals/modal-confirm.tsx +++ b/frontend/app/src/components/modals/modal-confirm.tsx @@ -9,10 +9,10 @@ interface iProps { isLoading?: boolean; setOpen: React.Dispatch>; title: string; - description: string | React.ReactNode; + description?: string | React.ReactNode; onConfirm: Function; onCancel: Function; - children: ReactNode; + children?: ReactNode; icon?: string; } diff --git a/frontend/app/src/components/ui/form.tsx b/frontend/app/src/components/ui/form.tsx index c61773fe0b..7d8505ee86 100644 --- a/frontend/app/src/components/ui/form.tsx +++ b/frontend/app/src/components/ui/form.tsx @@ -20,6 +20,8 @@ import { } from "react-hook-form"; import { Spinner } from "@/components/ui/spinner"; import Label, { LabelProps } from "@/components/ui/label"; +import { SlideOverContext } from "../display/slide-over"; +import { FormFieldValue } from "../form/type"; export type FormRef = ReturnType; @@ -32,6 +34,7 @@ export interface FormProps extends Omit, "on export const Form = React.forwardRef( ({ form, defaultValues, className, children, onSubmit, ...props }: FormProps, ref) => { const currentForm = form ?? useForm({ defaultValues }); + const slideOverContext = useContext(SlideOverContext); useImperativeHandle(ref, () => currentForm); @@ -39,6 +42,27 @@ export const Form = React.forwardRef( currentForm.reset(defaultValues); }, [JSON.stringify(defaultValues)]); + // Callback version of watch. It's your responsibility to unsubscribe when done. + useEffect(() => { + // Stop logic if there is no context to prevent the slide over close + if (!slideOverContext?.setPreventClose) return; + + const subscription = currentForm.watch((formData: Record) => { + const updatedValue = Object.entries(formData).find( + ([key, field]: [string, FormFieldValue]) => { + const defaultValue = defaultValues && defaultValues[key]; + + return (defaultValue?.value || field.value) && defaultValue?.value !== field.value; + } + ); + + if (updatedValue && slideOverContext.setPreventClose) + slideOverContext.setPreventClose(true); + }); + + return () => subscription.unsubscribe(); + }, [currentForm.watch]); + return (
( event.stopPropagation(); } + if (slideOverContext?.setPreventClose) slideOverContext?.setPreventClose(false); + if (!onSubmit) return; currentForm.handleSubmit(onSubmit)(event); From 1e7a316094bdae1683e7fd571ac1fa2cac674649 Mon Sep 17 00:00:00 2001 From: pa-lem Date: Mon, 23 Sep 2024 10:49:35 +0200 Subject: [PATCH 02/12] disable rule --- frontend/app/.eslintrc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/app/.eslintrc b/frontend/app/.eslintrc index 6982c2cd60..81da3d72ac 100644 --- a/frontend/app/.eslintrc +++ b/frontend/app/.eslintrc @@ -25,6 +25,7 @@ "tsconfig.json" ], "rules": { + "react/prop-types": "off", "quotes": [ "error", "double", @@ -74,4 +75,4 @@ "semi": "error", "no-trailing-spaces": "error" } -} +} \ No newline at end of file From 7469eba4729485749fb38a081125e6146a97cec0 Mon Sep 17 00:00:00 2001 From: pa-lem Date: Mon, 23 Sep 2024 11:03:54 +0200 Subject: [PATCH 03/12] add fragment --- changelog/4419.added.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog/4419.added.md diff --git a/changelog/4419.added.md b/changelog/4419.added.md new file mode 100644 index 0000000000..3163d1c4d0 --- /dev/null +++ b/changelog/4419.added.md @@ -0,0 +1 @@ +* Prevent form to be clsoed if the form has been updated \ No newline at end of file From 00e9090b6db79379b28200d874710a815ab897fc Mon Sep 17 00:00:00 2001 From: pa-lem Date: Mon, 23 Sep 2024 15:50:47 +0200 Subject: [PATCH 04/12] update modal --- frontend/app/src/components/display/slide-over.tsx | 7 ++++--- frontend/app/src/components/modals/modal-delete.tsx | 4 +++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/frontend/app/src/components/display/slide-over.tsx b/frontend/app/src/components/display/slide-over.tsx index 22223ddc6c..0a7d21575f 100644 --- a/frontend/app/src/components/display/slide-over.tsx +++ b/frontend/app/src/components/display/slide-over.tsx @@ -6,7 +6,7 @@ import { ObjectHelpButton } from "@/components/menu/object-help-button"; import { useAtomValue } from "jotai/index"; import { currentBranchAtom } from "@/state/atoms/branches.atom"; import { IModelSchema } from "@/state/atoms/schema.atom"; -import ModalConfirm from "../modals/modal-confirm"; +import ModalDelete from "../modals/modal-delete"; interface Props { open: boolean; @@ -84,13 +84,14 @@ export default function SlideOver(props: Props) { - setOpen(true)} onConfirm={() => setPreventClose(false)} open={!open && preventClose} setOpen={() => setPreventClose(false)} + confirmLabel="Close" /> ); diff --git a/frontend/app/src/components/modals/modal-delete.tsx b/frontend/app/src/components/modals/modal-delete.tsx index 1c9a22f4d1..3baee92424 100644 --- a/frontend/app/src/components/modals/modal-delete.tsx +++ b/frontend/app/src/components/modals/modal-delete.tsx @@ -12,6 +12,7 @@ interface iProps { onDelete: () => void; onCancel: () => void; children?: ReactNode; + confirmLabel?: string; } export default function ModalDelete({ @@ -23,6 +24,7 @@ export default function ModalDelete({ setOpen, isLoading, children, + confirmLabel, }: iProps) { const cancelButtonRef = useRef(null); @@ -84,7 +86,7 @@ export default function ModalDelete({ isLoading={isLoading} data-cy="modal-delete-confirm" data-testid="modal-delete-confirm"> - Delete + {confirmLabel ?? "Delete"}