@hackoregon/component-library
Version:
Official repo for Hack Oregon React component library
295 lines (267 loc) • 6.6 kB
JavaScript
/* eslint-disable no-nested-ternary */
import React, { useState } from "react";
import {
arrayOf,
bool,
func,
node,
number,
oneOfType,
shape,
string
} from "prop-types";
import { css } from "emotion";
import shortid from "shortid";
import PieChart from "../PieChart/PieChart";
import HorizontalBarChart from "../HorizontalBarChart/HorizontalBarChart";
import civicFormat from "../utils/civicFormat";
import { ICONS } from "../styleConstants";
const container = css`
position: absolute;
bottom: 50px;
left: 25px;
display: flex;
flex-direction: column;
width: 500px;
background: rgba(243, 242, 243, 0.9);
color: rgb(85, 85, 85);
border: 1px solid #ddd;
border-radius: 2px;
box-shadow: 5px 5px 15px -3px rgba(0, 0, 0, 0.2);
@media (max-width: 900px) {
width: 92%;
left: 1%;
}
`;
const dashboardOpen = css`
height: 400px;
overflow-y: auto;
overflow-x: hidden;
opacity: 0.9;
transition: height 750ms ease-out, opacity 1.5s ease-in;
`;
const dashboardClosed = css`
height: 0;
opacity: 0;
transition: height 750ms ease-out, opacity 0s linear;
`;
const contentContainer = css`
position: relative;
display: flex;
flex-direction: column;
overflow-y: auto;
overflow-x: hidden;
`;
const toggleContainer = css`
position: relative;
display: flex;
flex-direction: row;
height: 100%;
width: 100%;
color: #dc4556;
z-index: 4;
opacity: 0.9;
border: 1px solid #ddd;
border-radius: 2px;
box-shadow: 5px 5px 15px -3px rgba(0, 0, 0, 0.2);
`;
const toggleTitle = css`
flex: 3;
margin auto 20px;
`;
const toggleArrow = css`
flex: 1;
margin 1% auto;
font-size: 20px;
text-align: center;
`;
const watermarkContainer = css`
position: absolute;
left: -1px;
top: -1px;
`;
const buttonContainer = css`
position: absolute;
bottom: 40px;
display: flex;
flex-direction: row;
width: 100%;
`;
const icon = css`
text-align: center;
border: 1px solid lightGray;
background-color: #f0f0f0;
color: dimGray;
font-size: 20px;
height: 98%;
width: 50%;
margin: 0 auto;
`;
const iconActive = css`
text-align: center;
border: 1px solid lightGray;
background-color: #f0f0f0;
color: #dc4556;
font-size: 20px;
height: 98%;
width: 50%;
margin: 0 auto;
`;
const viz = css`
width: 90%;
margin: 2% 2% 2% 8%;
padding-bottom: 25px;
overflow-y: hidden;
`;
const donutPercent = css`
position: absolute;
bottom: 35px;
left: 28%;
width: 50%;
margin: auto;
text-align: center;
`;
const createTextViz = text => (
<div className={viz} key={shortid.generate()}>
<h2>{text.title}</h2>
<h3>{text.data.toLocaleString()}</h3>
</div>
);
const createDonutViz = donut => {
const [percentValue] = donut.data;
const salmon = "#EE495C";
const gray = "#a9a9a9";
return (
<div className={viz} key={shortid.generate()}>
<h2>{donut.title}</h2>
<h2 className={donutPercent}>
{percentValue.y < 1
? civicFormat.percentage(percentValue.y)
: `${percentValue.y.toFixed(1)}%`}
</h2>
<PieChart
data={donut.data}
colors={[salmon, gray]}
width={400}
height={400}
innerRadius={110}
halfDoughnut
/>
</div>
);
};
const createBarsViz = bars => (
<div className={viz} key={shortid.generate()}>
<h2>{bars.title}</h2>
<HorizontalBarChart
minimalist={bars.minimalist}
data={bars.data}
sortOrder={bars.sortOrder}
dataValue={bars.dataValue}
dataLabel={bars.dataLabel}
dataKeyLabel=""
title=""
subtitle=""
xLabel=""
yLabel=""
/>
</div>
);
const placeholder = (
<div className={viz}>
<h2>Please select a polygon</h2>
</div>
);
const CivicDashboard = props => {
const { data, children, isDashboardOpen, onClick } = props;
const [display, setDisplay] = useState("visualizations");
const createVisualizations = data.map((object, index) => {
const vizType = object.visualizationType;
return vizType === "Text"
? createTextViz(object, index)
: vizType === "PercentDonut"
? createDonutViz(object, index)
: vizType === "ComparisonBar"
? createBarsViz(object, index)
: null;
});
const [hasVisualizations] = createVisualizations;
const visualizations = hasVisualizations ? createVisualizations : placeholder;
const civicWatermark = (
<div className={watermarkContainer}>
<svg width="134" height="135" xmlns="http://www.w3.org/2000/svg">
<g fill="none" fillRule="evenodd">
<path d="M0 134.658V0l11.566 11.597v123.061H0z" fill="#191119" />
<path
d="M133.864 0v11.597H11.566v.008L0 .008V0h133.864z"
fill="#DC4556"
/>
</g>
</svg>
</div>
);
const visualizationButtons = (
<div className={buttonContainer}>
<div
className={display === "description" ? iconActive : icon}
onClick={() => setDisplay("description")}
onKeyPress={() => setDisplay("description")}
tabIndex={0}
role="button"
>
<div className={ICONS.info} />
</div>
<div
className={display === "visualizations" ? iconActive : icon}
onClick={() => setDisplay("visualizations")}
onKeyPress={() => setDisplay("visualizations")}
tabIndex={0}
role="button"
>
<div className={ICONS.eye} />
</div>
</div>
);
const dashboardToggleButton = (
<div
className={toggleContainer}
onClick={() => onClick()}
onKeyPress={() => onClick()}
tabIndex={0}
role="button"
>
<div className={toggleTitle}>
{isDashboardOpen ? "" : "Please select a polygon"}
</div>
<div className={toggleArrow}>
<div className={isDashboardOpen ? ICONS.arrowDown : ICONS.arrowUp} />
</div>
</div>
);
return (
<div className={container}>
<div className={isDashboardOpen ? dashboardOpen : dashboardClosed}>
<div className={contentContainer}>
{display === "description" ? children : visualizations}
</div>
{civicWatermark}
{children ? visualizationButtons : null}
</div>
{dashboardToggleButton}
</div>
);
};
CivicDashboard.propTypes = {
data: arrayOf(
shape({
data: oneOfType([arrayOf(shape({})), number, string]),
id: oneOfType([number, string]),
title: string,
visualizationType: string
})
),
children: node,
isDashboardOpen: bool,
onClick: func
};
export default CivicDashboard;