UNPKG

@lobehub/ui

Version:

Lobe UI is an open-source UI component library for building AIGC web apps

1 lines • 13.9 kB
{"version":3,"file":"CalendarPanel.mjs","names":["datePickerMessages"],"sources":["../../../src/base-ui/DatePicker/CalendarPanel.tsx"],"sourcesContent":["'use client';\n\nimport { cx } from 'antd-style';\nimport dayjs from 'dayjs';\nimport { ChevronLeft, ChevronRight } from 'lucide-react';\nimport { type KeyboardEvent, memo, useEffect, useRef, useState } from 'react';\n\nimport { panelStyles } from '@/base-ui/panelStyles';\nimport datePickerMessages from '@/i18n/resources/en/datePicker';\nimport { useTranslation } from '@/i18n/useTranslation';\nimport Icon from '@/Icon';\n\nimport {\n buildMonthGrid,\n type CalendarMode,\n type DateBounds,\n getWeekdayLabels,\n getWeekStart,\n isDayDisabled,\n isMonthDisabled,\n isSameDay,\n isYearDisabled,\n moveDayFocus,\n orderRange,\n} from './calendar';\nimport { styles } from './style';\n\nexport interface CalendarPanelProps {\n bounds: DateBounds;\n hovered?: Date | null;\n mode: CalendarMode;\n month: Date;\n onHover?: (date: Date | null) => void;\n onMonthChange: (month: Date) => void;\n onSelect: (date: Date) => void;\n range?: [Date | null, Date | null];\n selected?: Date | null;\n showNav?: boolean;\n}\n\nconst MONTHS = Array.from({ length: 12 }, (_, index) => index);\n\nconst CalendarPanel = memo<CalendarPanelProps>(\n ({\n bounds,\n hovered,\n mode,\n month,\n onHover,\n onMonthChange,\n onSelect,\n range,\n selected,\n showNav = true,\n }) => {\n const { t } = useTranslation(datePickerMessages);\n const [view, setView] = useState<CalendarMode>(mode);\n const [focused, setFocused] = useState<Date>(selected ?? month);\n const gridRef = useRef<HTMLDivElement>(null);\n const weekStart = getWeekStart();\n const today = new Date();\n const current = dayjs(month);\n const decadeStart = Math.floor(current.year() / 12) * 12;\n const days = buildMonthGrid(month, weekStart);\n const focusDay =\n dayjs(focused).isSame(month, 'month') && !isDayDisabled(focused, bounds)\n ? focused\n : (days.find((day) => dayjs(day).isSame(month, 'month') && !isDayDisabled(day, bounds)) ??\n current.startOf('month').toDate());\n const keyboardMoveRef = useRef(false);\n\n useEffect(() => {\n if (!keyboardMoveRef.current) return;\n keyboardMoveRef.current = false;\n gridRef.current?.querySelector<HTMLButtonElement>('[data-focus-target]')?.focus();\n }, [focused, month]);\n\n const step = (direction: 1 | -1) => {\n const unit = view === 'date' ? 'month' : 'year';\n const amount = view === 'year' ? 12 * direction : direction;\n onMonthChange(current.add(amount, unit).toDate());\n };\n\n const drillUp = () => setView(view === 'date' ? 'month' : 'year');\n\n const handleDayKey = (event: KeyboardEvent<HTMLButtonElement>) => {\n let next = moveDayFocus(focusDay, event.key, weekStart);\n if (!next) return;\n event.preventDefault();\n for (let step = 0; next && isDayDisabled(next, bounds) && step < 366; step++) {\n next = moveDayFocus(next, event.key, weekStart);\n }\n if (!next || isDayDisabled(next, bounds)) return;\n keyboardMoveRef.current = true;\n setFocused(next);\n if (!dayjs(next).isSame(month, 'month')) onMonthChange(next);\n };\n\n const band = (() => {\n if (!range?.[0]) return null;\n const end = range[1] ?? hovered;\n return end ? orderRange(range[0], end) : null;\n })();\n\n const renderDays = () => {\n return (\n <div\n aria-label={current.format('MMMM YYYY')}\n className={styles.grid}\n ref={gridRef}\n role=\"grid\"\n onMouseLeave={() => onHover?.(null)}\n >\n {getWeekdayLabels(weekStart).map((label) => (\n <span className={cx(styles.weekday, panelStyles.label)} key={label} role=\"columnheader\">\n {label}\n </span>\n ))}\n {days.map((date) => {\n const outside = !dayjs(date).isSame(month, 'month');\n const isStart = !outside && !!band && isSameDay(date, band[0]);\n const isEnd = !outside && !!band && isSameDay(date, band[1]);\n const inBand =\n !outside &&\n !!band &&\n dayjs(date).isAfter(band[0], 'day') &&\n dayjs(date).isBefore(band[1], 'day');\n const pressed = !outside && (isSameDay(date, selected) || isStart || isEnd);\n const focusTarget = isSameDay(date, focusDay) && !outside;\n\n return (\n <span\n aria-selected={pressed}\n key={date.toISOString()}\n role=\"gridcell\"\n className={cx(\n styles.cell,\n inBand && styles.band,\n isStart && !isEnd && styles.bandStart,\n isEnd && !isStart && styles.bandEnd,\n )}\n >\n <button\n aria-label={dayjs(date).format('MMMM D, YYYY')}\n aria-pressed={pressed}\n className={styles.day}\n data-focus-target={focusTarget ? '' : undefined}\n data-outside={outside ? '' : undefined}\n data-today={isSameDay(date, today) ? '' : undefined}\n disabled={isDayDisabled(date, bounds)}\n tabIndex={focusTarget ? 0 : -1}\n type=\"button\"\n onClick={() => onSelect(dayjs(date).startOf('day').toDate())}\n onKeyDown={handleDayKey}\n onMouseEnter={() => onHover?.(date)}\n >\n {date.getDate()}\n </button>\n </span>\n );\n })}\n </div>\n );\n };\n\n const renderMonths = () => (\n <div className={styles.tiles}>\n {MONTHS.map((index) => {\n const date = current.month(index).startOf('month').toDate();\n return (\n <button\n aria-pressed={!!selected && dayjs(selected).isSame(date, 'month')}\n className={styles.tile}\n data-today={dayjs(today).isSame(date, 'month') ? '' : undefined}\n disabled={isMonthDisabled(date, bounds)}\n key={index}\n type=\"button\"\n onClick={() => {\n if (mode === 'month') return onSelect(date);\n onMonthChange(date);\n setView('date');\n }}\n >\n {dayjs(date).format('MMM')}\n </button>\n );\n })}\n </div>\n );\n\n const renderYears = () => (\n <div className={styles.tiles}>\n {MONTHS.map((index) => {\n const date = dayjs(new Date(decadeStart + index, 0, 1)).toDate();\n return (\n <button\n aria-pressed={!!selected && dayjs(selected).isSame(date, 'year')}\n className={styles.tile}\n data-today={dayjs(today).isSame(date, 'year') ? '' : undefined}\n disabled={isYearDisabled(date, bounds)}\n key={index}\n type=\"button\"\n onClick={() => {\n if (mode === 'year') return onSelect(date);\n onMonthChange(current.year(decadeStart + index).toDate());\n setView('month');\n }}\n >\n {decadeStart + index}\n </button>\n );\n })}\n </div>\n );\n\n const title = (() => {\n if (view === 'date')\n return (\n <>\n {current.format('MMMM')}{' '}\n <span className={panelStyles.titleMuted}>{current.format('YYYY')}</span>\n </>\n );\n if (view === 'month') return current.format('YYYY');\n return `${decadeStart} – ${decadeStart + 11}`;\n })();\n\n return (\n <div className={styles.calendar}>\n <div className={styles.header}>\n <button\n className={panelStyles.title}\n disabled={view === 'year'}\n type=\"button\"\n onClick={drillUp}\n >\n {title}\n </button>\n {showNav && (\n <div className={styles.navGroup}>\n <button\n aria-label={t('datePicker.previous')}\n className={panelStyles.nav}\n type=\"button\"\n onClick={() => step(-1)}\n >\n <Icon icon={ChevronLeft} size={14} />\n </button>\n <button\n aria-label={t('datePicker.next')}\n className={panelStyles.nav}\n type=\"button\"\n onClick={() => step(1)}\n >\n <Icon icon={ChevronRight} size={14} />\n </button>\n </div>\n )}\n </div>\n {view === 'date' && renderDays()}\n {view === 'month' && renderMonths()}\n {view === 'year' && renderYears()}\n </div>\n );\n },\n);\n\nCalendarPanel.displayName = 'CalendarPanel';\n\nexport default CalendarPanel;\n"],"mappings":"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"}