diff --git a/spotlight-client/package.json b/spotlight-client/package.json index 0bb59211..79bcd7dd 100644 --- a/spotlight-client/package.json +++ b/spotlight-client/package.json @@ -16,6 +16,7 @@ }, "dependencies": { "@auth0/auth0-spa-js": "^1.13.1", + "@reach/auto-id": "^0.15.0", "@reach/router": "^1.3.4", "@recidiviz/case-triage-components": "^0.2.2", "@types/body-scroll-lock": "^2.6.1", diff --git a/spotlight-client/src/SiteNavigation/SiteNavigationMobile.tsx b/spotlight-client/src/SiteNavigation/SiteNavigationMobile.tsx index aa43424a..ea06acb7 100644 --- a/spotlight-client/src/SiteNavigation/SiteNavigationMobile.tsx +++ b/spotlight-client/src/SiteNavigation/SiteNavigationMobile.tsx @@ -15,9 +15,10 @@ // along with this program. If not, see . // ============================================================================= +import { disableBodyScroll, enableBodyScroll } from "body-scroll-lock"; import { observer } from "mobx-react-lite"; import { rem } from "polished"; -import React from "react"; +import React, { useRef } from "react"; import useCollapse from "react-collapsed"; import { animated, useSpring } from "react-spring/web.cjs"; import styled from "styled-components/macro"; @@ -65,9 +66,11 @@ const ExternalNavLink = styled(ExternalNavLinkBase)` const NavMenu = styled.ul` font-family: ${typefaces.display}; font-size: ${rem(20)}; + height: calc(100vh - ${rem(NAV_BAR_HEIGHT)}); letter-spacing: -0.015em; line-height: 1.3; margin-left: ${rem(16)}; + overflow: auto; `; const NavMenuItem = styled.li` @@ -83,12 +86,21 @@ const NavMenuItem = styled.li` const SiteNavigation: React.FC = () => { const { tenant } = useDataStore(); + const menuScrollRef = useRef(null); + const { getCollapseProps, getToggleProps, isExpanded, setExpanded, - } = useCollapse({}); + } = useCollapse({ + onCollapseStart: () => { + if (menuScrollRef.current) enableBodyScroll(menuScrollRef.current); + }, + onExpandStart: () => { + if (menuScrollRef.current) disableBodyScroll(menuScrollRef.current); + }, + }); const animatedStyles = useSpring({ from: { background: colors.background }, @@ -121,61 +133,63 @@ const SiteNavigation: React.FC = () => { - - {tenant && ( - <> - - setExpanded(false)} - to={getUrlForResource({ - page: "tenant", - params: { tenantId: tenant.id }, - })} - > - Home - - - {Object.values(tenant.systemNarratives).map( - (narrative) => - narrative && ( - - setExpanded(false)} - to={getUrlForResource({ - page: "narrative", - params: { - tenantId: tenant.id, - narrativeTypeId: narrative.id, - }, - })} - > - {narrative.title} - - - ) - )} - {tenant.racialDisparitiesNarrative && ( +
+ + {tenant && ( + <> setExpanded(false)} to={getUrlForResource({ - page: "narrative", - params: { - tenantId: tenant.id, - narrativeTypeId: "RacialDisparities", - }, + page: "tenant", + params: { tenantId: tenant.id }, })} > - {tenant.racialDisparitiesNarrative.title} + Home - )} - - Feedback - - - )} - + {Object.values(tenant.systemNarratives).map( + (narrative) => + narrative && ( + + setExpanded(false)} + to={getUrlForResource({ + page: "narrative", + params: { + tenantId: tenant.id, + narrativeTypeId: narrative.id, + }, + })} + > + {narrative.title} + + + ) + )} + {tenant.racialDisparitiesNarrative && ( + + setExpanded(false)} + to={getUrlForResource({ + page: "narrative", + params: { + tenantId: tenant.id, + narrativeTypeId: "RacialDisparities", + }, + })} + > + {tenant.racialDisparitiesNarrative.title} + + + )} + + Feedback + + + )} + +
); }; diff --git a/spotlight-client/src/SystemNarrativePage/SystemNarrativePage.tsx b/spotlight-client/src/SystemNarrativePage/SystemNarrativePage.tsx index ed5f517a..0a76d785 100644 --- a/spotlight-client/src/SystemNarrativePage/SystemNarrativePage.tsx +++ b/spotlight-client/src/SystemNarrativePage/SystemNarrativePage.tsx @@ -19,10 +19,8 @@ import HTMLReactParser from "html-react-parser"; import React from "react"; import SystemNarrative from "../contentModels/SystemNarrative"; import { - Chevron, NarrativeIntroContainer, NarrativeIntroCopy, - NarrativeScrollIndicator, NarrativeSectionBody, NarrativeSectionTitle, NarrativeTitle, @@ -44,11 +42,6 @@ const SystemNarrativePage: React.FC<{ {HTMLReactParser(narrative.introduction)} - - SCROLL - - - ), }, diff --git a/spotlight-client/src/UiLibrary/narrative.ts b/spotlight-client/src/UiLibrary/narrative.ts index daf65b8f..88e4775d 100644 --- a/spotlight-client/src/UiLibrary/narrative.ts +++ b/spotlight-client/src/UiLibrary/narrative.ts @@ -18,7 +18,6 @@ import { rem } from "polished"; import styled from "styled-components/macro"; import breakpoints from "./breakpoints"; -import colors from "./colors"; import CopyBlock from "./CopyBlock"; import { FullScreenSection } from "./PageSection"; import PageTitle from "./PageTitle"; @@ -52,25 +51,6 @@ export const NarrativeIntroCopy = styled(CopyBlock)` } `; -export const NarrativeScrollIndicator = styled.div` - align-items: center; - color: ${colors.caption}; - display: flex; - flex-direction: column; - font-size: ${rem(12)}; - font-weight: 500; - letter-spacing: 0.05em; - margin-top: ${rem(32)}; - - @media screen and (min-width: ${breakpoints.tablet[0]}px) { - margin-top: ${rem(144)}; - } - - span { - margin-bottom: ${rem(16)}; - } -`; - export const NarrativeSectionTitle = styled.h2` font-family: ${typefaces.display}; font-size: ${rem(24)}; diff --git a/spotlight-client/src/VizControls/VizControls.tsx b/spotlight-client/src/VizControls/VizControls.tsx index 07d188ea..b1e02fe1 100644 --- a/spotlight-client/src/VizControls/VizControls.tsx +++ b/spotlight-client/src/VizControls/VizControls.tsx @@ -35,15 +35,11 @@ const ControlsGroup = styled.div` display: flex; flex-wrap: wrap; margin-bottom: ${rem(16)}; - padding-bottom: ${rem(16)}; `; const FilterWrapper = styled.div` - margin: 0 ${rem(16)}; - - &:first-child { - margin-left: 0; - } + margin-bottom: ${rem(16)}; + margin-right: ${rem(32)}; &:last-child { margin-right: 0; @@ -57,6 +53,7 @@ const Button = styled.button` cursor: pointer; font-size: ${rem(14)}; font-weight: 500; + margin-bottom: ${rem(16)}; padding: none; `; diff --git a/spotlight-client/src/VizRecidivismRateSingleFollowup/VizRecidivismRateSingleFollowup.tsx b/spotlight-client/src/VizRecidivismRateSingleFollowup/VizRecidivismRateSingleFollowup.tsx index 573f6985..1946f0ee 100644 --- a/spotlight-client/src/VizRecidivismRateSingleFollowup/VizRecidivismRateSingleFollowup.tsx +++ b/spotlight-client/src/VizRecidivismRateSingleFollowup/VizRecidivismRateSingleFollowup.tsx @@ -16,7 +16,7 @@ // ============================================================================= import { observer } from "mobx-react-lite"; -import React from "react"; +import React, { useState } from "react"; import Measure from "react-measure"; import { animated, useSpring, useTransition } from "react-spring/web.cjs"; import styled from "styled-components/macro"; @@ -70,7 +70,12 @@ type VizRecidivismRateSingleFollowupProps = { const VizRecidivismRateSingleFollowup: React.FC = ({ metric, }) => { - const { singleFollowupDemographics, demographicView, unknowns } = metric; + const { + demographicView, + followUpYears, + singleFollowupDemographics, + unknowns, + } = metric; const [chartContainerStyles, setChartContainerStyles] = useSpring(() => ({ from: { height: singleChartHeight }, @@ -84,6 +89,14 @@ const VizRecidivismRateSingleFollowup: React.FC(); + // this is NOT a sophisticated heuristic, but the bar labels + // (which are years) tend to get cramped around this size + const useAngledLabels = + typeof containerWidth === "number" && + containerWidth < 400 && + followUpYears === 1; + if (demographicView === "nofilter") throw new Error( "Unable to display this metric without demographic filter." @@ -94,7 +107,10 @@ const VizRecidivismRateSingleFollowup: React.FC { - if (bounds) setChartContainerStyles({ height: bounds.height }); + if (bounds) { + setChartContainerStyles({ height: bounds.height }); + setContainerWidth(bounds.width); + } }} > {({ measureRef }) => ( @@ -115,6 +131,7 @@ const VizRecidivismRateSingleFollowup: React.FC. // ============================================================================= +import { useId } from "@reach/auto-id"; import { scaleLinear } from "d3-scale"; import { rgba } from "polished"; import React, { useState } from "react"; @@ -26,7 +27,7 @@ import { $Keys } from "utility-types"; import ResponsiveTooltipController from "../charts/ResponsiveTooltipController"; import { formatAsNumber } from "../utils"; import MeasureWidth from "../MeasureWidth"; -import { animation, breakpoints, colors, typefaces } from "../UiLibrary"; +import { breakpoints, colors, typefaces } from "../UiLibrary"; export const CHART_BOTTOM_PADDING = 80; export const CHART_HEIGHT = 500; @@ -119,9 +120,9 @@ const targetColor = rgba(baseColor, 0.5); const hoverColor = rgba(baseColor, 0.2); -const Gradients = ( +const Gradients = ({ idPrefix }: { idPrefix: string | undefined }) => ( <> - + - + - + @@ -221,6 +222,12 @@ export default function SingleStepSankey({ ); }; + // some browsers (most notably iOS Safari) fail to render these gradients + // if this component appears more than once on the page, because the ids + // are then non-unique in the context of the HTML document. Thus the prefix, + // which should be unique per component instance. + const gradientIdPrefix = useId(); + return ( {({ measureRef, width }) => ( @@ -232,10 +239,12 @@ export default function SingleStepSankey({ setHighlighted={setHighlighted} > } baseMarkProps={{ transitionDuration: { - fill: animation.defaultDuration, + // transitions don't work well with our gradient fills; + // less janky to just disable them + fill: 0, }, }} edges={edges} @@ -243,7 +252,7 @@ export default function SingleStepSankey({ return { fill: shouldFade(d) ? hoverColor - : `url(#${d.source.id.toLowerCase()}Gradient)`, + : `url(#${gradientIdPrefix}${d.source.id.toLowerCase()}Gradient)`, }; }} margin={{ ...MARGIN, right: rightMargin }} diff --git a/spotlight-client/src/charts/BarChartTrellis.tsx b/spotlight-client/src/charts/BarChartTrellis.tsx index 6b55cae1..51fa3bf0 100644 --- a/spotlight-client/src/charts/BarChartTrellis.tsx +++ b/spotlight-client/src/charts/BarChartTrellis.tsx @@ -30,7 +30,7 @@ import MeasureWidth from "../MeasureWidth"; export const singleChartHeight = 300; -const MARGIN = { top: 56, bottom: 64, left: 48, right: 0 }; +const MARGIN = { top: 56, bottom: 80, left: 48, right: 0 }; const ChartTitle = styled.text` font-weight: 500; @@ -52,6 +52,7 @@ type BarChartData = { }; type BarChartTrellisProps = { + angledLabels?: boolean; barAxisLabel?: string; data: BarChartData[]; formatBarLabel?: (label: string) => string; @@ -63,6 +64,7 @@ type BarChartTrellisProps = { * with identical Y axis ranges and cross-highlighting. */ export function BarChartTrellis({ + angledLabels, barAxisLabel, data, formatBarLabel = (label) => label, @@ -123,7 +125,13 @@ export function BarChartTrellis({ oAccessor="label" // @ts-expect-error Semiotic types can't handle a styled component here but it's fine oLabel={(barLabel) => ( - + {formatBarLabel(barLabel as string)} )} diff --git a/yarn.lock b/yarn.lock index 05d40414..78a1fed0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1637,6 +1637,14 @@ "@reach/utils" "0.11.2" tslib "^2.0.0" +"@reach/auto-id@^0.15.0": + version "0.15.0" + resolved "https://registry.yarnpkg.com/@reach/auto-id/-/auto-id-0.15.0.tgz#f46afebfc140b2099b95c7aec1f049d167d3833d" + integrity sha512-iACaCcZeqAhDf4OOwJpmHHS/LaRj9z3Ip8JmlhpCrFWV2YOIiiZk42amlBZX6CKH66Md+eriYZQk3TyAjk6Oxg== + dependencies: + "@reach/utils" "0.15.0" + tslib "^2.1.0" + "@reach/descendants@0.10.5": version "0.10.5" resolved "https://registry.yarnpkg.com/@reach/descendants/-/descendants-0.10.5.tgz#2611174e9e9b326dba548356221e2f8c8f5c8612" @@ -1719,6 +1727,14 @@ tslib "^2.0.0" warning "^4.0.3" +"@reach/utils@0.15.0": + version "0.15.0" + resolved "https://registry.yarnpkg.com/@reach/utils/-/utils-0.15.0.tgz#5b183d668f9bb900b2dec7a33c028a2a828d27b2" + integrity sha512-JHHN7T5ucFiuQbqkgv8ECbRWKfRiJxrO/xHR3fHf+f2C7mVs/KkJHhYtovS1iEapR4silygX9PY0+QUmHPOTYw== + dependencies: + tiny-warning "^1.0.3" + tslib "^2.1.0" + "@react-hook/change@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@react-hook/change/-/change-1.0.0.tgz#4604bc93d616fcc0311f528464edd7cb24083289" @@ -15156,6 +15172,11 @@ tslib@^2.0.0, tslib@^2.0.3: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.3.tgz#8e0741ac45fc0c226e58a17bfc3e64b9bc6ca61c" integrity sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ== +tslib@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.2.0.tgz#fb2c475977e35e241311ede2693cee1ec6698f5c" + integrity sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w== + tsutils@^3.17.1: version "3.17.1" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759"