diff --git a/client/src/components/Roadtrip/RoadtripCorridorPanel.tsx b/client/src/components/Roadtrip/RoadtripCorridorPanel.tsx
index 5e2b14c26..01785ae95 100644
--- a/client/src/components/Roadtrip/RoadtripCorridorPanel.tsx
+++ b/client/src/components/Roadtrip/RoadtripCorridorPanel.tsx
@@ -4,6 +4,7 @@ import {
AlertTriangle, MapPin, X, type LucideIcon,
} from 'lucide-react'
import { useTranslation } from '../../i18n/TranslationContext'
+import { Tooltip } from '../shared/Tooltip'
import { useSettingsStore } from '../../store/settingsStore'
import { formatDistance } from '../../utils/units'
import CustomSelect from '../shared/CustomSelect'
@@ -89,15 +90,16 @@ function ResultRow({ poi, onAdd }: { poi: CorridorPoi; onAdd?: () => void }): Re
{/* Always there, quiet until the row is under the pointer: a button that only
exists on hover is one a keyboard user has to find by faith. */}
{onAdd ? (
-
+
+
+
) : null}
)
diff --git a/client/src/components/Roadtrip/RoadtripSidebar.test.tsx b/client/src/components/Roadtrip/RoadtripSidebar.test.tsx
index f8b6ed0d6..060b735cc 100644
--- a/client/src/components/Roadtrip/RoadtripSidebar.test.tsx
+++ b/client/src/components/Roadtrip/RoadtripSidebar.test.tsx
@@ -125,14 +125,13 @@ describe('RoadtripSidebar', () => {
wrap()
// Both are plain text at the row's edge — no pill, no icon. What separates the time
- // somebody chose from the one the drive worked out is weight and ink, and the reason
- // is named for a screen reader either way.
+ // somebody chose from the one the drive worked out is weight and ink. The reason is
+ // named in a tooltip, which only enters the DOM on hover, so the visible difference
+ // is what this pins.
const pinned = screen.getByText('09:00')
const computed = screen.getByText('10:30')
expect(pinned.className).toContain('font-semibold')
- expect(pinned).toHaveAttribute('title', 'Time you set')
expect(computed.className).not.toContain('font-semibold')
- expect(computed).toHaveAttribute('title', 'Calculated from the drive')
})
it('FE-ROADTRIP-SIDEBAR-005: flags a stop the drive cannot reach in time', () => {
@@ -202,7 +201,9 @@ describe('RoadtripSidebar', () => {
// Said twice on purpose: once as the break in the chain, once on the arrival after
// it, where "01:30" would otherwise read as tonight.
expect(screen.getAllByText('Next day')).toHaveLength(2)
- expect(screen.getAllByTitle('Calculated from the drive')[1].textContent).toContain('+1')
+ // The day marker rides on the arrival that crossed midnight, where "01:30" would
+ // otherwise read as tonight.
+ expect(screen.getByText('01:30').textContent).toContain('+1')
})
it('FE-ROADTRIP-SIDEBAR-012: a late arrival past midnight keeps both findings', () => {
diff --git a/client/src/components/Roadtrip/RoadtripSidebar.tsx b/client/src/components/Roadtrip/RoadtripSidebar.tsx
index a8e3865bd..4bc6c5a37 100644
--- a/client/src/components/Roadtrip/RoadtripSidebar.tsx
+++ b/client/src/components/Roadtrip/RoadtripSidebar.tsx
@@ -5,6 +5,7 @@ import {
type LucideIcon,
} from 'lucide-react'
import { useTranslation } from '../../i18n/TranslationContext'
+import { Tooltip } from '../shared/Tooltip'
import { useSettingsStore } from '../../store/settingsStore'
import { formatDistance } from '../../utils/units'
import { formatDate, formatClockTime } from '../../utils/formatters'
@@ -107,23 +108,22 @@ function OffRoadBadge({ meters }: { meters: number }): React.ReactElement {
const { t } = useTranslation()
const distanceUnit = useSettingsStore(s => s.settings.distance_unit)
return (
-
-
-
+
+
+
+
+
+
+ {formatDistance(meters / 1000, distanceUnit)}
+
-
- {formatDistance(meters / 1000, distanceUnit)}
-
-
+
)
}
@@ -157,24 +157,25 @@ function StayBadge({ minutes, onEdit }: { minutes: number | null; onEdit?: () =>
// and a button inside a button is invalid HTML that React warns about and that browsers
// resolve by dropping the inner element.
return (
- { e.stopPropagation(); onEdit() }}
- onKeyDown={e => {
- if (e.key !== 'Enter' && e.key !== ' ') return
- e.preventDefault()
- e.stopPropagation()
- onEdit()
- }}
- title={t('roadtrip.stop.stay')}
- aria-label={text ? `${t('roadtrip.stop.stay')}: ${text}` : t('roadtrip.stay.add')}
- className={`${shell} cursor-pointer transition-colors hover:border-content-faint`}
- >
- {label}{value}
-
+
+ { e.stopPropagation(); onEdit() }}
+ onKeyDown={e => {
+ if (e.key !== 'Enter' && e.key !== ' ') return
+ e.preventDefault()
+ e.stopPropagation()
+ onEdit()
+ }}
+ aria-label={text ? `${t('roadtrip.stop.stay')}: ${text}` : t('roadtrip.stay.add')}
+ className={`${shell} cursor-pointer transition-colors hover:border-content-faint`}
+ >
+ {label}{value}
+
+
)
}
@@ -302,11 +303,11 @@ function DriveBand({ leg, onAskAlternatives, alternativesOpen }: {
the gesture people try first — the shuffle mark stays as the sign that it can
be clicked, and as where the open state shows. */}
{onAskAlternatives && leg ? (
+
+
) : (
{band}
@@ -477,15 +479,17 @@ function DriveFindingBadge({ warning }: { warning: ScheduleWarning }): React.Rea
if (!text) return null
const Icon = warning.code === 'range' ? Fuel : Clock
return (
+
{warning.code === 'range'
@@ -493,6 +497,7 @@ function DriveFindingBadge({ warning }: { warning: ScheduleWarning }): React.Rea
: `+${formatDurationShort((warning.overMinutes ?? 0) * 60)}`}
+
)
}
@@ -547,9 +552,9 @@ function Arrival({ entry }: { entry: ScheduleEntry }): React.ReactElement {
// look like it had a button in it. What is left of the distinction is weight and ink —
// enough to see which time somebody chose, without a second shape in the rail.
return (
+
+
)
}
@@ -641,15 +647,16 @@ function Stop({ stop, number, entry, late, driveFindings, selected, continues, o
{(driveFindings ?? []).map(w => )}
{lateText ? (
-
-
- {`+${formatDurationShort((late?.minutes ?? 0) * 60)}`}
-
+
+
+
+ {`+${formatDurationShort((late?.minutes ?? 0) * 60)}`}
+
+
) : null}
{entry?.arrival ? : null}
@@ -720,16 +727,14 @@ function DaySection({ day, selectedAssignmentId, onSelectStop, onReorderStop, on
})}
{day.dayWarning ? (
-
-
- {t('roadtrip.limit.dayOver', {
- time: formatDurationShort((day.dayWarning.minutes - day.dayWarning.limitMinutes) * 60),
- })}
-
+
+
+
+ {t('roadtrip.limit.dayOver', {
+ time: formatDurationShort((day.dayWarning.minutes - day.dayWarning.limitMinutes) * 60),
+ })}
+
+
) : null}
{t('roadtrip.day.stopCount', { count: day.stops.filter(s => !isServiceStopType(s.stopType)).length })}