From 87cdc413aabf9cbaaf44b2668fe9a0bb79f8a6de Mon Sep 17 00:00:00 2001 From: Taylor Ho Date: Fri, 14 Aug 2026 14:55:59 -0700 Subject: [PATCH] Polish workflow editor interactions Signed-off-by: Taylor Ho --- .../features/workflows/ui/ChannelCombobox.tsx | 56 +++++++++++++++---- .../features/workflows/ui/WorkflowDialog.tsx | 12 ++-- .../workflows/ui/WorkflowFormBuilder.tsx | 2 +- 3 files changed, 54 insertions(+), 16 deletions(-) diff --git a/desktop/src/features/workflows/ui/ChannelCombobox.tsx b/desktop/src/features/workflows/ui/ChannelCombobox.tsx index fac1cfa7d..5eb0eb586 100644 --- a/desktop/src/features/workflows/ui/ChannelCombobox.tsx +++ b/desktop/src/features/workflows/ui/ChannelCombobox.tsx @@ -16,6 +16,7 @@ import type { Channel } from "@/shared/api/types"; import { cn } from "@/shared/lib/cn"; import { Popover, PopoverContent, PopoverTrigger } from "@/shared/ui/popover"; import { PortalledScrollArea } from "@/shared/ui/PortalledScrollArea"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@/shared/ui/tooltip"; function ChannelPrivacyIcon({ channel }: { channel: Channel }) { const Icon = @@ -40,6 +41,8 @@ type ChannelComboboxProps = { id?: string; isChannelDisabled?: (channel: Channel) => boolean; onChange: (value: string) => void; + readOnly?: boolean; + readOnlyTooltip?: string; required?: boolean; variant?: "header" | "field"; value: string; @@ -55,14 +58,25 @@ export function ChannelCombobox({ id, isChannelDisabled, onChange, + readOnly = false, + readOnlyTooltip = "The channel can't be changed after a workflow is created.", required = false, variant = "header", value, }: ChannelComboboxProps) { - const [open, setOpen] = React.useState(defaultOpen); + const [open, setOpen] = React.useState(false); const [query, setQuery] = React.useState(""); const [highlightedIndex, setHighlightedIndex] = React.useState(0); + React.useEffect(() => { + if (!defaultOpen) return; + + // Let the pointer interaction that mounted the containing dialog finish + // before installing Radix's outside-interaction listeners. + const frame = window.requestAnimationFrame(() => setOpen(true)); + return () => window.cancelAnimationFrame(frame); + }, [defaultOpen]); + const selected = channels.find((c) => c.id === value); const currentPubkey = useIdentityQuery().data?.pubkey; const dmParticipantPubkeys = React.useMemo(() => { @@ -150,6 +164,34 @@ export function ChannelCombobox({ } } + const selectedLabel = selected + ? (channelLabels.get(selected.id) ?? selected.name) + : value + ? "Unavailable channel" + : emptyLabel; + + if (readOnly) { + return ( + + + + + {readOnlyTooltip} + + ); + } + return ( @@ -181,13 +223,7 @@ export function ChannelCombobox({ ) : required && !value ? ( ) : null} - - {selected - ? (channelLabels.get(selected.id) ?? selected.name) - : value - ? "Unavailable channel" - : emptyLabel} - + {selectedLabel} @@ -222,7 +258,7 @@ export function ChannelCombobox({ {allowEmpty && !query ? (