Skip to content

Commit

Permalink
Merge pull request #140 from maykinmedia/issue/dialog-autofocus-alter…
Browse files Browse the repository at this point in the history
…native

✨ - feat: implemented useFormDialog hook
  • Loading branch information
Xaohs authored Oct 10, 2024
2 parents 1bd5291 + aad4220 commit 9fb6f1d
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 31 deletions.
2 changes: 2 additions & 0 deletions src/hooks/dialog/dialog.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ export const Prompt: Story = {
"Annuleren",
args.onConfirm,
args.onCancel,
undefined,
true,
)
}
>
Expand Down
123 changes: 123 additions & 0 deletions src/hooks/dialog/useFormDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
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";

/**
* 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
* @param autofocus
*/
const fn = (
title: string,
message: React.ReactNode,
fields: FormField[],
labelConfirm: string,
labelCancel: string,
onConfirm: (data: AttributeData) => void,
onCancel?: () => void,
modalProps?: Partial<ModalProps>,
formProps?: FormProps,
autofocus?: boolean,
) => {
dialog(
title,
<>
{typeof message === "string" ? <P>{message}</P> : message}
<PromptForm
message={message}
fields={fields}
labelConfirm={labelConfirm}
labelCancel={labelCancel}
onConfirm={onConfirm}
onCancel={onCancel}
formProps={formProps}
autofocus={autofocus}
/>
</>,
undefined,
{ allowClose: false, ...modalProps },
);
};

return fn;
};

const PromptForm = ({
fields,
labelConfirm,
labelCancel,
onConfirm,
onCancel,
formProps,
autofocus = false,
}: {
message: React.ReactNode;
fields: FormField[];
labelConfirm: string;
labelCancel: string;
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 (
<Form
fields={fields}
labelSubmit={labelConfirm}
secondaryActions={[
{
children: labelCancel,
type: "button",
variant: "secondary",
onClick: () => {
setModalProps({ open: false });
onCancel?.();
},
},
]}
validateOnChange={true}
onSubmit={(_, data) => {
setModalProps({ open: false });
onConfirm(data);
}}
{...formProps}
/>
);
};
59 changes: 28 additions & 31 deletions src/hooks/dialog/useprompt.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import React, { useContext } from "react";
import React from "react";

import { Form, ModalProps, P } from "../../components";
import { ModalServiceContext } from "../../contexts";
import { useDialog } from "./usedialog";
import { ModalProps } from "../../components";
import { FormField } from "../../lib";
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
Expand All @@ -23,6 +22,7 @@ export const usePrompt = () => {
* @param onConfirm
* @param onCancel
* @param modalProps
* @param autofocus
*/
const fn = (
title: string,
Expand All @@ -33,34 +33,31 @@ export const usePrompt = () => {
onConfirm: (message: string) => void,
onCancel?: () => void,
modalProps?: Partial<ModalProps>,
autofocus?: boolean,
) => {
dialog(
const fields: FormField[] = [
{
label,
name: "message",
required: true,
type: "text",
},
];

formDialog(
title,
<>
{typeof message === "string" ? <P>{message}</P> : message}
<Form
fields={[{ label, name: "message", required: true }]}
labelSubmit={labelConfirm}
secondaryActions={[
{
children: labelCancel,
variant: "secondary",
type: "button",
onClick: () => {
setModalProps({ open: false });
onCancel?.();
},
},
]}
validateOnChange={true}
onSubmit={(_, { message }) => {
setModalProps({ open: false });
onConfirm(message as string);
}}
/>
</>,
message,
fields,
labelConfirm,
labelCancel,
(data) => {
const message = data.message as string;
onConfirm(message);
},
onCancel,
modalProps,
undefined,
{ allowClose: false, ...modalProps },
autofocus,
);
};

Expand Down

0 comments on commit 9fb6f1d

Please sign in to comment.