Skip to content

Commit

Permalink
Project page filters (#185)
Browse files Browse the repository at this point in the history
* Handle missing banner project properly

* Move "Featured Projects" header into React world

* Add static filter buttons

* Hook up frontend behavior of buttons

* Filter buttons work

* Test new filtering infrastructure

* Make linters pass

* CSS style fix

* Dynamically changing title for more-projects table

* Empty divs replaced with nulls where appropriate

* Fix some tests

* Get rid of FeaturedProjectsTitle

* Better CSS class name for filter button group

* Increase test coverage

* Satisfy tslint

* Fix some typespecs

* Promote regular projects to featured if mode selected

* Include banner in featured list at XS breakpoint

* Make linter happy

* Make button group width relative in XS

* Correct one snapshot

* Fix some tests that only fail in CI

* update snapshots

* improve code cov

* fix lint issues

* Only render more-projects table if there are projects to show

* Display banner properly at XS
  • Loading branch information
phildarnowsky authored Sep 5, 2019
1 parent 98a1992 commit 84dfdf5
Show file tree
Hide file tree
Showing 36 changed files with 1,633 additions and 736 deletions.
8 changes: 8 additions & 0 deletions apps/site/assets/css/_transit-near-me.scss
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,14 @@ $xxl-map-height: map-get($container-max-widths, xxl) * (9 / 16);
margin-top: $base-spacing / 4;
}

.c-mode-filter__filter-btn-group {
display: inline;

&:last-of-type {
margin-left: $base-spacing * .5;
}
}

.m-tnm-sidebar__via {
font-weight: $font-weight-medium;
line-height: 1;
Expand Down
80 changes: 80 additions & 0 deletions apps/site/assets/css/project-page.scss
Original file line number Diff line number Diff line change
Expand Up @@ -347,3 +347,83 @@
content: $fa-var-check-circle;
}
}

.filter-and-search {
background-color: $gray-lightest;
padding: $base-spacing $base-spacing * .9375;

@include media-breakpoint-up(md) {
padding-left: 0;
padding-right: 0;
}

.m-tnm-sidebar__filter-btn {
@include media-breakpoint-only(xs) {
width: 48%;
}

@include media-breakpoint-only(sm) {
width: 124px;
}

@include media-breakpoint-up(md) {
width: 130px;
}
}

.c-mode-filter__filter-btn-group {
@include media-breakpoint-only(xs) {
display: block;
}

@include media-breakpoint-up(sm) {
display: inline;
}

&:last-of-type {
@include media-breakpoint-only(xs) {
margin-left: 0;
margin-top: $base-spacing * .25;
}
}
}

.m-tnm-sidebar__filter-header {
@include media-breakpoint-up(sm) {
display: block;
}
}
}

.filter-and-search__wrapper {
@include media-breakpoint-up(md) {
margin: 0 auto;
}

@include media-breakpoint-only(md) {
max-width: 768px;
}

@include media-breakpoint-only(lg) {
max-width: 960px;
}

@include media-breakpoint-only(xxl) {
max-width: 1200px;
}
}

.filter-and-search__filter-button {
margin-bottom: $base-spacing * .5;
position: relative;
text-align: center;
width: $base-spacing * 9;

@include media-breakpoint-only(xs) {
margin-right: $base-spacing * .25;
}
}

