adwaita-web
Version:
A GTK inspired toolkit designed to build awesome web apps
281 lines (280 loc) • 9.52 kB
JavaScript
import cx from "clsx";
import { addDays, addMonths, endOfMonth, format, startOfMonth } from "date-fns";
import React, { useState } from "react";
import { GoNext, GoPrevious } from "../icons";
import { Box } from "./Box";
import { Button } from "./Button";
import { Input } from "./Input";
import { InputGroup } from "./InputGroup";
import { Label } from "./Label";
const weekDayLetters = ["S", "M", "T", "W", "T", "F", "S"];
const months = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[9, 10, 11]
];
const yearButtons = Array(3).fill(0).map((_, n) => n);
var MODE = /* @__PURE__ */ ((MODE2) => {
MODE2["DAY"] = "day";
MODE2["MONTH"] = "month";
MODE2["YEAR"] = "year";
return MODE2;
})(MODE || {});
class Calendar extends React.Component {
state = {
mode: "day" /* DAY */,
value: new Date(),
current: startOfMonth(new Date())
};
setMode(mode) {
this.setState({ mode });
}
setCurrent(current) {
this.setState({ current });
}
setMonth = (month) => {
const current = new Date(this.state.current);
current.setMonth(month);
this.setCurrent(current);
this.setMode("day" /* DAY */);
};
setYear = (year) => {
this.changeYear(year);
this.setMode("day" /* DAY */);
};
changeYear = (year) => {
const current = new Date(this.state.current);
current.setFullYear(year);
this.setCurrent(current);
};
onPrevious = () => {
this.setCurrent(addMonths(this.state.current, -1));
};
onNext = () => {
this.setCurrent(addMonths(this.state.current, 1));
};
select = (item) => {
const value = toDate(item);
if (this.props.onChange)
this.props.onChange(value);
else
this.setState({ value });
};
getValue() {
return this.props.value ?? this.state.value;
}
renderDays() {
const today = new Date();
const value = this.getValue();
const current = this.state.current;
const weeks = getWeeks(current);
return /* @__PURE__ */ React.createElement("table", null, /* @__PURE__ */ React.createElement("tbody", null, /* @__PURE__ */ React.createElement("tr", {
className: "Calendar__weekDays"
}, weekDayLetters.map((letter, j) => /* @__PURE__ */ React.createElement("td", {
key: j,
className: "Calendar__day"
}, letter))), weeks.map((week, i) => /* @__PURE__ */ React.createElement("tr", {
key: i,
className: "Calendar__week"
}, week.map((item, j) => /* @__PURE__ */ React.createElement("td", {
key: j,
className: cx("Calendar__day", `day-${j}`, {
selected: isEqual(item, value),
today: isEqual(item, today),
"other-month": !isEqualMonth(item, current)
})
}, item !== void 0 && /* @__PURE__ */ React.createElement(Button, {
flat: true,
circular: true,
onClick: () => this.select(item)
}, item.date)))))));
}
renderMonths() {
return /* @__PURE__ */ React.createElement("table", null, /* @__PURE__ */ React.createElement("tbody", null, months.map((row, i) => /* @__PURE__ */ React.createElement("tr", {
key: i
}, row.map((month) => /* @__PURE__ */ React.createElement("td", {
key: month,
className: "Calendar__month"
}, /* @__PURE__ */ React.createElement(Button, {
flat: true,
onClick: () => this.setMonth(month)
}, format(new Date(2e3, month, 15), "MMMM"))))))));
}
renderYears() {
const { current } = this.state;
const year = current.getFullYear();
return /* @__PURE__ */ React.createElement(Box, {
fill: true,
align: true,
justify: true
}, /* @__PURE__ */ React.createElement(YearPicker, {
value: year,
onChange: this.changeYear,
onAccept: this.setYear
}));
}
render() {
const { current, mode } = this.state;
return /* @__PURE__ */ React.createElement(Box, {
className: cx("Calendar", `mode-${mode}`),
vertical: true,
compact: true
}, /* @__PURE__ */ React.createElement(Box, {
className: "Calendar__header",
horizontal: true,
compact: true
}, /* @__PURE__ */ React.createElement(Button, {
flat: true,
icon: GoPrevious,
onClick: this.onPrevious,
className: "Calendar__previous"
}), /* @__PURE__ */ React.createElement(Button, {
flat: true,
className: "Calendar__monthLabel",
onClick: () => this.setMode(mode !== "month" /* MONTH */ ? "month" /* MONTH */ : "day" /* DAY */)
}, mode !== "month" /* MONTH */ ? format(current, "MMMM") : /* @__PURE__ */ React.createElement(Label, {
info: true
}, "Go Back")), /* @__PURE__ */ React.createElement(Button, {
flat: true,
className: "Calendar__yearLabel",
onClick: () => this.setMode(mode !== "year" /* YEAR */ ? "year" /* YEAR */ : "day" /* DAY */)
}, mode !== "year" /* YEAR */ ? format(current, "yyyy") : /* @__PURE__ */ React.createElement(Label, {
info: true
}, "Go Back")), /* @__PURE__ */ React.createElement(Button, {
flat: true,
icon: GoNext,
onClick: this.onNext,
className: "Calendar__next"
})), /* @__PURE__ */ React.createElement("div", {
className: "Calendar__grid"
}, mode === "day" /* DAY */ ? this.renderDays() : mode === "month" /* MONTH */ ? this.renderMonths() : mode === "year" /* YEAR */ ? this.renderYears() : null));
}
}
function YearPicker({ value, onChange, onAccept }) {
const [ticks, setTicks] = useState(0);
const onWheel = (ev) => {
ev.preventDefault();
const direction = ev.deltaY < 0 ? -1 : ev.deltaY > 0 ? 1 : 0;
const newTicks = ticks + direction;
if (Math.abs(newTicks) > 5) {
onChange(value + direction);
setTicks(0);
} else {
setTicks(newTicks);
}
};
const onSubmit = (ev) => {
ev.preventDefault();
onAccept(value);
};
return /* @__PURE__ */ React.createElement("div", {
className: "YearPicker",
onWheel
}, yearButtons.map((diff, i) => /* @__PURE__ */ React.createElement("div", {
key: value - (yearButtons.length - diff),
className: "YearPicker__year"
}, /* @__PURE__ */ React.createElement(Button, {
flat: true,
onClick: () => onAccept(value - (yearButtons.length - diff))
}, value - (yearButtons.length - diff)))), /* @__PURE__ */ React.createElement("form", {
className: "YearPicker__input",
onSubmit
}, /* @__PURE__ */ React.createElement(Box, {
horizontal: true
}, /* @__PURE__ */ React.createElement(InputGroup, null, /* @__PURE__ */ React.createElement(Button, {
flat: true,
size: "huge",
icon: GoPrevious,
onClick: () => onChange(value - 1)
}), /* @__PURE__ */ React.createElement(Input, {
flat: true,
size: "huge",
type: "number",
value: value.toString(),
onChange: (v) => onChange(Number(v.replace(/[^0-9.,]/g, "") ?? 0))
}), /* @__PURE__ */ React.createElement(Button, {
flat: true,
size: "huge",
icon: GoNext,
onClick: () => onChange(value + 1)
})))), yearButtons.map((diff) => /* @__PURE__ */ React.createElement("div", {
key: value + 1 + diff,
className: "YearPicker__year"
}, /* @__PURE__ */ React.createElement(Button, {
flat: true,
onClick: () => onAccept(value + 1 + diff)
}, value + 1 + diff))));
}
function isEqual(item, value) {
return value.getFullYear() === item.year && value.getMonth() === item.month && value.getDate() === item.date;
}
function isEqualMonth(item, value) {
return value.getFullYear() === item.year && value.getMonth() === item.month;
}
function toDate(item) {
return new Date(item.year, item.month, item.date);
}
function getWeeks(current) {
const year = current.getFullYear();
const month = current.getMonth();
const firstDay = startOfMonth(current);
const lastDay = endOfMonth(current);
const firstDate = firstDay.getDay();
const lastDate = lastDay.getDate();
const weeks = [Array(7).fill(void 0)];
let currentWeek = weeks[0];
let currentDate = firstDate;
for (let i = 1; i <= lastDate; i++) {
if (currentDate === 7) {
currentDate = 0;
currentWeek = Array(7).fill(void 0);
weeks.push(currentWeek);
}
currentWeek[currentDate] = { year, month, date: i };
currentDate++;
}
if (firstDay.getDay() !== 0) {
const previousMonth = addMonths(current, -1);
const currentWeek2 = weeks[0];
const firstDayOfWeek = addDays(firstDay, -firstDay.getDay());
const firstDayOfWeekDate = firstDayOfWeek.getDate();
for (let i = 0; i < firstDay.getDay(); i++) {
currentWeek2[i] = {
year: previousMonth.getFullYear(),
month: previousMonth.getMonth(),
date: firstDayOfWeekDate + i
};
}
}
if (lastDay.getDay() !== 6) {
const nextMonth = addMonths(current, 1);
const currentWeek2 = weeks[weeks.length - 1];
let currentDate2 = 1;
for (let i = lastDay.getDay() + 1; i <= 6; i++) {
currentWeek2[i] = {
year: nextMonth.getFullYear(),
month: nextMonth.getMonth(),
date: currentDate2++
};
}
}
if (weeks.length <= 5) {
const nextMonth = addMonths(current, 1);
const lastDayOfCalendar = weeks[weeks.length - 1][6];
const currentWeek2 = [];
weeks.push(currentWeek2);
const lastDayOfWeekDate = lastDayOfCalendar.month === nextMonth.getMonth() ? lastDayOfCalendar.date : 1;
for (let i = 0; i <= 6; i++) {
currentWeek2[i] = {
year: nextMonth.getFullYear(),
month: nextMonth.getMonth(),
date: lastDayOfWeekDate + i
};
}
}
return weeks;
}
export {
Calendar
};