Skip to content

Commit

Permalink
Merge pull request #214 from Mezzanine-UI/refactor/date-picker
Browse files Browse the repository at this point in the history
Bug fixing for Calendar components and some new features
  • Loading branch information
travor20814 authored Dec 18, 2023
2 parents ba478a0 + 4b1ea73 commit 1d94f1f
Show file tree
Hide file tree
Showing 13 changed files with 357 additions and 47 deletions.
41 changes: 38 additions & 3 deletions packages/react/src/Calendar/Calendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,16 @@ export interface CalendarProps
| 'isYearDisabled'
| 'isYearInRange'
| 'onYearHover'>;
/**
* Disabled `Month` calendar button click
* @default false
*/
disabledMonthSwitch?: boolean;
/**
* Disabled `Year` calendar button click
* @default false
*/
disabledYearSwitch?: boolean;
/**
* Use this prop to switch calendars.
* @default 'day'
Expand Down Expand Up @@ -131,8 +141,10 @@ const Calendar = forwardRef<HTMLDivElement, CalendarProps>(function Calendar(pro
calendarWeeksProps,
calendarYearsProps,
className,
disabledMonthSwitch,
disableOnNext,
disableOnPrev,
disabledYearSwitch,
displayMonthLocale = displayMonthLocaleFromConfig,
displayWeekDayLocale,
isDateDisabled,
Expand Down Expand Up @@ -167,6 +179,8 @@ const Calendar = forwardRef<HTMLDivElement, CalendarProps>(function Calendar(pro
displayCalendar = (
<CalendarDays
{...calendarDaysProps}
isYearDisabled={isYearDisabled}
isMonthDisabled={isMonthDisabled}
isDateDisabled={isDateDisabled}
isDateInRange={isDateInRange}
onClick={onChange}
Expand All @@ -180,6 +194,8 @@ const Calendar = forwardRef<HTMLDivElement, CalendarProps>(function Calendar(pro
displayCalendar = (
<CalendarWeeks
{...calendarWeeksProps}
isYearDisabled={isYearDisabled}
isMonthDisabled={isMonthDisabled}
isWeekDisabled={isWeekDisabled}
isWeekInRange={isWeekInRange}
onClick={onChange}
Expand All @@ -193,6 +209,7 @@ const Calendar = forwardRef<HTMLDivElement, CalendarProps>(function Calendar(pro
displayCalendar = (
<CalendarMonths
{...calendarMonthsProps}
isYearDisabled={isYearDisabled}
isMonthDisabled={isMonthDisabled}
isMonthInRange={isMonthInRange}
onClick={onChange}
Expand Down Expand Up @@ -225,14 +242,26 @@ const Calendar = forwardRef<HTMLDivElement, CalendarProps>(function Calendar(pro
<>
<button
type="button"
className={cx(classes.button, classes.controlsButton)}
className={cx(
classes.button,
classes.controlsButton,
disabledMonthSwitch && classes.buttonDisabled,
)}
disabled={disabledMonthSwitch}
aria-disabled={disabledMonthSwitch}
onClick={onMonthControlClick}
>
{getMonthShortName(getMonth(referenceDate), displayMonthLocale)}
</button>
<button
type="button"
className={cx(classes.button, classes.controlsButton)}
className={cx(
classes.button,
classes.controlsButton,
disabledYearSwitch && classes.buttonDisabled,
)}
disabled={disabledYearSwitch}
aria-disabled={disabledYearSwitch}
onClick={onYearControlClick}
>
{getYear(referenceDate)}
Expand All @@ -243,7 +272,13 @@ const Calendar = forwardRef<HTMLDivElement, CalendarProps>(function Calendar(pro
controls = (
<button
type="button"
className={cx(classes.button, classes.controlsButton)}
className={cx(
classes.button,
classes.controlsButton,
disabledYearSwitch && classes.buttonDisabled,
)}
disabled={disabledYearSwitch}
aria-disabled={disabledYearSwitch}
onClick={onYearControlClick}
>
{getYear(referenceDate)}
Expand Down
8 changes: 7 additions & 1 deletion packages/react/src/Calendar/CalendarDays.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,14 @@ import { NativeElementPropsWithoutKeyAndRef } from '../utils/jsx-types';
import { cx } from '../utils/cx';
import CalendarDayOfWeek, { CalendarDayOfWeekProps } from './CalendarDayOfWeek';
import { useCalendarContext } from './CalendarContext';
import type { CalendarYearsProps } from './CalendarYears';
import type { CalendarMonthsProps } from './CalendarMonths';

export interface CalendarDaysProps
extends
Pick<CalendarDayOfWeekProps, 'displayWeekDayLocale'>,
Pick<CalendarYearsProps, 'isYearDisabled'>,
Pick<CalendarMonthsProps, 'isMonthDisabled'>,
Omit<NativeElementPropsWithoutKeyAndRef<'div'>, 'onClick' | 'children'> {
/**
* Provide if you have a custom disabling logic. The method takes the date object as its parameter.
Expand Down Expand Up @@ -60,6 +64,8 @@ function CalendarDays(props: CalendarDaysProps) {
const {
className,
displayWeekDayLocale = displayWeekDayLocaleFromConfig,
isYearDisabled,
isMonthDisabled,
isDateDisabled,
isDateInRange,
onClick: onClickProp,
Expand Down Expand Up @@ -98,7 +104,7 @@ function CalendarDays(props: CalendarDaysProps) {
? thisMonth + 1
: thisMonth;
const date = setDate(setMonth(referenceDate, month), dateNum);
const disabled = isDateDisabled && isDateDisabled(date);
const disabled = (isYearDisabled?.(date) || isMonthDisabled?.(date) || isDateDisabled?.(date)) || false;
const inactive = !disabled && (isPrevMonth || isNextMonth);
const inRange = !inactive && isDateInRange && isDateInRange(date);
const active = !disabled && !inactive && value && isDateIncluded(date, value);
Expand Down
8 changes: 6 additions & 2 deletions packages/react/src/Calendar/CalendarMonths.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import {
DateType,
calendarMonths,
} from '@mezzanine-ui/core/calendar';
import type { CalendarYearsProps } from './CalendarYears';
import { cx } from '../utils/cx';
import { NativeElementPropsWithoutKeyAndRef } from '../utils/jsx-types';
import { useCalendarContext } from './CalendarContext';

export interface CalendarMonthsProps
extends
Omit<NativeElementPropsWithoutKeyAndRef<'div'>, 'onClick' | 'children'> {
Omit<NativeElementPropsWithoutKeyAndRef<'div'>, 'onClick' | 'children'>,
Pick<CalendarYearsProps, 'isYearDisabled'> {
/**
* The locale you want to use when rendering the names of month.
* If none provided, it will use the `displayMonthLocale` from calendar context.
Expand Down Expand Up @@ -59,6 +61,7 @@ function CalendarMonths(props: CalendarMonthsProps) {
displayMonthLocale = displayMonthLocaleFromConfig,
isMonthDisabled,
isMonthInRange,
isYearDisabled,
onClick: onClickProp,
onMonthHover,
referenceDate,
Expand All @@ -80,7 +83,8 @@ function CalendarMonths(props: CalendarMonthsProps) {
{calendarMonths.map((month) => {
const monthDateType = setMonth(referenceDate, month);
const active = value && isMonthIncluded(monthDateType, value);
const disabled = isMonthDisabled && isMonthDisabled(monthDateType);
/** @NOTE Current month should be disabled when current year is disabled */
const disabled = (isYearDisabled?.(monthDateType) || isMonthDisabled?.(monthDateType)) || false;
const inRange = isMonthInRange && isMonthInRange(monthDateType);

