diff --git a/pkg/interface/src/logic/lib/formGroup.ts b/pkg/interface/src/logic/lib/formGroup.ts new file mode 100644 index 0000000000..e2f7ec7d79 --- /dev/null +++ b/pkg/interface/src/logic/lib/formGroup.ts @@ -0,0 +1,18 @@ +import React from "react"; + +export type SubmitHandler = () => Promise; +interface IFormGroupContext { + addSubmit: (id: string, submit: SubmitHandler) => void; + onDirty: (id: string, touched: boolean) => void; + onErrors: (id: string, errors: boolean) => void; + submitAll: () => Promise; +} + +const fallback: IFormGroupContext = { + addSubmit: () => {}, + onDirty: () => {}, + onErrors: () => {}, + submitAll: () => Promise.resolve(), +}; + +export const FormGroupContext = React.createContext(fallback); diff --git a/pkg/interface/src/views/components/FormGroup.tsx b/pkg/interface/src/views/components/FormGroup.tsx new file mode 100644 index 0000000000..6d78408e02 --- /dev/null +++ b/pkg/interface/src/views/components/FormGroup.tsx @@ -0,0 +1,134 @@ +import React, { + ReactNode, + useEffect, + useCallback, + useState, + useMemo, +} from "react"; +import { Button, Box, Row, Col } from "@tlon/indigo-react"; +import _ from "lodash"; +import { useFormikContext } from "formik"; +import { PropFunc } from "~/types"; +import { FormGroupContext, SubmitHandler } from "~/logic/lib/formGroup"; +import { StatelessAsyncButton } from "./StatelessAsyncButton"; + +export function useFormGroupContext(id: string) { + const ctx = React.useContext(FormGroupContext); + const addSubmit = useCallback( + (submit: SubmitHandler) => { + ctx.addSubmit(id, submit); + }, + [ctx.addSubmit, id] + ); + const onDirty = useCallback( + (dirty: boolean) => { + console.log(id, dirty); + ctx.onDirty(id, dirty); + }, + [ctx.onDirty, id] + ); + + const onErrors = useCallback( + (errors: boolean) => { + ctx.onErrors(id, errors); + }, + [ctx.onErrors, id] + ); + + return { + onDirty, + addSubmit, + onErrors, + }; +} + +export function FormGroupChild(props: { id: string }) { + const { id } = props; + const { addSubmit, onDirty, onErrors } = useFormGroupContext(id); + const { submitForm, dirty, errors } = useFormikContext(); + + useEffect(() => { + addSubmit(submitForm); + }, [submitForm]); + + useEffect(() => { + onDirty(dirty); + }, [dirty, onDirty]); + + useEffect(() => { + onErrors(_.keys(_.pickBy(errors, (s) => !!s)).length > 0); + }, [errors, onErrors]); + + return ; +} + +export function FormGroup(props: PropFunc) { + const { children, ...rest } = props; + const [submits, setSubmits] = useState({} as { [id: string]: SubmitHandler }); + const [dirty, setDirty] = useState({} as Record); + const [errors, setErrors] = useState({} as Record); + const addSubmit = useCallback((id: string, s: SubmitHandler) => { + setSubmits((ss) => ({ ...ss, [id]: s })); + }, []); + + const submitAll = useCallback(() => { + return Promise.all( + _.map( + _.pickBy(submits, (_v, k) => dirty[k]), + (f) => f() + ) + ); + }, [submits, dirty]); + + const onDirty = useCallback( + (id: string, t: boolean) => { + setDirty((ts) => ({ ...ts, [id]: t })); + }, + [setDirty] + ); + + const onErrors = useCallback((id: string, e: boolean) => { + setErrors((es) => ({ ...es, [id]: e })); + }, []); + + const context = { addSubmit, submitAll, onErrors, onDirty }; + + const hasErrors = useMemo( + () => _.keys(_.pickBy(errors, (s) => !!s)).length > 0, + [errors] + ); + const isDirty = useMemo( + () => _.keys(_.pickBy(dirty, _.identity)).length > 0, + [dirty] + ); + + return ( + + + {children} + + {isDirty && ( + + + + Update + + + )} + + ); +}