diff --git a/.changeset/serious-roses-roll.md b/.changeset/serious-roses-roll.md new file mode 100644 index 000000000..ceb92bd96 --- /dev/null +++ b/.changeset/serious-roses-roll.md @@ -0,0 +1,5 @@ +--- +"ingred-ui": patch +--- + +refactor datepicker and daterangepicker diff --git a/src/components/Calendar/Calendar/internal/Day/Day.tsx b/src/components/Calendar/Calendar/internal/Day/Day.tsx index 7a0b7c1ec..c4dff325a 100644 --- a/src/components/Calendar/Calendar/internal/Day/Day.tsx +++ b/src/components/Calendar/Calendar/internal/Day/Day.tsx @@ -10,15 +10,15 @@ type Props = { }; export const Day: FC = memo( - ({ selected, value, onClickDate, children }) => ( - { - onClickDate?.(value); - }} - > - {children} - - ), + ({ selected, value, onClickDate, children }) => { + const handleClick = () => { + onClickDate?.(value); + }; + + return ( + + {children} + + ); + }, ); diff --git a/src/components/Calendar/CalendarRange/CalendarRange.tsx b/src/components/Calendar/CalendarRange/CalendarRange.tsx index 4b88e1a9d..50ce3b00c 100644 --- a/src/components/Calendar/CalendarRange/CalendarRange.tsx +++ b/src/components/Calendar/CalendarRange/CalendarRange.tsx @@ -14,6 +14,8 @@ import { import { useScroll } from "../hooks/useScroll"; import { getDayState } from "./utils"; import { Action, Actions } from "../internal/Actions"; +import { ClickState, ClickStateType } from "./constants"; +import { DateRange } from "./types"; export type CalendarRangeProps = React.HTMLAttributes & { startDate: Dayjs; @@ -22,19 +24,13 @@ export type CalendarRangeProps = React.HTMLAttributes & { /** * 親コンポーネントで calendar を任意のタイミングで閉じたい場合に使用する */ - onClose?: (clickState: "start" | "end") => void; + onClose?: (clickState: ClickStateType) => void; /** * 閉じるボタンを押したときの振る舞い * この関数が渡されてないときは、閉じるボタンが表示されない */ onClickCloseButton?: () => void; - onDatesChange: ({ - startDate, - endDate, - }: { - startDate: Dayjs; - endDate: Dayjs; - }) => void; + onDatesChange: ({ startDate, endDate }: DateRange) => void; }; /** @@ -57,25 +53,27 @@ export const CalendarRange = forwardRef( ) { const scrollRef = useRef(null); const { monthList } = useScroll(startDate ?? dayjs(), scrollRef); - const [clickState, setClickState] = useState<"start" | "end">("start"); + const [clickState, setClickState] = useState( + ClickState.START, + ); const handleDateChange = useCallback( (value: Dayjs) => { onClose && onClose(clickState); switch (clickState) { - case "start": + case ClickState.START: onDatesChange?.({ startDate: value, endDate, }); - setClickState("end"); + setClickState(ClickState.END); break; - case "end": + case ClickState.END: onDatesChange?.({ startDate, endDate: value, }); - setClickState("start"); + setClickState(ClickState.START); break; // Maybe, I will add other state. default: diff --git a/src/components/Calendar/CalendarRange/constants.ts b/src/components/Calendar/CalendarRange/constants.ts new file mode 100644 index 000000000..01f2c6a9b --- /dev/null +++ b/src/components/Calendar/CalendarRange/constants.ts @@ -0,0 +1,15 @@ +export const DayState = { + NONE: "none", + START: "start", + END: "end", + BETWEEN: "between", +} as const; + +export type DayStateType = (typeof DayState)[keyof typeof DayState]; + +export const ClickState = { + START: "start", + END: "end", +} as const; + +export type ClickStateType = (typeof ClickState)[keyof typeof ClickState]; diff --git a/src/components/Calendar/CalendarRange/internal/Day/Day.tsx b/src/components/Calendar/CalendarRange/internal/Day/Day.tsx index 48c20d84a..0da9af0b5 100644 --- a/src/components/Calendar/CalendarRange/internal/Day/Day.tsx +++ b/src/components/Calendar/CalendarRange/internal/Day/Day.tsx @@ -1,9 +1,10 @@ import { Dayjs } from "dayjs"; import React, { FC, memo, ReactNode } from "react"; import { DayStyle, DayBetween, DayEnd, DayStart } from "./styled"; +import { DayState, DayStateType } from "../../constants"; type Props = { - state: "start" | "end" | "between" | "none"; + state: DayStateType; value: Dayjs; onClickDate?: (value: Dayjs) => void; children: ReactNode; @@ -16,11 +17,11 @@ export const Day: FC = memo( }; switch (state) { - case "start": + case DayState.START: return {children}; - case "end": + case DayState.END: return {children}; - case "between": + case DayState.BETWEEN: return {children}; default: return {children}; diff --git a/src/components/Calendar/CalendarRange/types.ts b/src/components/Calendar/CalendarRange/types.ts index 9568f2e2b..5eff507a4 100644 --- a/src/components/Calendar/CalendarRange/types.ts +++ b/src/components/Calendar/CalendarRange/types.ts @@ -4,5 +4,3 @@ export type DateRange = { startDate: Dayjs; endDate: Dayjs; }; - -export type ClickState = "start" | "end"; diff --git a/src/components/Calendar/CalendarRange/utils.ts b/src/components/Calendar/CalendarRange/utils.ts index 09dab9e02..3c10c0472 100644 --- a/src/components/Calendar/CalendarRange/utils.ts +++ b/src/components/Calendar/CalendarRange/utils.ts @@ -1,4 +1,5 @@ import dayjs, { Dayjs } from "dayjs"; +import { DayState } from "./constants"; const isStart = (startDate: Dayjs | null, month: Dayjs, day: number) => startDate?.format("YYYY-MM-DD") === @@ -32,14 +33,14 @@ export const getDayState = ( month: Dayjs, day: number, ) => { - if (isStart(startDate, month, day)) { - return "start"; + switch (true) { + case isStart(startDate, month, day): + return DayState.START; + case isEnd(endDate, month, day): + return DayState.END; + case isBetween(startDate, endDate, month, day): + return DayState.BETWEEN; + default: + return DayState.NONE; } - if (isEnd(endDate, month, day)) { - return "end"; - } - if (isBetween(startDate, endDate, month, day)) { - return "between"; - } - return "none"; }; diff --git a/src/components/Calendar/internal/Actions/Actions.tsx b/src/components/Calendar/internal/Actions/Actions.tsx index d09a117fb..fadc62494 100644 --- a/src/components/Calendar/internal/Actions/Actions.tsx +++ b/src/components/Calendar/internal/Actions/Actions.tsx @@ -8,25 +8,25 @@ export type Action = { onClick: () => void; }; -// CalendarRange が実装されたら移動する export const Actions = memo(({ actions }: { actions?: Action[] }) => { const theme = useTheme(); const [clickedAction, setClickedAction] = useState(null); + const handleClick = (action: Action) => () => { + setClickedAction(action.text); + action.onClick(); + }; + return actions ? ( - {actions.map(({ text, onClick }, i) => ( + {actions.map((action, i) => ( { - setClickedAction(text); - onClick(); - }} + key={`${action.text}-${i.toString()}`} + clicked={clickedAction === action.text} + onClick={handleClick(action)} > - {text} + {action.text} ))} diff --git a/src/components/Calendar/styled.ts b/src/components/Calendar/styled.ts index 255f44ef9..06ecd978a 100644 --- a/src/components/Calendar/styled.ts +++ b/src/components/Calendar/styled.ts @@ -1,7 +1,6 @@ import styled from "styled-components"; import { Flex } from ".."; -// TODO: CalendarRange を実装時に移動する export const Container = styled.div` padding: 0 ${({ theme }) => theme.spacing * 3}px; position: relative; diff --git a/src/components/DateField/DateField/DateField.tsx b/src/components/DateField/DateField/DateField.tsx index 316c94069..0563131a2 100644 --- a/src/components/DateField/DateField/DateField.tsx +++ b/src/components/DateField/DateField/DateField.tsx @@ -1,7 +1,7 @@ import React, { forwardRef, memo } from "react"; import { Icon, Input } from "../.."; import { useDateField } from "../useDateField"; -import { useMergeRefs } from "../utils"; +import { useMergeRefs } from "../../../hooks/useMergeRefs"; import { CalendarIcon, InputContainer } from "../styled"; import { DateFieldProps } from "../types"; diff --git a/src/components/DateField/DateRangeField/DateRangeField.tsx b/src/components/DateField/DateRangeField/DateRangeField.tsx index 00e0a433b..ed85b3f20 100644 --- a/src/components/DateField/DateRangeField/DateRangeField.tsx +++ b/src/components/DateField/DateRangeField/DateRangeField.tsx @@ -1,9 +1,13 @@ import React, { forwardRef, memo, useMemo } from "react"; import { Icon, Input } from "../.."; -import { useMergeRefs } from "../utils"; +import { useMergeRefs } from "../../../hooks/useMergeRefs"; import { CalendarIcon, InputContainer } from "./styled"; import { Dayjs } from "dayjs"; import { useDateField } from "../useDateField"; +import { + ClickState, + ClickStateType, +} from "../../Calendar/CalendarRange/constants"; type Range = { startDate: Dayjs; @@ -24,9 +28,9 @@ const DateRangeField = forwardRef( ) { const width = useMemo(() => format.length * 12, [format]); - const handleChange = (t: "start" | "end") => (date: Dayjs) => { + const handleChange = (t: ClickStateType) => (date: Dayjs) => { const { startDate, endDate } = rest.date; - if (t === "start") { + if (t === ClickState.START) { rest.onDatesChange?.({ endDate, startDate: date, @@ -43,14 +47,14 @@ const DateRangeField = forwardRef( ...rest, format, date: rest.date.startDate, - onDateChange: handleChange("start"), + onDateChange: handleChange(ClickState.START), }); const { ref: endInputRef, ...endProps } = useDateField({ ...rest, format, date: rest.date.endDate, - onDateChange: handleChange("end"), + onDateChange: handleChange(ClickState.END), }); const startRef = useMergeRefs(propRef, startInputRef); diff --git a/src/components/DateField/utils.ts b/src/components/DateField/utils.ts index be0f892a2..1bfb3996d 100644 --- a/src/components/DateField/utils.ts +++ b/src/components/DateField/utils.ts @@ -1,5 +1,3 @@ -import { useMemo } from "react"; - type Sections = { start: number; end: number; @@ -57,31 +55,5 @@ export const getSections = (formattedDate?: string | null) => { /** * 開始位置と終了位置と値を持つセクションをフォーマットされた日付に変換する */ -export const formatString = (sectionsWithCharactor: Sections[]) => - sectionsWithCharactor.map((section) => section.value).join(""); - -type ReactRef = - | React.RefCallback - | React.MutableRefObject - | React.ForwardedRef - | string - | null - | undefined; - -// from: https://github.com/voyagegroup/ingred-ui/blob/master/src/hooks/useMergeRefs.ts -export function useMergeRefs(...refs: ReactRef[]): React.Ref { - return useMemo(() => { - if (refs.every((ref) => ref === null)) { - return null; - } - return (refValue: T) => { - for (const ref of refs) { - if (typeof ref === "function") { - ref(refValue); - } else if (ref && typeof ref !== "string") { - ref.current = refValue; - } - } - }; - }, [refs]); -} +export const formatString = (sectionsWithCharacter: Sections[]) => + sectionsWithCharacter.map((section) => section.value).join(""); diff --git a/src/components/NewDatePicker/NewDatePicker.tsx b/src/components/NewDatePicker/NewDatePicker.tsx index 2f05af27b..868be6ff9 100644 --- a/src/components/NewDatePicker/NewDatePicker.tsx +++ b/src/components/NewDatePicker/NewDatePicker.tsx @@ -1,6 +1,5 @@ import React, { forwardRef, memo, useCallback, useState } from "react"; import { Flex, Calendar, DateField } from ".."; -// 後で export しておく import { Action } from "../Calendar/internal/Actions"; import { Dayjs } from "dayjs"; import { @@ -20,14 +19,7 @@ export type NewDatePickerProps = { }; /** - * @todo HTMLInputElementにする - * @example - * ```tsx - * - * ``` + * @memo 次のメジャーリリースで DatePicker に変更。現行の DatePicker は削除。 */ export const NewDatePicker = forwardRef( function DatePicker( diff --git a/src/components/NewDateRangePicker/NewDateRangePicker.tsx b/src/components/NewDateRangePicker/NewDateRangePicker.tsx index 3f23cb213..5dc8910bc 100644 --- a/src/components/NewDateRangePicker/NewDateRangePicker.tsx +++ b/src/components/NewDateRangePicker/NewDateRangePicker.tsx @@ -11,14 +11,22 @@ import { } from "@floating-ui/react"; import { Dayjs } from "dayjs"; import { Action } from "../Calendar/internal/Actions"; +import { + ClickState, + ClickStateType, +} from "../Calendar/CalendarRange/constants"; +import { DateRange } from "../Calendar/CalendarRange/types"; export type NewDateRangePickerProps = { startDate: Dayjs; endDate: Dayjs; actions?: Action[]; - onDatesChange: (date: { startDate: Dayjs; endDate: Dayjs }) => void; + onDatesChange: (date: DateRange) => void; }; +/** + * @memo 次のメジャーリリースで DateRangePicker に変更。現行の DateRangePicker は削除。 + */ export const DateRangePicker = forwardRef< HTMLDivElement, NewDateRangePickerProps @@ -43,8 +51,8 @@ export const DateRangePicker = forwardRef< setOpen((prev) => !prev); }; - const handleClose = (clickState: "start" | "end") => { - if (clickState === "end") { + const handleClose = (clickState: ClickStateType) => { + if (clickState === ClickState.END) { setOpen(false); } };