const NAV = [ { id: 'setup', label: 'Setup', icon: ( ) }, { id: 'baseline', label: 'Baseline', icon: ( ) }, { id: 'forecast', label: 'Forecast', icon: ( ) }, ] // The forecasts are listed here, above the steps, rather than offered as a // dropdown in the status bar. Two reasons: a scenario is a thing you open, not // a setting you change, and the status bar is otherwise read-only, so a control // sitting in it did not read as one. // // Not nested under the Forecast button, which is where this started: Baseline // works on the same version, so hanging it off one step would either // misrepresent the scope or duplicate the list under two. It reads // "what am I working on" and then "what step am I on". // // The source is deliberately absent. `gs.osm_skinny` means nothing to someone // editing a forecast, and a version already carries its source, so choosing a // forecast settles it. Picking the source is a Setup concern and the status bar // only offers it there. export default function Sidebar({ view, setView, expanded, setExpanded, versions = [], versionId, setVersionId, sourceId, setSourceId, }) { const selected = versions.find(v => String(v.id) === String(versionId)) function pick(v) { // The source comes with the forecast. This is the whole reason the source // needs no control of its own outside Setup: every version carries its // source_id, so choosing one settles both. Set first, so the version is not // briefly validated against the outgoing source's list. if (v.source_id != null && String(v.source_id) !== String(sourceId)) { setSourceId(String(v.source_id)) } setVersionId(String(v.id)) // Setup is the one step a version means nothing to, so choosing one there // is a statement of intent to go and work on it. if (view === 'setup') setView('forecast') } return (
Pivot Forecast
{expanded ? (
Forecasts
{/* Capped and scrollable: the list grows with the work, and the steps below it must never be pushed off the bottom. */}
{versions.length === 0 && (
none yet
)} {versions.map(v => { const active = String(v.id) === String(versionId) return ( ) })}
) : ( // Collapsed, the list has nowhere to go, so all that survives is which // one is open — as a tooltip, on a button that opens the rail again.
)}
) }