Files
app/frontend/src/components/Flow/FlowPanel.tsx
T
stroblmeandClaude Opus 5 954d90fbf7 Move panel explanations into tooltips
The node, flow, widget and dashboard panels explained themselves in
paragraphs under every control. They now carry a caption and an info
tooltip, so a panel reads as a list of settings.

InfoTip, PanelSection and FieldLabel live beside PANEL_SECTION in
SidePanel.tsx, which the panels already share. InfoTip holds its own open
state because a Radix tooltip ignores a touch pointer, and below md the
whole panel is a full-screen sheet — hover-only help would leave a phone
with the caption and nothing else.

Dashboard/panels.tsx drops from 30 helper paragraphs to 5; the ones left
are empty states and status, not explanation. Em-dashes are out of the
user-facing strings, except where one separates the halves of a select
item.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015YrQnKV3bnQd4K342y8tKj
2026-08-31 10:24:26 +02:00

165 lines
4.7 KiB
TypeScript

import { useState } from "react"
import type { FlowDef_Input } from "@/client"
import { Button } from "@/components/ui/button"
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog"
import { Switch } from "@/components/ui/switch"
import { BoundarySections } from "./FlowBoundary"
import { PanelSection, PanelTitle, SidePanel } from "./SidePanel"
/**
* The flow's own settings, in the same panel its nodes use — down to where the
* name sits and how confirming a new one works.
*/
export function FlowPanel({
open,
definition,
nodeCount,
onChange,
onDelete,
hasDraft,
enabled,
toggling,
onToggleEnabled,
onClose,
}: {
open: boolean
definition: FlowDef_Input
nodeCount: number
onChange: (next: FlowDef_Input) => void
onDelete: () => void
hasDraft: boolean
enabled: boolean
toggling: boolean
onToggleEnabled: (next: boolean) => void
onClose: () => void
}) {
const [confirmOpen, setConfirmOpen] = useState(false)
return (
<>
<SidePanel
open={open}
label="Flow settings"
testId="flow-panel"
bodyKey={definition.name}
onClose={onClose}
header={
<PanelTitle
value={definition.title ?? ""}
placeholder={definition.name}
label="Flow title"
onConfirm={(title) => onChange({ ...definition, title })}
/>
}
footer={
<Button
variant="ghost"
size="sm"
className="text-destructive hover:text-destructive"
onClick={() => setConfirmOpen(true)}
data-testid="delete-flow"
>
Delete flow
</Button>
}
>
<div className="grid gap-6 p-4">
<PanelSection
title="Running"
className="gap-3"
help="While this is on, the engine keeps the flow's subscriptions, schedules and webhooks live. Off, none of it is subscribed, scheduled or reachable."
>
<div className="flex items-center justify-between gap-3">
<p className="text-sm text-muted-foreground">
{enabled ? "Live" : "Stopped"}
</p>
<Switch
checked={enabled}
disabled={toggling}
onCheckedChange={onToggleEnabled}
aria-label="Run this flow"
data-testid="flow-enabled"
/>
</div>
</PanelSection>
<BoundarySections
flow={definition.name}
definition={definition}
running={enabled}
onChange={onChange}
/>
<PanelSection
title="Contents"
className="gap-3"
help={
<>
Every message in this flow is named under{" "}
<span className="font-mono">{definition.name}</span>, so two
flows can use the same short name without colliding.
</>
}
>
<p className="text-sm text-muted-foreground">
{nodeCount === 0
? "No nodes yet."
: `${nodeCount} node${nodeCount === 1 ? "" : "s"}`}
</p>
</PanelSection>
{hasDraft ? (
<PanelSection
title="Unpublished changes"
className="gap-3"
help="Edits are kept as a draft until you publish them. The bar below publishes this one, or discards it."
>
<p className="text-sm text-muted-foreground">
The engine is still running the last published version.
</p>
</PanelSection>
) : null}
</div>
</SidePanel>
<Dialog open={confirmOpen} onOpenChange={setConfirmOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>
Delete {definition.title || definition.name}?
</DialogTitle>
<DialogDescription>
This removes the flow and the code of its{" "}
{nodeCount === 1 ? "node" : `${nodeCount} nodes`}. Its history
stays in the flow store's git repository.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button variant="outline" onClick={() => setConfirmOpen(false)}>
Keep it
</Button>
<Button
variant="destructive"
onClick={() => {
setConfirmOpen(false)
onDelete()
}}
data-testid="confirm-delete-flow"
>
Delete flow
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</>
)
}