From 15928010773b2e90ae84e53d2b62885de4b67419 Mon Sep 17 00:00:00 2001 From: Aditya Bhumbla Date: Wed, 7 Feb 2024 20:20:08 -0800 Subject: [PATCH 1/5] inital collapsible legs --- src/components/Itinerary.jsx | 22 +++- src/components/ItineraryBikeLeg.jsx | 64 ++++++---- src/components/ItineraryHeader.css | 8 ++ src/components/ItineraryHeader.jsx | 35 ++++- src/components/ItineraryStep.css | 3 +- src/components/ItineraryTransitLeg.jsx | 169 +++++++++++++++++-------- src/components/ModeIcon.jsx | 10 +- src/components/Routes.jsx | 9 ++ src/components/RoutesOverview.jsx | 4 +- src/components/primitives/Icon.jsx | 1 + src/features/routes.js | 14 ++ src/lib/icons/icon-chevron.svg | 1 + 12 files changed, 254 insertions(+), 86 deletions(-) create mode 100644 src/lib/icons/icon-chevron.svg diff --git a/src/components/Itinerary.jsx b/src/components/Itinerary.jsx index 17328f69..b76f80a2 100644 --- a/src/components/Itinerary.jsx +++ b/src/components/Itinerary.jsx @@ -1,4 +1,5 @@ import * as React from 'react'; +import { useDispatch } from 'react-redux'; import { FormattedMessage, useIntl } from 'react-intl'; import { formatDurationBetween } from '../lib/time'; import { getAgencyDisplayName } from '../lib/region'; @@ -7,6 +8,7 @@ import ItineraryBikeLeg from './ItineraryBikeLeg'; import ItineraryHeader from './ItineraryHeader'; import ItineraryTransitLeg from './ItineraryTransitLeg'; import ItineraryElevationProfile from './ItineraryElevationProfile'; +import { isSignificantLeg } from './RoutesOverview'; import { ReactComponent as NavLeftArrow } from 'iconoir/icons/nav-arrow-left.svg'; import { ReactComponent as ArriveIcon } from 'iconoir/icons/triangle-flag.svg'; @@ -17,8 +19,11 @@ export default function Itinerary({ destinationDescription, onBackClick, onStepClick, + onIconClick, + viewingLeg, scrollToStep, }) { + const dispatch = useDispatch(); const intl = useIntl(); const [scrollToLegIdx, scrollToStepIdx] = scrollToStep || []; @@ -29,10 +34,16 @@ export default function Itinerary({ key={idx} leg={leg} onStopClick={onStepClick.bind(null, idx)} + onIconClick={onIconClick.bind(null, idx)} + expanded={viewingLeg === idx} scrollTo={scrollToLegIdx === idx} /> ); - } else { + } else if (isSignificantLeg(leg)) { + const isOnlyLeg = legs.length === 1; + if (isOnlyLeg) { + onIconClick(idx); + } // Where are we biking to? (Either final destination, or name of transit stop to board) const legDestination = idx === legs.length - 1 @@ -43,7 +54,10 @@ export default function Itinerary({ key={idx} leg={leg} legDestination={legDestination} + isOnlyLeg={isOnlyLeg} onStepClick={onStepClick.bind(null, idx)} + onIconClick={onIconClick.bind(null, idx)} + expanded={viewingLeg === idx} scrollToStep={scrollToLegIdx === idx ? scrollToStepIdx : null} /> ); @@ -130,7 +144,11 @@ export default function Itinerary({
{renderedLegs} - + - - {instructionsWithBikeInfra.map((step, stepIdx) => - isArriveStep(step) - ? null - : [ - , - - {step.bikeInfra} - , - ], + + {expanded ? ( + <> + {isOnlyLeg ? null : ( + + )} + + {instructionsWithBikeInfra.map((step, stepIdx) => + isArriveStep(step) + ? null + : [ + , + + {step.bikeInfra} + , + ], + )} + + + ) : ( + )} - ); } diff --git a/src/components/ItineraryHeader.css b/src/components/ItineraryHeader.css index 338f355d..19005dbb 100644 --- a/src/components/ItineraryHeader.css +++ b/src/components/ItineraryHeader.css @@ -25,6 +25,14 @@ height: 32px; } +.ItineraryHeader_arrow { + transform: rotate(0deg); +} + +.ItineraryHeader_arrow_90 { + transform: rotate(90deg); +} + .ItineraryHeader_header { font-size: 20px; font-weight: bold; diff --git a/src/components/ItineraryHeader.jsx b/src/components/ItineraryHeader.jsx index 78eb248b..bec7827a 100644 --- a/src/components/ItineraryHeader.jsx +++ b/src/components/ItineraryHeader.jsx @@ -5,10 +5,25 @@ import { getTextColor } from '../lib/colors'; import Icon from './primitives/Icon'; import ItineraryRow from './ItineraryRow'; import { ReactComponent as WarningTriangle } from 'iconoir/icons/warning-triangle.svg'; +import ArrowChevron from '../lib/icons/icon-chevron.svg'; import './ItineraryHeader.css'; -export default function ItineraryHeader({ alerts, children, icon, iconColor }) { +function alertSummary(alertBody) { + return alertBody.slice(0, 40) + '...'; +} + +export default function ItineraryHeader({ + alerts, + children, + icon, + iconColor, + alertsExpanded, + onIconClick, + onAlertClick, + expanded, + displayArrow, +}) { const intl = useIntl(); const iconIsWhite = getTextColor(iconColor).main === 'white'; @@ -27,12 +42,22 @@ export default function ItineraryHeader({ alerts, children, icon, iconColor }) { })} style={{ backgroundColor: iconColor }} > - {icon} + + {icon} + + {displayArrow && ( + + )}

{header}

{subheading &&

{subheading}

} {alerts?.length > 0 && ( -
    +
      {alerts.map(([alertHeader, alertBody], idx) => (
    • )} {alertBody && ( - {alertBody} + + {alertsExpanded ? alertBody : alertSummary(alertBody)} + )}
    • ))} diff --git a/src/components/ItineraryStep.css b/src/components/ItineraryStep.css index 3197730c..7d919eab 100644 --- a/src/components/ItineraryStep.css +++ b/src/components/ItineraryStep.css @@ -7,8 +7,9 @@ } .ItineraryStep_iconSmall { + position: relative; margin-left: 9px; - top: 10px; + top: 11px; } .ItineraryStep_iconSmall path { diff --git a/src/components/ItineraryTransitLeg.jsx b/src/components/ItineraryTransitLeg.jsx index 7064fc5b..dd473cac 100644 --- a/src/components/ItineraryTransitLeg.jsx +++ b/src/components/ItineraryTransitLeg.jsx @@ -11,14 +11,30 @@ import ItineraryDivider from './ItineraryDivider'; import ItinerarySpacer from './ItinerarySpacer'; import ItineraryStep from './ItineraryStep'; import ModeIcon from './ModeIcon'; +import Icon from './Icon'; +import { ReactComponent as WarningTriangle } from 'iconoir/icons/warning-triangle.svg'; import { ReactComponent as Circle } from 'iconoir/icons/circle.svg'; import './ItineraryTransitLeg.css'; +import { useState, useCallback } from 'react'; -export default function ItineraryTransitLeg({ leg, onStopClick, scrollTo }) { +export default function ItineraryTransitLeg({ + leg, + onStopClick, + onIconClick, + scrollTo, + expanded, +}) { const intl = useIntl(); + const [alertsExpanded, setAlertsExpanded] = useState(false); + + const toggleAlertsExpanded = useCallback( + () => setAlertsExpanded(!alertsExpanded), + [alertsExpanded, setAlertsExpanded], + ); + const { stops } = leg; const departure = formatTime(leg.departure_time); @@ -45,8 +61,12 @@ export default function ItineraryTransitLeg({ leg, onStopClick, scrollTo }) { return (
      } + icon={} iconColor={leg.route_color || DEFAULT_PT_COLOR} + expanded={expanded} + displayArrow={true} + alertsExpanded={alertsExpanded} + onAlertClick={toggleAlertsExpanded} alerts={alertsForHeader} > @@ -57,6 +77,36 @@ export default function ItineraryTransitLeg({ leg, onStopClick, scrollTo }) { /> + {chunks}, + headsign: leg.trip_headsign, + }} + /> + {/* { + alertsForHeader?.length > 0 ? <> + {spacerWithMiddot} + + + + : null + } +
      */} + {spacerWithMiddot} {spacerWithMiddot} {formatDurationBetween(leg.departure_time, leg.arrival_time, intl)}
      - - - - {spacerWithMiddot} - {departure} - - - 0 - ? intl.formatMessage( - { - defaultMessage: - '{numStops} {numStops, plural,' + - ' one {stop}' + - ' other {stops}' + - '} before', - description: - 'the number of stops between two listed transit stops', - }, - { numStops: stopsBetweenStartAndEnd }, - ) - : null - } - > {chunks}, + stop: stops[0].stop_name, + }} /> - - - - + {expanded ? ( +
      + {stops.slice(1, -1).map((stop, stopIdx) => ( + + + + + + ))} +
      + ) : ( +
      + 0 + ? intl.formatMessage( + { + defaultMessage: + '{numStops} {numStops, plural,' + + ' one {stop}' + + ' other {stops}' + + '} before', + description: + 'the number of stops between two listed transit stops', + }, + { numStops: stopsBetweenStartAndEnd }, + ) + : null + } /> - {spacerWithMiddot} - {arrival} - +
      + )} + + {chunks}, + stop: stops[stops.length - 1].stop_name, + }} + /> + {spacerWithMiddot} + {arrival} -
      ); diff --git a/src/components/ModeIcon.jsx b/src/components/ModeIcon.jsx index 79aad1ab..280c6904 100644 --- a/src/components/ModeIcon.jsx +++ b/src/components/ModeIcon.jsx @@ -18,7 +18,13 @@ import { ReactComponent as FerryIcon } from 'iconoir/icons/sea-waves.svg'; * for you. */ -export default function ModeIcon({ mode, width, height, fallback = BusIcon }) { +export default function ModeIcon({ + mode, + width, + height, + onClick, + fallback = BusIcon, +}) { let IconSvg; switch (mode) { @@ -48,5 +54,5 @@ export default function ModeIcon({ mode, width, height, fallback = BusIcon }) { break; } - return ; + return ; } diff --git a/src/components/Routes.jsx b/src/components/Routes.jsx index ba769088..bbc39d9a 100644 --- a/src/components/Routes.jsx +++ b/src/components/Routes.jsx @@ -7,6 +7,7 @@ import { TRANSIT_SERVICE_AREA } from '../lib/region'; import { routeClicked, itineraryBackClicked, + itineraryIconClicked, itineraryStepClicked, itineraryStepBackClicked, } from '../features/routes'; @@ -23,6 +24,7 @@ export default function Routes(props) { activeRoute, details, viewingStep, + viewingLeg, destinationDescription, outOfAreaStart, outOfAreaEnd, @@ -50,6 +52,7 @@ export default function Routes(props) { routes: routes.routes, activeRoute: routes.activeRoute, details: routes.viewingDetails, + viewingLeg: routes.viewingLeg, viewingStep: routes.viewingStep, destinationDescription, outOfAreaStart, @@ -67,6 +70,10 @@ export default function Routes(props) { dispatch(itineraryStepClicked(legClicked, stepClicked)); }; + const handleIconClick = (legClicked) => { + dispatch(itineraryIconClicked(legClicked)); + }; + const handleBackClick = () => { dispatch(itineraryBackClicked()); }; @@ -122,6 +129,8 @@ export default function Routes(props) { route={routes[activeRoute]} onBackClick={handleBackClick} onStepClick={handleStepClick} + onIconClick={handleIconClick} + viewingLeg={viewingLeg} destinationDescription={destinationDescription} scrollToStep={prevViewingStep} /> diff --git a/src/components/RoutesOverview.jsx b/src/components/RoutesOverview.jsx index 5f109bbb..ddf1123b 100644 --- a/src/components/RoutesOverview.jsx +++ b/src/components/RoutesOverview.jsx @@ -46,7 +46,7 @@ export default function RoutesOverview({ >
        - {route.legs.filter(_isSignificantLeg).map((leg, index) => ( + {route.legs.filter(isSignificantLeg).map((leg, index) => ( {index > 0 && (
      • @@ -166,7 +166,7 @@ export default function RoutesOverview({ ); } -function _isSignificantLeg(leg) { +export function isSignificantLeg(leg) { // For filtering out short, interpolated legs const THRESHOLD_IN_METERS = 120; return !( diff --git a/src/components/primitives/Icon.jsx b/src/components/primitives/Icon.jsx index 6498bed8..50a7d30f 100644 --- a/src/components/primitives/Icon.jsx +++ b/src/components/primitives/Icon.jsx @@ -23,6 +23,7 @@ export default function Icon(props) { '-scale-x-100': props.flipHorizontally, [props.className]: !!props.className, })} + onClick={props.onClick} > {props.children} diff --git a/src/features/routes.js b/src/features/routes.js index 13374e57..17f17196 100644 --- a/src/features/routes.js +++ b/src/features/routes.js @@ -13,6 +13,7 @@ const DEFAULT_STATE = { // State specific to an active route. viewingDetails: false, // True if viewing detailed itinerary for the active route + viewingLeg: null, // number leg if viewing a particular leg of active route. viewingStep: null, // Array [leg, step] if viewing a particular step of active route. }; @@ -124,6 +125,12 @@ export function routesReducer(state = DEFAULT_STATE, action) { }); case 'itinerary_back_clicked': return { ...state, viewingDetails: false }; + case 'itinerary_icon_clicked': + if (state.viewingLeg === action.leg) { + return { ...state, viewingLeg: null }; + } else { + return { ...state, viewingLeg: action.leg }; + } case 'itinerary_step_clicked': return { ...state, viewingStep: [action.leg, action.step] }; case 'itinerary_step_back_clicked': @@ -265,3 +272,10 @@ export function itineraryStepClicked(legIndex, stepIndex) { export function itineraryStepBackClicked() { return { type: 'itinerary_step_back_clicked' }; } + +export function itineraryIconClicked(legIndex) { + return { + type: 'itinerary_icon_clicked', + leg: legIndex, + }; +} diff --git a/src/lib/icons/icon-chevron.svg b/src/lib/icons/icon-chevron.svg new file mode 100644 index 00000000..4ebce57d --- /dev/null +++ b/src/lib/icons/icon-chevron.svg @@ -0,0 +1 @@ + \ No newline at end of file From 7fcd00fb1692f39fc0685f3fc1d64e2ea3920869 Mon Sep 17 00:00:00 2001 From: Chris Arvin Date: Wed, 3 Apr 2024 18:24:42 -0700 Subject: [PATCH 2/5] more compact directions + street names for bike routes --- src/components/Itinerary.css | 2 +- src/components/Itinerary.jsx | 1 + src/components/ItineraryBikeLeg.jsx | 6 +- src/components/ItineraryHeader.css | 35 ++++--- src/components/ItineraryHeader.jsx | 8 -- src/components/ItineraryRow.css | 11 +- src/components/ItineraryStep.css | 13 ++- src/components/ItineraryStep.jsx | 2 +- src/components/ItineraryTransitLeg.jsx | 133 ++++--------------------- src/lib/formatMajorStreets.js | 53 ++++++++++ 10 files changed, 118 insertions(+), 146 deletions(-) create mode 100644 src/lib/formatMajorStreets.js diff --git a/src/components/Itinerary.css b/src/components/Itinerary.css index 686064cf..3ef2b815 100644 --- a/src/components/Itinerary.css +++ b/src/components/Itinerary.css @@ -1,5 +1,5 @@ .Itinerary { - padding: 32px 32px; + padding: 32px 20px; } .Itinerary_overallTimeHeading { diff --git a/src/components/Itinerary.jsx b/src/components/Itinerary.jsx index b76f80a2..6e00475f 100644 --- a/src/components/Itinerary.jsx +++ b/src/components/Itinerary.jsx @@ -59,6 +59,7 @@ export default function Itinerary({ onIconClick={onIconClick.bind(null, idx)} expanded={viewingLeg === idx} scrollToStep={scrollToLegIdx === idx ? scrollToStepIdx : null} + displayLegElevation={false} /> ); } diff --git a/src/components/ItineraryBikeLeg.jsx b/src/components/ItineraryBikeLeg.jsx index 4f4252f8..f7d162c8 100644 --- a/src/components/ItineraryBikeLeg.jsx +++ b/src/components/ItineraryBikeLeg.jsx @@ -2,6 +2,7 @@ import * as React from 'react'; import { FormattedMessage, useIntl } from 'react-intl'; import { BIKEHOPPER_THEME_COLOR } from '../lib/colors'; import formatDistance from '../lib/formatDistance'; +import formatMajorStreets from '../lib/formatMajorStreets'; import { describeBikeInfra } from '../lib/geometry'; import { formatDurationBetween } from '../lib/time'; import InstructionSigns from '../lib/InstructionSigns'; @@ -22,6 +23,7 @@ export default function ItineraryBikeLeg({ onStepClick, onIconClick, scrollToStep, + displayLegElevation, }) { const intl = useIntl(); const instructionsWithBikeInfra = React.useMemo(() => { @@ -83,12 +85,14 @@ export default function ItineraryBikeLeg({ {formatDistance(leg.distance, intl)} {spacer} {formatDurationBetween(leg.departure_time, leg.arrival_time, intl)} + {spacer} + {formatMajorStreets(leg)} {expanded ? ( <> - {isOnlyLeg ? null : ( + {isOnlyLeg || !displayLegElevation ? null : ( )} diff --git a/src/components/ItineraryHeader.css b/src/components/ItineraryHeader.css index 19005dbb..04707cdd 100644 --- a/src/components/ItineraryHeader.css +++ b/src/components/ItineraryHeader.css @@ -6,9 +6,9 @@ .ItineraryHeader_iconContainer { display: inline-flex; - width: 60px; - height: 60px; - border-radius: 30px; + width: 36px; + height: 36px; + border-radius: 18px; text-align: center; flex-direction: column; justify-content: space-around; @@ -21,8 +21,8 @@ .ItineraryHeader_icon { top: 0; - width: 32px; - height: 32px; + width: 20px; + height: 20px; } .ItineraryHeader_arrow { @@ -34,30 +34,41 @@ } .ItineraryHeader_header { - font-size: 20px; - font-weight: bold; + font-size: 17px; + font-weight: 500; margin: 0; } .ItineraryHeader_subheading { font-weight: normal; - font-size: 16px; - margin: 8px 0; + font-size: 14px; + margin: 2px 0 0 0; + opacity: 0.6; } .ItineraryHeader_alerts { - background-color: #fffbca; + background-color: #fff5c0; padding: 8px; - margin: 0; + margin: 8px 0 0 0; list-style-type: none; - border: 1px solid rgb(187, 187, 187); + border: 1px solid #f4d598; border-radius: 4px; + font-size: 14px; } .ItineraryHeader_alertIcon { margin-right: 4px; position: relative; top: 4px; + width: 16px; + height: 16px; + display: inline-block; + vertical-align: top; +} + +.ItineraryHeader_alertIcon svg { + width: 16px; + height: 16px; } .ItineraryHeader_alertHeader { diff --git a/src/components/ItineraryHeader.jsx b/src/components/ItineraryHeader.jsx index bec7827a..c36b2e32 100644 --- a/src/components/ItineraryHeader.jsx +++ b/src/components/ItineraryHeader.jsx @@ -45,14 +45,6 @@ export default function ItineraryHeader({ {icon} - {displayArrow && ( - - )}

        {header}

        {subheading &&

        {subheading}

        } diff --git a/src/components/ItineraryRow.css b/src/components/ItineraryRow.css index 77c4d17a..12f8b05d 100644 --- a/src/components/ItineraryRow.css +++ b/src/components/ItineraryRow.css @@ -5,17 +5,17 @@ } .ItineraryRow_timeline { - width: 60px; - margin-right: 18px; + width: 36px; + margin-right: 12px; box-sizing: border-box; flex-shrink: 0; /* Draws the literal time*line* ... a dark gray line going down the middle. */ background: linear-gradient( to right, - rgba(0, 0, 0, 0) 30px, - rgba(187, 187, 187, 1) 30px 33px, - rgba(0, 0, 0, 0) 33px 100% + rgba(0, 0, 0, 0) 17px, + rgba(187, 187, 187, 1) 17px 19px, + rgba(0, 0, 0, 0) 17px 100% ); } @@ -24,4 +24,5 @@ flex-direction: column; display: flex; justify-content: space-evenly; + padding-bottom: 4px; } diff --git a/src/components/ItineraryStep.css b/src/components/ItineraryStep.css index 7d919eab..c8a0f9ef 100644 --- a/src/components/ItineraryStep.css +++ b/src/components/ItineraryStep.css @@ -1,5 +1,5 @@ .ItineraryStep_iconContainer { - margin-left: 15px; + margin-left: 7px; } .ItineraryStep_iconLarge path { @@ -8,7 +8,7 @@ .ItineraryStep_iconSmall { position: relative; - margin-left: 9px; + margin-left: 3.5px; top: 11px; } @@ -17,7 +17,14 @@ } .ItineraryStep_content { - margin: 8px 0; + margin: 0px 0; + font-size: 14px; +} + +.ItineraryTransitLeg { + .ItineraryStep_content { + margin: 8px 0; + } } .ItineraryStep_iconSvg { diff --git a/src/components/ItineraryStep.jsx b/src/components/ItineraryStep.jsx index d1509263..31164246 100644 --- a/src/components/ItineraryStep.jsx +++ b/src/components/ItineraryStep.jsx @@ -11,7 +11,7 @@ export default function ItineraryStep({ rootRef, children, }) { - const iconSize = smallIcon ? 15 : 30; + const iconSize = smallIcon ? 15 : 22; return ( @@ -197,119 +198,21 @@ export default function ItineraryTransitLeg({ // Internal-only component that gives localized strings for all possible // transit modes. -function ItineraryTransitLegHeaderMessage({ name, mode, agency }) { - switch (mode) { - case MODES.TRAM_STREETCAR_LIGHT_RAIL: - return ( - - ); - case MODES.MONORAIL: - return ( - - ); - case MODES.SUBWAY_METRO: - return ( - - ); - case MODES.RAIL_INTERCITY_LONG_DISTANCE: - return ( - - ); - case MODES.BUS: - case MODES.TROLLEYBUS: - return ( - - ); - case MODES.FERRY: - return ( - - ); - case MODES.CABLE_TRAM: - case MODES.AERIAL_TRAM_SUSPENDED_CABLE_CAR: - return ( - - ); - case MODES.FUNICULAR: - return ( - - ); - default: - return ( - - ); - } +function ItineraryTransitLegHeaderMessage({ + name, + mode, + agency, + lastStopName, +}) { + return ( + + ); } diff --git a/src/lib/formatMajorStreets.js b/src/lib/formatMajorStreets.js new file mode 100644 index 00000000..5e73d421 --- /dev/null +++ b/src/lib/formatMajorStreets.js @@ -0,0 +1,53 @@ +export default function formatMajorStreets(leg) { + var distanceByStreetName = {}; + leg.instructions.forEach((instruction) => { + if (!instruction.street_name) { + return; + } + + const streetName = instruction.street_name; + distanceByStreetName[streetName] = distanceByStreetName[streetName] || 0; + distanceByStreetName[streetName] += instruction.distance; + }); + + var streetsWithDistance = Object.keys(distanceByStreetName).map( + (streetName) => { + return { + name: streetName, + totalDistance: distanceByStreetName[streetName], + }; + }, + ); + + if (streetsWithDistance.length < 1) { + return; + } + + var quartileDistance = quartileStreets(streetsWithDistance, 0.85); + + var streetsOverQuartile = streetsWithDistance.filter((street) => { + return street.totalDistance >= quartileDistance; + }); + + return 'via ' + streetsOverQuartile.map((s) => s.name).join(', '); +} + +function quartileStreets(streets, q) { + streets = streets.concat([]); + streets.sort((a, b) => { + return a.totalDistance - b.totalDistance; + }); + + var pos = (streets.length - 1) * q; + var base = Math.floor(pos); + var rest = pos - base; + + if (streets[base + 1] !== undefined) { + return ( + streets[base].totalDistance + + rest * (streets[base + 1].totalDistance - streets[base].totalDistance) + ); + } else { + return streets[base].totalDistance; + } +} From 1cf4958449dfe8be5adf8a7e404045caec5b5cdf Mon Sep 17 00:00:00 2001 From: Aditya Bhumbla Date: Wed, 17 Apr 2024 20:07:51 -0700 Subject: [PATCH 3/5] CSS improvements, moving stuff around --- src/components/Itinerary.jsx | 10 +-- src/components/ItineraryBikeLeg.jsx | 26 +++----- src/components/ItineraryBikeStep.css | 7 +++ src/components/ItineraryBikeStep.jsx | 26 +++++++- src/components/ItineraryDivider.css | 20 +----- src/components/ItineraryDivider.jsx | 11 ---- src/components/ItineraryHeader.css | 8 +-- src/components/ItineraryHeader.jsx | 39 +++++++++--- src/components/ItineraryStep.css | 31 +++++++--- src/components/ItineraryStep.jsx | 58 +++++++++++------- src/components/ItineraryTransitLeg.css | 8 +++ src/components/ItineraryTransitLeg.jsx | 85 +++++++++++++------------- src/components/Routes.jsx | 8 +-- src/components/RoutesOverview.jsx | 11 +--- src/components/primitives/Icon.jsx | 1 - src/features/routes.js | 6 +- src/lib/leg.js | 9 +++ 17 files changed, 210 insertions(+), 154 deletions(-) create mode 100644 src/components/ItineraryBikeStep.css create mode 100644 src/lib/leg.js diff --git a/src/components/Itinerary.jsx b/src/components/Itinerary.jsx index 6e00475f..f1c52b65 100644 --- a/src/components/Itinerary.jsx +++ b/src/components/Itinerary.jsx @@ -8,7 +8,7 @@ import ItineraryBikeLeg from './ItineraryBikeLeg'; import ItineraryHeader from './ItineraryHeader'; import ItineraryTransitLeg from './ItineraryTransitLeg'; import ItineraryElevationProfile from './ItineraryElevationProfile'; -import { isSignificantLeg } from './RoutesOverview'; +import { isSignificantLeg } from '../lib/leg'; import { ReactComponent as NavLeftArrow } from 'iconoir/icons/nav-arrow-left.svg'; import { ReactComponent as ArriveIcon } from 'iconoir/icons/triangle-flag.svg'; @@ -19,7 +19,7 @@ export default function Itinerary({ destinationDescription, onBackClick, onStepClick, - onIconClick, + onToggleLegExpand, viewingLeg, scrollToStep, }) { @@ -34,7 +34,7 @@ export default function Itinerary({ key={idx} leg={leg} onStopClick={onStepClick.bind(null, idx)} - onIconClick={onIconClick.bind(null, idx)} + onToggleLegExpand={onToggleLegExpand.bind(null, idx)} expanded={viewingLeg === idx} scrollTo={scrollToLegIdx === idx} /> @@ -42,7 +42,7 @@ export default function Itinerary({ } else if (isSignificantLeg(leg)) { const isOnlyLeg = legs.length === 1; if (isOnlyLeg) { - onIconClick(idx); + onToggleLegExpand(idx); } // Where are we biking to? (Either final destination, or name of transit stop to board) const legDestination = @@ -56,7 +56,7 @@ export default function Itinerary({ legDestination={legDestination} isOnlyLeg={isOnlyLeg} onStepClick={onStepClick.bind(null, idx)} - onIconClick={onIconClick.bind(null, idx)} + onToggleLegExpand={onToggleLegExpand.bind(null, idx)} expanded={viewingLeg === idx} scrollToStep={scrollToLegIdx === idx ? scrollToStepIdx : null} displayLegElevation={false} diff --git a/src/components/ItineraryBikeLeg.jsx b/src/components/ItineraryBikeLeg.jsx index f7d162c8..56cae327 100644 --- a/src/components/ItineraryBikeLeg.jsx +++ b/src/components/ItineraryBikeLeg.jsx @@ -9,7 +9,6 @@ import InstructionSigns from '../lib/InstructionSigns'; import useScrollToRef from '../hooks/useScrollToRef'; import ItineraryBikeStep from './ItineraryBikeStep'; import ItineraryHeader from './ItineraryHeader'; -import ItineraryDivider from './ItineraryDivider'; import ItinerarySpacer from './ItinerarySpacer'; import { ReactComponent as BikeIcon } from 'iconoir/icons/bicycle.svg'; @@ -21,7 +20,7 @@ export default function ItineraryBikeLeg({ isOnlyLeg, expanded, onStepClick, - onIconClick, + onToggleLegExpand, scrollToStep, displayLegElevation, }) { @@ -70,9 +69,8 @@ export default function ItineraryBikeLeg({ iconColor={BIKEHOPPER_THEME_COLOR} alerts={alerts} expanded={expanded} - displayArrow={true} alertsExpanded={true} - onIconClick={onIconClick} + onToggleLegExpand={onToggleLegExpand} > {expanded ? ( - <> +
        + + {isOnlyLeg || !displayLegElevation ? null : ( )} @@ -103,23 +103,17 @@ export default function ItineraryBikeLeg({ , - - {step.bikeInfra} - , ], )} - - +
        ) : ( )} diff --git a/src/components/ItineraryBikeStep.css b/src/components/ItineraryBikeStep.css new file mode 100644 index 00000000..402ded38 --- /dev/null +++ b/src/components/ItineraryBikeStep.css @@ -0,0 +1,7 @@ +.ItineraryBikeStep_content { + margin-bottom: 15px; +} + +.ItineraryBikeStep_infra { + color: #438601; +} diff --git a/src/components/ItineraryBikeStep.jsx b/src/components/ItineraryBikeStep.jsx index 3087766c..8df98402 100644 --- a/src/components/ItineraryBikeStep.jsx +++ b/src/components/ItineraryBikeStep.jsx @@ -3,6 +3,9 @@ import { FormattedMessage, useIntl } from 'react-intl'; import BorderlessButton from './BorderlessButton'; import InstructionSigns from '../lib/InstructionSigns'; import ItineraryStep from './ItineraryStep'; +import classnames from 'classnames'; + +import './ItineraryBikeStep.css'; import { ReactComponent as MapsTurnBack } from 'iconoir/icons/maps-turn-back.svg'; import { ReactComponent as LongArrowUpLeft } from 'iconoir/icons/long-arrow-up-left.svg'; @@ -13,9 +16,12 @@ import { ReactComponent as QuestionMarkCircle } from 'iconoir/icons/help-circle. import { ReactComponent as ArrowTrCircle } from 'iconoir/icons/arrow-tr-circle.svg'; let _warnedOfFallback = false; +const spacerWithMiddot = ' \u00B7 '; export default function ItineraryBikeStep({ step, + distance, + infra, isFirstStep, onClick, rootRef, @@ -313,7 +319,25 @@ export default function ItineraryBikeStep({ return ( - {msg} +
        + + {msg} + {spacerWithMiddot} + {distance} + {infra ? spacerWithMiddot : null} + + {infra} + + +
        ); } diff --git a/src/components/ItineraryDivider.css b/src/components/ItineraryDivider.css index 1d8e56a1..7d523e64 100644 --- a/src/components/ItineraryDivider.css +++ b/src/components/ItineraryDivider.css @@ -1,24 +1,10 @@ -.ItineraryDivider_subheading { - font-weight: bold; - font-size: 14px; - line-height: 14px; - margin: 0 0 8px; -} - -.ItineraryDivider_subheadingTransit { - color: #626262; -} - -.ItineraryDivider_subheadingBike { - color: #438601; -} - .ItineraryDivider_horizontalRule { color: #626262; font-size: 12px; line-height: 12px; - margin-bottom: 12px; - margin-top: 8px; + margin-bottom: 5px; + margin-left: 25px; + margin-top: 5px; display: inline-block; height: 12px; diff --git a/src/components/ItineraryDivider.jsx b/src/components/ItineraryDivider.jsx index 581461f0..093e513e 100644 --- a/src/components/ItineraryDivider.jsx +++ b/src/components/ItineraryDivider.jsx @@ -10,17 +10,6 @@ export default function ItineraryDivider(props) { return ( {'' /* no content for timeline side of row */} - {subheading && ( - - {subheading} - - )} {detail && {detail}} diff --git a/src/components/ItineraryHeader.css b/src/components/ItineraryHeader.css index 04707cdd..004ac540 100644 --- a/src/components/ItineraryHeader.css +++ b/src/components/ItineraryHeader.css @@ -25,12 +25,8 @@ height: 20px; } -.ItineraryHeader_arrow { - transform: rotate(0deg); -} - -.ItineraryHeader_arrow_90 { - transform: rotate(90deg); +.ItineraryHeader_headerRow { + display: flex; } .ItineraryHeader_header { diff --git a/src/components/ItineraryHeader.jsx b/src/components/ItineraryHeader.jsx index c36b2e32..75907ebc 100644 --- a/src/components/ItineraryHeader.jsx +++ b/src/components/ItineraryHeader.jsx @@ -5,6 +5,8 @@ import { getTextColor } from '../lib/colors'; import Icon from './primitives/Icon'; import ItineraryRow from './ItineraryRow'; import { ReactComponent as WarningTriangle } from 'iconoir/icons/warning-triangle.svg'; +import { ReactComponent as NavDownArrow } from 'iconoir/icons/nav-arrow-down.svg'; +import { ReactComponent as NavUpArrow } from 'iconoir/icons/nav-arrow-up.svg'; import ArrowChevron from '../lib/icons/icon-chevron.svg'; import './ItineraryHeader.css'; @@ -18,11 +20,11 @@ export default function ItineraryHeader({ children, icon, iconColor, + displayArrow = true, + expanded, alertsExpanded, - onIconClick, + onToggleLegExpand, onAlertClick, - expanded, - displayArrow, }) { const intl = useIntl(); const iconIsWhite = getTextColor(iconColor).main === 'white'; @@ -41,13 +43,34 @@ export default function ItineraryHeader({ ItineraryHeader_iconContainer__isWhite: iconIsWhite, })} style={{ backgroundColor: iconColor }} + onClick={onToggleLegExpand} > - - {icon} - + {icon} +
        + + {displayArrow && ( +
        + + {expanded ? ( + + ) : ( + + )} + +
        + )} +
        +

        {header}

        + {subheading && ( +

        {subheading}

        + )} +
        -

        {header}

        - {subheading &&

        {subheading}

        } {alerts?.length > 0 && (
          {alerts.map(([alertHeader, alertBody], idx) => ( diff --git a/src/components/ItineraryStep.css b/src/components/ItineraryStep.css index c8a0f9ef..79a1f4d0 100644 --- a/src/components/ItineraryStep.css +++ b/src/components/ItineraryStep.css @@ -1,3 +1,7 @@ +/* .ItineraryStep { + margin-left: 20px; +} */ + .ItineraryStep_iconContainer { margin-left: 7px; } @@ -9,22 +13,35 @@ .ItineraryStep_iconSmall { position: relative; margin-left: 3.5px; - top: 11px; + top: 6px; } .ItineraryStep_iconSmall path { - stroke-width: 4px; + stroke-width: 3px; +} + +.ItineraryStep_iconTiny { + position: relative; + margin-left: 5px; + top: 4px; +} + +.ItineraryStep_iconTiny path { + stroke-width: 2px; } .ItineraryStep_content { - margin: 0px 0; + margin-left: 25px; font-size: 14px; + margin-bottom: 0px; +} + +.ItineraryStep_contentLowMargin { + margin-top: 0px; } -.ItineraryTransitLeg { - .ItineraryStep_content { - margin: 8px 0; - } +.ItineraryStep_contentHighMargin { + margin-top: 5px; } .ItineraryStep_iconSvg { diff --git a/src/components/ItineraryStep.jsx b/src/components/ItineraryStep.jsx index 31164246..00ef0e87 100644 --- a/src/components/ItineraryStep.jsx +++ b/src/components/ItineraryStep.jsx @@ -7,33 +7,49 @@ import './ItineraryStep.css'; export default function ItineraryStep({ IconSVGComponent, - smallIcon, + iconSize, + highMargin = false, rootRef, children, }) { - const iconSize = smallIcon ? 15 : 22; + const iconSizePx = iconSize === 'tiny' ? 12 : iconSize === 'small' ? 15 : 22; return ( - - - + + - - - -

          {children}

          -
          + + + + +

          + {children} +

          + +
      ); } diff --git a/src/components/ItineraryTransitLeg.css b/src/components/ItineraryTransitLeg.css index dd46751d..72502eda 100644 --- a/src/components/ItineraryTransitLeg.css +++ b/src/components/ItineraryTransitLeg.css @@ -4,3 +4,11 @@ padding: 0; margin: 0; } + +.ItineraryDivider_headsign { + font-style: italic; + font-size: 14px; + /* line-height: 14px; */ + /* margin: 0 0 8px; */ + color: #626262; +} diff --git a/src/components/ItineraryTransitLeg.jsx b/src/components/ItineraryTransitLeg.jsx index 00f1620c..0444613c 100644 --- a/src/components/ItineraryTransitLeg.jsx +++ b/src/components/ItineraryTransitLeg.jsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { FormattedMessage, useIntl } from 'react-intl'; import { DEFAULT_PT_COLOR } from '../lib/colors'; import { formatTime, formatDurationBetween } from '../lib/time'; -import { MODES } from '../lib/TransitModes'; +import classnames from 'classnames'; import { getAgencyDisplayName } from '../lib/region'; import useScrollToRef from '../hooks/useScrollToRef'; import BorderlessButton from './BorderlessButton'; @@ -11,8 +11,7 @@ import ItineraryDivider from './ItineraryDivider'; import ItinerarySpacer from './ItinerarySpacer'; import ItineraryStep from './ItineraryStep'; import ModeIcon from './ModeIcon'; -import Icon from './Icon'; -import { ReactComponent as WarningTriangle } from 'iconoir/icons/warning-triangle.svg'; +// import { ReactComponent as WarningTriangle } from 'iconoir/icons/warning-triangle.svg'; import { ReactComponent as Circle } from 'iconoir/icons/circle.svg'; @@ -22,7 +21,7 @@ import { useState, useCallback } from 'react'; export default function ItineraryTransitLeg({ leg, onStopClick, - onIconClick, + onToggleLegExpand, scrollTo, expanded, }) { @@ -61,11 +60,11 @@ export default function ItineraryTransitLeg({ return (
      } + icon={} iconColor={leg.route_color || DEFAULT_PT_COLOR} expanded={expanded} - displayArrow={true} alertsExpanded={alertsExpanded} + onToggleLegExpand={onToggleLegExpand} onAlertClick={toggleAlertsExpanded} alerts={alertsForHeader} > @@ -78,36 +77,6 @@ export default function ItineraryTransitLeg({ /> - {chunks}, - headsign: leg.trip_headsign, - }} - /> - {/* { - alertsForHeader?.length > 0 ? <> - {spacerWithMiddot} - - - - : null - } -
      */} - {spacerWithMiddot}
      - + {chunks}, @@ -135,19 +108,39 @@ export default function ItineraryTransitLeg({ /> {spacerWithMiddot} {departure} +
      + +
      + {expanded ? ( -
      +
      {stops.slice(1, -1).map((stop, stopIdx) => ( ) : ( -
      +
      )} - + {chunks}, diff --git a/src/components/Routes.jsx b/src/components/Routes.jsx index bbc39d9a..acf11212 100644 --- a/src/components/Routes.jsx +++ b/src/components/Routes.jsx @@ -7,9 +7,9 @@ import { TRANSIT_SERVICE_AREA } from '../lib/region'; import { routeClicked, itineraryBackClicked, - itineraryIconClicked, itineraryStepClicked, itineraryStepBackClicked, + legExpandToggled, } from '../features/routes'; import describePlace from '../lib/describePlace'; import RoutesOverview from './RoutesOverview'; @@ -70,8 +70,8 @@ export default function Routes(props) { dispatch(itineraryStepClicked(legClicked, stepClicked)); }; - const handleIconClick = (legClicked) => { - dispatch(itineraryIconClicked(legClicked)); + const handleToggleLegExpand = (legClicked) => { + dispatch(legExpandToggled(legClicked)); }; const handleBackClick = () => { @@ -129,7 +129,7 @@ export default function Routes(props) { route={routes[activeRoute]} onBackClick={handleBackClick} onStepClick={handleStepClick} - onIconClick={handleIconClick} + onToggleLegExpand={handleToggleLegExpand} viewingLeg={viewingLeg} destinationDescription={destinationDescription} scrollToStep={prevViewingStep} diff --git a/src/components/RoutesOverview.jsx b/src/components/RoutesOverview.jsx index ddf1123b..593bd7dc 100644 --- a/src/components/RoutesOverview.jsx +++ b/src/components/RoutesOverview.jsx @@ -5,6 +5,7 @@ import formatDistance from '../lib/formatDistance'; import { TRANSIT_DATA_ACKNOWLEDGEMENT } from '../lib/region'; import { formatInterval } from '../lib/time'; import Icon from './primitives/Icon'; +import { isSignificantLeg } from '../lib/leg'; import RouteLeg from './RouteLeg'; import SelectionList from './SelectionList'; import SelectionListItem from './SelectionListItem'; @@ -166,16 +167,6 @@ export default function RoutesOverview({ ); } -export function isSignificantLeg(leg) { - // For filtering out short, interpolated legs - const THRESHOLD_IN_METERS = 120; - return !( - leg.type === 'bike2' && - leg.interpolated && - leg.distance < THRESHOLD_IN_METERS - ); -} - function _outOfAreaMsg(intl, start, end) { const which = start ? (end ? 'both' : 'start') : end ? 'end' : 'neither'; if (which === 'neither') return null; diff --git a/src/components/primitives/Icon.jsx b/src/components/primitives/Icon.jsx index 50a7d30f..6498bed8 100644 --- a/src/components/primitives/Icon.jsx +++ b/src/components/primitives/Icon.jsx @@ -23,7 +23,6 @@ export default function Icon(props) { '-scale-x-100': props.flipHorizontally, [props.className]: !!props.className, })} - onClick={props.onClick} > {props.children} diff --git a/src/features/routes.js b/src/features/routes.js index 17f17196..ba1969ae 100644 --- a/src/features/routes.js +++ b/src/features/routes.js @@ -125,7 +125,7 @@ export function routesReducer(state = DEFAULT_STATE, action) { }); case 'itinerary_back_clicked': return { ...state, viewingDetails: false }; - case 'itinerary_icon_clicked': + case 'leg_expand_toggled': if (state.viewingLeg === action.leg) { return { ...state, viewingLeg: null }; } else { @@ -273,9 +273,9 @@ export function itineraryStepBackClicked() { return { type: 'itinerary_step_back_clicked' }; } -export function itineraryIconClicked(legIndex) { +export function legExpandToggled(legIndex) { return { - type: 'itinerary_icon_clicked', + type: 'leg_expand_toggled', leg: legIndex, }; } diff --git a/src/lib/leg.js b/src/lib/leg.js new file mode 100644 index 00000000..314d3410 --- /dev/null +++ b/src/lib/leg.js @@ -0,0 +1,9 @@ +export function isSignificantLeg(leg) { + // For filtering out short, interpolated legs + const THRESHOLD_IN_METERS = 120; + return !( + leg.type === 'bike2' && + leg.interpolated && + leg.distance < THRESHOLD_IN_METERS + ); +} From 808960547729959bfc656cd2ccad7d680576c4d8 Mon Sep 17 00:00:00 2001 From: Aditya Bhumbla Date: Wed, 17 Apr 2024 20:29:27 -0700 Subject: [PATCH 4/5] restore mode text --- src/components/ItineraryTransitLeg.jsx | 136 +++++++++++++++++++++---- 1 file changed, 115 insertions(+), 21 deletions(-) diff --git a/src/components/ItineraryTransitLeg.jsx b/src/components/ItineraryTransitLeg.jsx index 0444613c..045d4438 100644 --- a/src/components/ItineraryTransitLeg.jsx +++ b/src/components/ItineraryTransitLeg.jsx @@ -11,7 +11,6 @@ import ItineraryDivider from './ItineraryDivider'; import ItinerarySpacer from './ItinerarySpacer'; import ItineraryStep from './ItineraryStep'; import ModeIcon from './ModeIcon'; -// import { ReactComponent as WarningTriangle } from 'iconoir/icons/warning-triangle.svg'; import { ReactComponent as Circle } from 'iconoir/icons/circle.svg'; @@ -70,7 +69,6 @@ export default function ItineraryTransitLeg({ > - ); +function ItineraryTransitLegHeaderMessage({ mode, agency, lastStopName }) { + switch (mode) { + case MODES.TRAM_STREETCAR_LIGHT_RAIL: + return ( + + ); + case MODES.MONORAIL: + return ( + + ); + case MODES.SUBWAY_METRO: + return ( + + ); + case MODES.RAIL_INTERCITY_LONG_DISTANCE: + return ( + + ); + case MODES.BUS: + case MODES.TROLLEYBUS: + return ( + + ); + case MODES.FERRY: + return ( + + ); + case MODES.CABLE_TRAM: + case MODES.AERIAL_TRAM_SUSPENDED_CABLE_CAR: + return ( + + ); + case MODES.FUNICULAR: + return ( + + ); + default: + return ( + + ); + } } From 8af51ea87dc6f83e6694c522bc844af6b80e9685 Mon Sep 17 00:00:00 2001 From: Aditya Bhumbla Date: Wed, 17 Apr 2024 20:30:12 -0700 Subject: [PATCH 5/5] fix import --- src/components/ItineraryTransitLeg.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/ItineraryTransitLeg.jsx b/src/components/ItineraryTransitLeg.jsx index 045d4438..dd3d6dca 100644 --- a/src/components/ItineraryTransitLeg.jsx +++ b/src/components/ItineraryTransitLeg.jsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { FormattedMessage, useIntl } from 'react-intl'; import { DEFAULT_PT_COLOR } from '../lib/colors'; +import { MODES } from '../lib/TransitModes'; import { formatTime, formatDurationBetween } from '../lib/time'; import classnames from 'classnames'; import { getAgencyDisplayName } from '../lib/region';