import { createContext, ReactNode, useContext } from 'react'; import { isSameDay } from 'date-fns'; import { DayPickerBase } from 'types/DayPickerBase'; import { DayPickerMultipleProps, isDayPickerMultiple } from 'types/DayPickerMultiple'; import { DayClickEventHandler } from 'types/EventHandlers'; import { InternalModifier, Modifiers } from 'types/Modifiers'; /** Represent the modifiers that are changed by the multiple selection. */ export type SelectMultipleModifiers = Pick< Modifiers, InternalModifier.Disabled >; /** Represents the value of a {@link SelectMultipleContext}. */ export interface SelectMultipleContextValue { /** The days that have been selected. */ selected: Date[] | undefined; /** The modifiers for the corresponding selection. */ modifiers: SelectMultipleModifiers; /** Event handler to attach to the day button to enable the multiple select. */ onDayClick?: DayClickEventHandler; } /** * The SelectMultiple context shares details about the selected days when in * multiple selection mode. * * Access this context from the {@link useSelectMultiple} hook. */ export const SelectMultipleContext = createContext< SelectMultipleContextValue | undefined >(undefined); export type SelectMultipleProviderProps = { initialProps: DayPickerBase; children?: ReactNode; }; /** Provides the values for the {@link SelectMultipleContext}. */ export function SelectMultipleProvider( props: SelectMultipleProviderProps ): JSX.Element { if (!isDayPickerMultiple(props.initialProps)) { const emptyContextValue: SelectMultipleContextValue = { selected: undefined, modifiers: { disabled: [] } }; return ( {props.children} ); } return ( ); } /** @private */ export interface SelectMultipleProviderInternalProps { initialProps: DayPickerMultipleProps; children?: ReactNode; } export function SelectMultipleProviderInternal({ initialProps, children }: SelectMultipleProviderInternalProps): JSX.Element { const { selected, min, max } = initialProps; const onDayClick: DayClickEventHandler = (day, activeModifiers, e) => { initialProps.onDayClick?.(day, activeModifiers, e); const isMinSelected = Boolean( activeModifiers.selected && min && selected?.length === min ); if (isMinSelected) { return; } const isMaxSelected = Boolean( !activeModifiers.selected && max && selected?.length === max ); if (isMaxSelected) { return; } const selectedDays = selected ? [...selected] : []; if (activeModifiers.selected) { const index = selectedDays.findIndex((selectedDay) => isSameDay(day, selectedDay) ); selectedDays.splice(index, 1); } else { selectedDays.push(day); } initialProps.onSelect?.(selectedDays, day, activeModifiers, e); }; const modifiers: SelectMultipleModifiers = { disabled: [] }; if (selected) { modifiers.disabled.push((day: Date) => { const isMaxSelected = max && selected.length > max - 1; const isSelected = selected.some((selectedDay) => isSameDay(selectedDay, day) ); return Boolean(isMaxSelected && !isSelected); }); } const contextValue = { selected, onDayClick, modifiers }; return ( {children} ); } /** * Hook to access the {@link SelectMultipleContextValue}. * * This hook is meant to be used inside internal or custom components. */ export function useSelectMultiple(): SelectMultipleContextValue { const context = useContext(SelectMultipleContext); if (!context) { throw new Error( 'useSelectMultiple must be used within a SelectMultipleProvider' ); } return context; }