diff --git a/src/form/Calendar/Calendar.story.tsx b/src/form/Calendar/Calendar.story.tsx index de3103f36..dade4622e 100644 --- a/src/form/Calendar/Calendar.story.tsx +++ b/src/form/Calendar/Calendar.story.tsx @@ -1,7 +1,6 @@ import { useState } from 'react'; import { Card } from '../../layout/Card'; import { Calendar } from './Calendar'; -import { CalendarRange } from './CalendarRange'; import { add, addMonths, sub } from 'date-fns'; import { Divider, Stack } from '../../layout'; @@ -119,11 +118,13 @@ export const Multiview = () => { return ( - setRange(val as [Date, Date])} - numMonths={3} - enableDayOfWeek + numMonths={2} + direction="past" + max="now" + showDayOfWeek isRange /> diff --git a/src/form/Calendar/Calendar.tsx b/src/form/Calendar/Calendar.tsx index 2c87ee5af..41a1eed0f 100644 --- a/src/form/Calendar/Calendar.tsx +++ b/src/form/Calendar/Calendar.tsx @@ -3,6 +3,7 @@ import React, { FC, useCallback, useMemo, useState } from 'react'; import { Button } from '../../elements/Button'; import { add, + addMonths, addYears, endOfDecade, getMonth, @@ -19,6 +20,7 @@ import { import { DateFormat } from '../../data/DateFormat'; import { CalendarDays } from './CalendarDays'; import { CalendarMonths } from './CalendarMonths'; +import { CalendarRange } from './CalendarRange'; import { CalendarYears } from './CalendarYears'; import { SmallHeading } from '../../typography'; @@ -63,6 +65,16 @@ export interface CalendarProps { */ previousArrow?: React.ReactNode | string; + /** + * The number of calendar months to display. + */ + numMonths?: number; + + /** + * Defaults view to show past or future months if multiple months shown. + */ + direction?: 'past' | 'future'; + /** * The date format to use for the calendar. Defaults 'MMMM yyyy'. */ @@ -71,7 +83,7 @@ export interface CalendarProps { /** * Whether to display day of week labels */ - enableDayOfWeek?: boolean; + showDayOfWeek?: boolean; /** * Whether to animate the calendar. @@ -95,9 +107,12 @@ export const Calendar: FC = ({ value, disabled, isRange, + numMonths, + direction, previousArrow, nextArrow, dateFormat, + showDayOfWeek, animated, onChange, onViewChange @@ -125,6 +140,12 @@ export const Calendar: FC = ({ 'forward' | 'back' | null >(null); + const displayMonths = Array.from(Array(numMonths).keys()); + const showPast = direction === 'past'; + if (showPast) { + displayMonths.reverse(); + } + const previousClickHandler = useCallback(() => { setScrollDirection('back'); if (view === 'days') { @@ -225,11 +246,16 @@ export const Calendar: FC = ({ > {view === 'days' && ( - +
+ {displayMonths.map(i => ( + + ))} +
)} {view === 'months' && <>{yearValue}} {view === 'years' && ( @@ -260,7 +286,7 @@ export const Calendar: FC = ({ scale: { type: animated ? 'tween' : false } }} > - {view === 'days' && ( + {view === 'days' && numMonths === 1 && ( = ({ disabled={disabled} isRange={isRange} current={isRange ? [rangeStart, rangeEnd] : date} + showDayOfWeek={showDayOfWeek} + xAnimation={xAnimation} + animated={animated} + onChange={dateChangeHandler} + /> + )} + {view === 'days' && numMonths !== 1 && ( + = ({ max, animated, xAnimation = 0, - enableDayOfWeek, + showDayOfWeek, hidePrevMonthDays, hideNextMonthDays, onChange, @@ -254,7 +254,7 @@ export const CalendarDays: FC = ({ opacity: { duration: 0.2, type: animated ? 'tween' : false } }} > - {enableDayOfWeek && ( + {showDayOfWeek && (
{DAY_OF_WEEK_LABELS.map(day => (
diff --git a/src/form/Calendar/CalendarRange.tsx b/src/form/Calendar/CalendarRange.tsx deleted file mode 100644 index 9cbbd807f..000000000 --- a/src/form/Calendar/CalendarRange.tsx +++ /dev/null @@ -1,273 +0,0 @@ -import { FC, Fragment, useCallback, useMemo, useState } from 'react'; -import { - add, - addMonths, - addYears, - endOfDecade, - getMonth, - getYear, - isSameDay, - max as maxDate, - min as minDate, - setMonth, - setYear, - startOfDecade, - sub, - subYears -} from 'date-fns'; -import { AnimatePresence, motion } from 'framer-motion'; -import { Button } from '../../elements/Button'; -import { CalendarProps, CalendarViewType } from './Calendar'; -import { CalendarDays } from './CalendarDays'; -import { CalendarMonths } from './CalendarMonths'; -import { CalendarYears } from './CalendarYears'; -import { DateFormat } from '../../data/DateFormat'; -import { SmallHeading } from '../../typography'; -import css from './Calendar.module.css'; - -export interface CalendarRangeProps extends CalendarProps { - /** - * The number of months to display in the range. - * Defaults to 2. - */ - numMonths?: number; -} - -export const CalendarRange: FC = ({ - min, - max, - value, - disabled, - isRange, - previousArrow, - nextArrow, - dateFormat, - animated, - onChange, - onViewChange, - numMonths, - enableDayOfWeek -}) => { - const date = useMemo( - () => (Array.isArray(value) ? value?.[0] : value) ?? new Date(), - [value] - ); - const rangeStart = useMemo( - () => value?.[0] ?? date ?? new Date(), - [date, value] - ); - const rangeEnd = useMemo( - () => value?.[1] ?? date ?? new Date(), - [date, value] - ); - - const [viewValue, setViewValue] = useState(date || new Date()); - const [monthValue, setMonthValue] = useState(getMonth(date)); - const [yearValue, setYearValue] = useState(getYear(date)); - const [decadeStart, setDecadeStart] = useState(startOfDecade(date)); - const [decadeEnd, setDecadeEnd] = useState(endOfDecade(date)); - const [view, setView] = useState('days'); - const [hoveringDate, setHoveringDate] = useState(null); - const [scrollDirection, setScrollDirection] = useState< - 'forward' | 'back' | null - >(null); - - if (numMonths < 0) { - return null; - } - - const displayMonths = Array.from(Array(numMonths).keys()); - - const previousClickHandler = useCallback(() => { - setScrollDirection('back'); - if (view === 'days') { - setViewValue(sub(viewValue, { months: 1 })); - } else if (view === 'months') { - setYearValue(yearValue - 1); - } else { - setDecadeStart(subYears(decadeStart, 10)); - setDecadeEnd(subYears(decadeEnd, 10)); - } - }, [decadeEnd, decadeStart, view, viewValue, yearValue]); - - const nextClickHandler = useCallback(() => { - setScrollDirection('forward'); - if (view === 'days') { - setViewValue(add(viewValue, { months: 1 })); - } else if (view === 'months') { - setYearValue(yearValue + 1); - } else { - setDecadeStart(addYears(decadeStart, 10)); - setDecadeEnd(addYears(decadeEnd, 10)); - } - }, [decadeEnd, decadeStart, view, viewValue, yearValue]); - - const headerClickHandler = useCallback(() => { - const newView = view === 'days' ? 'months' : 'years'; - setScrollDirection(null); - setView(newView); - onViewChange?.(newView); - }, [onViewChange, view]); - - const dateChangeHandler = useCallback( - (date: Date) => { - if (!isRange) { - onChange?.(date); - setMonthValue(getMonth(date)); - setYearValue(getYear(date)); - } else { - if (isSameDay(rangeStart, rangeEnd)) { - onChange?.([minDate([rangeStart, date]), maxDate([rangeEnd, date])]); - } else { - onChange?.([date, date]); - } - } - }, - [isRange, onChange, rangeEnd, rangeStart] - ); - - const monthsChangeHandler = useCallback( - month => { - setViewValue(setMonth(setYear(min || new Date(), yearValue), month)); - setMonthValue(month); - setView('days'); - onViewChange?.('days'); - }, - [min, yearValue, onViewChange] - ); - - const yearChangeHandler = useCallback( - year => { - setViewValue(setYear(min || new Date(), year)); - setYearValue(year); - setView('months'); - onViewChange?.('months'); - }, - [min, onViewChange] - ); - - const xAnimation = useMemo(() => { - switch (scrollDirection) { - case 'forward': - return '100%'; - case 'back': - return '-100%'; - default: - return 0; - } - }, [scrollDirection]); - - return ( -
-
- - - -
- - - {view === 'days' && - displayMonths.map(month => ( - - 0} - hideNextMonthDays={month < numMonths - 1} - enableDayOfWeek={enableDayOfWeek} - /> - - ))} - {view === 'months' && ( - - )} - {view === 'years' && ( - - )} - - -
- ); -}; - -CalendarRange.defaultProps = { - previousArrow: '←', - nextArrow: '→', - animated: true, - dateFormat: 'MMMM yyyy', - range: [new Date(), new Date()], - numMonths: 2 -}; diff --git a/src/form/Calendar/CalendarRange/CalendarRange.module.css b/src/form/Calendar/CalendarRange/CalendarRange.module.css new file mode 100644 index 000000000..424ab530b --- /dev/null +++ b/src/form/Calendar/CalendarRange/CalendarRange.module.css @@ -0,0 +1,4 @@ +.calendar { + display: flex; + gap: var(--spacing-lg); +} diff --git a/src/form/Calendar/CalendarRange/CalendarRange.tsx b/src/form/Calendar/CalendarRange/CalendarRange.tsx new file mode 100644 index 000000000..3a51a3f20 --- /dev/null +++ b/src/form/Calendar/CalendarRange/CalendarRange.tsx @@ -0,0 +1,51 @@ +import { FC, Fragment, useState } from 'react'; +import { addMonths } from 'date-fns'; +import { CalendarProps } from '../Calendar'; +import { CalendarDays } from '../CalendarDays'; +import css from './CalendarRange.module.css'; + +export interface CalendarRangeProps extends Omit { + /** + * The currently displayed month of the calendar. + */ + value?: Date; +} + +export const CalendarRange: FC = ({ + value, + onChange, + numMonths, + direction, + ...rest +}) => { + const [hoveringDate, setHoveringDate] = useState(null); + + if (numMonths < 0) { + return null; + } + + const displayMonths = Array.from(Array(numMonths).keys()); + const showPast = direction === 'past'; + if (direction === 'past') { + displayMonths.reverse(); + } + + return ( +
+ {displayMonths.map(month => ( + + 0} + hideNextMonthDays={month < numMonths - 1} + numMonths={numMonths} + onChange={onChange} + {...rest} + /> + + ))} +
+ ); +}; diff --git a/src/form/Calendar/CalendarRange/index.ts b/src/form/Calendar/CalendarRange/index.ts new file mode 100644 index 000000000..2e7e3953a --- /dev/null +++ b/src/form/Calendar/CalendarRange/index.ts @@ -0,0 +1 @@ +export * from './CalendarRange'; diff --git a/src/form/Calendar/index.ts b/src/form/Calendar/index.ts index eb3ea071e..a72338059 100644 --- a/src/form/Calendar/index.ts +++ b/src/form/Calendar/index.ts @@ -1,2 +1 @@ export * from './Calendar'; -export * from './CalendarRange'; diff --git a/src/utils/Theme/themes/dark.ts b/src/utils/Theme/themes/dark.ts index 47414cbb4..6fc94b9f2 100644 --- a/src/utils/Theme/themes/dark.ts +++ b/src/utils/Theme/themes/dark.ts @@ -424,7 +424,15 @@ export const darkTheme: Theme = { 'sub-margin': '0 0 var(--spacing-xs) 0' }, calendar: { - 'calendar-spacing': '8px' + 'calendar-spacing': '8px', + 'calendar-gap': '0px', + 'calendar-day-radius': 'var(--button-border-radius)', + 'calendar-day-color': 'var(--button-color)', + 'calendar-day-background': 'transparent', + 'calendar-day-background-selected': 'var(--primary-background)', + 'calendar-day-border-selected': 'var(--primary-background)', + 'calendar-day-background-hover': 'var(--primary-background)', + 'calendar-day-border-hover': 'var(--primary-background)' }, badge: { 'badge-color-background-default': darkColors.white,