diff --git a/client/src/components/Roadtrip/RoadtripCorridorPanel.tsx b/client/src/components/Roadtrip/RoadtripCorridorPanel.tsx index 3ced31657..ef6236c50 100644 --- a/client/src/components/Roadtrip/RoadtripCorridorPanel.tsx +++ b/client/src/components/Roadtrip/RoadtripCorridorPanel.tsx @@ -348,6 +348,7 @@ export default function RoadtripCorridorPanel({ corridor, routes, onAddPoi }: Ro scene={search.loading ? 'search' : 'idle'} mood={filteredToNothing ? 'confused' : undefined} size={88} + compact surface="var(--bg-secondary)" title={ search.loading diff --git a/client/src/components/Roadtrip/RoadtripLimitsCard.tsx b/client/src/components/Roadtrip/RoadtripLimitsCard.tsx index 6baeed246..f9c771f24 100644 --- a/client/src/components/Roadtrip/RoadtripLimitsCard.tsx +++ b/client/src/components/Roadtrip/RoadtripLimitsCard.tsx @@ -79,13 +79,16 @@ export default function RoadtripLimitsCard({ onSave }: { onSave?.('roadtrip_range_km', Math.round(km)) } - // What is set, on the button itself, so the form does not have to be open to see it. - // Nothing set reads as "off" rather than as an empty string, which would look broken. - const summary = [ - legMinutes ? formatDurationShort(legMinutes * 60) : null, - dayMinutes ? formatDurationShort(dayMinutes * 60) : null, - rangeKm ? formatDistance(rangeKm, distanceUnit) : null, - ].filter(Boolean).join(' ยท ') + // What is set, on the button itself, so the form does not have to be open to read it. + // Each one keeps its own icon, because three numbers in a row say nothing about which + // is which: a clock, a calendar and a pump do. Distances go through formatDistance, so + // an imperial traveller reads miles here and types miles in the dialog, while what is + // stored stays kilometres either way. + const badges = [ + legMinutes ? { key: 'leg', Icon: Clock, text: formatDurationShort(legMinutes * 60) } : null, + dayMinutes ? { key: 'day', Icon: CalendarClock, text: formatDurationShort(dayMinutes * 60) } : null, + rangeKm ? { key: 'range', Icon: Fuel, text: formatDistance(rangeKm, distanceUnit) } : null, + ].filter(Boolean) as { key: string; Icon: typeof Clock; text: string }[] return ( <> @@ -98,16 +101,37 @@ export default function RoadtripLimitsCard({ onSave }: { onClick={() => setOpen(true)} className="flex w-full items-center gap-3 rounded-xl border border-edge-faint bg-surface-card px-3.5 py-3 text-start transition-colors hover:bg-surface-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent" > - - - - + {/* No icon tile on the trigger: the three badges below already carry a clock, a + calendar and a pump, and a fourth mark in front of the title only ate the width + they need to sit on one line. */} + {t('roadtrip.limit.title')} - - {summary || t('roadtrip.limit.none')} - + {badges.length ? ( + + {badges.map(({ key, Icon, text }) => ( + + + + + + {text} + + + ))} + + ) : ( + + {t('roadtrip.limit.none')} + + )} diff --git a/client/src/components/shared/EmptyState.tsx b/client/src/components/shared/EmptyState.tsx index d1c54731c..89745ac79 100644 --- a/client/src/components/shared/EmptyState.tsx +++ b/client/src/components/shared/EmptyState.tsx @@ -22,6 +22,7 @@ export default function EmptyState({ size = 104, surface = 'var(--bg-card)', layout = 'stack', + compact = false, className = '', action, }: { @@ -31,6 +32,14 @@ export default function EmptyState({ size?: number surface?: string layout?: 'stack' | 'row' + /** + * Quieter type for a state inside a narrow column rather than on a page. + * + * The sidebar's empty state sits under its own controls, so a line at the page size + * competes with them for the eye; at the caption tier it reads as what it is, a note + * about why the list below is blank. + */ + compact?: boolean className?: string /** Optional call to action under the title, for states that have an obvious next step. */ action?: ReactNode @@ -44,7 +53,7 @@ export default function EmptyState({ style={{ '--m-ink': 'var(--text-primary)', '--m-bg': surface } as CSSProperties} > -

{title}

+

{title}

{action} )