feat: add customizable port colors to WorkflowPortRender (#360)

* feat(workflow-port): add customizable port colors

* fix(workflow-colors): unify CSS variable naming to --g-workflow-* format - Update LineColors enum and demo configuration - Add CSS variable definitions - Ensure consistent naming across all workflow-related variables

---------

Co-authored-by: husky-dot <xiaozhi@xiaozhideMacBook-Pro.local>
Co-authored-by: husky-dot <xiaozhi@172-0-8-36.lightspeed.rcsntx.sbcglobal.net>
This commit is contained in:
小智 2025-06-10 18:26:30 +08:00 committed by GitHub
parent f994881b22
commit a6d61d347e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 163 additions and 60 deletions

View File

@ -77,12 +77,12 @@ export function useEditorProps(
return json;
},
lineColor: {
hidden: 'var(--g-line-color-hidden,transparent)',
default: 'var(--g-line-color-default,#4d53e8)',
drawing: 'var(--g-line-color-drawing, #5DD6E3)',
hovered: 'var(--g-line-color-hover,#37d0ff)',
selected: 'var(--g-line-color-selected,#37d0ff)',
error: 'var(--g-line-color-hover,red)',
hidden: 'var(--g-workflow-line-color-hidden,transparent)',
default: 'var(--g-workflow-line-color-default,#4d53e8)',
drawing: 'var(--g-workflow-line-color-drawing, #5DD6E3)',
hovered: 'var(--g-workflow-line-color-hover,#37d0ff)',
selected: 'var(--g-workflow-line-color-selected,#37d0ff)',
error: 'var(--g-workflow-line-color-error,red)',
},
/*
* Check whether the line can be added

View File

@ -1,71 +1,80 @@
:root {
--g-workflow-port-color-primary: #4d53e8;
--g-workflow-port-color-secondary: #9197f1;
--g-workflow-port-color-error: #ff0000;
--g-workflow-port-color-background: #ffffff;
/* Port colors */
--g-workflow-port-color-primary: #4d53e8;
--g-workflow-port-color-secondary: #9197f1;
--g-workflow-port-color-error: #ff0000;
--g-workflow-port-color-background: #ffffff;
/* Line colors */
--g-workflow-line-color-hidden: transparent;
--g-workflow-line-color-default: #4d53e8;
--g-workflow-line-color-drawing: #5dd6e3;
--g-workflow-line-color-hover: #37d0ff;
--g-workflow-line-color-selected: #37d0ff;
--g-workflow-line-color-error: red;
}
.gedit-selector-bounds-background {
cursor: move;
display: none !important;
cursor: move;
display: none !important;
}
.gedit-selector-bounds-foreground {
cursor: move;
position: absolute;
left: 0;
top: 0;
width: 0;
height: 0;
outline: 1px solid var(--g-playground-selectBox-outline);
z-index: 33;
background-color: var(--g-playground-selectBox-background);
cursor: move;
position: absolute;
left: 0;
top: 0;
width: 0;
height: 0;
outline: 1px solid var(--g-playground-selectBox-outline);
z-index: 33;
background-color: var(--g-playground-selectBox-background);
}
@keyframes blink {
0% {
opacity: 1;
}
50% {
opacity: 0;
}
100% {
opacity: 1;
}
0% {
opacity: 1;
}
50% {
opacity: 0;
}
100% {
opacity: 1;
}
}
.node-running {
border: 1px dashed rgb(78, 64, 229) !important;
border-radius: 8px;
border: 1px dashed rgb(78, 64, 229) !important;
border-radius: 8px;
}
.demo-editor {
flex-grow: 1;
position: relative;
height: 100%;
flex-grow: 1;
position: relative;
height: 100%;
}
.demo-container {
position: absolute;
left: 0px;
top: 0px;
display: flex;
width: 100%;
height: 100%;
flex-direction: column;
position: absolute;
left: 0px;
top: 0px;
display: flex;
width: 100%;
height: 100%;
flex-direction: column;
}
.demo-tools {
padding: 10px;
display: flex;
justify-content: space-between;
padding: 10px;
display: flex;
justify-content: space-between;
}
.demo-tools-group > * {
margin-right: 8px;
margin-right: 8px;
}
.mouse-pad-option-icon {
display: flex;
justify-content: center;
align-items: center;
display: flex;
justify-content: center;
align-items: center;
}

