FN-7302: compact quick-add workflow selector
Keep the Quick Add workflow selector compact without sacrificing readable workflow menu options. - Narrow the closed workflow trigger and truncate only its label so action controls keep room. - Portal and viewport-clamp the expanded workflow menu for right-side columns and mobile layouts. - Preserve readable wrapping for long workflow names and duplicate-id subtitles in the menu. - Cover compact trigger sizing, menu readability, and viewport clamping with QuickEntryBox tests. - Add a patch changeset for the published Fusion package. Files changed: .changeset/fn-7302-quick-add-workflow-trigger.md | 7 ++ .../dashboard/app/components/QuickEntryBox.css | 29 ++++-- .../dashboard/app/components/QuickEntryBox.tsx | 114 ++++++++++++++++++++- .../components/__tests__/QuickEntryBox.test.tsx | 79 +++++++++++++- 4 files changed, 211 insertions(+), 18 deletions(-) Fusion-Task-Id: FN-7302 Fusion-Task-Lineage: 4a8042d8-6cc5-4f40-bbcb-71aa5bfb7cd2 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-7302-quick-add-workflow-trigger.md
Normal file
7
.changeset/fn-7302-quick-add-workflow-trigger.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
summary: Make the Quick Add workflow selector compact while keeping long menu names readable.
|
||||||
|
category: fix
|
||||||
|
dev: Narrows only the closed QuickEntryBox workflow trigger; menu width and workflow routing remain unchanged.
|
||||||
@@ -140,14 +140,24 @@ 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:
|
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.
|
Operators need workflow identity at quick-add time. Render shared WorkflowIcon output inline and let no-icon custom workflows collapse without blank icon shells.
|
||||||
|
|
||||||
|
FNXC:QuickAddWorkflow 2026-06-30-14:10:
|
||||||
|
The closed selector should be compact enough to leave room for Save/Fast/Subtask controls, while the opened menu stays wide and viewport-bounded so longer workflow names remain readable.
|
||||||
|
|
||||||
|
FNXC:QuickAddWorkflow 2026-06-30-15:05:
|
||||||
|
Long workflow labels must truncate only in the closed trigger. Menu option text wraps inside the viewport-bounded menu so operators can read the full workflow name and duplicate-id subtitle.
|
||||||
|
|
||||||
|
FNXC:QuickAddWorkflow 2026-06-30-16:16:
|
||||||
|
The wide menu is portaled and fixed-positioned by QuickEntryBox so right-side board columns and mobile wrapped action rows clamp to the viewport instead of overflowing from the compact trigger anchor.
|
||||||
*/
|
*/
|
||||||
.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);
|
flex: 0 1 auto;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
max-width: min(calc(var(--space-xl) * 11), 100%);
|
width: fit-content;
|
||||||
|
max-width: min(calc(var(--space-xl) * 5), 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.quick-entry-workflow-trigger {
|
.quick-entry-workflow-trigger {
|
||||||
@@ -155,9 +165,9 @@ Operators need workflow identity at quick-add time. Keep the trigger wide enough
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
gap: var(--space-xs);
|
gap: var(--space-xs);
|
||||||
width: 100%;
|
width: auto;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
max-width: min(calc(var(--space-xl) * 11), calc(100vw - var(--space-lg)));
|
max-width: min(calc(var(--space-xl) * 5), calc(100vw - var(--space-lg)));
|
||||||
}
|
}
|
||||||
|
|
||||||
.quick-entry-workflow-icon {
|
.quick-entry-workflow-icon {
|
||||||
@@ -172,9 +182,7 @@ Operators need workflow identity at quick-add time. Keep the trigger wide enough
|
|||||||
}
|
}
|
||||||
|
|
||||||
.quick-entry-workflow-menu {
|
.quick-entry-workflow-menu {
|
||||||
position: absolute;
|
position: fixed;
|
||||||
inset-block-start: calc(100% + var(--space-xs));
|
|
||||||
inset-inline-start: 0;
|
|
||||||
width: min(calc(var(--space-xl) * 16), 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)));
|
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));
|
||||||
@@ -203,9 +211,8 @@ Operators need workflow identity at quick-add time. Keep the trigger wide enough
|
|||||||
.quick-entry-workflow-option-id {
|
.quick-entry-workflow-option-id {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
overflow: hidden;
|
overflow-wrap: anywhere;
|
||||||
text-overflow: ellipsis;
|
white-space: normal;
|
||||||
white-space: nowrap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.quick-entry-workflow-option-id {
|
.quick-entry-workflow-option-id {
|
||||||
|
|||||||
@@ -196,6 +196,9 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels,
|
|||||||
);
|
);
|
||||||
const realWorkflowOptions = useMemo(() => getRealWorkflowOptions(workflowOptions), [workflowOptions]);
|
const realWorkflowOptions = useMemo(() => getRealWorkflowOptions(workflowOptions), [workflowOptions]);
|
||||||
const workflowPickerRef = useRef<HTMLDivElement>(null);
|
const workflowPickerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const workflowTriggerRef = useRef<HTMLButtonElement>(null);
|
||||||
|
const workflowPickerPortalRef = useRef<HTMLDivElement>(null);
|
||||||
|
const [workflowPickerPosition, setWorkflowPickerPosition] = useState<{ top: number; left: number; width: number; maxHeight?: number } | null>(null);
|
||||||
const previousWorkflowDefaultRef = useRef<{ workflowId: string | null | undefined; defaultWorkflowId: string | null | undefined }>({ workflowId, defaultWorkflowId });
|
const previousWorkflowDefaultRef = useRef<{ workflowId: string | null | undefined; defaultWorkflowId: string | null | undefined }>({ workflowId, defaultWorkflowId });
|
||||||
const [showWorkflowPicker, setShowWorkflowPicker] = useState(false);
|
const [showWorkflowPicker, setShowWorkflowPicker] = useState(false);
|
||||||
/*
|
/*
|
||||||
@@ -594,7 +597,9 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels,
|
|||||||
const handleClickOutside = (e: MouseEvent) => {
|
const handleClickOutside = (e: MouseEvent) => {
|
||||||
const target = e.target as Node;
|
const target = e.target as Node;
|
||||||
if (workflowPickerRef.current?.contains(target)) return;
|
if (workflowPickerRef.current?.contains(target)) return;
|
||||||
|
if (workflowPickerPortalRef.current?.contains(target)) return;
|
||||||
setShowWorkflowPicker(false);
|
setShowWorkflowPicker(false);
|
||||||
|
setWorkflowPickerPosition(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
document.addEventListener("mousedown", handleClickOutside);
|
document.addEventListener("mousedown", handleClickOutside);
|
||||||
@@ -877,6 +882,7 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels,
|
|||||||
}
|
}
|
||||||
if (showWorkflowPicker) {
|
if (showWorkflowPicker) {
|
||||||
setShowWorkflowPicker(false);
|
setShowWorkflowPicker(false);
|
||||||
|
setWorkflowPickerPosition(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (showAgentPicker) {
|
if (showAgentPicker) {
|
||||||
@@ -1060,6 +1066,56 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels,
|
|||||||
});
|
});
|
||||||
}, [getEffectiveViewport]);
|
}, [getEffectiveViewport]);
|
||||||
|
|
||||||
|
const updateWorkflowPickerPosition = useCallback(() => {
|
||||||
|
const trigger = workflowTriggerRef.current;
|
||||||
|
if (!trigger) return;
|
||||||
|
|
||||||
|
const rect = trigger.getBoundingClientRect();
|
||||||
|
const { width: viewportWidth, height: viewportHeight, offsetTop, offsetLeft } = getEffectiveViewport();
|
||||||
|
const horizontalPadding = 16;
|
||||||
|
const verticalPadding = 16;
|
||||||
|
const gap = 4;
|
||||||
|
const isMobile = viewportWidth <= 768;
|
||||||
|
const preferredHeight = Math.min(viewportHeight * (isMobile ? 0.6 : 0.5), 360);
|
||||||
|
/*
|
||||||
|
FNXC:QuickAddWorkflow 2026-06-30-16:16:
|
||||||
|
The workflow menu is wider than its compact trigger, so portal and clamp it against the visual viewport instead of anchoring it to the trigger's inline start. This keeps right-side Board columns and wrapped mobile action rows readable without horizontal overflow.
|
||||||
|
*/
|
||||||
|
const preferredWidth = isMobile
|
||||||
|
? Math.min(viewportWidth - horizontalPadding * 2, 448)
|
||||||
|
: Math.min(Math.max(rect.width * 3, 448), 512);
|
||||||
|
const width = Math.min(
|
||||||
|
preferredWidth,
|
||||||
|
Math.max(viewportWidth - horizontalPadding * 2, 240),
|
||||||
|
);
|
||||||
|
|
||||||
|
const triggerTop = rect.top - offsetTop;
|
||||||
|
const triggerBottom = rect.bottom - offsetTop;
|
||||||
|
const triggerLeft = rect.left - offsetLeft;
|
||||||
|
const spaceBelow = viewportHeight - triggerBottom;
|
||||||
|
const spaceAbove = triggerTop;
|
||||||
|
const availableBelow = Math.max(spaceBelow - verticalPadding - gap, 180);
|
||||||
|
const availableAbove = Math.max(spaceAbove - verticalPadding - gap, 180);
|
||||||
|
const openUpward = spaceBelow < preferredHeight && spaceAbove > spaceBelow;
|
||||||
|
const maxHeight = Math.max(
|
||||||
|
Math.min(openUpward ? availableAbove : availableBelow, preferredHeight),
|
||||||
|
180,
|
||||||
|
);
|
||||||
|
|
||||||
|
const left = Math.min(
|
||||||
|
Math.max(triggerLeft, horizontalPadding),
|
||||||
|
viewportWidth - horizontalPadding - width,
|
||||||
|
) + offsetLeft;
|
||||||
|
const top = openUpward
|
||||||
|
? Math.max(verticalPadding + offsetTop, triggerTop - maxHeight - gap + offsetTop)
|
||||||
|
: Math.min(
|
||||||
|
triggerBottom + gap + offsetTop,
|
||||||
|
viewportHeight + offsetTop - verticalPadding - maxHeight,
|
||||||
|
);
|
||||||
|
|
||||||
|
setWorkflowPickerPosition({ top, left, width, maxHeight });
|
||||||
|
}, [getEffectiveViewport]);
|
||||||
|
|
||||||
const updateDepDropdownPosition = useCallback(() => {
|
const updateDepDropdownPosition = useCallback(() => {
|
||||||
const trigger = depTriggerRef.current;
|
const trigger = depTriggerRef.current;
|
||||||
if (!trigger) return;
|
if (!trigger) return;
|
||||||
@@ -1337,6 +1393,31 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels,
|
|||||||
};
|
};
|
||||||
}, [isRefineMenuOpen, updateRefineMenuPosition]);
|
}, [isRefineMenuOpen, updateRefineMenuPosition]);
|
||||||
|
|
||||||
|
// Keep workflow picker portal anchored during scroll/resize
|
||||||
|
useEffect(() => {
|
||||||
|
if (!showWorkflowPicker) return;
|
||||||
|
|
||||||
|
const handleReposition = () => updateWorkflowPickerPosition();
|
||||||
|
|
||||||
|
window.addEventListener("resize", handleReposition);
|
||||||
|
window.addEventListener("scroll", handleReposition, true);
|
||||||
|
|
||||||
|
const vv = window.visualViewport;
|
||||||
|
if (vv) {
|
||||||
|
vv.addEventListener("resize", handleReposition);
|
||||||
|
vv.addEventListener("scroll", handleReposition);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("resize", handleReposition);
|
||||||
|
window.removeEventListener("scroll", handleReposition, true);
|
||||||
|
if (vv) {
|
||||||
|
vv.removeEventListener("resize", handleReposition);
|
||||||
|
vv.removeEventListener("scroll", handleReposition);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [showWorkflowPicker, updateWorkflowPickerPosition]);
|
||||||
|
|
||||||
// Keep dependency dropdown portal anchored during scroll/resize
|
// Keep dependency dropdown portal anchored during scroll/resize
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!showDeps) return;
|
if (!showDeps) return;
|
||||||
@@ -1688,6 +1769,7 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels,
|
|||||||
{showWorkflowSelector && (
|
{showWorkflowSelector && (
|
||||||
<div className="quick-entry-workflow-wrap" ref={workflowPickerRef}>
|
<div className="quick-entry-workflow-wrap" ref={workflowPickerRef}>
|
||||||
<button
|
<button
|
||||||
|
ref={workflowTriggerRef}
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-sm dep-trigger quick-entry-workflow-trigger"
|
className="btn btn-sm dep-trigger quick-entry-workflow-trigger"
|
||||||
data-testid="quick-entry-workflow-trigger"
|
data-testid="quick-entry-workflow-trigger"
|
||||||
@@ -1705,12 +1787,21 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels,
|
|||||||
setIsModelMenuOpen(false);
|
setIsModelMenuOpen(false);
|
||||||
setModelMenuPosition(null);
|
setModelMenuPosition(null);
|
||||||
setActiveModelSubmenu(null);
|
setActiveModelSubmenu(null);
|
||||||
setShowWorkflowPicker((prev) => !prev);
|
setShowWorkflowPicker((prev) => {
|
||||||
|
const next = !prev;
|
||||||
|
if (next) {
|
||||||
|
updateWorkflowPickerPosition();
|
||||||
|
} else {
|
||||||
|
setWorkflowPickerPosition(null);
|
||||||
|
}
|
||||||
|
return next;
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === "Escape") {
|
if (e.key === "Escape") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setShowWorkflowPicker(false);
|
setShowWorkflowPicker(false);
|
||||||
|
setWorkflowPickerPosition(null);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
title={t("tasks.quickEntryWorkflowTitle", "Workflow for the next task")}
|
title={t("tasks.quickEntryWorkflowTitle", "Workflow for the next task")}
|
||||||
@@ -1724,8 +1815,21 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels,
|
|||||||
<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>
|
||||||
{showWorkflowPicker && (
|
{showWorkflowPicker && portalRoot && workflowPickerPosition && createPortal(
|
||||||
<div className="dep-dropdown quick-entry-workflow-menu" role="listbox" data-testid="quick-entry-workflow-menu">
|
<div
|
||||||
|
ref={workflowPickerPortalRef}
|
||||||
|
className="dep-dropdown quick-entry-workflow-menu"
|
||||||
|
role="listbox"
|
||||||
|
data-testid="quick-entry-workflow-menu"
|
||||||
|
style={{
|
||||||
|
position: "fixed",
|
||||||
|
top: `${workflowPickerPosition.top}px`,
|
||||||
|
left: `${workflowPickerPosition.left}px`,
|
||||||
|
width: `${workflowPickerPosition.width}px`,
|
||||||
|
maxHeight: workflowPickerPosition.maxHeight ? `${workflowPickerPosition.maxHeight}px` : undefined,
|
||||||
|
overflowY: workflowPickerPosition.maxHeight ? "auto" : undefined,
|
||||||
|
}}
|
||||||
|
>
|
||||||
<div className="dep-dropdown-search-header">{t("tasks.quickEntryWorkflowHeader", "Create in workflow")}</div>
|
<div className="dep-dropdown-search-header">{t("tasks.quickEntryWorkflowHeader", "Create in workflow")}</div>
|
||||||
{realWorkflowOptions.map((option) => {
|
{realWorkflowOptions.map((option) => {
|
||||||
const duplicateName = (quickEntryWorkflowNameCounts.get(option.name) ?? 0) > 1;
|
const duplicateName = (quickEntryWorkflowNameCounts.get(option.name) ?? 0) > 1;
|
||||||
@@ -1745,6 +1849,7 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels,
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
setQuickEntryWorkflowId(option.id);
|
setQuickEntryWorkflowId(option.id);
|
||||||
setShowWorkflowPicker(false);
|
setShowWorkflowPicker(false);
|
||||||
|
setWorkflowPickerPosition(null);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<WorkflowIcon workflowId={option.id} icon={option.icon} className="quick-entry-workflow-icon" decorative />
|
<WorkflowIcon workflowId={option.id} icon={option.icon} className="quick-entry-workflow-icon" decorative />
|
||||||
@@ -1755,7 +1860,8 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels,
|
|||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>,
|
||||||
|
portalRoot,
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1699,17 +1699,90 @@ 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 wider workflow selector", () => {
|
it("keeps long workflow names readable in the menu while the trigger uses a truncation label", () => {
|
||||||
|
const longName = "Long workflow name that should remain readable in the opened menu";
|
||||||
|
renderQuickEntryBox({
|
||||||
|
workflowId: "wf-long",
|
||||||
|
defaultWorkflowId: "wf-default",
|
||||||
|
workflowOptions: [
|
||||||
|
{ id: "wf-default", name: "Coding", columns: [] },
|
||||||
|
{ id: "wf-long", name: longName, icon: "🧭", columns: [] },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const trigger = screen.getByTestId("quick-entry-workflow-trigger");
|
||||||
|
const triggerLabel = trigger.querySelector(".quick-entry-workflow-label");
|
||||||
|
expect(triggerLabel).toHaveTextContent(longName);
|
||||||
|
expect(triggerLabel).toHaveClass("quick-entry-workflow-label");
|
||||||
|
|
||||||
|
fireEvent.click(trigger);
|
||||||
|
expect(screen.getByTestId("quick-entry-workflow-menu")).toHaveTextContent(longName);
|
||||||
|
const optionName = screen.getByTestId("quick-entry-workflow-option-wf-long").querySelector(".quick-entry-workflow-option-name");
|
||||||
|
expect(optionName).toHaveTextContent(longName);
|
||||||
|
expect(optionName).toHaveClass("quick-entry-workflow-option-name");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("clamps the wider workflow menu within the viewport from right-side triggers", () => {
|
||||||
|
const viewportWidth = 800;
|
||||||
|
vi.spyOn(window, "innerWidth", "get").mockReturnValue(viewportWidth);
|
||||||
|
vi.spyOn(window, "innerHeight", "get").mockReturnValue(720);
|
||||||
|
renderQuickEntryBox({
|
||||||
|
workflowId: "wf-long",
|
||||||
|
defaultWorkflowId: "wf-default",
|
||||||
|
workflowOptions: [
|
||||||
|
{ id: "wf-default", name: "Coding", columns: [] },
|
||||||
|
{ id: "wf-long", name: "A long readable workflow name", icon: "🧭", columns: [] },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const trigger = screen.getByTestId("quick-entry-workflow-trigger");
|
||||||
|
vi.spyOn(trigger, "getBoundingClientRect").mockReturnValue({
|
||||||
|
x: 720,
|
||||||
|
y: 100,
|
||||||
|
width: 64,
|
||||||
|
height: 32,
|
||||||
|
top: 100,
|
||||||
|
right: 784,
|
||||||
|
bottom: 132,
|
||||||
|
left: 720,
|
||||||
|
toJSON: () => ({}),
|
||||||
|
} as DOMRect);
|
||||||
|
|
||||||
|
fireEvent.click(trigger);
|
||||||
|
const menu = screen.getByTestId("quick-entry-workflow-menu");
|
||||||
|
const left = parseFloat(menu.style.left);
|
||||||
|
const width = parseFloat(menu.style.width);
|
||||||
|
|
||||||
|
expect(menu.style.position).toBe("fixed");
|
||||||
|
expect(left).toBeGreaterThanOrEqual(16);
|
||||||
|
expect(left + width).toBeLessThanOrEqual(viewportWidth - 16);
|
||||||
|
expect(width).toBeGreaterThan(64);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses tokenized responsive CSS for a compact workflow trigger and readable menu", () => {
|
||||||
|
const wrapRule = cssRuleBody(QUICK_ENTRY_BOX_CSS, ".quick-entry-workflow-wrap");
|
||||||
const triggerRule = cssRuleBody(QUICK_ENTRY_BOX_CSS, ".quick-entry-workflow-trigger");
|
const triggerRule = cssRuleBody(QUICK_ENTRY_BOX_CSS, ".quick-entry-workflow-trigger");
|
||||||
|
const labelRule = cssRuleBody(QUICK_ENTRY_BOX_CSS, ".quick-entry-workflow-label");
|
||||||
const menuRule = cssRuleBody(QUICK_ENTRY_BOX_CSS, ".quick-entry-workflow-menu");
|
const menuRule = cssRuleBody(QUICK_ENTRY_BOX_CSS, ".quick-entry-workflow-menu");
|
||||||
const optionCopyRule = cssRuleBody(QUICK_ENTRY_BOX_CSS, ".quick-entry-workflow-option-copy");
|
const optionCopyRule = cssRuleBody(QUICK_ENTRY_BOX_CSS, ".quick-entry-workflow-option-copy");
|
||||||
|
const optionNameRule = cssRuleBody(QUICK_ENTRY_BOX_CSS, ".quick-entry-workflow-option-name,\n.quick-entry-workflow-option-id");
|
||||||
|
|
||||||
expect(triggerRule).toContain("max-width: min(calc(var(--space-xl) * 11), calc(100vw - var(--space-lg)))");
|
expect(wrapRule).toContain("flex: 0 1 auto");
|
||||||
|
expect(wrapRule).toContain("width: fit-content");
|
||||||
|
expect(wrapRule).toContain("max-width: min(calc(var(--space-xl) * 5), 100%)");
|
||||||
|
expect(triggerRule).toContain("width: auto");
|
||||||
|
expect(triggerRule).toContain("max-width: min(calc(var(--space-xl) * 5), calc(100vw - var(--space-lg)))");
|
||||||
expect(triggerRule).toContain("gap: var(--space-xs)");
|
expect(triggerRule).toContain("gap: var(--space-xs)");
|
||||||
|
expect(labelRule).toContain("text-overflow: ellipsis");
|
||||||
|
expect(menuRule).toContain("position: fixed");
|
||||||
|
expect(menuRule).not.toContain("inset-inline-start: 0");
|
||||||
expect(menuRule).toContain("width: min(calc(var(--space-xl) * 16), calc(100vw - var(--space-lg)))");
|
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(menuRule).toContain("min-width: min(calc(var(--space-xl) * 14), calc(100vw - var(--space-lg)))");
|
||||||
expect(optionCopyRule).toContain("gap: var(--space-2xs)");
|
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(optionNameRule).toContain("overflow-wrap: anywhere");
|
||||||
|
expect(optionNameRule).toContain("white-space: normal");
|
||||||
|
expect(optionNameRule).not.toContain("text-overflow: ellipsis");
|
||||||
|
expect(`${wrapRule}\n${triggerRule}\n${labelRule}\n${menuRule}\n${optionCopyRule}\n${optionNameRule}`).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\)\)\)/);
|
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