.filter-and-search__filter-buttons {
margin-top: $base-spacing * .25;
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,8 @@ test("dismiss fullscreen error with a key stroke", () => {

expect(containerEl.getAttribute("style")).toEqual("display: none");
});

test("handle case where the element is not available", () => {
document.body.innerHTML = `<div></div>`;
registerDismissFullscreenError();
});
83 changes: 54 additions & 29 deletions apps/site/assets/ts/components/ModeFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const ModeButton = ({
className={`btn btn-secondary btn-sm m-tnm-sidebar__filter-btn ${
isModeSelected(mode) ? "active" : "inactive"
}`}
id={`mode-button__${mode}`}
onClick={onClick(mode)}
type="button"
aria-label={
Expand All @@ -56,8 +57,18 @@ const ModeButton = ({
const shouldShowModeButton = (
mode: string,
modeButtonsToShow?: string[]
): boolean =>
modeButtonsToShow === undefined || modeButtonsToShow.includes(mode);
): boolean => {
if (modeButtonsToShow) {
return modeButtonsToShow.includes(mode);
}

// By default, show all available modes except ferry.

if (mode === "ferry") {
return false;
}
return true;
};

export const ModeFilter = ({
isModeSelected,
Expand All @@ -67,32 +78,46 @@ export const ModeFilter = ({
<div className="m-tnm-sidebar__filter-bar">
<span className="m-tnm-sidebar__filter-header u-small-caps">Filter</span>

{shouldShowModeButton("subway", modeButtonsToShow) && (
<ModeButton
mode="subway"
icon="subway"
name="Subway"
isModeSelected={isModeSelected}
onClick={mode => () => onModeClickAction(mode)}
/>
)}
{shouldShowModeButton("bus", modeButtonsToShow) && (
<ModeButton
mode="bus"
icon="bus"
name="Bus"
isModeSelected={isModeSelected}
onClick={mode => () => onModeClickAction(mode)}
/>
)}
{shouldShowModeButton("commuter_rail", modeButtonsToShow) && (
<ModeButton
mode="commuter_rail"
icon="commuter_rail"
name="Rail"
isModeSelected={isModeSelected}
onClick={mode => () => onModeClickAction(mode)}
/>
)}
<div className="c-mode-filter__filter-btn-group">
{shouldShowModeButton("subway", modeButtonsToShow) && (
<ModeButton
mode="subway"
icon="subway"
name="Subway"
isModeSelected={isModeSelected}
onClick={mode => () => onModeClickAction(mode)}
/>
)}
{shouldShowModeButton("bus", modeButtonsToShow) && (
<ModeButton
mode="bus"
icon="bus"
name="Bus"
isModeSelected={isModeSelected}
onClick={mode => () => onModeClickAction(mode)}
/>
)}
</div>

<div className="c-mode-filter__filter-btn-group">
{shouldShowModeButton("commuter_rail", modeButtonsToShow) && (
<ModeButton
mode="commuter_rail"
icon="commuter_rail"
name="Rail"
isModeSelected={isModeSelected}
onClick={mode => () => onModeClickAction(mode)}
/>
)}
{shouldShowModeButton("ferry", modeButtonsToShow) && (
<ModeButton
mode="ferry"
icon="ferry"
name="Ferry"
isModeSelected={isModeSelected}
onClick={mode => () => onModeClickAction(mode)}
/>
)}
</div>
</div>
);
Original file line number Diff line number Diff line change
Expand Up @@ -9,53 +9,64 @@ exports[`render renders 1`] = `
>
Filter
</span>
<button
aria-label="remove filter by subway"
className="btn btn-secondary btn-sm m-tnm-sidebar__filter-btn active"
onClick={[Function]}
type="button"
<div
className="c-mode-filter__filter-btn-group"
>
<span
className="m-tnm-sidebar__mode-icon"
dangerouslySetInnerHTML={
Object {
"__html": "SVG",
<button
aria-label="remove filter by subway"
className="btn btn-secondary btn-sm m-tnm-sidebar__filter-btn active"
id="mode-button__subway"
onClick={[Function]}
type="button"
>
<span
className="m-tnm-sidebar__mode-icon"
dangerouslySetInnerHTML={
Object {
"__html": "SVG",
}
}
}
/>
Subway
</button>
<button
aria-label="remove filter by bus"
className="btn btn-secondary btn-sm m-tnm-sidebar__filter-btn active"
onClick={[Function]}
type="button"
>
<span
className="m-tnm-sidebar__mode-icon"
dangerouslySetInnerHTML={
Object {
"__html": "SVG",
/>
Subway
</button>
<button
aria-label="remove filter by bus"
className="btn btn-secondary btn-sm m-tnm-sidebar__filter-btn active"
id="mode-button__bus"
onClick={[Function]}
type="button"
>
<span
className="m-tnm-sidebar__mode-icon"
dangerouslySetInnerHTML={
Object {
"__html": "SVG",
}
}
}
/>
Bus
</button>
<button
aria-label="remove filter by commuter_rail"
className="btn btn-secondary btn-sm m-tnm-sidebar__filter-btn active"
onClick={[Function]}
type="button"
/>
Bus
</button>
</div>
<div
className="c-mode-filter__filter-btn-group"
>
<span
className="m-tnm-sidebar__mode-icon"
dangerouslySetInnerHTML={
Object {
"__html": "SVG",
<button
aria-label="remove filter by commuter_rail"
className="btn btn-secondary btn-sm m-tnm-sidebar__filter-btn active"
id="mode-button__commuter_rail"
onClick={[Function]}
type="button"
>
<span
className="m-tnm-sidebar__mode-icon"
dangerouslySetInnerHTML={
Object {
"__html": "SVG",
}
}
}
/>
Rail
</button>
/>
Rail
</button>
</div>
</div>
`;
29 changes: 20 additions & 9 deletions apps/site/assets/ts/leaflet/components/Map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ const Component = ({
const getBounds = require("../bounds").default;
/* eslint-enable */
const { Map, Marker, Polyline, Popup, TileLayer } = leaflet;
/* istanbul ignore next */
const boundsOrByMarkers = bounds || (boundsByMarkers && getBounds(markers));
const position = mapCenter(markers, defaultCenter);
const nonNullZoom = zoom === null ? undefined : zoom;
Expand All @@ -84,18 +85,28 @@ const Component = ({
attribution='&amp;copy <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
url={`${tileServerUrl}/osm_tiles/{z}/{x}/{y}.png`}
/>
{polylines.map(polyline => (
<Polyline
key={polyline.id || `polyline-${Math.floor(Math.random() * 1000)}`}
positions={polyline.positions}
color={polyline.color}
weight={polyline.weight}
/>
))}
{polylines.map(
/* istanbul ignore next */
polyline => (
<Polyline
key={
polyline.id || `polyline-${Math.floor(Math.random() * 1000)}`
}
positions={polyline.positions}
color={polyline.color}
weight={polyline.weight}
/>
)
)}
{markers.map(marker => (
<Marker
icon={buildIcon(marker.icon, marker.icon_opts)}
key={marker.id || `marker-${Math.floor(Math.random() * 1000)}`}
key={
marker.id ||
/* istanbul ignore next */ `marker-${Math.floor(
Math.random() * 1000
)}`
}
position={[marker.latitude, marker.longitude]}
ref={ref => ref && rotateMarker(ref.leafletElement, marker)}
zIndexOffset={marker.z_index}
Expand Down
14 changes: 9 additions & 5 deletions apps/site/assets/ts/projects/__tests__/BannerTest.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import React from "react";
import renderer from "react-test-renderer";
import { shallow, mount } from "enzyme";
import {
createReactRoot,
enzymeToJsonWithoutProps
} from "../../app/helpers/testUtils";
import { mount } from "enzyme";
import { createReactRoot } from "../../app/helpers/testUtils";
import { SimpleProject } from "../components/__projects";
import Banner, { cmsRouteToClass } from "../components/Banner";

Expand Down Expand Up @@ -110,6 +107,13 @@ it("renders", () => {
expect(tree).toMatchSnapshot();
});

it("returns null if no banner project", () => {
const wrapper = mount(
<Banner banner={null} placeholderImageUrl={placeholderImageUrl} />
);
expect(wrapper.html()).toEqual(null);
});

const placeholderImageUrl = "path-to-some-image";
it("renders with unknown background color if no routes are listed", () => {
const wrapper = mount(
Expand Down
Loading

0 comments on commit 84dfdf5

Please sign in to comment.