type check

This commit is contained in:
Michael Zhang 2023-08-24 13:40:56 -04:00
parent 924c93271b
commit af97f17a2c
6 changed files with 68 additions and 239 deletions

View file

@ -1,4 +1,11 @@
import { Fragment, useCallback, useContext, useEffect, useState } from "react"; import {
Fragment,
Key,
useCallback,
useContext,
useEffect,
useState,
} from "react";
import styles from "./ScrollContainer.module.scss"; import styles from "./ScrollContainer.module.scss";
import classNames from "classnames"; import classNames from "classnames";
import { getWeek } from "date-fns"; import { getWeek } from "date-fns";
@ -87,8 +94,8 @@ function ScrollContainer<T>({
// Identify the middle month // Identify the middle month
const el = scrollyEl.children[middleIdx]; const el = scrollyEl.children[middleIdx];
if (el) { if (el) {
const date = new Date(el.dataset.isodate!); // const date = new Date(el.dataset.isodate!);
console.log("first run, scrolling into view", date.getMonth()); // console.log("first run, scrolling into view", date.getMonth());
el.scrollIntoView(); el.scrollIntoView();
setFirstLoad(false); setFirstLoad(false);
@ -111,8 +118,8 @@ function ScrollContainer<T>({
// entry.intersectionRect.width * entry.intersectionRect.height; // entry.intersectionRect.width * entry.intersectionRect.height;
// if (intersectionArea === 0) continue; // if (intersectionArea === 0) continue;
const date = new Date(entry.target.dataset.isodate!); // const date = new Date(entry.target.dataset.isodate!);
console.log("intersected with", weekString(date), entry); // console.log("intersected with", weekString(date), entry);
if (entry.target === firstChild) { if (entry.target === firstChild) {
const newResetScrollToEl = { const newResetScrollToEl = {
@ -140,9 +147,9 @@ function ScrollContainer<T>({
} }
}); });
const firstDate = new Date(firstChild.dataset.isodate!); // const firstDate = new Date(firstChild.dataset.isodate!);
const lastDate = new Date(lastChild.dataset.isodate!); // const lastDate = new Date(lastChild.dataset.isodate!);
console.log("observing", weekString(firstDate), weekString(lastDate)); // console.log("observing", weekString(firstDate), weekString(lastDate));
observer.observe(firstChild); observer.observe(firstChild);
observer.observe(lastChild); observer.observe(lastChild);

View file

@ -6,7 +6,6 @@ import Button from "src/widgets/Button";
import MonthCalendar from "./MonthCalendar"; import MonthCalendar from "./MonthCalendar";
import WeekCalendar from "./WeekCalendar"; import WeekCalendar from "./WeekCalendar";
import { usePersistentState } from "lib/persistentState"; import { usePersistentState } from "lib/persistentState";
import MonthCalendar2 from "./MonthCalendar2";
export enum CalendarMode { export enum CalendarMode {
Week = "Week", Week = "Week",
@ -109,10 +108,10 @@ interface ActualCalendarProps {
function ActualCalendar({ mode }: ActualCalendarProps) { function ActualCalendar({ mode }: ActualCalendarProps) {
switch (mode) { switch (mode) {
case CalendarMode.Month: case CalendarMode.Month:
return <MonthCalendar2 />; return <MonthCalendar />;
case CalendarMode.Week: case CalendarMode.Week:
return <WeekCalendar />; return <WeekCalendar />;
case CalendarMode.Year: case CalendarMode.Year:
return <MonthCalendar2 />; return <MonthCalendar />;
} }
} }

View file

@ -1,4 +1,4 @@
import { forwardRef } from "react"; import { ForwardedRef, forwardRef } from "react";
import styles from "./Month.module.scss"; import styles from "./Month.module.scss";
import { import {
addDays, addDays,
@ -23,7 +23,7 @@ interface MonthProps {
function Month( function Month(
{ month, dateGridHeight, isActive, dateCellHeight, onDateClick }: MonthProps, { month, dateGridHeight, isActive, dateCellHeight, onDateClick }: MonthProps,
ref ref: ForwardedRef<HTMLDivElement>
) { ) {
const now = new Date(); const now = new Date();
const monthName = month.toLocaleString("default", { month: "long" }); const monthName = month.toLocaleString("default", { month: "long" });

View file

@ -1,4 +1,5 @@
import { useCallback, useContext, useEffect, useState } from "react"; import { useContext, useEffect, useState } from "react";
import { CalendarContext } from "./Calendar";
import styles from "./MonthCalendar.module.scss"; import styles from "./MonthCalendar.module.scss";
import { import {
addMonths, addMonths,
@ -9,31 +10,25 @@ import {
} from "date-fns"; } from "date-fns";
import { daysOfWeek, monthNameOf, weekBoundsOfMonth } from "lib/date"; import { daysOfWeek, monthNameOf, weekBoundsOfMonth } from "lib/date";
import Month from "./Month"; import Month from "./Month";
import { CalendarContext } from "./Calendar"; import ScrollContainer from "lib/ScrollContainer";
const NUM_MONTHS_SHOWN = 5; const NUM_MONTHS_SHOWN = 5;
export default function MonthCalendar() { export default function MonthCalendar() {
const { onDateClick, setCurrentTitle } = useContext(CalendarContext);
const now = new Date(); const now = new Date();
const { onDateClick, setCurrentTitle, viewportHeight } = useContext(
const [resetScrollToEl, setResetScrollToEl] = useState< CalendarContext
[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 range = [-2, -1, 0, 1, 2];
const [monthsShown, setMonthsShown] = useState( const [monthsShown, setMonthsShown] = useState(
range.map((x) => addMonths(startOfMonth(now), x)) range.map((x) => addMonths(startOfMonth(now), x))
); );
const centerMonth = monthsShown[2]; const centerMonth = monthsShown[2];
console.log(
monthNameOf(centerMonth),
monthsShown.map((x) => monthNameOf(x))
);
useEffect(() => { useEffect(() => {
setCurrentTitle( setCurrentTitle(
@ -44,98 +39,41 @@ export default function MonthCalendar() {
); );
}, [centerMonth]); }, [centerMonth]);
// Scroll to today on first load const generatePreviousObject = (el: HTMLElement) => {
const [firstLoad, setFirstLoad] = useState(true); const date = new Date(el.dataset.isodate!);
useEffect(() => { const prevMonth = subMonths(date, 1);
if (firstLoad && scrollyEl && viewportHeight > 0) { const newWeeksShown = [prevMonth, ...monthsShown];
// Identify the middle month setMonthsShown(newWeeksShown.slice(0, NUM_MONTHS_SHOWN));
const el = scrollyEl.querySelector( };
`[data-isodate="${centerMonth.toISOString()}"]`
);
if (el) {
console.log("first run, scrolling into view", el);
el.scrollIntoView(false);
setFirstLoad(false); const generateNextObject = (el: HTMLElement) => {
} const date = new Date(el.dataset.isodate!);
} const nextMonth = addMonths(date, 1);
}, [firstLoad, scrollyEl, viewportHeight]); const newWeeksShown = [...monthsShown, nextMonth];
setMonthsShown(newWeeksShown.slice(-NUM_MONTHS_SHOWN));
};
// Add resize observer on the viewport const keyOf = (month: Date) => month.toISOString();
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 // The calendar should always show 6 rows
const dateCellHeight = viewportHeight / 6; const dateCellHeight = viewportHeight / 6;
// Add intersection observer to tell when we scrolled too far up or down const renderItem = (month: Date) => {
useEffect(() => { // How many rows will this month take up?
if (!scrollyEl) return; const [startWeek, endWeek] = weekBoundsOfMonth(month);
const numWeeks = differenceInWeeks(endWeek, startWeek);
const dateGridHeight = dateCellHeight * numWeeks;
const children: HTMLElement[] = [...scrollyEl.children]; const props = {
if (children.length !== NUM_MONTHS_SHOWN) throw new Error("wtf"); month,
const firstChild = children[0]; dateGridHeight,
const lastChild = children[NUM_MONTHS_SHOWN - 1]; dateCellHeight,
isActive: isSameMonth(centerMonth, month),
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]); return (
<Month key={month.toString()} {...props} onDateClick={onDateClick} />
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 ( return (
<> <>
@ -147,30 +85,16 @@ export default function MonthCalendar() {
))} ))}
</div> </div>
<div className={styles.scrollyPart} ref={scrollyRef}> <div className={styles.monthContainer}>
{monthsShown.map((month) => { <ScrollContainer
// How many rows will this month take up? className={styles.weekScroll}
const [startWeek, endWeek] = weekBoundsOfMonth(month); direction="vertical"
const numWeeks = differenceInWeeks(endWeek, startWeek); list={monthsShown}
const dateGridHeight = dateCellHeight * numWeeks; renderItem={renderItem}
keyOf={keyOf}
const props = { generateNextObject={generateNextObject}
month, generatePreviousObject={generatePreviousObject}
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>
</> </>
); );

View file

@ -1,101 +0,0 @@
import { useContext, useEffect, useState } from "react";
import { CalendarContext } from "./Calendar";
import styles from "./MonthCalendar.module.scss";
import {
addMonths,
differenceInWeeks,
isSameMonth,
startOfMonth,
subMonths,
} from "date-fns";
import { daysOfWeek, monthNameOf, weekBoundsOfMonth } from "lib/date";
import Month from "./Month";
import ScrollContainer from "lib/ScrollContainer";
const NUM_MONTHS_SHOWN = 5;
export default function MonthCalendar2() {
const now = new Date();
const { onDateClick, setCurrentTitle, viewportHeight } = useContext(
CalendarContext
);
const range = [-2, -1, 0, 1, 2];
const [monthsShown, setMonthsShown] = useState(
range.map((x) => addMonths(startOfMonth(now), x))
);
const centerMonth = monthsShown[2];
console.log(
monthNameOf(centerMonth),
monthsShown.map((x) => monthNameOf(x))
);
useEffect(() => {
setCurrentTitle(
<>
<b>{monthNameOf(centerMonth)}</b>
{centerMonth.getFullYear()}
</>
);
}, [centerMonth]);
const generatePreviousObject = (el: HTMLElement) => {
const date = new Date(el.dataset.isodate!);
const prevMonth = subMonths(date, 1);
const newWeeksShown = [prevMonth, ...monthsShown];
setMonthsShown(newWeeksShown.slice(0, NUM_MONTHS_SHOWN));
};
const generateNextObject = (el: HTMLElement) => {
const date = new Date(el.dataset.isodate!);
const nextMonth = addMonths(date, 1);
const newWeeksShown = [...monthsShown, nextMonth];
setMonthsShown(newWeeksShown.slice(-NUM_MONTHS_SHOWN));
};
const keyOf = (month: Date) => month.toISOString();
// The calendar should always show 6 rows
const dateCellHeight = viewportHeight / 6;
const renderItem = (month: Date) => {
// 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),
};
return (
<Month key={month.toString()} {...props} onDateClick={onDateClick} />
);
};
return (
<>
<div className={styles.daysOfWeek}>
{daysOfWeek.map((day) => (
<div key={day} className={styles.dayOfWeek}>
{day}
</div>
))}
</div>
<div className={styles.monthContainer}>
<ScrollContainer
className={styles.weekScroll}
direction="vertical"
list={monthsShown}
renderItem={renderItem}
keyOf={keyOf}
generateNextObject={generateNextObject}
generatePreviousObject={generatePreviousObject}
/>
</div>
</>
);
}

View file

@ -1,11 +1,11 @@
import { HTMLProps } from "react"; import { ChangeEvent, HTMLProps } from "react";
interface InputBoxProps extends HTMLProps<HTMLInputElement> { interface InputBoxProps extends HTMLProps<HTMLInputElement> {
setValue: (_: string) => any; setValue: (_: string) => any;
} }
export default function InputBox({ setValue, ...props }: InputBoxProps) { export default function InputBox({ setValue, ...props }: InputBoxProps) {
const newOnChange = (evt) => { const newOnChange = (evt: ChangeEvent<HTMLInputElement>) => {
setValue(evt.target.value); setValue(evt.target.value);
}; };