diff --git a/packages/competition/src/App.tsx b/packages/competition/src/App.tsx index b2e32aa..ccbfa34 100644 --- a/packages/competition/src/App.tsx +++ b/packages/competition/src/App.tsx @@ -1,4 +1,4 @@ -import { Button, Input, showToast } from "../../ui/dist"; +import { Button, DatePicker, Input, showToast } from "../../ui/dist"; import "./App.css"; function App() { @@ -6,6 +6,7 @@ function App() { <> + ); } diff --git a/packages/ui/lib/Calendar/Calendar.module.scss b/packages/ui/lib/Calendar/Calendar.module.scss index db5efaf..6b2a3e8 100644 --- a/packages/ui/lib/Calendar/Calendar.module.scss +++ b/packages/ui/lib/Calendar/Calendar.module.scss @@ -1,3 +1,4 @@ +@use '../variables' as *; .base { height: fit-content; width: fit-content; @@ -5,7 +6,7 @@ border-radius: 10px; background-color: var(--white-color); color: var(--black-color); - box-shadow: 1px 1px 4px 0px var(--shadow-color); + box-shadow: $shadow; .calendarTitle { font-size: 18px; font-weight: 1000; @@ -18,15 +19,16 @@ .buttonContainer { display: flex; gap: 3px; - cursor: pointer; - svg { - opacity: 0.8; - transition: all 0.2s ease-in-out; - } - svg:hover { - scale: 0.95; - opacity: 1; - transition: all 0.2s ease-in-out; + .button { + height: 24px; + width: 24px; + position: relative; + svg { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + } } } } diff --git a/packages/ui/lib/Calendar/Calendar.stories.tsx b/packages/ui/lib/Calendar/Calendar.stories.tsx index 4533b48..e06df89 100644 --- a/packages/ui/lib/Calendar/Calendar.stories.tsx +++ b/packages/ui/lib/Calendar/Calendar.stories.tsx @@ -23,8 +23,7 @@ type Story = StoryObj; const defaultProps: CalendarProps = { onChange: function () {}, - selected: new Date(), - defaultSelected: new Date(), + defaultSelected: undefined, }; export const DefaultCalendar: Story = { @@ -37,6 +36,5 @@ export const ExampleCalendar: Story = { args: { ...defaultProps, onChange: test, - selected: new Date(0), }, }; diff --git a/packages/ui/lib/Calendar/Calendar.tsx b/packages/ui/lib/Calendar/Calendar.tsx index 637cc5e..0590d37 100644 --- a/packages/ui/lib/Calendar/Calendar.tsx +++ b/packages/ui/lib/Calendar/Calendar.tsx @@ -2,12 +2,13 @@ import classNames from 'classnames'; import React, { useEffect, useState } from 'react'; import styles from './Calendar.module.scss'; import DayItem from './DayItem'; +import { Button } from '..'; -export interface CalendarProps { +export interface CalendarProps extends React.HtmlHTMLAttributes { /** * the onChange of the calendar */ - onChange?: (value: Date) => void; + onchange?: (value: Date) => void; /** * the selected of the date */ @@ -19,9 +20,9 @@ export interface CalendarProps { } export const Calendar = React.forwardRef( - ({ onChange, selected, defaultSelected }, ref) => { + ({ onchange, selected = undefined, defaultSelected = undefined, ...rest }, ref) => { // Initialize the state to store the selected date - const [selectDate, setSelectDate] = useState(defaultSelected || new Date()); + const [selectDate, setSelectDate] = useState(defaultSelected); const [currentDate] = useState(new Date()); const [numberOfDaysFromPrevMonth, setNumberOfDaysFromPrevMonth] = useState(0); const [numberOfDaysInLastMonth, setNumberOfDaysInLastMonth] = useState(0); @@ -86,82 +87,53 @@ export const Calendar = React.forwardRef( }; useEffect(() => { - onChange && onChange(selectDate); - }, [selectDate, onChange]); + selectDate && onchange && onchange(selectDate); + }, [selectDate, onchange]); return (
- changeMonth(true)} + className={styles['button']} > - - - - - - + + + + +
{(([, month, , year]) => `${month}, ${year}`)( - new Date(currentDate.getFullYear(), selectMonth, 0).toDateString().split(' '), + new Date(currentDate.getFullYear(), selectMonth + 1, 0).toDateString().split(' '), )}
diff --git a/packages/ui/lib/Calendar/DayItem.tsx b/packages/ui/lib/Calendar/DayItem.tsx index 9d82ac8..7f3c49a 100644 --- a/packages/ui/lib/Calendar/DayItem.tsx +++ b/packages/ui/lib/Calendar/DayItem.tsx @@ -4,7 +4,7 @@ import styles from './Calendar.module.scss'; interface DayItemProps { index: number; isOtherMonth: 'pre' | 'this' | 'after'; - selectDate: Date; + selectDate?: Date; currentDate: Date; onChange: (value: Date) => void; selectMonth: number; @@ -32,16 +32,20 @@ const DayItem = memo(function otherMonthItem({ break; } - function isSameDay(date1: Date, date2: Date) { + function isSameDay(date1?: Date, date2?: Date) { return ( - date1.getFullYear() === date2.getFullYear() && - date1.getMonth() === date2.getMonth() && - date1.getDate() === date2.getDate() + date1?.getFullYear() === date2?.getFullYear() && + date1?.getMonth() === date2?.getMonth() && + date1?.getDate() === date2?.getDate() ); } const thisDate = new Date(currentDate.getFullYear(), month, index); + const handleClick = () => { + thisDate && onChange(thisDate); + }; + return (
onChange(thisDate)} + onClick={handleClick} > {index}
diff --git a/packages/ui/lib/DatePicker/DatePicker.module.scss b/packages/ui/lib/DatePicker/DatePicker.module.scss new file mode 100644 index 0000000..2517945 --- /dev/null +++ b/packages/ui/lib/DatePicker/DatePicker.module.scss @@ -0,0 +1,59 @@ +@mixin animation($type) { + animation-name: #{$type}; + animation-duration: 200ms; + animation-timing-function: ease-in-out; + animation-fill-mode: forwards; +} +.date-picker-button { + display: flex; + align-items: center; + gap: 10px; + color: var(--shadow-color) !important; + transition: all 0.2s ease-in-out; + padding: 10px 15px !important; + width: 280px; + &.select { + color: var(--primary-color) !important; + svg { + fill: var(--primary-color) !important; + } + } + svg { + fill: var(--shadow-color); + transition: all 0.2s ease-in-out; + } + &:hover { + color: var(--primary-color) !important; + svg { + fill: var(--primary-color); + } + } +} +.calendar-container { + position: absolute; + margin-top: 5px; + &.in { + @include animation(in); + } + &.hide { + @include animation(hide); + } +} + +@keyframes in { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +@keyframes hide { + from { + opacity: 1; + } + to { + opacity: 0; + } +} diff --git a/packages/ui/lib/DatePicker/DatePicker.stories.tsx b/packages/ui/lib/DatePicker/DatePicker.stories.tsx new file mode 100644 index 0000000..e5b39c8 --- /dev/null +++ b/packages/ui/lib/DatePicker/DatePicker.stories.tsx @@ -0,0 +1,55 @@ +import type { Meta, StoryObj } from '@storybook/react'; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import React from 'react'; +import { DatePicker, type DatePickerProps } from './DatePicker'; + +const test = (value: Date) => { + console.log('selectDate', value); +}; + +function generateRandomDate() { + const startTimestamp = new Date(2000, 0, 1).getTime(); // 开始日期的时间戳,这里设置为2000年1月1日 + const endTimestamp = new Date().getTime(); // 当前日期的时间戳,作为结束日期 + + const randomTimestamp = Math.random() * (endTimestamp - startTimestamp) + startTimestamp; + const randomDate = new Date(randomTimestamp); + + return randomDate; +} + +const meta = { + title: 'Components/DatePicker', + component: DatePicker, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: {}, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +const defaultProps: DatePickerProps = { + onchange: test, +}; + +export const DefaultDatePicker: Story = { + args: { + ...defaultProps, + }, +}; + +export const RandomDefaultDatePicker: Story = { + args: { + ...defaultProps, + defaultPickDate: new Date(), + }, +}; + +export const RandomDatePicker: Story = { + args: { + pickDate: generateRandomDate(), + }, +}; diff --git a/packages/ui/lib/DatePicker/DatePicker.tsx b/packages/ui/lib/DatePicker/DatePicker.tsx new file mode 100644 index 0000000..b992acd --- /dev/null +++ b/packages/ui/lib/DatePicker/DatePicker.tsx @@ -0,0 +1,135 @@ +import React, { useEffect, useState } from 'react'; +import { Button, Calendar } from '..'; +import styles from './DatePicker.module.scss'; + +export interface DatePickerProps extends React.HtmlHTMLAttributes { + /** + * onchange, the onchange of the datepicker + */ + onchange?: (value: Date) => void; + /** + * defaultPickDate, the defaultPickDate of the datepicker + */ + defaultPickDate?: Date; + /** + * pickDate, the pickDate of the datePicker + */ + pickDate?: Date; +} + +export const DatePicker = React.forwardRef( + ({ onchange, defaultPickDate, pickDate, ...rest }, ref) => { + const [calendarVisible, setCalendarVisible] = useState(false); + const [selectDate, setSelectDate] = useState(defaultPickDate); + const [selectDateString, setSelectDateString] = useState( + defaultPickDate?.toString(), + ); + const [calendarIn, setCalendarIn] = useState(false); + const [calendarHide, setCalendarHide] = useState(false); + + useEffect(() => { + if (pickDate) { + setSelectDate(pickDate); + setSelectDateString(pickDate.toString()); + } + }, [pickDate]); + + useEffect(() => { + selectDate && onchange && onchange(selectDate); + }, [selectDate, onchange]); + + const handleDate = (value: Date) => { + setSelectDate(value); + if (value.toString() !== new Date().toString()) { + setSelectDateString(value.toString()); + } + }; + + const handleCalendarVisible = (e: React.MouseEvent) => { + e.stopPropagation(); + const newCalendarVisible = !calendarVisible; + if (newCalendarVisible) { + openCalendar(); + } + if (!newCalendarVisible) { + closeCalendar(); + } + }; + + const openCalendar = () => { + document.body.addEventListener('click', closeCalendar); + setCalendarIn(true); + setCalendarVisible(true); + setTimeout(() => { + setCalendarIn(false); + }, 200); + }; + + const closeCalendar = () => { + document.body.removeEventListener('click', closeCalendar); + setCalendarHide(true); + setTimeout(() => { + setCalendarHide(false); + setCalendarVisible(false); + }, 200); + }; + + console.log('selectDateString', selectDateString); + + return ( + <> +
+ + {calendarVisible && ( +
+ { + e.stopPropagation(); + }} + > +
+ )} +
+ + ); + }, +); + +DatePicker.displayName = 'DatePicker'; diff --git a/packages/ui/lib/DatePicker/index.ts b/packages/ui/lib/DatePicker/index.ts new file mode 100644 index 0000000..a48b62e --- /dev/null +++ b/packages/ui/lib/DatePicker/index.ts @@ -0,0 +1 @@ +export * from './DatePicker'; diff --git a/packages/ui/lib/Input/Input.tsx b/packages/ui/lib/Input/Input.tsx index ee0ab92..ba8045d 100644 --- a/packages/ui/lib/Input/Input.tsx +++ b/packages/ui/lib/Input/Input.tsx @@ -57,14 +57,14 @@ export const Input = React.forwardRef( onchange, isFillFather = false, value, - defaultValue, + defaultValue = '', ...rest }, ref, ) => { //设置isUpLabel来调节Label上浮状态 const [isUpInputLabel, setIsUpInputLabel] = useState(false); - const [inputValue, setInputValue] = useState(defaultValue); + const [inputValue, setInputValue] = useState(defaultValue); const InputClass = classnames( styles['base'], styles[disabled ? 'disabled' : ''], diff --git a/packages/ui/lib/Select/Select.tsx b/packages/ui/lib/Select/Select.tsx index b472479..8885c86 100644 --- a/packages/ui/lib/Select/Select.tsx +++ b/packages/ui/lib/Select/Select.tsx @@ -56,6 +56,7 @@ export const Select = React.forwardRef( optionsList.find((item) => item.key === defaultSelectKey), ); const [options, setOptions] = useState(optionsList); + const [inputValue, setInputValue] = useState(''); const showOptions: MouseEventHandler = () => { if (!disabled) setVisble(!visible); @@ -67,15 +68,19 @@ export const Select = React.forwardRef( function handleClick(value: OptionProps): void { setSelectItem(value); - onchange(value); + setInputValue(value.label); } + useEffect(() => { + selectItem && onchange(selectItem); + selectItem?.value && setInputValue(selectItem?.value); + }, [selectItem, onchange]); + const handleOptions = (value: string) => { if (value === '') { setSelectItem(undefined); } - const results = fuzzySearch(optionsList, value); - setOptions(results); + setInputValue(value); }; function fuzzySearch(optionsList: OptionProps[], searchTerm: string): OptionProps[] { @@ -89,6 +94,10 @@ export const Select = React.forwardRef( }, 100); }; + useEffect(() => { + const results = fuzzySearch(optionsList, inputValue); + setOptions(results); + }, [inputValue, optionsList]); return ( <>
( >