Quellcode durchsuchen

doc: complete node form basic example

YuanHeDx vor 10 Monaten
Ursprung
Commit
8027436e06

+ 1 - 1
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",

+ 2 - 1
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:*",

+ 5 - 0
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;
+`;

+ 18 - 0
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;
+}
+

+ 21 - 0
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) => (
+  <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 - 0
apps/demo-node-form/src/components/index.ts

@@ -0,0 +1,2 @@
+export { FieldTitle } from './field-title';
+export { FieldWrapper } from './field-wrapper';

+ 41 - 0
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) => (
+  <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 - 0
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 = () => (
+  <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;

+ 10 - 4
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;

+ 5 - 0
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';

+ 0 - 4
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: [],

+ 4 - 17
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: () => (
-      <div>
-        <div>Basic Node</div>
-        <p>name</p>
-        <Field name="name">
-          <Input />
-        </Field>
-        <p>city</p>
-        <Field name="city">
-          <Input />
-        </Field>
-      </div>
-    ),
-  },
+  formMeta: DEFAULT_FORM_META,
 };

+ 85 - 23
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 (
-    <FreeLayoutEditorProvider {...editorProps}>
-      <div className="demo-free-container">
-        <div className="demo-free-layout">
-          <NodeAddPanel />
-          <EditorRenderer className="demo-free-editor" />
-        </div>
-        <Tools />
-        <Minimap />
-      </div>
-    </FreeLayoutEditorProvider>
-  )
+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 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 (
     <PreviewEditor files={files} previewStyle={{ height: 500 }} editorStyle={{ height: 500 }}>
-      <Editor />
+      <Editor registry={DEFAULT_DEMO_REGISTRY} initialData={DEFAULT_INITIAL_DATA} />
     </PreviewEditor>
   );
 };

+ 3 - 3
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:*