doc: add form effect examples

This commit is contained in:
YuanHeDx 2025-03-17 20:35:23 +08:00 committed by YuanHeDx
parent 8027436e06
commit b4450db944
15 changed files with 268 additions and 29 deletions

View File

@ -16,3 +16,9 @@
margin-bottom: 6px;
}
.field-note{
color: #a3a0a0 !important;
font-size: 12px;
margin: 6px 0;
}

View File

@ -7,15 +7,18 @@ interface FieldWrapperProps {
title: string;
children?: React.ReactNode;
error?: string;
note?: string;
}
export const FieldWrapper = ({ required, title, children, error }: FieldWrapperProps) => (
export const FieldWrapper = ({ required, title, children, error, note }: FieldWrapperProps) => (
<div className="field-wrapper">
<div className="field-title">
{title}
{note ? <p className="field-note">{note}</p> : null}
{required ? <span className="required">*</span> : null}
</div>
{children}
<p className="error-message">{error}</p>
{note ? <br /> : null}
</div>
);

View File

@ -1,22 +1,25 @@
export const fieldWrapperTs = `import React from 'react';
import './index.css';
import './field-wrapper.css';
interface FieldWrapperProps {
required?: boolean;
title: string;
children?: React.ReactNode;
error?: string;
note?: string;
}
export const FieldWrapper = ({ required, title, children, error }: FieldWrapperProps) => (
export const FieldWrapper = ({ required, title, children, error, note }: FieldWrapperProps) => (
<div className="field-wrapper">
<div className="field-title">
{title}
{note ? <p className="field-note">{note}</p> : null}
{required ? <span className="required">*</span> : null}
</div>
{children}
<p className="error-message">{error}</p>
{note ? <br /> : null}
</div>
);
`;
@ -38,4 +41,25 @@ export const fieldWrapperCss = `.error-message {
.field-title {
margin-bottom: 6px;
}
.field-note{
color: #a3a0a0 !important;
font-size: 12px;
margin: 6px 0;
}
`;
export const defaultInitialDataTs = `import { WorkflowJSON } from '@flowgram.ai/free-layout-editor';
export const DEFAULT_INITIAL_DATA: WorkflowJSON = {
nodes: [
{
id: 'node_0',
type: 'custom',
meta: {
position: { x: 400, y: 0 },
},
},
],
edges: [],
};`;

View File

@ -14,7 +14,7 @@ interface EditorProps {
}
export const Editor = ({ registry, initialData }: EditorProps) => {
const editorProps = useEditorProps({ registry, initialData });
const editorProps = useEditorProps({ registries: [registry], initialData });
return (
<FreeLayoutEditorProvider {...editorProps}>
<div className="demo-free-container">

View File

@ -3,4 +3,4 @@ export { FieldTitle, FieldWrapper } from './components';
export { DEFAULT_FORM_META } from './form-meta';
export { DEFAULT_DEMO_REGISTRY } from './node-registries';
export { DEFAULT_INITIAL_DATA } from './initial-data';
export { fieldWrapperTs, fieldWrapperCss } from './constant';
export { fieldWrapperTs, fieldWrapperCss, defaultInitialDataTs } from './constant';

View File

@ -5,4 +5,4 @@ export { FreeLayoutSimple } from './free-layout-simple';
export { FreeLayoutSimplePreview } from './free-layout-simple/preview';
export { FixedLayoutSimple } from './fixed-layout-simple';
export { FixedLayoutSimplePreview } from './fixed-layout-simple/preview';
export { NodeFormBasicPreview } from './form-basic/preview.tsx';
export { NodeFormBasicPreview, NodeFormEffectPreview } from './node-form';

View File

@ -1,12 +1,13 @@
import {
DEFAULT_DEMO_REGISTRY,
DEFAULT_INITIAL_DATA,
defaultInitialDataTs,
fieldWrapperCss,
fieldWrapperTs,
} from '@flowgram.ai/demo-node-form';
import { PreviewEditor } from '../preview-editor';
import { Editor } from '.';
import { Editor } from './editor';
const registryCode = {
code: `import {
@ -71,28 +72,10 @@ export const nodeRegistry: WorkflowNodeRegistry = {
active: true,
};
const initialDataCode = {
code: `import { WorkflowJSON } from '@flowgram.ai/free-layout-editor';
export const DEFAULT_INITIAL_DATA: WorkflowJSON = {
nodes: [
{
id: 'node_0',
type: 'custom',
meta: {
position: { x: 400, y: 0 },
},
},
],
edges: [],
};`,
active: true,
};
export const NodeFormBasicPreview = () => {
const files = {
'node-registry.tsx': registryCode,
'initial-data.ts': initialDataCode,
'initial-data.ts': { code: defaultInitialDataTs, active: true },
'field-wrapper.tsx': { code: fieldWrapperTs, active: true },
'field-wrapper.css': { code: fieldWrapperCss, active: true },
};

View File

@ -0,0 +1,82 @@
import {
DataEvent,
EffectProps,
Field,
FieldRenderProps,
FormMeta,
ValidateTrigger,
WorkflowNodeRegistry,
} from '@flowgram.ai/free-layout-editor';
import { FieldWrapper } from '@flowgram.ai/demo-node-form';
import { Input } from '@douyinfe/semi-ui';
import '../index.css';
const render = () => (
<div className="demo-node-content">
<div className="demo-node-title">Effect Examples</div>
<Field name="field1">
{({ field }: FieldRenderProps<string>) => (
<FieldWrapper
title="Basic effect"
note={'The following field will console.log field value on value change'}
>
<Input size={'small'} {...field} />
</FieldWrapper>
)}
</Field>
<Field name="field2">
{({ field }: FieldRenderProps<string>) => (
<FieldWrapper
title="Control other fields"
note={'The following field will change Field 3 value on value change'}
>
<Input size={'small'} {...field} />
</FieldWrapper>
)}
</Field>
<Field name="field3">
{({ field }: FieldRenderProps<string>) => (
<FieldWrapper title="Controlled by other fields">
<Input size={'small'} {...field} />
</FieldWrapper>
)}
</Field>
</div>
);
interface FormData {
field1: string;
field2: string;
field3: string;
}
const formMeta: FormMeta<FormData> = {
render,
validateTrigger: ValidateTrigger.onChange,
effect: {
field1: [
{
event: DataEvent.onValueChange,
effect: ({ value }: EffectProps<string, FormData>) => {
console.log('field1 value:', value);
},
},
],
field2: [
{
event: DataEvent.onValueChange,
effect: ({ value, form }: EffectProps<string, FormData>) => {
form.setValueIn('field3', 'field2 value is ' + value);
},
},
],
},
};
export const nodeRegistry: WorkflowNodeRegistry = {
type: 'custom',
meta: {},
defaultPorts: [{ type: 'output' }, { type: 'input' }],
formMeta,
};

View File

@ -0,0 +1,112 @@
import {
DEFAULT_INITIAL_DATA,
defaultInitialDataTs,
fieldWrapperCss,
fieldWrapperTs,
} from '@flowgram.ai/demo-node-form';
import { Editor } from '../editor.tsx';
import { PreviewEditor } from '../../preview-editor.tsx';
import { nodeRegistry } from './node-registry.tsx';
const nodeRegistryFile = {
code: `import {
DataEvent,
EffectProps,
Field,
FieldRenderProps,
FormMeta,
ValidateTrigger,
WorkflowNodeRegistry,
} from '@flowgram.ai/free-layout-editor';
import { FieldWrapper } from '@flowgram.ai/demo-node-form';
import { Input } from '@douyinfe/semi-ui';
import '../index.css';
const render = () => (
<div className="demo-node-content">
<div className="demo-node-title">Effect Examples</div>
<Field name="field1">
{({ field }: FieldRenderProps<string>) => (
<FieldWrapper
title="Basic effect"
note={'The following field will console.log field value on value change'}
>
<Input size={'small'} {...field} />
</FieldWrapper>
)}
</Field>
<Field name="field2">
{({ field }: FieldRenderProps<string>) => (
<FieldWrapper
title="Control other fields"
note={'The following field will change Field 3 value on value change'}
>
<Input size={'small'} {...field} />
</FieldWrapper>
)}
</Field>
<Field name="field3">
{({ field }: FieldRenderProps<string>) => (
<FieldWrapper title="Controlled by other fields">
<Input size={'small'} {...field} />
</FieldWrapper>
)}
</Field>
</div>
);
interface FormData {
field1: string;
field2: string;
field3: string;
}
const formMeta: FormMeta<FormData> = {
render,
validateTrigger: ValidateTrigger.onChange,
effect: {
field1: [
{
event: DataEvent.onValueChange,
effect: ({ value }: EffectProps<string, FormData>) => {
console.log('field1 value:', value);
},
},
],
field2: [
{
event: DataEvent.onValueChange,
effect: ({ value, form }: EffectProps<string, FormData>) => {
form.setValueIn('field3', 'field2 value is ' + value);
},
},
],
},
};
export const nodeRegistry: WorkflowNodeRegistry = {
type: 'custom',
meta: {},
defaultPorts: [{ type: 'output' }, { type: 'input' }],
formMeta,
};
`,
active: true,
};
export const NodeFormEffectPreview = () => {
const files = {
'node-registry.tsx': nodeRegistryFile,
'initial-data.ts': { code: defaultInitialDataTs, active: true },
'field-wrapper.tsx': { code: fieldWrapperTs, active: true },
'field-wrapper.css': { code: fieldWrapperCss, active: true },
};
return (
<PreviewEditor files={files} previewStyle={{ height: 500 }} editorStyle={{ height: 500 }}>
<Editor registry={nodeRegistry} initialData={DEFAULT_INITIAL_DATA} />
</PreviewEditor>
);
};

View File

@ -0,0 +1,12 @@
.demo-node-content {
padding: 8px 12px;
flex-grow: 1;
width: 100%;
}
.demo-node-title {
font-weight: 500;
font-size: 14px;
width: 100%;
margin: 4px 0px 12px 0px;
}

View File

@ -0,0 +1,2 @@
export { NodeFormBasicPreview } from './basic-preview';
export { NodeFormEffectPreview } from './effect/preview';

View File

@ -1,3 +1,4 @@
[
"basic"
"basic",
"effect"
]

View File

@ -0,0 +1,10 @@
---
outline: false
---
# 表单项变更副作用 ( effect)
import { NodeFormEffectPreview } from '../../../../components';
<NodeFormEffectPreview />

View File

@ -51,14 +51,18 @@ export enum DataEvent {
export type EffectReturn = () => void;
export type Effect<TFieldValue = any, TFormValues = any> = (props: {
export interface EffectProps<TFieldValue = any, TFormValues = any> {
name: FieldName;
value: TFieldValue;
prevValue?: TFieldValue;
formValues: TFormValues;
form: IForm;
context: NodeContext;
}) => void | EffectReturn;
}
export type Effect<TFieldValue = any, TFormValues = any> = (
props: EffectProps<TFieldValue, TFormValues>
) => void | EffectReturn;
export type ArrayAppendEffect<TFieldValue = any, TFormValues = any> = (props: {
index: number;