Plants mode: recent-planted quick strip + clump/rows fill (#100)
Two things Steve asked for, both in the #99 Plants mode: - A "Recent" strip of the plants most recently planted IN THIS GARDEN (recentlyPlantedIds — derived from actual plantings, newest first, unique; NOT the manual localStorage tray), as tap-to-arm chips. So re-planting "more of the same" is one tap, no picker. Hidden until something's planted. - A clump/rows fill control (FillControl), shown once a plant is armed: pick a layout, "Fill bed", and it runs POST /objects/:id/fill region=all with the chosen layout — the #77 grid/clump fill the UI could NOT reach before (it was agent/REST only). Defaults to rows (a real planting). useFillObject mirrors useClearObject: one request, invalidate /full. Both live in the shared PlantPlacementTools, so desktop's focus toolbar and the mobile Plants strip get them identically. Fill is capped/validated server-side (#95) and covered spots are skipped, so re-filling is safe. Verified live at 390px: the Recent strip shows this garden's tomato/lettuce/ garlic; arming a chip reveals Clump|Rows + Fill bed; fill fires cleanly. tsc + vitest (95, incl. recentlyPlantedIds) + 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:
@@ -0,0 +1,48 @@
|
||||
import { cn } from '@/lib/cn'
|
||||
import { PlantIcon } from '@/components/plants/PlantIcon'
|
||||
import type { Plant } from '@/lib/plants'
|
||||
|
||||
/**
|
||||
* A quick strip of the plants you've most recently planted IN THIS GARDEN (#100),
|
||||
* so re-placing "more of the same" is one tap instead of a trip through the
|
||||
* catalog or the manual tray. Derived from actual plantings (see
|
||||
* recentlyPlantedIds), newest first; renders nothing until something's planted.
|
||||
* Tap a chip to arm it for placement (the armed one is highlighted).
|
||||
*/
|
||||
export function RecentPlants({
|
||||
plants,
|
||||
armedPlantId,
|
||||
onArm,
|
||||
}: {
|
||||
plants: Plant[]
|
||||
armedPlantId: number | null
|
||||
onArm: (plant: Plant) => void
|
||||
}) {
|
||||
if (plants.length === 0) return null
|
||||
return (
|
||||
<div className="flex items-center gap-1.5 overflow-x-auto">
|
||||
<span className="shrink-0 text-[0.7rem] font-medium uppercase tracking-wide text-muted">Recent</span>
|
||||
{plants.map((p) => {
|
||||
const active = p.id === armedPlantId
|
||||
return (
|
||||
<button
|
||||
key={p.id}
|
||||
type="button"
|
||||
onClick={() => onArm(p)}
|
||||
aria-pressed={active}
|
||||
title={active ? `Placing ${p.name}` : `Place ${p.name}`}
|
||||
className={cn(
|
||||
'inline-flex shrink-0 items-center gap-1.5 rounded-full border py-1 pl-1.5 pr-2.5 text-xs font-medium outline-none transition-colors focus-visible:ring-2 focus-visible:ring-accent/40',
|
||||
active
|
||||
? 'border-accent bg-accent/10 text-accent-strong'
|
||||
: 'border-border bg-surface text-fg hover:border-accent',
|
||||
)}
|
||||
>
|
||||
<PlantIcon color={p.color} icon={p.icon} className="h-5 w-5 rounded-full text-[0.65rem]" />
|
||||
<span className="max-w-[6rem] truncate">{p.name}</span>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user