Skip to content

Commit 9e9ff38

Browse files
committed
chore: small refactor
1 parent 07fa4e4 commit 9e9ff38

File tree

5 files changed

+89
-66
lines changed

5 files changed

+89
-66
lines changed

src/components/experimental/Calendar/Calendar.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import { format } from 'date-fns';
99
import ChevronLeftIcon from '../../../icons/arrows/ChevronLeftIcon';
1010
import ChevronRightIcon from '../../../icons/arrows/ChevronRightIcon';
1111
import * as Styled from './Calendar.styled';
12-
import { CalendarDayButton } from './CalendarDayButton';
13-
import { SelectionTypeContext, type SelectionType } from './Calendar.context';
12+
import { CalendarDayButton } from './components/CalendarDayButton';
13+
import { SelectionTypeContext, type SelectionType } from './context/Calendar.context';
1414

1515
export type Range = RdpRange;
1616
type DateFnsFormatOptions = Parameters<typeof format>[2];

src/components/experimental/Calendar/CalendarDayButton.tsx renamed to src/components/experimental/Calendar/components/CalendarDayButton.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, { useRef, useEffect, useContext } from 'react';
22
import { DayButton as RdpDayButton, getDefaultClassNames } from 'react-day-picker';
3-
import * as Styled from './Calendar.styled';
4-
import { SelectionTypeContext } from './Calendar.context';
3+
import * as Styled from '../Calendar.styled';
4+
import { SelectionTypeContext } from '../context/Calendar.context';
55

66
type CalendarDayButtonProps = React.ComponentProps<typeof RdpDayButton>;
77

src/components/experimental/Calendar/Calendar.context.ts renamed to src/components/experimental/Calendar/context/Calendar.context.ts

File renamed without changes.

src/components/experimental/DatePicker/DatePicker.tsx

Lines changed: 19 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { format as dfFormat, isValid as dfIsValid, parse as dfParse } from 'date-fns';
1+
import { format as dfFormat } from 'date-fns';
22
import React from 'react';
33
import styled from 'styled-components';
44

5-
import { CalendarDate, fromDate, getLocalTimeZone, type DateValue } from '@internationalized/date';
5+
import { type DateValue } from '@internationalized/date';
66

77
import type { Matcher, DateRange as RdpRange } from 'react-day-picker';
88
import { DropdownSelectIcon, DropupSelectIcon } from '../../../icons';
@@ -13,11 +13,20 @@ import { Button } from '../Field/Button';
1313
import type { FieldProps } from '../Field/Props';
1414
import { FocusTrap, Popover } from '../Popover/Popover';
1515
import { Chip, ChipRemoveButton, Chips } from './DatePicker.styled';
16+
import {
17+
calendarDateToDate,
18+
dateToCalendarDate,
19+
getSeparator,
20+
inBounds,
21+
multipleSummary,
22+
stripTime,
23+
toJSDate,
24+
tryParse,
25+
type Mode
26+
} from './util';
1627

1728
type DateRange = RdpRange | undefined;
1829

