From e82dade07139b4bcd764ab3bdd5817196f2638d2 Mon Sep 17 00:00:00 2001 From: David Anthony Date: Tue, 7 Jul 2026 08:55:31 -0700 Subject: [PATCH 1/3] feat(apollo-react): add LockableValueField HITL prototype MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prototype for a Quick Approve panel demonstrating quick-form editing inside the Property Panel experience. - LockableValueField: a string field built on apollo-wind's InputGroup, with a lock/unlock popover (locked = read-only, not disabled) and a Fixed value / Expression type-switcher popover, plus an AI-assist popover (describe + generate) and an Insert-variable affordance. - NodePropertyPanel "Lockable Field" story: a Quick Approve panel with a Delivery channel multi-select, an Assignment criteria search + dropdown, and a Quick form section (UI/JSON toggle) containing a draggable, deletable, re-orderable list of LockableValueField rows seeded with Invoice Number / Submission Date / Approved Amount. Built entirely on existing apollo-wind primitives (InputGroup, DropdownMenu, Popover, MultiSelect, Select, Card, ToggleGroup) and the existing @dnd-kit dependency already used by StageNode, following this file's established prototype conventions (PanelFrame, CasePanel-style row chrome). No production wiring yet — this is a review prototype. --- .../NodePropertyPanel/LockableValueField.tsx | 257 +++++++++++++ .../NodePropertyPanel.stories.tsx | 348 +++++++++++++++++- .../components/NodePropertyPanel/index.ts | 2 + 3 files changed, 605 insertions(+), 2 deletions(-) create mode 100644 packages/apollo-react/src/canvas/components/NodePropertyPanel/LockableValueField.tsx diff --git a/packages/apollo-react/src/canvas/components/NodePropertyPanel/LockableValueField.tsx b/packages/apollo-react/src/canvas/components/NodePropertyPanel/LockableValueField.tsx new file mode 100644 index 000000000..a5c3b69fd --- /dev/null +++ b/packages/apollo-react/src/canvas/components/NodePropertyPanel/LockableValueField.tsx @@ -0,0 +1,257 @@ +import { + Button, + cn, + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, + InputGroup, + InputGroupAddon, + InputGroupButton, + InputGroupInput, + Label, + Popover, + PopoverContent, + PopoverTrigger, + Textarea, +} from '@uipath/apollo-wind'; +import { ChevronDown, Code2, Lock, LockOpen, Sparkles, Type } from 'lucide-react'; +import type { ReactNode } from 'react'; +import { useId } from 'react'; + +export type LockableValueFieldMode = 'fixed' | 'expression'; + +export interface LockableValueFieldProps { + /** Current field value. */ + value?: string; + /** Called when the user edits the value (only fires while unlocked). */ + onValueChange?: (value: string) => void; + /** Whether the field is read-only. Defaults to true. */ + locked?: boolean; + /** Called when the user toggles the lock. */ + onLockedChange?: (locked: boolean) => void; + /** Fixed value vs. JS expression. Defaults to 'fixed'. */ + mode?: LockableValueFieldMode; + /** Called when the user switches modes. */ + onModeChange?: (mode: LockableValueFieldMode) => void; + /** Shows a required-field asterisk next to the default label. Ignored when `label` is provided. */ + required?: boolean; + /** Overrides the default mode-based label (e.g. a field name instead of "String value"). */ + label?: ReactNode; + /** Extra content rendered after the built-in AI assist / Insert variable buttons (e.g. a delete button). */ + headerActions?: ReactNode; + id?: string; + className?: string; +} + +const FIELD_LABEL: Record = { + fixed: 'String value', + expression: 'Write a string expression', +}; + +const FIELD_PLACEHOLDER: Record = { + fixed: 'Enter a value', + expression: 'Enter an expression', +}; + +/** + * LockableValueField — a string field that can be locked to read-only and + * switched between a literal value and a JS expression. + * + * Prototype: the expression mode is styled as code (monospace) but does not + * yet carry real syntax highlighting or evaluation. See ExpressionField for + * the direction a full code-editing surface would take. + */ +export function LockableValueField({ + value = '', + onValueChange, + locked = true, + onLockedChange, + mode = 'fixed', + onModeChange, + required, + label, + headerActions, + id, + className, +}: LockableValueFieldProps) { + const promptId = useId(); + + return ( +
+
+ {label ?? ( + + )} +
+ + + + + +
+ +