UNPKG

@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
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;