From 127dba33e491f71bcea677ed314f2920461f1f21 Mon Sep 17 00:00:00 2001 From: Moshe Immermam Date: Tue, 18 Jun 2024 13:53:56 +0300 Subject: [PATCH] wip: formik modal chore: improve modal fix: fix type issues --- src/components/Forms/SpecEditorForm.tsx | 324 +++++++++--------- .../Add/steps/CatalogFormOption.tsx | 8 +- .../SpecEditor/ConfigScrapperSpecEditor.tsx | 34 +- .../SpecEditor/HealthSpecEditor.tsx | 52 +-- src/components/SpecEditor/ScraperTypes.ts | 17 +- src/components/SpecEditor/SpecEditor.tsx | 34 +- src/ui/Modal/FormikModal.stories.tsx | 66 ++++ src/ui/Modal/FormikModal.tsx | 278 +++++++++++++++ src/ui/Modal/index.tsx | 4 +- 9 files changed, 583 insertions(+), 234 deletions(-) create mode 100644 src/ui/Modal/FormikModal.stories.tsx create mode 100644 src/ui/Modal/FormikModal.tsx diff --git a/src/components/Forms/SpecEditorForm.tsx b/src/components/Forms/SpecEditorForm.tsx index b07e381bb..f0220bef4 100644 --- a/src/components/Forms/SpecEditorForm.tsx +++ b/src/components/Forms/SpecEditorForm.tsx @@ -1,5 +1,5 @@ import clsx from "clsx"; -import { Form, Formik } from "formik"; +import { Form, Formik, useFormikContext } from "formik"; import { useCallback, useMemo, useRef, useState } from "react"; import { Button } from "../../ui/Buttons/Button"; import { Tab, Tabs } from "../../ui/Tabs/Tabs"; @@ -18,6 +18,7 @@ import FormikTextInput from "./Formik/FormikTextInput"; type SpecEditorFormProps = { loadSpec: () => Record; + footer: boolean; updateSpec: (spec: Record) => void; onBack: () => void; specFormat: "yaml" | "json"; @@ -26,14 +27,17 @@ type SpecEditorFormProps = { onDeleted: () => void; }; + + export default function SpecEditorForm({ resourceInfo, loadSpec = () => ({}), - updateSpec = () => {}, - onBack = () => {}, + updateSpec = () => { }, + onBack = () => { }, specFormat = "yaml", + footer = true, selectedSpec, - onDeleted = () => {} + onDeleted = () => { } }: SpecEditorFormProps) { const formRef = useRef(null); const [activeTabs, setActiveTabs] = useState<"Form" | "Code">( @@ -90,164 +94,170 @@ export default function SpecEditorForm({ }; return ( - { - updateSpec(values); - }} - validateOnBlur - validateOnChange - > - {({ handleSubmit, handleReset, setFieldTouched }) => ( -
{ - handleSubmit(e); - touchAllFormFields(setFieldTouched); - }} - onReset={handleReset} - className="flex flex-col flex-1 overflow-y-auto space-y-4" - ref={formRef} - > -
-
- {isFieldSupportedByResourceType("name") && ( - - )} - {isFieldSupportedByResourceType("icon") && ( - - )} - {isFieldSupportedByResourceType("labels") && ( - - )} - {isFieldSupportedByResourceType("source") && ( - { + // console.log('123') + // updateSpec(values); + // }} + // validateOnBlur + // validateOnChange + // > + // {({ handleSubmit, handleReset, setFieldTouched }) => ( + // { + // console.log('456') + // handleSubmit(e); + // touchAllFormFields(setFieldTouched); + // }} + // onReset={handleReset} + // className="flex flex-col flex-1 overflow-y-auto space-y-4" + // ref={formRef} + // > + <> +
+
+ {isFieldSupportedByResourceType("name") && ( + + )} + {isFieldSupportedByResourceType("icon") && ( + + )} + {isFieldSupportedByResourceType("labels") && ( + + )} + {isFieldSupportedByResourceType("source") && ( +
+
+ {selectedSpec.type !== "form" ? ( + <> + + + + ) : ( + setActiveTabs(v as "Code" | "Form")} + > + +
+ +
+
+ + + +
+ )} +
+
+ {footer && +
+
+ + {!initialValues.id && ( +
+
)} - {isFieldSupportedByResourceType("schedule") && ( - )} -
-
- {selectedSpec.type !== "form" ? ( - <> - - - - ) : ( - setActiveTabs(v as "Code" | "Form")} - > - -
- -
-
- - - -
- )} -
-
-
-
- - {!initialValues.id && ( -
-
- )} - {!!initialValues.id && ( - - )} -
+
- - )} - +
+ } + + // + // )} + // ); } diff --git a/src/components/Integrations/Add/steps/CatalogFormOption.tsx b/src/components/Integrations/Add/steps/CatalogFormOption.tsx index fc991a689..123f931b4 100644 --- a/src/components/Integrations/Add/steps/CatalogFormOption.tsx +++ b/src/components/Integrations/Add/steps/CatalogFormOption.tsx @@ -1,13 +1,13 @@ import { useSettingsCreateResource } from "@flanksource-ui/api/query-hooks/mutations/useSettingsResourcesMutations"; import { schemaResourceTypes } from "@flanksource-ui/components/SchemaResourcePage/resourceTypes"; import ConfigScrapperSpecEditor from "@flanksource-ui/components/SpecEditor/ConfigScrapperSpecEditor"; +import { SpecEditorProps } from "@flanksource-ui/components/SpecEditor/SpecEditor"; -type Props = { +type Props = Pick & { onSuccess: () => void; - onBack: () => void; }; -export default function CatalogFormOption({ onSuccess, onBack }: Props) { +export default function CatalogFormOption({ onSuccess, ...props }: Props) { const resourceInfo = schemaResourceTypes.find( (resource) => resource.name === "Catalog Scraper" ); @@ -19,7 +19,7 @@ export default function CatalogFormOption({ onSuccess, onBack }: Props) { return (
- +
); } diff --git a/src/components/SpecEditor/ConfigScrapperSpecEditor.tsx b/src/components/SpecEditor/ConfigScrapperSpecEditor.tsx index b1fa87e0a..971ffbd75 100644 --- a/src/components/SpecEditor/ConfigScrapperSpecEditor.tsx +++ b/src/components/SpecEditor/ConfigScrapperSpecEditor.tsx @@ -1,18 +1,7 @@ import { useMemo } from "react"; -import { FaCog } from "react-icons/fa"; -import AWSConfigsFormEditor from "../Forms/Configs/AWSConfigsFormEditor"; -import AzureConfigsFormEditor from "../Forms/Configs/AzureConfigsFormEditor"; -import AzureDevopsConfigsFormEditor from "../Forms/Configs/AzureDevopsConfigsFormEditor"; -import FileConfigsFormEditor from "../Forms/Configs/FileConfigsFormEditor"; -import GithubActionsConfigsFormEditor from "../Forms/Configs/GithubActionsConfigsFormEditor"; -import HttpConfigsFormEditor from "../Forms/Configs/HttpConfigsFormEditor"; -import KubernetesConfigsFormEditor from "../Forms/Configs/KubernetesConfigsFormEditor"; -import KubernetesFileConfigsFormEditor from "../Forms/Configs/KubernetesFileConfigsFormEditor"; -import SQLConfigsFormEditor from "../Forms/Configs/SQLConfigsFormEditor"; -import TrivyConfigsFormEditor from "../Forms/Configs/TrivyConfigsFormEditor"; import { SchemaResourceType } from "../SchemaResourcePage/resourceTypes"; -import SpecEditor from "./SpecEditor"; import scraperTypes from "./ScraperTypes"; +import SpecEditor, { SpecEditorProps } from "./SpecEditor"; const resourceInfo: Pick = { name: "Catalog Scraper", @@ -20,20 +9,25 @@ const resourceInfo: Pick = { table: "config_scrapers" }; -type ConfigScrapperSpecEditorProps = { +type ConfigScrapperSpecEditorProps = Omit< + SpecEditorProps, + "types" | "resourceInfo" +> & { + footer?: boolean; resourceValue?: Record; onSubmit?: (spec: Record) => void; - onBack?: () => void; - onDeleted?: () => void; }; export default function ConfigScrapperSpecEditor({ resourceValue, + footer = true, onSubmit = () => {}, - onBack, - onDeleted = () => {} + ...props }: ConfigScrapperSpecEditorProps) { - const configTypes = scraperTypes(onSubmit, resourceValue); + const configTypes = useMemo( + () => scraperTypes(onSubmit, resourceValue), + [onSubmit, resourceValue] + ); // there should only be one spec, so we can just grab the first key that isn't // schedule, otherwise we'll just use custom @@ -46,11 +40,11 @@ export default function ConfigScrapperSpecEditor({ return ( ); } diff --git a/src/components/SpecEditor/HealthSpecEditor.tsx b/src/components/SpecEditor/HealthSpecEditor.tsx index 106a5b0c2..6405452ff 100644 --- a/src/components/SpecEditor/HealthSpecEditor.tsx +++ b/src/components/SpecEditor/HealthSpecEditor.tsx @@ -48,7 +48,7 @@ export default function HealthSpecEditor({ configForm: HTTPHealthFormEditor, specsMapField: "http.0", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/http" + help: "canary-checker/reference/http" }, { type: "custom", @@ -62,7 +62,7 @@ export default function HealthSpecEditor({ }, icon: "aws", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/aws-config-rule" + help: "canary-checker/reference/aws-config-rule" }, { type: "custom", @@ -76,7 +76,7 @@ export default function HealthSpecEditor({ }, icon: "aws-config", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/aws-config" + help: "canary-checker/reference/aws-config" }, { type: "custom", @@ -103,7 +103,7 @@ export default function HealthSpecEditor({ }, icon: "ldap", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/ldap" + help: "canary-checker/reference/ldap" }, { @@ -120,7 +120,7 @@ export default function HealthSpecEditor({ configForm: ExecHealthFormEditor, specsMapField: "exec.0", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/exec" + help: "canary-checker/reference/exec" }, { type: "form", @@ -136,7 +136,7 @@ export default function HealthSpecEditor({ configForm: AlertmanagerHealthFormEditor, specsMapField: "alertManager.0", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/alert-manager" + help: "canary-checker/reference/alert-manager" }, { type: "custom", @@ -150,7 +150,7 @@ export default function HealthSpecEditor({ }, icon: "cloudwatch", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/cloudwatch" + help: "canary-checker/reference/cloudwatch" }, { type: "form", @@ -166,7 +166,7 @@ export default function HealthSpecEditor({ configForm: ElasticsearchHealthFormEditor, specsMapField: "elasticsearch.0", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/elasticsearch" + help: "canary-checker/reference/elasticsearch" }, { type: "form", @@ -182,7 +182,7 @@ export default function HealthSpecEditor({ configForm: RedisHealthFormEditor, specsMapField: "redis.0", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/redis" + help: "canary-checker/reference/redis" }, { type: "form", @@ -198,7 +198,7 @@ export default function HealthSpecEditor({ configForm: MongoHealthFormEditor, specsMapField: "mongo.0", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/mongo" + help: "canary-checker/reference/mongo" }, { type: "custom", @@ -212,7 +212,7 @@ export default function HealthSpecEditor({ }, icon: "dns", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/dns" + help: "canary-checker/reference/dns" }, { type: "form", @@ -228,7 +228,7 @@ export default function HealthSpecEditor({ configForm: ICMPHealthFormEditor, specsMapField: "icmp.0", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/icmp" + help: "canary-checker/reference/icmp" }, { type: "custom", @@ -242,7 +242,7 @@ export default function HealthSpecEditor({ }, icon: "gcp", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/gcs-bucket" + help: "canary-checker/reference/gcs-bucket" }, { type: "custom", @@ -256,7 +256,7 @@ export default function HealthSpecEditor({ }, icon: "aws-s3-bucket", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/s3-bucket" + help: "canary-checker/reference/s3-bucket" }, { type: "custom", @@ -270,7 +270,7 @@ export default function HealthSpecEditor({ }, icon: "smb", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/smb" + help: "canary-checker/reference/smb" }, { type: "custom", @@ -284,7 +284,7 @@ export default function HealthSpecEditor({ }, icon: "sftp", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/sftp" + help: "canary-checker/reference/sftp" }, { type: "form", @@ -300,7 +300,7 @@ export default function HealthSpecEditor({ configForm: FolderHealthFormEditor, specsMapField: "folder.0", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/folder" + help: "canary-checker/reference/folder" }, { type: "custom", @@ -314,7 +314,7 @@ export default function HealthSpecEditor({ }, icon: "prometheus", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/prometheus" + help: "canary-checker/reference/prometheus" }, { type: "custom", @@ -328,7 +328,7 @@ export default function HealthSpecEditor({ }, icon: "kubernetes", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/kubernetes" + help: "canary-checker/reference/kubernetes" }, { @@ -343,7 +343,7 @@ export default function HealthSpecEditor({ }, icon: "kubernetes", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/kubernetesResource" + help: "canary-checker/reference/kubernetesResource" }, { type: "custom", @@ -357,7 +357,7 @@ export default function HealthSpecEditor({ }, icon: "postgres", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/postgres" + help: "canary-checker/reference/postgres" }, { type: "custom", @@ -371,7 +371,7 @@ export default function HealthSpecEditor({ }, icon: "config", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/catalog" + help: "canary-checker/reference/catalog" }, { type: "custom", @@ -385,7 +385,7 @@ export default function HealthSpecEditor({ }, icon: "azure-devops", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/azure-devops" + help: "canary-checker/reference/azure-devops" }, { type: "custom", @@ -399,7 +399,7 @@ export default function HealthSpecEditor({ }, icon: "jmeter", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/jmeter" + help: "canary-checker/reference/jmeter" }, { type: "custom", @@ -413,7 +413,7 @@ export default function HealthSpecEditor({ }, icon: "junit", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/junit" + help: "canary-checker/reference/junit" }, { type: "custom", @@ -442,7 +442,7 @@ export default function HealthSpecEditor({ configForm: TCPHealthFormEditor, specsMapField: "tcp.0", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/tcp" + help: "canary-checker/reference/tcp" }, { type: "custom", diff --git a/src/components/SpecEditor/ScraperTypes.ts b/src/components/SpecEditor/ScraperTypes.ts index edc28f7e7..da842b4ff 100644 --- a/src/components/SpecEditor/ScraperTypes.ts +++ b/src/components/SpecEditor/ScraperTypes.ts @@ -31,7 +31,7 @@ export default function scraperTypes( configForm: AWSConfigsFormEditor, specsMapField: "aws.0", schemaFileName: "config_aws.schema.json", - docsLink: "config-db/scrapers/aws" + help: "config-db/scrapers/aws" }, { type: "form", @@ -49,7 +49,7 @@ export default function scraperTypes( configForm: KubernetesConfigsFormEditor, specsMapField: "kubernetes.0", schemaFileName: "config_kubernetes.schema.json", - docsLink: "config-db/scrapers/kubernetes" + help: "config-db/scrapers/kubernetes" }, { type: "form", @@ -65,7 +65,7 @@ export default function scraperTypes( configForm: AzureConfigsFormEditor, specsMapField: "azure.0", schemaFileName: "config_azure.schema.json", - docsLink: "config-db/scrapers/azure" + help: "config-db/scrapers/azure" }, { type: "form", @@ -81,7 +81,7 @@ export default function scraperTypes( configForm: KubernetesFileConfigsFormEditor, specsMapField: "kubernetesFile.0", schemaFileName: "config_kubernetesfile.schema.json", - docsLink: "config-db/scrapers/kubernetes-file" + help: "config-db/scrapers/kubernetes-file" }, { type: "form", @@ -97,7 +97,7 @@ export default function scraperTypes( configForm: SQLConfigsFormEditor, specsMapField: "sql.0", schemaFileName: "config_sql.schema.json", - docsLink: "config-db/scrapers/sql" + help: "config-db/scrapers/sql" }, { type: "form", @@ -113,7 +113,7 @@ export default function scraperTypes( configForm: TrivyConfigsFormEditor, specsMapField: "trivy.0", schemaFileName: "config_trivy.schema.json", - docsLink: "config-db/scrapers/trivy" + help: "config-db/scrapers/trivy" }, { @@ -130,7 +130,7 @@ export default function scraperTypes( configForm: FileConfigsFormEditor, specsMapField: "file.0", schemaFileName: "config_file.schema.json", - docsLink: "config-db/scrapers/file" + help: "config-db/scrapers/file" }, { @@ -163,9 +163,8 @@ export default function scraperTypes( configForm: AzureDevopsConfigsFormEditor, specsMapField: "azureDevops.0", schemaFileName: "config_azuredevops.schema.json", - docsLink: "config-db/scrapers/azure-devops" + help: "config-db/scrapers/azure-devops" }, - { type: "form", name: "githubActions", diff --git a/src/components/SpecEditor/SpecEditor.tsx b/src/components/SpecEditor/SpecEditor.tsx index 2fca942cc..5c68c9c20 100644 --- a/src/components/SpecEditor/SpecEditor.tsx +++ b/src/components/SpecEditor/SpecEditor.tsx @@ -1,5 +1,5 @@ import { Button } from "@flanksource-ui/ui/Buttons/Button"; -import { modalHelpLinkAtom } from "@flanksource-ui/ui/Modal"; +import { modalHelpLinkAtom, useModal } from "@flanksource-ui/ui/Modal"; import { useAtom } from "jotai"; import React, { useEffect, useState } from "react"; import { Icon } from "../../ui/Icons/Icon"; @@ -15,7 +15,7 @@ export type SpecTypeCommonFields = { loadSpec: () => Record; updateSpec: (spec: Record) => void; schemaFileName: string | undefined; - docsLink?: string; + help?: string; }; export type SpecTypeInputForm = SpecTypeCommonFields & { @@ -51,23 +51,27 @@ export type SpecType = SpecTypeInputForm | SpecTypeCode | SpecTypeCustom; export type SpecEditorProps = { types: SpecType[]; + footer?: boolean; format?: "json" | "yaml"; resourceInfo: Pick; selectedSpec?: string; onBack?: () => void; onDeleted?: () => void; + onTypeSelected?: (type?: SpecType) => void; }; export default function SpecEditor({ types, format = "yaml", + footer = true, resourceInfo, selectedSpec, onBack, - onDeleted = () => {} + onTypeSelected, + onDeleted = () => { } }: SpecEditorProps) { - const [, setModalHelpLink] = useAtom(modalHelpLinkAtom); - const [, setIntegrationsModalSubTitle] = useAtom(integrationsModalSubTitle); + + const { props, setProps } = useModal(); const [selectedSpecItem, setSelectedSpecItem] = useState< SpecType | undefined @@ -101,6 +105,7 @@ export default function SpecEditor({
{ setSelectedSpecItem(undefined); @@ -119,8 +124,12 @@ export default function SpecEditor({
{ setSelectedSpecItem(type); - setModalHelpLink(type.docsLink); - setIntegrationsModalSubTitle(type.label ?? type.name); + setProps({ + ...props, + helpLink: type.help, + title: `Add ${type.label}`, + + }) }} role={"button"} className="flex flex-col items-center space-y-2 justify-center p-2 border border-gray-300 hover:border-blue-200 hover:bg-gray-100 rounded-md text-center h-20" @@ -136,16 +145,7 @@ export default function SpecEditor({
))}
- {onBack && ( -
-
- )} +
)} diff --git a/src/ui/Modal/FormikModal.stories.tsx b/src/ui/Modal/FormikModal.stories.tsx new file mode 100644 index 000000000..62af2b9fd --- /dev/null +++ b/src/ui/Modal/FormikModal.stories.tsx @@ -0,0 +1,66 @@ +import { FormikCodeEditor } from "@flanksource-ui/components/Forms/Formik/FormikCodeEditor"; +import FormikTextInput from "@flanksource-ui/components/Forms/Formik/FormikTextInput"; +import { StoryFn } from "@storybook/react"; +import { useState } from "react"; +import FormikModal from "./FormikModal"; + +export default { + title: "FormikModal", + component: FormikModal +}; + +const Template: StoryFn = (arg) => { + const [isOpen, setIsOpen] = useState(false); + const handleClose = () => setIsOpen(false); + return ( + <> + + + + ); +}; + +export const Variant1 = Template.bind({}); +Variant1.args = { + title: "Modal title", + formikFormProps: { + onDelete: (values) => console.log("deleted", values), + onSave: (values) => console.log("saved", values), + onBack: () => console.log("backed"), + showClose: true, + showDelete: true, + showSave: true + }, + size: "medium", + showExpand: true, + children: ( + <> + + + + + + + ) +}; diff --git a/src/ui/Modal/FormikModal.tsx b/src/ui/Modal/FormikModal.tsx new file mode 100644 index 000000000..44c0c47cb --- /dev/null +++ b/src/ui/Modal/FormikModal.tsx @@ -0,0 +1,278 @@ +import { Dialog, Transition } from "@headlessui/react"; +import { XIcon } from "@heroicons/react/solid"; +import clsx from "clsx"; +import { Form, Formik } from "formik"; +import { Fragment, createContext, useContext, useRef, useState } from "react"; +import { BsArrowsFullscreen, BsFullscreenExit } from "react-icons/bs"; +import { IModalProps, useDialogSize } from "."; +import { Button } from "../Buttons/Button"; +import DialogButton from "../Buttons/DialogButton"; +import HelpLink from "../Buttons/HelpLink"; + +interface FormikFormModalProps { + showBack?: boolean; + showDelete?: boolean; + showSave?: boolean; + showClose?: boolean; + touchAllOnSubmit?: boolean; + saveTitle?: string; + backTitle?: string; + deleteTitle?: string; + closeTitle?: string; + onBack?: () => void; + onSave?: (values: Record) => void | Promise; + onDelete?: (values: Record) => void | Promise; +} + +export type FormikModalContextProps = { + props: FormikFormModalProps; + setProps: (props: FormikFormModalProps) => void; + values: Record; +}; + +export function useFormikModal() { + const { props, setProps: set, values } = useContext(FormikModalContext); + const setProps = (p: FormikFormModalProps) => { + set(p); + }; + return { + props, + setProps, + values + }; +} + +export const FormikModalContext = createContext( + undefined! +); + +type FormikModalProps = IModalProps & { + formikFormProps: FormikFormModalProps; + /** + * Can only be used during the initial render of the modal, after that, you + * can use Formik's setFieldValue to update the form values. + */ + initialValues?: Record; +}; + +export default function FormikModal({ + onClose = () => {}, + formikFormProps, + size = "medium", + helpLink, + showExpand, + bodyClass, + initialValues, + title, + titleClass, + hideCloseButton, + childClassName, + ...props +}: FormikModalProps) { + const [formikProps, setFormikProps] = useState(() => { + // set default values for formikFormProps + const { + saveTitle = "Save", + backTitle = "Back", + deleteTitle = "Delete", + closeTitle = "Close", + ...props + } = formikFormProps; + + return { + ...props, + saveTitle, + backTitle, + deleteTitle, + closeTitle + }; + }); + + const [_size, _setSize] = useState(size); + const sizeClass = useDialogSize(_size); + const formRef = useRef(null); + + const touchAllFormFields = ( + setFieldTouched: ( + field: string, + isTouched?: boolean | undefined, + shouldValidate?: boolean | undefined + ) => void + ) => { + [...(formRef.current?.elements || [])].forEach((element) => { + setFieldTouched(element.getAttribute("name")!, true, true); + }); + }; + return ( + + { + if (props.allowBackgroundClose) { + onClose(); + } + }} + {...props} + > +
+ {/* @ts-ignore */} + + + + + +
+
+

+ {title} +

+ + {helpLink && } + {showExpand && _size !== "full" && _size !== "small" && ( + _setSize("full")} + /> + )} + + {showExpand && _size === "full" && ( + _setSize("medium")} + /> + )} + + {!hideCloseButton && ( + + )} +
+ +
+ { + if (formikProps.onSave) { + formikProps.onSave(values); + } + }} + validateOnBlur + validateOnChange + > + {({ handleSubmit, handleReset, setFieldTouched, values }) => ( + +
{ + handleSubmit(e); + if (formikProps.touchAllOnSubmit) { + touchAllFormFields(setFieldTouched); + } + }} + onReset={handleReset} + className="flex flex-col h-full justify-between flex-1 space-y-4" + ref={formRef} + > +
+ {props.children} +
+ + {(formikProps.showBack || + formikProps.showSave || + formikProps.showDelete || + formikProps.showClose) && ( +
+
+ {formikProps.showBack && formikProps.onBack && ( +
+ +
+ )} + {formikProps.showDelete && + formikProps.onDelete && ( + + )} + + {formikProps.showClose && onClose && ( + + )} + {formikProps.showSave && ( + + )} +
+
+ )} +
+
+ )} +
+
+
+
+
+
+
+ ); +} diff --git a/src/ui/Modal/index.tsx b/src/ui/Modal/index.tsx index 949482526..292701e43 100644 --- a/src/ui/Modal/index.tsx +++ b/src/ui/Modal/index.tsx @@ -34,7 +34,7 @@ export const ModalContext = createContext(undefined!); */ export const modalHelpLinkAtom = atom(undefined); -export type ModalSize = "small" | "medium" | "large" | "full"; +export type ModalSize = "very-small" | "small" | "medium" | "large" | "full"; export interface IModalProps { title?: React.ReactNode; @@ -113,6 +113,7 @@ export function Modal({ const sizeClass = useDialogSize(_size); return ( + // @ts-ignore + {/* @ts-ignore */}