UNPKG

adwaita-web

Version:

A GTK inspired toolkit designed to build awesome web apps

281 lines (280 loc) 9.52 kB
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 };