Skip to content

Commit

Permalink
Merge pull request #28 from ibi-group/callback-disable-all-submodes
Browse files Browse the repository at this point in the history
fix(trip form): add callback for detecting when all submodes are disabled
  • Loading branch information
josh-willis-arcadis authored Sep 25, 2024
2 parents dc9c14e + f147b68 commit 1415ce8
Show file tree
Hide file tree
Showing 16 changed files with 834 additions and 425 deletions.
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
"workspaces": [
"packages/*"
],
"resolutions": {
"react": "18.2.0",
"react-animate-height": "3.0.4"
},
"devDependencies": {
"@babel/cli": "^7.10",
"@babel/core": "^7.10",
Expand Down
1 change: 1 addition & 0 deletions packages/trip-form/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@

```
TBD
```
1 change: 0 additions & 1 deletion packages/trip-form/i18n/en-US.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ otpUi:
walkTolerance-labelHigh: More Walking
walkTolerance-labelLow: Less Walking
wheelchair-label: Accessible Routing
settingsLabel: "{mode} settings"
SettingsSelectorPanel:
bikeOnly: Bike Only
escooterOnly: E-scooter Only
Expand Down
1 change: 0 additions & 1 deletion packages/trip-form/i18n/fr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ otpUi:
walkTolerance-labelHigh: Plus de marche
walkTolerance-labelLow: Moins de marche
wheelchair-label: Accès avec mobilité réduite
settingsLabel: "Paramètres pour : {mode}"
SettingsSelectorPanel:
bikeOnly: Vélo uniquement
escooterOnly: Trottinette uniquement
Expand Down
2 changes: 2 additions & 0 deletions packages/trip-form/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@
"private": false,
"dependencies": {
"@opentripplanner/core-utils": "^11.4.4",
"@opentripplanner/building-blocks": "^1.0.3",
"@floating-ui/react": "^0.19.2",
"@styled-icons/bootstrap": "^10.34.0",
"@styled-icons/boxicons-regular": "^10.38.0",
"@styled-icons/fa-regular": "^10.37.0",
"@styled-icons/fa-solid": "^10.37.0",
"date-fns": "^2.28.0",
"flat": "^5.0.2",
"react-animate-height": "^3.0.4",
"react-indiana-drag-scroll": "^2.0.1",
"react-inlinesvg": "^2.3.0"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import React, { ReactElement, useState } from "react";
import { ModeButtonDefinition } from "@opentripplanner/types";
import * as Core from "..";
import { QueryParamChangeEvent } from "../types";
import {
addSettingsToButton,
extractModeSettingDefaultsToObject,
populateSettingWithValue,
setModeButtonEnabled
} from "./utils";
import {
defaultModeButtonDefinitions,
getIcon,
modeSettingDefinitionsWithDropdown
} from "../__mocks__/mode-selector-buttons";

const initialState = {
enabledModeButtons: ["transit"],
modeSettingValues: {}
};

function pipe<T>(...fns: Array<(arg: T) => T>) {
return (value: T) => fns.reduce((acc, fn) => fn(acc), value);
}

const MetroModeSubsettingsComponent = ({
fillModeIcons,
modeButtonDefinitions,
onAllSubmodesDisabled,
onSetModeSettingValue,
onToggleModeButton
}: {
fillModeIcons?: boolean;
modeButtonDefinitions: Array<ModeButtonDefinition>;
onAllSubmodesDisabled?: (modeButton: ModeButtonDefinition) => void;
onSetModeSettingValue: (event: QueryParamChangeEvent) => void;
onToggleModeButton: (key: string, newState: boolean) => void;
}): ReactElement => {
const [modeSettingValues, setModeSettingValues] = useState({});
const modeSettingValuesWithDefaults = {
...extractModeSettingDefaultsToObject(modeSettingDefinitionsWithDropdown),
...initialState.modeSettingValues,
...modeSettingValues
};

const [activeModeButtonKeys, setModeButtonKeys] = useState(
initialState.enabledModeButtons
);

const addIconToModeSetting = msd => ({
...msd,
icon: getIcon(msd.iconName)
});

const processedModeSettings = modeSettingDefinitionsWithDropdown.map(
pipe(
addIconToModeSetting,
populateSettingWithValue(modeSettingValuesWithDefaults)
)
);

const processedModeButtons = modeButtonDefinitions.map(
pipe(
addSettingsToButton(processedModeSettings),
setModeButtonEnabled(activeModeButtonKeys)
)
);

const toggleModeButtonAction = (key: string, newState: boolean) => {
if (newState) {
setModeButtonKeys([...activeModeButtonKeys, key]);
} else {
setModeButtonKeys(activeModeButtonKeys.filter(button => button !== key));
}
// Storybook Action:
onToggleModeButton(key, newState);
};

const onAllSubmodesDisabledAction = (modeButton: ModeButtonDefinition) => {
toggleModeButtonAction(modeButton.key, false);
// Storybook Action:
onAllSubmodesDisabled?.(modeButton);
};

const setModeSettingValueAction = (event: QueryParamChangeEvent) => {
setModeSettingValues({ ...modeSettingValues, ...event });
// Storybook Action:
onSetModeSettingValue(event);
};

return (
<div style={{ maxWidth: "500px" }}>
<Core.AdvancedModeSubsettingsContainer
fillModeIcons={fillModeIcons}
label="Select a transit mode"
modeButtons={processedModeButtons}
onAllSubmodesDisabled={onAllSubmodesDisabledAction}
onSettingsUpdate={setModeSettingValueAction}
onToggleModeButton={toggleModeButtonAction}
/>
</div>
);
};

const Template = (args: {
fillModeIcons?: boolean;
onSetModeSettingValue: (event: QueryParamChangeEvent) => void;
onToggleModeButton: (key: string, newState: boolean) => void;
}): ReactElement => (
<MetroModeSubsettingsComponent
modeButtonDefinitions={defaultModeButtonDefinitions}
// eslint-disable-next-line react/jsx-props-no-spreading
{...args}
/>
);

export const AdvancedModeSettingsButtons = Template.bind({});

export default {
argTypes: {
fillModeIcons: { control: "boolean" },
onSetModeSettingValue: { action: "set mode setting value" },
onToggleModeButton: { action: "toggle button" },
onAllSubmodesDisabled: { action: "all submodes disabled" }
},
component: MetroModeSubsettingsComponent,
title: "Trip Form Components/Advanced Mode Settings Buttons"
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import React from "react";
import AnimateHeight from "react-animate-height";
import styled from "styled-components";
import colors from "@opentripplanner/building-blocks";
import { Check2 } from "@styled-icons/bootstrap";
import { ModeButtonDefinition } from "@opentripplanner/types";
import { useIntl } from "react-intl";
import SubSettingsPane from "../SubSettingsPane";
import generateModeButtonLabel from "../i18n";
import { invisibleCss } from "..";
import { QueryParamChangeEvent } from "../../types";

const { blue, grey } = colors;

const SettingsContainer = styled.div`
width: 100%;
`;

const StyledModeSettingsButton = styled.div<{
accentColor: string;
fillModeIcons: boolean;
subsettings: boolean;
}>`
& > label {
align-items: center;
background-color: #fff;
border: 2px solid ${props => props.accentColor};
border-left-width: 2px;
border-right-width: 2px;
color: ${props => props.accentColor};
cursor: pointer;
display: grid;
font-size: 18px;
font-weight: 400;
gap: 20px;
grid-template-columns: 40px auto 40px;
height: 51px;
justify-items: center;
margin-bottom: 0;
margin-top: -2px;
padding: 0 10px;
}
& > input {
${invisibleCss}
&:checked + label {
background-color: ${props => props.accentColor};
color: #fff;
border-bottom-left-radius: ${props => props.subsettings && 0} !important;
border-bottom-right-radius: ${props => props.subsettings && 0} !important;
}
&:focus-visible + label,
&:focus + label {
outline: ${props => props.accentColor} 1px solid;
outline-offset: -4px;
}
}
& > input:checked {
&:focus-visible + label,
&:focus + label {
outline: white 1px solid;
}
}
span {
justify-self: flex-start;
}
svg {
height: 26px;
width: 26px;
fill: ${props =>
props.fillModeIcons === false ? "inherit" : "currentcolor"};
}
&:hover {
cursor: pointer;
}
`;

const StyledSettingsContainer = styled.div`
border: 1px solid ${grey[300]};
border-top: 0;
padding: 1em;
`;

interface Props {
accentColor?: string;
fillModeIcons: boolean;
id: string;
modeButton: ModeButtonDefinition;
onAllSubmodesDisabled?: (modeButton: ModeButtonDefinition) => void;
onSettingsUpdate: (event: QueryParamChangeEvent) => void;
onToggle: () => void;
}

const AdvancedModeSettingsButton = ({
accentColor = blue[700],
fillModeIcons,
id,
modeButton,
onSettingsUpdate,
onAllSubmodesDisabled,
onToggle
}: Props): JSX.Element => {
const intl = useIntl();
const label = generateModeButtonLabel(modeButton.key, intl, modeButton.label);
const checkboxId = `metro-submode-selector-mode-${id}`;
return (
<SettingsContainer className="advanced-submode-container">
<StyledModeSettingsButton
accentColor={accentColor}
className="advanced-submode-mode-button"
fillModeIcons={fillModeIcons}
id={modeButton.key}
subsettings={modeButton.modeSettings.length > 0}
>
<input
aria-label={label}
checked={modeButton.enabled ?? undefined}
id={checkboxId}
onChange={onToggle}
type="checkbox"
/>
<label htmlFor={checkboxId}>
<modeButton.Icon />
<span>{modeButton?.label}</span>
{modeButton.enabled && <Check2 />}
</label>
</StyledModeSettingsButton>
{modeButton.modeSettings.length > 0 && (
<AnimateHeight duration={300} height={modeButton.enabled ? "auto" : 0}>
<StyledSettingsContainer className="subsettings-container">
<SubSettingsPane
onSettingUpdate={onSettingsUpdate}
modeButton={modeButton}
onAllSubmodesDisabled={onAllSubmodesDisabled}
/>
</StyledSettingsContainer>
</AnimateHeight>
)}
</SettingsContainer>
);
};

export default AdvancedModeSettingsButton;
Loading

0 comments on commit 1415ce8

Please sign in to comment.