diff --git a/src/Form/components/Form.tsx b/src/Form/components/Form.tsx deleted file mode 100644 index 4c7c047..0000000 --- a/src/Form/components/Form.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import React, { type ReactNode } from 'react' -import { Card, Stack, LoadingOverlay } from '@mantine/core' -import { FormSection } from './FormSection' - -interface FormProps { - children: ReactNode - loading?: boolean - [key: string]: any -} - -export const Form: React.FC & { - Section: typeof FormSection -} = ({ children, loading, ...others }) => { - return ( - - - {children} - - ) -} - -Form.Section = FormSection diff --git a/src/Form/components/FormLayout.tsx b/src/Form/components/FormLayout.tsx deleted file mode 100644 index 3fe1c74..0000000 --- a/src/Form/components/FormLayout.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import React, { type ReactNode } from 'react' -import { Modal } from '@mantine/core' -import { Form } from './Form' -import { FormLayoutStoreProvider, useFormLayoutStore } from '../store/FormLayout.store' -import type { RequestType } from '../types' - -interface FormLayoutProps { - children: ReactNode - dirty?: boolean - loading?: boolean - onCancel?: () => void - onSubmit?: () => void - request?: RequestType - modal?: boolean - modalProps?: any - nested?: boolean - deleteFormProps?: any - [key: string]: any -} - -const LayoutComponent: React.FC = ({ - children, - modal, - modalProps, - ...others -}) => { - const { request } = useFormLayoutStore((state) => ({ - request: state.request, - })) - - const modalWidth = request === 'delete' ? 400 : modalProps?.width - - return modal === true ? ( - modalProps?.onClose?.()} - opened={modalProps?.opened || false} - size="auto" - withCloseButton={false} - centered={request !== 'delete'} - {...modalProps} - > -
-
{children}
-
-
- ) : ( -
{children}
- ) -} - -export const FormLayout: React.FC = (props) => ( - - - -) diff --git a/src/Form/components/FormSection.tsx b/src/Form/components/FormSection.tsx deleted file mode 100644 index 7fa9368..0000000 --- a/src/Form/components/FormSection.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import React, { type ReactNode } from 'react' -import { Stack, Group, Paper, Button, Title, Box } from '@mantine/core' -import { useFormLayoutStore } from '../store/FormLayout.store' - -interface FormSectionProps { - type: 'header' | 'body' | 'footer' | 'error' - title?: string - rightSection?: ReactNode - children?: ReactNode - buttonTitles?: { submit?: string; cancel?: string } - className?: string - [key: string]: any -} - -export const FormSection: React.FC = ({ - type, - title, - rightSection, - children, - buttonTitles, - className, - ...others -}) => { - const { onCancel, onSubmit, request, loading } = useFormLayoutStore((state) => ({ - onCancel: state.onCancel, - onSubmit: state.onSubmit, - request: state.request, - loading: state.loading, - })) - - if (type === 'header') { - return ( - - - {title} - - {rightSection && {rightSection}} - - ) - } - - if (type === 'body') { - return ( - - {children} - - ) - } - - if (type === 'footer') { - return ( - - {children} - {request !== 'view' && ( - <> - - - - )} - - ) - } - - if (type === 'error') { - return ( - - {children} - - ) - } - - return null -} diff --git a/src/Form/components/SuperForm.tsx b/src/Form/components/SuperForm.tsx deleted file mode 100644 index 935a8f7..0000000 --- a/src/Form/components/SuperForm.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import React, { forwardRef, type ReactElement, type Ref } from 'react' -import { FormProvider, useForm, type FieldValues } from 'react-hook-form' -import { Provider } from '../store/SuperForm.store' -import type { SuperFormProps, SuperFormRef } from '../types' -import Layout from './SuperFormLayout' -import SuperFormPersist from './SuperFormPersist' - -const SuperForm = ( - { useFormProps, gridRef, children, persist, ...others }: SuperFormProps, - ref -) => { - const form = useForm({ ...useFormProps }) - - return ( - - - {persist && ( - - )} - gridRef={gridRef} ref={ref}> - {children} - - - - ) -} - -const FRSuperForm = forwardRef(SuperForm) as ( - props: SuperFormProps & { - ref?: Ref> - } -) => ReactElement - -export default FRSuperForm diff --git a/src/Form/components/SuperFormLayout.tsx b/src/Form/components/SuperFormLayout.tsx deleted file mode 100644 index 51dc6a2..0000000 --- a/src/Form/components/SuperFormLayout.tsx +++ /dev/null @@ -1,364 +0,0 @@ -import React, { - forwardRef, - RefObject, - useEffect, - useImperativeHandle, - useMemo, - type MutableRefObject, - type ReactElement, - type ReactNode, - type Ref, -} from 'react' -import { useFormContext, useFormState, type FieldValues, type UseFormReturn } from 'react-hook-form' -import { v4 as uuid } from 'uuid' -import { - ActionIcon, - Group, - List, - LoadingOverlay, - Paper, - Spoiler, - Stack, - Title, - Tooltip, - Transition, -} from '@mantine/core' -import { IconChevronsLeft, IconChevronsRight } from '@tabler/icons-react' -import { useUncontrolled } from '@mantine/hooks' -import useRemote from '../hooks/useRemote' -import { useStore } from '../store/SuperForm.store' -import classes from '../styles/Form.module.css' -import { Form } from './Form' -import { FormLayout } from './FormLayout' -import type { GridRef, SuperFormRef } from '../types' - -const SuperFormLayout = ( - { - children, - gridRef, - }: { - children: React.ReactNode | ((props: UseFormReturn) => React.ReactNode) - gridRef?: MutableRefObject | null> - }, - ref -) => { - // Component store State - const { - layoutProps, - meta, - nested, - onBeforeSubmit, - onCancel, - onLayoutMounted, - onLayoutUnMounted, - onResetForm, - onSubmit, - primeData, - request, - tableName, - value, - } = useStore((state) => ({ - extraButtons: state.extraButtons, - layoutProps: state.layoutProps, - meta: state.meta, - nested: state.nested, - onBeforeSubmit: state.onBeforeSubmit, - onCancel: state.onCancel, - onLayoutMounted: state.onLayoutMounted, - onLayoutUnMounted: state.onLayoutUnMounted, - onResetForm: state.onResetForm, - onSubmit: state.onSubmit, - primeData: state.primeData, - request: state.request, - tableName: state.remote?.tableName, - value: state.value, - })) - - const [_opened, _setOpened] = useUncontrolled({ - value: layoutProps?.bodyRightSection?.opened, - defaultValue: false, - onChange: layoutProps?.bodyRightSection?.setOpened, - }) - - // Component Hooks - const form = useFormContext() - const formState = useFormState({ control: form.control }) - - const { isFetching, mutateAsync, error, queryKey } = useRemote(gridRef) - - // Component variables - const formUID = useMemo(() => { - return meta?.id ?? uuid() - }, []) - - const requestString = request?.charAt(0).toUpperCase() + request?.slice(1) - - const renderRightSection = ( - <> - - _setOpened(!_opened)} - radius='6' - m={2} - > - {_opened ? : } - - - - - {typeof children === 'function' ? children({ ...form }) : children} - - - {(transitionStyles) => ( - - {layoutProps?.bodyRightSection?.render?.({ - form, - formValue: form.getValues(), - isFetching, - opened: _opened, - queryKey, - setOpened: _setOpened, - })} - - )} - - - - ) - - // Component Callback Functions - const onFormSubmit = async (data: T | any, closeForm: boolean = true) => { - const res: any = - typeof onBeforeSubmit === 'function' - ? await mutateAsync?.(await onBeforeSubmit(data, request, form)) - : await mutateAsync?.(data) - - if ((tableName?.length ?? 0) > 0) { - if (res?.ok || (res?.status >= 200 && res?.status < 300)) { - onSubmit?.(res?.data, request, data, form, closeForm) - } else { - form.setError('root', { - message: res.status === 401 ? 'Username or password is incorrect' : res?.error, - }) - } - } else { - onSubmit?.(data, request, data, form, closeForm) - } - } - - // Component use Effects - useEffect(() => { - if (request === 'insert') { - if (onResetForm) { - onResetForm(primeData, form).then((resetData) => { - form.reset(resetData) - }) - } else { - form.reset(primeData) - } - } else if ((request === 'change' || request === 'delete') && (tableName?.length ?? 0) === 0) { - if (onResetForm) { - onResetForm(value, form).then((resetData) => { - form.reset(resetData) - }) - } else { - form.reset(value) - } - } - onLayoutMounted?.() - return onLayoutUnMounted - }, [ - request, - primeData, - tableName, - value, - form.reset, - onResetForm, - onLayoutMounted, - onLayoutUnMounted, - ]) - - useEffect(() => { - if ( - (Object.keys(formState.errors)?.length > 0 || error) && - _opened === false && - layoutProps?.showErrorList !== false - ) { - _setOpened(true) - } - }, [Object.keys(formState.errors)?.length > 0, error, layoutProps?.showErrorList]) - - useImperativeHandle, SuperFormRef>(ref, () => ({ - form, - mutation: { isFetching, mutateAsync, error }, - submit: (closeForm: boolean = true, afterSubmit?: (data: T | any) => void) => { - return form.handleSubmit(async (data: T | any) => { - await onFormSubmit(data, closeForm) - afterSubmit?.(data) - })() - }, - queryKey, - getFormState: () => formState, - })) - - return ( -
{ - e.stopPropagation() - e.preventDefault() - form.handleSubmit((data: T | any) => { - onFormSubmit(data) - })(e) - }} - style={{ height: '100%' }} - className={request === 'view' ? classes.disabled : ''} - > - {/* */} - {layoutProps?.noLayout ? ( - typeof layoutProps?.bodyRightSection?.render === 'function' ? ( - renderRightSection - ) : typeof children === 'function' ? ( - <> - - {children({ ...form })} - - ) : ( - <> - - {children} - - ) - ) : ( - onCancel?.(request)} - onSubmit={form.handleSubmit((data: T | any) => { - onFormSubmit(data) - })} - request={request} - modal={false} - nested={nested} - > - {!layoutProps?.noHeader && ( - - )} - {(Object.keys(formState.errors)?.length > 0 || error) && ( - - - {(error?.message?.length ?? 0) > 0 - ? 'Server Error' - : 'Required information is incomplete*'} - - {(error as any)?.response?.data?.msg || - (error as any)?.response?.data?._error || - error?.message} - {layoutProps?.showErrorList !== false && ( - - - {getErrorMessages(formState.errors)} - - - )} - - )} - {typeof layoutProps?.bodyRightSection?.render === 'function' ? ( - - {renderRightSection} - - ) : ( - - {typeof children === 'function' ? children({ ...form }) : children} - - )} - {!layoutProps?.noFooter && ( - >) - : layoutProps?.footerSectionProps)} - > - {typeof layoutProps?.extraButtons === 'function' - ? layoutProps?.extraButtons(form) - : layoutProps?.extraButtons} - - )} - - )} - - ) -} - -const getErrorMessages = (errors: any): ReactNode | null => { - return Object.keys(errors ?? {}).map((key) => { - if (typeof errors[key] === 'object' && key !== 'ref') { - return getErrorMessages(errors[key]) - } - if (key !== 'message') { - return null - } - - return {errors[key]} - }) -} - -const FRSuperFormLayout = forwardRef(SuperFormLayout) as ( - props: { - children: React.ReactNode | ((props: UseFormReturn) => React.ReactNode) - gridRef?: MutableRefObject - } & { - ref?: Ref> - } -) => ReactElement - -export default FRSuperFormLayout diff --git a/src/Form/components/SuperFormPersist.tsx b/src/Form/components/SuperFormPersist.tsx deleted file mode 100644 index 8026511..0000000 --- a/src/Form/components/SuperFormPersist.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { useEffect, useState } from 'react' -import { useFormContext, useFormState } from 'react-hook-form' -import { useDebouncedCallback } from '@mantine/hooks' -import useSubscribe from '../hooks/use-subscribe' -import { useSuperFormStore } from '../store/SuperForm.store' -import { openConfirmModal } from '../utils/openConfirmModal' - -const SuperFormPersist = ({ storageKey }: { storageKey?: string | null }) => { - // Component store State - const [persistKey, setPersistKey] = useState('') - const { isDirty, isReady, isSubmitted } = useFormState() - - const { remote, request } = useSuperFormStore((state) => ({ - request: state.request, - remote: state.remote, - })) - - // Component Hooks - const { reset, setValue } = useFormContext() - - const handleFormChange = useDebouncedCallback(({ values }) => { - setPersistKey(() => { - const key = `superform-persist-${storageKey?.length > 0 ? storageKey : `${remote?.tableName || 'local'}-${request}-${values[remote?.primaryKey] ?? ''}`}` - - if (!isDirty) { - return key - } - - window.localStorage.setItem(key, JSON.stringify(values)) - - return key - }) - }, 250) - - useSubscribe('', handleFormChange) - - // Component use Effects - useEffect(() => { - if (isReady && persistKey) { - const data = window.localStorage.getItem(persistKey) - if (!data) { - return - } - - if (isSubmitted) { - window.localStorage.removeItem(persistKey) - return - } - - openConfirmModal( - () => { - reset(JSON.parse(data)) - setValue('_dirty', true, { shouldDirty: true }) - }, - () => { - window.localStorage.removeItem(persistKey) - }, - 'Do you want to restore the previous data that was not submitted?' - ) - } - }, [isReady, isSubmitted, persistKey]) - - return null -} - -export default SuperFormPersist diff --git a/src/Form/config/ApiConfig.tsx b/src/Form/config/ApiConfig.tsx deleted file mode 100644 index 588898b..0000000 --- a/src/Form/config/ApiConfig.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import React, { createContext, useContext, ReactNode, useState } from 'react' - -interface ApiConfigContextValue { - apiURL: string - setApiURL: (url: string) => void -} - -const ApiConfigContext = createContext(null) - -interface ApiConfigProviderProps { - children: ReactNode - defaultApiURL?: string -} - -export const ApiConfigProvider: React.FC = ({ - children, - defaultApiURL = '', -}) => { - const [apiURL, setApiURL] = useState(defaultApiURL) - - return ( - - {children} - - ) -} - -export const useApiConfig = (): ApiConfigContextValue => { - const context = useContext(ApiConfigContext) - if (!context) { - throw new Error('useApiConfig must be used within ApiConfigProvider') - } - return context -} - -/** - * Hook to get API URL with optional override - * @param overrideURL - Optional URL to use instead of context value - */ -export const useApiURL = (overrideURL?: string): string => { - const { apiURL } = useApiConfig() - return overrideURL ?? apiURL -} diff --git a/src/Form/containers/Drawer/SuperFormDrawer.tsx b/src/Form/containers/Drawer/SuperFormDrawer.tsx deleted file mode 100644 index 111ad82..0000000 --- a/src/Form/containers/Drawer/SuperFormDrawer.tsx +++ /dev/null @@ -1,146 +0,0 @@ -import React, { - forwardRef, - useCallback, - useImperativeHandle, - useRef, - useState, - type ReactElement, - type Ref, -} from 'react' -import { IconX } from '@tabler/icons-react' -import type { FieldValues } from 'react-hook-form' -import { ActionIcon, Drawer } from '@mantine/core' -import type { SuperFormDrawerProps, SuperFormDrawerRef, SuperFormRef } from '../../types' -import SuperForm from '../../components/SuperForm' -import { openConfirmModal } from '../../utils/openConfirmModal' - -const SuperFormDrawer = ( - { drawerProps, noCloseOnSubmit, ...formProps }: SuperFormDrawerProps, - ref: Ref> -) => { - // Component Refs - const formRef = useRef>(null) - const drawerRef = useRef(null) - - // Component store State - // Tell drawer that form layout mounted to fix refs - const [layoutMounted, setLayoutMounted] = useState(false) - - // Component Callback Functions - const onSubmit = (data: T, request, formData, form, closeForm: boolean = true) => { - formProps?.onSubmit?.(data, request, formData, form, closeForm) - - if (request === 'delete') { - drawerProps?.onClose() - } - - if (!noCloseOnSubmit) { - if (closeForm) { - drawerProps?.onClose() - } - } - } - - const onCancel = (request) => { - if (formRef?.current?.getFormState().isDirty) { - openConfirmModal(() => { - drawerProps?.onClose() - formProps?.onCancel?.(request) - }) - } else { - drawerProps?.onClose() - formProps?.onCancel?.(request) - } - } - - const onLayoutMounted = useCallback(() => { - setLayoutMounted(true) - formProps?.onLayoutMounted?.() - }, [formProps?.onLayoutMounted]) - - const onLayoutUnMounted = useCallback(() => { - setLayoutMounted(false) - formProps?.onLayoutUnMounted?.() - }, [formProps?.onLayoutUnMounted]) - - // Component use Effects - useImperativeHandle, SuperFormDrawerRef>( - ref, - () => ({ - ...formRef.current, - drawer: drawerRef.current, - } as SuperFormDrawerRef), - [layoutMounted] - ) - - return ( - { - if (e.key === 'Escape' && drawerProps.closeOnEscape !== false) { - e.stopPropagation() - onCancel(formProps.request) - } - }} - overlayProps={{ backgroundOpacity: 0.5, blur: 0.5 }} - padding={6} - position='right' - transitionProps={{ - transition: 'slide-left', - duration: 150, - timingFunction: 'linear', - }} - size={500} - styles={{ - content: { - display: 'flex', - flexDirection: 'column', - justifyContent: 'stretch', - }, - body: { - minHeight: '100px', - flexGrow: 1, - }, - }} - keepMounted={false} - {...drawerProps} - closeOnEscape={false} - withCloseButton={false} - title={null} - > - - {...formProps} - onCancel={onCancel} - onSubmit={onSubmit} - onLayoutMounted={onLayoutMounted} - onLayoutUnMounted={onLayoutUnMounted} - ref={formRef} - layoutProps={{ - ...formProps?.layoutProps, - rightSection: ( - { - onCancel(formProps?.request) - }} - > - - - ), - title: - (drawerProps.title as string) ?? - formProps?.layoutProps?.title ?? - (formProps?.request as string), - }} - /> - - ) -} - -const FRSuperFormDrawer = forwardRef(SuperFormDrawer) as ( - props: SuperFormDrawerProps & { ref?: Ref> } -) => ReactElement - -export default FRSuperFormDrawer diff --git a/src/Form/containers/Modal/SuperFormModal.tsx b/src/Form/containers/Modal/SuperFormModal.tsx deleted file mode 100644 index 46bd768..0000000 --- a/src/Form/containers/Modal/SuperFormModal.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import React, { - forwardRef, - useCallback, - useImperativeHandle, - useRef, - useState, - type ReactElement, - type Ref, -} from 'react' -import type { FieldValues } from 'react-hook-form' -import { Modal, ScrollArea } from '@mantine/core' -import type { SuperFormModalProps, SuperFormModalRef, SuperFormRef } from '../../types' -import SuperForm from '../../components/SuperForm' -import { openConfirmModal } from '../../utils/openConfirmModal' - -const SuperFormModal = ( - { modalProps, noCloseOnSubmit, ...formProps }: SuperFormModalProps, - ref: Ref> -) => { - // Component Refs - const modalRef = useRef(null) - const formRef = useRef>(null) - - // Component store State - // Tell drawer that form layout mounted to fix refs - const [layoutMounted, setLayoutMounted] = useState(false) - - // Component Callback Functions - const onSubmit = (data: T, request, formData, form, closeForm: boolean = true) => { - formProps?.onSubmit?.(data, request, formData, form, closeForm) - - if (request === 'delete') { - modalProps?.onClose() - } - - if (!noCloseOnSubmit) { - if (closeForm) { - modalProps?.onClose() - } - } - } - - const onCancel = (request) => { - if (formRef?.current?.getFormState().isDirty) { - openConfirmModal(() => { - modalProps?.onClose() - formProps?.onCancel?.(request) - }) - } else { - modalProps?.onClose() - formProps?.onCancel?.(request) - } - } - - const onLayoutMounted = useCallback(() => { - setLayoutMounted(true) - formProps?.onLayoutMounted?.() - }, [formProps?.onLayoutMounted]) - - const onLayoutUnMounted = useCallback(() => { - setLayoutMounted(false) - formProps?.onLayoutUnMounted?.() - }, [formProps?.onLayoutUnMounted]) - - // Component use Effects - useImperativeHandle, SuperFormModalRef>( - ref, - () => ({ - ...formRef.current, - modal: modalRef.current, - } as SuperFormModalRef), - [layoutMounted] - ) - - return ( - - - {...formProps} - onCancel={onCancel} - onSubmit={onSubmit} - onLayoutMounted={onLayoutMounted} - onLayoutUnMounted={onLayoutUnMounted} - ref={formRef} - /> - - ) -} - -const FRSuperFormModal = forwardRef(SuperFormModal) as ( - props: SuperFormModalProps & { ref?: Ref> } -) => ReactElement - -export default FRSuperFormModal diff --git a/src/Form/containers/Popover/SuperFormPopover.tsx b/src/Form/containers/Popover/SuperFormPopover.tsx deleted file mode 100644 index 264af05..0000000 --- a/src/Form/containers/Popover/SuperFormPopover.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import React, { - forwardRef, - useCallback, - useImperativeHandle, - useRef, - useState, - type ReactElement, - type Ref, -} from 'react' -import type { FieldValues } from 'react-hook-form' -import { Box, Popover } from '@mantine/core' -import { useUncontrolled } from '@mantine/hooks' -import type { SuperFormPopoverProps, SuperFormPopoverRef, SuperFormRef } from '../../types' -import SuperForm from '../../components/SuperForm' -import { openConfirmModal } from '../../utils/openConfirmModal' - -const SuperFormPopover = ( - { popoverProps, target, noCloseOnSubmit, ...formProps }: SuperFormPopoverProps, - ref: Ref> -) => { - // Component Refs - const popoverRef = useRef(null) - const formRef = useRef>(null) - - // Component store State - // Tell drawer that form layout mounted to fix refs - const [layoutMounted, setLayoutMounted] = useState(false) - - // Component Hooks - const [_value, _onChange] = useUncontrolled({ - value: popoverProps?.opened, - onChange: popoverProps?.onChange, - }) - - // Component Callback Functions - const onSubmit = (data: T, request, formData, form, closeForm: boolean = true) => { - formProps?.onSubmit?.(data, request, formData, form, closeForm) - - if (request === 'delete') { - _onChange(false) - } - - if (!noCloseOnSubmit) { - if (closeForm) { - _onChange(false) - } - } - } - - const onCancel = (request) => { - if (formRef?.current?.getFormState().isDirty) { - openConfirmModal(() => { - _onChange(false) - formProps?.onCancel?.(request) - }) - } else { - _onChange(false) - formProps?.onCancel?.(request) - } - } - - const onLayoutMounted = useCallback(() => { - setLayoutMounted(true) - formProps?.onLayoutMounted?.() - }, [formProps?.onLayoutMounted]) - - const onLayoutUnMounted = useCallback(() => { - setLayoutMounted(false) - formProps?.onLayoutUnMounted?.() - }, [formProps?.onLayoutUnMounted]) - - // Component use Effects - useImperativeHandle, SuperFormPopoverRef>( - ref, - () => ({ - ...formRef.current, - popover: popoverRef.current, - } as SuperFormPopoverRef), - [layoutMounted] - ) - - return ( - _onChange(false)} - opened={_value} - position='left' - radius='md' - withArrow - withinPortal - zIndex={200} - keepMounted={false} - {...popoverProps} - > - - _onChange(true)}>{target} - - - - - - ) -} - -const FRSuperFormPopover = forwardRef(SuperFormPopover) as ( - props: SuperFormPopoverProps & { ref?: Ref> } -) => ReactElement - -export default FRSuperFormPopover diff --git a/src/Form/hooks/use-drawer-form-state.tsx b/src/Form/hooks/use-drawer-form-state.tsx deleted file mode 100644 index 57dfbac..0000000 --- a/src/Form/hooks/use-drawer-form-state.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import { useState } from 'react' -import { FieldValues } from 'react-hook-form' -import { SuperFormProps, RequestType, ExtendedDrawerProps } from '../types' - -interface UseDrawerFormState extends Partial> { - drawerProps: Partial - opened?: boolean - onClose?: () => void - request: RequestType - [key: string]: any -} - -type AskFunction = (request: RequestType, buffer: any) => void - -const useDrawerFormState = ( - props?: Partial> -): { - formProps: UseDrawerFormState - setFormProps: React.Dispatch>> - open: (props: Partial>) => void - close: () => void - ask: AskFunction -} => { - const [formProps, setFormProps] = useState>({ - opened: false, - request: 'insert', - ...props, - onClose: () => - setFormProps((curr) => ({ - ...curr, - opened: false, - drawerProps: { ...curr.drawerProps, opened: false }, - })), - drawerProps: { opened: false, onClose: () => {}, ...props?.drawerProps }, - }) - - return { - formProps, - setFormProps, - open: (props?: Partial>) => { - setFormProps((curr) => { - return { - ...curr, - ...props, - request: props.request ?? curr.request, - opened: true, - drawerProps: { - ...curr.drawerProps, - ...props?.drawerProps, - opened: true, - onClose: curr.onClose, - }, - primeData: props?.primeData, - useFormProps: { - ...curr.useFormProps, - ...props?.useFormProps, - }, - layoutProps: { - ...curr.layoutProps, - ...props?.layoutProps, - }, - useQueryOptions: { - ...curr.useQueryOptions, - ...props?.useQueryOptions, - }, - meta: { - ...curr.meta, - ...props?.meta, - }, - useMutationOptions: { - ...curr.useMutationOptions, - ...props?.useMutationOptions, - }, - } - }) - }, - close: () => - setFormProps((curr) => ({ - ...curr, - opened: false, - drawerProps: { ...curr.drawerProps, opened: false, onClose: curr.onClose }, - })), - ask: (request: RequestType, buffer: any) => { - setFormProps((curr) => ({ - ...curr, - request, - value: buffer, - opened: true, - drawerProps: { ...curr.drawerProps, opened: true, onClose: curr.onClose }, - })) - }, - } -} - -export default useDrawerFormState -export type { UseDrawerFormState } diff --git a/src/Form/hooks/use-modal-form-state.tsx b/src/Form/hooks/use-modal-form-state.tsx deleted file mode 100644 index 9156aa8..0000000 --- a/src/Form/hooks/use-modal-form-state.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import { useState } from 'react' -import { FieldValues } from 'react-hook-form' -import { ModalProps } from '@mantine/core' -import { SuperFormProps, RequestType } from '../types' - -interface UseModalFormState extends Partial> { - modalProps: ModalProps - opened?: boolean - onClose?: () => void - request: RequestType - [key: string]: any -} - -type AskFunction = (request: RequestType, buffer: any) => void - -const useModalFormState = ( - props?: Partial> -): { - formProps: UseModalFormState - setFormProps: React.Dispatch>> - open: (props: Partial>) => void - close: () => void - ask: AskFunction -} => { - const [formProps, setFormProps] = useState>({ - opened: false, - request: 'insert', - ...props, - onClose: () => - setFormProps((curr) => ({ - ...curr, - opened: false, - modalProps: { ...curr.modalProps, opened: false }, - })), - modalProps: { opened: false, onClose: () => {}, ...props?.modalProps }, - }) - - return { - formProps, - setFormProps, - open: (props?: Partial>) => { - setFormProps((curr) => { - return { - ...curr, - ...props, - request: props.request ?? curr.request, - opened: true, - modalProps: { - ...curr.modalProps, - ...props?.modalProps, - opened: true, - onClose: curr.onClose, - }, - primeData: props?.primeData, - useFormProps: { - ...curr.useFormProps, - ...props?.useFormProps, - }, - layoutProps: { - ...curr.layoutProps, - ...props?.layoutProps, - }, - useQueryOptions: { - ...curr.useQueryOptions, - ...props?.useQueryOptions, - }, - meta: { - ...curr.meta, - ...props?.meta, - }, - useMutationOptions: { - ...curr.useMutationOptions, - ...props?.useMutationOptions, - }, - } - }) - }, - close: () => - setFormProps((curr) => ({ - ...curr, - opened: false, - modalProps: { ...curr.modalProps, opened: false, onClose: curr.onClose }, - })), - ask: (request: RequestType, buffer: any) => { - setFormProps((curr) => ({ - ...curr, - request, - value: buffer, - opened: true, - modalProps: { ...curr.modalProps, opened: true, onClose: curr.onClose }, - })) - }, - } -} - -export default useModalFormState -export type { UseModalFormState } diff --git a/src/Form/hooks/use-popover-form-state.tsx b/src/Form/hooks/use-popover-form-state.tsx deleted file mode 100644 index 6b86607..0000000 --- a/src/Form/hooks/use-popover-form-state.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import { useState } from 'react' -import { FieldValues } from 'react-hook-form' -import { PopoverProps } from '@mantine/core' -import { SuperFormProps, RequestType } from '../types' - -interface UsePopoverFormState extends Partial> { - popoverProps: Omit - opened?: boolean - onClose?: () => void - request: RequestType - [key: string]: any -} - -type AskFunction = (request: RequestType, buffer: any) => void - -const usePopoverFormState = ( - props?: Partial> -): { - formProps: UsePopoverFormState - setFormProps: React.Dispatch>> - open: (props: Partial>) => void - close: () => void - ask: AskFunction -} => { - const [formProps, setFormProps] = useState>({ - opened: false, - request: 'insert', - ...props, - popoverProps: { opened: false, onClose: () => {}, ...props?.popoverProps }, - onClose: () => - setFormProps((curr) => ({ - ...curr, - opened: false, - popoverProps: { ...curr.popoverProps, opened: false }, - })), - }) - - return { - formProps, - setFormProps, - open: (props?: Partial>) => { - setFormProps((curr) => { - return { - ...curr, - ...props, - request: props.request ?? curr.request, - opened: true, - popoverProps: { - ...curr.popoverProps, - ...props?.popoverProps, - opened: true, - onClose: curr.onClose, - }, - primeData: props?.primeData, - useFormProps: { - ...curr.useFormProps, - ...props?.useFormProps, - }, - layoutProps: { - ...curr.layoutProps, - ...props?.layoutProps, - }, - useQueryOptions: { - ...curr.useQueryOptions, - ...props?.useQueryOptions, - }, - meta: { - ...curr.meta, - ...props?.meta, - }, - useMutationOptions: { - ...curr.useMutationOptions, - ...props?.useMutationOptions, - }, - } - }) - }, - close: () => - setFormProps((curr) => ({ - ...curr, - opened: false, - popoverProps: { ...curr.popoverProps, opened: false, onClose: curr.onClose }, - })), - ask: (request: RequestType, buffer: any) => { - setFormProps((curr) => ({ - ...curr, - request, - value: buffer, - opened: true, - popoverProps: { ...curr.popoverProps, opened: true, onClose: curr.onClose }, - })) - }, - } -} - -export default usePopoverFormState -export type { UsePopoverFormState } diff --git a/src/Form/hooks/use-subscribe.tsx b/src/Form/hooks/use-subscribe.tsx deleted file mode 100644 index 12dec90..0000000 --- a/src/Form/hooks/use-subscribe.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { useEffect } from 'react' -import { - EventType, - FieldValues, - FormState, - InternalFieldName, - Path, - ReadFormState, - useFormContext, - UseFormReturn, -} from 'react-hook-form' - -const useSubscribe = ( - name: Path | readonly Path[] | undefined, - callback: ( - data: Partial> & { - values: FieldValues - name?: InternalFieldName - type?: EventType - }, - form?: UseFormReturn - ) => void, - formState?: ReadFormState, - deps?: unknown[] -) => { - const form = useFormContext() - - return useEffect(() => { - const unsubscribe = form.subscribe({ - name, - callback: (data) => callback(data, form), - formState: { values: true, ...formState }, - exact: true, - }) - - return unsubscribe - }, [form.subscribe, ...(deps || [])]) -} - -export default useSubscribe diff --git a/src/Form/hooks/use-super-form-state.tsx b/src/Form/hooks/use-super-form-state.tsx deleted file mode 100644 index 90b6e25..0000000 --- a/src/Form/hooks/use-super-form-state.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { useState } from 'react' -import { FieldValues } from 'react-hook-form' -import { SuperFormProps, RequestType } from '../types' - -interface UseSuperFormState extends Partial> { - request: RequestType - [key: string]: any -} - -type AskFunction = (request: RequestType, buffer: any) => void - -const useSuperFormState = ( - props?: UseSuperFormState -): { - formProps: UseSuperFormState - setFormProps: React.Dispatch>> - ask: AskFunction -} => { - const [formProps, setFormProps] = useState>({ - request: 'insert', - ...props, - }) - - return { - formProps, - setFormProps, - ask: (request: RequestType, buffer: any) => { - setFormProps((curr) => ({ - ...curr, - request, - value: buffer, - })) - }, - } -} - -export default useSuperFormState -export type { UseSuperFormState } diff --git a/src/Form/hooks/useRemote.tsx b/src/Form/hooks/useRemote.tsx deleted file mode 100644 index 07603a2..0000000 --- a/src/Form/hooks/useRemote.tsx +++ /dev/null @@ -1,123 +0,0 @@ -import { useEffect, type MutableRefObject } from 'react' -import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' -import { useFormContext, useFormState, type FieldValues } from 'react-hook-form' -import { useStore } from '../store/SuperForm.store' -import { useApiURL } from '../config/ApiConfig' -import { getNestedValue } from '../utils/getNestedValue' -import { fetchClient, type FetchResponse, FetchError } from '../utils/fetchClient' -import type { GridRef } from '../types' - -const useRemote = (gridRef?: MutableRefObject | null>) => { - // Component store State - const { onResetForm, remote, request, useMutationOptions, useQueryOptions, value } = useStore( - (state) => ({ - onResetForm: state.onResetForm, - remote: state.remote, - request: state.request, - useMutationOptions: state.useMutationOptions, - useQueryOptions: state.useQueryOptions, - value: state.value, - }) - ) - - // Component Hooks - const form = useFormContext() - const { isDirty } = useFormState({ control: form.control }) - - // Component use Effects - const qc = useQueryClient() - - // Get API URL from context or override - const contextApiURL = useApiURL() - - const id = remote?.primaryKey?.includes('.') - ? getNestedValue(remote?.primaryKey, value) - : value?.[remote?.primaryKey ?? ''] - - const queryKey = useQueryOptions?.queryKey || [remote?.tableName, id] - - const enabled = - useQueryOptions?.enabled || - !!( - (remote?.enabled ?? true) && - (remote?.tableName ?? '').length > 0 && - (request === 'change' || request === 'delete') && - String(id) !== 'undefined' && - (String(id)?.length ?? 0) > 0 - ) - - let url = remote?.apiURL ?? `${contextApiURL}/${remote?.tableName}` - url = url?.endsWith('/') ? url.substring(0, url.length - 1) : url - - const { isSuccess, status, data, isFetching } = useQuery>({ - queryKey, - queryFn: () => fetchClient.get(`${url}/${id}`, remote?.apiOptions), - enabled, - refetchOnMount: 'always', - refetchOnReconnect: !isDirty, - refetchOnWindowFocus: !isDirty, - staleTime: 0, - gcTime: 0, - ...useQueryOptions, - }) - - const changeMut = useMutation({ - // @ts-ignore - mutationFn: (mutVal: T) => { - if (!remote?.tableName || !remote?.primaryKey) { - return Promise.resolve(null) - } - - return request === 'insert' - ? fetchClient.post(url, mutVal, remote?.apiOptions) - : request === 'change' - ? fetchClient.post(`${url}/${id}`, mutVal, remote?.apiOptions) - : request === 'delete' - ? fetchClient.delete(`${url}/${id}`, remote?.apiOptions) - : Promise.resolve(null) - }, - onSettled: (response: FetchResponse | null) => { - qc?.invalidateQueries({ queryKey: [remote?.tableName] }) - - if (request !== 'delete' && response) { - if (onResetForm) { - onResetForm(response?.data, form).then(() => { - form.reset(response?.data, { keepDirty: false }) - }) - } else { - form.reset(response?.data, { keepDirty: false }) - } - } - - gridRef?.current?.refresh?.() - // @ts-ignore - gridRef?.current?.selectRow?.(response?.data?.[remote?.primaryKey ?? '']) - }, - ...useMutationOptions, - }) - - useEffect(() => { - if (isSuccess && status === 'success' && enabled && !isFetching) { - if (!Object.keys(data?.data ?? {}).includes(remote?.primaryKey ?? '')) { - throw new Error('Primary key not found in remote data') - } - - if (onResetForm) { - onResetForm(data?.data, form).then((resetData) => { - form.reset(resetData) - }) - } else { - form.reset(data?.data) - } - } - }, [isSuccess, status, enabled, isFetching]) - - return { - error: changeMut.error as FetchError, - isFetching: (enabled ? isFetching : false) || changeMut?.isPending, - mutateAsync: changeMut.mutateAsync, - queryKey, - } -} - -export default useRemote diff --git a/src/Form/store/FormLayout.store.tsx b/src/Form/store/FormLayout.store.tsx deleted file mode 100644 index 80c7dbe..0000000 --- a/src/Form/store/FormLayout.store.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import React, { createContext, useContext, type ReactNode } from 'react' -import { create } from 'zustand' -import type { RequestType } from '../types' - -interface FormLayoutState { - request: RequestType - loading: boolean - dirty: boolean - onCancel?: () => void - onSubmit?: () => void - setState: (key: string, value: any) => void -} - -const createFormLayoutStore = (initialProps: any) => - create((set) => ({ - request: initialProps.request || 'insert', - loading: initialProps.loading || false, - dirty: initialProps.dirty || false, - onCancel: initialProps.onCancel, - onSubmit: initialProps.onSubmit, - setState: (key, value) => set({ [key]: value }), - })) - -const FormLayoutStoreContext = createContext | null>(null) - -export const FormLayoutStoreProvider: React.FC<{ children: ReactNode; [key: string]: any }> = ({ - children, - ...props -}) => { - const storeRef = React.useRef>() - if (!storeRef.current) { - storeRef.current = createFormLayoutStore(props) - } - - return ( - - {children} - - ) -} - -export const useFormLayoutStore = (selector: (state: FormLayoutState) => T): T => { - const store = useContext(FormLayoutStoreContext) - if (!store) { - throw new Error('useFormLayoutStore must be used within FormLayoutStoreProvider') - } - return store(selector) -} diff --git a/src/Form/store/SuperForm.store.tsx b/src/Form/store/SuperForm.store.tsx deleted file mode 100644 index 22d62ce..0000000 --- a/src/Form/store/SuperForm.store.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import type { SuperFormProviderProps } from '../types' -import { createSyncStore } from '@warkypublic/zustandsyncstore' - -const { Provider, useStore } = createSyncStore((set) => ({ - request: 'insert', - setRequest: (request) => { - set({ request }) - }, - - value: undefined, - setValue: (value) => { - set({ value }) - }, - - noCloseOnSubmit: false, - setNoCloseOnSubmit: (noCloseOnSubmit) => { - set({ noCloseOnSubmit }) - }, -})) - -export { Provider, useStore } -export const useSuperFormStore = useStore diff --git a/src/Form/styles/Form.module.css b/src/Form/styles/Form.module.css deleted file mode 100644 index aa6f336..0000000 --- a/src/Form/styles/Form.module.css +++ /dev/null @@ -1,10 +0,0 @@ -.disabled { - pointer-events: none; - opacity: 0.9; -} - -.sticky { - position: -webkit-sticky; - position: sticky; - bottom: 0; -} diff --git a/src/Form/types/form.types.ts b/src/Form/types/form.types.ts deleted file mode 100644 index c415c1b..0000000 --- a/src/Form/types/form.types.ts +++ /dev/null @@ -1,135 +0,0 @@ -import type { UseMutationOptions, UseMutationResult, UseQueryOptions } from '@tanstack/react-query' -import type { FieldValues, UseFormProps, UseFormReturn, UseFormStateReturn } from 'react-hook-form' -import type { ModalProps, PaperProps, PopoverProps, DrawerProps } from '@mantine/core' -import type { RemoteConfig } from './remote.types' - -export type RequestType = 'insert' | 'change' | 'view' | 'select' | 'delete' | 'get' | 'set' - -// Grid integration types (simplified - removes BTGlideRef dependency) -export interface GridRef { - refresh?: () => void - selectRow?: (id: any) => void -} - -export interface FormSectionBodyProps { - // Add properties as needed from original FormLayout - [key: string]: any -} - -export interface FormSectionFooterProps { - // Add properties as needed from original FormLayout - [key: string]: any -} - -export interface BodyRightSection { - opened?: boolean - setOpened?: (opened: boolean) => void - w: number | string - hideToggleButton?: boolean - paperProps?: PaperProps - render: (props: { - form: UseFormReturn - formValue: T - isFetching: boolean - opened: boolean - queryKey: any - setOpened: (opened: boolean) => void - }) => React.ReactNode -} - -export interface SuperFormLayoutProps { - buttonTitles?: { submit?: string; cancel?: string } - extraButtons?: React.ReactNode | ((form: UseFormReturn) => React.ReactNode) - noFooter?: boolean - noHeader?: boolean - noLayout?: boolean - bodySectionProps?: Partial - footerSectionProps?: - | Partial - | ((ref: React.RefObject>) => Partial) - rightSection?: React.ReactNode - bodyRightSection?: BodyRightSection - title?: string - showErrorList?: boolean -} - -export interface CommonFormProps { - gridRef?: React.MutableRefObject | null> - layoutProps?: SuperFormLayoutProps - meta?: { [key: string]: any } - nested?: boolean - onCancel?: (request: RequestType) => void - onLayoutMounted?: () => void - onLayoutUnMounted?: () => void - onResetForm?: (data: T, form?: UseFormReturn) => Promise - onBeforeSubmit?: ( - data: T, - request: RequestType, - form?: UseFormReturn - ) => Promise - onSubmit?: ( - data: T, - request: RequestType, - formData?: T, - form?: UseFormReturn, - closeForm?: boolean - ) => void - primeData?: any - readonly?: boolean - remote?: RemoteConfig - request: RequestType - persist?: boolean | { storageKey?: string } - useMutationOptions?: UseMutationOptions - useQueryOptions?: Partial> - value?: T | null -} - -export interface SuperFormProps extends CommonFormProps { - children: React.ReactNode | ((props: UseFormReturn) => React.ReactNode) - useFormProps?: UseFormProps -} - -export interface SuperFormProviderProps extends Omit, 'children'> { - children?: React.ReactNode -} - -export interface SuperFormModalProps extends SuperFormProps { - modalProps: ModalProps - noCloseOnSubmit?: boolean -} - -export interface SuperFormPopoverProps extends SuperFormProps { - popoverProps?: Omit - target: any - noCloseOnSubmit?: boolean -} - -export interface ExtendedDrawerProps extends DrawerProps { - // Add any extended drawer props needed - [key: string]: any -} - -export interface SuperFormDrawerProps extends SuperFormProps { - drawerProps: ExtendedDrawerProps - noCloseOnSubmit?: boolean -} - -export interface SuperFormRef { - form: UseFormReturn - mutation: Partial> - submit: (closeForm?: boolean, afterSubmit?: (data: T | any) => void) => Promise - queryKey?: any - getFormState: () => UseFormStateReturn -} - -export interface SuperFormDrawerRef extends SuperFormRef { - drawer: HTMLDivElement | null -} - -export interface SuperFormModalRef extends SuperFormRef { - modal: HTMLDivElement | null -} - -export interface SuperFormPopoverRef extends SuperFormRef { - popover: HTMLDivElement | null -} diff --git a/src/Form/types/index.ts b/src/Form/types/index.ts deleted file mode 100644 index 31e655c..0000000 --- a/src/Form/types/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './form.types' -export * from './remote.types' diff --git a/src/Form/types/remote.types.ts b/src/Form/types/remote.types.ts deleted file mode 100644 index 3101e33..0000000 --- a/src/Form/types/remote.types.ts +++ /dev/null @@ -1,11 +0,0 @@ -export interface RemoteConfig { - apiOptions?: RequestInit - apiURL?: string - enabled?: boolean - fetchSize?: number - hotFields?: string[] - primaryKey?: string - sqlFilter?: string - tableName: string - uniqueKeys?: string[] -} diff --git a/src/Form/utils/fetchClient.ts b/src/Form/utils/fetchClient.ts deleted file mode 100644 index 23e899f..0000000 --- a/src/Form/utils/fetchClient.ts +++ /dev/null @@ -1,161 +0,0 @@ -export interface FetchOptions extends RequestInit { - params?: Record - timeout?: number -} - -export interface FetchResponse { - data: T - status: number - statusText: string - ok: boolean - error?: string -} - -export class FetchError extends Error { - constructor( - public message: string, - public status?: number, - public response?: any - ) { - super(message) - this.name = 'FetchError' - } -} - -/** - * Fetch wrapper with timeout support and axios-like interface - */ -async function fetchWithTimeout( - url: string, - options: FetchOptions = {} -): Promise { - const { timeout = 30000, ...fetchOptions } = options - - const controller = new AbortController() - const timeoutId = setTimeout(() => controller.abort(), timeout) - - try { - const response = await fetch(url, { - ...fetchOptions, - signal: controller.signal, - }) - clearTimeout(timeoutId) - return response - } catch (error) { - clearTimeout(timeoutId) - throw error - } -} - -/** - * GET request - */ -export async function get( - url: string, - options?: FetchOptions -): Promise> { - try { - const response = await fetchWithTimeout(url, { - ...options, - method: 'GET', - headers: { - 'Content-Type': 'application/json', - ...options?.headers, - }, - }) - - const data = await response.json() - - return { - data, - status: response.status, - statusText: response.statusText, - ok: response.ok, - error: response.ok ? undefined : data?.message || data?.error || response.statusText, - } - } catch (error) { - throw new FetchError( - error instanceof Error ? error.message : 'Network request failed', - undefined, - error - ) - } -} - -/** - * POST request - */ -export async function post( - url: string, - data?: any, - options?: FetchOptions -): Promise> { - try { - const response = await fetchWithTimeout(url, { - ...options, - method: 'POST', - headers: { - 'Content-Type': 'application/json', - ...options?.headers, - }, - body: JSON.stringify(data), - }) - - const responseData = await response.json() - - return { - data: responseData, - status: response.status, - statusText: response.statusText, - ok: response.ok, - error: response.ok ? undefined : responseData?.message || responseData?.error || response.statusText, - } - } catch (error) { - throw new FetchError( - error instanceof Error ? error.message : 'Network request failed', - undefined, - error - ) - } -} - -/** - * DELETE request - */ -export async function del( - url: string, - options?: FetchOptions -): Promise> { - try { - const response = await fetchWithTimeout(url, { - ...options, - method: 'DELETE', - headers: { - 'Content-Type': 'application/json', - ...options?.headers, - }, - }) - - const data = await response.json().catch(() => ({})) - - return { - data, - status: response.status, - statusText: response.statusText, - ok: response.ok, - error: response.ok ? undefined : data?.message || data?.error || response.statusText, - } - } catch (error) { - throw new FetchError( - error instanceof Error ? error.message : 'Network request failed', - undefined, - error - ) - } -} - -export const fetchClient = { - get, - post, - delete: del, -} diff --git a/src/Form/utils/getNestedValue.ts b/src/Form/utils/getNestedValue.ts deleted file mode 100644 index be6265f..0000000 --- a/src/Form/utils/getNestedValue.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Retrieves a nested value from an object using dot notation path - * @param path - Dot-separated path (e.g., "user.address.city") - * @param obj - Object to extract value from - * @returns The value at the specified path, or undefined if not found - */ -export const getNestedValue = (path: string, obj: any): any => { - return path.split('.').reduce((prev, curr) => prev?.[curr], obj) -} diff --git a/src/Form/utils/openConfirmModal.ts b/src/Form/utils/openConfirmModal.ts deleted file mode 100644 index baef0fa..0000000 --- a/src/Form/utils/openConfirmModal.ts +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react' -import { Stack, Text } from '@mantine/core' -import { modals } from '@mantine/modals' - -export const openConfirmModal = ( - onConfirm: () => void, - onCancel?: (() => void) | null, - description?: string | null -) => - modals.openConfirmModal({ - size: 'xs', - children: ( - - - You have unsaved changes in this form. - - - {description ?? - 'Closing now will discard any modifications you have made. Are you sure you want to continue?'} - - - ), - labels: { confirm: description ? 'Restore' : 'Confirm', cancel: 'Cancel' }, - confirmProps: { color: description ? 'blue' : 'red', size: 'compact-xs' }, - cancelProps: { size: 'compact-xs' }, - groupProps: { gap: 'xs' }, - withCloseButton: false, - onConfirm, - onCancel, - }) diff --git a/src/Former/Former.store.tsx b/src/Former/Former.store.tsx index 64597f3..77f50cd 100644 --- a/src/Former/Former.store.tsx +++ b/src/Former/Former.store.tsx @@ -1,8 +1,8 @@ +import { newUUID } from '@warkypublic/artemis-kit'; import { createSyncStore } from '@warkypublic/zustandsyncstore'; import { produce } from 'immer'; import type { FormerProps, FormerState } from './Former.types'; -import { newUUID } from '@warkypublic/artemis-kit'; const { Provider: FormerProvider, useStore: useFormerStore } = createSyncStore< FormerState & Partial>, @@ -171,7 +171,7 @@ const { Provider: FormerProvider, useStore: useFormerStore } = createSyncStore< }, values: undefined, }), - ({ onConfirmDelete, primeData, request, values, id, onClose, useStoreApi }) => { + ({ id, onClose, onConfirmDelete, primeData, request, useStoreApi, values }) => { let _onConfirmDelete = onConfirmDelete; if (!onConfirmDelete) { _onConfirmDelete = async () => { @@ -180,10 +180,6 @@ const { Provider: FormerProvider, useStore: useFormerStore } = createSyncStore< } return { - onConfirmDelete: _onConfirmDelete, - primeData, - request: (request || 'insert').replace('change', 'update'), - values: { ...primeData, ...values }, id: !id ? newUUID() : id, onClose: () => { const dirty = useStoreApi.getState().dirty; @@ -204,6 +200,10 @@ const { Provider: FormerProvider, useStore: useFormerStore } = createSyncStore< } } }, + onConfirmDelete: _onConfirmDelete, + primeData, + request: (request || 'insert').replace('change', 'update'), + values: { ...primeData, ...values }, }; } ); diff --git a/src/Former/Former.tsx b/src/Former/Former.tsx index 9f14ebd..1960cb2 100644 --- a/src/Former/Former.tsx +++ b/src/Former/Former.tsx @@ -86,10 +86,10 @@ const FormerInner = forwardRef, Partial> & Props if (formMethods) { formMethods.subscribe({ - formState: { isDirty: true }, callback: ({ isDirty }) => { setState('dirty', isDirty); }, + formState: { isDirty: true }, }); } }, [formMethods]); diff --git a/src/Former/Former.types.ts b/src/Former/Former.types.ts index ca7dee2..3546333 100644 --- a/src/Former/Former.types.ts +++ b/src/Former/Former.types.ts @@ -7,14 +7,6 @@ import type { import type React from 'react'; import type { FieldValues, UseFormProps, UseFormReturn } from 'react-hook-form'; -export type FormerSectionRender = ( - children: React.ReactNode, - opened: boolean | undefined, - onClose: ((data?: T) => void) | undefined, - onOpen: ((data?: T) => void) | undefined, - getState: FormerState['getState'] -) => React.ReactNode; - export type FormerAPICallType = ( mode: 'mutate' | 'read', request: RequestType, @@ -23,39 +15,39 @@ export type FormerAPICallType = ( ) => Promise; export interface FormerProps { - id?: string; afterGet?: (data: T) => Promise | void; afterSave?: (data: T) => Promise | void; - uniqueKeyField?: string; beforeSave?: (data: T) => Promise | T; + dirty?: boolean; disableHTMlForm?: boolean; + id?: string; keepOpen?: boolean; + layout?: { + buttonAreaGroupProps?: GroupProps; + buttonOnTop?: boolean; + closeButtonProps?: ButtonProps; + closeButtonTitle?: React.ReactNode; + renderBottom?: FormerSectionRender; + renderTop?: FormerSectionRender; + saveButtonProps?: ButtonProps; + saveButtonTitle?: React.ReactNode; + title?: string; + }; onAPICall?: FormerAPICallType; onCancel?: () => void; onChange?: (value: T) => void; onClose?: (data?: T) => void; onConfirmDelete?: (values?: T) => Promise; - onOpen?: (data?: T) => void; + onOpen?: (data?: T) => void; opened?: boolean; - dirty?: boolean; primeData?: T; request: RequestType; + uniqueKeyField?: string; useFormProps?: UseFormProps; values?: T; - wrapper?: FormerSectionRender; - layout?: { - renderTop?: FormerSectionRender; - renderBottom?: FormerSectionRender; - saveButtonTitle?: React.ReactNode; - closeButtonTitle?: React.ReactNode; - saveButtonProps?: ButtonProps; - closeButtonProps?: ButtonProps; - buttonOnTop?: boolean; - buttonAreaGroupProps?: GroupProps; - title?: string; - }; + wrapper?: FormerSectionRender; } export interface FormerRef { @@ -68,6 +60,14 @@ export interface FormerRef { validate: () => Promise; } +export type FormerSectionRender = ( + children: React.ReactNode, + opened: boolean | undefined, + onClose: ((data?: T) => void) | undefined, + onOpen: ((data?: T) => void) | undefined, + getState: FormerState['getState'] +) => React.ReactNode; + export interface FormerState { deleteConfirmed?: boolean; error?: string; diff --git a/src/Former/FormerButtonArea.tsx b/src/Former/FormerButtonArea.tsx index 93045f0..24a4e52 100644 --- a/src/Former/FormerButtonArea.tsx +++ b/src/Former/FormerButtonArea.tsx @@ -1,28 +1,29 @@ -import { Group, Button, Tooltip } from '@mantine/core'; -import { IconX, IconDeviceFloppy } from '@tabler/icons-react'; +import { Button, Group, Tooltip } from '@mantine/core'; +import { IconDeviceFloppy, IconX } from '@tabler/icons-react'; + import { useFormerStore } from './Former.store'; export const FormerButtonArea = () => { const { - save, - onClose, buttonAreaGroupProps, - saveButtonProps, closeButtonProps, closeButtonTitle, - saveButtonTitle, - request, dirty, + onClose, + request, + save, + saveButtonProps, + saveButtonTitle, } = useFormerStore((state) => ({ - save: state.save, - onClose: state.onClose, buttonAreaGroupProps: state.layout?.buttonAreaGroupProps, - saveButtonProps: state.layout?.saveButtonProps, closeButtonProps: state.layout?.closeButtonProps, closeButtonTitle: state.layout?.closeButtonTitle, - saveButtonTitle: state.layout?.saveButtonTitle, - request: state.request, dirty: state.dirty, + onClose: state.onClose, + request: state.request, + save: state.save, + saveButtonProps: state.layout?.saveButtonProps, + saveButtonTitle: state.layout?.saveButtonTitle, })); const disabledSave = @@ -31,19 +32,19 @@ export const FormerButtonArea = () => { return ( - + {typeof onClose === 'function' && ( - setOpen(false)} former={{ request: 'insert' }}> + setOpen(false)} opened={open}>
Test
{children}
} - //opened={true} - uniqueKeyField="rid_usernote" + disableHTMlForm={disableHTML} + layout={layout} onAPICall={ apiOptions.type === 'api' ? FormerRestHeadSpecAPI({ @@ -111,16 +110,17 @@ export const FormTest = () => { }) : StubAPI() } - disableHTMlForm={disableHTML} onChange={setFormData} onClose={() => setOpen(false)} opened={open} primeData={{ a: '66', test: 'primed' }} ref={ref} request={request as any} + //wrapper={(children, getState) =>
{children}
} + //opened={true} + uniqueKeyField="rid_usernote" useFormProps={{ criteriaMode: 'all', shouldUseNativeValidation: false }} values={formData} - layout={layout} // wrapper={ // wrapped // ? (children, opened, onClose, _onOpen, getState) => { @@ -169,10 +169,10 @@ export const FormTest = () => {
{apiOptions.type === 'api' && ( { setApiOptions({ ...apiOptions, ...values }); }} + values={apiOptions} /> )}