'use client'; import { getUserLocale } from 'get-user-locale'; import { formatMonthYear as defaultFormatMonthYear, formatYear as defaultFormatYear, } from '../shared/dateFormatter.js'; import { getBeginNext, getBeginNext2, getBeginPrevious, getBeginPrevious2, getCenturyLabel, getDecadeLabel, getEndPrevious, getEndPrevious2, } from '../shared/dates.js'; import type { Action, NavigationLabelFunc, RangeType } from '../shared/types.js'; const className = 'react-calendar__navigation'; type NavigationProps = { /** * The beginning of a period that shall be displayed. If you wish to use react-calendar in an uncontrolled way, use `defaultActiveStartDate` instead. * * @example new Date(2017, 0, 1) */ activeStartDate: Date; drillUp: () => void; /** * Function called to override default formatting of months and years. Can be used to use your own formatting function. * * @example (locale, date) => formatDate(date, 'MMMM YYYY') */ formatMonthYear?: typeof defaultFormatMonthYear; /** * Function called to override default formatting of year in the top navigation section. Can be used to use your own formatting function. * * @example (locale, date) => formatDate(date, 'YYYY') */ formatYear?: typeof defaultFormatYear; /** * Locale that should be used by the calendar. Can be any [IETF language tag](https://en.wikipedia.org/wiki/IETF_language_tag). * * **Note**: When using SSR, setting this prop may help resolving hydration errors caused by locale mismatch between server and client. * * @example 'hu-HU' */ locale?: string; /** * Maximum date that the user can select. Periods partially overlapped by maxDate will also be selectable, although react-calendar will ensure that no later date is selected. * * @example new Date() */ maxDate?: Date; /** * Minimum date that the user can select. Periods partially overlapped by minDate will also be selectable, although react-calendar will ensure that no earlier date is selected. * * @example new Date() */ minDate?: Date; /** * `aria-label` attribute of a label rendered on calendar navigation bar. * * @example 'Go up' */ navigationAriaLabel?: string; /** * `aria-live` attribute of a label rendered on calendar navigation bar. * * @default undefined * @example 'polite' */ navigationAriaLive?: 'off' | 'polite' | 'assertive'; /** * Content of a label rendered on calendar navigation bar. * * @example ({ date, label, locale, view }) => alert(`Current view: ${view}, date: ${date.toLocaleDateString(locale)}`) */ navigationLabel?: NavigationLabelFunc; /** * `aria-label` attribute of the "next on higher level" button on the navigation pane. * * @example 'Jump forwards' */ next2AriaLabel?: string; /** * Content of the "next on higher level" button on the navigation pane. Setting the value explicitly to null will hide the icon. * * @example '»' * @example */ next2Label?: React.ReactNode; /** * `aria-label` attribute of the "next" button on the navigation pane. * * @example 'Next' */ nextAriaLabel?: string; /** * Content of the "next" button on the navigation pane. Setting the value explicitly to null will hide the icon. * * @example '›' * @example */ nextLabel?: React.ReactNode; /** * `aria-label` attribute of the "previous on higher level" button on the navigation pane. * * @example 'Jump backwards' */ prev2AriaLabel?: string; /** * Content of the "previous on higher level" button on the navigation pane. Setting the value explicitly to null will hide the icon. * * @example '«' * @example */ prev2Label?: React.ReactNode; /** * `aria-label` attribute of the "previous" button on the navigation pane. * * @example 'Previous' */ prevAriaLabel?: string; /** * Content of the "previous" button on the navigation pane. Setting the value explicitly to null will hide the icon. * * @example '‹' * @example */ prevLabel?: React.ReactNode; setActiveStartDate: (nextActiveStartDate: Date, action: Action) => void; /** * Whether to show two months/years/… at a time instead of one. Defaults `showFixedNumberOfWeeks` prop to be `true`. * * @default false * @example true */ showDoubleView?: boolean; /** * Determines which calendar view shall be opened. Does not disable navigation. Can be `"day"`, `"month"`, `"year"`, `"decade"` or `"century"`. * * @example 'year' */ view: RangeType; views: string[]; }; export default function Navigation({ activeStartDate, drillUp, formatMonthYear = defaultFormatMonthYear, formatYear = defaultFormatYear, locale, maxDate, minDate, navigationAriaLabel = '', navigationAriaLive, navigationLabel, next2AriaLabel = '', next2Label = '»', nextAriaLabel = '', nextLabel = '›', prev2AriaLabel = '', prev2Label = '«', prevAriaLabel = '', prevLabel = '‹', setActiveStartDate, showDoubleView, view, views, }: NavigationProps): React.ReactElement { const drillUpAvailable = views.indexOf(view) > 0; const shouldShowPrevNext2Buttons = view !== 'century'; const previousActiveStartDate = getBeginPrevious(view, activeStartDate); const previousActiveStartDate2 = shouldShowPrevNext2Buttons ? getBeginPrevious2(view, activeStartDate) : undefined; const nextActiveStartDate = getBeginNext(view, activeStartDate); const nextActiveStartDate2 = shouldShowPrevNext2Buttons ? getBeginNext2(view, activeStartDate) : undefined; const prevButtonDisabled = (() => { if (previousActiveStartDate.getFullYear() < 0) { return true; } const previousActiveEndDate = getEndPrevious(view, activeStartDate); return minDate && minDate >= previousActiveEndDate; })(); const prev2ButtonDisabled = shouldShowPrevNext2Buttons && (() => { if ((previousActiveStartDate2 as Date).getFullYear() < 0) { return true; } const previousActiveEndDate = getEndPrevious2(view, activeStartDate); return minDate && minDate >= previousActiveEndDate; })(); const nextButtonDisabled = maxDate && maxDate < nextActiveStartDate; const next2ButtonDisabled = shouldShowPrevNext2Buttons && maxDate && maxDate < (nextActiveStartDate2 as Date); function onClickPrevious() { setActiveStartDate(previousActiveStartDate, 'prev'); } function onClickPrevious2() { setActiveStartDate(previousActiveStartDate2 as Date, 'prev2'); } function onClickNext() { setActiveStartDate(nextActiveStartDate, 'next'); } function onClickNext2() { setActiveStartDate(nextActiveStartDate2 as Date, 'next2'); } function renderLabel(date: Date) { const label = (() => { switch (view) { case 'century': return getCenturyLabel(locale, formatYear, date); case 'decade': return getDecadeLabel(locale, formatYear, date); case 'year': return formatYear(locale, date); case 'month': return formatMonthYear(locale, date); default: throw new Error(`Invalid view: ${view}.`); } })(); return navigationLabel ? navigationLabel({ date, label, locale: locale || getUserLocale() || undefined, view, }) : label; } function renderButton() { const labelClassName = `${className}__label`; return ( ); } return (
{prev2Label !== null && shouldShowPrevNext2Buttons ? ( ) : null} {prevLabel !== null && ( )} {renderButton()} {nextLabel !== null && ( )} {next2Label !== null && shouldShowPrevNext2Buttons ? ( ) : null}
); }