diff --git a/packages/haring-react-shared/src/helpers/utilities.ts b/packages/haring-react-shared/src/helpers/utilities.ts index 0197ea01..303a5911 100644 --- a/packages/haring-react-shared/src/helpers/utilities.ts +++ b/packages/haring-react-shared/src/helpers/utilities.ts @@ -11,3 +11,30 @@ export function isNotNullNorEmpty( export function isCallback(maybeFunc: T | U): maybeFunc is T { return typeof maybeFunc === 'function'; } + +export function isObject(value: unknown): value is object { + return Boolean(value && typeof value === 'object' && !Array.isArray(value)); +} + +export function findNestedObject( + object: object, + keyToMatch: string, + valueToMatch: string, +): object | null { + if (isObject(object)) { + const entries = Object.entries(object); + for (const element of entries) { + const [objectKey, objectValue] = element; + if (objectKey === keyToMatch && objectValue && valueToMatch) { + return object; + } + if (isObject(objectValue)) { + const child = findNestedObject(objectValue, keyToMatch, valueToMatch); + if (child !== null) { + return child; + } + } + } + } + return null; +} diff --git a/packages/haring-react-shared/src/index.tsx b/packages/haring-react-shared/src/index.tsx index 10d7ac72..14c66602 100644 --- a/packages/haring-react-shared/src/index.tsx +++ b/packages/haring-react-shared/src/index.tsx @@ -22,6 +22,8 @@ export { createThemes, isCallback, isNotNullNorEmpty, + isObject, + findNestedObject, typeGuard, typeGuardInterface, } from './helpers'; diff --git a/packages/haring-react/src/Form/DynamicZone/DynamicZone.tsx b/packages/haring-react/src/Form/DynamicZone/DynamicZone.tsx index 4a937af8..5845e30d 100644 --- a/packages/haring-react/src/Form/DynamicZone/DynamicZone.tsx +++ b/packages/haring-react/src/Form/DynamicZone/DynamicZone.tsx @@ -67,8 +67,16 @@ export function DynamicZone(props: IDynamicZoneProps): ReactElement { {...block.blockCardProps} key={block.id} actions={block.blockActions} - footerChildren={block.blockFooter} - headerChildren={block.blockHeader} + footerChildren={ + typeof block.blockFooter === 'function' + ? block.blockFooter(block, index) + : block.blockFooter + } + headerChildren={ + typeof block.blockHeader === 'function' + ? block.blockHeader(block, index) + : block.blockHeader + } internalComponentProps={internalBlockComponentProps} onToggle={(opened) => onToggleBlock(block, index, opened)} opened={block.opened} diff --git a/packages/haring-react/src/types/dynamic-zone.ts b/packages/haring-react/src/types/dynamic-zone.ts index 3dd4dc57..6c9dae17 100644 --- a/packages/haring-react/src/types/dynamic-zone.ts +++ b/packages/haring-react/src/types/dynamic-zone.ts @@ -17,8 +17,12 @@ export interface IBaseBlock extends Record { export interface IBaseBlockCardOptions { blockActions?: IAction[]; blockCardProps?: CardProps; - blockFooter?: ReactNode; - blockHeader?: ReactNode; + blockFooter?: + | ReactNode + | ((block: IBaseBlockFull, index: number) => ReactNode); + blockHeader?: + | ReactNode + | ((block: IBaseBlockFull, index: number) => ReactNode); } export type IBaseBlockFull = Block & diff --git a/packages/storybook-pages/src/Form/ReactHookFormDynamicZone/ReactHookFormDynamicZone.mock.tsx b/packages/storybook-pages/src/Form/ReactHookFormDynamicZone/ReactHookFormDynamicZone.mock.tsx new file mode 100644 index 00000000..02b9244b --- /dev/null +++ b/packages/storybook-pages/src/Form/ReactHookFormDynamicZone/ReactHookFormDynamicZone.mock.tsx @@ -0,0 +1,10 @@ +import type { ReactElement } from 'react'; + +export function CommonFormErrorText(props: { message: string }): ReactElement { + const { message } = props; + return ( + + Error: {message} + + ); +} diff --git a/packages/storybook-pages/src/Form/ReactHookFormDynamicZone/ReactHookFormDynamicZone.tsx b/packages/storybook-pages/src/Form/ReactHookFormDynamicZone/ReactHookFormDynamicZone.tsx index bf94e0ea..e2554096 100644 --- a/packages/storybook-pages/src/Form/ReactHookFormDynamicZone/ReactHookFormDynamicZone.tsx +++ b/packages/storybook-pages/src/Form/ReactHookFormDynamicZone/ReactHookFormDynamicZone.tsx @@ -6,7 +6,9 @@ import type { SubmitErrorHandler, SubmitHandler, } from 'react-hook-form'; +import type { UseFormRegister } from 'react-hook-form/dist/types/form'; +import { ErrorMessage } from '@hookform/error-message'; import { Box, Group, Stack } from '@mantine/core'; import { Cube, Leaf } from '@phosphor-icons/react'; import { FormDynamicZone } from '@smile/haring-react'; @@ -14,6 +16,8 @@ import { useFieldArray, useForm } from 'react-hook-form'; import { withExceptionCapturing } from '../utilities/react-hook-form-utilities'; +import { CommonFormErrorText } from './ReactHookFormDynamicZone.mock'; + interface IContentA extends IBaseBlock { value: string; } @@ -39,6 +43,117 @@ const initialBlocks: IDynamicContents[] = [ }, ]; +const availableBlock: ( + register: UseFormRegister, + errors: object, +) => IFormDynamicZoneBlock[] = (register, errors) => [ + { + block: { + blockType: 'exampleA', + opened: true, + value: '', + }, + blockButtonOptions: { + blockType: 'exampleA', + label: 'Example A', + leftSection: , + }, + blockCardOptions: { + blockFooter: (_b, i) => ( + + ( + + )} + /> + ( + + )} + /> + + ), + blockHeader: ( + <> + + Example A + + ), + }, + renderFunc: (b: IExampleBlock, i: number): ReactElement => { + return ( + + + + + ); + }, + }, + { + block: { + blockType: 'exampleB', + opened: true, + selected: '', + }, + blockButtonOptions: { + blockType: 'exampleB', + label: 'Example B', + leftSection: , + }, + blockCardOptions: { + blockFooter: (_b, i) => ( + ( + + )} + /> + ), + blockHeader: ( + <> + + Example B + + ), + }, + renderFunc: (b: IExampleBlock, i: number): ReactElement => { + return ( + + ); + }, + }, +]; + interface IFields { content: IDynamicContents[]; email: string; @@ -59,8 +174,7 @@ export function ReactHookFormDynamicZone( handleSubmit, register, getValues, - watch, - // formState: { errors }, + formState: { errors }, } = useForm({ defaultValues: { content: initialBlocks, @@ -81,94 +195,6 @@ export function ReactHookFormDynamicZone( update(index, { ...updatedBlock, opened }); } - const availableBlocks: IFormDynamicZoneBlock[] = [ - { - block: { - blockType: 'exampleA', - opened: true, - value: '', - }, - blockButtonOptions: { - blockType: 'exampleA', - label: 'Example A', - leftSection: , - }, - blockCardOptions: { - blockHeader: ( - <> - - Example A - - ), - }, - renderFunc: (b: IExampleBlock, i: number): ReactElement => { - return ( - - - - - ); - }, - }, - { - block: { - blockType: 'exampleB', - opened: true, - selected: '', - }, - blockButtonOptions: { - blockType: 'exampleB', - label: 'Example B', - leftSection: , - }, - blockCardOptions: { - blockHeader: ( - <> - - Example B - - ), - }, - renderFunc: (b: IExampleBlock, i: number): ReactElement => { - return ( - - ); - }, - }, - ]; - - console.log('watch', watch('content')); - // TODO: everything important seems to work, now test default values (first in the sense of giving an existing array of blocks with existing values, - // feeding it into the form defaultValues and sending it down into the dynamic zone, - // then maybe some way to give default values on register?, - // then test error display and various complex use cases, - // then maybe test animations - return (
- availableBlocks={availableBlocks} + availableBlocks={availableBlock(register, errors)} blocksArray={fields} onAppendUpdate={(newBlock: IDynamicContents) => append(newBlock)} onRemoveUpdate={(i: number) => remove(i)} @@ -186,16 +212,6 @@ export function ReactHookFormDynamicZone( onToggleUpdate={onToggle} /> - {/* */} - {/* messages &&*/} - {/* Object.entries(messages).map(([type, message]) => (*/} - {/*

{message}

*/} - {/* ))*/} - {/* }*/} - {/*/ >*/}