Keep the footer engine controls reachable on constrained mobile and tablet viewports. - Render footer engine controls as a fixed full-width bottom panel through tablet widths while preserving the desktop anchored popover. - Account for executor footer, mobile navigation, safe-area, standalone, and ICB offsets in panel placement. - Add CSS regression coverage, dashboard documentation, and a patch changeset. Files changed: .changeset/fn-7273-mobile-engine-controls-menu.md | 7 ++ docs/dashboard-guide.md | 3 +- .../dashboard/app/components/EngineControlMenu.css | 38 +++++++++- .../__tests__/EngineControlMenu.css.test.ts | 88 +++++++++++++++++++++- 4 files changed, 128 insertions(+), 8 deletions(-) Fusion-Task-Id: FN-7273 Fusion-Task-Lineage: d4764555-c8eb-4086-9130-d2f568727ae1 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
207 lines
6.5 KiB
CSS
207 lines
6.5 KiB
CSS
.engine-control-menu {
|
|
position: relative;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.engine-control-menu__trigger {
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.engine-control-menu__trigger:hover {
|
|
color: var(--text);
|
|
}
|
|
|
|
.engine-control-menu__popover {
|
|
position: absolute;
|
|
right: 0;
|
|
bottom: calc(100% + var(--space-xs));
|
|
z-index: 70;
|
|
width: min(24rem, calc(100vw - (var(--space-lg) * 2)));
|
|
max-height: min(32rem, calc(100vh - var(--space-2xl)));
|
|
overflow: auto;
|
|
padding: var(--space-md);
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--space-md);
|
|
background: var(--card);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-lg);
|
|
box-shadow: var(--shadow-lg);
|
|
color: var(--text);
|
|
}
|
|
|
|
.engine-control-menu__header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: flex-end;
|
|
margin-block-end: calc(var(--space-xs) * -1);
|
|
}
|
|
|
|
.engine-control-menu__close {
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.engine-control-menu__close:hover {
|
|
color: var(--text);
|
|
}
|
|
|
|
.engine-control-menu__section {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--space-sm);
|
|
}
|
|
|
|
.engine-control-menu__section--actions {
|
|
gap: var(--space-xs);
|
|
}
|
|
|
|
.engine-control-menu__action {
|
|
width: 100%;
|
|
justify-content: flex-start;
|
|
gap: var(--space-sm);
|
|
}
|
|
|
|
.engine-control-menu__action:disabled {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.engine-control-menu__section-header,
|
|
.engine-control-menu__slider-label {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: var(--space-md);
|
|
}
|
|
|
|
.engine-control-menu__section-header {
|
|
color: var(--text);
|
|
font-weight: 600;
|
|
}
|
|
|
|
.engine-control-menu__save-state {
|
|
color: var(--text-muted);
|
|
font-size: var(--font-size-xs);
|
|
font-weight: 500;
|
|
}
|
|
|
|
/* FNXC:GlobalConcurrencyControls 2026-06-25-22:45: "All projects" scope caption sits between the global-cap title and its save-state indicator; muted so the save-state remains the emphasized signal. */
|
|
.engine-control-menu__scope-caption {
|
|
margin-inline-start: auto;
|
|
color: var(--text-muted);
|
|
font-size: var(--font-size-xs);
|
|
font-weight: 500;
|
|
}
|
|
|
|
.engine-control-menu__save-state--saving {
|
|
color: var(--color-warning);
|
|
}
|
|
|
|
.engine-control-menu__save-state--saved {
|
|
color: var(--color-success);
|
|
}
|
|
|
|
.engine-control-menu__save-state--error,
|
|
.engine-control-menu__error {
|
|
color: var(--color-error);
|
|
}
|
|
|
|
/* FNXC:GlobalConcurrencyControls 2026-06-25-14:10: The global cap is a cross-project setting; separate it visually from the per-project sliders below with a divider and an "All projects" caption. */
|
|
.engine-control-menu__section--global {
|
|
padding-bottom: var(--space-sm);
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
|
|
.engine-control-menu__slider {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--space-xs);
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.engine-control-menu__slider-label strong {
|
|
color: var(--text);
|
|
font-family: var(--font-mono);
|
|
}
|
|
|
|
.engine-control-menu__slider-meta {
|
|
color: var(--text-muted);
|
|
font-size: var(--font-size-xs);
|
|
}
|
|
|
|
.engine-control-menu__range-wrap {
|
|
--engine-control-range-thumb-size: var(--space-md);
|
|
position: relative;
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.engine-control-menu__range {
|
|
width: 100%;
|
|
accent-color: var(--accent);
|
|
}
|
|
|
|
/* FNXC:GlobalConcurrencyControls 2026-06-26-06:26: The current-use marker reuses the global .status-dot convention and is positioned with logical inset properties so utilization is visible on LTR/RTL slider tracks without intercepting drag input. */
|
|
.engine-control-menu__use-marker {
|
|
--use-pct: 0%;
|
|
--use-offset: 0%;
|
|
position: absolute;
|
|
inset-block-start: 50%;
|
|
inset-inline-start: var(--use-offset, var(--use-pct));
|
|
transform: translate(-50%, -50%);
|
|
pointer-events: none;
|
|
}
|
|
|
|
.engine-control-menu__error {
|
|
margin: 0;
|
|
font-size: var(--font-size-xs);
|
|
}
|
|
|
|
.executor-status-bar__segment--engine-controls .engine-control-menu > .engine-control-menu__popover.card {
|
|
position: absolute;
|
|
right: 0;
|
|
bottom: calc(100% + var(--space-xs));
|
|
}
|
|
|
|
@media (max-width: 1024px) {
|
|
.engine-control-menu__popover {
|
|
/*
|
|
FNXC:EngineControls 2026-06-30-07:40:
|
|
The footer Engine Controls menu opens from a fixed mobile footer, so the mobile panel must not inherit desktop corner anchoring. Keep it viewport-fixed, full-width between token gutters, scrollable, and lifted above the executor footer plus mobile nav stack including safe-area, standalone, and ICB compensation.
|
|
|
|
FNXC:EngineControls 2026-06-30-16:10:
|
|
Browser verification showed the footer context can lose this low-specificity mobile override to later footer/card rules and collapse to the trigger's narrow corner. Keep a matching higher-specificity footer rule below so position:fixed and the full-width safe gutters win in the actual ExecutorStatusBar renderer.
|
|
|
|
FNXC:EngineControls 2026-06-30-16:25:
|
|
Tablet browser verification at 820px still overflowed with the desktop anchored popover. Extend the viewport-safe panel breakpoint through common narrow tablets so the footer trigger cannot place a 24rem menu outside the visible inline bounds.
|
|
|
|
FNXC:EngineControls 2026-06-30-16:42:
|
|
Browser verification at 1024px landscape showed the footer popover could still inherit the shared .card position and render off-screen. Keep the viewport-safe footer override active through that tablet width, and keep a footer-specific desktop rule outside the media query so .card cannot reset the anchored popover on wider screens.
|
|
*/
|
|
--engine-control-mobile-bottom-stack: calc(var(--executor-footer-height) + var(--mobile-nav-height) + max(env(safe-area-inset-bottom, 0px), var(--space-md)) + var(--standalone-bottom-gap) + var(--icb-bottom-offset, 0px));
|
|
position: fixed;
|
|
left: var(--space-sm);
|
|
right: var(--space-sm);
|
|
bottom: var(--engine-control-mobile-bottom-stack);
|
|
width: auto;
|
|
max-height: min(28rem, calc(100dvh - var(--engine-control-mobile-bottom-stack) - (var(--space-md) * 2)));
|
|
overflow: auto;
|
|
}
|
|
|
|
.executor-status-bar__segment--engine-controls .engine-control-menu > .engine-control-menu__popover.card {
|
|
--engine-control-mobile-bottom-stack: calc(var(--executor-footer-height) + var(--mobile-nav-height) + max(env(safe-area-inset-bottom, 0px), var(--space-md)) + var(--standalone-bottom-gap) + var(--icb-bottom-offset, 0px));
|
|
position: fixed;
|
|
left: var(--space-sm);
|
|
right: var(--space-sm);
|
|
bottom: var(--engine-control-mobile-bottom-stack);
|
|
width: auto;
|
|
max-height: min(28rem, calc(100dvh - var(--engine-control-mobile-bottom-stack) - (var(--space-md) * 2)));
|
|
overflow: auto;
|
|
}
|
|
|
|
.engine-control-menu__header {
|
|
justify-content: flex-end;
|
|
}
|
|
}
|