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 => (
+ -
+
+
+ ))}
+
+)
+
+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 => (
+ |
+ {weekname}
+ |
+ ))}
+
+
+
+ {calendar.map(week => (
+
+ {week.map(date => (
+ |
+
+ |
+ ))}
+
+ ))}
+
+
+ )
+
+ 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 ? (