wip
This commit is contained in:
parent
59a7849173
commit
f375c25a48
7 changed files with 366 additions and 229 deletions
0
lib/ScrollContainer.module.scss
Normal file
0
lib/ScrollContainer.module.scss
Normal file
102
lib/ScrollContainer.tsx
Normal file
102
lib/ScrollContainer.tsx
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
import {
|
||||||
|
ForwardedRef,
|
||||||
|
forwardRef,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
|
import styles from "./ScrollContainer.module.scss";
|
||||||
|
import { subMonths } from "date-fns";
|
||||||
|
|
||||||
|
interface ScrollContainerProps<T> {
|
||||||
|
direction: "horizontal" | "vertical";
|
||||||
|
|
||||||
|
/** A list of things managed */
|
||||||
|
list: T[];
|
||||||
|
|
||||||
|
generatePreviousObject: () => void;
|
||||||
|
generateNextObject: () => void;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Generic infinite scrolling container
|
||||||
|
*/
|
||||||
|
function ScrollContainer<T>(
|
||||||
|
{ direction, list }: ScrollContainerProps<T>,
|
||||||
|
ref: ForwardedRef<HTMLDivElement>
|
||||||
|
) {
|
||||||
|
const [resetScrollToEl, setResetScrollToEl] = useState<
|
||||||
|
[HTMLElement, number] | null
|
||||||
|
>(null);
|
||||||
|
|
||||||
|
// Viewport state
|
||||||
|
const [viewportSize, setViewportSize] = useState<[number, number]>([0, 0]);
|
||||||
|
const [viewportWidth, viewportHeight] = viewportSize;
|
||||||
|
const viewportMetric =
|
||||||
|
direction === "horizontal" ? viewportWidth : viewportHeight;
|
||||||
|
|
||||||
|
// Scroll element state
|
||||||
|
const [scrollyEl, setScrollyEl] = useState<HTMLDivElement | null>(null);
|
||||||
|
const scrollyRef = useCallback((node: HTMLDivElement) => {
|
||||||
|
if (node) {
|
||||||
|
setScrollyEl(node);
|
||||||
|
setViewportSize([node.clientWidth, node.clientHeight]);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Add resize observer on the viewport
|
||||||
|
useEffect(() => {
|
||||||
|
const observer = new ResizeObserver((entries) => {
|
||||||
|
if (entries && scrollyEl) {
|
||||||
|
setViewportSize([scrollyEl.clientWidth, scrollyEl.clientHeight]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (scrollyEl) observer.observe(scrollyEl);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (scrollyEl) observer.unobserve(scrollyEl);
|
||||||
|
};
|
||||||
|
}, [scrollyEl]);
|
||||||
|
|
||||||
|
// Add intersection observer to tell when we scrolled too far
|
||||||
|
useEffect(() => {
|
||||||
|
if (!scrollyEl) return;
|
||||||
|
|
||||||
|
const children: HTMLElement[] = [...scrollyEl.children];
|
||||||
|
const firstChild = children[0];
|
||||||
|
const lastChild = children[children.length - 1];
|
||||||
|
|
||||||
|
const observer = new IntersectionObserver((entries) => {
|
||||||
|
if (viewportMetric === 0) return;
|
||||||
|
let newList = [...list];
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (!entry.isIntersecting) continue;
|
||||||
|
|
||||||
|
if (entry.target === firstChild) {
|
||||||
|
console.log("intersected");
|
||||||
|
const firstChildDate = new Date(firstChild.dataset.isodate!);
|
||||||
|
const prevMonth = subMonths(firstChildDate, 1);
|
||||||
|
newList = [prevMonth, ...monthsShown];
|
||||||
|
newList = newMonthsShown.slice(0, NUM_MONTHS_SHOWN);
|
||||||
|
|
||||||
|
setResetScrollToEl([
|
||||||
|
firstChild,
|
||||||
|
scrollyEl.scrollTop - firstChild.offsetTop,
|
||||||
|
]);
|
||||||
|
} else if (entry.target === lastChild) {
|
||||||
|
const lastChildDate = new Date(lastChild.dataset.isodate!);
|
||||||
|
const nextMonth = addMonths(lastChildDate, 1);
|
||||||
|
newMonthsShown = [...monthsShown, nextMonth];
|
||||||
|
newMonthsShown = newMonthsShown.slice(-NUM_MONTHS_SHOWN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setMonthsShown(newMonthsShown);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return <div className={styles.scrollyEl} ref={scrollyRef}></div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default forwardRef(ScrollContainer);
|
|
@ -22,31 +22,3 @@
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.daysOfWeek {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(7, 1fr);
|
|
||||||
|
|
||||||
div {
|
|
||||||
flex-grow: 1;
|
|
||||||
text-align: right;
|
|
||||||
font-weight: 100;
|
|
||||||
border-bottom: 1px solid gray;
|
|
||||||
padding: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.scrollyPart {
|
|
||||||
flex-grow: 1;
|
|
||||||
|
|
||||||
overflow-y: scroll; /* Add the ability to scroll the y axis */
|
|
||||||
|
|
||||||
/* Hide the scrollbar for Internet Explorer, Edge and Firefox */
|
|
||||||
-ms-overflow-style: none; /* Internet Explorer and Edge */
|
|
||||||
scrollbar-width: none; /* Firefox */
|
|
||||||
|
|
||||||
/* Hide the scrollbar for Chrome, Safari and Opera */
|
|
||||||
&::-webkit-scrollbar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,225 +1,75 @@
|
||||||
import { forwardRef, useCallback, useEffect, useRef, useState } from "react";
|
import { Dispatch, SetStateAction, createContext, useState } from "react";
|
||||||
import styles from "./Calendar.module.scss";
|
import styles from "./Calendar.module.scss";
|
||||||
import {
|
|
||||||
addDays,
|
|
||||||
addMonths,
|
|
||||||
differenceInWeeks,
|
|
||||||
isSameMonth,
|
|
||||||
startOfMonth,
|
|
||||||
subMonths,
|
|
||||||
} from "date-fns";
|
|
||||||
import Month from "./Month";
|
|
||||||
import { monthNameOf, weekBoundsOfMonth } from "lib/month";
|
|
||||||
import { useJournals } from "lib/queries";
|
|
||||||
import ToggleSwitch from "src/widgets/ToggleSwitch";
|
import ToggleSwitch from "src/widgets/ToggleSwitch";
|
||||||
import InputBox from "src/widgets/InputBox";
|
import InputBox from "src/widgets/InputBox";
|
||||||
import Button from "src/widgets/Button";
|
import Button from "src/widgets/Button";
|
||||||
|
import MonthCalendar from "./MonthCalendar";
|
||||||
|
|
||||||
const daysOfWeek = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
interface ICalendarContext {
|
||||||
const NUM_MONTHS_SHOWN = 5;
|
onDateClick: (_: Date) => void;
|
||||||
|
setCurrentTitle: Dispatch<SetStateAction<JSX.Element>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CalendarContext = createContext<ICalendarContext>(null!);
|
||||||
|
|
||||||
interface CalendarProps {
|
interface CalendarProps {
|
||||||
onDateClick?: (_: Date) => void;
|
onDateClick: (_: Date) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Calendar({ onDateClick }: CalendarProps) {
|
export default function Calendar({ onDateClick }: CalendarProps) {
|
||||||
const now = new Date();
|
const [currentTitle, setCurrentTitle] = useState<JSX.Element>(<></>);
|
||||||
|
|
||||||
const [viewportHeight, setViewportHeight] = useState(0);
|
const scrollToToday = () => {};
|
||||||
const [scrollyEl, setScrollyEl] = useState<HTMLDivElement | null>(null);
|
|
||||||
const [resetScrollToEl, setResetScrollToEl] = useState<
|
|
||||||
[HTMLElement, number] | null
|
|
||||||
>(null);
|
|
||||||
|
|
||||||
const range = [-2, -1, 0, 1, 2];
|
|
||||||
const [monthsShown, setMonthsShown] = useState(
|
|
||||||
range.map((x) => addMonths(startOfMonth(now), x))
|
|
||||||
);
|
|
||||||
const centerMonth = monthsShown[2];
|
|
||||||
|
|
||||||
const startDate = monthsShown[0];
|
|
||||||
const endDate = monthsShown[NUM_MONTHS_SHOWN - 1];
|
|
||||||
|
|
||||||
const [firstLoad, setFirstLoad] = useState(true);
|
|
||||||
useEffect(() => {
|
|
||||||
if (firstLoad && scrollyEl && viewportHeight > 0) {
|
|
||||||
// Identify the middle month
|
|
||||||
const el = scrollyEl.querySelector(
|
|
||||||
`[data-isodate="${centerMonth.toISOString()}"]`
|
|
||||||
);
|
|
||||||
if (el) {
|
|
||||||
console.log("first run, scrolling into view", el);
|
|
||||||
el.scrollIntoView();
|
|
||||||
|
|
||||||
setFirstLoad(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [firstLoad, scrollyEl, viewportHeight]);
|
|
||||||
|
|
||||||
const scrollyRef = useCallback((node: HTMLDivElement) => {
|
|
||||||
if (node) {
|
|
||||||
setScrollyEl(node);
|
|
||||||
setViewportHeight(node.clientHeight);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Add resize observer on the viewport
|
|
||||||
useEffect(() => {
|
|
||||||
const observer = new ResizeObserver((entries) => {
|
|
||||||
if (entries && scrollyEl) setViewportHeight(scrollyEl.clientHeight);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (scrollyEl) observer.observe(scrollyEl);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
if (scrollyEl) observer.unobserve(scrollyEl);
|
|
||||||
};
|
|
||||||
}, [scrollyEl]);
|
|
||||||
|
|
||||||
// Add intersection observer to tell when we scrolled too far up or down
|
|
||||||
useEffect(() => {
|
|
||||||
if (!scrollyEl) return;
|
|
||||||
|
|
||||||
const children: HTMLElement[] = [...scrollyEl.children];
|
|
||||||
if (children.length !== NUM_MONTHS_SHOWN) throw new Error("wtf");
|
|
||||||
const firstChild = children[0];
|
|
||||||
const lastChild = children[NUM_MONTHS_SHOWN - 1];
|
|
||||||
|
|
||||||
const observer = new IntersectionObserver((entries) => {
|
|
||||||
let newMonthsShown = monthsShown;
|
|
||||||
let scrollUp = false;
|
|
||||||
|
|
||||||
for (const entry of entries) {
|
|
||||||
if (!entry.isIntersecting) continue;
|
|
||||||
|
|
||||||
if (entry.target === firstChild) {
|
|
||||||
console.log("intersected");
|
|
||||||
const firstChildDate = new Date(firstChild.dataset.isodate!);
|
|
||||||
const prevMonth = subMonths(firstChildDate, 1);
|
|
||||||
newMonthsShown = [prevMonth, ...monthsShown];
|
|
||||||
newMonthsShown = newMonthsShown.slice(0, NUM_MONTHS_SHOWN);
|
|
||||||
|
|
||||||
setResetScrollToEl([
|
|
||||||
firstChild,
|
|
||||||
scrollyEl.scrollTop - firstChild.offsetTop,
|
|
||||||
]);
|
|
||||||
} else if (entry.target === lastChild) {
|
|
||||||
const lastChildDate = new Date(lastChild.dataset.isodate!);
|
|
||||||
const nextMonth = addMonths(lastChildDate, 1);
|
|
||||||
newMonthsShown = [...monthsShown, nextMonth];
|
|
||||||
newMonthsShown = newMonthsShown.slice(-NUM_MONTHS_SHOWN);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setMonthsShown(newMonthsShown);
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log("observing", firstChild, lastChild);
|
|
||||||
observer.observe(firstChild);
|
|
||||||
observer.observe(lastChild);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
observer.unobserve(firstChild);
|
|
||||||
observer.unobserve(lastChild);
|
|
||||||
};
|
|
||||||
}, [monthsShown, scrollyEl]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!resetScrollToEl) return;
|
|
||||||
if (!scrollyEl) return;
|
|
||||||
|
|
||||||
console.log("current scroll top", scrollyEl.scrollTop);
|
|
||||||
console.log("params", resetScrollToEl);
|
|
||||||
const [el, height] = resetScrollToEl;
|
|
||||||
scrollyEl.scrollTop = el.offsetTop + height;
|
|
||||||
console.log("new scroll top", scrollyEl.scrollTop, el.offsetTop + height);
|
|
||||||
setResetScrollToEl(null);
|
|
||||||
}, [resetScrollToEl, scrollyEl]);
|
|
||||||
|
|
||||||
console.log("SCROLLY REF IS", scrollyRef);
|
|
||||||
|
|
||||||
// The calendar should always show 6 rows
|
|
||||||
const dateCellHeight = viewportHeight / 6;
|
|
||||||
|
|
||||||
const monthName = monthNameOf(centerMonth);
|
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// middleMonthEl?.scrollIntoView();
|
|
||||||
// }, [middleMonthRef]);
|
|
||||||
|
|
||||||
const [searchQuery, setSearchQuery] = useState("");
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
|
|
||||||
const [currentLayout, setCurrentLayout] = useState("Month");
|
const [currentMode, setCurrentMode] = useState("Month");
|
||||||
const [currentView, setCurrentView] = useState("Calendar");
|
const [currentView, setCurrentView] = useState("Calendar");
|
||||||
|
|
||||||
|
const contextValue = { onDateClick, setCurrentTitle };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.calendar}>
|
<CalendarContext.Provider value={contextValue}>
|
||||||
<div className={styles.header}>
|
<div className={styles.calendar}>
|
||||||
<div className={styles.title}>
|
<div className={styles.header}>
|
||||||
<div>
|
<div className={styles.title}>
|
||||||
<b>{monthName}</b>
|
<div>{currentTitle}</div>
|
||||||
{centerMonth.getFullYear()}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<InputBox
|
<InputBox
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
setValue={setSearchQuery}
|
setValue={setSearchQuery}
|
||||||
placeholder="Search..."
|
placeholder="Search..."
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={styles.controlButtons}>
|
|
||||||
<Button>Today</Button>
|
|
||||||
|
|
||||||
<ToggleSwitch
|
|
||||||
options={["Week", "Month", "Year"]}
|
|
||||||
value={currentLayout}
|
|
||||||
setValue={setCurrentLayout}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ToggleSwitch
|
|
||||||
options={["Calendar", "Graph"]}
|
|
||||||
value={currentView}
|
|
||||||
setValue={setCurrentView}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={styles.daysOfWeek}>
|
|
||||||
{daysOfWeek.map((day) => (
|
|
||||||
<div key={day} className={styles.dayOfWeek}>
|
|
||||||
{day}
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
|
||||||
|
<div className={styles.controlButtons}>
|
||||||
|
<Button>Today</Button>
|
||||||
|
|
||||||
|
<ToggleSwitch
|
||||||
|
options={["Week", "Month", "Year"]}
|
||||||
|
value={currentMode}
|
||||||
|
setValue={setCurrentMode}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ToggleSwitch
|
||||||
|
options={["Calendar", "Graph"]}
|
||||||
|
value={currentView}
|
||||||
|
setValue={setCurrentView}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={styles.scrollyPart} ref={scrollyRef}>
|
<ActualCalendar mode={currentMode} />
|
||||||
{monthsShown.map((month) => {
|
|
||||||
// How many rows will this month take up?
|
|
||||||
const [startWeek, endWeek] = weekBoundsOfMonth(month);
|
|
||||||
const numWeeks = differenceInWeeks(endWeek, startWeek);
|
|
||||||
const dateGridHeight = dateCellHeight * numWeeks;
|
|
||||||
|
|
||||||
const props = {
|
|
||||||
month,
|
|
||||||
dateGridHeight,
|
|
||||||
dateCellHeight,
|
|
||||||
isActive: isSameMonth(centerMonth, month),
|
|
||||||
};
|
|
||||||
const ref = undefined;
|
|
||||||
// (month === currentlyShownMonth && middleMonthRef) || undefined;
|
|
||||||
return (
|
|
||||||
<Month
|
|
||||||
key={month.toString()}
|
|
||||||
{...props}
|
|
||||||
ref={ref}
|
|
||||||
onDateClick={onDateClick}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</CalendarContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ActualCalendar({ mode }) {
|
||||||
|
switch (mode) {
|
||||||
|
default:
|
||||||
|
return <MonthCalendar />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
27
src/calendar/MonthCalendar.module.scss
Normal file
27
src/calendar/MonthCalendar.module.scss
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
.daysOfWeek {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(7, 1fr);
|
||||||
|
|
||||||
|
div {
|
||||||
|
flex-grow: 1;
|
||||||
|
text-align: right;
|
||||||
|
font-weight: 100;
|
||||||
|
border-bottom: 1px solid gray;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrollyPart {
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
|
overflow-y: scroll; /* Add the ability to scroll the y axis */
|
||||||
|
|
||||||
|
/* Hide the scrollbar for Internet Explorer, Edge and Firefox */
|
||||||
|
-ms-overflow-style: none; /* Internet Explorer and Edge */
|
||||||
|
scrollbar-width: none; /* Firefox */
|
||||||
|
|
||||||
|
/* Hide the scrollbar for Chrome, Safari and Opera */
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
178
src/calendar/MonthCalendar.tsx
Normal file
178
src/calendar/MonthCalendar.tsx
Normal file
|
@ -0,0 +1,178 @@
|
||||||
|
import { useCallback, useContext, useEffect, useState } from "react";
|
||||||
|
import styles from "./MonthCalendar.module.scss";
|
||||||
|
import {
|
||||||
|
addMonths,
|
||||||
|
differenceInWeeks,
|
||||||
|
isSameMonth,
|
||||||
|
startOfMonth,
|
||||||
|
subMonths,
|
||||||
|
} from "date-fns";
|
||||||
|
import { monthNameOf, weekBoundsOfMonth } from "lib/month";
|
||||||
|
import Month from "./Month";
|
||||||
|
import { CalendarContext } from "./Calendar";
|
||||||
|
|
||||||
|
const daysOfWeek = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
||||||
|
const NUM_MONTHS_SHOWN = 5;
|
||||||
|
|
||||||
|
export default function MonthCalendar() {
|
||||||
|
const { onDateClick, setCurrentTitle } = useContext(CalendarContext);
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
const [resetScrollToEl, setResetScrollToEl] = useState<
|
||||||
|
[HTMLElement, number] | null
|
||||||
|
>(null);
|
||||||
|
const [viewportHeight, setViewportHeight] = useState(0);
|
||||||
|
const [scrollyEl, setScrollyEl] = useState<HTMLDivElement | null>(null);
|
||||||
|
const scrollyRef = useCallback((node: HTMLDivElement) => {
|
||||||
|
if (node) {
|
||||||
|
setScrollyEl(node);
|
||||||
|
setViewportHeight(node.clientHeight);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const range = [-2, -1, 0, 1, 2];
|
||||||
|
const [monthsShown, setMonthsShown] = useState(
|
||||||
|
range.map((x) => addMonths(startOfMonth(now), x))
|
||||||
|
);
|
||||||
|
const centerMonth = monthsShown[2];
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setCurrentTitle(
|
||||||
|
<>
|
||||||
|
<b>{monthNameOf(centerMonth)}</b>
|
||||||
|
{centerMonth.getFullYear()}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}, [centerMonth]);
|
||||||
|
|
||||||
|
// Scroll to today on first load
|
||||||
|
const [firstLoad, setFirstLoad] = useState(true);
|
||||||
|
useEffect(() => {
|
||||||
|
if (firstLoad && scrollyEl && viewportHeight > 0) {
|
||||||
|
// Identify the middle month
|
||||||
|
const el = scrollyEl.querySelector(
|
||||||
|
`[data-isodate="${centerMonth.toISOString()}"]`
|
||||||
|
);
|
||||||
|
if (el) {
|
||||||
|
console.log("first run, scrolling into view", el);
|
||||||
|
el.scrollIntoView();
|
||||||
|
|
||||||
|
setFirstLoad(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [firstLoad, scrollyEl, viewportHeight]);
|
||||||
|
|
||||||
|
// Add resize observer on the viewport
|
||||||
|
useEffect(() => {
|
||||||
|
const observer = new ResizeObserver((entries) => {
|
||||||
|
if (entries && scrollyEl) setViewportHeight(scrollyEl.clientHeight);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (scrollyEl) observer.observe(scrollyEl);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (scrollyEl) observer.unobserve(scrollyEl);
|
||||||
|
};
|
||||||
|
}, [scrollyEl]);
|
||||||
|
|
||||||
|
// The calendar should always show 6 rows
|
||||||
|
const dateCellHeight = viewportHeight / 6;
|
||||||
|
|
||||||
|
// Add intersection observer to tell when we scrolled too far up or down
|
||||||
|
useEffect(() => {
|
||||||
|
if (!scrollyEl) return;
|
||||||
|
|
||||||
|
const children: HTMLElement[] = [...scrollyEl.children];
|
||||||
|
if (children.length !== NUM_MONTHS_SHOWN) throw new Error("wtf");
|
||||||
|
const firstChild = children[0];
|
||||||
|
const lastChild = children[NUM_MONTHS_SHOWN - 1];
|
||||||
|
|
||||||
|
const observer = new IntersectionObserver((entries) => {
|
||||||
|
if (viewportHeight === 0) return;
|
||||||
|
let newMonthsShown = monthsShown;
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (!entry.isIntersecting) continue;
|
||||||
|
|
||||||
|
if (entry.target === firstChild) {
|
||||||
|
console.log("intersected");
|
||||||
|
const firstChildDate = new Date(firstChild.dataset.isodate!);
|
||||||
|
const prevMonth = subMonths(firstChildDate, 1);
|
||||||
|
newMonthsShown = [prevMonth, ...monthsShown];
|
||||||
|
newMonthsShown = newMonthsShown.slice(0, NUM_MONTHS_SHOWN);
|
||||||
|
|
||||||
|
setResetScrollToEl([
|
||||||
|
firstChild,
|
||||||
|
scrollyEl.scrollTop - firstChild.offsetTop,
|
||||||
|
]);
|
||||||
|
} else if (entry.target === lastChild) {
|
||||||
|
const lastChildDate = new Date(lastChild.dataset.isodate!);
|
||||||
|
const nextMonth = addMonths(lastChildDate, 1);
|
||||||
|
newMonthsShown = [...monthsShown, nextMonth];
|
||||||
|
newMonthsShown = newMonthsShown.slice(-NUM_MONTHS_SHOWN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setMonthsShown(newMonthsShown);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("observing", firstChild, lastChild);
|
||||||
|
observer.observe(firstChild);
|
||||||
|
observer.observe(lastChild);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
observer.unobserve(firstChild);
|
||||||
|
observer.unobserve(lastChild);
|
||||||
|
};
|
||||||
|
}, [monthsShown, scrollyEl]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!resetScrollToEl) return;
|
||||||
|
if (!scrollyEl) return;
|
||||||
|
|
||||||
|
console.log("current scroll top", scrollyEl.scrollTop);
|
||||||
|
console.log("params", resetScrollToEl);
|
||||||
|
const [el, height] = resetScrollToEl;
|
||||||
|
scrollyEl.scrollTop = el.offsetTop + height;
|
||||||
|
console.log("new scroll top", scrollyEl.scrollTop, el.offsetTop + height);
|
||||||
|
setResetScrollToEl(null);
|
||||||
|
}, [resetScrollToEl, scrollyEl]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={styles.daysOfWeek}>
|
||||||
|
{daysOfWeek.map((day) => (
|
||||||
|
<div key={day} className={styles.dayOfWeek}>
|
||||||
|
{day}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.scrollyPart} ref={scrollyRef}>
|
||||||
|
{monthsShown.map((month) => {
|
||||||
|
// How many rows will this month take up?
|
||||||
|
const [startWeek, endWeek] = weekBoundsOfMonth(month);
|
||||||
|
const numWeeks = differenceInWeeks(endWeek, startWeek);
|
||||||
|
const dateGridHeight = dateCellHeight * numWeeks;
|
||||||
|
|
||||||
|
const props = {
|
||||||
|
month,
|
||||||
|
dateGridHeight,
|
||||||
|
dateCellHeight,
|
||||||
|
isActive: isSameMonth(centerMonth, month),
|
||||||
|
};
|
||||||
|
const ref = undefined;
|
||||||
|
// (month === currentlyShownMonth && middleMonthRef) || undefined;
|
||||||
|
return (
|
||||||
|
<Month
|
||||||
|
key={month.toString()}
|
||||||
|
{...props}
|
||||||
|
ref={ref}
|
||||||
|
onDateClick={onDateClick}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
8
src/calendar/WeekCalendar.tsx
Normal file
8
src/calendar/WeekCalendar.tsx
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import { useContext } from "react";
|
||||||
|
import { CalendarContext } from "./Calendar";
|
||||||
|
|
||||||
|
export default function WeekCalendar() {
|
||||||
|
const { onDateClick, setCurrentTitle } = useContext(CalendarContext);
|
||||||
|
|
||||||
|
return <></>;
|
||||||
|
}
|
Loading…
Reference in a new issue