From 1730c346bb1c05a21080b2eb2bd5f104f8227865 Mon Sep 17 00:00:00 2001 From: BenGSchulz Date: Wed, 15 Jul 2026 11:50:08 -0700 Subject: [PATCH] feat(apollo-react): add NodeIOView + JsonTree schema-aware JSON components MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Schema-aware JSON viewing/editing for canvas node IO, in two layers: - canvas/components/JsonTree: standalone JSON schema+value tree. Merges a JSON Schema with a value into an annotated, inline-editable tree (search, filters, custom row wrapper, custom value cells, path copy, schema-status decorations). Keys declared by the schema but absent from the value still render; value-only keys surface as extra. - canvas/components/NodeIOView: composed panel body over JsonTree — title bar, Schema/JSON/consumer tabs, and a before-content slot. - canvas/controls/NodeOutputModeSelect: node output mode dropdown (Live/Static/ Simulated) with useDefaultNodeOutputModes. --- packages/apollo-react/package.json | 1 + .../components/JsonTree/DecorationChip.tsx | 53 + .../components/JsonTree/EditorChrome.tsx | 91 ++ .../JsonTree/JsonContainerEditor.tsx | 96 ++ .../JsonTree/JsonLeafValueEditor.tsx | 101 ++ .../JsonTree/JsonMultilineLeafEditor.tsx | 71 ++ .../canvas/components/JsonTree/JsonTree.tsx | 236 +++++ .../components/JsonTree/JsonTree.types.ts | 288 ++++++ .../components/JsonTree/JsonTreeRow.tsx | 464 +++++++++ .../components/JsonTree/JsonTreeToolbar.tsx | 232 +++++ .../components/JsonTree/JsonTypeBadge.tsx | 65 ++ .../canvas/components/JsonTree/NodeKey.tsx | 76 ++ .../canvas/components/JsonTree/RowActions.tsx | 114 +++ .../components/JsonTree/ScalarValueCell.tsx | 167 ++++ .../components/JsonTree/buildJsonTree.test.ts | 376 +++++++ .../components/JsonTree/buildJsonTree.ts | 456 +++++++++ .../canvas/components/JsonTree/clipboard.ts | 37 + .../src/canvas/components/JsonTree/index.ts | 9 + .../components/JsonTree/leafEditTypes.ts | 50 + .../NodeIOView/NodeIOView.stories.tsx | 925 ++++++++++++++++++ .../components/NodeIOView/NodeIOView.test.tsx | 849 ++++++++++++++++ .../components/NodeIOView/NodeIOView.tsx | 295 ++++++ .../components/NodeIOView/NodeIOView.types.ts | 105 ++ .../components/NodeIOView/PanelTitleBar.tsx | 39 + .../src/canvas/components/NodeIOView/index.ts | 3 + .../NodePropertyPanel.stories.tsx | 830 +--------------- .../src/canvas/components/index.ts | 6 +- .../NodeOutputModeSelect.test.tsx | 45 + .../NodeOutputModeSelect.tsx | 172 ++++ .../controls/NodeOutputModeSelect/index.ts | 7 + .../apollo-react/src/canvas/controls/index.ts | 7 + .../apollo-react/src/canvas/locales/en.json | 51 +- .../src/i18n/useSafeLingui.test.tsx | 58 +- .../apollo-react/src/i18n/useSafeLingui.ts | 37 +- pnpm-lock.yaml | 3 + 35 files changed, 5576 insertions(+), 839 deletions(-) create mode 100644 packages/apollo-react/src/canvas/components/JsonTree/DecorationChip.tsx create mode 100644 packages/apollo-react/src/canvas/components/JsonTree/EditorChrome.tsx create mode 100644 packages/apollo-react/src/canvas/components/JsonTree/JsonContainerEditor.tsx create mode 100644 packages/apollo-react/src/canvas/components/JsonTree/JsonLeafValueEditor.tsx create mode 100644 packages/apollo-react/src/canvas/components/JsonTree/JsonMultilineLeafEditor.tsx create mode 100644 packages/apollo-react/src/canvas/components/JsonTree/JsonTree.tsx create mode 100644 packages/apollo-react/src/canvas/components/JsonTree/JsonTree.types.ts create mode 100644 packages/apollo-react/src/canvas/components/JsonTree/JsonTreeRow.tsx create mode 100644 packages/apollo-react/src/canvas/components/JsonTree/JsonTreeToolbar.tsx create mode 100644 packages/apollo-react/src/canvas/components/JsonTree/JsonTypeBadge.tsx create mode 100644 packages/apollo-react/src/canvas/components/JsonTree/NodeKey.tsx create mode 100644 packages/apollo-react/src/canvas/components/JsonTree/RowActions.tsx create mode 100644 packages/apollo-react/src/canvas/components/JsonTree/ScalarValueCell.tsx create mode 100644 packages/apollo-react/src/canvas/components/JsonTree/buildJsonTree.test.ts create mode 100644 packages/apollo-react/src/canvas/components/JsonTree/buildJsonTree.ts create mode 100644 packages/apollo-react/src/canvas/components/JsonTree/clipboard.ts create mode 100644 packages/apollo-react/src/canvas/components/JsonTree/index.ts create mode 100644 packages/apollo-react/src/canvas/components/JsonTree/leafEditTypes.ts create mode 100644 packages/apollo-react/src/canvas/components/NodeIOView/NodeIOView.stories.tsx create mode 100644 packages/apollo-react/src/canvas/components/NodeIOView/NodeIOView.test.tsx create mode 100644 packages/apollo-react/src/canvas/components/NodeIOView/NodeIOView.tsx create mode 100644 packages/apollo-react/src/canvas/components/NodeIOView/NodeIOView.types.ts create mode 100644 packages/apollo-react/src/canvas/components/NodeIOView/PanelTitleBar.tsx create mode 100644 packages/apollo-react/src/canvas/components/NodeIOView/index.ts create mode 100644 packages/apollo-react/src/canvas/controls/NodeOutputModeSelect/NodeOutputModeSelect.test.tsx create mode 100644 packages/apollo-react/src/canvas/controls/NodeOutputModeSelect/NodeOutputModeSelect.tsx create mode 100644 packages/apollo-react/src/canvas/controls/NodeOutputModeSelect/index.ts diff --git a/packages/apollo-react/package.json b/packages/apollo-react/package.json index b046bcd32..465cfb170 100644 --- a/packages/apollo-react/package.json +++ b/packages/apollo-react/package.json @@ -186,6 +186,7 @@ "@lexical/table": "0.42.0", "@lexical/utils": "0.42.0", "@lingui/core": "^5.6.1", + "@lingui/message-utils": "^5.6.1", "@lingui/react": "^5.6.1", "@mui/icons-material": "^5.18.0", "@mui/material": "^5.18.0", diff --git a/packages/apollo-react/src/canvas/components/JsonTree/DecorationChip.tsx b/packages/apollo-react/src/canvas/components/JsonTree/DecorationChip.tsx new file mode 100644 index 000000000..8bb48842b --- /dev/null +++ b/packages/apollo-react/src/canvas/components/JsonTree/DecorationChip.tsx @@ -0,0 +1,53 @@ +import { cn } from '@uipath/apollo-wind'; +import { CanvasTooltip } from '../CanvasTooltip'; +import type { NodeDecoration, NodeDecorationTone } from './JsonTree.types'; + +const CHIP_TONE_TEXT_CLASS: Record = { + neutral: 'text-foreground-subtle', + info: 'text-info', + warning: 'text-warning', + error: 'text-error', +}; + +const CHIP_TONE_BORDER_CLASS: Record = { + neutral: 'border-border', + info: 'border-info/40', + warning: 'border-warning/40', + error: 'border-error/40', +}; + +export function DecorationChip({ decoration }: { decoration?: NodeDecoration }) { + const chip = decoration?.chip; + if (!chip || (chip.label == null && chip.icon == null)) return null; + const tone = chip.tone ?? 'neutral'; + + // A label gets the pill treatment; a bare icon renders unboxed. + const content = chip.label ? ( + + {chip.icon && {chip.icon}} + {chip.label} + + ) : ( + + {chip.icon} + + ); + + if (!chip.tooltip) return content; + return ( + {chip.tooltip}}> + {content} + + ); +} diff --git a/packages/apollo-react/src/canvas/components/JsonTree/EditorChrome.tsx b/packages/apollo-react/src/canvas/components/JsonTree/EditorChrome.tsx new file mode 100644 index 000000000..c72cb045a --- /dev/null +++ b/packages/apollo-react/src/canvas/components/JsonTree/EditorChrome.tsx @@ -0,0 +1,91 @@ +import { Button, cn } from '@uipath/apollo-wind'; +import { useEffect, useRef } from 'react'; +import { useSafeLingui } from '../../../i18n'; + +/** Apply/Cancel row shared by the multiline editors. */ +export function EditorActions({ + onApply, + onCancel, + applyDisabled = false, +}: { + onApply: () => void; + onCancel: () => void; + applyDisabled?: boolean; +}) { + const { _ } = useSafeLingui(); + return ( +
+ + +
+ ); +} + +export interface EditorTextareaProps { + value: string; + onChange: (value: string) => void; + /** Bound to Ctrl/Cmd+Enter. */ + onApply: () => void; + /** Bound to Escape (contained so a host dialog does not also close). */ + onCancel: () => void; + invalid: boolean; + rows: number; + ariaLabel: string; + placeholder?: string; + className?: string; +} + +/** Multiline editing surface shared by the leaf and container editors. */ +export function EditorTextarea({ + value, + onChange, + onApply, + onCancel, + invalid, + rows, + ariaLabel, + placeholder, + className, +}: EditorTextareaProps) { + const textareaRef = useRef(null); + + // Mounted in response to the user's edit action; focus follows it (the + // attribute form trips a11y linting). + useEffect(() => { + textareaRef.current?.focus(); + }, []); + + return ( +