feat: add customization calendar widget

This commit is contained in:
Sergey Krylov 2024-04-29 11:41:26 +03:00
parent 29c1bce814
commit c950bc6757
7 changed files with 71 additions and 25 deletions

View File

@ -1 +1,2 @@
export type { CalendarWidgetProps } from './ui/CalendarWidget.ui';
export { default as CalendarWidget } from './ui/CalendarWidget.ui'; export { default as CalendarWidget } from './ui/CalendarWidget.ui';

View File

@ -1,10 +1,15 @@
import type { DataDateString } from 'shared/types/date';
import classes from './CalendarWidget.module.css'; import classes from './CalendarWidget.module.css';
import { CalendarWidgetProvider } from './CalendarWidgetContext'; import { CalendarWidgetProvider } from './CalendarWidgetContext';
import { DateWrapper } from './components/DateWrapper'; import { DateWrapper } from './components/DateWrapper';
import { DayOfWeekRow } from './components/DayOfWeekRow'; import { DayOfWeekRow } from './components/DayOfWeekRow';
import { HeaderRow } from './components/HeaderRow'; import { HeaderRow } from './components/HeaderRow';
import type { FC } from 'react'; import type { FC, HTMLAttributes } from 'react';
type DateCellPropsType = HTMLAttributes<HTMLDivElement>;
type DateCellPropsFuncType = (params: { date: Date; dataDate: DataDateString }) => DateCellPropsType;
export type CalendarWidgetProps = { export type CalendarWidgetProps = {
maxDate?: Date; maxDate?: Date;
@ -13,6 +18,8 @@ export type CalendarWidgetProps = {
locale?: 'en' | 'ru'; locale?: 'en' | 'ru';
highlightToday?: boolean; highlightToday?: boolean;
highlightValue?: boolean; highlightValue?: boolean;
DateCellProps?: DateCellPropsFuncType | DateCellPropsType;
}; };
const CalendarWidget: FC<CalendarWidgetProps> = (props) => ( const CalendarWidget: FC<CalendarWidgetProps> = (props) => (
<CalendarWidgetProvider {...props}> <CalendarWidgetProvider {...props}>

View File

@ -4,11 +4,16 @@ import React, { createContext, useMemo, useState } from 'react';
import type { CalendarWidgetProps } from './CalendarWidget.ui'; import type { CalendarWidgetProps } from './CalendarWidget.ui';
import type { FC, PropsWithChildren } from 'react'; import type { FC, PropsWithChildren } from 'react';
type CalendarWidgetContextValue = Required<CalendarWidgetProps> & { type CalendarWidgetContextValue = Omit<CalendarWidgetProps, 'locale' | 'maxDate' | 'minDate' | 'value'> & {
targetYear: number; targetYear: number;
targetMonthIndex: number; targetMonthIndex: number;
targetDate: Date; targetDate: Date;
setTargetDate: React.Dispatch<React.SetStateAction<Date>>; setTargetDate: React.Dispatch<React.SetStateAction<Date>>;
maxDate: NonNullable<CalendarWidgetProps['maxDate']>;
minDate: NonNullable<CalendarWidgetProps['minDate']>;
locale: NonNullable<CalendarWidgetProps['locale']>;
value: NonNullable<CalendarWidgetProps['value']>;
}; };
export const CalendarWidgetContext = createContext({} as CalendarWidgetContextValue); export const CalendarWidgetContext = createContext({} as CalendarWidgetContextValue);
@ -21,8 +26,6 @@ export const CalendarWidgetProvider: FC<CalendarWidgetProviderProps> = (props) =
minDate = new Date(1990, 0, 1), minDate = new Date(1990, 0, 1),
locale = 'ru', locale = 'ru',
value = new Date(), value = new Date(),
highlightToday = false,
highlightValue = false,
...restProps ...restProps
} = props; } = props;
@ -37,10 +40,8 @@ export const CalendarWidgetProvider: FC<CalendarWidgetProviderProps> = (props) =
maxDate, maxDate,
locale, locale,
value, value,
highlightToday,
highlightValue,
...restProps, ...restProps,
}), [highlightValue, targetDate, minDate, maxDate, locale, value, highlightToday, restProps]); }), [targetDate, minDate, maxDate, locale, value, restProps]);
return ( return (
<CalendarWidgetContext.Provider value={contextValue}> <CalendarWidgetContext.Provider value={contextValue}>

View File

@ -32,6 +32,6 @@ describe('Base Render', () => {
setup(new Date(2024, 3, 15)); setup(new Date(2024, 3, 15));
const dateCellElem = screen.getByTestId('date-cell'); const dateCellElem = screen.getByTestId('date-cell');
expect(dateCellElem).toHaveAttribute('data-date', '4/15/2024'); expect(dateCellElem).toHaveAttribute('data-date', '04/15/2024');
}); });
}); });

View File

