mirror of
https://gitee.com/ByteDance/flowgram.ai.git
synced 2025-07-07 17:43:29 +08:00
doc: complete node form basic example
This commit is contained in:
parent
493d3084f5
commit
8027436e06
@ -49,7 +49,7 @@
|
|||||||
"@types/node": "^18",
|
"@types/node": "^18",
|
||||||
"@types/react": "^18",
|
"@types/react": "^18",
|
||||||
"@types/react-dom": "^18",
|
"@types/react-dom": "^18",
|
||||||
"@types/styled-components": "^5",
|
"styled-components": "^5",
|
||||||
"@typescript-eslint/parser": "^6.10.0",
|
"@typescript-eslint/parser": "^6.10.0",
|
||||||
"eslint": "^8.54.0",
|
"eslint": "^8.54.0",
|
||||||
"less": "^4.1.2",
|
"less": "^4.1.2",
|
||||||
|
|||||||
@ -34,7 +34,8 @@
|
|||||||
"@flowgram.ai/free-snap-plugin": "workspace:*",
|
"@flowgram.ai/free-snap-plugin": "workspace:*",
|
||||||
"@flowgram.ai/minimap-plugin": "workspace:*",
|
"@flowgram.ai/minimap-plugin": "workspace:*",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18"
|
"react-dom": "^18",
|
||||||
|
"styled-components": "^5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@flowgram.ai/ts-config": "workspace:*",
|
"@flowgram.ai/ts-config": "workspace:*",
|
||||||
|
|||||||
5
apps/demo-node-form/src/components/field-title.tsx
Normal file
5
apps/demo-node-form/src/components/field-title.tsx
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
export const FieldTitle = styled.div`
|
||||||
|
padding-bottom: 4px;
|
||||||
|
`;
|
||||||
18
apps/demo-node-form/src/components/field-wrapper.css
Normal file
18
apps/demo-node-form/src/components/field-wrapper.css
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
.error-message {
|
||||||
|
color: #f5222d !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.required {
|
||||||
|
color: #f5222d !important;
|
||||||
|
padding-left: 4px
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-title {
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
21
apps/demo-node-form/src/components/field-wrapper.tsx
Normal file
21
apps/demo-node-form/src/components/field-wrapper.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import './field-wrapper.css';
|
||||||
|
|
||||||
|
interface FieldWrapperProps {
|
||||||
|
required?: boolean;
|
||||||
|
title: string;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FieldWrapper = ({ required, title, children, error }: FieldWrapperProps) => (
|
||||||
|
<div className="field-wrapper">
|
||||||
|
<div className="field-title">
|
||||||
|
{title}
|
||||||
|
{required ? <span className="required">*</span> : null}
|
||||||
|
</div>
|
||||||
|
{children}
|
||||||
|
<p className="error-message">{error}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
2
apps/demo-node-form/src/components/index.ts
Normal file
2
apps/demo-node-form/src/components/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export { FieldTitle } from './field-title';
|
||||||
|
export { FieldWrapper } from './field-wrapper';
|
||||||
41
apps/demo-node-form/src/constant.ts
Normal file
41
apps/demo-node-form/src/constant.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
export const fieldWrapperTs = `import React from 'react';
|
||||||
|
|
||||||
|
import './index.css';
|
||||||
|
|
||||||
|
interface FieldWrapperProps {
|
||||||
|
required?: boolean;
|
||||||
|
title: string;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FieldWrapper = ({ required, title, children, error }: FieldWrapperProps) => (
|
||||||
|
<div className="field-wrapper">
|
||||||
|
<div className="field-title">
|
||||||
|
{title}
|
||||||
|
{required ? <span className="required">*</span> : null}
|
||||||
|
</div>
|
||||||
|
{children}
|
||||||
|
<p className="error-message">{error}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const fieldWrapperCss = `.error-message {
|
||||||
|
color: #f5222d !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.required {
|
||||||
|
color: #f5222d !important;
|
||||||
|
padding-left: 4px
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-title {
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
51
apps/demo-node-form/src/form-meta.tsx
Normal file
51
apps/demo-node-form/src/form-meta.tsx
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import {
|
||||||
|
Field,
|
||||||
|
FieldRenderProps,
|
||||||
|
FormMeta,
|
||||||
|
ValidateTrigger,
|
||||||
|
} from '@flowgram.ai/free-layout-editor';
|
||||||
|
import { Input } from '@douyinfe/semi-ui';
|
||||||
|
|
||||||
|
// FieldWrapper is not provided by sdk, and can be customized
|
||||||
|
import { FieldWrapper } from './components';
|
||||||
|
|
||||||
|
const render = () => (
|
||||||
|
<div className="demo-node-content">
|
||||||
|
<div className="demo-node-title">Basic Node</div>
|
||||||
|
<Field name="name">
|
||||||
|
{({ field, fieldState }: FieldRenderProps<string>) => (
|
||||||
|
<FieldWrapper required title="Name" error={fieldState.errors?.[0]?.message}>
|
||||||
|
<Input size={'small'} {...field} />
|
||||||
|
</FieldWrapper>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
<Field name="city">
|
||||||
|
{({ field, fieldState }: FieldRenderProps<string>) => (
|
||||||
|
<FieldWrapper required title="City" error={fieldState.errors?.[0]?.message}>
|
||||||
|
<Input size={'small'} {...field} />
|
||||||
|
</FieldWrapper>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const formMeta: FormMeta = {
|
||||||
|
render,
|
||||||
|
defaultValues: { name: 'Tina', city: 'Hangzhou' },
|
||||||
|
validateTrigger: ValidateTrigger.onChange,
|
||||||
|
validate: {
|
||||||
|
name: ({ value }) => {
|
||||||
|
if (!value) {
|
||||||
|
return 'Name is required';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
city: ({ value }) => {
|
||||||
|
if (!value) {
|
||||||
|
return 'City is required';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DEFAULT_FORM_META = formMeta;
|
||||||
@ -11,11 +11,17 @@
|
|||||||
box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.1);
|
box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.demo-free-node-title {
|
.demo-node-content {
|
||||||
background-color: #93bfe2;
|
padding: 8px 12px;
|
||||||
|
flex-grow: 1;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-radius: 8px 8px 0 0;
|
}
|
||||||
padding: 4px 12px;
|
|
||||||
|
.demo-node-title {
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 14px;
|
||||||
|
width: 100%;
|
||||||
|
margin: 4px 0px 12px 0px;
|
||||||
}
|
}
|
||||||
.demo-free-node-content {
|
.demo-free-node-content {
|
||||||
padding: 4px 12px;
|
padding: 4px 12px;
|
||||||
|
|||||||
@ -1 +1,6 @@
|
|||||||
export { Editor } from './editor';
|
export { Editor } from './editor';
|
||||||
|
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';
|
||||||
|
|||||||
@ -8,10 +8,6 @@ export const DEFAULT_INITIAL_DATA: WorkflowJSON = {
|
|||||||
meta: {
|
meta: {
|
||||||
position: { x: 400, y: 0 },
|
position: { x: 400, y: 0 },
|
||||||
},
|
},
|
||||||
data: {
|
|
||||||
title: 'Custom',
|
|
||||||
content: 'Custom node content',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
edges: [],
|
edges: [],
|
||||||
|
|||||||
@ -1,23 +1,10 @@
|
|||||||
import { WorkflowNodeRegistry, Field } from '@flowgram.ai/free-layout-editor';
|
import { WorkflowNodeRegistry } from '@flowgram.ai/free-layout-editor';
|
||||||
import { Input, TextArea } from '@douyinfe/semi-ui';
|
|
||||||
|
import { DEFAULT_FORM_META } from './form-meta';
|
||||||
|
|
||||||
export const DEFAULT_DEMO_REGISTRY: WorkflowNodeRegistry = {
|
export const DEFAULT_DEMO_REGISTRY: WorkflowNodeRegistry = {
|
||||||
type: 'custom',
|
type: 'custom',
|
||||||
meta: {},
|
meta: {},
|
||||||
defaultPorts: [{ type: 'output' }, { type: 'input' }],
|
defaultPorts: [{ type: 'output' }, { type: 'input' }],
|
||||||
formMeta: {
|
formMeta: DEFAULT_FORM_META,
|
||||||
render: () => (
|
|
||||||
<div>
|
|
||||||
<div>Basic Node</div>
|
|
||||||
<p>name</p>
|
|
||||||
<Field name="name">
|
|
||||||
<Input />
|
|
||||||
</Field>
|
|
||||||
<p>city</p>
|
|
||||||
<Field name="city">
|
|
||||||
<Input />
|
|
||||||
</Field>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,42 +1,104 @@
|
|||||||
|
import {
|
||||||
|
DEFAULT_DEMO_REGISTRY,
|
||||||
|
DEFAULT_INITIAL_DATA,
|
||||||
|
fieldWrapperCss,
|
||||||
|
fieldWrapperTs,
|
||||||
|
} from '@flowgram.ai/demo-node-form';
|
||||||
|
|
||||||
import { PreviewEditor } from '../preview-editor';
|
import { PreviewEditor } from '../preview-editor';
|
||||||
import { Editor } from '.';
|
import { Editor } from '.';
|
||||||
|
|
||||||
const indexCode = {
|
const registryCode = {
|
||||||
code: `import {
|
code: `import {
|
||||||
EditorRenderer,
|
Field,
|
||||||
FreeLayoutEditorProvider,
|
FieldRenderProps,
|
||||||
|
FormMeta,
|
||||||
|
ValidateTrigger,
|
||||||
} from '@flowgram.ai/free-layout-editor';
|
} from '@flowgram.ai/free-layout-editor';
|
||||||
|
import { Input } from '@douyinfe/semi-ui';
|
||||||
|
|
||||||
import { useEditorProps } from './hooks/use-editor-props'
|
// FieldWrapper is not provided by sdk, it can be customized
|
||||||
import '@flowgram.ai/free-layout-editor/index.css';
|
import { FieldWrapper } from './components';
|
||||||
import './index.css';
|
|
||||||
|
|
||||||
export const App = () => {
|
const render = () => (
|
||||||
const editorProps = useEditorProps()
|
<div className="demo-node-content">
|
||||||
return (
|
<div className="demo-node-title">Basic Node</div>
|
||||||
<FreeLayoutEditorProvider {...editorProps}>
|
<Field name="name">
|
||||||
<div className="demo-free-container">
|
{({ field, fieldState }: FieldRenderProps<string>) => (
|
||||||
<div className="demo-free-layout">
|
<FieldWrapper required title="Name" error={fieldState.errors?.[0]?.message}>
|
||||||
<NodeAddPanel />
|
<Input size={'small'} {...field} />
|
||||||
<EditorRenderer className="demo-free-editor" />
|
</FieldWrapper>
|
||||||
</div>
|
)}
|
||||||
<Tools />
|
</Field>
|
||||||
<Minimap />
|
|
||||||
</div>
|
<Field name="city">
|
||||||
</FreeLayoutEditorProvider>
|
{({ field, fieldState }: FieldRenderProps<string>) => (
|
||||||
)
|
<FieldWrapper required title="City" error={fieldState.errors?.[0]?.message}>
|
||||||
|
<Input size={'small'} {...field} />
|
||||||
|
</FieldWrapper>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const formMeta: FormMeta = {
|
||||||
|
render,
|
||||||
|
defaultValues: { name: 'Tina', city: 'Hangzhou' },
|
||||||
|
validateTrigger: ValidateTrigger.onChange,
|
||||||
|
validate: {
|
||||||
|
name: ({ value }) => {
|
||||||
|
if (!value) {
|
||||||
|
return 'Name is required';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
city: ({ value }) => {
|
||||||
|
if (!value) {
|
||||||
|
return 'City is required';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
`,
|
|
||||||
|
|
||||||
|
|
||||||
|
export const nodeRegistry: WorkflowNodeRegistry = {
|
||||||
|
type: 'custom',
|
||||||
|
meta: {},
|
||||||
|
defaultPorts: [{ type: 'output' }, { type: 'input' }],
|
||||||
|
formMeta
|
||||||
|
};
|
||||||
|
`,
|
||||||
|
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,
|
active: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NodeFormBasicPreview = () => {
|
export const NodeFormBasicPreview = () => {
|
||||||
const files = {
|
const files = {
|
||||||
'index.tsx': indexCode,
|
'node-registry.tsx': registryCode,
|
||||||
|
'initial-data.ts': initialDataCode,
|
||||||
|
'field-wrapper.tsx': { code: fieldWrapperTs, active: true },
|
||||||
|
'field-wrapper.css': { code: fieldWrapperCss, active: true },
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<PreviewEditor files={files} previewStyle={{ height: 500 }} editorStyle={{ height: 500 }}>
|
<PreviewEditor files={files} previewStyle={{ height: 500 }} editorStyle={{ height: 500 }}>
|
||||||
<Editor />
|
<Editor registry={DEFAULT_DEMO_REGISTRY} initialData={DEFAULT_INITIAL_DATA} />
|
||||||
</PreviewEditor>
|
</PreviewEditor>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
6
common/config/rush/pnpm-lock.yaml
generated
6
common/config/rush/pnpm-lock.yaml
generated
@ -245,9 +245,6 @@ importers:
|
|||||||
'@types/react-dom':
|
'@types/react-dom':
|
||||||
specifier: ^18
|
specifier: ^18
|
||||||
version: 18.3.5(@types/react@18.3.16)
|
version: 18.3.5(@types/react@18.3.16)
|
||||||
'@types/styled-components':
|
|
||||||
specifier: ^5
|
|
||||||
version: 5.1.34
|
|
||||||
'@typescript-eslint/parser':
|
'@typescript-eslint/parser':
|
||||||
specifier: ^6.10.0
|
specifier: ^6.10.0
|
||||||
version: 6.21.0(eslint@8.57.1)(typescript@5.0.4)
|
version: 6.21.0(eslint@8.57.1)(typescript@5.0.4)
|
||||||
@ -333,6 +330,9 @@ importers:
|
|||||||
react-dom:
|
react-dom:
|
||||||
specifier: ^18
|
specifier: ^18
|
||||||
version: 18.3.1(react@18.3.1)
|
version: 18.3.1(react@18.3.1)
|
||||||
|
styled-components:
|
||||||
|
specifier: ^5
|
||||||
|
version: 5.3.11(@babel/core@7.26.0)(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@flowgram.ai/eslint-config':
|
'@flowgram.ai/eslint-config':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user