diff --git a/apps/demo-free-layout/package.json b/apps/demo-free-layout/package.json
index de73eb02..d02ffcfd 100644
--- a/apps/demo-free-layout/package.json
+++ b/apps/demo-free-layout/package.json
@@ -49,7 +49,7 @@
"@types/node": "^18",
"@types/react": "^18",
"@types/react-dom": "^18",
- "@types/styled-components": "^5",
+ "styled-components": "^5",
"@typescript-eslint/parser": "^6.10.0",
"eslint": "^8.54.0",
"less": "^4.1.2",
diff --git a/apps/demo-node-form/package.json b/apps/demo-node-form/package.json
index b582fb1b..f4e53eb2 100644
--- a/apps/demo-node-form/package.json
+++ b/apps/demo-node-form/package.json
@@ -34,7 +34,8 @@
"@flowgram.ai/free-snap-plugin": "workspace:*",
"@flowgram.ai/minimap-plugin": "workspace:*",
"react": "^18",
- "react-dom": "^18"
+ "react-dom": "^18",
+ "styled-components": "^5"
},
"devDependencies": {
"@flowgram.ai/ts-config": "workspace:*",
diff --git a/apps/demo-node-form/src/components/field-title.tsx b/apps/demo-node-form/src/components/field-title.tsx
new file mode 100644
index 00000000..3bc680d9
--- /dev/null
+++ b/apps/demo-node-form/src/components/field-title.tsx
@@ -0,0 +1,5 @@
+import styled from 'styled-components';
+
+export const FieldTitle = styled.div`
+ padding-bottom: 4px;
+`;
diff --git a/apps/demo-node-form/src/components/field-wrapper.css b/apps/demo-node-form/src/components/field-wrapper.css
new file mode 100644
index 00000000..57de555c
--- /dev/null
+++ b/apps/demo-node-form/src/components/field-wrapper.css
@@ -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;
+}
+
diff --git a/apps/demo-node-form/src/components/field-wrapper.tsx b/apps/demo-node-form/src/components/field-wrapper.tsx
new file mode 100644
index 00000000..7e26c9e7
--- /dev/null
+++ b/apps/demo-node-form/src/components/field-wrapper.tsx
@@ -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) => (
+
+
+ {title}
+ {required ? * : null}
+
+ {children}
+
{error}
+
+);
diff --git a/apps/demo-node-form/src/components/index.ts b/apps/demo-node-form/src/components/index.ts
new file mode 100644
index 00000000..f04829e6
--- /dev/null
+++ b/apps/demo-node-form/src/components/index.ts
@@ -0,0 +1,2 @@
+export { FieldTitle } from './field-title';
+export { FieldWrapper } from './field-wrapper';
diff --git a/apps/demo-node-form/src/constant.ts b/apps/demo-node-form/src/constant.ts
new file mode 100644
index 00000000..7cf14596
--- /dev/null
+++ b/apps/demo-node-form/src/constant.ts
@@ -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) => (
+
+
+ {title}
+ {required ? * : null}
+
+ {children}
+
{error}
+
+);
+`;
+
+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;
+}
+`;
diff --git a/apps/demo-node-form/src/form-meta.tsx b/apps/demo-node-form/src/form-meta.tsx
new file mode 100644
index 00000000..8cf213e0
--- /dev/null
+++ b/apps/demo-node-form/src/form-meta.tsx
@@ -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 = () => (
+
+);
+
+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;
diff --git a/apps/demo-node-form/src/index.css b/apps/demo-node-form/src/index.css
index c76cebfb..15728111 100644
--- a/apps/demo-node-form/src/index.css
+++ b/apps/demo-node-form/src/index.css
@@ -11,11 +11,17 @@
box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.1);
}
-.demo-free-node-title {
- background-color: #93bfe2;
+.demo-node-content {
+ padding: 8px 12px;
+ flex-grow: 1;
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 {
padding: 4px 12px;
diff --git a/apps/demo-node-form/src/index.tsx b/apps/demo-node-form/src/index.tsx
index 2f6832bf..f99f9f87 100644
--- a/apps/demo-node-form/src/index.tsx
+++ b/apps/demo-node-form/src/index.tsx
@@ -1 +1,6 @@
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';
diff --git a/apps/demo-node-form/src/initial-data.ts b/apps/demo-node-form/src/initial-data.ts
index b2e76742..0a0c8cdb 100644
--- a/apps/demo-node-form/src/initial-data.ts
+++ b/apps/demo-node-form/src/initial-data.ts
@@ -8,10 +8,6 @@ export const DEFAULT_INITIAL_DATA: WorkflowJSON = {
meta: {
position: { x: 400, y: 0 },
},
- data: {
- title: 'Custom',
- content: 'Custom node content',
- },
},
],
edges: [],
diff --git a/apps/demo-node-form/src/node-registries.tsx b/apps/demo-node-form/src/node-registries.tsx
index 21edbcbd..1afa4cab 100644
--- a/apps/demo-node-form/src/node-registries.tsx
+++ b/apps/demo-node-form/src/node-registries.tsx
@@ -1,23 +1,10 @@
-import { WorkflowNodeRegistry, Field } from '@flowgram.ai/free-layout-editor';
-import { Input, TextArea } from '@douyinfe/semi-ui';
+import { WorkflowNodeRegistry } from '@flowgram.ai/free-layout-editor';
+
+import { DEFAULT_FORM_META } from './form-meta';
export const DEFAULT_DEMO_REGISTRY: WorkflowNodeRegistry = {
type: 'custom',
meta: {},
defaultPorts: [{ type: 'output' }, { type: 'input' }],
- formMeta: {
- render: () => (
-
-
Basic Node
-
name
-
-
-
-
city
-
-
-
-
- ),
- },
+ formMeta: DEFAULT_FORM_META,
};
diff --git a/apps/docs/components/form-basic/preview.tsx b/apps/docs/components/form-basic/preview.tsx
index aa21182d..97742096 100644
--- a/apps/docs/components/form-basic/preview.tsx
+++ b/apps/docs/components/form-basic/preview.tsx
@@ -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 { Editor } from '.';
-const indexCode = {
+const registryCode = {
code: `import {
- EditorRenderer,
- FreeLayoutEditorProvider,
+ Field,
+ FieldRenderProps,
+ FormMeta,
+ ValidateTrigger,
} from '@flowgram.ai/free-layout-editor';
+import { Input } from '@douyinfe/semi-ui';
-import { useEditorProps } from './hooks/use-editor-props'
-import '@flowgram.ai/free-layout-editor/index.css';
-import './index.css';
+// FieldWrapper is not provided by sdk, it can be customized
+import { FieldWrapper } from './components';
-export const App = () => {
- const editorProps = useEditorProps()
- return (
-
-
-
- )
+const render = () => (
+
+);
+
+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,
};
export const NodeFormBasicPreview = () => {
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 (
-
+
);
};
diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml
index 51f69125..b612c91b 100644
--- a/common/config/rush/pnpm-lock.yaml
+++ b/common/config/rush/pnpm-lock.yaml
@@ -245,9 +245,6 @@ importers:
'@types/react-dom':
specifier: ^18
version: 18.3.5(@types/react@18.3.16)
- '@types/styled-components':
- specifier: ^5
- version: 5.1.34
'@typescript-eslint/parser':
specifier: ^6.10.0
version: 6.21.0(eslint@8.57.1)(typescript@5.0.4)
@@ -333,6 +330,9 @@ importers:
react-dom:
specifier: ^18
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:
'@flowgram.ai/eslint-config':
specifier: workspace:*