Non-occluding mobile inspector + always-visible mode bar (#101)
Two things at once, because they're one layout: on a phone, selecting a bed opened the rail as a bottom sheet that COVERED the whole garden, and opening Journal/Assistant hid the mode bar until you closed it. You couldn't see what you were editing, or switch modes without backing out. Now the rail is an in-flow PEEK. Instead of `fixed bottom-0 max-h-70vh` overlaying everything, EditorRail on mobile is a ≤50vh flex child the editor places BETWEEN the canvas and the mode bar: canvas (flex-1, shrinks) → rail peek (≤50vh) → mode bar (always shown) So the garden stays visible in the top band, the mode bar stays reachable below, and you can tap another mode straight from an open panel. The contextual tool strip yields to the peek (gated on !railTab), and tapping a canvas mode closes the panel (deselecting if it was the inspector). Desktop is untouched — the same EditorRail is the right-hand column there (`md:` styles), the mode bar stays `md:hidden`. Answers Steve's two calls from the end-of-run questions: always-visible mode bar + non-occluding inspector. Verified live at 390px (inspector + journal peeks keep the garden and mode bar visible; mode switching works) and 1280px (desktop unchanged). tsc + vitest + build green; DESIGN updated. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01H3zbym8Doka2d7D48maSgZ
This commit is contained in:
@@ -47,9 +47,13 @@ export function EditorRail({
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
// Phone: a bottom sheet over the canvas. Desktop: a column beside it.
|
||||
'fixed inset-x-0 bottom-0 z-30 flex max-h-[70vh] flex-col rounded-t-xl border-t border-border bg-surface shadow-lg',
|
||||
'md:static md:max-h-none md:w-80 md:shrink-0 md:rounded-xl md:border md:shadow-sm',
|
||||
// Phone: an in-flow PEEK — a capped-height panel that sits between the
|
||||
// canvas and the always-visible mode bar (the editor's flex column places
|
||||
// it there), so the garden stays visible above it and the mode bar stays
|
||||
// reachable below. The canvas flexes to fill whatever's left. Desktop: a
|
||||
// fixed-width column beside the canvas.
|
||||
'flex max-h-[50vh] min-h-0 shrink-0 flex-col rounded-t-xl border-t border-border bg-surface shadow-lg',
|
||||
'md:static md:max-h-none md:w-80 md:rounded-xl md:border md:shadow-sm',
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-1 border-b border-border px-2 py-1.5">
|
||||
|
||||
@@ -244,7 +244,13 @@ export function GardenEditorPage() {
|
||||
} else if (m === 'assistant') {
|
||||
setRailTab('chat')
|
||||
} else {
|
||||
if (railTab === 'journal' || railTab === 'chat') setRailTab(null)
|
||||
// A canvas mode closes whatever's in the rail; the inspector is about the
|
||||
// selection, so closing it deselects too.
|
||||
if (railTab === 'inspector') {
|
||||
select(null)
|
||||
selectPlanting(null)
|
||||
}
|
||||
setRailTab(null)
|
||||
if (m === 'fixtures' && focusedObjectId != null) exitFocus()
|
||||
}
|
||||
}
|
||||
@@ -629,11 +635,37 @@ export function GardenEditorPage() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Mobile bottom: contextual tools for the current mode + the mode switch
|
||||
bar (#99). md:hidden — desktop uses the left column. A panel-mode rail
|
||||
(journal/assistant) or the inspector overlays this while open. */}
|
||||
{/* The rail sits between the canvas and the mode bar. On mobile it's an
|
||||
in-flow PEEK (≤50vh), so the garden stays visible above it and the mode
|
||||
bar below (see EditorRail); on desktop it's the right-hand column. */}
|
||||
{railTab && (
|
||||
<EditorRail
|
||||
tabs={railTabs}
|
||||
activeId={railTab}
|
||||
onActivate={setRailTab}
|
||||
onClose={() => {
|
||||
// Only the inspector is *about* the selection, so only closing it
|
||||
// deselects; dismissing a panel leaves the canvas as you had it. Any
|
||||
// panel rail (journal/history/chat) drops back to a canvas mode.
|
||||
if (railTab === 'inspector') {
|
||||
select(null)
|
||||
selectPlanting(null)
|
||||
} else {
|
||||
// Back to a canvas mode — Plants if you're still inside a bed, else
|
||||
// Fixtures. (Hardcoding Fixtures here docked the object palette inside
|
||||
// a focused bed.)
|
||||
setMode(focusedObjectId != null ? 'plants' : 'fixtures')
|
||||
}
|
||||
setRailTab(null)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Mobile bottom: contextual tools for the current canvas mode + the
|
||||
always-visible mode switch bar (#99/#101). md:hidden — desktop uses the
|
||||
left column. The tool strip yields to a rail peek when one is open. */}
|
||||
<div className="shrink-0 md:hidden">
|
||||
{canEdit && (mode === 'fixtures' || mode === 'plants') && (
|
||||
{canEdit && !railTab && (mode === 'fixtures' || mode === 'plants') && (
|
||||
<div className="mb-2 min-h-[2.25rem]">
|
||||
{mode === 'fixtures' && <Palette />}
|
||||
{mode === 'plants' &&
|
||||
@@ -677,30 +709,6 @@ export function GardenEditorPage() {
|
||||
<ModeBar mode={mode} onSelect={selectMode} hasAssistant={!!capabilities.data?.agent} canEdit={canEdit} />
|
||||
</div>
|
||||
|
||||
{railTab && (
|
||||
<EditorRail
|
||||
tabs={railTabs}
|
||||
activeId={railTab}
|
||||
onActivate={setRailTab}
|
||||
onClose={() => {
|
||||
// Only the inspector is *about* the selection, so only closing it
|
||||
// deselects; dismissing a panel leaves the canvas as you had it. Any
|
||||
// panel rail (journal/history/chat) drops back to a canvas mode so the
|
||||
// mobile mode bar reappears.
|
||||
if (railTab === 'inspector') {
|
||||
select(null)
|
||||
selectPlanting(null)
|
||||
} else {
|
||||
// Back to a canvas mode so the mode bar reappears — Plants if you're
|
||||
// still inside a bed, else Fixtures. (Hardcoding Fixtures here docked
|
||||
// the object palette inside a focused bed.)
|
||||
setMode(focusedObjectId != null ? 'plants' : 'fixtures')
|
||||
}
|
||||
setRailTab(null)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{picker && (
|
||||
<PlantPicker
|
||||
unit={garden.unitPref}
|
||||
|
||||
Reference in New Issue
Block a user