19-
type Mode = 'single' | 'multiple' | 'range';
20-
2130
type CommonProps = Pick<FieldProps, 'description' | 'errorMessage'> & {
2231
label?: string;
2332
placeholder?: string;
@@ -136,7 +145,10 @@ export function DatePicker(props: DatePickerProps): JSX.Element {
136145
const multipleValue = modeLocal === 'multiple' ? (props as MultipleProps).value : undefined;
137146
const rangeValue = modeLocal === 'range' ? (props as RangeProps).value : undefined;
138147

139-
const sepForRange = React.useMemo<string>(() => getSeparator(props), [modeLocal, (props as RangeProps).separator]);
148+
const sepForRange = React.useMemo<string>(
149+
() => getSeparator(modeLocal, (props as RangeProps).separator),
150+
[modeLocal, (props as RangeProps).separator]
151+
);
140152

141153
const neutralPlaceholder =
142154
placeholder ??
@@ -229,7 +241,7 @@ export function DatePicker(props: DatePickerProps): JSX.Element {
229241

230242
// input value
231243
const inputValue =
232-
mode === 'multiple'
244+
modeLocal === 'multiple'
233245
? multipleSummary(
234246
multipleValue ?? [],
235247
displayFormat,
@@ -238,7 +250,7 @@ export function DatePicker(props: DatePickerProps): JSX.Element {
238250
)
239251
: text;
240252

241-
const readOnly = mode === 'multiple' || !!legacyIsDisabled;
253+
const readOnly = modeLocal === 'multiple' || !!legacyIsDisabled;
242254

243255
// calendar handlers
244256
const handleSelectSingle = React.useCallback(
@@ -493,58 +505,3 @@ export function DatePicker(props: DatePickerProps): JSX.Element {
493505
</div>
494506
);
495507
}
496-
497-
/* ---------- helpers ---------- */
498-
499-
function tryParse(raw: string, fmt: string, locale?: Locale): Date | null {
500-
if (!raw?.trim()) return null;
501-
const p = dfParse(raw, fmt, new Date(), { locale });
502-
if (dfIsValid(p)) return p;
503-
const loose = new Date(raw);
504-
return dfIsValid(loose) ? loose : null;
505-
}
506-
507-
function inBounds(d: Date, min?: Date, max?: Date) {
508-
const t = stripTime(d).getTime();
509-
return (min ? t >= stripTime(min).getTime() : true) && (max ? t <= stripTime(max).getTime() : true);
510-
}
511-
512-
function stripTime(d: Date) {
513-
const x = new Date(d);
514-
x.setHours(0, 0, 0, 0);
515-
return x;
516-
}
517-
518-
function multipleSummary(dates: Date[], fmt: string, locale?: Locale, strategy: 'firstDate' | 'count' = 'count') {
519-
const count = dates.length;
520-
if (count === 0) return '';
521-
if (strategy === 'firstDate') {
522-
return dfFormat(dates[0], fmt, { locale }) + (count > 1 ? ` +${count - 1}` : '');
523-
}
524-
return count === 1 ? dfFormat(dates[0], fmt, { locale }) : `${count} dates selected`;
525-
}
526-
527-
function getSeparator(props: DatePickerProps) {
528-
return (props.mode === 'range' ? props.separator : undefined) ?? ' – ';
529-
}
530-
531-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
532-
function toJSDate(d: any): Date | undefined {
533-
if (!d) return undefined;
534-
if (d instanceof Date) return d;
535-
if (typeof d === 'object' && 'year' in d && 'month' in d && 'day' in d) {
536-
return new Date(d.year as number, (d.month as number) - 1, d.day as number);
537-
}
538-
return undefined;
539-
}
540-
541-
function dateToCalendarDate(d: Date): CalendarDate {
542-
const zdt = fromDate(d, getLocalTimeZone());
543-
return new CalendarDate(zdt.year, zdt.month, zdt.day);
544-
}
545-
546-
function calendarDateToDate(dv: DateValue): Date {
547-
// DateValue has year/month/day in Gregorian by default
548-
// Construct a JS Date in local time at midnight.
549-
return new Date(dv.year, dv.month - 1, dv.day);
550-
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// util/index.ts
2+
import { format as dfFormat, isValid as dfIsValid, parse as dfParse } from 'date-fns';
3+
import { CalendarDate, fromDate, getLocalTimeZone, type DateValue } from '@internationalized/date';
4+
5+
export type Mode = 'single' | 'multiple' | 'range';
6+
7+
export function tryParse(raw: string, fmt: string, locale?: Locale): Date | null {
8+
if (!raw?.trim()) return null;
9+
const p = dfParse(raw, fmt, new Date(), { locale });
10+
if (dfIsValid(p)) return p;
11+
const loose = new Date(raw);
12+
return dfIsValid(loose) ? loose : null;
13+
}
14+
15+
export function inBounds(d: Date, min?: Date, max?: Date): boolean {
16+
const t = stripTime(d).getTime();
17+
return (min ? t >= stripTime(min).getTime() : true) && (max ? t <= stripTime(max).getTime() : true);
18+
}
19+
20+
export function stripTime(d: Date): Date {
21+
const x = new Date(d);
22+
x.setHours(0, 0, 0, 0);
23+
return x;
24+
}
25+
26+
export function multipleSummary(
27+
dates: Date[],
28+
fmt: string,
29+
locale?: Locale,
30+
strategy: 'firstDate' | 'count' = 'count'
31+
): string {
32+
const count = dates.length;
33+
if (count === 0) return '';
34+
if (strategy === 'firstDate') {
35+
return dfFormat(dates[0], fmt, { locale }) + (count > 1 ? ` +${count - 1}` : '');
36+
}
37+
return count === 1 ? dfFormat(dates[0], fmt, { locale }) : `${count} dates selected`;
38+
}
39+
40+
export function getSeparator(mode?: Mode, separator?: string): string {
41+
return (mode === 'range' ? separator : undefined) ?? ' – ';
42+
}
43+
44+
type CalendarLike = { year: number; month: number; day: number };
45+
function isCalendarLike(v: unknown): v is CalendarLike {
46+
return !!v && typeof v === 'object' && 'year' in v && 'month' in v && 'day' in v;
47+
}
48+
49+
export function toJSDate(d: unknown): Date | undefined {
50+
if (!d) return undefined;
51+
if (d instanceof Date) return d;
52+
if (isCalendarLike(d)) {
53+
return new Date(d.year, d.month - 1, d.day);
54+
}
55+
return undefined;
56+
}
57+
58+
export function dateToCalendarDate(d: Date): CalendarDate {
59+
const zdt = fromDate(d, getLocalTimeZone());
60+
return new CalendarDate(zdt.year, zdt.month, zdt.day);
61+
}
62+
63+
export function calendarDateToDate(dv: DateValue): Date {
64+
// DateValue has year/month/day; create a local JS Date at midnight.
65+
return new Date(dv.year, dv.month - 1, dv.day);
66+
}

0 commit comments

Comments
 (0)