diff --git a/.storybook/previewDecorator.tsx b/.storybook/previewDecorator.tsx
index 7b01dd9..36e0979 100644
--- a/.storybook/previewDecorator.tsx
+++ b/.storybook/previewDecorator.tsx
@@ -1,13 +1,16 @@
import { MantineProvider } from '@mantine/core';
+import { ModalsProvider } from '@mantine/modals';
import '@mantine/core/styles.css';
export function PreviewDecorator(Story: any, { parameters }: any) {
console.log('Rendering decorator', parameters);
return (
-
-
-
+
+
+
+
+
);
}
diff --git a/eslint.config.ts b/eslint.config.ts
index f5ee91a..74afdf7 100644
--- a/eslint.config.ts
+++ b/eslint.config.ts
@@ -16,19 +16,22 @@ const config = defineConfig([
plugins: { js },
},
// reactHooks.configs['recommended-latest'],
- {...reactRefresh.configs.vite, ignores: ['**/*.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],},
+ { ...reactRefresh.configs.vite, ignores: ['**/*.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'] },
tseslint.configs.recommended,
{
...pluginReact.configs.flat.recommended,
- ignores: ['**/*.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
- rules: {...pluginReact.configs.flat.recommended.rules,
+ ignores: ['**/*.test.{js,mjs,cjs,ts,mts,cts,jsx,tsx}', '*stories.tsx'],
+ rules: {
+ ...pluginReact.configs.flat.recommended.rules,
'react/react-in-jsx-scope': 'off',
- }
+ 'react-refresh/only-export-components': 'warn',
+ },
},
perfectionist.configs['recommended-alphabetical'],
{
rules: {
'@typescript-eslint/no-explicit-any': 'warn',
+ '@typescript-eslint/ban-ts-comment': 'off',
},
},
]);
diff --git a/package.json b/package.json
index 48521e8..5599ff1 100644
--- a/package.json
+++ b/package.json
@@ -49,7 +49,9 @@
"./oranguru.css": "./src/oranguru.css"
},
"dependencies": {
+
"moment": "^2.30.1"
+
},
"devDependencies": {
"@changesets/cli": "^2.29.7",
@@ -94,10 +96,13 @@
"@mantine/core": "^8.3.1",
"@mantine/hooks": "^8.3.1",
"@mantine/notifications": "^8.3.5",
+ "@mantine/modals": "^8.3.5",
"@tabler/icons-react": "^3.35.0",
"@tanstack/react-query": "^5.90.5",
"@warkypublic/artemis-kit": "^1.0.10",
"@warkypublic/zustandsyncstore": "^0.0.4",
+ "react-hook-form": "^7.71.0",
+
"immer": "^10.1.3",
"react": ">= 19.0.0",
"react-dom": ">= 19.0.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 35a63f7..962ddeb 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -17,6 +17,9 @@ importers:
'@mantine/hooks':
specifier: ^8.3.1
version: 8.3.1(react@19.2.0)
+ '@mantine/modals':
+ specifier: ^8.3.5
+ version: 8.3.12(@mantine/core@8.3.1(@mantine/hooks@8.3.1(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@mantine/hooks@8.3.1(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
'@mantine/notifications':
specifier: ^8.3.5
version: 8.3.5(@mantine/core@8.3.1(@mantine/hooks@8.3.1(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@mantine/hooks@8.3.1(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
@@ -38,6 +41,9 @@ importers:
moment:
specifier: ^2.30.1
version: 2.30.1
+ react-hook-form:
+ specifier: ^7.71.0
+ version: 7.71.0(react@19.2.0)
use-sync-external-store:
specifier: '>= 1.4.0'
version: 1.5.0(react@19.2.0)
@@ -702,6 +708,14 @@ packages:
peerDependencies:
react: ^18.x || ^19.x
+ '@mantine/modals@8.3.12':
+ resolution: {integrity: sha512-+uRyGe2lLy601qlMk+8aR9d/Aibu+dZi6Jcmvm5z8Gw4ocviyMMlnd8BLSQ/Jvib2OX8fWj+yUQN7FMQ4Rbwjw==}
+ peerDependencies:
+ '@mantine/core': 8.3.12
+ '@mantine/hooks': 8.3.12
+ react: ^18.x || ^19.x
+ react-dom: ^18.x || ^19.x
+
'@mantine/notifications@8.3.5':
resolution: {integrity: sha512-8TvzrPxfdtOLGTalv7Ei1hy2F6KbR3P7/V73yw3AOKhrf1ydS89sqV2ShbsucHGJk9Pto0wjdTPd8Q7pm5MAYw==}
peerDependencies:
@@ -2843,6 +2857,12 @@ packages:
resolution: {integrity: sha512-OeR2jAxdoqUMHIn/nS9fgreI5hSpgGoL5ezdal4+oO7YSSgJR8ga+PkYGJrSrJ9MKlPcQjMQXnketrD7WNmNsg==}
engines: {node: '>= 6'}
+ react-hook-form@7.71.0:
+ resolution: {integrity: sha512-oFDt/iIFMV9ZfV52waONXzg4xuSlbwKUPvXVH2jumL1me5qFhBMc4knZxuXiZ2+j6h546sYe3ZKJcg/900/iHw==}
+ engines: {node: '>=18.0.0'}
+ peerDependencies:
+ react: ^16.8.0 || ^17 || ^18 || ^19
+
react-html-attributes@1.4.6:
resolution: {integrity: sha512-uS3MmThNKFH2EZUQQw4k5pIcU7XIr208UE5dktrj/GOH1CMagqxDl4DCLpt3o2l9x+IB5nVYBeN3Cr4IutBXAg==}
@@ -4285,6 +4305,13 @@ snapshots:
dependencies:
react: 19.2.0
+ '@mantine/modals@8.3.12(@mantine/core@8.3.1(@mantine/hooks@8.3.1(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@mantine/hooks@8.3.1(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
+ dependencies:
+ '@mantine/core': 8.3.1(@mantine/hooks@8.3.1(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ '@mantine/hooks': 8.3.1(react@19.2.0)
+ react: 19.2.0
+ react-dom: 19.2.0(react@19.2.0)
+
'@mantine/notifications@8.3.5(@mantine/core@8.3.1(@mantine/hooks@8.3.1(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@mantine/hooks@8.3.1(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
dependencies:
'@mantine/core': 8.3.1(@mantine/hooks@8.3.1(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
@@ -6686,6 +6713,10 @@ snapshots:
dependencies:
prop-types: 15.8.1
+ react-hook-form@7.71.0(react@19.2.0):
+ dependencies:
+ react: 19.2.0
+
react-html-attributes@1.4.6:
dependencies:
html-element-attributes: 1.3.1
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}
- >
-
-
-
-
- ) : (
-
- )
-}
-
-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 (
-
- )
-}
-
-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
new file mode 100644
index 0000000..3806971
--- /dev/null
+++ b/src/Former/Former.store.tsx
@@ -0,0 +1,188 @@
+import { createSyncStore } from '@warkypublic/zustandsyncstore';
+import { produce } from 'immer';
+
+import type { FormerProps, FormerState } from './Former.types';
+
+const { Provider: FormerProvider, useStore: useFormerStore } = createSyncStore<
+ FormerState & Partial>,
+ FormerProps
+>(
+ (set, get) => ({
+ getState: (key) => {
+ const current = get();
+ return current?.[key];
+ },
+ load: async (reset?: boolean) => {
+ try {
+ set({ loading: true });
+ const keyName = get()?.apiKeyField || 'id';
+ const keyValue = (get().values as any)?.[keyName] ?? (get().primeData as any)?.[keyName];
+ if (get().onAPICall && keyValue !== undefined) {
+ let data = await get().onAPICall!(
+ 'read',
+ get().request || 'insert',
+ get().values,
+ keyValue
+ );
+ if (get().afterGet) {
+ data = await get().afterGet!({ ...data });
+ }
+ set({ loading: false, values: data });
+ get().onChange?.(data);
+ }
+ if (reset && get().getFormMethods) {
+ const formMethods = get().getFormMethods!();
+ formMethods.reset();
+ }
+ } catch (e) {
+ set({ error: (e as Error)?.message ?? e, loading: false });
+ }
+ set({ loading: false });
+ },
+
+ onChange: (values) => {
+ set({ values });
+ },
+ request: 'insert',
+ reset: async () => {
+ const state = get();
+ if (state.getFormMethods) {
+ if (state.request !== 'insert') {
+ await state.load(true);
+ }
+
+ const formMethods = state.getFormMethods!();
+ formMethods.reset({ ...state.values, ...state.primeData });
+ }
+ },
+ save: async (e?: React.BaseSyntheticEvent