From 39d69db5ebff3979d5372f40343035ab22b4ce34 Mon Sep 17 00:00:00 2001 From: Julian Roeland Date: Thu, 3 Oct 2024 13:42:29 +0200 Subject: [PATCH 1/2] :sparkles: - feat: implemented useFormDialog hook --- src/hooks/dialog/useFormDialog.tsx | 112 +++++++++++++++++++++++++++++ src/hooks/dialog/useprompt.tsx | 57 +++++++-------- 2 files changed, 137 insertions(+), 32 deletions(-) create mode 100644 src/hooks/dialog/useFormDialog.tsx diff --git a/src/hooks/dialog/useFormDialog.tsx b/src/hooks/dialog/useFormDialog.tsx new file mode 100644 index 0000000..069dbce --- /dev/null +++ b/src/hooks/dialog/useFormDialog.tsx @@ -0,0 +1,112 @@ +import { + AttributeData, + Form, + FormField, + FormProps, + ModalProps, + P, +} from "@maykin-ui/admin-ui"; +import React, { useContext } from "react"; + +import { ModalServiceContext } from "../../contexts"; +import { useDialog } from "./usedialog"; + +/** + * Returns a function which, when called: shows a form dialog with a + * confirmation callback and an optional cancellation callback. + */ +export const useFormDialog = () => { + const dialog = useDialog(); + + /** + * Shows a prompt dialog with a confirmation callback and an optional + * cancellation callback. + * @param title + * @param message + * @param fields + * @param labelConfirm + * @param labelCancel + * @param onConfirm + * @param onCancel + * @param modalProps + * @param formProps + */ + const fn = ( + title: string, + message: React.ReactNode, + fields: FormField[], + labelConfirm: string, + labelCancel: string, + onConfirm: (data: AttributeData) => void, + onCancel?: () => void, + modalProps?: Partial, + formProps?: FormProps, + ) => { + dialog( + title, + <> + {typeof message === "string" ?

{message}

: message} + + , + undefined, + { allowClose: false, ...modalProps }, + ); + }; + + return fn; +}; + +const PromptForm = ({ + message, + fields, + labelConfirm, + labelCancel, + onConfirm, + onCancel, + formProps, +}: { + message: React.ReactNode; + fields: FormField[]; + labelConfirm: string; + labelCancel: string; + onConfirm: (data: AttributeData) => void; + onCancel?: () => void; + formProps?: FormProps; +}) => { + const { setModalProps } = useContext(ModalServiceContext); + + return ( + <> + {typeof message === "string" ?

{message}

: message} +
{ + setModalProps({ open: false }); + onCancel?.(); + }, + }, + ]} + validateOnChange={true} + onSubmit={(_, data) => { + setModalProps({ open: false }); + onConfirm(data); + }} + {...formProps} + /> + + ); +}; diff --git a/src/hooks/dialog/useprompt.tsx b/src/hooks/dialog/useprompt.tsx index cc3e548..50a44ac 100644 --- a/src/hooks/dialog/useprompt.tsx +++ b/src/hooks/dialog/useprompt.tsx @@ -1,16 +1,15 @@ -import React, { useContext } from "react"; +import { FormField } from "@maykin-ui/admin-ui"; +import React from "react"; -import { Form, ModalProps, P } from "../../components"; -import { ModalServiceContext } from "../../contexts"; -import { useDialog } from "./usedialog"; +import { ModalProps } from "../../components"; +import { useFormDialog } from "./useFormDialog"; /** * Returns a function which, when called: shows a prompt dialog with a * confirmation callback and an optional cancellation callback. */ export const usePrompt = () => { - const dialog = useDialog(); - const { setModalProps } = useContext(ModalServiceContext); + const formDialog = useFormDialog(); /** * Shows a prompt dialog with a confirmation callback and an optional @@ -34,33 +33,27 @@ export const usePrompt = () => { onCancel?: () => void, modalProps?: Partial, ) => { - dialog( + const fields: FormField[] = [ + { + label, + name: "message", + required: true, + type: "text", + }, + ]; + + formDialog( title, - <> - {typeof message === "string" ?

{message}

: message} - { - setModalProps({ open: false }); - onCancel?.(); - }, - }, - ]} - validateOnChange={true} - onSubmit={(_, { message }) => { - setModalProps({ open: false }); - onConfirm(message as string); - }} - /> - , - undefined, - { allowClose: false, ...modalProps }, + message, + fields, + labelConfirm, + labelCancel, + (data) => { + const message = data.message as string; + onConfirm(message); + }, + onCancel, + modalProps, ); }; From aad42207ebd70beb861016b07cfa61bbf64165f7 Mon Sep 17 00:00:00 2001 From: Julian Roeland Date: Thu, 3 Oct 2024 15:00:06 +0200 Subject: [PATCH 2/2] :sparkles: - feat: implemented autofocus --- src/hooks/dialog/dialog.stories.tsx | 2 + src/hooks/dialog/useFormDialog.tsx | 77 ++++++++++++++++------------- src/hooks/dialog/useprompt.tsx | 6 ++- 3 files changed, 51 insertions(+), 34 deletions(-) diff --git a/src/hooks/dialog/dialog.stories.tsx b/src/hooks/dialog/dialog.stories.tsx index 9e47938..5479f7d 100644 --- a/src/hooks/dialog/dialog.stories.tsx +++ b/src/hooks/dialog/dialog.stories.tsx @@ -98,6 +98,8 @@ export const Prompt: Story = { "Annuleren", args.onConfirm, args.onCancel, + undefined, + true, ) } > diff --git a/src/hooks/dialog/useFormDialog.tsx b/src/hooks/dialog/useFormDialog.tsx index 069dbce..f59dabd 100644 --- a/src/hooks/dialog/useFormDialog.tsx +++ b/src/hooks/dialog/useFormDialog.tsx @@ -1,14 +1,8 @@ -import { - AttributeData, - Form, - FormField, - FormProps, - ModalProps, - P, -} from "@maykin-ui/admin-ui"; -import React, { useContext } from "react"; +import React, { useContext, useEffect } from "react"; +import { Form, FormProps, ModalProps, P } from "../../components"; import { ModalServiceContext } from "../../contexts"; +import { AttributeData, FormField } from "../../lib"; import { useDialog } from "./usedialog"; /** @@ -30,6 +24,7 @@ export const useFormDialog = () => { * @param onCancel * @param modalProps * @param formProps + * @param autofocus */ const fn = ( title: string, @@ -41,6 +36,7 @@ export const useFormDialog = () => { onCancel?: () => void, modalProps?: Partial, formProps?: FormProps, + autofocus?: boolean, ) => { dialog( title, @@ -54,6 +50,7 @@ export const useFormDialog = () => { onConfirm={onConfirm} onCancel={onCancel} formProps={formProps} + autofocus={autofocus} /> , undefined, @@ -65,13 +62,13 @@ export const useFormDialog = () => { }; const PromptForm = ({ - message, fields, labelConfirm, labelCancel, onConfirm, onCancel, formProps, + autofocus = false, }: { message: React.ReactNode; fields: FormField[]; @@ -80,33 +77,47 @@ const PromptForm = ({ onConfirm: (data: AttributeData) => void; onCancel?: () => void; formProps?: FormProps; + autofocus?: boolean; }) => { const { setModalProps } = useContext(ModalServiceContext); + useEffect(() => { + if (!autofocus) return; + // Delay the focus slightly to ensure modal and form are fully rendered + const timer = setTimeout(() => { + // We focus a form element, and if none are found, we focus the submit button, and otherwise none + const formElement: HTMLFormElement | null = document.querySelector( + "form input , form textarea , form select , form button[type=submit]", + ); + if (formElement) { + formElement.focus(); + } + }, 100); + + return () => clearTimeout(timer); + }, []); + return ( - <> - {typeof message === "string" ?

{message}

: message} - { - setModalProps({ open: false }); - onCancel?.(); - }, + { + setModalProps({ open: false }); + onCancel?.(); }, - ]} - validateOnChange={true} - onSubmit={(_, data) => { - setModalProps({ open: false }); - onConfirm(data); - }} - {...formProps} - /> - + }, + ]} + validateOnChange={true} + onSubmit={(_, data) => { + setModalProps({ open: false }); + onConfirm(data); + }} + {...formProps} + /> ); }; diff --git a/src/hooks/dialog/useprompt.tsx b/src/hooks/dialog/useprompt.tsx index 50a44ac..b6d66d9 100644 --- a/src/hooks/dialog/useprompt.tsx +++ b/src/hooks/dialog/useprompt.tsx @@ -1,7 +1,7 @@ -import { FormField } from "@maykin-ui/admin-ui"; import React from "react"; import { ModalProps } from "../../components"; +import { FormField } from "../../lib"; import { useFormDialog } from "./useFormDialog"; /** @@ -22,6 +22,7 @@ export const usePrompt = () => { * @param onConfirm * @param onCancel * @param modalProps + * @param autofocus */ const fn = ( title: string, @@ -32,6 +33,7 @@ export const usePrompt = () => { onConfirm: (message: string) => void, onCancel?: () => void, modalProps?: Partial, + autofocus?: boolean, ) => { const fields: FormField[] = [ { @@ -54,6 +56,8 @@ export const usePrompt = () => { }, onCancel, modalProps, + undefined, + autofocus, ); };