diff --git a/app/javascript/layouts/Layout.tsx b/app/javascript/layouts/Layout.tsx index ff312ff27b..11c6aab29c 100644 --- a/app/javascript/layouts/Layout.tsx +++ b/app/javascript/layouts/Layout.tsx @@ -183,11 +183,7 @@ const Layout = (props: LayoutProps) => { mainContentId={"main-content"} /> -
+
{props.children}
diff --git a/app/javascript/modules/listings/DirectoryPageNavigationBar.scss b/app/javascript/modules/listings/DirectoryPageNavigationBar.scss new file mode 100644 index 0000000000..50518c0320 --- /dev/null +++ b/app/javascript/modules/listings/DirectoryPageNavigationBar.scss @@ -0,0 +1,54 @@ +.directory-page-navigation-bar { + position: sticky; + top: 0; + z-index: 1; + background-color: #fff; + margin: 0 auto; + width: 100%; + max-width: var(--bloom-width-5xl); + + --inter-row-gap: var(--bloom-s12); + --max-width: var(--bloom-width-3xl); + --max-width-large-screen: var(--bloom-width-5xl); + --margin: var(--inter-row-gap) auto; + --seeds-focus-ring-box-shadow: none !important; + --seeds-focus-ring-outline: none !important; + + display: flex; + flex-direction: row; + max-width: var(--max-width); + overflow-x: auto; + overflow-y: hidden; + margin: var(--margin); + + a:focus { + box-shadow: none !important; + } + + @media (min-width: $screen-lg) { + --max-width: var(--max-width-large-screen); + } + + .seeds-button { + flex: 1 0 0; + min-width: max-content; + margin-left: 16px; + padding: var(--sizes-size-s4, 16px) var(--sizes-size-s6, 24px); + + background: var(--components-tabs-background-color, #fff); + color: var(--components-tabs-label-color, #767676); + border: var(--borders-width-1, 1px) solid var(--components-tabs-border-color, #dedee0); + border-radius: 0px; + } + .seeds-button:hover, + .active { + color: var(--components-tabs-active-label-color, #222); + background: var(--components-tabs-background-color); + + border-top: var(--borders-width-1, 1px) solid var(--components-tabs-border-color, #dedee0); + border-right: var(--borders-width-1, 1px) solid var(--components-tabs-border-color, #dedee0); + border-left: var(--borders-width-1, 1px) solid var(--components-tabs-border-color, #dedee0); + border-bottom: var(--components-tabs-active-indicator-width, 2px) solid + var(--components-tabs-active-indicator-color, #0077da); + } +} diff --git a/app/javascript/modules/listings/DirectoryPageNavigationBar.tsx b/app/javascript/modules/listings/DirectoryPageNavigationBar.tsx new file mode 100644 index 0000000000..a136b3ee97 --- /dev/null +++ b/app/javascript/modules/listings/DirectoryPageNavigationBar.tsx @@ -0,0 +1,64 @@ +import React from "react" +import "./DirectoryPageNavigationBar.scss" +import { Button } from "@bloom-housing/ui-seeds" +import { Icon } from "@bloom-housing/ui-components" + +const DirectoryPageNavigationBar = ({ + directoryType, + listingLengths, + activeItem, + setActiveItem, +}: { + directoryType: string + listingLengths: { open: number; upcoming: number; fcfs: number; results: number } + activeItem: string + setActiveItem: React.Dispatch +}) => { + return ( +
+ + {directoryType === "forSale" && ( + + )} + + +
+ ) +} +export default DirectoryPageNavigationBar diff --git a/app/javascript/modules/listings/GenericDirectory.scss b/app/javascript/modules/listings/GenericDirectory.scss new file mode 100644 index 0000000000..436cc7434d --- /dev/null +++ b/app/javascript/modules/listings/GenericDirectory.scss @@ -0,0 +1,3 @@ +.site-content { + scroll-behavior: smooth; +} diff --git a/app/javascript/modules/listings/GenericDirectory.tsx b/app/javascript/modules/listings/GenericDirectory.tsx index ffa8cef8a4..67b36e9a14 100644 --- a/app/javascript/modules/listings/GenericDirectory.tsx +++ b/app/javascript/modules/listings/GenericDirectory.tsx @@ -1,4 +1,4 @@ -import React, { Dispatch, ReactNode, SetStateAction, useEffect, useState } from "react" +import React, { Dispatch, ReactNode, SetStateAction, useEffect, useRef, useState } from "react" import { LoadingOverlay, StackedTableRow } from "@bloom-housing/ui-components" import type RailsRentalListing from "../../api/types/rails/listings/RailsRentalListing" @@ -16,6 +16,7 @@ import { import { RailsListing } from "./SharedHelpers" import "./ListingDirectory.scss" import { MailingListSignup } from "../../components/MailingListSignup" +import DirectoryPageNavigationBar from "./DirectoryPageNavigationBar" interface RentalDirectoryProps { listingsAPI: (filters?: EligibilityFilters) => Promise @@ -44,6 +45,7 @@ export const GenericDirectory = (props: RentalDirectoryProps) => { // Whether any listings are a match. const [match, setMatch] = useState(false) const [filters, setFilters] = useState(props.filters ?? null) + const [activeItem, setActiveItem] = useState(null) useEffect(() => { void props.listingsAPI(props.filters).then((listings) => { @@ -62,6 +64,32 @@ export const GenericDirectory = (props: RentalDirectoryProps) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [filters]) + const observerRef = useRef(null) + useEffect(() => { + const handleIntersectionEvent = (element) => { + let newActiveItem = activeItem + let prevY = null + for (const e of element) { + if (e.isIntersecting) { + if (!prevY) { + console.log("first if") + prevY = e.boundingClientRect.y + newActiveItem = e.target.id + } + + if (e.boundingClientRect.y < prevY) { + console.log("second if") + newActiveItem = e.target.id + } + } + } + + setActiveItem(newActiveItem) + } + + observerRef.current = new IntersectionObserver(handleIntersectionEvent) + }, [activeItem]) + const hasFiltersSet = filters !== null return ( @@ -69,20 +97,50 @@ export const GenericDirectory = (props: RentalDirectoryProps) => { {!loading && ( <> {props.getPageHeader(filters, setFilters, match)} +
- {openListingsView( - listings.open, - props.directoryType, - props.getSummaryTable, - hasFiltersSet - )} - {props.directoryType === "forSale" && - fcfsSalesView( - [...listings.fcfsSalesOpen, ...listings.fcfsSalesNotYetOpen], +
{ + if (el) { + observerRef?.current?.observe(el) + } + }} + > + {openListingsView( + listings.open, props.directoryType, props.getSummaryTable, hasFiltersSet )} +
+ {props.directoryType === "forSale" && ( +
{ + if (el) { + observerRef?.current?.observe(el) + } + }} + > + {fcfsSalesView( + [...listings.fcfsSalesOpen, ...listings.fcfsSalesNotYetOpen], + props.directoryType, + props.getSummaryTable, + hasFiltersSet + )} +
+ )} {props.findMoreActionBlock} {filters && additionalView( @@ -91,8 +149,30 @@ export const GenericDirectory = (props: RentalDirectoryProps) => { props.getSummaryTable, hasFiltersSet )} - {upcomingLotteriesView(listings.upcoming, props.directoryType, props.getSummaryTable)} - {lotteryResultsView(listings.results, props.directoryType, props.getSummaryTable)} +
{ + if (el) { + observerRef?.current?.observe(el) + } + }} + > + {upcomingLotteriesView( + listings.upcoming, + props.directoryType, + props.getSummaryTable + )} +
+
{ + if (el) { + observerRef?.current?.observe(el) + } + }} + > + {lotteryResultsView(listings.results, props.directoryType, props.getSummaryTable)} +
)}