feat(form): ✨ enhance form functionality and API integration
* Refactor key handling to use uniqueKeyField * Add reset functionality to clear dirty state after save * Introduce new API call specifications for REST and resolve * Implement predefined wrappers for dialogs and popovers * Update todo list to reflect completed tasks
This commit is contained in:
@@ -16,7 +16,7 @@ const { Provider: FormerProvider, useStore: useFormerStore } = createSyncStore<
|
||||
load: async (reset?: boolean) => {
|
||||
try {
|
||||
set({ loading: true });
|
||||
const keyName = get()?.apiKeyField || 'id';
|
||||
const keyName = get()?.uniqueKeyField || 'id';
|
||||
const keyValue = (get().values as any)?.[keyName] ?? (get().primeData as any)?.[keyName];
|
||||
if (get().onAPICall && keyValue !== undefined) {
|
||||
let data = await get().onAPICall!(
|
||||
@@ -98,7 +98,7 @@ const { Provider: FormerProvider, useStore: useFormerStore } = createSyncStore<
|
||||
}
|
||||
|
||||
if (get().onAPICall) {
|
||||
const keyName = get()?.apiKeyField || 'id';
|
||||
const keyName = get()?.uniqueKeyField || 'id';
|
||||
const keyValue =
|
||||
(get().values as any)?.[keyName] ?? (get().primeData as any)?.[keyName];
|
||||
const savedData = await get().onAPICall!(
|
||||
@@ -112,6 +112,7 @@ const { Provider: FormerProvider, useStore: useFormerStore } = createSyncStore<
|
||||
}
|
||||
set({ loading: false, values: savedData });
|
||||
get().onChange?.(savedData);
|
||||
formMethods.reset(savedData); //reset with saved data to clear dirty state
|
||||
if (!keepOpen) {
|
||||
get().onClose?.(savedData);
|
||||
}
|
||||
@@ -119,6 +120,7 @@ const { Provider: FormerProvider, useStore: useFormerStore } = createSyncStore<
|
||||
}
|
||||
|
||||
set({ loading: false, values: data });
|
||||
formMethods.reset(data); //reset with saved data to clear dirty state
|
||||
get().onChange?.(data);
|
||||
if (!keepOpen) {
|
||||
get().onClose?.(data);
|
||||
@@ -169,7 +171,7 @@ const { Provider: FormerProvider, useStore: useFormerStore } = createSyncStore<
|
||||
},
|
||||
values: undefined,
|
||||
}),
|
||||
({ onConfirmDelete, primeData, request, values, id }) => {
|
||||
({ onConfirmDelete, primeData, request, values, id, onClose, useStoreApi }) => {
|
||||
let _onConfirmDelete = onConfirmDelete;
|
||||
if (!onConfirmDelete) {
|
||||
_onConfirmDelete = async () => {
|
||||
@@ -180,9 +182,28 @@ const { Provider: FormerProvider, useStore: useFormerStore } = createSyncStore<
|
||||
return {
|
||||
onConfirmDelete: _onConfirmDelete,
|
||||
primeData,
|
||||
request: request || 'insert',
|
||||
request: (request || 'insert').replace('change', 'update'),
|
||||
values: { ...primeData, ...values },
|
||||
id: !id ? newUUID() : id,
|
||||
onClose: () => {
|
||||
const dirty = useStoreApi.getState().dirty;
|
||||
const setState = useStoreApi.getState().setState;
|
||||
if (dirty) {
|
||||
if (confirm('You have unsaved changes. Are you sure you want to close?')) {
|
||||
if (onClose) {
|
||||
onClose();
|
||||
} else {
|
||||
setState('opened', false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (onClose) {
|
||||
onClose();
|
||||
} else {
|
||||
setState('opened', false);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
@@ -78,11 +78,20 @@ const FormerInner = forwardRef<FormerRef<any>, Partial<FormerProps<any>> & Props
|
||||
return await validate();
|
||||
},
|
||||
}),
|
||||
[getState, onChange]
|
||||
[getState, onChange, validate, save, reset, setState, onClose, onOpen]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setState('getFormMethods', () => formMethods);
|
||||
|
||||
if (formMethods) {
|
||||
formMethods.subscribe({
|
||||
formState: { isDirty: true },
|
||||
callback: ({ isDirty }) => {
|
||||
setState('dirty', isDirty);
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [formMethods]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -4,6 +4,7 @@ import type {
|
||||
LoadingOverlayProps,
|
||||
ScrollAreaAutosizeProps,
|
||||
} from '@mantine/core';
|
||||
import type React from 'react';
|
||||
import type { FieldValues, UseFormProps, UseFormReturn } from 'react-hook-form';
|
||||
|
||||
export type FormerSectionRender<T extends FieldValues = any> = (
|
||||
@@ -14,20 +15,22 @@ export type FormerSectionRender<T extends FieldValues = any> = (
|
||||
getState: FormerState<T>['getState']
|
||||
) => React.ReactNode;
|
||||
|
||||
export interface FormerProps<T extends FieldValues = any> {
|
||||
id?: string;
|
||||
afterGet?: (data: T) => Promise<T> | void;
|
||||
afterSave?: (data: T) => Promise<void> | void;
|
||||
apiKeyField?: string;
|
||||
beforeSave?: (data: T) => Promise<T> | T;
|
||||
disableHTMlForm?: boolean;
|
||||
keepOpen?: boolean;
|
||||
onAPICall?: (
|
||||
export type FormerAPICallType<T extends FieldValues = any> = (
|
||||
mode: 'mutate' | 'read',
|
||||
request: RequestType,
|
||||
value?: T,
|
||||
key?: number | string
|
||||
) => 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;
|
||||
disableHTMlForm?: boolean;
|
||||
keepOpen?: boolean;
|
||||
onAPICall?: FormerAPICallType<T>;
|
||||
onCancel?: () => void;
|
||||
onChange?: (value: T) => void;
|
||||
onClose?: (data?: T) => void;
|
||||
@@ -35,6 +38,7 @@ export interface FormerProps<T extends FieldValues = any> {
|
||||
onOpen?: (data?: T) => void;
|
||||
|
||||
opened?: boolean;
|
||||
dirty?: boolean;
|
||||
primeData?: T;
|
||||
request: RequestType;
|
||||
useFormProps?: UseFormProps<T>;
|
||||
@@ -44,6 +48,8 @@ export interface FormerProps<T extends FieldValues = any> {
|
||||
layout?: {
|
||||
renderTop?: FormerSectionRender<T>;
|
||||
renderBottom?: FormerSectionRender<T>;
|
||||
saveButtonTitle?: React.ReactNode;
|
||||
closeButtonTitle?: React.ReactNode;
|
||||
saveButtonProps?: ButtonProps;
|
||||
closeButtonProps?: ButtonProps;
|
||||
buttonOnTop?: boolean;
|
||||
|
||||
@@ -1,14 +1,33 @@
|
||||
import { Group, Button } from '@mantine/core';
|
||||
import { Group, Button, Tooltip } from '@mantine/core';
|
||||
import { IconX, IconDeviceFloppy } from '@tabler/icons-react';
|
||||
import { useFormerStore } from './Former.store';
|
||||
|
||||
export const FormerButtonArea = () => {
|
||||
const { save, onClose, buttonAreaGroupProps } = useFormerStore((state) => ({
|
||||
const {
|
||||
save,
|
||||
onClose,
|
||||
buttonAreaGroupProps,
|
||||
saveButtonProps,
|
||||
closeButtonProps,
|
||||
closeButtonTitle,
|
||||
saveButtonTitle,
|
||||
request,
|
||||
dirty,
|
||||
} = 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,
|
||||
}));
|
||||
|
||||
const disabledSave =
|
||||
['select', 'view'].includes(request || '') || (['update'].includes(request || '') && !dirty);
|
||||
|
||||
return (
|
||||
<Group
|
||||
justify="center"
|
||||
@@ -24,22 +43,41 @@ export const FormerButtonArea = () => {
|
||||
leftSection={<IconX />}
|
||||
size="sm"
|
||||
px="md"
|
||||
onClick={() => onClose()}
|
||||
miw={'8rem'}
|
||||
{...closeButtonProps}
|
||||
onClick={() => {
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
Close
|
||||
{closeButtonTitle || 'Close'}
|
||||
</Button>
|
||||
)}
|
||||
<Tooltip
|
||||
label={
|
||||
disabledSave ? (
|
||||
<p>
|
||||
Cannot save in view or select mode, or no changes made. <br />
|
||||
Try changing some values.
|
||||
</p>
|
||||
) : (
|
||||
<p>Save the current record</p>
|
||||
)
|
||||
}
|
||||
>
|
||||
<Button
|
||||
color="green"
|
||||
leftSection={<IconDeviceFloppy />}
|
||||
size="sm"
|
||||
px="md"
|
||||
onClick={() => save()}
|
||||
miw={'8rem'}
|
||||
bg={request === 'delete' ? 'red' : undefined}
|
||||
{...saveButtonProps}
|
||||
disabled={disabledSave}
|
||||
onClick={() => save()}
|
||||
>
|
||||
Save
|
||||
{saveButtonTitle || 'Save'}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
</Group>
|
||||
);
|
||||
|
||||
@@ -17,8 +17,7 @@ export const FormerLayout = (props: PropsWithChildren) => {
|
||||
save,
|
||||
scrollAreaProps,
|
||||
id,
|
||||
layout,
|
||||
getState,
|
||||
opened,
|
||||
} = useFormerStore((state) => ({
|
||||
disableHTMlForm: state.disableHTMlForm,
|
||||
getFormMethods: state.getFormMethods,
|
||||
@@ -30,8 +29,8 @@ export const FormerLayout = (props: PropsWithChildren) => {
|
||||
save: state.save,
|
||||
scrollAreaProps: state.scrollAreaProps,
|
||||
id: state.id,
|
||||
layout: state.layout,
|
||||
getState: state.getState,
|
||||
|
||||
opened: state.opened,
|
||||
}));
|
||||
|
||||
useEffect(() => {
|
||||
@@ -41,7 +40,7 @@ export const FormerLayout = (props: PropsWithChildren) => {
|
||||
load(true);
|
||||
}
|
||||
}
|
||||
}, [getFormMethods, request]);
|
||||
}, [getFormMethods, request, opened]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
72
src/Former/FormerResolveSpecAPI.ts
Normal file
72
src/Former/FormerResolveSpecAPI.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import type { FormerAPICallType } from './Former.types';
|
||||
|
||||
interface ResolveSpecRequest {
|
||||
operation: 'read' | 'create' | 'update' | 'delete';
|
||||
data?: Record<string, any>;
|
||||
options?: {
|
||||
preload?: string[];
|
||||
columns?: string[];
|
||||
filters?: Array<{ column: string; operator: string; value: any }>;
|
||||
sort?: string[];
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
customOperators?: any[];
|
||||
computedColumns?: any[];
|
||||
};
|
||||
}
|
||||
|
||||
function FormerResolveSpecAPI(options: {
|
||||
url: string;
|
||||
authToken: string;
|
||||
signal?: AbortSignal;
|
||||
fetchOptions?: Partial<RequestInit>;
|
||||
}): FormerAPICallType {
|
||||
return async (mode, request, value, key) => {
|
||||
const baseUrl = options.url.replace(/\/$/, '');
|
||||
|
||||
// Build URL: /[schema]/[table_or_entity]/[id]
|
||||
let url = `${baseUrl}`;
|
||||
if (request !== 'insert' && key) {
|
||||
url = `${url}/${key}`;
|
||||
}
|
||||
|
||||
// Build ResolveSpec request body
|
||||
const resolveSpecRequest: ResolveSpecRequest = {
|
||||
operation:
|
||||
mode === 'read'
|
||||
? 'read'
|
||||
: request === 'delete'
|
||||
? 'delete'
|
||||
: request === 'update'
|
||||
? 'update'
|
||||
: 'create',
|
||||
};
|
||||
|
||||
if (mode === 'mutate') {
|
||||
resolveSpecRequest.data = value;
|
||||
}
|
||||
|
||||
const fetchOptions: RequestInit = {
|
||||
cache: 'no-cache',
|
||||
signal: options.signal,
|
||||
...options.fetchOptions,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${options.authToken}`,
|
||||
...options.fetchOptions?.headers,
|
||||
},
|
||||
body: JSON.stringify(resolveSpecRequest),
|
||||
};
|
||||
|
||||
const response = await fetch(url, fetchOptions);
|
||||
if (!response.ok) {
|
||||
throw new Error(`API request failed with status ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data as any;
|
||||
};
|
||||
}
|
||||
|
||||
export { FormerResolveSpecAPI, type ResolveSpecRequest };
|
||||
50
src/Former/FormerRestHeadSpecAPI.ts
Normal file
50
src/Former/FormerRestHeadSpecAPI.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import type { FormerAPICallType } from './Former.types';
|
||||
|
||||
function FormerRestHeadSpecAPI(options: {
|
||||
url: string;
|
||||
authToken: string;
|
||||
signal?: AbortSignal;
|
||||
fetchOptions?: Partial<RequestInit>;
|
||||
}): FormerAPICallType {
|
||||
return async (mode, request, value, key) => {
|
||||
const baseUrl = options.url ?? ''; // Remove trailing slashes
|
||||
let url = baseUrl;
|
||||
let fetchOptions: RequestInit = {
|
||||
cache: 'no-cache',
|
||||
signal: options.signal,
|
||||
...options.fetchOptions,
|
||||
method:
|
||||
mode === 'read'
|
||||
? 'GET'
|
||||
: request === 'delete'
|
||||
? 'DELETE'
|
||||
: 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') {
|
||||
url = `${baseUrl}/${key}`;
|
||||
}
|
||||
|
||||
const response = await fetch(url, fetchOptions);
|
||||
if (!response.ok) {
|
||||
throw new Error(`API request failed with status ${response.status}`);
|
||||
}
|
||||
|
||||
if (mode === 'read') {
|
||||
const data = await response.json();
|
||||
return data as any;
|
||||
} else {
|
||||
return value as any;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export { FormerRestHeadSpecAPI };
|
||||
114
src/Former/FormerWrappers.tsx
Normal file
114
src/Former/FormerWrappers.tsx
Normal file
@@ -0,0 +1,114 @@
|
||||
import {
|
||||
Drawer,
|
||||
Modal,
|
||||
Popover,
|
||||
type DrawerProps,
|
||||
type ModalProps,
|
||||
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;
|
||||
return (
|
||||
<Former
|
||||
{...former}
|
||||
opened={opened}
|
||||
onClose={onClose}
|
||||
wrapper={(children, opened, onClose, _onOpen, getState) => {
|
||||
const values = getState('values');
|
||||
const request = getState('request');
|
||||
const uniqueKeyField = getState('uniqueKeyField') ?? 'id';
|
||||
return (
|
||||
<Drawer
|
||||
h={'100%'}
|
||||
title={
|
||||
request === 'delete'
|
||||
? `Delete Record - ${values?.[uniqueKeyField]}`
|
||||
: request === 'insert'
|
||||
? 'New Record'
|
||||
: `Edit Record - ${values?.[uniqueKeyField]}`
|
||||
}
|
||||
closeOnClickOutside={false}
|
||||
{...rest}
|
||||
onClose={() => onClose?.()}
|
||||
opened={opened ?? false}
|
||||
>
|
||||
{children}
|
||||
</Drawer>
|
||||
);
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Former>
|
||||
);
|
||||
};
|
||||
|
||||
export const FormerModel = (props: ModalProps & { former: FormerProps }) => {
|
||||
const { former, children, opened, onClose, ...rest } = props;
|
||||
return (
|
||||
<Former
|
||||
{...former}
|
||||
opened={opened}
|
||||
onClose={onClose}
|
||||
wrapper={(children, opened, onClose, _onOpen, getState) => {
|
||||
const values = getState('values');
|
||||
const request = getState('request');
|
||||
const uniqueKeyField = getState('uniqueKeyField') ?? 'id';
|
||||
return (
|
||||
<Modal
|
||||
h={'100%'}
|
||||
title={
|
||||
request === 'delete'
|
||||
? `Delete Record - ${values?.[uniqueKeyField]}`
|
||||
: request === 'insert'
|
||||
? 'New Record'
|
||||
: `Edit Record - ${values?.[uniqueKeyField]}`
|
||||
}
|
||||
closeOnClickOutside={false}
|
||||
{...rest}
|
||||
onClose={() => onClose?.()}
|
||||
opened={opened ?? false}
|
||||
>
|
||||
{children}
|
||||
</Modal>
|
||||
);
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Former>
|
||||
);
|
||||
};
|
||||
|
||||
export const FormerPopover = (
|
||||
props: PopoverProps & { former: FormerProps; target: React.ReactNode }
|
||||
) => {
|
||||
const { former, children, opened, onClose, target, ...rest } = props;
|
||||
return (
|
||||
<Former
|
||||
{...former}
|
||||
opened={opened}
|
||||
onClose={onClose}
|
||||
wrapper={(children, opened, onClose, _onOpen, _getState) => {
|
||||
return (
|
||||
<Popover
|
||||
withArrow
|
||||
closeOnClickOutside={false}
|
||||
width={250}
|
||||
trapFocus
|
||||
middlewares={{ inline: true }}
|
||||
{...rest}
|
||||
onClose={() => onClose?.()}
|
||||
opened={opened ?? false}
|
||||
>
|
||||
<Popover.Target>{target}</Popover.Target>
|
||||
<Popover.Dropdown>{children}</Popover.Dropdown>
|
||||
</Popover>
|
||||
);
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Former>
|
||||
);
|
||||
};
|
||||
40
src/Former/stories/apiFormData.tsx
Normal file
40
src/Former/stories/apiFormData.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import { TextInput } from '@mantine/core';
|
||||
import { Former } from '../Former';
|
||||
import { useUncontrolled } from '@mantine/hooks';
|
||||
|
||||
import { Controller } from 'react-hook-form';
|
||||
|
||||
export const ApiFormData = (props: {
|
||||
values?: Record<string, unknown>;
|
||||
onChange?: (values: Record<string, unknown>) => void;
|
||||
primeData?: Record<string, unknown>;
|
||||
}) => {
|
||||
const [values, setValues] = useUncontrolled<Record<string, unknown>>({
|
||||
value: props.values,
|
||||
defaultValue: { url: '', authToken: '', ...props.primeData },
|
||||
finalValue: { url: '', authToken: '', ...props.primeData },
|
||||
onChange: props.onChange,
|
||||
});
|
||||
|
||||
return (
|
||||
<Former
|
||||
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} />}
|
||||
/>
|
||||
<Controller
|
||||
name="authToken"
|
||||
render={({ field }) => <TextInput type="password" label="Auth Token" {...field} />}
|
||||
/>
|
||||
</Former>
|
||||
);
|
||||
};
|
||||
@@ -2,14 +2,38 @@ import { Button, Drawer, Group, Paper, Select, Stack, Switch } from '@mantine/co
|
||||
import { useRef, useState } from 'react';
|
||||
import { Controller } from 'react-hook-form';
|
||||
|
||||
import type { FormerProps, FormerRef } from '../Former.types';
|
||||
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';
|
||||
|
||||
const StubAPI = (): FormerAPICallType => (mode, request, value) => {
|
||||
console.log('API Call', mode, request, value);
|
||||
if (mode === 'read') {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve({ a: 'Another Value', test: 'Loaded Value' });
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve(value || {});
|
||||
}, 1000);
|
||||
});
|
||||
};
|
||||
|
||||
export const FormTest = () => {
|
||||
const [request, setRequest] = useState<null | string>('insert');
|
||||
const [wrapped, setWrapped] = useState(false);
|
||||
const [disableHTML, setDisableHTML] = useState(false);
|
||||
const [apiOptions, setApiOptions] = useState({
|
||||
url: '',
|
||||
authToken: '',
|
||||
type: '',
|
||||
});
|
||||
const [layout, setLayout] = useState({
|
||||
buttonOnTop: false,
|
||||
title: 'Custom Former Title',
|
||||
@@ -17,8 +41,8 @@ export const FormTest = () => {
|
||||
} as FormerProps['layout']);
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const [formData, setFormData] = useState({ a: 99 });
|
||||
console.log('formData', formData);
|
||||
const [formData, setFormData] = useState({ a: 99, rid_usernote: 3047 });
|
||||
//console.log('formData render', formData);
|
||||
|
||||
const ref = useRef<FormerRef>(null);
|
||||
return (
|
||||
@@ -44,6 +68,13 @@ export const FormTest = () => {
|
||||
label="Button On Top"
|
||||
onChange={(event) => setLayout({ ...layout, buttonOnTop: event.currentTarget.checked })}
|
||||
/>
|
||||
<Switch
|
||||
checked={apiOptions.type === 'api'}
|
||||
label="Use API"
|
||||
onChange={(event) =>
|
||||
setApiOptions({ ...apiOptions, type: event.currentTarget.checked ? 'api' : '' })
|
||||
}
|
||||
/>
|
||||
</Group>
|
||||
<Button onClick={() => setOpen(true)}>Open Former Drawer</Button>
|
||||
<Group>
|
||||
@@ -65,25 +96,21 @@ export const FormTest = () => {
|
||||
Test Show/Hide
|
||||
</Button>
|
||||
</Group>
|
||||
<FormerModel opened={open} onClose={() => setOpen(false)} former={{ request: 'insert' }}>
|
||||
<div>Test</div>
|
||||
</FormerModel>
|
||||
<Former
|
||||
//wrapper={(children, getState) => <div>{children}</div>}
|
||||
//opened={true}
|
||||
apiKeyField="a"
|
||||
onAPICall={(mode, request, value) => {
|
||||
console.log('API Call', mode, request, value);
|
||||
if (mode === 'read') {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve({ a: 'Another Value', test: 'Loaded Value' });
|
||||
}, 1000);
|
||||
});
|
||||
uniqueKeyField="rid_usernote"
|
||||
onAPICall={
|
||||
apiOptions.type === 'api'
|
||||
? FormerRestHeadSpecAPI({
|
||||
authToken: apiOptions.authToken,
|
||||
url: apiOptions.url,
|
||||
})
|
||||
: StubAPI()
|
||||
}
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve(value || {});
|
||||
}, 1000);
|
||||
});
|
||||
}}
|
||||
disableHTMlForm={disableHTML}
|
||||
onChange={setFormData}
|
||||
onClose={() => setOpen(false)}
|
||||
@@ -94,29 +121,28 @@ export const FormTest = () => {
|
||||
useFormProps={{ criteriaMode: 'all', shouldUseNativeValidation: false }}
|
||||
values={formData}
|
||||
layout={layout}
|
||||
wrapper={
|
||||
wrapped
|
||||
? (children, opened, onClose, _onOpen, getState) => {
|
||||
const values = getState('values');
|
||||
return (
|
||||
<Drawer
|
||||
h={'100%'}
|
||||
onClose={() => onClose?.()}
|
||||
opened={opened ?? false}
|
||||
title={`Drawer Former - Current A Value: ${values?.a}`}
|
||||
w={'50%'}
|
||||
// wrapper={
|
||||
// wrapped
|
||||
// ? (children, opened, onClose, _onOpen, getState) => {
|
||||
// const values = getState('values');
|
||||
// return (
|
||||
// <Drawer
|
||||
// h={'100%'}
|
||||
// onClose={() => onClose?.()}
|
||||
// opened={opened ?? false}
|
||||
// title={`Drawer Former - Current A Value: ${values?.a}`}
|
||||
// w={'50%'}
|
||||
// >
|
||||
// <Paper h="100%" shadow="sm" w="100%" withBorder>
|
||||
// {children}
|
||||
// </Paper>
|
||||
// </Drawer>
|
||||
// );
|
||||
// }
|
||||
// : undefined
|
||||
// }
|
||||
>
|
||||
<Paper h="100%" shadow="sm" w="100%" withBorder>
|
||||
{children}
|
||||
<Button>Test Save</Button>
|
||||
</Paper>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<Stack h="1200px">
|
||||
<Stack pb={'400px'}>
|
||||
<Stack>
|
||||
<Controller
|
||||
name="test"
|
||||
@@ -127,6 +153,11 @@ export const FormTest = () => {
|
||||
render={({ field }) => <input type="text" {...field} placeholder="B" />}
|
||||
rules={{ required: 'Field is required' }}
|
||||
/>
|
||||
<Controller
|
||||
name="note"
|
||||
render={({ field }) => <input type="text" {...field} placeholder="note" />}
|
||||
rules={{ required: 'Field is required' }}
|
||||
/>
|
||||
</Stack>
|
||||
{!disableHTML && (
|
||||
<Stack>
|
||||
@@ -136,6 +167,14 @@ export const FormTest = () => {
|
||||
)}
|
||||
</Stack>
|
||||
</Former>
|
||||
{apiOptions.type === 'api' && (
|
||||
<ApiFormData
|
||||
values={apiOptions}
|
||||
onChange={(values) => {
|
||||
setApiOptions({ ...apiOptions, ...values });
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
- [x] Wrapper must receive button areas etc. Better scroll areas.
|
||||
- [ ] Predefined wrappers (Model,Dialog,notification,popover)
|
||||
- [ ] Headerspec API
|
||||
- [ ] Relspec API
|
||||
- [x] Predefined wrappers (Model,Dialog,notification,popover)
|
||||
- [x] Headerspec API
|
||||
- [x] Relspec API
|
||||
- [ ] SocketSpec API
|
||||
- [x] Layout Tool
|
||||
- [x] Header Section
|
||||
|
||||
Reference in New Issue
Block a user