diff --git a/packages/icons/src/trimet/TriMet.js b/packages/icons/src/trimet/TriMet.js
new file mode 100644
index 000000000..90bff427e
--- /dev/null
+++ b/packages/icons/src/trimet/TriMet.js
@@ -0,0 +1,28 @@
+import React from "react";
+
+const SvgTriMet = () => (
+
+);
+
+export default SvgTriMet;
diff --git a/packages/icons/src/trimet/index.js b/packages/icons/src/trimet/index.js
index 3f0d7d044..7f0e9e5d1 100644
--- a/packages/icons/src/trimet/index.js
+++ b/packages/icons/src/trimet/index.js
@@ -36,6 +36,7 @@ import Streetcar from "./Streetcar";
import StreetcarCircle from "./StreetcarCircle";
import Transittracker from "./Transittracker";
import TransittrackerSolid from "./TransittrackerSolid";
+import TriMet from "./TriMet";
import TripPlanner from "./TripPlanner";
import TripPlannerSolid from "./TripPlannerSolid";
import Walk from "./Walk";
@@ -83,6 +84,7 @@ export {
StreetcarCircle,
Transittracker,
TransittrackerSolid,
+ TriMet,
TripPlanner,
TripPlannerSolid,
Walk,
diff --git a/packages/settings-selector/package.json b/packages/settings-selector/package.json
new file mode 100644
index 000000000..910671ae9
--- /dev/null
+++ b/packages/settings-selector/package.json
@@ -0,0 +1,28 @@
+{
+ "name": "@opentripplanner/settings-selector",
+ "version": "0.0.1",
+ "description": "Trip Settings Selector and Related Components",
+ "author": "@binh-dam-ibigroup",
+ "homepage": "https://github.com/opentripplanner/otp-ui/#readme",
+ "license": "MIT",
+ "main": "index.js",
+ "module": "src/index.js",
+ "private": false,
+ "dependencies": {
+ "@opentripplanner/icons": "^0.0.1",
+ "@opentripplanner/core-utils": "^0.0.2"
+ },
+ "publishConfig": {
+ "registry": "https://registry.yarnpkg.com"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/opentripplanner/otp-ui.git"
+ },
+ "scripts": {
+ "test": "echo \"Error: run tests from root\" && exit 1"
+ },
+ "bugs": {
+ "url": "https://github.com/opentripplanner/otp-ui/issues"
+ }
+}
diff --git a/packages/settings-selector/src/mode-button.js b/packages/settings-selector/src/mode-button.js
new file mode 100644
index 000000000..d1e2a909d
--- /dev/null
+++ b/packages/settings-selector/src/mode-button.js
@@ -0,0 +1,76 @@
+import React from "react";
+import PropTypes from "prop-types";
+
+import { ModeButtonContainer, ModeButtonBtn, ModeButtonTitle } from "./styled";
+
+/**
+ * ModeButton lets the user pick a travel mode.
+ * It includes the actual button that supports HTML/React text and graphics,
+ * and a title displayed when hovering the mouse over the button, and, optionally, underneath it.
+ * A ModeButton can be enabled or disabled, active or inactive.
+ */
+const ModeButton = props => {
+ const { selected, children, enabled, showTitle, title, onClick } = props;
+
+ const activeClassName = selected ? "active" : "";
+ const disabledClassName = enabled ? "" : "disabled";
+
+ return (
+
+
+ {children}
+
+
+ {showTitle && (
+ {title}
+ )}
+
+ );
+};
+
+ModeButton.propTypes = {
+ /**
+ * The contents of the button. Can be any HTML/React content.
+ */
+ children: PropTypes.oneOfType([
+ PropTypes.node,
+ PropTypes.arrayOf(PropTypes.node)
+ ]),
+ /**
+ * Determines whether the button is currently enabled.
+ */
+ enabled: PropTypes.bool,
+ /**
+ * Triggered when the user clicks the button.
+ */
+ onClick: PropTypes.func,
+ /**
+ * Determines whether the button should appear selected.
+ */
+ selected: PropTypes.bool,
+ /**
+ * Determines whether the title should be displayed (underneath the button).
+ */
+ showTitle: PropTypes.bool,
+ /**
+ * A title text for the button, displayed as popup when the user hover the mouse over the button,
+ * and optionally displayed underneath the button if showTitle is true.
+ */
+ title: PropTypes.string
+};
+
+ModeButton.defaultProps = {
+ children: null,
+ enabled: true,
+ onClick: null,
+ selected: false,
+ showTitle: true,
+ title: null
+};
+
+export default ModeButton;
diff --git a/packages/settings-selector/src/mode-button.story.js b/packages/settings-selector/src/mode-button.story.js
new file mode 100644
index 000000000..bdf4dbbc9
--- /dev/null
+++ b/packages/settings-selector/src/mode-button.story.js
@@ -0,0 +1,76 @@
+import React from "react";
+import { action } from "@storybook/addon-actions";
+import { withInfo } from "@storybook/addon-info";
+import * as Icons from "@opentripplanner/icons";
+
+import ModeButton from "./mode-button";
+
+const background = story => (
+
+ {story()}
+
+);
+
+export default {
+ title: "Mode Button",
+ component: "ModeButton",
+ decorators: [withInfo, background],
+ parameters: {
+ info: {
+ text: `
+ ModeButton lets the user pick a travel mode.
+ It includes the actual button that supports HTML/React text and graphics,
+ and a title displayed when hovering the mouse over the button, and, optionally, underneath it.
+ A ModeButton can be enabled or disabled, and active or inactive.
+ `
+ }
+ }
+};
+
+const onClick = action("onClick");
+
+export const normal = () => (
+
+
+ +
+
+ Go by train or bike
+
+);
+
+export const active = () => (
+
+
+ Train
+
+);
+
+export const disabled = () => (
+
+
+ Can't select!
+
+
+);
+
+export const labelOnly = () => (
+
+
+ Walk Only
+
+);
diff --git a/packages/settings-selector/src/mode-selector.js b/packages/settings-selector/src/mode-selector.js
new file mode 100644
index 000000000..01a9bc8f1
--- /dev/null
+++ b/packages/settings-selector/src/mode-selector.js
@@ -0,0 +1,94 @@
+import React from "react";
+import PropTypes from "prop-types";
+import * as Icons from "@opentripplanner/icons";
+import { isTransit } from "@opentripplanner/core-utils/lib/itinerary";
+
+import { MainModeRow, SecondaryModeRow, TertiaryModeRow } from "./styled";
+import ModeButton from "./mode-button";
+
+/**
+ * ModeSelector is the control container where the OTP user selects
+ * the primary transportation modes, e.g. transit+bike, walk, micromobility...
+ */
+const ModeSelector = props => {
+ const { selectedModes } = props;
+ const modesHaveTransit = selectedModes.some(isTransit);
+
+ return (
+
+
+
+
+ Take Transit
+
+
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+
+
+
+ Walk Only
+
+
+
+ Bike Only
+
+
+
+ );
+};
+
+ModeSelector.propTypes = {
+ /**
+ * An array of strings, each representing one transportation mode used for OTP queries.
+ */
+ selectedModes: PropTypes.arrayOf(PropTypes.string)
+};
+
+ModeSelector.defaultProps = {
+ selectedModes: null
+};
+
+export default ModeSelector;
diff --git a/packages/settings-selector/src/mode-selector.story.js b/packages/settings-selector/src/mode-selector.story.js
new file mode 100644
index 000000000..f35523cfd
--- /dev/null
+++ b/packages/settings-selector/src/mode-selector.story.js
@@ -0,0 +1,34 @@
+import React from "react";
+import { withInfo } from "@storybook/addon-info";
+
+import ModeSelector from "./mode-selector";
+
+const background = story => (
+
+ {story()}
+
+);
+
+const selectedModes = ["BICYCLE", "TRAM", "RAIL", "BUS"];
+
+export default {
+ title: "Mode Selector",
+ decorators: [withInfo, background],
+ parameters: {
+ info: {
+ text: `
+ ModeSelector is the control container where the OTP user selects
+ the primary transportation modes such as transit, bike, walk, or micromobility.
+ `
+ }
+ }
+};
+
+export const normal = () => ;
diff --git a/packages/settings-selector/src/styled.js b/packages/settings-selector/src/styled.js
new file mode 100644
index 000000000..78f70c099
--- /dev/null
+++ b/packages/settings-selector/src/styled.js
@@ -0,0 +1,84 @@
+import styled from "styled-components";
+
+export const ModeButtonContainer = styled.div`
+ display: inline-block;
+ text-align: center;
+ box-sizing: border-box;
+`;
+
+export const ModeButtonBtn = styled.button`
+ border: 1px solid rgb(187, 187, 187);
+ padding: 3px;
+ border-radius: 3px;
+ width: 100%;
+ height: 100%;
+ font-size: inherit;
+ font-family: inherit;
+ background: none;
+ outline: none;
+ cursor: pointer;
+ box-sizing: border-box;
+
+ :hover {
+ background-color: rgb(173, 216, 230);
+ }
+
+ &.active {
+ border: 2px solid rgb(0, 0, 0);
+ background-color: rgb(173, 216, 230);
+ }
+ svg {
+ vertical-align: middle;
+ width: 1.25em;
+ margin: 0 5px;
+ }
+ &.disabled {
+ cursor: default;
+ }
+ &.disabled svg {
+ fill: #ccc;
+ }
+`;
+
+export const ModeButtonTitle = styled.div`
+ font-size: 10px;
+ padding: 4px;
+
+ &.disabled {
+ color: #ccc;
+ }
+`;
+
+const ModeRow = styled.div`
+ margin-bottom: 10px;
+ > * {
+ width: 33.333333%;
+ padding: 5px;
+ box-sizing: border-box;
+ }
+`;
+
+export const MainModeRow = styled.div`
+ padding: 5px;
+ font-size: 200%;
+ margin-bottom: 10px;
+ box-sizing: border-box;
+ > * {
+ width: 100%;
+ height: 55px;
+ }
+`;
+
+export const SecondaryModeRow = styled(ModeRow)`
+ font-size: 150%;
+ > * {
+ height: 58px;
+ }
+`;
+
+export const TertiaryModeRow = styled(ModeRow)`
+ font-size: 90%;
+ > * {
+ height: 48px;
+ }
+`;