const onClick = onClickProp ? () => { onClickProp(monthDateType); } : undefined;
Expand Down
8 changes: 7 additions & 1 deletion packages/react/src/Calendar/CalendarWeeks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,14 @@ import { NativeElementPropsWithoutKeyAndRef } from '../utils/jsx-types';
import { cx } from '../utils/cx';
import CalendarDayOfWeek, { CalendarDayOfWeekProps } from './CalendarDayOfWeek';
import { useCalendarContext } from './CalendarContext';
import type { CalendarYearsProps } from './CalendarYears';
import type { CalendarMonthsProps } from './CalendarMonths';

export interface CalendarWeeksProps
extends
Pick<CalendarDayOfWeekProps, 'displayWeekDayLocale'>,
Pick<CalendarYearsProps, 'isYearDisabled'>,
Pick<CalendarMonthsProps, 'isMonthDisabled'>,
Omit<NativeElementPropsWithoutKeyAndRef<'div'>, 'onClick' | 'children'> {
/**
* Provide if you have a custom disabling logic.
Expand Down Expand Up @@ -64,6 +68,8 @@ function CalendarWeeks(props: CalendarWeeksProps) {
const {
className,
displayWeekDayLocale = displayWeekDayLocaleFromConfig,
isYearDisabled,
isMonthDisabled,
isWeekDisabled,
isWeekInRange,
onClick: onClickProp,
Expand Down Expand Up @@ -105,7 +111,7 @@ function CalendarWeeks(props: CalendarWeeksProps) {
dates.push(date);
});

const disabled = isWeekDisabled && isWeekDisabled(dates[0]);
const disabled = (isYearDisabled?.(dates[0]) || isMonthDisabled?.(dates[0]) || isWeekDisabled?.(dates[0])) || false;
const inactive = !disabled && (weekStartInPrevMonth || weekStartInNextMonth);
const active = !disabled && !inactive && value && isWeekIncluded(dates[0], value);
const inRange = !disabled && !inactive && isWeekInRange && isWeekInRange(dates[0]);
Expand Down
106 changes: 88 additions & 18 deletions packages/react/src/DatePicker/DatePicker.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
import CalendarMethodsDayjs from '@mezzanine-ui/core/calendarMethodsDayjs';
import CalendarMethodsMoment from '@mezzanine-ui/core/calendarMethodsMoment';
import CalendarMethodsLuxon from '@mezzanine-ui/core/calendarMethodsLuxon';
import { useState } from 'react';
import { CSSProperties, useState } from 'react';
import moment from 'moment';
import DatePicker, { DatePickerProps } from './DatePicker';
import Typography from '../Typography';
Expand All @@ -18,7 +18,7 @@ export default {
} as Meta;

function usePickerChange() {
const [val, setVal] = useState<DateType | undefined>('2022-01-05');
const [val, setVal] = useState<DateType | undefined>(new Date().toISOString());
const onChange = (v?: DateType) => { setVal(v); };

return [val, onChange] as const;
Expand Down Expand Up @@ -135,7 +135,7 @@ export const Basic = () => {
export const Method = () => {
const containerStyle = { margin: '0 0 24px 0' };
const typoStyle = { margin: '0 0 12px 0' };
const [val, setVal] = useState<DateType>(new Date());
const [val, setVal] = useState<DateType | undefined>(new Date().toISOString());
const onChange = (v?: DateType) => { setVal(v); };

return (
Expand Down Expand Up @@ -277,18 +277,21 @@ export const Modes = () => {

export const CustomDisable = () => {
const containerStyle = { margin: '0 0 24px 0' };
const typoStyle = { margin: '0 0 12px 0' };
const typoStyle = { margin: '0 0 12px 0', whiteSpace: 'pre-line' } as CSSProperties;
const [valD, onChangeD] = usePickerChange();
const [valW, onChangeW] = usePickerChange();
const [valM, onChangeM] = usePickerChange();
const [valY, onChangeY] = usePickerChange();

// We use moment.date instead of moment.add is because storybook currently has internal conflict with the method.
const disabledDatesStart = moment().date(moment().date() - 7);
const disabledDatesStart = moment().date(moment().date() + 3);
const disabledDatesEnd = moment().date(moment().date() + 7);
const disabledMonthsStart = moment().month(moment().month() - 2);
const disabledMonthsEnd = moment().month(moment().month() + 2);
const disabledYearsStart = moment().year(moment().year() - 2);
const disabledYearsEnd = moment().year(moment().year() + 2);
const disabledWeeksStart = moment().week(moment().week() - 5);
const disabledWeeksEnd = moment().week(moment().week() - 2);
const disabledMonthsStart = moment().month(moment().month() - 5);
const disabledMonthsEnd = moment().month(moment().month() - 1);
const disabledYearsStart = moment().year(moment().year() - 20);
const disabledYearsEnd = moment().year(moment().year() - 1);

const isDateDisabled = (target: DateType) => (
moment(target).isBetween(
Expand All @@ -299,6 +302,15 @@ export const CustomDisable = () => {
)
);

const isWeekDisabled = (target: DateType) => (
moment(target).isBetween(
disabledWeeksStart,
disabledWeeksEnd,
'week',
'[]',
)
);

const isMonthDisabled = (target: DateType) => (
moment(target).isBetween(
disabledMonthsStart,
Expand All @@ -321,7 +333,31 @@ export const CustomDisable = () => {
<CalendarConfigProvider methods={CalendarMethodsMoment}>
<div style={containerStyle}>
<Typography variant="h5" style={typoStyle}>
{`Disabled Dates: ${disabledDatesStart.format('YYYY-MM-DD')} ~ ${disabledDatesEnd.format('YYYY-MM-DD')}`}
{`(mode='day')
disabledMonthSwitch = true
disabledYearSwitch = true
disableOnNext = true
disableOnPrev = true`}
</Typography>
<DatePicker
value={valD}
onChange={onChangeD}
mode="day"
format="YYYY-MM-DD"
placeholder="YYYY-MM-DD"
disabledMonthSwitch
disabledYearSwitch
disableOnNext
disableOnPrev
/>
</div>
<div style={containerStyle}>
<Typography variant="h5" style={typoStyle}>
{`(mode='day') Disabled
Years: ${disabledYearsStart.format('YYYY')} ~ ${disabledYearsEnd.format('YYYY')}
Months: ${disabledMonthsStart.format('YYYY-MM')} ~ ${disabledMonthsEnd.format('YYYY-MM')}
Dates: ${disabledDatesStart.format('YYYY-MM-DD')} ~ ${disabledDatesEnd.format('YYYY-MM-DD')}
`}
</Typography>
<DatePicker
value={valD}
Expand All @@ -330,33 +366,67 @@ export const CustomDisable = () => {
format="YYYY-MM-DD"
placeholder="YYYY-MM-DD"
isDateDisabled={isDateDisabled}
isMonthDisabled={isMonthDisabled}
isYearDisabled={isYearDisabled}
/>
</div>
<div style={containerStyle}>
<Typography variant="h5" style={typoStyle}>
{`Disabled Months:
${disabledMonthsStart.format('YYYY-MM')} ~ ${disabledMonthsEnd.format('YYYY-MM')}`}
{`(mode='week') Disabled
Years: ${disabledYearsStart.format('YYYY')} ~ ${disabledYearsEnd.format('YYYY')}
Months: ${disabledMonthsStart.format('YYYY-MM')} ~ ${disabledMonthsEnd.format('YYYY-MM')}
Weeks: ${disabledWeeksStart.format(getDefaultModeFormat('week'))} ~ ${disabledWeeksEnd.format(getDefaultModeFormat('week'))}`}
</Typography>
<DatePicker
value={valW}
onChange={onChangeW}
mode="week"
format={getDefaultModeFormat('week')}
placeholder={getDefaultModeFormat('week')}
isYearDisabled={isYearDisabled}
isMonthDisabled={isMonthDisabled}
isWeekDisabled={isWeekDisabled}
/>
</div>
<div style={containerStyle}>
<Typography variant="h5" style={typoStyle}>
{`(mode='day') Disabled Dates:
${disabledDatesStart.format(getDefaultModeFormat('day'))} ~ ${disabledDatesEnd.format(getDefaultModeFormat('day'))}`}
</Typography>
<DatePicker
value={valD}
onChange={onChangeD}
mode="day"
format={getDefaultModeFormat('day')}
placeholder={getDefaultModeFormat('day')}
isDateDisabled={isDateDisabled}
/>
</div>
<div style={containerStyle}>
<Typography variant="h5" style={typoStyle}>
{`(mode='month') Disabled Months:
${disabledMonthsStart.format(getDefaultModeFormat('month'))} ~ ${disabledMonthsEnd.format(getDefaultModeFormat('month'))}`}
</Typography>
<DatePicker
value={valM}
onChange={onChangeM}
mode="month"
format="YYYY-MM"
placeholder="YYYY-MM"
format={getDefaultModeFormat('month')}
placeholder={getDefaultModeFormat('month')}
isMonthDisabled={isMonthDisabled}
/>
</div>
<div style={containerStyle}>
<Typography variant="h5" style={typoStyle}>
{`Disabled Years:
${disabledYearsStart.format('YYYY')} ~ ${disabledYearsEnd.format('YYYY')}`}
{`(mode='year') Disabled Years:
${disabledYearsStart.format(getDefaultModeFormat('year'))} ~ ${disabledYearsEnd.format(getDefaultModeFormat('year'))}`}
</Typography>
<DatePicker
value={valY}
onChange={onChangeY}
mode="year"
format="YYYY"
placeholder="YYYY"
format={getDefaultModeFormat('year')}
placeholder={getDefaultModeFormat('year')}
isYearDisabled={isYearDisabled}
/>
</div>
Expand Down
Loading

0 comments on commit 1d94f1f

Please sign in to comment.