Files
TREK/client/src/components/Planner/PlacesSidebarList.tsx
T
Maurice 2879433bd3 fix(dawarich): the panel opens to its full height and the rail scrolls past it
Capping it was the wrong half of the fix: the stays were reachable but the panel was cut
off at half the rail, which is not what opening something means.

It sits inside the places list's own scroller now, above the rows, so it simply grows to
whatever the day's stays need — 3350px for forty of them — and the rail scrolls past it
to the places. No second scrollbar, no fold. As a band of its own it could never do that:
the list is the flex child that scrolls, so anything unbounded above it squeezed the list
to nothing and took the rail's scrolling with it.
2026-09-17 15:05:29 +02:00

89 lines
3.8 KiB
TypeScript

import { Fragment, type ReactNode } from 'react'
import EmptyState from '../shared/EmptyState'
import { MemoPlaceRow } from './PlacesSidebarRow'
import type { SidebarState } from './usePlacesSidebar'
import { usePluginViewContributions, PluginCardFooter } from '../Plugins/PluginContributions'
export function PlacesList({ header, ...S }: SidebarState & {
/**
* A block that sits above the places and scrolls WITH them.
*
* The Dawarich panel lives here rather than in a band of its own above the list. As its
* own band it could not grow: the list is the flex child that scrolls, so a panel with
* ten stays in it squeezed the list to nothing and took the rail's scrolling with it,
* and capping the panel left half its stays below a fold with no way to reach them.
* Inside the scroller it simply opens to its full height and the rail scrolls past it.
*/
header?: ReactNode
}) {
const {
filtered, scrollContainerRef, onScrollTopChange, filter, t, canEditPlaces, onAddPlace,
categories, selectedPlaceId, plannedIds, inDaySet, selectedIds, selectMode, selectedDayId,
isMobile, onPlaceClick, openContextMenu, onAssignToDay, toggleSelected, setDayPickerPlace, registerPlaceRow, tripId,
} = S
// Plugin-contributed columns/actions for the places view, keyed by place id (#plugins).
const contribFor = usePluginViewContributions('places', tripId)
return (
<div className="trek-stagger" style={{ flex: 1, overflowY: 'auto', minHeight: 0 }} ref={scrollContainerRef} onScroll={(e) => onScrollTopChange?.((e.currentTarget as HTMLElement).scrollTop)}>
{header}
{filtered.length === 0 ? (
/* The mascot and one line, the shape every other empty state in TREK has.
The add link stays as the state's action: an empty list of places is
one of the few that has an obvious next step. */
<EmptyState
scene="search"
mood="sad"
size={92}
fill
surface="var(--bg-secondary)"
title={filter === 'unplanned' ? t('places.allPlanned') : t('places.noneFound')}
action={canEditPlaces ? (
<button
type="button"
onClick={onAddPlace}
className="text-caption text-content underline underline-offset-2 hover:text-accent"
>
{t('places.addPlace')}
</button>
) : undefined}
/>
) : (
filtered.map(place => {
const cat = categories.find(c => c.id === place.category_id)
const isSelected = place.id === selectedPlaceId
const isPlanned = plannedIds.has(place.id)
const inDay = inDaySet.has(place.id)
const isChecked = selectedIds.has(place.id)
const contributions = contribFor(place.id)
return (
<Fragment key={place.id}>
<MemoPlaceRow
place={place}
category={cat}
isSelected={isSelected}
isPlanned={isPlanned}
inDay={inDay}
isChecked={isChecked}
selectMode={selectMode}
selectedDayId={selectedDayId}
canEditPlaces={canEditPlaces}
isMobile={isMobile}
t={t}
onPlaceClick={onPlaceClick}
onContextMenu={openContextMenu}
onAssignToDay={onAssignToDay}
toggleSelected={toggleSelected}
setDayPickerPlace={setDayPickerPlace}
registerPlaceRow={registerPlaceRow}
/>
{contributions.length > 0 && (
<div style={{ padding: '0 14px 8px 16px' }}><PluginCardFooter items={contributions} tripId={tripId} /></div>
)}
</Fragment>
)
})
)}
</div>
)
}