@ -1,47 +1,77 @@
import classnames from 'classnames'; import classnames from 'classnames';
import { useContext } from 'react'; import React, { useContext, useMemo, useRef } from 'react';
import { getDataDateString } from 'shared/utils/date';
import { CalendarWidgetContext } from '../../../CalendarWidgetContext'; import { CalendarWidgetContext } from '../../../CalendarWidgetContext';
import classes from './DateCell.style.module.css'; import classes from './DateCell.style.module.css';
import type { FC } from 'react'; import type { FC, MouseEventHandler, KeyboardEventHandler } from 'react';
type DateCellProps = { type DateCellProps = React.HTMLAttributes<HTMLDivElement> & {
date: Date; date: Date;
}; };
const DateCell: FC<DateCellProps> = (props) => { const DateCell: FC<DateCellProps> = (props) => {
const { const {
date, date,
} = props; } = props;
const format = 'en-En'; const rootDateCellRef = useRef<HTMLDivElement>(null);
const { const {
targetMonthIndex, targetMonthIndex,
value, value,
highlightToday, highlightToday,
highlightValue, highlightValue,
DateCellProps = {},
} = useContext(CalendarWidgetContext); } = useContext(CalendarWidgetContext);
const todayDate = new Date(); const todayDate = new Date();
const dataDate = new Intl.DateTimeFormat(format).format(date); const dataDate = getDataDateString(date);
const dataToday = new Intl.DateTimeFormat(format).format(todayDate); const dataToday = getDataDateString(todayDate);
const dataValue = new Intl.DateTimeFormat(format).format(value); const dataValue = getDataDateString(value);
const isOutMonthRange = date.getMonth() !== targetMonthIndex; const isOutMonthRange = date.getMonth() !== targetMonthIndex;
const {
className,
onClick,
onKeyPress,
...restProps
} = useMemo(() => {
if (typeof DateCellProps === 'function') {
return DateCellProps({ date, dataDate });
}
return DateCellProps;
// @ts-ignore
}, [DateCellProps, dataDate, date]);
const onClickHandler: MouseEventHandler<HTMLDivElement> = (event) => {
onClick?.({ ...event, target: rootDateCellRef.current as EventTarget });
};
const onKeyPressHandler: KeyboardEventHandler<HTMLDivElement> = (event) => {
onKeyPress?.(event);
};
return ( return (
<div <div
key={dataDate} key={dataDate}
className={classes.dateCell} ref={rootDateCellRef}
className={classnames(classes.dateCell, className)}
data-date={dataDate} data-date={dataDate}
data-testid="date-cell" data-testid="date-cell"
role="button"
tabIndex={0}
onClick={onClickHandler}
onKeyPress={onKeyPressHandler}
{...restProps}
> >
<span <span
className={classnames( className={classnames(
{ {
[classes.today as string]: highlightToday && dataDate === dataToday, [classes.today as string]: Boolean(highlightToday) && dataDate === dataToday,
[classes.outMothRange as string]: isOutMonthRange, [classes.outMothRange as string]: isOutMonthRange,
[classes.valueDate as string]: highlightValue && dataDate === dataValue, [classes.valueDate as string]: Boolean(highlightValue) && dataDate === dataValue,
}, },
)} )}
> >

View File

@ -33,7 +33,7 @@ describe('Test Date Row', () => {
setupWeek(); setupWeek();
const dateRowElem = screen.getByTestId('date-row'); const dateRowElem = screen.getByTestId('date-row');
expect(dateRowElem).toHaveAttribute('data-row', '4/15/2024-4/21/2024'); expect(dateRowElem).toHaveAttribute('data-row', '04/15/2024-04/21/2024');
}); });
test('Correct style class', () => { test('Correct style class', () => {

View File

@ -1,3 +1,5 @@
import { getDataDateString } from 'shared/utils/date';
import { DateCell } from '../../DateCell'; import { DateCell } from '../../DateCell';
import classes from './DateRow.style.module.css'; import classes from './DateRow.style.module.css';
@ -12,16 +14,21 @@ const DateRow: FC<DateRowType> = (props) => {
cells, cells,
} = props; } = props;
const format = 'en-En'; const firstDate = cells[0];
const lastDate = cells[cells.length - 1];
const firstDate = new Intl.DateTimeFormat(format).format(cells[0]); if (!firstDate || !lastDate) {
const lastDate = new Intl.DateTimeFormat(format).format(cells[cells.length - 1]); return null;
}
const firstDataDate = getDataDateString(firstDate);
const lastDataDate = getDataDateString(lastDate);
return ( return (
<div <div
key={`${firstDate}-${lastDate}`} key={`${firstDataDate}-${lastDataDate}`}
className={classes.dateRow} className={classes.dateRow}
data-row={`${firstDate}-${lastDate}`} data-row={`${firstDataDate}-${lastDataDate}`}
data-testid="date-row" data-testid="date-row"
> >
{cells.map((item) => ( {cells.map((item) => (