@codeworker.br/govbr-tw-react
Version:
Biblioteca de componentes React usando Tailwind CSS que implementa o Padrão Digital de Governo.
251 lines (250 loc) • 13.2 kB
JavaScript
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { forwardRef, useCallback, useEffect, useMemo, useState } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faChevronLeft, faChevronRight } from "@fortawesome/free-solid-svg-icons";
import BASE_CLASSNAMES from "../../config/baseClassNames";
import { cn } from "../../libs/utils";
import { Button } from "../Button";
import NativeSelect from "../NativeSelect";
import { calendarVariants, dayButtonVariants } from "./variants";
const toStartOfDay = (date) => new Date(date.getFullYear(), date.getMonth(), date.getDate());
const toStartOfMonth = (date) => new Date(date.getFullYear(), date.getMonth(), 1);
const addMonths = (date, amount) => new Date(date.getFullYear(), date.getMonth() + amount, 1);
const monthToIndex = (date) => date.getFullYear() * 12 + date.getMonth();
const clampMonth = (target, min, max) => {
const monthStart = toStartOfMonth(target);
if (min) {
const minMonth = toStartOfMonth(min);
if (monthToIndex(monthStart) < monthToIndex(minMonth)) {
return minMonth;
}
}
if (max) {
const maxMonth = toStartOfMonth(max);
if (monthToIndex(monthStart) > monthToIndex(maxMonth)) {
return maxMonth;
}
}
return monthStart;
};
const isSameDay = (a, b) => !!a &&
!!b &&
a.getFullYear() === b.getFullYear() &&
a.getMonth() === b.getMonth() &&
a.getDate() === b.getDate();
const isBeforeDay = (a, b) => {
if (!a || !b)
return false;
return a.getTime() < b.getTime();
};
const isAfterDay = (a, b) => {
if (!a || !b)
return false;
return a.getTime() > b.getTime();
};
const clampToRange = (date, min, max) => {
if (min && isBeforeDay(date, min)) {
return min;
}
if (max && isAfterDay(date, max)) {
return max;
}
return date;
};
const getWeekdayLabels = (locale, weekStartsOn, override) => {
if (override && override.length === 7) {
return override
.slice(weekStartsOn)
.concat(override.slice(0, weekStartsOn));
}
const formatter = new Intl.DateTimeFormat(locale, { weekday: "short" });
const base = new Date(2021, 0, 3); // Sunday
return Array.from({ length: 7 }).map((_, index) => {
const day = (weekStartsOn + index) % 7;
const target = new Date(base);
target.setDate(base.getDate() + day);
return formatter.format(target);
});
};
const getMonthLabels = (locale, override) => {
if (override && override.length === 12) {
return override;
}
const formatter = new Intl.DateTimeFormat(locale, { month: "long" });
return Array.from({ length: 12 }).map((_, index) => {
const date = new Date(2021, index, 1);
const label = formatter.format(date);
return label.charAt(0).toUpperCase() + label.slice(1);
});
};
const Calendar = forwardRef((_a, ref) => {
var { className, value, defaultValue, onChange, minDate, maxDate, disabledDate, variant = "light", density = "comfortable", weekStartsOn = 0, locale = "pt-BR", yearRange, translations } = _a, props = __rest(_a, ["className", "value", "defaultValue", "onChange", "minDate", "maxDate", "disabledDate", "variant", "density", "weekStartsOn", "locale", "yearRange", "translations"]);
const isControlled = value !== undefined;
const initialSelected = useMemo(() => {
var _a;
return (_a = value !== null && value !== void 0 ? value : defaultValue) !== null && _a !== void 0 ? _a : clampToRange(new Date(), minDate ? toStartOfDay(minDate) : undefined, maxDate ? toStartOfDay(maxDate) : undefined);
}, [value, defaultValue, minDate, maxDate]);
const [internalValue, setInternalValue] = useState(isControlled ? value : initialSelected);
useEffect(() => {
if (isControlled) {
setInternalValue(value);
}
}, [isControlled, value]);
const selectedDate = isControlled ? value : internalValue;
const [visibleMonth, setVisibleMonth] = useState(() => toStartOfMonth(selectedDate !== null && selectedDate !== void 0 ? selectedDate : new Date()));
useEffect(() => {
if (selectedDate) {
setVisibleMonth((current) => {
const target = toStartOfMonth(selectedDate);
if (current.getFullYear() === target.getFullYear() && current.getMonth() === target.getMonth()) {
return current;
}
return target;
});
}
}, [selectedDate]);
const handleSelect = useCallback((date) => {
if (disabledDate === null || disabledDate === void 0 ? void 0 : disabledDate(date))
return;
if (minDate && isBeforeDay(date, toStartOfDay(minDate)))
return;
if (maxDate && isAfterDay(date, toStartOfDay(maxDate)))
return;
if (!isControlled) {
setInternalValue(date);
}
onChange === null || onChange === void 0 ? void 0 : onChange(date);
}, [disabledDate, isControlled, maxDate, minDate, onChange]);
useEffect(() => {
setVisibleMonth((current) => clampMonth(current, minDate, maxDate));
}, [minDate, maxDate]);
const canGoPrev = useMemo(() => {
if (!minDate)
return true;
const target = addMonths(visibleMonth, -1);
return monthToIndex(target) >= monthToIndex(minDate);
}, [visibleMonth, minDate]);
const canGoNext = useMemo(() => {
if (!maxDate)
return true;
const target = addMonths(visibleMonth, 1);
return monthToIndex(target) <= monthToIndex(maxDate);
}, [visibleMonth, maxDate]);
const goPrev = useCallback(() => {
if (!canGoPrev)
return;
setVisibleMonth((current) => addMonths(current, -1));
}, [canGoPrev]);
const goNext = useCallback(() => {
if (!canGoNext)
return;
setVisibleMonth((current) => addMonths(current, 1));
}, [canGoNext]);
const monthLabels = useMemo(() => getMonthLabels(locale, translations === null || translations === void 0 ? void 0 : translations.months), [locale, translations === null || translations === void 0 ? void 0 : translations.months]);
const currentYear = visibleMonth.getFullYear();
const years = useMemo(() => {
var _a, _b;
const inferredStart = (_a = yearRange === null || yearRange === void 0 ? void 0 : yearRange.start) !== null && _a !== void 0 ? _a : currentYear - 50;
const inferredEnd = (_b = yearRange === null || yearRange === void 0 ? void 0 : yearRange.end) !== null && _b !== void 0 ? _b : currentYear + 50;
const start = minDate
? Math.max(inferredStart, minDate.getFullYear())
: inferredStart;
const end = maxDate
? Math.min(inferredEnd, maxDate.getFullYear())
: inferredEnd;
if (start > end) {
return [currentYear];
}
return Array.from({ length: end - start + 1 }, (_, index) => start + index);
}, [currentYear, yearRange, minDate, maxDate]);
const weekdays = useMemo(() => getWeekdayLabels(locale, weekStartsOn, translations === null || translations === void 0 ? void 0 : translations.weekdaysShort), [locale, weekStartsOn, translations === null || translations === void 0 ? void 0 : translations.weekdaysShort]);
const dayMatrix = useMemo(() => {
const startOfMonth = toStartOfMonth(visibleMonth);
const startDay = startOfMonth.getDay();
const diff = (startDay - weekStartsOn + 7) % 7;
const gridStart = new Date(startOfMonth);
gridStart.setDate(gridStart.getDate() - diff);
return Array.from({ length: 42 }).map((_, index) => {
const cellDate = new Date(gridStart);
cellDate.setDate(gridStart.getDate() + index);
const dayDate = toStartOfDay(cellDate);
const isCurrentMonth = cellDate.getMonth() === visibleMonth.getMonth();
const disabledByRange = (minDate && isBeforeDay(dayDate, toStartOfDay(minDate))) ||
(maxDate && isAfterDay(dayDate, toStartOfDay(maxDate)));
const isDisabled = disabledByRange || !!(disabledDate === null || disabledDate === void 0 ? void 0 : disabledDate(dayDate));
const today = toStartOfDay(new Date());
const isTodayCell = isSameDay(dayDate, today);
const isSelected = selectedDate
? isSameDay(dayDate, toStartOfDay(selectedDate))
: false;
return {
date: cellDate,
isCurrentMonth,
isToday: isTodayCell,
isSelected,
isDisabled,
};
});
}, [
disabledDate,
maxDate,
minDate,
selectedDate,
visibleMonth,
weekStartsOn,
]);
const navButtonVariant = variant === "dark" ? "ghost-dark" : "ghost";
const buttonDensity = density === "compact" ? "high" : "default";
const selectVariant = variant === "dark" ? "dark" : "default";
const handleMonthChange = useCallback((event) => {
const nextMonth = parseInt(event.target.value, 10);
setVisibleMonth((current) => {
const updated = new Date(current);
updated.setMonth(nextMonth);
return updated;
});
}, []);
const handleYearChange = useCallback((event) => {
const nextYear = parseInt(event.target.value, 10);
setVisibleMonth((current) => {
const updated = new Date(current);
updated.setFullYear(nextYear);
return updated;
});
}, []);
return (_jsxs("div", Object.assign({ ref: ref, className: cn(calendarVariants({ variant, density }), BASE_CLASSNAMES.calendar.root, className) }, props, { children: [_jsxs("div", { className: cn("flex items-center justify-between gap-2", BASE_CLASSNAMES.calendar.header), children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { size: "icon", density: buttonDensity, variant: navButtonVariant, onClick: goPrev, disabled: !canGoPrev, "aria-label": "Mes anterior", children: _jsx(FontAwesomeIcon, { icon: faChevronLeft }) }), _jsx(Button, { size: "icon", density: buttonDensity, variant: navButtonVariant, onClick: goNext, disabled: !canGoNext, "aria-label": "Proximo mes", children: _jsx(FontAwesomeIcon, { icon: faChevronRight }) })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(NativeSelect, { className: "min-w-[8rem]", variant: selectVariant, selected: String(visibleMonth.getMonth()), onChange: handleMonthChange, firstOption: "Mes", options: monthLabels.map((label, index) => ({
label,
value: String(index),
})) }), _jsx(NativeSelect, { className: "min-w-[6rem]", variant: selectVariant, selected: String(visibleMonth.getFullYear()), onChange: handleYearChange, firstOption: "Ano", options: years.map((yearValue) => ({
label: String(yearValue),
value: String(yearValue),
})) })] })] }), _jsx("div", { className: cn("grid grid-cols-7 text-center text-xs font-semibold uppercase tracking-wide", density === "compact" ? "gap-1" : "gap-2"), children: weekdays.map((weekday) => (_jsx("span", { children: weekday }, weekday))) }), _jsx("div", { className: cn("grid grid-cols-7", density === "compact" ? "gap-1" : "gap-2", BASE_CLASSNAMES.calendar.grid), children: dayMatrix.map(({ date, isCurrentMonth, isDisabled, isSelected, isToday }) => (_jsx("button", { type: "button", className: cn(dayButtonVariants({
variant,
density,
isSelected,
isToday,
isOutside: !isCurrentMonth,
}), BASE_CLASSNAMES.calendar.day), onClick: () => {
handleSelect(toStartOfDay(date));
if (!isCurrentMonth) {
setVisibleMonth(toStartOfMonth(date));
}
}, disabled: isDisabled, children: date.getDate() }, date.toISOString()))) }), selectedDate && (_jsx("div", { className: "text-sm opacity-70", children: new Intl.DateTimeFormat(locale, {
day: "numeric",
month: "long",
year: "numeric",
}).format(selectedDate) }))] })));
});
Calendar.displayName = "Calendar";
export default Calendar;