From f65c5d8a4d854b2d8a2b879230d0cf6f30e1d048 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=ED=98=84=EC=98=81?= <89445100+hamo-o@users.noreply.github.com> Date: Fri, 30 Aug 2024 17:51:09 +0900 Subject: [PATCH] =?UTF-8?q?[Feature]=20Picker=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EA=B5=AC=ED=98=84=20(#139)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: day picker 설치 * feat: LeftArrow 아이콘 추가 * feat: DatePicker UI 초기구현 * fix: Date Button UI 수정 * feat: Calendar 아이콘 추가 * feat: Date 전용 DropDown 추가 * fix: thead 관련 오류 수정, 필요 없는 import 삭제 * feat: button 클릭 영역 확대 * feat: single mode로 제한 * feat: DateDropDown 빌드 * feat: usePickerState 훅 분리 * feat: Picker button style 분리 * feat: TimePicker 초기 UI * feat: TimePicker를 usePickerState 훅을 이용해서 동작할 수 있도록 구현 * chore: TimePicker 내보내기 추가 * fix: label 스타일 변경 및 TimePicker 라벨 추가 * design: 버튼 스타일 변경 * fix: 12시 관련 에러처리 * design: 아이콘 cursor 속성 추가 * fix: 출력 date, time 네이밍 수정 및 isAM 관련 로직 수정 * fix: initialDate 객체가 아니라 바로 받을 수 있도록 수정, 12시 엣지케이스 수정 * feat: Date range도 선택할 수 있도록 수정 * feat: usePickerRangeDateState, usePickerTimeState 훅 분리 * fix: month 관련 오류 수정 * fix: 0시 12시로 변환 * chore: 추가 컴포넌트 빌드 * refactor: Context API 활용 * remove: 불필요한 hook 삭제 * chore: PickerGroup 빌드 * fix: a11y color-contrast 체크 해제 * refactor: Date to string 유틸함수 사용하기 * refactor: pickerClassNames 분리 * refactor: pickerComponents 분리 * rename: 폴더구조 변경 * chore: 빌드 내용 추가 * fix: 복잡한 삼항연산자 개선 및 빌드 에러를 해결하기 위한 폴더구조 재변경 * refactor: 여러번 호출되는 getHours 개선 * fix: styled와 className 통일 및 불필요한 styled 삭제 * feat: 시작, 끝 날짜 선택 후 재선택 시 초기화 * refactor: format~ 으로 함수 네이밍 변경 * refactor: format~ 함수 util로 분리 * refactor: useTimeState 훅 분리 * refactor: formatDateToString return값 변경 * fix: 타입 단언 제거 * chore: pickerComponents 빌드 제외 * chore: package.json 변경 및 rollup config 변경 * chore: 스토리북 console.log 삭제 * feat: DatePicker 바깥 영역 클릭 시 드롭다운 닫기 * chore: changeset 추가 * design: TimePicker select 및 active 시 디자인 적용 * chore: 불필요한 닫는 태그 삭제 * refactor: useTimeState 훅 코드리뷰 반영 * fix: 여러 타입 모두 disabled 상태일 때 이벤트 막기 --- .changeset/lazy-sheep-pull.md | 5 + .changeset/sour-swans-wait.md | 5 + packages/scripts/generateBuildConfig.ts | 1 + packages/wow-icons/src/component/Calendar.tsx | 50 ++++++++ .../wow-icons/src/component/LeftArrow.tsx | 42 ++++++ packages/wow-icons/src/component/index.ts | 2 + packages/wow-icons/src/svg/calendar.svg | 8 ++ packages/wow-icons/src/svg/left-arrow.svg | 3 + packages/wow-ui/package.json | 26 ++++ packages/wow-ui/rollup.config.js | 5 + .../wow-ui/src/components/Button/index.tsx | 4 +- .../src/components/Picker/DateDropDown.tsx | 115 +++++++++++++++++ .../components/Picker/DatePicker.stories.tsx | 110 ++++++++++++++++ .../src/components/Picker/PickerContext.ts | 24 ++++ .../src/components/Picker/PickerGroup.tsx | 76 +++++++++++ .../src/components/Picker/RangeDatePicker.tsx | 87 +++++++++++++ .../components/Picker/SingleDatePicker.tsx | 75 +++++++++++ .../src/components/Picker/TimePicker.tsx | 120 ++++++++++++++++++ .../Picker/pickerButtonStyle.css.ts | 71 +++++++++++ .../src/components/Picker/pickerClassNames.ts | 55 ++++++++ .../components/Picker/pickerComponents.tsx | 49 +++++++ packages/wow-ui/src/hooks/useTimeState.ts | 40 ++++++ packages/wow-ui/src/utils/formatToString.ts | 28 ++++ .../styled-system/types/conditions.d.ts | 2 +- pnpm-lock.yaml | 16 +++ 25 files changed, 1016 insertions(+), 3 deletions(-) create mode 100644 .changeset/lazy-sheep-pull.md create mode 100644 .changeset/sour-swans-wait.md create mode 100644 packages/wow-icons/src/component/Calendar.tsx create mode 100644 packages/wow-icons/src/component/LeftArrow.tsx create mode 100644 packages/wow-icons/src/svg/calendar.svg create mode 100644 packages/wow-icons/src/svg/left-arrow.svg create mode 100644 packages/wow-ui/src/components/Picker/DateDropDown.tsx create mode 100644 packages/wow-ui/src/components/Picker/DatePicker.stories.tsx create mode 100644 packages/wow-ui/src/components/Picker/PickerContext.ts create mode 100644 packages/wow-ui/src/components/Picker/PickerGroup.tsx create mode 100644 packages/wow-ui/src/components/Picker/RangeDatePicker.tsx create mode 100644 packages/wow-ui/src/components/Picker/SingleDatePicker.tsx create mode 100644 packages/wow-ui/src/components/Picker/TimePicker.tsx create mode 100644 packages/wow-ui/src/components/Picker/pickerButtonStyle.css.ts create mode 100644 packages/wow-ui/src/components/Picker/pickerClassNames.ts create mode 100644 packages/wow-ui/src/components/Picker/pickerComponents.tsx create mode 100644 packages/wow-ui/src/hooks/useTimeState.ts create mode 100644 packages/wow-ui/src/utils/formatToString.ts diff --git a/.changeset/lazy-sheep-pull.md b/.changeset/lazy-sheep-pull.md new file mode 100644 index 00000000..ddbcb186 --- /dev/null +++ b/.changeset/lazy-sheep-pull.md @@ -0,0 +1,5 @@ +--- +"wowds-ui": patch +--- + +RangeDatePicker, SingleDatePicker, TimePicker 컴포넌트를 구현합니다. diff --git a/.changeset/sour-swans-wait.md b/.changeset/sour-swans-wait.md new file mode 100644 index 00000000..e676ff48 --- /dev/null +++ b/.changeset/sour-swans-wait.md @@ -0,0 +1,5 @@ +--- +"wowds-icons": patch +--- + +LeftArrow 아이콘을 추가합니다. diff --git a/packages/scripts/generateBuildConfig.ts b/packages/scripts/generateBuildConfig.ts index 36a68f74..c9bed9e1 100644 --- a/packages/scripts/generateBuildConfig.ts +++ b/packages/scripts/generateBuildConfig.ts @@ -25,6 +25,7 @@ const excludedComponents = [ "DropDownWrapper", "CollectionContext", "DropDownOptionList", + "pickerComponents", ]; const getFilteredComponentFiles = async (directoryPath: string) => { diff --git a/packages/wow-icons/src/component/Calendar.tsx b/packages/wow-icons/src/component/Calendar.tsx new file mode 100644 index 00000000..98b3601c --- /dev/null +++ b/packages/wow-icons/src/component/Calendar.tsx @@ -0,0 +1,50 @@ +import { forwardRef } from "react"; +import { color } from "wowds-tokens"; + +import type { IconProps } from "@/types/Icon.ts"; + +const Calendar = forwardRef( + ( + { + className, + width = "24", + height = "24", + viewBox = "0 0 24 24", + stroke = "white", + ...rest + }, + ref + ) => { + return ( + + + + + + + + + ); + } +); + +Calendar.displayName = "Calendar"; +export default Calendar; diff --git a/packages/wow-icons/src/component/LeftArrow.tsx b/packages/wow-icons/src/component/LeftArrow.tsx new file mode 100644 index 00000000..10aad208 --- /dev/null +++ b/packages/wow-icons/src/component/LeftArrow.tsx @@ -0,0 +1,42 @@ +import { forwardRef } from "react"; +import { color } from "wowds-tokens"; + +import type { IconProps } from "@/types/Icon.ts"; + +const LeftArrow = forwardRef( + ( + { + className, + width = "20", + height = "20", + viewBox = "0 0 20 20", + stroke = "white", + ...rest + }, + ref + ) => { + return ( + + + + ); + } +); + +LeftArrow.displayName = "LeftArrow"; +export default LeftArrow; diff --git a/packages/wow-icons/src/component/index.ts b/packages/wow-icons/src/component/index.ts index ef0816dd..11ab8e7f 100644 --- a/packages/wow-icons/src/component/index.ts +++ b/packages/wow-icons/src/component/index.ts @@ -1,8 +1,10 @@ +export { default as Calendar } from "./Calendar.tsx"; export { default as Check } from "./Check.tsx"; export { default as Close } from "./Close.tsx"; export { default as DownArrow } from "./DownArrow.tsx"; export { default as Edit } from "./Edit.tsx"; export { default as Help } from "./Help.tsx"; +export { default as LeftArrow } from "./LeftArrow.tsx"; export { default as Link } from "./Link.tsx"; export { default as Plus } from "./Plus.tsx"; export { default as Reload } from "./Reload.tsx"; diff --git a/packages/wow-icons/src/svg/calendar.svg b/packages/wow-icons/src/svg/calendar.svg new file mode 100644 index 00000000..e7ea74b1 --- /dev/null +++ b/packages/wow-icons/src/svg/calendar.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/packages/wow-icons/src/svg/left-arrow.svg b/packages/wow-icons/src/svg/left-arrow.svg new file mode 100644 index 00000000..2d9aed25 --- /dev/null +++ b/packages/wow-icons/src/svg/left-arrow.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/wow-ui/package.json b/packages/wow-ui/package.json index cd521102..8c17bdc1 100644 --- a/packages/wow-ui/package.json +++ b/packages/wow-ui/package.json @@ -70,6 +70,31 @@ "require": "./dist/RadioGroup.cjs", "import": "./dist/RadioGroup.js" }, + "./DateDropDown": { + "types": "./dist/components/Picker/DateDropDown.d.ts", + "require": "./dist/DateDropDown.cjs", + "import": "./dist/DateDropDown.js" + }, + "./PickerGroup": { + "types": "./dist/components/Picker/PickerGroup.d.ts", + "require": "./dist/PickerGroup.cjs", + "import": "./dist/PickerGroup.js" + }, + "./RangeDatePicker": { + "types": "./dist/components/Picker/RangeDatePicker.d.ts", + "require": "./dist/RangeDatePicker.cjs", + "import": "./dist/RangeDatePicker.js" + }, + "./SingleDatePicker": { + "types": "./dist/components/Picker/SingleDatePicker.d.ts", + "require": "./dist/SingleDatePicker.cjs", + "import": "./dist/SingleDatePicker.js" + }, + "./TimePicker": { + "types": "./dist/components/Picker/TimePicker.d.ts", + "require": "./dist/TimePicker.cjs", + "import": "./dist/TimePicker.js" + }, "./MultiGroup": { "types": "./dist/components/MultiGroup/index.d.ts", "require": "./dist/MultiGroup.cjs", @@ -154,6 +179,7 @@ "dependencies": { "clsx": "^2.1.1", "lottie-react": "^2.4.0", + "react-day-picker": "^9.0.8", "wowds-icons": "workspace:^" }, "peerDependencies": { diff --git a/packages/wow-ui/rollup.config.js b/packages/wow-ui/rollup.config.js index f03811d6..2113035a 100644 --- a/packages/wow-ui/rollup.config.js +++ b/packages/wow-ui/rollup.config.js @@ -30,6 +30,11 @@ export default { SearchBar: "./src/components/SearchBar", RadioButton: "./src/components/RadioGroup/RadioButton", RadioGroup: "./src/components/RadioGroup/RadioGroup", + DateDropDown: "./src/components/Picker/DateDropDown", + PickerGroup: "./src/components/Picker/PickerGroup", + RangeDatePicker: "./src/components/Picker/RangeDatePicker", + SingleDatePicker: "./src/components/Picker/SingleDatePicker", + TimePicker: "./src/components/Picker/TimePicker", MultiGroup: "./src/components/MultiGroup", DropDownOption: "./src/components/DropDown/DropDownOption", DropDown: "./src/components/DropDown", diff --git a/packages/wow-ui/src/components/Button/index.tsx b/packages/wow-ui/src/components/Button/index.tsx index 51b369b0..ea6fad1e 100644 --- a/packages/wow-ui/src/components/Button/index.tsx +++ b/packages/wow-ui/src/components/Button/index.tsx @@ -134,7 +134,7 @@ const ButtonStyle = cva({ _disabled: { borderColor: "darkDisabled", color: "darkDisabled", - cursor: "not-allowed", + pointerEvents: "none", }, _hover: { borderColor: "blueHover", @@ -152,7 +152,7 @@ const ButtonStyle = cva({ _disabled: { color: "blueDisabled", - cursor: "not-allowed", + pointerEvents: "none", }, _hover: { shadow: "blue", diff --git a/packages/wow-ui/src/components/Picker/DateDropDown.tsx b/packages/wow-ui/src/components/Picker/DateDropDown.tsx new file mode 100644 index 00000000..454dfe47 --- /dev/null +++ b/packages/wow-ui/src/components/Picker/DateDropDown.tsx @@ -0,0 +1,115 @@ +"use client"; + +import { cva } from "@styled-system/css"; +import { Flex, styled } from "@styled-system/jsx"; +import { Calendar } from "wowds-icons"; + +interface DateDropDownPropsBase { + label?: string; + placeholder?: string; + onClick: () => void; +} + +interface SingleDateDropDownProps extends DateDropDownPropsBase { + mode: "single"; + selectedValue?: string; +} + +interface RangeDateDropDownProps extends DateDropDownPropsBase { + mode: "range"; + selectedValue?: { start?: string; end?: string }; +} + +type DateDropDownProps = SingleDateDropDownProps | RangeDateDropDownProps; + +const DateDropDown = ({ + mode, + placeholder, + label, + selectedValue, + onClick, +}: DateDropDownProps) => { + const formatSelectedValue = () => { + if (!selectedValue) return placeholder; + if (mode === "range" && selectedValue.start) return selectedValue.start; + if (mode === "single") return selectedValue; + }; + + return ( + + {label && ( + + {label} + + )} + + + ); +}; + +export default DateDropDown; + +const dropdownStyle = cva({ + base: { + lg: { + maxWidth: "22.375rem", + }, + smDown: { + width: "100%", + }, + display: "flex", + alignItems: "center", + justifyContent: "space-between", + + border: "1px solid", + borderRadius: "sm", + borderColor: "outline", + outline: "none", + + paddingY: "xs", + paddingX: "sm", + + backgroundColor: "background", + cursor: "pointer", + }, +}); + +const placeholderStyle = cva({ + base: { + textStyle: "body1", + }, + variants: { + type: { + default: { + color: "outline", + _hover: { + color: "sub", + }, + }, + focused: { + color: "primary", + }, + selected: { + color: "textBlack", + }, + }, + }, +}); diff --git a/packages/wow-ui/src/components/Picker/DatePicker.stories.tsx b/packages/wow-ui/src/components/Picker/DatePicker.stories.tsx new file mode 100644 index 00000000..35c8b21f --- /dev/null +++ b/packages/wow-ui/src/components/Picker/DatePicker.stories.tsx @@ -0,0 +1,110 @@ +import type { Meta } from "@storybook/react"; +import { Flex } from "@styled-system/jsx"; +import { useState } from "react"; + +import type { Time } from "@/components/Picker/PickerContext"; +import PickerGroup from "@/components/Picker/PickerGroup"; +import RangeDatePicker from "@/components/Picker/RangeDatePicker"; +import DatePicker from "@/components/Picker/SingleDatePicker"; +import TimePicker from "@/components/Picker/TimePicker"; + +const meta = { + title: "UI/DatePicker", + component: DatePicker, + tags: ["autodocs"], + parameters: { + componentSubtitle: "DatePicker 컴포넌트", + a11y: { + config: { + rules: [{ id: "color-contrast", enabled: false }], + }, + }, + }, + argTypes: { + label: { + description: "DatePicker의 라벨을 나타냅니다.", + table: { + type: { summary: "string" }, + defaultValue: { summary: undefined }, + }, + }, + selected: { + description: "DatePicker의 선택된 날짜 값을 나타냅니다.", + table: { + type: { summary: "Date" }, + defaultValue: { summary: undefined }, + }, + }, + onSelect: { + description: "DatePicker의 날짜를 선택할 수 있는 함수를 나타냅니다.", + table: { + type: { summary: "function" }, + defaultValue: { + summary: undefined, + }, + }, + }, + placeholder: { + description: + "DatePicker의 드롭다운에 들어갈 placeholder 텍스트를 나타냅니다.", + table: { + type: { summary: "string" }, + defaultValue: { summary: undefined }, + }, + }, + }, +} satisfies Meta; + +export default meta; + +export const Default = () => { + const [selected, setSelected] = useState(); + return ( + + ); +}; + +export const WithInitialDate = () => { + const [selected, setSelected] = useState(new Date()); + return ( + + ); +}; + +export const WithTimePicker = () => { + const [selected, setSelected] = useState(new Date()); + + return ( + + + + + ); +}; + +export const DateRange = () => { + // DateRange 타입 + const [selected, setSelected] = useState< + { from: Date | undefined; to?: Date | undefined } | undefined + >(); + return ( + + ); +}; + +export const TimeRange = () => { + const [start, setStart] = useState