FN-7296: show quick-add workflow icons
Improve the quick-add workflow selector so workflow choices are easier to identify before creating a task. - Render shared workflow icons in the quick-add trigger and workflow option list. - Widen the workflow selector/menu with tokenized responsive CSS while avoiding empty custom icon shells. - Cover built-in, custom, and iconless workflow rendering plus widened CSS expectations. - Add a patch changeset for the published Fusion package. Files changed: .changeset/fn-7296-quick-add-workflow-icons.md | 7 ++++ .../dashboard/app/components/QuickEntryBox.css | 45 ++++++++++++++++++++-- .../dashboard/app/components/QuickEntryBox.tsx | 15 +++++++- .../components/__tests__/QuickEntryBox.test.tsx | 41 +++++++++++++++++--- 4 files changed, 98 insertions(+), 10 deletions(-) Fusion-Task-Id: FN-7296 Fusion-Task-Lineage: e493eb27-6957-4fbd-bfb9-903f9ec43b29 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-7296-quick-add-workflow-icons.md
Normal file
7
.changeset/fn-7296-quick-add-workflow-icons.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
summary: Show workflow icons and wider names in the Quick Add workflow selector.
|
||||||
|
category: fix
|
||||||
|
dev: Reuses WorkflowIcon in QuickEntryBox and widens the tokenized selector/menu styles.
|
||||||
@@ -138,15 +138,30 @@ The global `.description-with-refine textarea { padding-right: 70px }` (styles.c
|
|||||||
/*
|
/*
|
||||||
FNXC:QuickAddWorkflow 2026-06-30-00:00:
|
FNXC:QuickAddWorkflow 2026-06-30-00:00:
|
||||||
Quick-add workflow targeting sits in the action row as a compact existing-button dropdown. Use tokenized sizing/colors so the selector wraps with Save/Plan controls on narrow Board and List surfaces without introducing a separate visual hierarchy.
|
Quick-add workflow targeting sits in the action row as a compact existing-button dropdown. Use tokenized sizing/colors so the selector wraps with Save/Plan controls on narrow Board and List surfaces without introducing a separate visual hierarchy.
|
||||||
|
|
||||||
|
FNXC:QuickAddWorkflow 2026-06-30-12:38:
|
||||||
|
Operators need workflow identity at quick-add time. Keep the trigger wide enough for realistic names, render shared WorkflowIcon output inline, and let no-icon custom workflows collapse without blank icon shells.
|
||||||
*/
|
*/
|
||||||
.quick-entry-workflow-wrap {
|
.quick-entry-workflow-wrap {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
flex: 1 1 calc(var(--space-xl) * 8);
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
max-width: min(calc(var(--space-xl) * 11), 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.quick-entry-workflow-trigger {
|
.quick-entry-workflow-trigger {
|
||||||
max-width: calc(var(--space-xl) * 8);
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: var(--space-xs);
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
max-width: min(calc(var(--space-xl) * 11), calc(100vw - var(--space-lg)));
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-entry-workflow-icon {
|
||||||
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.quick-entry-workflow-label {
|
.quick-entry-workflow-label {
|
||||||
@@ -160,7 +175,8 @@ Quick-add workflow targeting sits in the action row as a compact existing-button
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
inset-block-start: calc(100% + var(--space-xs));
|
inset-block-start: calc(100% + var(--space-xs));
|
||||||
inset-inline-start: 0;
|
inset-inline-start: 0;
|
||||||
min-width: min(calc(var(--space-xl) * 10), calc(100vw - var(--space-lg)));
|
width: min(calc(var(--space-xl) * 16), calc(100vw - var(--space-lg)));
|
||||||
|
min-width: min(calc(var(--space-xl) * 14), calc(100vw - var(--space-lg)));
|
||||||
max-width: calc(100vw - var(--space-lg));
|
max-width: calc(100vw - var(--space-lg));
|
||||||
max-height: min(calc(var(--space-xl) * 10), calc(100vh - var(--space-xl) * 2));
|
max-height: min(calc(var(--space-xl) * 10), calc(100vh - var(--space-xl) * 2));
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
@@ -173,6 +189,28 @@ Quick-add workflow targeting sits in the action row as a compact existing-button
|
|||||||
background: transparent;
|
background: transparent;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-entry-workflow-option-copy {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-2xs);
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-entry-workflow-option-name,
|
||||||
|
.quick-entry-workflow-option-id {
|
||||||
|
min-width: 0;
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-entry-workflow-option-id {
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-family: var(--font-mono);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
@@ -182,7 +220,8 @@ Quick-add workflow targeting sits in the action row as a compact existing-button
|
|||||||
}
|
}
|
||||||
|
|
||||||
.quick-entry-workflow-menu {
|
.quick-entry-workflow-menu {
|
||||||
min-width: min(calc(var(--space-xl) * 9), calc(100vw - var(--space-lg)));
|
width: min(calc(var(--space-xl) * 14), calc(100vw - var(--space-lg)));
|
||||||
|
min-width: min(calc(var(--space-xl) * 11), calc(100vw - var(--space-lg)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { useNodes } from "../hooks/useNodes";
|
|||||||
import { NodeHealthDot } from "./NodeHealthDot";
|
import { NodeHealthDot } from "./NodeHealthDot";
|
||||||
import { ProviderIcon } from "./ProviderIcon";
|
import { ProviderIcon } from "./ProviderIcon";
|
||||||
import { WorkflowOptionalStepsDropdown } from "./WorkflowOptionalStepsDropdown";
|
import { WorkflowOptionalStepsDropdown } from "./WorkflowOptionalStepsDropdown";
|
||||||
|
import { WorkflowIcon } from "./WorkflowIcon";
|
||||||
|
|
||||||
const STORAGE_KEY = "kb-quick-entry-text";
|
const STORAGE_KEY = "kb-quick-entry-text";
|
||||||
const ALLOWED_IMAGE_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
|
const ALLOWED_IMAGE_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
|
||||||
@@ -309,6 +310,7 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels,
|
|||||||
}, [realWorkflowOptions]);
|
}, [realWorkflowOptions]);
|
||||||
const showWorkflowSelector = workflowId !== undefined && realWorkflowOptions.length >= 2 && quickEntryWorkflowId !== null;
|
const showWorkflowSelector = workflowId !== undefined && realWorkflowOptions.length >= 2 && quickEntryWorkflowId !== null;
|
||||||
const quickEntryWorkflowLabel = selectedQuickEntryWorkflow?.name ?? t("tasks.workflow", "Workflow");
|
const quickEntryWorkflowLabel = selectedQuickEntryWorkflow?.name ?? t("tasks.workflow", "Workflow");
|
||||||
|
const selectedQuickEntryWorkflowIcon = selectedQuickEntryWorkflow?.icon;
|
||||||
const selectedWorkflowForCreate = workflowId === undefined ? undefined : quickEntryWorkflowId;
|
const selectedWorkflowForCreate = workflowId === undefined ? undefined : quickEntryWorkflowId;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -1693,6 +1695,12 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels,
|
|||||||
}}
|
}}
|
||||||
title={t("tasks.quickEntryWorkflowTitle", "Workflow for the next task")}
|
title={t("tasks.quickEntryWorkflowTitle", "Workflow for the next task")}
|
||||||
>
|
>
|
||||||
|
<WorkflowIcon
|
||||||
|
workflowId={selectedQuickEntryWorkflow?.id ?? quickEntryWorkflowId ?? ""}
|
||||||
|
icon={selectedQuickEntryWorkflowIcon}
|
||||||
|
className="quick-entry-workflow-icon"
|
||||||
|
decorative
|
||||||
|
/>
|
||||||
<span className="quick-entry-workflow-label">{quickEntryWorkflowLabel}</span>
|
<span className="quick-entry-workflow-label">{quickEntryWorkflowLabel}</span>
|
||||||
<ChevronDown size={12} aria-hidden="true" />
|
<ChevronDown size={12} aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
@@ -1719,8 +1727,11 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels,
|
|||||||
setShowWorkflowPicker(false);
|
setShowWorkflowPicker(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="dep-dropdown-title">{option.name}</span>
|
<WorkflowIcon workflowId={option.id} icon={option.icon} className="quick-entry-workflow-icon" decorative />
|
||||||
{duplicateName ? <span className="dep-dropdown-subtitle">{option.id}</span> : null}
|
<span className="quick-entry-workflow-option-copy">
|
||||||
|
<span className="dep-dropdown-title quick-entry-workflow-option-name">{option.name}</span>
|
||||||
|
{duplicateName ? <span className="dep-dropdown-subtitle quick-entry-workflow-option-id">{option.id}</span> : null}
|
||||||
|
</span>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -1655,6 +1655,30 @@ describe("QuickEntryBox", () => {
|
|||||||
expect(fetchWorkflowOptionalSteps).not.toHaveBeenCalledWith("__all_workflows__", TEST_PROJECT_ID);
|
expect(fetchWorkflowOptionalSteps).not.toHaveBeenCalledWith("__all_workflows__", TEST_PROJECT_ID);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("renders built-in and custom workflow icons without blank custom-icon shells", () => {
|
||||||
|
renderQuickEntryBox({
|
||||||
|
workflowId: "builtin:coding",
|
||||||
|
defaultWorkflowId: "builtin:coding",
|
||||||
|
workflowOptions: [
|
||||||
|
{ id: "builtin:coding", name: "Coding", columns: [] },
|
||||||
|
{ id: "wf-custom", name: "Custom workflow", icon: "🚀", columns: [] },
|
||||||
|
{ id: "wf-no-icon", name: "No icon workflow", columns: [] },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const trigger = screen.getByTestId("quick-entry-workflow-trigger");
|
||||||
|
expect(trigger.querySelector(".workflow-icon--builtin")).toBeTruthy();
|
||||||
|
expect(trigger.querySelector(".quick-entry-workflow-label")).toHaveTextContent("Coding");
|
||||||
|
|
||||||
|
fireEvent.click(trigger);
|
||||||
|
const customOption = screen.getByTestId("quick-entry-workflow-option-wf-custom");
|
||||||
|
expect(customOption.querySelector(".workflow-icon--custom")).toHaveTextContent("🚀");
|
||||||
|
expect(screen.getByTestId("quick-entry-workflow-option-wf-no-icon").querySelector(".workflow-icon")).toBeNull();
|
||||||
|
|
||||||
|
fireEvent.click(customOption);
|
||||||
|
expect(screen.getByTestId("quick-entry-workflow-trigger").querySelector(".workflow-icon--custom")).toHaveTextContent("🚀");
|
||||||
|
});
|
||||||
|
|
||||||
it("hides the selector without real workflow choices and leaves no shell", () => {
|
it("hides the selector without real workflow choices and leaves no shell", () => {
|
||||||
renderQuickEntryBox({ workflowId: "wf-default", workflowOptions: [{ id: "wf-default", name: "Coding", columns: [] }] });
|
renderQuickEntryBox({ workflowId: "wf-default", workflowOptions: [{ id: "wf-default", name: "Coding", columns: [] }] });
|
||||||
|
|
||||||
@@ -1662,11 +1686,18 @@ describe("QuickEntryBox", () => {
|
|||||||
expect(screen.getByTestId("quick-entry-actions").querySelector(".quick-entry-workflow-wrap")).toBeNull();
|
expect(screen.getByTestId("quick-entry-actions").querySelector(".quick-entry-workflow-wrap")).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("uses tokenized responsive CSS for the workflow selector", () => {
|
it("uses tokenized responsive CSS for the wider workflow selector", () => {
|
||||||
const selectorRule = cssRuleBody(QUICK_ENTRY_BOX_CSS, ".quick-entry-workflow-menu");
|
const triggerRule = cssRuleBody(QUICK_ENTRY_BOX_CSS, ".quick-entry-workflow-trigger");
|
||||||
expect(selectorRule).toContain("var(--space-");
|
const menuRule = cssRuleBody(QUICK_ENTRY_BOX_CSS, ".quick-entry-workflow-menu");
|
||||||
expect(selectorRule).not.toMatch(/#[0-9a-f]{3,8}|rgb\(/i);
|
const optionCopyRule = cssRuleBody(QUICK_ENTRY_BOX_CSS, ".quick-entry-workflow-option-copy");
|
||||||
expect(QUICK_ENTRY_BOX_CSS).toMatch(/@media \(max-width: 768px\) \{[\s\S]*?quick-entry-workflow/);
|
|
||||||
|
expect(triggerRule).toContain("max-width: min(calc(var(--space-xl) * 11), calc(100vw - var(--space-lg)))");
|
||||||
|
expect(triggerRule).toContain("gap: var(--space-xs)");
|
||||||
|
expect(menuRule).toContain("width: min(calc(var(--space-xl) * 16), calc(100vw - var(--space-lg)))");
|
||||||
|
expect(menuRule).toContain("min-width: min(calc(var(--space-xl) * 14), calc(100vw - var(--space-lg)))");
|
||||||
|
expect(optionCopyRule).toContain("gap: var(--space-2xs)");
|
||||||
|
expect(`${triggerRule}\n${menuRule}\n${optionCopyRule}`).not.toMatch(/#[0-9a-f]{3,8}|rgb\(|\d+px/i);
|
||||||
|
expect(QUICK_ENTRY_BOX_CSS).toMatch(/@media \(max-width: 768px\) \{[\s\S]*?quick-entry-workflow-menu[\s\S]*?min-width: min\(calc\(var\(--space-xl\) \* 11\), calc\(100vw - var\(--space-lg\)\)\)/);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user