Skip to content

Commit

Permalink
refactor: (Calendar) reimplement the inner state for better controlle…
Browse files Browse the repository at this point in the history
…d and uncontrolled behavior, also adjust the type of `value` param in `onChange`
  • Loading branch information
awmleer committed Mar 16, 2022
1 parent 0293ee9 commit 2f6a928
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 70 deletions.
18 changes: 9 additions & 9 deletions src/components/calendar/calendar.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@

### Props

| Name | Description | Type | Default |
| ------------- | ---------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | ---------- |
| selectionMode | The selection mode. Disable selection when this prop is not set. | `'single' \| 'range'` | - |
| value | The selected date or date range. | `Date \| null` when selection mode is "single". `[Date, Date] \| null` when selection mode is "range" | - |
| defaultValue | The default selected date or date range. | Same as `value` prop. | - |
| onChange | Trigger when selected date changes. | `(val: Date) => void` when selection mode is "single". `(val: [Date, Date]) => void` when selection mode is "range". | - |
| onPageChange | Trigger when changed year or month. | `(year: number, month: number) => void` | - |
| weekStartsOn | Week starts on which day. | `'Monday' \| 'Sunday'` | `'Sunday'` |
| renderLabel | The label render function. | `(date: Date) => string \| null \| undefined` | - |
| Name | Description | Type | Default |
| ------------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ---------- |
| selectionMode | The selection mode. Disable selection when this prop is not set. | `'single' \| 'range'` | - |
| value | The selected date or date range. | `Date \| null` when selection mode is "single". `[Date, Date] \| null` when selection mode is "range" | - |
| defaultValue | The default selected date or date range. | Same as `value` prop. | - |
| onChange | Trigger when selected date changes. | `(val: Date \| null) => void` when selection mode is "single". `(val: [Date, Date] \| null) => void` when selection mode is "range". | - |
| onPageChange | Trigger when changed year or month. | `(year: number, month: number) => void` | - |
| weekStartsOn | Week starts on which day. | `'Monday' \| 'Sunday'` | `'Sunday'` |
| renderLabel | The label render function. | `(date: Date) => string \| null \| undefined` | - |

### CSS Variables

Expand Down
108 changes: 56 additions & 52 deletions src/components/calendar/calendar.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import React, {
forwardRef,
ReactNode,
useMemo,
useState,
useImperativeHandle,
} from 'react'
import { NativeProps, withNativeProps } from '../../utils/native-props'
import dayjs, { Dayjs } from 'dayjs'
import dayjs from 'dayjs'
import classNames from 'classnames'
import { mergeProps } from '../../utils/with-default-props'
import { ArrowLeft } from './arrow-left'
import { ArrowLeftDouble } from './arrow-left-double'
import { useConfig } from '../config-provider'
import isoWeek from 'dayjs/plugin/isoWeek'
import { useIsomorphicLayoutEffect, useUpdateEffect } from 'ahooks'
import { useUpdateEffect } from 'ahooks'
import { usePropsValue } from '../../utils/use-props-value'
import { convertValueToRange, DateRange } from './convert'

dayjs.extend(isoWeek)

Expand Down Expand Up @@ -41,19 +42,20 @@ export type CalendarProps = {
selectionMode: 'single'
value?: Date | null
defaultValue?: Date | null
onChange?: (val: Date) => void
onChange?: (val: Date | null) => void
}
| {
selectionMode: 'range'
value?: [Date, Date] | null
defaultValue?: [Date, Date] | null
onChange?: (val: [Date, Date]) => void
onChange?: (val: [Date, Date] | null) => void
}
) &
NativeProps

const defaultProps = {
weekStartsOn: 'Sunday',
defaultValue: null,
}

