chore(form): 🗑️ remove unused form components and types

* Refactor Former components to streamline functionality
* Update stories to reflect changes in form structure
This commit is contained in:
2026-01-14 21:56:55 +02:00
parent cd2f6db880
commit e777e1fa3a
38 changed files with 133 additions and 2125 deletions

View File

@@ -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<any> & Partial<FormerProps<any>>,
@@ -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 },
};
}
);

View File

@@ -86,10 +86,10 @@ const FormerInner = forwardRef<FormerRef<any>, Partial<FormerProps<any>> & Props
if (formMethods) {
formMethods.subscribe({
formState: { isDirty: true },
callback: ({ isDirty }) => {
setState('dirty', isDirty);
},
formState: { isDirty: true },
});
}
}, [formMethods]);

View File

@@ -7,14 +7,6 @@ import type {
import type React from 'react';
import type { FieldValues, UseFormProps, UseFormReturn } from 'react-hook-form';
export type FormerSectionRender<T extends FieldValues = any> = (
children: React.ReactNode,
opened: boolean | undefined,
onClose: ((data?: T) => void) | undefined,
onOpen: ((data?: T) => void) | undefined,
getState: FormerState<T>['getState']
) => React.ReactNode;
export type FormerAPICallType<T extends FieldValues = any> = (
mode: 'mutate' | 'read',
request: RequestType,
@@ -23,39 +15,39 @@ export type FormerAPICallType<T extends FieldValues = any> = (
) => Promise<T>;
export interface FormerProps<T extends FieldValues = any> {
id?: string;
afterGet?: (data: T) => Promise<T> | void;
afterSave?: (data: T) => Promise<void> | void;
uniqueKeyField?: string;
beforeSave?: (data: T) => Promise<T> | T;
dirty?: boolean;
disableHTMlForm?: boolean;
id?: string;
keepOpen?: boolean;
layout?: {
buttonAreaGroupProps?: GroupProps;
buttonOnTop?: boolean;
closeButtonProps?: ButtonProps;
closeButtonTitle?: React.ReactNode;
renderBottom?: FormerSectionRender<T>;
renderTop?: FormerSectionRender<T>;
saveButtonProps?: ButtonProps;
saveButtonTitle?: React.ReactNode;
title?: string;
};
onAPICall?: FormerAPICallType<T>;
onCancel?: () => void;
onChange?: (value: T) => void;
onClose?: (data?: T) => void;
onConfirmDelete?: (values?: T) => Promise<boolean>;
onOpen?: (data?: T) => void;
onOpen?: (data?: T) => void;
opened?: boolean;
dirty?: boolean;
primeData?: T;
request: RequestType;
uniqueKeyField?: string;
useFormProps?: UseFormProps<T>;
values?: T;
wrapper?: FormerSectionRender<T>;
layout?: {
renderTop?: FormerSectionRender<T>;
renderBottom?: FormerSectionRender<T>;
saveButtonTitle?: React.ReactNode;
closeButtonTitle?: React.ReactNode;
saveButtonProps?: ButtonProps;
closeButtonProps?: ButtonProps;
buttonOnTop?: boolean;
buttonAreaGroupProps?: GroupProps;
title?: string;
};
wrapper?: FormerSectionRender<T>;
}
export interface FormerRef<T extends FieldValues = any> {
@@ -68,6 +60,14 @@ export interface FormerRef<T extends FieldValues = any> {
validate: () => Promise<boolean>;
}
export type FormerSectionRender<T extends FieldValues = any> = (
children: React.ReactNode,
opened: boolean | undefined,
onClose: ((data?: T) => void) | undefined,
onOpen: ((data?: T) => void) | undefined,
getState: FormerState<T>['getState']
) => React.ReactNode;
export interface FormerState<T extends FieldValues = any> {
deleteConfirmed?: boolean;
error?: string;

View File

@@ -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 (
<Group
justify="center"
w="100%"
p="xs"
style={{ boxShadow: '2px 2px 5px rgba(47, 47, 47, 0.1)' }}
w="100%"
{...buttonAreaGroupProps}
>
<Group justify="space-evenly" grow>
<Group grow justify="space-evenly">
{typeof onClose === 'function' && (
<Button
color="orange"
leftSection={<IconX />}
size="sm"
px="md"
miw={'8rem'}
px="md"
size="sm"
{...closeButtonProps}
onClick={() => {
onClose();
@@ -65,12 +66,12 @@ export const FormerButtonArea = () => {
}
>
<Button
bg={request === 'delete' ? 'red' : undefined}
color="green"
leftSection={<IconDeviceFloppy />}
size="sm"
px="md"
miw={'8rem'}
bg={request === 'delete' ? 'red' : undefined}
px="md"
size="sm"
{...saveButtonProps}
disabled={disabledSave}
onClick={() => save()}

View File

@@ -9,28 +9,28 @@ export const FormerLayout = (props: PropsWithChildren) => {
const {
disableHTMlForm,
getFormMethods,
id,
load,
loading,
loadingOverlayProps,
opened,
request,
reset,
save,
scrollAreaProps,
id,
opened,
} = useFormerStore((state) => ({
disableHTMlForm: state.disableHTMlForm,
getFormMethods: state.getFormMethods,
id: state.id,
load: state.load,
loading: state.loading,
loadingOverlayProps: state.loadingOverlayProps,
opened: state.opened,
request: state.request,
reset: state.reset,
save: state.save,
scrollAreaProps: state.scrollAreaProps,
id: state.id,
opened: state.opened,
scrollAreaProps: state.scrollAreaProps,
}));
useEffect(() => {
@@ -58,15 +58,17 @@ export const FormerLayout = (props: PropsWithChildren) => {
}}
>
{disableHTMlForm ? (
<div x-data-request={request} key={`former_d${id}`}>
// eslint-disable-next-line react/no-unknown-property
<div key={`former_d${id}`} x-data-request={request}>
{props.children}
</div>
) : (
<form
key={`former_${id}`}
id={`former_f${id}`}
key={`former_${id}`}
onReset={(e) => reset(e)}
onSubmit={(e) => save(e)}
// eslint-disable-next-line react/no-unknown-property
x-data-request={request}
>
{props.children}

View File

@@ -2,11 +2,11 @@ import { useFormerStore } from './Former.store';
import { FormerButtonArea } from './FormerButtonArea';
export const FormerLayoutBottom = () => {
const { renderBottom, getState, opened, buttonOnTop } = useFormerStore((state) => ({
renderBottom: state.layout?.renderBottom,
const { buttonOnTop, getState, opened, renderBottom } = useFormerStore((state) => ({
buttonOnTop: state.layout?.buttonOnTop,
getState: state.getState,
opened: state.opened,
renderBottom: state.layout?.renderBottom,
}));
if (renderBottom) {

View File

@@ -2,11 +2,11 @@ import { useFormerStore } from './Former.store';
import { FormerButtonArea } from './FormerButtonArea';
export const FormerLayoutTop = () => {
const { renderTop, getState, opened, buttonOnTop } = useFormerStore((state) => ({
renderTop: state.layout?.renderTop,
const { buttonOnTop, getState, opened, renderTop } = useFormerStore((state) => ({
buttonOnTop: state.layout?.buttonOnTop,
getState: state.getState,
opened: state.opened,
renderTop: state.layout?.renderTop,
}));
if (renderTop) {

View File

@@ -1,25 +1,25 @@
import type { FormerAPICallType } from './Former.types';
interface ResolveSpecRequest {
operation: 'read' | 'create' | 'update' | 'delete';
data?: Record<string, any>;
operation: 'create' | 'delete' | 'read' | 'update';
options?: {
preload?: string[];
columns?: string[];
computedColumns?: any[];
customOperators?: any[];
filters?: Array<{ column: string; operator: string; value: any }>;
sort?: string[];
limit?: number;
offset?: number;
customOperators?: any[];
computedColumns?: any[];
preload?: string[];
sort?: string[];
};
}
function FormerResolveSpecAPI(options: {
url: string;
authToken: string;
signal?: AbortSignal;
fetchOptions?: Partial<RequestInit>;
signal?: AbortSignal;
url: string;
}): FormerAPICallType {
return async (mode, request, value, key) => {
const baseUrl = options.url.replace(/\/$/, '');
@@ -50,13 +50,13 @@ function FormerResolveSpecAPI(options: {
cache: 'no-cache',
signal: options.signal,
...options.fetchOptions,
method: 'POST',
body: JSON.stringify(resolveSpecRequest),
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${options.authToken}`,
'Content-Type': 'application/json',
...options.fetchOptions?.headers,
},
body: JSON.stringify(resolveSpecRequest),
method: 'POST',
};
const response = await fetch(url, fetchOptions);

View File

@@ -1,18 +1,24 @@
import type { FormerAPICallType } from './Former.types';
function FormerRestHeadSpecAPI(options: {
url: string;
authToken: string;
signal?: AbortSignal;
fetchOptions?: Partial<RequestInit>;
signal?: AbortSignal;
url: string;
}): FormerAPICallType {
return async (mode, request, value, key) => {
const baseUrl = options.url ?? ''; // Remove trailing slashes
let url = baseUrl;
let fetchOptions: RequestInit = {
const fetchOptions: RequestInit = {
cache: 'no-cache',
signal: options.signal,
...options.fetchOptions,
body: mode === 'mutate' && request !== 'delete' ? JSON.stringify(value) : undefined,
headers: {
Authorization: `Bearer ${options.authToken}`,
'Content-Type': 'application/json',
...options.fetchOptions?.headers,
},
method:
mode === 'read'
? 'GET'
@@ -21,12 +27,6 @@ function FormerRestHeadSpecAPI(options: {
: request === 'update'
? 'PUT'
: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${options.authToken}`,
...options.fetchOptions?.headers,
},
body: mode === 'mutate' && request !== 'delete' ? JSON.stringify(value) : undefined,
};
if (request !== 'insert') {

View File

@@ -1,27 +1,30 @@
import {
Drawer,
Modal,
Popover,
type DrawerProps,
Modal,
type ModalProps,
Popover,
type PopoverProps,
} from '@mantine/core';
import type { FormerProps } from './Former.types';
import { Former } from './Former';
export const FormerDialog = (props: DrawerProps & { former: FormerProps }) => {
const { former, children, opened, onClose, ...rest } = props;
export const FormerDialog = (props: { former: FormerProps } & DrawerProps) => {
const { children, former, onClose, opened, ...rest } = props;
return (
<Former
{...former}
opened={opened}
onClose={onClose}
opened={opened}
wrapper={(children, opened, onClose, _onOpen, getState) => {
const values = getState('values');
const request = getState('request');
const uniqueKeyField = getState('uniqueKeyField') ?? 'id';
return (
<Drawer
closeOnClickOutside={false}
h={'100%'}
title={
request === 'delete'
@@ -30,7 +33,6 @@ export const FormerDialog = (props: DrawerProps & { former: FormerProps }) => {
? 'New Record'
: `Edit Record - ${values?.[uniqueKeyField]}`
}
closeOnClickOutside={false}
{...rest}
onClose={() => onClose?.()}
opened={opened ?? false}
@@ -45,19 +47,20 @@ export const FormerDialog = (props: DrawerProps & { former: FormerProps }) => {
);
};
export const FormerModel = (props: ModalProps & { former: FormerProps }) => {
const { former, children, opened, onClose, ...rest } = props;
export const FormerModel = (props: { former: FormerProps } & ModalProps) => {
const { children, former, onClose, opened, ...rest } = props;
return (
<Former
{...former}
opened={opened}
onClose={onClose}
opened={opened}
wrapper={(children, opened, onClose, _onOpen, getState) => {
const values = getState('values');
const request = getState('request');
const uniqueKeyField = getState('uniqueKeyField') ?? 'id';
return (
<Modal
closeOnClickOutside={false}
h={'100%'}
title={
request === 'delete'
@@ -66,7 +69,6 @@ export const FormerModel = (props: ModalProps & { former: FormerProps }) => {
? 'New Record'
: `Edit Record - ${values?.[uniqueKeyField]}`
}
closeOnClickOutside={false}
{...rest}
onClose={() => onClose?.()}
opened={opened ?? false}
@@ -82,22 +84,22 @@ export const FormerModel = (props: ModalProps & { former: FormerProps }) => {
};
export const FormerPopover = (
props: PopoverProps & { former: FormerProps; target: React.ReactNode }
props: { former: FormerProps; target: React.ReactNode } & PopoverProps
) => {
const { former, children, opened, onClose, target, ...rest } = props;
const { children, former, onClose, opened, target, ...rest } = props;
return (
<Former
{...former}
opened={opened}
onClose={onClose}
wrapper={(children, opened, onClose, _onOpen, _getState) => {
opened={opened}
wrapper={(children, opened, onClose) => {
return (
<Popover
withArrow
closeOnClickOutside={false}
width={250}
trapFocus
middlewares={{ inline: true }}
trapFocus
width={250}
withArrow
{...rest}
onClose={() => onClose?.()}
opened={opened ?? false}

View File

@@ -0,0 +1,6 @@
export { Former } from './Former';
export type * from './Former.types';
export { FormerButtonArea } from './FormerButtonArea';
export { FormerResolveSpecAPI } from './FormerResolveSpecAPI';
export { FormerRestHeadSpecAPI } from './FormerRestHeadSpecAPI';
export { FormerDialog, FormerModel, FormerPopover } from './FormerWrappers';

View File

@@ -1,39 +1,39 @@
import { TextInput } from '@mantine/core';
import { Former } from '../Former';
import { useUncontrolled } from '@mantine/hooks';
import { Controller } from 'react-hook-form';
import { Former } from '../Former';
export const ApiFormData = (props: {
values?: Record<string, unknown>;
onChange?: (values: Record<string, unknown>) => void;
primeData?: Record<string, unknown>;
values?: Record<string, unknown>;
}) => {
const [values, setValues] = useUncontrolled<Record<string, unknown>>({
value: props.values,
defaultValue: { url: '', authToken: '', ...props.primeData },
finalValue: { url: '', authToken: '', ...props.primeData },
defaultValue: { authToken: '', url: '', ...props.primeData },
finalValue: { authToken: '', url: '', ...props.primeData },
onChange: props.onChange,
value: props.values,
});
return (
<Former
disableHTMlForm
id="api-form-data"
layout={{ saveButtonTitle: 'Save URL Parameters' }}
onChange={setValues}
primeData={props.primeData}
request="update"
uniqueKeyField="id"
disableHTMlForm
primeData={props.primeData}
values={values}
onChange={setValues}
layout={{ saveButtonTitle: 'Save URL Parameters' }}
id="api-form-data"
>
<Controller
name="url"
render={({ field }) => <TextInput type="url" label="URL" {...field} />}
render={({ field }) => <TextInput label="URL" type="url" {...field} />}
/>
<Controller
name="authToken"
render={({ field }) => <TextInput type="password" label="Auth Token" {...field} />}
render={({ field }) => <TextInput label="Auth Token" type="password" {...field} />}
/>
</Former>
);

View File

@@ -1,13 +1,13 @@
import { Button, Drawer, Group, Paper, Select, Stack, Switch } from '@mantine/core';
import { Button, Group, Select, Stack, Switch } from '@mantine/core';
import { useRef, useState } from 'react';
import { Controller } from 'react-hook-form';
import type { FormerAPICallType, FormerProps, FormerRef } from '../Former.types';
import { Former } from '../Former';
import { ApiFormData } from './apiFormData';
import { FormerRestHeadSpecAPI } from '../FormerRestHeadSpecAPI';
import { FormerDialog, FormerModel } from '../FormerWrappers';
import { FormerModel } from '../FormerWrappers';
import { ApiFormData } from './apiFormData';
const StubAPI = (): FormerAPICallType => (mode, request, value) => {
console.log('API Call', mode, request, value);
@@ -30,14 +30,14 @@ export const FormTest = () => {
const [wrapped, setWrapped] = useState(false);
const [disableHTML, setDisableHTML] = useState(false);
const [apiOptions, setApiOptions] = useState({
url: '',
authToken: '',
type: '',
url: '',
});
const [layout, setLayout] = useState({
buttonAreaGroupProps: { justify: 'center' },
buttonOnTop: false,
title: 'Custom Former Title',
buttonAreaGroupProps: { justify: 'center' },
} as FormerProps['layout']);
const [open, setOpen] = useState(false);
@@ -96,13 +96,12 @@ export const FormTest = () => {
Test Show/Hide
</Button>
</Group>
<FormerModel opened={open} onClose={() => setOpen(false)} former={{ request: 'insert' }}>
<FormerModel former={{ request: 'insert' }} onClose={() => setOpen(false)} opened={open}>
<div>Test</div>
</FormerModel>
<Former
//wrapper={(children, getState) => <div>{children}</div>}
//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) => <div>{children}</div>}
//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 = () => {
</Former>
{apiOptions.type === 'api' && (
<ApiFormData
values={apiOptions}
onChange={(values) => {
setApiOptions({ ...apiOptions, ...values });
}}
values={apiOptions}
/>
)}
</Stack>