feat(tours): bike tours in the planner, CyclOSM, and the planned cards' last points (BIK-01 to BIK-06, RTE-01, IO-01, LIB-02)

The tour type is a dropdown; a bike tour picks its profile, routes for it,
says when a point lies off every way, and shows what it runs on: surfaces,
kinds of way and, for a mountain bike, the trail grades, each pointed at on
the map. A type's own map (OpenHikingMap for a hike, CyclOSM for a bike tour)
is offered while a tour of that type is open, with an admin switch each.

From the planned cards: a hike avoids steep climbs, steps or ferries, nothing
else; a saved tour can be shared where the platform takes GPX files; and the
tours rail adds a tour from one menu: plan new, import GPX, or take one over
from another trip.
This commit is contained in:
Maurice
2026-10-19 21:33:49 +02:00
parent adcaca3c5f
commit 73237ffedb
85 changed files with 3728 additions and 647 deletions
+5 -3
View File
@@ -1,4 +1,4 @@
import type { MouseEvent, ReactNode } from 'react'
import type { MouseEvent, ReactNode, Ref } from 'react'
import { Clock, MoreHorizontal, type LucideIcon } from 'lucide-react'
import { Tooltip } from '../shared/Tooltip'
import { ContextMenu, useContextMenu } from '../shared/ContextMenu'
@@ -15,7 +15,7 @@ import { fs } from '../shared/DialogShell'
export const PANEL_BAR = 'flex min-h-[52px] flex-none items-center gap-1.5 border-b border-edge-faint px-3 py-2'
/** A 32px icon control on a panel bar. Raised like an active filter tab while `active`. */
export function BarButton({ label, tooltip, onClick, active = false, disabled = false, children, className = '', ariaPressed, ariaExpanded, ariaHaspopup, placement = 'bottom', testId }: {
export function BarButton({ label, tooltip, onClick, active = false, disabled = false, children, className = '', ariaPressed, ariaExpanded, ariaHaspopup, placement = 'bottom', testId, ref }: {
label: string
/** A longer explanation for the tooltip; the label still names the button. */
tooltip?: string
@@ -29,10 +29,12 @@ export function BarButton({ label, tooltip, onClick, active = false, disabled =
ariaExpanded?: boolean
ariaHaspopup?: 'dialog' | 'menu'
placement?: 'top' | 'bottom' | 'left' | 'right'
/** The button itself, for a list or a menu that opens beside it. */
ref?: Ref<HTMLButtonElement>
}) {
return (
<Tooltip label={tooltip ?? label} placement={placement}>
<button type="button" onClick={onClick} disabled={disabled} aria-label={label} data-testid={testId}
<button ref={ref} type="button" onClick={onClick} disabled={disabled} aria-label={label} data-testid={testId}
aria-pressed={ariaPressed} aria-expanded={ariaExpanded} aria-haspopup={ariaHaspopup}
className={`grid h-8 w-8 flex-none place-items-center rounded-[10px] transition-colors disabled:cursor-default disabled:opacity-40 ${active ? 'bg-surface-card text-content shadow-sm' : 'text-content-muted enabled:hover:bg-surface-card enabled:hover:text-content'} ${className}`}>
{children}