Skip to content

Commit

Permalink
[Agency Dashboard] Responsive layout for data viz page (#169)
Browse files Browse the repository at this point in the history
* new desktop layout for controls

* fix resizing chart

* height resizing for data viz

* responsive dashboard for tablet size

* mobile layout

* new mobile buttons

* scroll fixes + implement back buttons

* tweak padding and add dummy modal

* fix resizing not working if starting from tablet or mobile

* PR

* fix lint

* fix lint in common
  • Loading branch information
terryttsai committed Nov 21, 2022
1 parent add1126 commit a145288
Show file tree
Hide file tree
Showing 10 changed files with 413 additions and 131 deletions.
119 changes: 64 additions & 55 deletions agency-dashboard/src/DashboardView.styles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@
// =============================================================================

import {
COMMON_DESKTOP_WIDTH,
palette,
TABLET_WIDTH,
typography,
} from "@justice-counts/common/components/GlobalStyles";
import { Dropdown } from "@recidiviz/design-system";
import styled from "styled-components/macro";

export const Container = styled.div`
Expand All @@ -33,31 +34,39 @@ export const Container = styled.div`
`;

export const LeftPanel = styled.div`
margin-top: 16px;
margin-left: 24px;
margin-right: 24px;
width: 424px;
min-width: 424px;
margin-right: 126px;
@media only screen and (max-width: ${COMMON_DESKTOP_WIDTH - 1}px) {
display: none;
}
`;

export const RightPanel = styled.div`
display: flex;
flex-grow: 1;
flex-direction: column;
align-items: stretch;
`;
width: calc(100% - 574px);
height: 100%;
padding-left: 24px;
padding-right: 24px;
export const RightPanelTopContainer = styled.div`
display: flex;
@media only screen and (max-width: ${TABLET_WIDTH - 1}px) {
padding-left: 16px;
padding-right: 16px;
}
`;

export const LeftPanelBackButtonContainer = styled.div`
export const BackButtonContainer = styled.div`
${typography.sizeCSS.normal}
float: left;
padding-top: 8px;
padding-right: 8px;
padding-bottom: 8px;
margin-top: 16px;
display: flex;
align-items: center;
gap: 8px;
Expand All @@ -76,28 +85,66 @@ export const LeftPanelBackButtonContainer = styled.div`
}
`;

export const RightPanelBackButtonContainer = styled(BackButtonContainer)`
@media only screen and (min-width: ${COMMON_DESKTOP_WIDTH}px) {
display: none;
}
`;

export const MetricTitle = styled.div`
${typography.sizeCSS.headline}
margin-top: 86px;
margin-top: 36px;
margin-bottom: 16px;
hyphens: auto;
overflow-wrap: break-word;
`;

export const MetricOverviewTitle = styled.div`
${typography.sizeCSS.large}
margin-top: 48px;
export const RightPanelMetricTitle = styled(MetricTitle)`
margin-bottom: 40px;
&::after {
content: "Metric Overview";
@media only screen and (min-width: ${COMMON_DESKTOP_WIDTH}px) {
display: none;
}
@media only screen and (max-width: ${TABLET_WIDTH - 1}px) {
${typography.sizeCSS.title}
margin-bottom: 16px;
}
`;

export const MetricOverviewContent = styled.div`
${typography.sizeCSS.medium}
@media only screen and (max-width: ${TABLET_WIDTH - 1}px) {
${typography.sizeCSS.normal}
}
`;

export const RightPanelMetricOverviewContent = styled(MetricOverviewContent)`
margin-top: 16px;
@media only screen and (min-width: ${COMMON_DESKTOP_WIDTH}px) {
display: none;
}
`;

export const MetricOverviewActionsContainer = styled.div`
display: flex;
margin-top: 24px;
padding-bottom: 32px;
`;

export const RightPanelMetricOverviewActionsContainer = styled(
MetricOverviewActionsContainer
)`
@media only screen and (min-width: ${COMMON_DESKTOP_WIDTH}px) {
display: none;
}
@media only screen and (max-width: ${TABLET_WIDTH - 1}px) {
flex-direction: column;
padding-bottom: 64px;
}
`;

