mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 01:21:20 -04:00
fixing pod collapse expandings
This commit is contained in:
+2
-2
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -12,8 +12,8 @@
|
|||||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||||
<!-- site-metadata:inject -->
|
<!-- site-metadata:inject -->
|
||||||
<!-- analytics:inject -->
|
<!-- analytics:inject -->
|
||||||
<script type="module" crossorigin src="/assets/index-CAUR32ig.js"></script>
|
<script type="module" crossorigin src="/assets/index-D9i-ZujG.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-D2qTDWX0.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-D5gzYj1F.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ export default function BottomLeftPod({ roverId }) {
|
|||||||
) : (
|
) : (
|
||||||
<CornerPodToggle corner="bottom-left" expanded={false} label="Show rover controls" onClick={() => setOpen(true)} />
|
<CornerPodToggle corner="bottom-left" expanded={false} label="Show rover controls" onClick={() => setOpen(true)} />
|
||||||
)}
|
)}
|
||||||
{hornDevice ? <HornSettingsExpansion open={hornSettingsOpen} podOpen={open} onOpenChange={setHornSettingsOpen} /> : null}
|
{hornDevice ? <HornSettingsExpansion open={hornSettingsOpen} onOpenChange={setHornSettingsOpen} /> : null}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
// HUD Expansion Panel
|
||||||
|
// Purpose: Gives every independent HUD expansion one fixed toggle anchor and identical open/closed behavior.
|
||||||
|
import ExpansionToggle from './ExpansionToggle.jsx';
|
||||||
|
|
||||||
|
export default function ExpansionPanel({
|
||||||
|
open,
|
||||||
|
onOpenChange,
|
||||||
|
anchorClassName = '',
|
||||||
|
panelClassName = '',
|
||||||
|
panelAlign = 'left',
|
||||||
|
panelVerticalAlign = 'top',
|
||||||
|
openDirection,
|
||||||
|
closeDirection,
|
||||||
|
openLabel,
|
||||||
|
closeLabel,
|
||||||
|
children,
|
||||||
|
}) {
|
||||||
|
const panelAlignmentClass = panelAlign === 'right' ? 'right-0' : 'left-0';
|
||||||
|
const panelVerticalAlignmentClass = panelVerticalAlign === 'bottom' ? 'bottom-0' : 'top-0';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`pointer-events-auto z-20 h-3 w-8 ${anchorClassName}`}>
|
||||||
|
{/* The content is positioned behind the fixed 32-by-12 anchor. Opening or
|
||||||
|
closing changes only whether this panel exists; it never relocates or
|
||||||
|
restyles the toggle that the user just clicked. */}
|
||||||
|
{open ? (
|
||||||
|
<div className={`absolute z-0 ${panelAlignmentClass} ${panelVerticalAlignmentClass} ${panelClassName}`}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
<ExpansionToggle
|
||||||
|
direction={open ? closeDirection : openDirection}
|
||||||
|
label={open ? closeLabel : openLabel}
|
||||||
|
onClick={() => onOpenChange(!open)}
|
||||||
|
className="absolute left-0 top-0"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -6,16 +6,15 @@ const ICONS = { down: FaChevronDown, left: FaChevronLeft, right: FaChevronRight,
|
|||||||
|
|
||||||
export default function ExpansionToggle({ direction, label, onClick, className = '' }) {
|
export default function ExpansionToggle({ direction, label, onClick, className = '' }) {
|
||||||
const Icon = ICONS[direction] || FaChevronLeft;
|
const Icon = ICONS[direction] || FaChevronLeft;
|
||||||
const horizontalEdge = direction === 'up' || direction === 'down';
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
aria-label={label}
|
aria-label={label}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className={`flex shrink-0 items-center justify-center bg-black/60 text-[0.5rem] text-white/75 hover:bg-black hover:text-white ${horizontalEdge ? 'h-3 w-8' : 'h-8 w-3'} ${className}`}
|
className={`z-10 flex h-3 w-8 shrink-0 items-center justify-center bg-black/60 text-[0.5rem] text-white/75 hover:bg-black hover:text-white ${className}`}
|
||||||
>
|
>
|
||||||
{/* The black strip is intentionally retained around the chevron. A collapsed expansion is
|
{/* ExpansionPanel owns all positioning so this button has exactly one visual
|
||||||
therefore still a thin piece of that expansion, never a loose button over a pod. */}
|
form. Only the chevron direction changes between open and closed states. */}
|
||||||
<Icon aria-hidden="true" />
|
<Icon aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { useCallback } from 'react';
|
|||||||
import { HORN_MAX_FREQUENCY } from '../../../../controls/constants.js';
|
import { HORN_MAX_FREQUENCY } from '../../../../controls/constants.js';
|
||||||
import { useSettingsNamespace } from '../../../../settings/index.js';
|
import { useSettingsNamespace } from '../../../../settings/index.js';
|
||||||
import { HORN_SETTINGS_DEFAULTS } from '../../../../settings/namespaces.js';
|
import { HORN_SETTINGS_DEFAULTS } from '../../../../settings/namespaces.js';
|
||||||
import ExpansionToggle from './ExpansionToggle.jsx';
|
import ExpansionPanel from './ExpansionPanel.jsx';
|
||||||
|
|
||||||
function clampFrequency(value) {
|
function clampFrequency(value) {
|
||||||
const numeric = Number(value);
|
const numeric = Number(value);
|
||||||
@@ -12,7 +12,7 @@ function clampFrequency(value) {
|
|||||||
return Math.min(HORN_MAX_FREQUENCY, Math.round(numeric));
|
return Math.min(HORN_MAX_FREQUENCY, Math.round(numeric));
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function HornSettingsExpansion({ open, podOpen, onOpenChange }) {
|
export default function HornSettingsExpansion({ open, onOpenChange }) {
|
||||||
const { value, save } = useSettingsNamespace('horn', HORN_SETTINGS_DEFAULTS);
|
const { value, save } = useSettingsNamespace('horn', HORN_SETTINGS_DEFAULTS);
|
||||||
const waveform = value?.waveform === 'sine' ? 'sine' : 'saw';
|
const waveform = value?.waveform === 'sine' ? 'sine' : 'saw';
|
||||||
const frequencies = [...(Array.isArray(value?.freqs) ? value.freqs : HORN_SETTINGS_DEFAULTS.freqs), 0, 0, 0, 0]
|
const frequencies = [...(Array.isArray(value?.freqs) ? value.freqs : HORN_SETTINGS_DEFAULTS.freqs), 0, 0, 0, 0]
|
||||||
@@ -31,21 +31,25 @@ export default function HornSettingsExpansion({ open, podOpen, onOpenChange }) {
|
|||||||
});
|
});
|
||||||
}, [save]);
|
}, [save]);
|
||||||
|
|
||||||
if (!open) {
|
|
||||||
return (
|
|
||||||
<div className={`pointer-events-auto absolute left-10 z-20 flex h-3 w-8 bg-black/60 ${podOpen ? 'bottom-40' : 'bottom-0'}`}>
|
|
||||||
<ExpansionToggle direction="up" label="Show horn settings" onClick={() => onOpenChange(true)} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`pointer-events-auto absolute left-0 z-20 w-52 rounded-tr-xl bg-black/60 p-2 text-xs text-white ${podOpen ? 'bottom-40' : 'bottom-0'}`}>
|
/* Horn settings belongs to the left video wall, not to the peripheral pod.
|
||||||
|
Keeping this anchor fixed above the pod's maximum footprint means the
|
||||||
|
expansion never follows or overlaps the pod as that separate UI closes.
|
||||||
|
Its arrows mirror advanced info on the opposite wall: open points into
|
||||||
|
the screen and close points back toward the wall. */
|
||||||
|
<ExpansionPanel
|
||||||
|
open={open}
|
||||||
|
onOpenChange={onOpenChange}
|
||||||
|
anchorClassName="absolute bottom-40 left-0"
|
||||||
|
panelVerticalAlign="bottom"
|
||||||
|
panelClassName="w-52 rounded-tr-xl bg-black/60 p-2 pb-4 text-xs text-white"
|
||||||
|
openDirection="right"
|
||||||
|
closeDirection="left"
|
||||||
|
openLabel="Show horn settings"
|
||||||
|
closeLabel="Hide horn settings"
|
||||||
|
>
|
||||||
<div className="mb-2 flex items-center justify-between gap-2">
|
<div className="mb-2 flex items-center justify-between gap-2">
|
||||||
<span className="font-semibold text-cyan-100">Horn settings</span>
|
<span className="font-semibold text-cyan-100">Horn settings</span>
|
||||||
{/* This arrow belongs to the expansion. Closing the peripheral pod never changes
|
|
||||||
hornSettings visibility; it only moves this panel into the vacated corner. */}
|
|
||||||
<ExpansionToggle direction="down" label="Hide horn settings" onClick={() => onOpenChange(false)} />
|
|
||||||
</div>
|
</div>
|
||||||
<label className="flex items-center justify-between gap-2 text-slate-300">
|
<label className="flex items-center justify-between gap-2 text-slate-300">
|
||||||
<span>Wave</span>
|
<span>Wave</span>
|
||||||
@@ -73,6 +77,6 @@ export default function HornSettingsExpansion({ open, podOpen, onOpenChange }) {
|
|||||||
</label>
|
</label>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</ExpansionPanel>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { useSessionSelector } from '../../../../context/SessionContext.jsx';
|
|||||||
import { useSharedClock } from '../../../../hooks/useSharedClock.js';
|
import { useSharedClock } from '../../../../hooks/useSharedClock.js';
|
||||||
import RoverLabel from '../../../RoverLabel/index.jsx';
|
import RoverLabel from '../../../RoverLabel/index.jsx';
|
||||||
import CornerPodToggle from './CornerPodToggle.jsx';
|
import CornerPodToggle from './CornerPodToggle.jsx';
|
||||||
import ExpansionToggle from './ExpansionToggle.jsx';
|
import ExpansionPanel from './ExpansionPanel.jsx';
|
||||||
import usePodVisibility from './usePodVisibility.js';
|
import usePodVisibility from './usePodVisibility.js';
|
||||||
|
|
||||||
export default function TopLeftPod({ roverId }) {
|
export default function TopLeftPod({ roverId }) {
|
||||||
@@ -159,16 +159,18 @@ export default function TopLeftPod({ roverId }) {
|
|||||||
{/* The rover name is an independent edge expansion. Its visibility control lives in
|
{/* The rover name is an independent edge expansion. Its visibility control lives in
|
||||||
the expansion itself, and its position naturally moves into the corner whenever
|
the expansion itself, and its position naturally moves into the corner whenever
|
||||||
the conditional timer pod is absent or manually collapsed. */}
|
the conditional timer pod is absent or manually collapsed. */}
|
||||||
{nameOpen ? (
|
<ExpansionPanel
|
||||||
<div className={`flex h-11 items-center gap-2 bg-black/60 px-2 ${showTimer ? '' : 'rounded-br-xl'}`}>
|
open={nameOpen}
|
||||||
|
onOpenChange={setNameOpen}
|
||||||
|
anchorClassName={`relative shrink-0 ${showTimer || !turnActive ? '' : 'ml-10'}`}
|
||||||
|
panelClassName={`flex h-11 min-w-max items-center gap-2 bg-black/60 px-2 pt-3 ${showTimer ? '' : 'rounded-br-xl'}`}
|
||||||
|
openDirection="down"
|
||||||
|
closeDirection="up"
|
||||||
|
openLabel="Show rover name"
|
||||||
|
closeLabel="Hide rover name"
|
||||||
|
>
|
||||||
<RoverLabel roverId={roverId} fallback={roverId} className="px-2 py-1 text-base" />
|
<RoverLabel roverId={roverId} fallback={roverId} className="px-2 py-1 text-base" />
|
||||||
<ExpansionToggle direction="up" label="Hide rover name" onClick={() => setNameOpen(false)} />
|
</ExpansionPanel>
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className={`flex h-3 w-8 bg-black/60 ${showTimer ? '' : 'ml-10'}`}>
|
|
||||||
<ExpansionToggle direction="down" label="Show rover name" onClick={() => setNameOpen(true)} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { useSessionSelector } from '../../../../context/SessionContext.jsx';
|
|||||||
import { useTelemetrySelector } from '../../../../context/TelemetryContext.jsx';
|
import { useTelemetrySelector } from '../../../../context/TelemetryContext.jsx';
|
||||||
import { hostStatsEqual, selectHostStats, selectSpectatorTelemetry, spectatorTelemetryEqual } from '../../../../context/telemetryViews.js';
|
import { hostStatsEqual, selectHostStats, selectSpectatorTelemetry, spectatorTelemetryEqual } from '../../../../context/telemetryViews.js';
|
||||||
import CornerPodToggle from './CornerPodToggle.jsx';
|
import CornerPodToggle from './CornerPodToggle.jsx';
|
||||||
import ExpansionToggle from './ExpansionToggle.jsx';
|
import ExpansionPanel from './ExpansionPanel.jsx';
|
||||||
import usePodVisibility from './usePodVisibility.js';
|
import usePodVisibility from './usePodVisibility.js';
|
||||||
|
|
||||||
function finite(value) {
|
function finite(value) {
|
||||||
@@ -135,9 +135,17 @@ export default function TopRightPod({ roverId }) {
|
|||||||
|
|
||||||
{/* Advanced power is an independently persisted right-edge expansion. Its own arrow is
|
{/* Advanced power is an independently persisted right-edge expansion. Its own arrow is
|
||||||
retained when closed, and the whole panel moves into the corner if the pod closes. */}
|
retained when closed, and the whole panel moves into the corner if the pod closes. */}
|
||||||
{powerOpen ? (
|
<ExpansionPanel
|
||||||
<div className={`absolute right-0 w-56 rounded-bl-xl bg-black/60 p-1.5 pl-4 text-white ${batteryOpen ? 'top-[8.5rem]' : 'top-0'}`}>
|
open={powerOpen}
|
||||||
<ExpansionToggle direction="right" label="Hide power and computer" onClick={() => setPowerOpen(false)} className="absolute left-0 top-1/2 -translate-y-1/2" />
|
onOpenChange={setPowerOpen}
|
||||||
|
anchorClassName={`absolute right-0 ${batteryOpen ? 'top-[8.5rem]' : 'top-10'}`}
|
||||||
|
panelAlign="right"
|
||||||
|
panelClassName="w-56 rounded-bl-xl bg-black/60 p-1.5 pt-4 text-white"
|
||||||
|
openDirection="left"
|
||||||
|
closeDirection="right"
|
||||||
|
openLabel="Show power and computer"
|
||||||
|
closeLabel="Hide power and computer"
|
||||||
|
>
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<MetricRow icon={FaBolt} label="Roomba voltage" value={voltage == null ? '--' : `${(voltage / 1000).toFixed(1)} V`} percent={voltagePercent} iconClass="text-sky-300" fillClass="bg-sky-400" />
|
<MetricRow icon={FaBolt} label="Roomba voltage" value={voltage == null ? '--' : `${(voltage / 1000).toFixed(1)} V`} percent={voltagePercent} iconClass="text-sky-300" fillClass="bg-sky-400" />
|
||||||
<MetricRow icon={FaBolt} label="Roomba current" value={`${current > 0 ? '+' : ''}${Math.round(current)} mA`} percent={currentPercent * 100} iconClass={current < 0 ? 'text-amber-300' : 'text-emerald-300'} fillClass={current < 0 ? 'bg-amber-400' : 'bg-emerald-400'} />
|
<MetricRow icon={FaBolt} label="Roomba current" value={`${current > 0 ? '+' : ''}${Math.round(current)} mA`} percent={currentPercent * 100} iconClass={current < 0 ? 'text-amber-300' : 'text-emerald-300'} fillClass={current < 0 ? 'bg-amber-400' : 'bg-emerald-400'} />
|
||||||
@@ -148,12 +156,7 @@ export default function TopRightPod({ roverId }) {
|
|||||||
<SpeedTile icon={FaArrowDown} label="Download speed" value={download == null ? '--' : `${download.toFixed(1)} Mb/s`} colorClass="text-sky-300" />
|
<SpeedTile icon={FaArrowDown} label="Download speed" value={download == null ? '--' : `${download.toFixed(1)} Mb/s`} colorClass="text-sky-300" />
|
||||||
<SpeedTile icon={FaArrowUp} label="Upload speed" value={upload == null ? '--' : `${upload.toFixed(1)} Mb/s`} colorClass="text-violet-300" />
|
<SpeedTile icon={FaArrowUp} label="Upload speed" value={upload == null ? '--' : `${upload.toFixed(1)} Mb/s`} colorClass="text-violet-300" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</ExpansionPanel>
|
||||||
) : (
|
|
||||||
<div className={`absolute right-0 flex h-8 w-3 bg-black/60 ${batteryOpen ? 'top-[8.5rem]' : 'top-10'}`}>
|
|
||||||
<ExpansionToggle direction="left" label="Show power and computer" onClick={() => setPowerOpen(true)} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Battery danger is a stage-level warning, so it belongs near the user's focus instead
|
{/* Battery danger is a stage-level warning, so it belongs near the user's focus instead
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Scope: Owns presentation and the existing manual-assist lifecycle for the current driver HUD;
|
// Scope: Owns presentation and the existing manual-assist lifecycle for the current driver HUD;
|
||||||
// the archived desktop layout retains its previous DriveDockAction behavior.
|
// the archived desktop layout retains its previous DriveDockAction behavior.
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { FaChargingStation, FaChevronDown } from 'react-icons/fa';
|
import { FaChargingStation } from 'react-icons/fa';
|
||||||
import { useControlActions, useControlSelector } from '../../../../controls/index.js';
|
import { useControlActions, useControlSelector } from '../../../../controls/index.js';
|
||||||
import { formatKeyLabel } from '../../../../controls/keymapUtils.js';
|
import { formatKeyLabel } from '../../../../controls/keymapUtils.js';
|
||||||
import { useTelemetrySelector } from '../../../../context/TelemetryContext.jsx';
|
import { useTelemetrySelector } from '../../../../context/TelemetryContext.jsx';
|
||||||
@@ -14,7 +14,7 @@ import useCanControlRover from '../../../../hooks/useCanControlRover.js';
|
|||||||
import { useDriverLayout } from '../../../../layouts/driver/DriverLayoutContext.jsx';
|
import { useDriverLayout } from '../../../../layouts/driver/DriverLayoutContext.jsx';
|
||||||
import { useSessionSelector } from '../../../../context/SessionContext.jsx';
|
import { useSessionSelector } from '../../../../context/SessionContext.jsx';
|
||||||
import KeyPill from '../../../vip/VipAudioUploadCard/KeyPill.jsx';
|
import KeyPill from '../../../vip/VipAudioUploadCard/KeyPill.jsx';
|
||||||
import ExpansionToggle from '../CornerPods/ExpansionToggle.jsx';
|
import ExpansionPanel from '../CornerPods/ExpansionPanel.jsx';
|
||||||
import usePodVisibility from '../CornerPods/usePodVisibility.js';
|
import usePodVisibility from '../CornerPods/usePodVisibility.js';
|
||||||
|
|
||||||
function DockedAction({ driveKeyLabel, pending, controlsDisabled, error, onUndock }) {
|
function DockedAction({ driveKeyLabel, pending, controlsDisabled, error, onUndock }) {
|
||||||
@@ -101,29 +101,10 @@ function AutoDockingAction({ driveKeyLabel, pending, controlsDisabled, error, on
|
|||||||
function DockAssistAction({ active, pending, controlsDisabled, error, dockKeyLabel, onDock, onCancel, cornerOffsetClass, open, onOpenChange, batterySeverity }) {
|
function DockAssistAction({ active, pending, controlsDisabled, error, dockKeyLabel, onDock, onCancel, cornerOffsetClass, open, onOpenChange, batterySeverity }) {
|
||||||
const batteryUrgent = batterySeverity === 'urgent';
|
const batteryUrgent = batterySeverity === 'urgent';
|
||||||
const batteryLow = batterySeverity === 'low';
|
const batteryLow = batterySeverity === 'low';
|
||||||
if (!open) {
|
// When the battery pod is collapsed its triangular reopen control owns the
|
||||||
return (
|
// outermost forty pixels. Docking uses the next top-edge slot in both open
|
||||||
<button
|
// and collapsed states so the two independent controls never overlap.
|
||||||
type="button"
|
const dockPositionClass = cornerOffsetClass === 'right-0' ? 'right-10' : cornerOffsetClass;
|
||||||
aria-label="Show rover docking control"
|
|
||||||
title="Dock rover"
|
|
||||||
onClick={() => onOpenChange(true)}
|
|
||||||
className={`pointer-events-auto absolute top-0 z-20 flex h-6 w-10 items-center justify-center gap-1 rounded-bl text-[0.6rem] transition ${
|
|
||||||
batteryUrgent
|
|
||||||
? 'bg-red-950 text-red-100 hover:bg-red-900'
|
|
||||||
: batteryLow
|
|
||||||
? 'bg-amber-950 text-amber-100 hover:bg-amber-900'
|
|
||||||
: 'bg-indigo-950/75 text-indigo-100 hover:bg-indigo-900'
|
|
||||||
} ${cornerOffsetClass === 'right-0' ? 'right-10' : cornerOffsetClass}`}
|
|
||||||
>
|
|
||||||
{/* The collapsed tab retains feature identity instead of becoming an
|
|
||||||
anonymous expansion arrow whose purpose must be remembered. */}
|
|
||||||
<FaChargingStation aria-hidden="true" />
|
|
||||||
<FaChevronDown aria-hidden="true" />
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (active) {
|
if (active) {
|
||||||
return (
|
return (
|
||||||
<div className="pointer-events-none absolute inset-0 z-[60] flex items-center justify-center">
|
<div className="pointer-events-none absolute inset-0 z-[60] flex items-center justify-center">
|
||||||
@@ -147,29 +128,38 @@ function DockAssistAction({ active, pending, controlsDisabled, error, dockKeyLab
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`pointer-events-auto absolute top-0 z-20 flex items-stretch ${cornerOffsetClass}`}>
|
<ExpansionPanel
|
||||||
<ExpansionToggle direction="up" label="Hide dock controls" onClick={() => onOpenChange(false)} />
|
open={open}
|
||||||
<button
|
onOpenChange={onOpenChange}
|
||||||
type="button"
|
anchorClassName={`absolute top-0 ${dockPositionClass}`}
|
||||||
aria-label="Start rover docking assist"
|
panelAlign="right"
|
||||||
disabled={pending || controlsDisabled}
|
panelClassName="flex items-start"
|
||||||
onClick={onDock}
|
openDirection="down"
|
||||||
className={`flex items-center gap-1.5 rounded-bl-xl px-4 py-2 text-base font-bold shadow-xl ring-1 transition focus-visible:outline-none focus-visible:ring-2 disabled:cursor-wait disabled:opacity-75 ${
|
closeDirection="up"
|
||||||
batteryUrgent
|
openLabel="Show rover docking control"
|
||||||
? 'bg-red-950 text-red-50 ring-red-300/80 hover:bg-red-900 focus-visible:ring-red-200'
|
closeLabel="Hide dock controls"
|
||||||
: batteryLow
|
>
|
||||||
? 'bg-amber-950 text-amber-50 ring-amber-300/80 hover:bg-amber-900 focus-visible:ring-amber-200'
|
<div className="relative">
|
||||||
: 'bg-indigo-950/60 text-indigo-50 ring-indigo-300/70 hover:bg-indigo-900 focus-visible:ring-indigo-200'
|
<button
|
||||||
}`}
|
type="button"
|
||||||
>
|
aria-label="Start rover docking assist"
|
||||||
<FaChargingStation className="shrink-0" aria-hidden="true" />
|
disabled={pending || controlsDisabled}
|
||||||
<span>{pending ? 'Starting…' : batteryUrgent ? 'Dock now' : batteryLow ? 'Dock soon' : 'Dock rover'}</span>
|
onClick={onDock}
|
||||||
{dockKeyLabel && !pending ? <KeyPill label={dockKeyLabel} /> : null}
|
className={`flex items-center gap-1.5 rounded-bl-xl px-4 pb-2 pt-4 text-base font-bold shadow-xl ring-1 transition focus-visible:outline-none focus-visible:ring-2 disabled:cursor-wait disabled:opacity-75 ${
|
||||||
</button>
|
batteryUrgent
|
||||||
{/* The expansion toggle stays on the far side of this panel so the battery pod's
|
? 'bg-red-950 text-red-50 ring-red-300/80 hover:bg-red-900 focus-visible:ring-red-200'
|
||||||
triangular control remains unobstructed when both occupy the top-right area. */}
|
: batteryLow
|
||||||
|
? 'bg-amber-950 text-amber-50 ring-amber-300/80 hover:bg-amber-900 focus-visible:ring-amber-200'
|
||||||
|
: 'bg-indigo-950/60 text-indigo-50 ring-indigo-300/70 hover:bg-indigo-900 focus-visible:ring-indigo-200'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<FaChargingStation className="shrink-0" aria-hidden="true" />
|
||||||
|
<span>{pending ? 'Starting…' : batteryUrgent ? 'Dock now' : batteryLow ? 'Dock soon' : 'Dock rover'}</span>
|
||||||
|
{dockKeyLabel && !pending ? <KeyPill label={dockKeyLabel} /> : null}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
{error ? <div className="mt-2 max-w-64 bg-red-950/90 px-3 py-2 text-sm font-semibold text-red-100">{error}</div> : null}
|
{error ? <div className="mt-2 max-w-64 bg-red-950/90 px-3 py-2 text-sm font-semibold text-red-100">{error}</div> : null}
|
||||||
</div>
|
</ExpansionPanel>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user