From 2d5534b3a63bc55edc57e1f9dbc553a7490dd882 Mon Sep 17 00:00:00 2001 From: bicochan Date: Mon, 21 Jul 2025 21:45:34 +0900 Subject: [PATCH] =?UTF-8?q?[common/components]=20feat:=20InputDatePicker?= =?UTF-8?q?=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/common/components/package.json | 1 + packages/common/components/src/ui/index.ts | 1 + .../src/ui/input-date-picker/index.tsx | 136 ++++++++++++++++++ .../input-date-picker/input-date-picker.css | 7 + pnpm-lock.yaml | 15 +- 5 files changed, 154 insertions(+), 6 deletions(-) create mode 100644 packages/common/components/src/ui/input-date-picker/index.tsx create mode 100644 packages/common/components/src/ui/input-date-picker/input-date-picker.css diff --git a/packages/common/components/package.json b/packages/common/components/package.json index 0edd7211b..96d381605 100644 --- a/packages/common/components/package.json +++ b/packages/common/components/package.json @@ -57,6 +57,7 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "date-fns": "^3.6.0", + "dayjs": "^1.11.13", "embla-carousel-react": "^8.6.0", "js-cookie": "^3.0.5", "lucide-react": "^0.474.0", diff --git a/packages/common/components/src/ui/index.ts b/packages/common/components/src/ui/index.ts index 75231add8..ae913e79a 100644 --- a/packages/common/components/src/ui/index.ts +++ b/packages/common/components/src/ui/index.ts @@ -7,6 +7,7 @@ export * from "./carousel"; export * from "./checkbox"; export * from "./data-table"; export * from "./date-picker"; +export * from "./input-date-picker"; export * from "./date-range-picker"; export * from "./dialog"; export * from "./dropdown-menu"; diff --git a/packages/common/components/src/ui/input-date-picker/index.tsx b/packages/common/components/src/ui/input-date-picker/index.tsx new file mode 100644 index 000000000..93d2caf7c --- /dev/null +++ b/packages/common/components/src/ui/input-date-picker/index.tsx @@ -0,0 +1,136 @@ +"use client"; + +import dayjs from "dayjs"; +import { useEffect, useState } from "react"; +import { CalendarIcon } from "lucide-react"; + +import { Button } from "../button"; +import { Calendar } from "../calendar"; +import { Input } from "../input"; +import { Popover, PopoverContent, PopoverTrigger } from "../popover"; + +import "./input-date-picker.css"; + +export interface InputDatePickerProps { + value?: Date; + defaultValue?: Date; + onChange?: (date: Date | undefined) => void; + placeholder?: string; + disabled?: boolean; + className?: string; +} + +export function InputDatePicker({ + value: controlledValue, + defaultValue, + onChange, + placeholder, + disabled, + className, +}: InputDatePickerProps) { + const isControlled = controlledValue !== undefined; + + const [open, setOpen] = useState(false); + const [internalDate, setInternalDate] = useState( + defaultValue, + ); + const [month, setMonth] = useState( + isControlled ? controlledValue : defaultValue, + ); + + // 실제로 사용할 date 값 (제어/비제어 모두 지원) + const date = isControlled ? controlledValue : internalDate; + + // 표시할 문자열 값 + const [displayValue, setDisplayValue] = useState(date); + + // 제어 컴포넌트일 경우 외부 값 변경 감지 + useEffect(() => { + if (isControlled) { + setDisplayValue(controlledValue); + setMonth(controlledValue); + } + }, [isControlled, controlledValue]); + + // 내부 상태 변경 핸들러 + const handleDateChange = (newDate: Date | undefined) => { + if (!isControlled) { + setInternalDate(newDate); + } + + if (onChange) { + onChange(newDate); + } + + setDisplayValue(newDate); + }; + + return ( +
+
+ { + const newDate = new Date(e.target.value); + setDisplayValue(newDate); + + if (isValidDate(newDate)) { + handleDateChange(newDate); + setMonth(newDate); + } + }} + onKeyDown={(e) => { + if (e.key === "ArrowDown") { + e.preventDefault(); + setOpen(true); + } + }} + placeholder={placeholder} + /> + + + + + + { + handleDateChange(newDate); + setOpen(false); + }} + /> + + +
+
+ ); +} + +function isValidDate(date: Date | undefined) { + if (!date) { + return false; + } + return !isNaN(date.getTime()); +} diff --git a/packages/common/components/src/ui/input-date-picker/input-date-picker.css b/packages/common/components/src/ui/input-date-picker/input-date-picker.css new file mode 100644 index 000000000..1039a6a63 --- /dev/null +++ b/packages/common/components/src/ui/input-date-picker/input-date-picker.css @@ -0,0 +1,7 @@ +@tailwind components; + +@layer components { + .input-date-picker::-webkit-calendar-picker-indicator { + display: none; + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 159a582f7..d359a5e56 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -758,6 +758,9 @@ importers: date-fns: specifier: ^3.6.0 version: 3.6.0 + dayjs: + specifier: ^1.11.13 + version: 1.11.13 embla-carousel-react: specifier: ^8.6.0 version: 8.6.0(react@18.3.1) @@ -2405,7 +2408,7 @@ packages: magic-string: 0.27.0 react-docgen-typescript: 2.2.2(typescript@5.6.3) typescript: 5.6.3 - vite: 6.2.0(@types/node@22.13.9) + vite: 6.2.0(@types/node@22.13.5) dev: true /@jridgewell/gen-mapping@0.3.8: @@ -2497,7 +2500,7 @@ packages: dnd-multi-backend: 8.1.2(dnd-core@16.0.1) framer-motion: 11.18.2(react-dom@18.3.1)(react@18.3.1) react: 18.3.1 - react-dnd: 16.0.1(@types/node@22.13.9)(@types/react@18.3.18)(react@18.3.1) + react-dnd: 16.0.1(@types/node@22.13.5)(@types/react@18.3.18)(react@18.3.1) react-dnd-html5-backend: 16.0.1 react-dnd-touch-backend: 16.0.1 react-dom: 18.3.1(react@18.3.1) @@ -3998,7 +4001,7 @@ packages: browser-assert: 1.2.1 storybook: 8.6.0(prettier@3.5.2) ts-dedent: 2.2.0 - vite: 6.2.0(@types/node@22.13.9) + vite: 6.2.0(@types/node@22.13.5) dev: true /@storybook/components@8.6.0(storybook@8.6.0): @@ -4130,7 +4133,7 @@ packages: resolve: 1.22.10 storybook: 8.6.0(prettier@3.5.2) tsconfig-paths: 4.2.0 - vite: 6.2.0(@types/node@22.13.9) + vite: 6.2.0(@types/node@22.13.5) transitivePeerDependencies: - rollup - supports-color @@ -4605,7 +4608,7 @@ packages: '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.9) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 - vite: 6.2.0(@types/node@22.13.9) + vite: 6.2.0(@types/node@22.13.5) transitivePeerDependencies: - supports-color dev: true @@ -9903,7 +9906,7 @@ packages: debug: 4.4.0 globrex: 0.1.2 tsconfck: 3.1.5(typescript@5.6.3) - vite: 6.2.0(@types/node@22.13.9) + vite: 6.2.0(@types/node@22.13.5) transitivePeerDependencies: - supports-color - typescript