diff --git a/src/Former/Former.store.tsx b/src/Former/Former.store.tsx index 3806971..d57a57b 100644 --- a/src/Former/Former.store.tsx +++ b/src/Former/Former.store.tsx @@ -2,6 +2,7 @@ import { createSyncStore } from '@warkypublic/zustandsyncstore'; import { produce } from 'immer'; import type { FormerProps, FormerState } from './Former.types'; +import { newUUID } from '@warkypublic/artemis-kit'; const { Provider: FormerProvider, useStore: useFormerStore } = createSyncStore< FormerState & Partial>, @@ -168,18 +169,20 @@ const { Provider: FormerProvider, useStore: useFormerStore } = createSyncStore< }, values: undefined, }), - ({ onConfirmDelete, primeData, request, values }) => { + ({ onConfirmDelete, primeData, request, values, id }) => { let _onConfirmDelete = onConfirmDelete; if (!onConfirmDelete) { _onConfirmDelete = async () => { return confirm('Are you sure you want to delete this item?'); }; } + return { onConfirmDelete: _onConfirmDelete, primeData, request: request || 'insert', values: { ...primeData, ...values }, + id: !id ? newUUID() : id, }; } ); diff --git a/src/Former/Former.types.ts b/src/Former/Former.types.ts index e36f874..ae6f37f 100644 --- a/src/Former/Former.types.ts +++ b/src/Former/Former.types.ts @@ -1,7 +1,21 @@ -import type { LoadingOverlayProps, ScrollAreaAutosizeProps } from '@mantine/core'; +import type { + ButtonProps, + GroupProps, + LoadingOverlayProps, + ScrollAreaAutosizeProps, +} from '@mantine/core'; import type { FieldValues, UseFormProps, UseFormReturn } from 'react-hook-form'; +export type FormerSectionRender = ( + children: React.ReactNode, + opened: boolean | undefined, + onClose: ((data?: T) => void) | undefined, + onOpen: ((data?: T) => void) | undefined, + getState: FormerState['getState'] +) => React.ReactNode; + export interface FormerProps { + id?: string; afterGet?: (data: T) => Promise | void; afterSave?: (data: T) => Promise | void; apiKeyField?: string; @@ -25,13 +39,17 @@ export interface FormerProps { request: RequestType; useFormProps?: UseFormProps; values?: T; - wrapper?: ( - children: React.ReactNode, - opened: boolean | undefined, - onClose: ((data?: T) => void) | undefined, - onOpen: ((data?: T) => void) | undefined, - getState: >(key: K) => FormStateAndProps[K] - ) => React.ReactNode; + wrapper?: FormerSectionRender; + + layout?: { + renderTop?: FormerSectionRender; + renderBottom?: FormerSectionRender; + saveButtonProps?: ButtonProps; + closeButtonProps?: ButtonProps; + buttonOnTop?: boolean; + buttonAreaGroupProps?: GroupProps; + title?: string; + }; } export interface FormerRef { diff --git a/src/Former/FormerButtonArea.tsx b/src/Former/FormerButtonArea.tsx new file mode 100644 index 0000000..f91d8d4 --- /dev/null +++ b/src/Former/FormerButtonArea.tsx @@ -0,0 +1,46 @@ +import { Group, Button } from '@mantine/core'; +import { IconX, IconDeviceFloppy } from '@tabler/icons-react'; +import { useFormerStore } from './Former.store'; + +export const FormerButtonArea = () => { + const { save, onClose, buttonAreaGroupProps } = useFormerStore((state) => ({ + save: state.save, + onClose: state.onClose, + buttonAreaGroupProps: state.layout?.buttonAreaGroupProps, + })); + + return ( + + + {typeof onClose === 'function' && ( + + )} + + + + ); +}; diff --git a/src/Former/FormerLayout.tsx b/src/Former/FormerLayout.tsx index 6b76f46..4452b93 100644 --- a/src/Former/FormerLayout.tsx +++ b/src/Former/FormerLayout.tsx @@ -2,6 +2,8 @@ import { LoadingOverlay, ScrollAreaAutosize } from '@mantine/core'; import { type PropsWithChildren, useEffect } from 'react'; import { useFormerStore } from './Former.store'; +import { FormerLayoutBottom } from './FormerLayoutBottom'; +import { FormerLayoutTop } from './FormerLayoutTop'; export const FormerLayout = (props: PropsWithChildren) => { const { @@ -14,6 +16,9 @@ export const FormerLayout = (props: PropsWithChildren) => { reset, save, scrollAreaProps, + id, + layout, + getState, } = useFormerStore((state) => ({ disableHTMlForm: state.disableHTMlForm, getFormMethods: state.getFormMethods, @@ -24,6 +29,9 @@ export const FormerLayout = (props: PropsWithChildren) => { reset: state.reset, save: state.save, scrollAreaProps: state.scrollAreaProps, + id: state.id, + layout: state.layout, + getState: state.getState, })); useEffect(() => { @@ -35,8 +43,9 @@ export const FormerLayout = (props: PropsWithChildren) => { } }, [getFormMethods, request]); - if (disableHTMlForm) { - return ( + return ( + <> + { {...scrollAreaProps} style={{ height: '100%', - maxHeight: '89vh', padding: '0.25rem', width: '100%', ...scrollAreaProps?.style, }} > - {props.children} + {disableHTMlForm ? ( +
+ {props.children} +
+ ) : ( +
reset(e)} + onSubmit={(e) => save(e)} + x-data-request={request} + > + {props.children} +
+ )} + { visible={loading} />
- ); - } - - return ( - -
reset(e)} onSubmit={(e) => save(e)}> - {props.children} - - -
+ + ); }; diff --git a/src/Former/FormerLayoutBottom.tsx b/src/Former/FormerLayoutBottom.tsx new file mode 100644 index 0000000..76d9b75 --- /dev/null +++ b/src/Former/FormerLayoutBottom.tsx @@ -0,0 +1,23 @@ +import { useFormerStore } from './Former.store'; +import { FormerButtonArea } from './FormerButtonArea'; + +export const FormerLayoutBottom = () => { + const { renderBottom, getState, opened, buttonOnTop } = useFormerStore((state) => ({ + renderBottom: state.layout?.renderBottom, + buttonOnTop: state.layout?.buttonOnTop, + getState: state.getState, + opened: state.opened, + })); + + if (renderBottom) { + return renderBottom( + , + opened, + getState('onClose'), + getState('onOpen'), + getState + ); + } + + return buttonOnTop ? <> : ; +}; diff --git a/src/Former/FormerLayoutTop.tsx b/src/Former/FormerLayoutTop.tsx new file mode 100644 index 0000000..a51ffa2 --- /dev/null +++ b/src/Former/FormerLayoutTop.tsx @@ -0,0 +1,22 @@ +import { useFormerStore } from './Former.store'; +import { FormerButtonArea } from './FormerButtonArea'; + +export const FormerLayoutTop = () => { + const { renderTop, getState, opened, buttonOnTop } = useFormerStore((state) => ({ + renderTop: state.layout?.renderTop, + buttonOnTop: state.layout?.buttonOnTop, + getState: state.getState, + opened: state.opened, + })); + + if (renderTop) { + return renderTop( + , + opened, + getState('onClose'), + getState('onOpen'), + getState + ); + } + return buttonOnTop ? : <>; +}; diff --git a/src/Former/stories/example.tsx b/src/Former/stories/example.tsx index 2cf9316..68d5fda 100644 --- a/src/Former/stories/example.tsx +++ b/src/Former/stories/example.tsx @@ -2,13 +2,20 @@ import { Button, Drawer, Group, Paper, Select, Stack, Switch } from '@mantine/co import { useRef, useState } from 'react'; import { Controller } from 'react-hook-form'; -import type { FormerRef } from '../Former.types'; +import type { FormerProps, FormerRef } from '../Former.types'; import { Former } from '../Former'; export const FormTest = () => { const [request, setRequest] = useState('insert'); const [wrapped, setWrapped] = useState(false); + const [disableHTML, setDisableHTML] = useState(false); + const [layout, setLayout] = useState({ + buttonOnTop: false, + title: 'Custom Former Title', + buttonAreaGroupProps: { justify: 'center' }, + } as FormerProps['layout']); + const [open, setOpen] = useState(false); const [formData, setFormData] = useState({ a: 99 }); console.log('formData', formData); @@ -16,16 +23,28 @@ export const FormTest = () => { const ref = useRef(null); return ( - + setWrapped(event.currentTarget.checked)} + /> + setDisableHTML(event.currentTarget.checked)} + /> + setLayout({ ...layout, buttonOnTop: event.currentTarget.checked })} + /> + - - + {!disableHTML && ( + + + + + )} diff --git a/src/Former/todo.md b/src/Former/todo.md index 45dcdbb..ed5c59c 100644 --- a/src/Former/todo.md +++ b/src/Former/todo.md @@ -1,12 +1,12 @@ -- [ ] Wrapper must receive button areas etc. Better scroll areas. +- [x] Wrapper must receive button areas etc. Better scroll areas. - [ ] Predefined wrappers (Model,Dialog,notification,popover) - [ ] Headerspec API - [ ] Relspec API - [ ] SocketSpec API -- [ ] Layout Tool - - [ ] Header Section - - [ ] Button Section - - [ ] Footer Section +- [x] Layout Tool + - [x] Header Section + - [x] Button Section + - [x] Footer Section - [ ] Different Loaded for saving vs loading - [ ] Better Confirm Dialog - [ ] Reset Confirm Dialog