export const MetricOverviewActionButtonContainer = styled.div`
Expand All @@ -117,51 +164,13 @@ export const MetricOverviewActionButtonContainer = styled.div`
&:first-child {
padding-left: 0px;
}
`;

export const MetricOverviewActionButtonText = styled.div`
${typography.sizeCSS.normal}
margin-left: 8px;
`;

export const AllMetricsButtonContainer = styled.div`
padding-left: 16px;
padding-right: 16px;
padding-top: 8px;
padding-bottom: 8px;
display: flex;
align-items: center;
border-radius: 2px;
background: ${palette.solid.blue};
gap: 8px;
color: ${palette.solid.white};
&:hover {
cursor: pointer;
background: ${palette.solid.darkblue};
@media only screen and (max-width: ${TABLET_WIDTH - 1}px) {
padding-left: 0px;
}
`;

export const AllMetricsButtonText = styled.div`
export const MetricOverviewActionButtonText = styled.div`
${typography.sizeCSS.normal}
font-weight: 400;
&::after {
content: "Select Metric";
}
`;

export const ExtendedDropdown = styled(Dropdown)`
& > button {
margin-top: 8px;
margin-bottom: 8px;
margin-left: 15px;
transition-duration: 0ms;
background: none;
padding: 0;
border: none;
}
&:hover > button {
background: none;
}
margin-left: 8px;
`;
121 changes: 66 additions & 55 deletions agency-dashboard/src/DashboardView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,40 +16,48 @@
// =============================================================================

import { ReactComponent as DownloadIcon } from "@justice-counts/common/assets/download-icon.svg";
import { ReactComponent as GridIcon } from "@justice-counts/common/assets/grid-icon.svg";
import { ReactComponent as InfoIcon } from "@justice-counts/common/assets/info-icon.svg";
import { ReactComponent as LeftArrowIcon } from "@justice-counts/common/assets/left-arrow-icon.svg";
import { ReactComponent as ShareIcon } from "@justice-counts/common/assets/share-icon.svg";
import { DatapointsView } from "@justice-counts/common/components/DataViz/DatapointsView";
import { ExtendedDropdownMenuItem } from "@justice-counts/common/components/DataViz/DatapointsView.styles";
import { DropdownMenu, DropdownToggle } from "@recidiviz/design-system";
import { COMMON_DESKTOP_WIDTH } from "@justice-counts/common/components/GlobalStyles";
import { observer } from "mobx-react-lite";
import React, { useEffect } from "react";
import React, { useEffect, useState } from "react";
import { useLocation, useNavigate, useParams } from "react-router-dom";

import {
AllMetricsButtonContainer as SelectMetricButtonContainer,
AllMetricsButtonText,
BackButtonContainer,
Container,
ExtendedDropdown,
LeftPanel,
LeftPanelBackButtonContainer,
MetricOverviewActionButtonContainer,
MetricOverviewActionButtonText,
MetricOverviewActionsContainer,
MetricOverviewContent,
MetricOverviewTitle,
MetricTitle,
RightPanel,
RightPanelTopContainer,
RightPanelBackButtonContainer,
RightPanelMetricOverviewActionsContainer,
RightPanelMetricOverviewContent,
RightPanelMetricTitle,
} from "./DashboardView.styles";
import { HeaderBar } from "./Header/HeaderBar";
import { useStore } from "./stores";

const LeftPanelBackButton = () => (
<LeftPanelBackButtonContainer>
const getScreenWidth = () =>
window.innerWidth ||
document.documentElement.clientWidth ||
document.body.clientWidth;

const BackButton = ({ onClick }: { onClick: () => void }) => (
<BackButtonContainer onClick={onClick}>
<LeftArrowIcon />
</BackButtonContainer>
);

const RightPanelBackButton = ({ onClick }: { onClick: () => void }) => (
<RightPanelBackButtonContainer onClick={onClick}>
<LeftArrowIcon />
</LeftPanelBackButtonContainer>
</RightPanelBackButtonContainer>
);

const MetricOverviewActionInfoButton = () => (
Expand All @@ -75,37 +83,9 @@ const MetricOverviewActionShareButton = () => (
</MetricOverviewActionButtonContainer>
);

const SelectMetricButton = () => (
<SelectMetricButtonContainer>
<GridIcon />
<AllMetricsButtonText />
</SelectMetricButtonContainer>
);

const SelectMetricButtonDropdown: React.FC<{
onSelect: (metricKey: string) => void;
options: string[];
}> = ({ onSelect, options }) => (
<ExtendedDropdown>
<DropdownToggle>
<SelectMetricButton />
</DropdownToggle>
<DropdownMenu>
{options.map((value) => (
<ExtendedDropdownMenuItem
key={value}
onClick={() => {
onSelect(value);
}}
>
{value}
</ExtendedDropdownMenuItem>
))}
</DropdownMenu>
</ExtendedDropdown>
);

const DashboardView = () => {
const [shouldResizeChartHeight, setShouldResizeChartHeight] =
useState<boolean>(getScreenWidth() >= COMMON_DESKTOP_WIDTH);
const navigate = useNavigate();
const params = useParams();
const agencyId = Number(params.id);
Expand All @@ -131,6 +111,28 @@ const DashboardView = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [datapointsStore.loading]);

useEffect(() => {
const resizeListener = () => {
// change width from the state object
if (shouldResizeChartHeight && getScreenWidth() < COMMON_DESKTOP_WIDTH) {
setShouldResizeChartHeight(false);
} else if (
!shouldResizeChartHeight &&
getScreenWidth() >= COMMON_DESKTOP_WIDTH
) {
setShouldResizeChartHeight(true);
}
};
// set resize listener
window.addEventListener("resize", resizeListener);

// clean up function
return () => {
// remove resize listener
window.removeEventListener("resize", resizeListener);
};
}, [shouldResizeChartHeight]);

if (
!metricKey ||
(!datapointsStore.loading &&
Expand All @@ -151,38 +153,47 @@ const DashboardView = () => {
<Container key={metricKey}>
<HeaderBar />
<LeftPanel>
<LeftPanelBackButton />
<BackButton onClick={() => navigate(`/agency/${agencyId}`)} />
<MetricTitle>
{datapointsStore.metricKeyToDisplayName[metricKey] || metricKey}
</MetricTitle>
<MetricOverviewTitle />
<MetricOverviewContent>
Measures the number of individuals with at least one parole violation
during the reporting period.
</MetricOverviewContent>
<MetricOverviewActionsContainer>
<MetricOverviewActionInfoButton />
<MetricOverviewActionDownloadButton />
<MetricOverviewActionShareButton />
<MetricOverviewActionDownloadButton />
<MetricOverviewActionInfoButton />
</MetricOverviewActionsContainer>
</LeftPanel>
<RightPanel>
<RightPanelTopContainer>
<SelectMetricButtonDropdown
onSelect={(metric) =>
navigate(`/agency/${agencyId}/dashboard?metric=${metric}`)
}
options={metricNames}
/>
</RightPanelTopContainer>
<RightPanelBackButton onClick={() => navigate(`/agency/${agencyId}`)} />
<RightPanelMetricTitle>
{datapointsStore.metricKeyToDisplayName[metricKey] || metricKey}
</RightPanelMetricTitle>
<DatapointsView
datapointsGroupedByAggregateAndDisaggregations={
datapointsStore.datapointsByMetric[metricKey]
}
dimensionNamesByDisaggregation={
datapointsStore.dimensionNamesByMetricAndDisaggregation[metricKey]
}
metricNames={metricNames}
onMetricsSelect={(metric) =>
navigate(`/agency/${agencyId}/dashboard?metric=${metric}`)
}
resizeHeight={shouldResizeChartHeight}
/>
<RightPanelMetricOverviewContent>
Measures the number of individuals with at least one parole violation
during the reporting period.
</RightPanelMetricOverviewContent>
<RightPanelMetricOverviewActionsContainer>
<MetricOverviewActionShareButton />
<MetricOverviewActionDownloadButton />
<MetricOverviewActionInfoButton />
</RightPanelMetricOverviewActionsContainer>
</RightPanel>
</Container>
);
Expand Down
5 changes: 5 additions & 0 deletions agency-dashboard/src/Header/HeaderBar.styles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import {
HEADER_BAR_HEIGHT,
palette,
TABLET_WIDTH,
typography,
} from "@justice-counts/common/components/GlobalStyles";
import styled from "styled-components/macro";
Expand Down Expand Up @@ -59,6 +60,10 @@ export const Logo = styled.img`
export const HeaderTitle = styled.div`
flex-grow: 1;
padding-left: 16px;
@media only screen and (max-width: ${TABLET_WIDTH - 1}px) {
display: none;
}
`;

export const HeaderButtonsContainer = styled.div`
Expand Down
Loading

0 comments on commit a145288

Please sign in to comment.