export const Calendar = forwardRef<CalenderRef, CalendarProps>((p, ref) => {
Expand All @@ -66,26 +68,25 @@ export const Calendar = forwardRef<CalenderRef, CalendarProps>((p, ref) => {
if (item) markItems.unshift(item)
}

const dateRange = useMemo<[Date | null, Date | null]>(() => {
if (props.selectionMode === 'single') {
const value = props.value ?? props.defaultValue ?? null
return [value, value]
} else if (props.selectionMode === 'range') {
return props.value ?? props.defaultValue ?? [null, null]
} else {
return [null, null]
}
}, [props.selectionMode, props.value, props.defaultValue])
const [dateRange, setDateRange] = usePropsValue<DateRange>({
value:
props.value === undefined
? undefined
: convertValueToRange(props.selectionMode, props.value),
defaultValue: convertValueToRange(props.selectionMode, props.defaultValue),
onChange: v => {
if (props.selectionMode === 'single') {
props.onChange?.(v ? v[0] : null)
} else if (props.selectionMode === 'range') {
props.onChange?.(v)
}
},
})

const [begin, setBegin] = useState<Dayjs | null>(null)
const [end, setEnd] = useState<Dayjs | null>(null)
useIsomorphicLayoutEffect(() => {
setBegin(dateRange[0] ? dayjs(dateRange[0]) : null)
setEnd(dateRange[1] ? dayjs(dateRange[1]) : null)
}, [dateRange[0], dateRange[1]])
const [intermediate, setIntermediate] = useState(false)

const [current, setCurrent] = useState(() =>
dayjs(dateRange[0] ?? today).date(1)
dayjs(dateRange ? dateRange[0] : today).date(1)
)

useUpdateEffect(() => {
Expand Down Expand Up @@ -164,13 +165,18 @@ export const Calendar = forwardRef<CalenderRef, CalendarProps>((p, ref) => {
}
while (cells.length < 6 * 7) {
const d = iterator
const isSelect = (() => {
if (!begin) return false
if (d.isSame(begin, 'day')) return true
if (!end) return false
if (d.isSame(end, 'day')) return true
return d.isAfter(begin, 'day') && d.isBefore(end, 'day')
})()
let isSelect = false
let isBegin = false
let isEnd = false
if (dateRange) {
const [begin, end] = dateRange
isBegin = d.isSame(begin, 'day')
isEnd = d.isSame(end, 'day')
isSelect =
isBegin ||
isEnd ||
(d.isAfter(begin, 'day') && d.isBefore(end, 'day'))
}
const inThisMonth = d.month() === current.month()
cells.push(
<div
Expand All @@ -181,36 +187,34 @@ export const Calendar = forwardRef<CalenderRef, CalendarProps>((p, ref) => {
inThisMonth && {
[`${classPrefix}-cell-today`]: d.isSame(today, 'day'),
[`${classPrefix}-cell-selected`]: isSelect,
[`${classPrefix}-cell-selected-begin`]:
isSelect && d.isSame(begin, 'day'),
[`${classPrefix}-cell-selected-end`]:
isSelect && (!end || d.isSame(end, 'day')),
[`${classPrefix}-cell-selected-begin`]: isBegin,
[`${classPrefix}-cell-selected-end`]: isEnd,
}
)}
onClick={() => {
if (!props.selectionMode) return
const date = d.toDate()
if (props.selectionMode === 'single') {
setBegin(d)
setEnd(d)
props.onChange?.(d.toDate())
setDateRange([date, date])
} else if (props.selectionMode === 'range') {
if (begin !== null && end === null) {
if (begin.isSame(d.toDate())) {
setBegin(null)
setEnd(null)
} else {
if (d.isBefore(begin)) {
setEnd(begin)
setBegin(d)
props.onChange?.([d.toDate(), begin.toDate()])
} else {
setEnd(d)
props.onChange?.([begin.toDate(), d.toDate()])
}
}
if (!dateRange) {
setDateRange([date, date])
setIntermediate(true)
return
}
const [begin, end] = dateRange
if (d.isSame(begin, 'date') && d.isSame(end, 'day')) {
setDateRange(null)
setIntermediate(false)
return
}
if (intermediate) {
const another = dateRange[0]
setDateRange(another > date ? [date, another] : [another, date])
setIntermediate(false)
} else {
setBegin(d)
setEnd(null)
setDateRange([date, date])
setIntermediate(true)
}
}
if (!inThisMonth) {
Expand Down
18 changes: 9 additions & 9 deletions src/components/calendar/calendar.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@

### 属性

| 属性 | 说明 | 类型 | 默认值 |
| ------------- | ---------------------------------- | ------------------------------------------------------------------------------ | ---------- |
| selectionMode | 选择模式,不设置的话表示不支持选择 | `'single' \| 'range'` | - |
| value | 选择的日期 | 单选模式下为 `Date \| null`,多选模式下为 `[Date, Date] \| null` | - |
| defaultValue | 默认选择的日期 |`value` 属性 | - |
| onChange | 选择日期变化时触发 | 单选模式下为 `(val: Date) => void`,多选模式下为 `(val: [Date, Date]) => void` | - |
| onPageChange | 切换月或年时触发 | `(year: number, month: number) => void` | - |
| weekStartsOn | 每周以周几作为第一天 | `'Monday' \| 'Sunday'` | `'Sunday'` |
| renderLabel | 标注信息的渲染函数 | `(date: Date) => string \| null \| undefined` | - |
| 属性 | 说明 | 类型 | 默认值 |
| ------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------- | ---------- |
| selectionMode | 选择模式,不设置的话表示不支持选择 | `'single' \| 'range'` | - |
| value | 选择的日期 | 单选模式下为 `Date \| null`,多选模式下为 `[Date, Date] \| null` | - |
| defaultValue | 默认选择的日期 |`value` 属性 | - |
| onChange | 选择日期变化时触发 | 单选模式下为 `(val: Date \| null) => void`,多选模式下为 `(val: [Date, Date] \| null) => void` | - |
| onPageChange | 切换月或年时触发 | `(year: number, month: number) => void` | - |
| weekStartsOn | 每周以周几作为第一天 | `'Monday' \| 'Sunday'` | `'Sunday'` |
| renderLabel | 标注信息的渲染函数 | `(date: Date) => string \| null \| undefined` | - |

### CSS 变量

Expand Down
16 changes: 16 additions & 0 deletions src/components/calendar/convert.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export type DateRange = [Date, Date] | null
export function convertValueToRange(
selectionMode: 'single' | 'range' | undefined,
value: Date | [Date, Date] | null
): DateRange {
if (selectionMode === undefined) {
return null
}
if (value === null) {
return null
}
if (Array.isArray(value)) {
return value
}
return [value, value]
}
8 changes: 8 additions & 0 deletions src/components/calendar/demos/demo1.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ import React from 'react'
import { Calendar } from 'antd-mobile'
import { DemoBlock, DemoDescription } from 'demos'

const defaultSingle = new Date('2022-03-09')
const defaultRange: [Date, Date] = [
new Date('2022-03-09'),
new Date('2022-03-21'),
]

export default () => {
return (
<>
Expand All @@ -15,13 +21,15 @@ export default () => {
<DemoBlock title='选择某一天'>
<Calendar
selectionMode='single'
defaultValue={defaultSingle}
onChange={val => {
console.log(val)
}}
/>
</DemoBlock>
<DemoBlock title='选择日期范围'>
<Calendar
defaultValue={defaultRange}
selectionMode='range'
onChange={val => {
console.log(val)
Expand Down

0 comments on commit 2f6a928

Please sign in to comment.