refactor(frontend): enhance FlowEditor components with color handling and output rendering

### Changes
- Updated `OutputNodeHandle` to accept `hexColor` for dynamic color rendering based on connection state.
- Refactored `getTypeDisplayInfo` to include `hexColor` in the return object for better type display customization.
- Simplified output handle rendering in `OutputHandler` by introducing a recursive function to manage nested properties.

### Impact
These changes improve the visual feedback of output connections in the FlowEditor, enhancing user experience and maintainability of the component structure.
This commit is contained in:
abhi1992002
2026-01-03 18:15:56 +05:30
parent d5b195bcb5
commit 4e5af1677e
5 changed files with 137 additions and 72 deletions

View File

@@ -2,6 +2,7 @@ import { CircleIcon } from "@phosphor-icons/react";
import { Handle, Position } from "@xyflow/react";
import { useEdgeStore } from "../../../stores/edgeStore";
import { cleanUpHandleId } from "@/components/renderers/input-renderer-2/helpers";
import { cn } from "@/lib/utils";
const InputNodeHandle = ({
handleId,
@@ -36,13 +37,16 @@ const InputNodeHandle = ({
const OutputNodeHandle = ({
field_name,
nodeId,
hexColor,
}: {
field_name: string;
nodeId: string;
hexColor: string;
}) => {
const isOutputConnected = useEdgeStore((state) =>
state.isInputConnected(nodeId, field_name),
state.isOutputConnected(nodeId, field_name),
);
console.log(field_name, isOutputConnected);
return (
<Handle
type={"source"}
@@ -53,8 +57,9 @@ const OutputNodeHandle = ({
<div className="pointer-events-none">
<CircleIcon
size={16}
weight={isOutputConnected ? "fill" : "duotone"}
className={"text-gray-400 opacity-100"}
weight={"duotone"}
color={isOutputConnected ? hexColor : "gray"}
className={cn("text-gray-400 opacity-100")}
/>
</div>
</Handle>

View File

@@ -13,7 +13,6 @@ import {
} from "@/components/atoms/Tooltip/BaseTooltip";
import { useEdgeStore } from "@/app/(platform)/build/stores/edgeStore";
import { getTypeDisplayInfo } from "./helpers";
import { generateHandleId } from "../handlers/helpers";
import { BlockUIType } from "../../types";
export const OutputHandler = ({
@@ -29,6 +28,67 @@ export const OutputHandler = ({
const properties = outputSchema?.properties || {};
const [isOutputVisible, setIsOutputVisible] = useState(true);
const renderOutputHandles = (
schema: RJSFSchema,
keyPrefix: string = "",
titlePrefix: string = "",
): React.ReactNode[] => {
return Object.entries(schema).map(
([key, fieldSchema]: [string, RJSFSchema]) => {
const fullKey = keyPrefix ? `${keyPrefix}_#_${key}` : key;
const fieldTitle = titlePrefix + (fieldSchema?.title || key);
const isConnected = isOutputConnected(nodeId, fullKey);
const shouldShow = isConnected || isOutputVisible;
const { displayType, colorClass, hexColor } =
getTypeDisplayInfo(fieldSchema);
return shouldShow ? (
<div key={fullKey} className="flex flex-col items-end gap-2">
<div className="relative flex items-center gap-2">
{fieldSchema?.description && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<span
style={{ marginLeft: 6, cursor: "pointer" }}
aria-label="info"
tabIndex={0}
>
<InfoIcon />
</span>
</TooltipTrigger>
<TooltipContent>{fieldSchema?.description}</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
<Text variant="body" className="text-slate-700">
{fieldTitle}
</Text>
<Text variant="small" as="span" className={colorClass}>
({displayType})
</Text>
<OutputNodeHandle
field_name={fullKey}
nodeId={nodeId}
hexColor={hexColor}
/>
</div>
{/* Recursively render nested properties */}
{fieldSchema?.properties &&
renderOutputHandles(
fieldSchema.properties,
fullKey,
`${fieldTitle}.`,
)}
</div>
) : null;
},
);
};
return (
<div className="flex flex-col items-end justify-between gap-2 rounded-b-xlarge border-t border-slate-200/50 bg-white py-3.5">
<Button
@@ -49,44 +109,9 @@ export const OutputHandler = ({
</Text>
</Button>
{
<div className="flex flex-col items-end gap-2">
{Object.entries(properties).map(([key, property]: [string, any]) => {
const isConnected = isOutputConnected(nodeId, key);
const shouldShow = isConnected || isOutputVisible;
const { displayType, colorClass } = getTypeDisplayInfo(property);
return shouldShow ? (
<div key={key} className="relative flex items-center gap-2">
{property?.description && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<span
style={{ marginLeft: 6, cursor: "pointer" }}
aria-label="info"
tabIndex={0}
>
<InfoIcon />
</span>
</TooltipTrigger>
<TooltipContent>{property?.description}</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
<Text variant="body" className="text-slate-700">
{property?.title || key}{" "}
</Text>
<Text variant="small" as="span" className={colorClass}>
({displayType})
</Text>
<OutputNodeHandle field_name={key} nodeId={nodeId} />
</div>
) : null;
})}
</div>
}
<div className="flex flex-col items-end gap-2">
{renderOutputHandles(properties)}
</div>
</div>
);
};

View File

@@ -92,14 +92,38 @@ export const getTypeDisplayInfo = (schema: any) => {
if (schema?.type === "string" && schema?.format) {
const formatMap: Record<
string,
{ displayType: string; colorClass: string }
{ displayType: string; colorClass: string; hexColor: string }
> = {
file: { displayType: "file", colorClass: "!text-green-500" },
date: { displayType: "date", colorClass: "!text-blue-500" },
time: { displayType: "time", colorClass: "!text-blue-500" },
"date-time": { displayType: "datetime", colorClass: "!text-blue-500" },
"long-text": { displayType: "text", colorClass: "!text-green-500" },
"short-text": { displayType: "text", colorClass: "!text-green-500" },
file: {
displayType: "file",
colorClass: "!text-green-500",
hexColor: "#22c55e",
},
date: {
displayType: "date",
colorClass: "!text-blue-500",
hexColor: "#3b82f6",
},
time: {
displayType: "time",
colorClass: "!text-blue-500",
hexColor: "#3b82f6",
},
"date-time": {
displayType: "datetime",
colorClass: "!text-blue-500",
hexColor: "#3b82f6",
},
"long-text": {
displayType: "text",
colorClass: "!text-green-500",
hexColor: "#22c55e",
},
"short-text": {
displayType: "text",
colorClass: "!text-green-500",
hexColor: "#22c55e",
},
};
const formatInfo = formatMap[schema.format];
@@ -131,10 +155,23 @@ export const getTypeDisplayInfo = (schema: any) => {
any: "!text-gray-500",
};
const hexColorMap: Record<string, string> = {
string: "#22c55e",
number: "#3b82f6",
integer: "#3b82f6",
boolean: "#eab308",
object: "#a855f7",
array: "#6366f1",
null: "#6b7280",
any: "#6b7280",
};
const colorClass = colorMap[schema?.type] || "!text-gray-500";
const hexColor = hexColorMap[schema?.type] || "#6b7280";
return {
displayType,
colorClass,
hexColor,
};
};

View File

@@ -64,7 +64,7 @@ export const AnyOfField = (props: FieldProps) => {
registry={registry}
placeholder={props.placeholder}
autocomplete={props.autocomplete}
className="h-[22px] w-fit gap-1 border-none bg-zinc-100 px-1 pl-3 text-xs font-medium"
className="-ml-1 h-[22px] w-fit gap-1 px-1 pl-2 text-xs font-medium"
autofocus={props.autofocus}
label=""
hideLabel={true}

View File

@@ -45,29 +45,27 @@ export const AnyOfFieldTitle = (props: customFieldProps) => {
shouldShowTypeSelector(schema) && !isArrayItem && !isHandleConnected;
return (
<div className="flex items-center justify-between gap-2">
<div className="flex items-center">
<TitleFieldTemplate
id={title_id}
title={schema.title || name || ""}
required={required}
schema={schema}
registry={registry}
uiSchema={uiSchema}
/>
{isHandleConnected && (
<Text variant="small" className="mr-2 text-zinc-700">
(any)
</Text>
)}
<DescriptionFieldTemplate
id={description_id}
description={schema.description || ""}
schema={schema}
registry={registry}
/>
</div>
<div className="flex items-center gap-2">
<TitleFieldTemplate
id={title_id}
title={schema.title || name || ""}
required={required}
schema={schema}
registry={registry}
uiSchema={uiSchema}
/>
{isHandleConnected && (
<Text variant="small" className="mr-2 text-zinc-700">
(any)
</Text>
)}
{shouldShowSelector && selector}
<DescriptionFieldTemplate
id={description_id}
description={schema.description || ""}
schema={schema}
registry={registry}
/>
</div>
);
};