View File

@ -17,7 +17,15 @@ export const BaseNode = () => {
* https://github.com/bytedance/flowgram.ai/blob/main/packages/client/free-layout-editor/src/components/workflow-node-renderer.tsx
*/
return (
<WorkflowNodeRenderer className="demo-free-node" node={props.node}>
<WorkflowNodeRenderer
className="demo-free-node"
node={props.node}
// Optional port color customization
portPrimaryColor="#4d53e8" // Active state color (linked/hovered)
portSecondaryColor="#9197f1" // Default state color
portErrorColor="#ff4444" // Error state color
portBackgroundColor="#ffffff" // Background color
>
{
// Form rendering through formMeta generation
form?.render()

View File

@ -56,6 +56,15 @@ function BaseNode() {
Ports are ultimately rendered through the `WorkflowPortRender` component, supporting custom styles, or businesses can reimplement this component based on the source code, see [Free Layout Best Practices - Node Rendering](https://github.com/bytedance/flowgram.ai/blob/main/apps/demo-free-layout/src/components/base-node/node-wrapper.tsx)
### Custom Port Colors
You can customize port colors by passing color props to `WorkflowPortRender`:
- `primaryColor` - Active state color (when linked or hovered)
- `secondaryColor` - Default state color
- `errorColor` - Error state color
- `backgroundColor` - Background color
```tsx pure
import { WorkflowPortRender, useNodeRender } from '@flowgram.ai/free-layout-editor';
@ -67,7 +76,17 @@ function BaseNode() {
<div data-port-id="condition-if-0" data-port-type="output"></div>
<div data-port-id="condition-if-1" data-port-type="output"></div>
{ports.map((p) => (
<WorkflowPortRender key={p.id} entity={p} className="xxx" style={{ /* custom style */}}/>
<WorkflowPortRender
key={p.id}
entity={p}
className="xxx"
style={{ /* custom style */}}
// Custom port colors
primaryColor="#4d53e8" // Active state color (linked/hovered)
secondaryColor="#9197f1" // Default state color
errorColor="#ff4444" // Error state color
backgroundColor="#ffffff" // Background color
/>
))}
</div>
)

View File

@ -17,7 +17,15 @@ export const BaseNode = () => {
* https://github.com/bytedance/flowgram.ai/blob/main/packages/client/free-layout-editor/src/components/workflow-node-renderer.tsx
*/
return (
<WorkflowNodeRenderer className="demo-free-node" node={props.node}>
<WorkflowNodeRenderer
className="demo-free-node"
node={props.node}
// 可选的端口颜色自定义
portPrimaryColor="#4d53e8" // 激活状态颜色 (linked/hovered)
portSecondaryColor="#9197f1" // 默认状态颜色
portErrorColor="#ff4444" // 错误状态颜色
portBackgroundColor="#ffffff" // 背景颜色
>
{
// 表单渲染通过 formMeta 生成
form?.render()

View File

@ -56,6 +56,15 @@ function BaseNode() {
端口最终通过 `WorkflowPortRender` 组件渲染,支持自定义 style, 或者业务基于源码重新实现该组件, 参考 [自由布局最佳实践 - 节点渲染](https://github.com/bytedance/flowgram.ai/blob/main/apps/demo-free-layout/src/components/base-node/node-wrapper.tsx)
### 自定义端口颜色
可以通过向 `WorkflowPortRender` 传递颜色 props 来自定义端口颜色:
- `primaryColor` - 激活状态颜色linked/hovered
- `secondaryColor` - 默认状态颜色
- `errorColor` - 错误状态颜色
- `backgroundColor` - 背景颜色
```tsx pure
import { WorkflowPortRender, useNodeRender } from '@flowgram.ai/free-layout-editor';
@ -67,7 +76,17 @@ function BaseNode() {
<div data-port-id="condition-if-0" data-port-type="output"></div>
<div data-port-id="condition-if-1" data-port-type="output"></div>
{ports.map((p) => (
<WorkflowPortRender key={p.id} entity={p} className="xxx" style={{ /* custom style */}}/>
<WorkflowPortRender
key={p.id}
entity={p}
className="xxx"
style={{ /* custom style */}}
// 自定义端口颜色
primaryColor="#4d53e8" // 激活状态颜色linked/hovered
secondaryColor="#9197f1" // 默认状态颜色
errorColor="#ff4444" // 错误状态颜色
backgroundColor="#ffffff" // 背景颜色
/>
))}
</div>
)

View File

@ -24,12 +24,12 @@ export interface LineColor {
}
export enum LineColors {
HIDDEN = 'var(--g-line-color-hidden,transparent)', // 隐藏线条
DEFUALT = 'var(--g-line-color-default,#4d53e8)',
DRAWING = 'var(--g-line-color-drawing, #5DD6E3)', // '#b5bbf8', // '#9197F1',
HOVER = 'var(--g-line-color-hover,#37d0ff)',
SELECTED = 'var(--g-line-color-selected,#37d0ff)',
ERROR = 'var(--g-line-color-error,red)',
HIDDEN = 'var(--g-workflow-line-color-hidden,transparent)', // 隐藏线条
DEFUALT = 'var(--g-workflow-line-color-default,#4d53e8)',
DRAWING = 'var(--g-workflow-line-color-drawing, #5DD6E3)', // '#b5bbf8', // '#9197F1',
HOVER = 'var(--g-workflow-line-color-hover,#37d0ff)',
SELECTED = 'var(--g-workflow-line-color-selected,#37d0ff)',
ERROR = 'var(--g-workflow-line-color-error,red)',
}
export interface WorkflowLineRenderContribution {

View File

@ -19,6 +19,14 @@ export interface WorkflowNodeProps {
port: WorkflowPortEntity,
e: React.MouseEvent<HTMLDivElement> | React.MouseEventHandler<HTMLDivElement>
) => void;
/** 端口激活状态颜色 (linked/hovered) */
portPrimaryColor?: string;
/** 端口默认状态颜色 */
portSecondaryColor?: string;
/** 端口错误状态颜色 */
portErrorColor?: string;
/** 端口背景颜色 */
portBackgroundColor?: string;
}
export const WorkflowNodeRenderer: React.FC<WorkflowNodeProps> = (props) => {
@ -50,6 +58,10 @@ export const WorkflowNodeRenderer: React.FC<WorkflowNodeProps> = (props) => {
onClick={props.onPortClick ? (e) => props.onPortClick!(p, e) : undefined}
className={props.portClassName}
style={props.portStyle}
primaryColor={props.portPrimaryColor}
secondaryColor={props.portSecondaryColor}
errorColor={props.portErrorColor}
backgroundColor={props.portBackgroundColor}
/>
))}
</>

View File

@ -19,6 +19,14 @@ export interface WorkflowPortRenderProps {
className?: string;
style?: React.CSSProperties;
onClick?: React.MouseEventHandler<HTMLDivElement>;
/** 激活状态颜色 (linked/hovered) */
primaryColor?: string;
/** 默认状态颜色 */
secondaryColor?: string;
/** 错误状态颜色 */
errorColor?: string;
/** 背景颜色 */
backgroundColor?: string;
}
export const WorkflowPortRender: React.FC<WorkflowPortRenderProps> =
@ -79,10 +87,30 @@ export const WorkflowPortRender: React.FC<WorkflowPortRenderProps> =
// 有线条链接的时候深蓝色小圆点
linked,
});
// 构建 CSS 自定义属性用于颜色覆盖
const colorStyles: Record<string, string> = {};
if (props.primaryColor) {
colorStyles['--g-workflow-port-color-primary'] = props.primaryColor;
}
if (props.secondaryColor) {
colorStyles['--g-workflow-port-color-secondary'] = props.secondaryColor;
}
if (props.errorColor) {
colorStyles['--g-workflow-port-color-error'] = props.errorColor;
}
if (props.backgroundColor) {
colorStyles['--g-workflow-port-color-background'] = props.backgroundColor;
}
const combinedStyle = targetElement
? { ...props.style, ...colorStyles }
: { ...props.style, ...colorStyles, left: posX, top: posY };
const content = (
<WorkflowPointStyle
className={className}
style={targetElement ? props.style : { ...props.style, left: posX, top: posY }}
style={combinedStyle}
onClick={onClick}
data-port-entity-id={entity.id}
data-port-entity-type={entity.portType}