diff --git a/package-lock.json b/package-lock.json index 04a80b3..657637e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -68,7 +68,7 @@ "react": "^18.2.0", "react-colorful": "^5.6.1", "react-content-loader": "^6.2.1", - "react-day-picker": "^8.10.1", + "react-day-picker": "^9.13.0", "react-dom": "^18.2.0", "react-hook-form": "^7.43.9", "react-hot-toast": "^2.4.0", @@ -2140,6 +2140,13 @@ "node": ">=0.1.90" } }, + "node_modules/@date-fns/tz": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@date-fns/tz/-/tz-1.4.1.tgz", + "integrity": "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==", + "dev": true, + "license": "MIT" + }, "node_modules/@discoveryjs/json-ext": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", @@ -8291,6 +8298,13 @@ "url": "https://opencollective.com/date-fns" } }, + "node_modules/date-fns-jalali": { + "version": "4.1.0-0", + "resolved": "https://registry.npmjs.org/date-fns-jalali/-/date-fns-jalali-4.1.0-0.tgz", + "integrity": "sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==", + "dev": true, + "license": "MIT" + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -15986,17 +16000,36 @@ } }, "node_modules/react-day-picker": { - "version": "8.10.1", - "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-8.10.1.tgz", - "integrity": "sha512-TMx7fNbhLk15eqcMt+7Z7S2KF7mfTId/XJDjKE8f+IUcFn0l08/kI4FiYTL/0yuOLmEcbR4Fwe3GJf/NiiMnPA==", + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-9.13.0.tgz", + "integrity": "sha512-euzj5Hlq+lOHqI53NiuNhCP8HWgsPf/bBAVijR50hNaY1XwjKjShAnIe8jm8RD2W9IJUvihDIZ+KrmqfFzNhFQ==", "dev": true, + "license": "MIT", + "dependencies": { + "@date-fns/tz": "^1.4.1", + "date-fns": "^4.1.0", + "date-fns-jalali": "^4.1.0-0" + }, + "engines": { + "node": ">=18" + }, "funding": { "type": "individual", "url": "https://github.com/sponsors/gpbl" }, "peerDependencies": { - "date-fns": "^2.28.0 || ^3.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "react": ">=16.8.0" + } + }, + "node_modules/react-day-picker/node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" } }, "node_modules/react-docgen": { diff --git a/package.json b/package.json index 64859d8..7364c7b 100644 --- a/package.json +++ b/package.json @@ -109,7 +109,7 @@ "react": "^18.2.0", "react-colorful": "^5.6.1", "react-content-loader": "^6.2.1", - "react-day-picker": "^8.10.1", + "react-day-picker": "^9.13.0", "react-dom": "^18.2.0", "react-hook-form": "^7.43.9", "react-hot-toast": "^2.4.0", diff --git a/src/components/common/DatePickerPopup/index.tsx b/src/components/common/DatePickerPopup/index.tsx index 3fabd57..80e4b8b 100644 --- a/src/components/common/DatePickerPopup/index.tsx +++ b/src/components/common/DatePickerPopup/index.tsx @@ -2,27 +2,25 @@ import 'react-day-picker/dist/style.css'; import { noop, Nullable } from '@appello/common'; import { useClickAway } from '@appello/web-kit'; -import clsx from 'clsx'; import { eachMonthOfInterval, endOfYear, format, - isWeekend, setMonth, setYear, startOfDay, + startOfMonth, startOfYear, } from 'date-fns'; import React, { FC, useCallback, useEffect, useMemo, useState } from 'react'; import { - ActiveModifiers, CaptionLabelProps as ReactDayCaptionLabelProps, DateRange, - DayClickEventHandler, + type DayEventHandler, DayPicker, isDateRange, Matcher, - SelectRangeEventHandler, + type PropsRange, useDayPicker, } from 'react-day-picker'; import { createPortal } from 'react-dom'; @@ -37,18 +35,13 @@ import { formatWeekdayName } from './utils'; export interface DatePickerDefaultProps { mode?: undefined; value: Date | null; - onChange: DayClickEventHandler; + onChange: DayEventHandler; } export interface DatePickerRangeProps { mode: 'range'; value: Nullable; - onChange: ( - range: Nullable, - selectedDay: Date, - activeModifiers: ActiveModifiers, - e: React.MouseEvent, - ) => void; + onChange: (range: Nullable) => void; } export interface DatePickerBaseProps { @@ -84,16 +77,7 @@ export const DatePickerPopup: React.FC = props => { setMonth(month); }, []); - const renderWeekdayName = useCallback((date: Date) => { - const name = formatWeekdayName(date); - return ( - - {name} - - ); - }, []); - - const handleDayClick: DayClickEventHandler = useCallback( + const handleDayClick: DayEventHandler = useCallback( (day, ...args) => { if (mode === undefined) { onChange(startOfDay(day), ...args); @@ -103,10 +87,10 @@ export const DatePickerPopup: React.FC = props => { [mode, onChange, onClose], ); - const handleRangeSelect: SelectRangeEventHandler = useCallback( - (range, ...args) => { + const handleRangeSelect: PropsRange['onSelect'] = useCallback( + range => { if (mode === 'range') { - onChange(range ?? null, ...args); + onChange(range ?? null); } }, [mode, onChange], @@ -200,7 +184,7 @@ export const DatePickerPopup: React.FC = props => { onSelect: handleRangeSelect, } : { - mode: 'default' as const, + mode: 'single' as const, selected: value ?? undefined, onDayClick: handleDayClick, }; @@ -215,13 +199,15 @@ export const DatePickerPopup: React.FC = props => { }} disabled={disabledDate} formatters={{ - formatWeekdayName: renderWeekdayName, + formatWeekdayName: date => formatWeekdayName(date), }} modifiers={{ - weekend: isWeekend, + weekend: { + dayOfWeek: [0, 6], + }, }} modifiersClassNames={{ - weekend: 'rdp-day--weekend', + weekend: 'rdp-weekend', today: 'rdp-day--today', }} month={month} @@ -236,18 +222,22 @@ interface CaptionLabelProps extends ReactDayCaptionLabelProps { yearsLength?: number; } -const CaptionLabel: FC = ({ displayMonth, yearsLength = 100 }) => { - const { onMonthChange, month } = useDayPicker(); +const CaptionLabel: FC = ({ yearsLength = 100 }) => { + const { + dayPickerProps: { onMonthChange, month }, + } = useDayPicker(); - const monthLabel = useMemo(() => format(displayMonth, 'MMMM'), [displayMonth]); - const monthValue = useMemo(() => `${displayMonth.getMonth()}`, [displayMonth]); - const yearValue = useMemo(() => format(displayMonth, 'yyyy'), [displayMonth]); + const currentMonth = useMemo(() => month ?? new Date(), [month]); + const base = startOfMonth(currentMonth); + const monthLabel = useMemo(() => format(currentMonth, 'MMMM'), [currentMonth]); + const monthValue = useMemo(() => String(currentMonth.getMonth()), [currentMonth]); // "0".."11" + + const yearValue = useMemo(() => String(currentMonth.getFullYear()), [currentMonth]); const yearsOptions = Array.from({ length: yearsLength }, (_, index) => { const year = new Date().getFullYear() + 5 - index; return { label: year.toString(), value: year.toString() }; }); - const monthsOptions = eachMonthOfInterval({ start: startOfYear(new Date()), end: endOfYear(new Date()), @@ -260,7 +250,7 @@ const CaptionLabel: FC = ({ displayMonth, yearsLength = 100 } month && onMonthChange?.(setMonth(month, Number(e.target.value)))} + onChange={e => onMonthChange?.(setMonth(base, Number(e.target.value)))} >

{monthLabel}

@@ -270,7 +260,7 @@ const CaptionLabel: FC = ({ displayMonth, yearsLength = 100 } month && onMonthChange?.(setYear(month, Number(e.target.value)))} + onChange={e => onMonthChange?.(setYear(base, Number(e.target.value)))} >

{yearValue}

diff --git a/src/components/common/DatePickerPopup/styles.module.scss b/src/components/common/DatePickerPopup/styles.module.scss index 1791864..1350ee4 100644 --- a/src/components/common/DatePickerPopup/styles.module.scss +++ b/src/components/common/DatePickerPopup/styles.module.scss @@ -1,9 +1,3 @@ -.weekday { - &--weekend { - color: hsl(var(--red-color)); - } -} - .calendar-wrapper { position: fixed; z-index: 999; @@ -26,7 +20,7 @@ } } -.container:global(.rdp) { +.container:global(.rdp-root) { border: 1px solid hsl(var(--gray-5-color)); box-shadow: var(--shadow-4); background-color: hsl(var(--white-color)); @@ -34,66 +28,125 @@ margin-block: 0.43rem; border-radius: 0.5rem; padding: 1rem; - - --rdp-cell-size: 30px; + --rdp-day-width: 30px; + --rdp-day-height: 30px; + --rdp-nav-height: 30px; + --rdp-nav_button-width: 30px; + --rdp-nav_button-height: 30px; + --rdp-day_button-width: 30px; + --rdp-day_button-height: 30px; --rdp-accent-color: hsl(var(--accent-color)); --rdp-background-color: hsl(var(--accent-color) / 0.1); + --rdp-range_start-background: hsl(var(--accent-color)); + --rdp-range_end-background: hsl(var(--accent-color)); + --rdp-today-color: hsl(var(--accent-color)); } .container { :global { - .rdp-caption_label { - font-size: var(--p3-font-size); - line-height: var(--p3-line-height); - font-weight: 600; - } - - .rdp-nav_button { + .rdp-button_previous, + .rdp-button_next { border-radius: 8px; > svg { width: 12px; height: 12px; + fill: black; } } - .rdp-head_cell { - font-size: var(--p4-font-size); - line-height: var(--p4-line-height); - font-weight: 400; - text-transform: none; - height: 38px; - } + .rdp { + &-month_caption { + align-items: center; + } - .rdp-day { - font-size: var(--p5-font-size); - line-height: var(--p5-line-height); - font-weight: 600; - color: hsl(var(--gray-1-color)); - border-radius: 8px; - border-inline: 1px solid transparent; - margin-block: 1px; + &-caption_label { + font-size: var(--p3-font-size); + line-height: var(--p3-line-height); + font-weight: 600; + } + + &-day { + font-size: var(--p5-font-size); + line-height: var(--p5-line-height); + font-weight: 600; + color: hsl(var(--gray-1-color)); + border-radius: 8px; + border-inline: 1px solid transparent; + margin-block: 1px; + + &--today { + color: hsl(var(--accent-color)); + } + + &:not(.rdp-disabled):not(.rdp-range_start):not(.rdp-range_end):not(.rdp-range_middle):not( + .rdp-selected + ):hover { + cursor: pointer; + background-color: hsl(var(--accent-color) / 0.1); + border-radius: 8px; + } + + &_button:disabled { + cursor: not-allowed; + } + } + + &-disabled { + cursor: not-allowed; + } - &--today { - color: hsl(var(--accent-color)); + &-weekday { + opacity: 1; + font-size: var(--p4-font-size); + line-height: var(--p4-line-height); + font-weight: 400; + text-transform: none; + height: 38px; + + &:first-child { + color: hsl(var(--red-color)); + } + + &:last-child { + color: hsl(var(--red-color)); + } } - &--weekend { + &-weekend { color: hsl(var(--red-color)); } - &_selected { + &-selected { color: hsl(var(--white-color)); + background-color: hsl(var(--accent-color)); + border-radius: 8px; } &_disabled { cursor: not-allowed; } - &_range_middle { - border-radius: 0; - background-color: hsl(var(--accent-color) / 0.1); - color: hsl(var(--accent-color)); + &-range { + &_start:not(.rdp-range_end) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + + &_start:has { + border-radius: 8px; + } + + &_middle { + border-radius: 0; + background-color: hsl(var(--accent-color) / 0.1); + color: hsl(var(--accent-color)); + } + + &_end:not(.rdp-range_start) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } } } }