From 417042f0e66cfa6923d45c00c02a521ecd0e1a36 Mon Sep 17 00:00:00 2001 From: Morty Lu <15620954611@163.com> Date: Wed, 22 Feb 2023 21:48:12 +0800 Subject: [PATCH] Refactored Calendar component to remove dependency of primereact. --- packages/neuron-ui/package.json | 1 - .../src/widgets/Calendar/calendar.module.scss | 90 +++++++++ .../neuron-ui/src/widgets/Calendar/index.tsx | 186 ++++++++++++++++++ .../neuron-ui/src/widgets/Calendar/utils.ts | 116 +++++++++++ .../src/widgets/DatetimePicker/index.tsx | 42 +--- 5 files changed, 396 insertions(+), 39 deletions(-) create mode 100644 packages/neuron-ui/src/widgets/Calendar/calendar.module.scss create mode 100644 packages/neuron-ui/src/widgets/Calendar/index.tsx create mode 100644 packages/neuron-ui/src/widgets/Calendar/utils.ts diff --git a/packages/neuron-ui/package.json b/packages/neuron-ui/package.json index df399c9dbc..f2b266f4fc 100644 --- a/packages/neuron-ui/package.json +++ b/packages/neuron-ui/package.json @@ -49,7 +49,6 @@ "immer": "9.0.16", "jsqr": "1.4.0", "office-ui-fabric-react": "7.204.0", - "primereact": "8.7.1", "qr.js": "0.0.0", "react": "17.0.2", "react-dom": "17.0.2", diff --git a/packages/neuron-ui/src/widgets/Calendar/calendar.module.scss b/packages/neuron-ui/src/widgets/Calendar/calendar.module.scss new file mode 100644 index 0000000000..1e4bc1cd7c --- /dev/null +++ b/packages/neuron-ui/src/widgets/Calendar/calendar.module.scss @@ -0,0 +1,90 @@ +@import '../../styles/mixin.scss'; + +@mixin button { + @include medium-text; + appearance: none; + cursor: pointer; + font-size: 0.75rem; + line-height: 1rem; + font-weight: 500; + padding: 7px 7px; + border: none; + border-radius: 2px; + margin: 0; + box-sizing: border-box; + border-radius: 2px; + background-color: transparent; + &:hover { + @include semi-bold-text; + background-color: #efefef; + } + &[disabled] { + cursor: none; + opacity: 0.5; + box-shadow: none !important; + pointer-events: none; + &:hover { + background-color: transparent; + } + } +} + +.calendar { + width: 374px; + .calOptions { + width: 100%; + display: flex; + flex-wrap: wrap; + justify-content: space-between; + list-style-type: none; + list-style: none; + padding: 0; + button { + @include button; + width: 100px; + } + } +} + +.calendarHeader { + display: flex; + justify-content: space-between; + border-bottom: 1px solid #eee; + margin-top: 10px; + margin-bottom: 10px; + + .calPrev, .calNext { + @include button; + width: 30px; + } + .calTitle { + button { + @include button; + @include semi-bold-text; + height: 100%; + min-width: 100px; + margin-right: 10px; + } + } +} + +.calendarTable { + width: 100%; + + .calTableHeader { + @include semi-bold-text; + font-size: 13px; + } + + .calDateItem { + @include button; + width: 100%; + &:global(.today) { + border: 1px solid var(--nervos-green-light); + margin: -1px; + } + &:global(.active) { + background-color: var(--nervos-green); + } + } +} \ No newline at end of file diff --git a/packages/neuron-ui/src/widgets/Calendar/index.tsx b/packages/neuron-ui/src/widgets/Calendar/index.tsx new file mode 100644 index 0000000000..ea0ba80dda --- /dev/null +++ b/packages/neuron-ui/src/widgets/Calendar/index.tsx @@ -0,0 +1,186 @@ +import React, { useState } from 'react' +import { getMonthCalendar, useLocalNames, monthInRange, yearInRange, dateEqual } from './utils' +import styles from './calendar.module.scss' + +interface Option { + value: number + title: string + selectable: boolean +} +const Selector = ({ options, onChange }: { options: Option[]; onChange: (option: Option) => void }) => ( +
    + {options.map(option => ( +
  1. + +
  2. + ))} +
+) + +export interface CalendarProps { + value: Date | undefined + onChange: (value: Date) => void + minDate?: Date + maxDate?: Date +} +const Calendar: React.FC = ({ value, onChange, minDate = null, maxDate = null }) => { + const today = new Date() + + const [year, setYear] = useState(value === undefined ? today.getFullYear() : value.getFullYear()) + const [month, setMonth] = useState(value === undefined ? today.getMonth() + 1 : value.getMonth() + 1) + const [status, setStatus] = useState<'year' | 'month' | 'date'>('date') + + React.useEffect(() => { + setYear(value === undefined ? today.getFullYear() : value.getFullYear()) + setMonth(value === undefined ? today.getMonth() + 1 : value.getMonth() + 1) + }, [value]) + + const locale = useLocalNames() + const weeknames = [...Array(7).keys()].map(_ => locale.dayNamesMin[(_ + locale.firstDayOfWeek) % 7]) + const monthname = locale.monthNames[month - 1] + + const calendar = getMonthCalendar(year, month, { minDate, maxDate }) + const calendarTable = ( + + + + {weeknames.map(weekname => ( + + ))} + + + + {calendar.map(week => ( + + {week.map(date => ( + + ))} + + ))} + +
+ {weekname} +
+ +
+ ) + + const monthOptions: Option[] = [...Array(12).keys()].map(index => ({ + value: index + 1, + title: locale.monthNames[index], + selectable: monthInRange(year, index, { minDate, maxDate }), + })) + const yearOptions: Option[] = [...Array(12).keys()].map(index => ({ + value: year - 6 + index, + title: `${year - 6 + index}`, + selectable: yearInRange(year - 6 + index, { minDate, maxDate }), + })) + + const prevMonth = () => { + if (month > 1) { + setMonth(m => m - 1) + } else { + setYear(y => y - 1) + setMonth(12) + } + } + const nextMonth = () => { + if (month < 12) { + setMonth(m => m + 1) + } else { + setYear(y => y + 1) + setMonth(1) + } + } + + const calendarHeader = ( +
+ +
+ + +
+ +
+ ) + + const onChangeMonth = (monthOptionItem: Option) => { + setMonth(monthOptionItem.value) + setStatus('date') + } + const onChangeYear = (yearOptionItem: Option) => { + setYear(yearOptionItem.value) + setStatus('month') + } + + return ( +
+ {calendarHeader} + {status === 'date' && calendarTable} + {status === 'year' && } + {status === 'month' && } +
+ ) +} + +export default React.memo(Calendar, (prevProps, nextProps) => { + if (prevProps.value?.toDateString() !== nextProps.value?.toDateString()) { + return false + } + if (prevProps.minDate?.toDateString() !== nextProps.minDate?.toDateString()) { + return false + } + if (prevProps.maxDate?.toDateString() !== nextProps.maxDate?.toDateString()) { + return false + } + if (prevProps.onChange !== nextProps.onChange) { + return false + } + return true +}) diff --git a/packages/neuron-ui/src/widgets/Calendar/utils.ts b/packages/neuron-ui/src/widgets/Calendar/utils.ts new file mode 100644 index 0000000000..7b9f5afb97 --- /dev/null +++ b/packages/neuron-ui/src/widgets/Calendar/utils.ts @@ -0,0 +1,116 @@ +import { useTranslation } from 'react-i18next' + +export interface Day { + instance: Date + year: number + month: number + date: number + weekday: number + curMonth: boolean + isToday: boolean + label: string + selectable: boolean +} + +interface DateRange { + minDate: Date | null + maxDate: Date | null +} + +export function dayInRange(date: Date, range: DateRange) { + if (range.minDate !== null) { + range.minDate.setHours(0, 0, 0, 0) + if (date < range.minDate) { + return false + } + } + if (range.maxDate !== null) { + range.maxDate.setHours(0, 0, 0, 0) + if (date > range.maxDate) { + return false + } + } + return true +} + +export function monthInRange(year: number, month: number, range: DateRange) { + return dayInRange(new Date(year, month + 1, 1), range) +} + +export function yearInRange(year: number, range: DateRange) { + return dayInRange(new Date(year + 1, 1, 1), range) +} + +export function dateEqual(a: Date | undefined, b: Date | undefined) { + if (a === undefined || b === undefined) { + return false + } + const newA = new Date(a.valueOf()) + newA.setHours(0, 0, 0, 0) + const newB = new Date(b.valueOf()) + newB.setHours(0, 0, 0, 0) + return newA.valueOf() === newB.valueOf() +} + +export function getMonthCalendar(year: number, month: number, range: DateRange): Day[][] { + const today = new Date() + const weekdayOfFirstDay = new Date(year, month - 1, 1).getDay() + const numOfDaysInCalendar = 42 + const firstDayOfWeek = 0 + + const dateList: Day[] = [] + + for (let i = 1; i <= numOfDaysInCalendar; i++) { + const instance = new Date(year, month - 1, firstDayOfWeek - weekdayOfFirstDay + i) + const day: Day = { + instance, + year: instance.getFullYear(), + month: instance.getMonth() + 1, + date: instance.getDate(), + weekday: instance.getDay(), + curMonth: instance.getMonth() + 1 === month, + isToday: instance.toDateString() === today.toDateString(), + label: instance.toLocaleDateString(), + selectable: dayInRange(instance, range), + } + dateList.push(day) + } + + const calendarData: Day[][] = [] + + for (let i = 0; i < dateList.length; i += 7) { + calendarData.push(dateList.slice(i, i + 7)) + } + + return calendarData +} + +export const useLocalNames = () => { + const [t] = useTranslation() + + const locale = { + firstDayOfWeek: 0, + dayNames: ['sun', 'mon', 'tues', 'wed', 'thur', 'fri', 'sat'].map(dayname => t(`datetime.${dayname}.full`)), + dayNamesShort: ['sun', 'mon', 'tue', 'wed', 'thur', 'fri', 'sat'].map(dayname => t(`datetime.${dayname}.short`)), + dayNamesMin: ['sun', 'mon', 'tue', 'wed', 'thur', 'fri', 'sat'].map(dayname => t(`datetime.${dayname}.tag`)), + monthNames: ['jan', 'feb', 'mar', 'apr', 'may', 'june', 'july', 'aug', 'sept', 'oct', 'nov', 'dec'].map(monname => + t(`datetime.${monname}.short`) + ), + monthNamesShort: [ + 'jan', + 'feb', + 'mar', + 'apr', + 'may', + 'june', + 'july', + 'aug', + 'sept', + 'oct', + 'nov', + 'dec', + ].map(monname => t(`datetime.${monname}.short`)), + } + + return locale +} diff --git a/packages/neuron-ui/src/widgets/DatetimePicker/index.tsx b/packages/neuron-ui/src/widgets/DatetimePicker/index.tsx index dc09f7e562..df7f36ecde 100644 --- a/packages/neuron-ui/src/widgets/DatetimePicker/index.tsx +++ b/packages/neuron-ui/src/widgets/DatetimePicker/index.tsx @@ -1,8 +1,7 @@ import React, { useState, useCallback, useRef, useEffect } from 'react' -import { Calendar, CalendarChangeParams } from 'primereact/calendar' +import Calendar from 'widgets/Calendar' import Button from 'widgets/Button' import { useTranslation } from 'react-i18next' -import { addLocale } from 'primereact/api' import styles from './datetimePicker.module.scss' const SECONDS_PER_DAY = 24 * 3600 * 1000 @@ -42,32 +41,6 @@ const DatetimePicker = ({ const [display, setDisplay] = useState(preset ? formatDate(new Date(+preset)) : '') const inputRef = useRef(null) - const locale: any = { - firstDayOfWeek: 0, - dayNames: ['sun', 'mon', 'tues', 'wed', 'thur', 'fri', 'sat'].map(dayname => t(`datetime.${dayname}.full`)), - dayNamesShort: ['sun', 'mon', 'tue', 'wed', 'thur', 'fri', 'sat'].map(dayname => t(`datetime.${dayname}.short`)), - dayNamesMin: ['sun', 'mon', 'tue', 'wed', 'thur', 'fri', 'sat'].map(dayname => t(`datetime.${dayname}.tag`)), - monthNames: ['jan', 'feb', 'mar', 'apr', 'may', 'june', 'july', 'aug', 'sept', 'oct', 'nov', 'dec'].map(monname => - t(`datetime.${monname}.short`) - ), - monthNamesShort: [ - 'jan', - 'feb', - 'mar', - 'apr', - 'may', - 'june', - 'july', - 'aug', - 'sept', - 'oct', - 'nov', - 'dec', - ].map(monname => t(`datetime.${monname}.short`)), - } - - addLocale('es', locale) - let selected: Date | undefined = display ? new Date(display) : undefined if (selected?.toString() === 'Invalid Date') { selected = undefined @@ -100,8 +73,8 @@ const DatetimePicker = ({ ) const onCalendarChange = useCallback( - (e: CalendarChangeParams) => { - setDisplay(formatDate(new Date(+e.value!))) + (date: Date) => { + setDisplay(formatDate(new Date(+date!))) setStatus('done') }, [setDisplay, setStatus] @@ -155,14 +128,7 @@ const DatetimePicker = ({ onKeyPress={onKeyPress} /> )} - + {isSinceTomorrow ? null : {t('datetime.start-tomorrow')}} {notice ? (