Polish workflow enable controls

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
This commit is contained in:
Taylor Ho
2026-08-14 23:09:16 -07:00
parent b93a8eab2c
commit 8b9ee802ca
3 changed files with 67 additions and 36 deletions
@@ -30,11 +30,13 @@ import { rewriteRelayUrl } from "@/shared/lib/mediaUrl";
import { Button } from "@/shared/ui/button";
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/shared/ui/dropdown-menu";
import { Switch } from "@/shared/ui/switch";
import {
getWorkflowCardLabel,
getWorkflowDisplayStatus,
@@ -377,17 +379,28 @@ export function WorkflowCard({
<Copy className="mr-2 h-4 w-4" />
Duplicate
</DropdownMenuItem>
<DropdownMenuItem
<DropdownMenuCheckboxItem
checked={isEnabled}
className="gap-2 pl-2 [&>span:first-child]:hidden"
disabled={isTogglingEnabled}
onClick={() => onToggleEnabled(workflow)}
onCheckedChange={(checked) => {
if (checked !== isEnabled) onToggleEnabled(workflow);
}}
onSelect={(event) => event.preventDefault()}
>
{isEnabled ? (
<PowerOff className="mr-2 h-4 w-4" />
<Power className="mr-2 h-4 w-4 shrink-0" />
) : (
<Power className="mr-2 h-4 w-4" />
<PowerOff className="mr-2 h-4 w-4 shrink-0" />
)}
{isEnabled ? "Disable" : "Enable"}
</DropdownMenuItem>
<span>Enable</span>
<Switch
aria-hidden="true"
checked={isEnabled}
className="pointer-events-none ml-auto"
tabIndex={-1}
/>
</DropdownMenuCheckboxItem>
<DropdownMenuSeparator />
<DropdownMenuItem
className="text-destructive"
@@ -116,11 +116,13 @@ function WorkflowNameEditor({
generating,
name,
onCommit,
onEditingChange,
}: {
disabled: boolean;
generating: boolean;
name: string;
onCommit: (name: string) => boolean;
onEditingChange: (editing: boolean) => void;
}) {
const [editing, setEditing] = React.useState(false);
const [draft, setDraft] = React.useState(name);
@@ -134,11 +136,19 @@ function WorkflowNameEditor({
if (editing) inputRef.current?.select();
}, [editing]);
const changeEditing = React.useCallback(
(nextEditing: boolean) => {
setEditing(nextEditing);
onEditingChange(nextEditing);
},
[onEditingChange],
);
const commit = React.useCallback(() => {
const nextName = draft.trim();
if (!nextName || !onCommit(nextName)) return;
setEditing(false);
}, [draft, onCommit]);
changeEditing(false);
}, [changeEditing, draft, onCommit]);
if (editing) {
return (
@@ -155,7 +165,7 @@ function WorkflowNameEditor({
} else if (event.key === "Escape") {
event.preventDefault();
setDraft(name);
setEditing(false);
changeEditing(false);
}
}}
ref={inputRef}
@@ -186,7 +196,7 @@ function WorkflowNameEditor({
aria-label="Edit workflow name"
className="text-muted-foreground [&_svg]:size-3"
disabled={disabled || generating}
onClick={() => setEditing(true)}
onClick={() => changeEditing(true)}
size="icon-xs"
title="Edit workflow name"
type="button"
@@ -220,7 +230,8 @@ export function WorkflowDialog({
const [editorParseError, setEditorParseError] = React.useState<string | null>(
null,
);
const [headerTrailingElement, setHeaderTrailingElement] =
const [workflowNameEditing, setWorkflowNameEditing] = React.useState(false);
const [nameLeadingElement, setNameLeadingElement] =
React.useState<HTMLDivElement | null>(null);
const [savedWebhookInfo, setSavedWebhookInfo] = React.useState<{
relayHttpUrl: string;
@@ -265,6 +276,7 @@ export function WorkflowDialog({
setYamlDefinition(initialYaml);
setEditorMode(getInitialEditorMode(initialYaml));
setEditorParseError(null);
setWorkflowNameEditing(false);
setSavedWebhookInfo(null);
setDiscardConfirmationOpen(false);
resetCreate();
@@ -431,15 +443,25 @@ export function WorkflowDialog({
? "Copy this workflow and adjust its details."
: "Automate actions when something happens in a channel."}
</DialogDescription>
<WorkflowNameEditor
disabled={mutation.isPending || !canEditWorkflowName}
generating={generatingName}
name={workflowName}
onCommit={handleWorkflowNameCommit}
/>
<div className="flex items-center gap-2">
<WorkflowNameEditor
disabled={mutation.isPending || !canEditWorkflowName}
generating={generatingName}
name={workflowName}
onCommit={handleWorkflowNameCommit}
onEditingChange={setWorkflowNameEditing}
/>
<div
className={
editorMode === "form" && !workflowNameEditing
? "flex items-center"
: "hidden"
}
ref={setNameLeadingElement}
/>
</div>
</div>
<div className="flex items-center gap-2">
<div ref={setHeaderTrailingElement} />
<DialogClose asChild>
<Button
aria-label="Close"
@@ -458,8 +480,8 @@ export function WorkflowDialog({
<WorkflowFormBuilder
channels={channels}
disabled={mutation.isPending}
headerTrailingContainer={headerTrailingElement}
mode={editorMode}
nameLeadingContainer={nameLeadingElement}
onChange={(yaml) => {
mutation.reset();
yamlDefinitionRef.current = yaml;
@@ -147,8 +147,8 @@ function TriggerConfigFields({
type WorkflowFormBuilderProps = {
channels: Channel[];
disabled?: boolean;
headerTrailingContainer?: HTMLElement | null;
mode: WorkflowEditorMode;
nameLeadingContainer?: HTMLElement | null;
onChange: (yaml: string) => void;
onSelectedNodeChange: (node: WorkflowEditorPane) => void;
parseError: string | null;
@@ -365,8 +365,8 @@ function WorkflowNode({
export function WorkflowFormBuilder({
channels,
disabled,
headerTrailingContainer,
mode,
nameLeadingContainer,
onChange,
onSelectedNodeChange,
parseError,
@@ -822,22 +822,18 @@ export function WorkflowFormBuilder({
</div>
)}
</div>
{mode === "form" && headerTrailingContainer
{mode === "form" && nameLeadingContainer
? createPortal(
<div className="flex items-center gap-3">
<label className="text-sm text-foreground" htmlFor="wf-enabled">
Enable
</label>
<Switch
checked={formState.enabled}
disabled={disabled}
id="wf-enabled"
onCheckedChange={(checked) =>
updateFormState({ ...formState, enabled: checked })
}
/>
</div>,
headerTrailingContainer,
<Switch
aria-label="Enable workflow"
checked={formState.enabled}
disabled={disabled}
id="wf-enabled"
onCheckedChange={(checked) =>
updateFormState({ ...formState, enabled: checked })
}
/>,
nameLeadingContainer,
)
: null}
</>