type check
This commit is contained in:
parent
924c93271b
commit
af97f17a2c
6 changed files with 68 additions and 239 deletions
|
@ -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);
|
||||||
|
|
|
@ -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 />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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" });
|
||||||
|
|
|
@ -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,111 +39,26 @@ 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [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]);
|
|
||||||
|
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
|
// 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(() => {
|
|
||||||
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?
|
// How many rows will this month take up?
|
||||||
const [startWeek, endWeek] = weekBoundsOfMonth(month);
|
const [startWeek, endWeek] = weekBoundsOfMonth(month);
|
||||||
const numWeeks = differenceInWeeks(endWeek, startWeek);
|
const numWeeks = differenceInWeeks(endWeek, startWeek);
|
||||||
|
@ -160,17 +70,31 @@ export default function MonthCalendar() {
|
||||||
dateCellHeight,
|
dateCellHeight,
|
||||||
isActive: isSameMonth(centerMonth, month),
|
isActive: isSameMonth(centerMonth, month),
|
||||||
};
|
};
|
||||||
const ref = undefined;
|
|
||||||
// (month === currentlyShownMonth && middleMonthRef) || undefined;
|
|
||||||
return (
|
return (
|
||||||
<Month
|
<Month key={month.toString()} {...props} onDateClick={onDateClick} />
|
||||||
key={month.toString()}
|
|
||||||
{...props}
|
|
||||||
ref={ref}
|
|
||||||
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>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue