Updated sticky header logic.

This commit is contained in:
Mike Cao 2023-03-22 01:53:34 -07:00
parent 8532c673fe
commit f3e1f18e1b
7 changed files with 41 additions and 76 deletions

View File

@ -1,32 +0,0 @@
import { useMeasure } from 'react-basics';
import classNames from 'classnames';
import useSticky from 'hooks/useSticky';
export default function StickyHeader({
className,
stickyClassName,
stickyStyle,
enabled = true,
scrollElement,
children,
}) {
const { ref: scrollRef, isSticky } = useSticky({ scrollElement });
const { ref: measureRef, dimensions } = useMeasure();
const active = enabled && isSticky;
return (
<div
ref={measureRef}
data-sticky={active}
style={active ? { height: dimensions.height } : null}
>
<div
ref={scrollRef}
className={classNames(className, { [stickyClassName]: active })}
style={active ? { ...stickyStyle, width: dimensions.width } : null}
>
{children}
</div>
</div>
);
}

View File

@ -2,11 +2,11 @@ import { useMemo } from 'react';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import { Button, Icon, Text, Row, Column } from 'react-basics'; import { Button, Icon, Text, Row, Column } from 'react-basics';
import Link from 'next/link'; import Link from 'next/link';
import classNames from 'classnames';
import PageviewsChart from './PageviewsChart'; import PageviewsChart from './PageviewsChart';
import MetricsBar from './MetricsBar'; import MetricsBar from './MetricsBar';
import WebsiteHeader from './WebsiteHeader'; import WebsiteHeader from './WebsiteHeader';
import DateFilter from 'components/input/DateFilter'; import DateFilter from 'components/input/DateFilter';
import StickyHeader from 'components/common/StickyHeader';
import ErrorMessage from 'components/common/ErrorMessage'; import ErrorMessage from 'components/common/ErrorMessage';
import FilterTags from 'components/metrics/FilterTags'; import FilterTags from 'components/metrics/FilterTags';
import RefreshButton from 'components/input/RefreshButton'; import RefreshButton from 'components/input/RefreshButton';
@ -16,9 +16,9 @@ import useTimezone from 'hooks/useTimezone';
import usePageQuery from 'hooks/usePageQuery'; import usePageQuery from 'hooks/usePageQuery';
import { getDateArray, getDateLength } from 'lib/date'; import { getDateArray, getDateLength } from 'lib/date';
import Icons from 'components/icons'; import Icons from 'components/icons';
import { UI_LAYOUT_BODY } from 'lib/constants';
import { labels } from 'components/messages'; import { labels } from 'components/messages';
import styles from './WebsiteChart.module.css'; import styles from './WebsiteChart.module.css';
import useSticky from '../../hooks/useSticky';
export default function WebsiteChart({ export default function WebsiteChart({
websiteId, websiteId,
@ -37,6 +37,7 @@ export default function WebsiteChart({
query: { url, referrer, os, browser, device, country }, query: { url, referrer, os, browser, device, country },
} = usePageQuery(); } = usePageQuery();
const { get, useQuery } = useApi(); const { get, useQuery } = useApi();
const { ref, isSticky } = useSticky({ enabled: stickyHeader });
const { data, isLoading, error } = useQuery( const { data, isLoading, error } = useQuery(
['websites:pageviews', websiteId, modified, url, referrer, os, browser, device, country], ['websites:pageviews', websiteId, modified, url, referrer, os, browser, device, country],
@ -81,12 +82,13 @@ export default function WebsiteChart({
)} )}
</WebsiteHeader> </WebsiteHeader>
<FilterTags websiteId={websiteId} params={{ url, referrer, os, browser, device, country }} /> <FilterTags websiteId={websiteId} params={{ url, referrer, os, browser, device, country }} />
<StickyHeader <Row
stickyClassName={styles.sticky} ref={ref}
enabled={stickyHeader} className={classNames(styles.header, {
scrollElement={document.getElementById(UI_LAYOUT_BODY)} [styles.sticky]: stickyHeader,
[styles.isSticky]: isSticky,
})}
> >
<Row className={styles.header}>
<Column> <Column>
<MetricsBar websiteId={websiteId} /> <MetricsBar websiteId={websiteId} />
</Column> </Column>
@ -95,7 +97,6 @@ export default function WebsiteChart({
<DateFilter websiteId={websiteId} value={value} className={styles.dropdown} /> <DateFilter websiteId={websiteId} value={value} className={styles.dropdown} />
</Column> </Column>
</Row> </Row>
</StickyHeader>
<Row> <Row>
<Column className={styles.chart}> <Column className={styles.chart}>
{error && <ErrorMessage />} {error && <ErrorMessage />}

View File

@ -17,22 +17,23 @@
} }
.header { .header {
position: relative;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 10px 0;
min-height: 90px; min-height: 90px;
margin-bottom: 20px; margin-bottom: 20px;
background: var(--base50);
} }
.sticky { .sticky {
position: fixed; position: sticky;
top: 0; top: -1px;
background: var(--base50); z-index: 2;
}
.isSticky {
border-bottom: 1px solid var(--base300); border-bottom: 1px solid var(--base300);
z-index: 3;
width: inherit;
padding-top: 10px;
} }
.actions { .actions {

View File

@ -6,7 +6,6 @@ import firstBy from 'thenby';
import { GridRow, GridColumn } from 'components/layout/Grid'; import { GridRow, GridColumn } from 'components/layout/Grid';
import Page from 'components/layout/Page'; import Page from 'components/layout/Page';
import RealtimeChart from 'components/metrics/RealtimeChart'; import RealtimeChart from 'components/metrics/RealtimeChart';
import StickyHeader from 'components/common/StickyHeader';
import PageHeader from 'components/layout/PageHeader'; import PageHeader from 'components/layout/PageHeader';
import WorldMap from 'components/common/WorldMap'; import WorldMap from 'components/common/WorldMap';
import RealtimeLog from 'components/pages/realtime/RealtimeLog'; import RealtimeLog from 'components/pages/realtime/RealtimeLog';
@ -104,9 +103,7 @@ export default function RealtimeDashboard({ websiteId }) {
<PageHeader title={formatMessage(labels.realtime)}> <PageHeader title={formatMessage(labels.realtime)}>
<WebsiteSelect websiteId={websiteId} onSelect={handleSelect} /> <WebsiteSelect websiteId={websiteId} onSelect={handleSelect} />
</PageHeader> </PageHeader>
<StickyHeader stickyClassName={styles.sticky}>
<RealtimeHeader websiteId={websiteId} data={currentData} /> <RealtimeHeader websiteId={websiteId} data={currentData} />
</StickyHeader>
<div className={styles.chart}> <div className={styles.chart}>
<RealtimeChart data={realtimeData} unit="minute" records={REALTIME_RANGE} /> <RealtimeChart data={realtimeData} unit="minute" records={REALTIME_RANGE} />
</div> </div>

View File

@ -1,25 +1,23 @@
import { useState, useEffect, useRef } from 'react'; import { useState, useEffect, useRef } from 'react';
export default function useSticky({ scrollElement = document, defaultSticky = false }) { export default function useSticky({ defaultSticky = false, enabled = true }) {
const [isSticky, setIsSticky] = useState(defaultSticky); const [isSticky, setIsSticky] = useState(defaultSticky);
const ref = useRef(null); const ref = useRef(null);
const initialTop = useRef(null);
useEffect(() => { useEffect(() => {
const handleScroll = () => { let observer;
setIsSticky((scrollElement?.scrollTop ?? window.scrollY) > initialTop.current); const handler = ([entry]) => setIsSticky(entry.intersectionRatio < 1);
};
if (initialTop.current === null) { if (enabled && ref.current) {
initialTop.current = ref?.current?.offsetTop; observer = new IntersectionObserver(handler, { threshold: [1] });
observer.observe(ref.current);
} }
scrollElement.addEventListener('scroll', handleScroll, true);
return () => { return () => {
scrollElement.removeEventListener('scroll', handleScroll, true); if (observer) {
observer.disconnect();
}
}; };
}, [ref, setIsSticky, scrollElement]); }, [ref]);
return { ref, isSticky }; return { ref, isSticky };
} }

View File

@ -94,7 +94,7 @@
"node-fetch": "^3.2.8", "node-fetch": "^3.2.8",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"react": "^18.2.0", "react": "^18.2.0",
"react-basics": "^0.73.0", "react-basics": "^0.74.0",
"react-beautiful-dnd": "^13.1.0", "react-beautiful-dnd": "^13.1.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-intl": "^5.24.7", "react-intl": "^5.24.7",

View File

@ -7076,10 +7076,10 @@ rc@^1.2.7:
minimist "^1.2.0" minimist "^1.2.0"
strip-json-comments "~2.0.1" strip-json-comments "~2.0.1"
react-basics@^0.73.0: react-basics@^0.74.0:
version "0.73.0" version "0.74.0"
resolved "https://registry.yarnpkg.com/react-basics/-/react-basics-0.73.0.tgz#9555563f3407ac417dc833dfca47588123d55535" resolved "https://registry.yarnpkg.com/react-basics/-/react-basics-0.74.0.tgz#153433bc485d6b71d8edf377d1a83f1d55133e24"
integrity sha512-eEK8yWWrXO7JATBlPKBfFQlD1hNZoNeEtlYNx+QjOCLKu1qjClutP5nXWHmX4gHE97XFwUKzbTU35NkNEy5C0w== integrity sha512-Z9XwgEOSRvcPqFqFZL6HR59t/XrqhIB8uoYwbmon3IFX2W0kOPqkX1Box0c+2BibJoHp4N4mbfuZWK2kSEnq9g==
dependencies: dependencies:
classnames "^2.3.1" classnames "^2.3.1"
date-fns "^2.29.3" date-fns "^2.29.3"