diff --git a/config/components.ts b/config/components.ts index 4e95e34e55..8f737d4763 100644 --- a/config/components.ts +++ b/config/components.ts @@ -74,6 +74,7 @@ export const components = { experimental: [ '/guide/what-is-experimental', '/components/calendar', + '/components/calendar-view', '/components/dropdown', '/components/floating-bubble', '/components/image-uploader', diff --git a/src/components/calendar/arrow-left-double.tsx b/src/components/calendar-view/arrow-left-double.tsx similarity index 100% rename from src/components/calendar/arrow-left-double.tsx rename to src/components/calendar-view/arrow-left-double.tsx diff --git a/src/components/calendar/arrow-left.tsx b/src/components/calendar-view/arrow-left.tsx similarity index 100% rename from src/components/calendar/arrow-left.tsx rename to src/components/calendar-view/arrow-left.tsx diff --git a/src/components/calendar-view/calendar-view.en.md b/src/components/calendar-view/calendar-view.en.md new file mode 100644 index 0000000000..65262130c0 --- /dev/null +++ b/src/components/calendar-view/calendar-view.en.md @@ -0,0 +1,63 @@ +# CalendarView + +Used to select a date or date range. + +CalendarView is the content area of [Calendar](/components/calendar). + +## Demos + +Only the simplest content area is shown here, and other more usages can be consulted [Calendar](/zh/components/calendar) + + + +## CalendarView + +### Props + +| Name | Description | Type | Default | Version | +| --- | --- | --- | --- | --- | +| allowClear | Whether to allow clearing after another click. | `boolean` | `true` | +| defaultValue | The default selected date or date range. | Same as `value` prop. | - | +| max | Maximum value of a selectable range. | `Date` | - | +| min | Minimum value of a selectable range. | `Date` | - | - | +| 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". | - | +| renderTop | The top information of date render function. | `(date: Date) => ReactNode \| null \| undefined` | - | +| renderBottom | The bottom information of date render function. | `(date: Date) => ReactNode \| null \| undefined` | - | +| selectionMode | The selection mode. Disable selection when this prop is not set. | `'single' \| 'range'` | - | +| shouldDisableDate | Set whether the date is disable selection. The min and max Settings are ignored | `(date: Date) => boolean` | - | +| title | The title of calendar | `React.ReactNode` | `Date selection` | +| value | The selected date or date range. | `Date \| null` when selection mode is "single". `[Date, Date] \| null` when selection mode is "range" | - | +| weekStartsOn | Week starts on which day. | `'Monday' \| 'Sunday'` | `'Sunday'` | +| renderDate | Custom date rendering. | `(date: Date) => ReactNode` | - | 5.28.0 | + +### CSS Variables + +Not supported yet. + +### Ref + +| Name | Description | Type | +| --- | --- | --- | +| jumpTo | Jump to specified page | `(page: Page \| ((page: Page) => Page)) => void` | +| jumpToToday | Jump to today's page | `() => void` | +| getDateRange | get date | `[Date, Date]` | + +```ts +type Page = { month: number; year: number } +``` + +You can manually control the page turning of the calendar through Ref, for example: + +```ts +// Jump to today's page +ref.current.jumpToToday() + +// Jump to the specified year and month +ref.current.jumpTo({ year: 2021, month: 1 }) + +// Jump to three years later +ref.current.jumpTo(page => ({ + year: page.year + 3, + month: page.month, +})) +``` diff --git a/src/components/calendar/calendar.less b/src/components/calendar-view/calendar-view.less similarity index 54% rename from src/components/calendar/calendar.less rename to src/components/calendar-view/calendar-view.less index aaaf8d2e58..ce675711dd 100644 --- a/src/components/calendar/calendar.less +++ b/src/components/calendar-view/calendar-view.less @@ -1,72 +1,103 @@ -.adm-calendar { +.adm-calendar, +.adm-calendar-view, +.adm-calendar-popup { + & &-title { + flex: auto; + font-size: var(--adm-font-size-10); + } + & &-header { - display: flex; - flex-direction: row; - align-items: center; - justify-content: space-between; - padding-top: 4px; - a.adm-calendar-arrow-button { - padding: 4px 8px; - display: block; - flex: none; - svg { - height: 22px; - } - &.adm-calendar-arrow-button-right { - svg { - transform: rotate(180deg); - } - } - } - .adm-calendar-title { - font-size: var(--adm-font-size-10); - flex: auto; + padding: 12px; + border-bottom: 1px solid var(--adm-color-border); + + .adm-calendar-view-title { text-align: center; } } + & &-body { - display: flex; - flex-wrap: wrap; + height: 64vh; + overflow: auto; + + &::-webkit-scrollbar { + display: none; + } + + .adm-calendar-view-title { + position: sticky; + top: 0; + padding: 8px 20px; + background-color: var(--adm-color-box); + } + } + + & &-footer { + &-bottom { + padding: 0 20px 16px; + } + + .adm-divider { + margin-top: 0; + } + + .adm-button { + width: 100%; + } } + &-cells { display: flex; flex-direction: row; flex-wrap: wrap; justify-content: flex-start; align-items: stretch; - padding: 8px 8px 4px; + padding: 4px 8px; } + &-cell { flex: none; box-sizing: border-box; width: calc(100% / 7); - height: 48px; + min-height: 55px; margin-bottom: 4px; padding: 2px; color: var(--adm-color-text); cursor: pointer; + &&-today { color: var(--adm-color-primary); } + &&-disabled { color: var(--adm-color-light); - .adm-calendar-cell-bottom { + + .adm-calendar-view-cell-top, + .adm-calendar-view-cell-bottom { color: var(--adm-color-light); } } + &&-selected { && { - background: var(--adm-color-primary); - color: var(--adm-color-white); + background: rgba(22, 119, 255, 10%); + color: var(--adm-color-text); } - & .adm-calendar-cell-bottom { + + & .adm-calendar-view-cell-top, + & .adm-calendar-view-cell-bottom { color: var(--adm-color-white); } + &&-begin { + background: var(--adm-color-primary); + color: var(--adm-color-white); border-top-left-radius: 4px; border-bottom-left-radius: 4px; } + &&-end { + background: var(--adm-color-primary); + color: var(--adm-color-white); border-top-right-radius: 4px; border-bottom-right-radius: 4px; } @@ -79,16 +110,20 @@ display: flex; flex-direction: column; align-items: center; - justify-content: flex-end; - & &-top { + justify-content: center; + + & &-date { flex: none; - font-size: var(--adm-font-size-10); + line-height: 22px; + font-size: var(--adm-font-size-8); } + + & &-top, & &-bottom { flex: none; - font-size: var(--adm-font-size-4); - height: 12px; - line-height: 12px; + font-size: var(--adm-font-size-1); + height: 14px; + line-height: 14px; color: var(--adm-color-weak); } } @@ -101,8 +136,9 @@ border-bottom: solid 1px var(--adm-color-border); height: 45px; box-sizing: border-box; - font-size: var(--adm-font-size-7); + font-size: var(--adm-font-size-6); padding: 0 8px; + & &-cell { flex: 1; text-align: center; diff --git a/src/components/calendar-view/calendar-view.tsx b/src/components/calendar-view/calendar-view.tsx new file mode 100644 index 0000000000..6ed42221bb --- /dev/null +++ b/src/components/calendar-view/calendar-view.tsx @@ -0,0 +1,332 @@ +import React, { + forwardRef, + useState, + useImperativeHandle, + useMemo, +} from 'react' +import type { ReactNode } from 'react' +import { NativeProps, withNativeProps } from '../../utils/native-props' +import dayjs from 'dayjs' +import classNames from 'classnames' +import { mergeProps } from '../../utils/with-default-props' +import { useConfig } from '../config-provider' +import isoWeek from 'dayjs/plugin/isoWeek' +import isSameOrBefore from 'dayjs/plugin/isSameOrBefore' +import { usePropsValue } from '../../utils/use-props-value' +import { + convertValueToRange, + convertPageToDayjs, + DateRange, + Page, +} from './convert' + +dayjs.extend(isoWeek) +dayjs.extend(isSameOrBefore) + +const classPrefix = 'adm-calendar-view' + +export type CalendarViewRef = { + jumpTo: (page: Page | ((page: Page) => Page)) => void + jumpToToday: () => void + getDateRange: () => DateRange +} + +export type CalendarViewProps = { + title?: React.ReactNode + confirmText?: string + weekStartsOn?: 'Monday' | 'Sunday' + renderTop?: (date: Date) => React.ReactNode + renderDate?: (date: Date) => React.ReactNode + renderBottom?: (date: Date) => React.ReactNode + allowClear?: boolean + max?: Date + min?: Date + shouldDisableDate?: (date: Date) => boolean +} & ( + | { + selectionMode?: undefined + value?: undefined + defaultValue?: undefined + onChange?: undefined + } + | { + selectionMode: 'single' + value?: Date | null + defaultValue?: Date | null + onChange?: (val: Date | null) => void + } + | { + selectionMode: 'range' + value?: [Date, Date] | null + defaultValue?: [Date, Date] | null + onChange?: (val: [Date, Date] | null) => void + } +) & + NativeProps + +const defaultProps = { + weekStartsOn: 'Sunday', + defaultValue: null, + allowClear: true, + usePopup: true, + selectionMode: 'single', +} + +export const CalendarView = forwardRef( + (p, ref) => { + const today = dayjs() + const props = mergeProps(defaultProps, p) + const { locale } = useConfig() + const markItems = [...locale.Calendar.markItems] + if (props.weekStartsOn === 'Sunday') { + const item = markItems.pop() + if (item) markItems.unshift(item) + } + + const [dateRange, setDateRange] = usePropsValue({ + 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 [intermediate, setIntermediate] = useState(false) + + const [current, setCurrent] = useState(() => + dayjs(dateRange ? dateRange[0] : today).date(1) + ) + + useImperativeHandle(ref, () => ({ + jumpTo: pageOrPageGenerator => { + let page: Page + if (typeof pageOrPageGenerator === 'function') { + page = pageOrPageGenerator({ + year: current.year(), + month: current.month() + 1, + }) + } else { + page = pageOrPageGenerator + } + setCurrent(convertPageToDayjs(page)) + }, + jumpToToday: () => { + setCurrent(dayjs().date(1)) + }, + getDateRange: () => dateRange, + })) + + const header = ( +
+
+ {props.title ?? locale.Calendar.title} +
+
+ ) + + const maxDay = useMemo( + () => (props.max ? dayjs(props.max) : current.add(6, 'month')), + [props.max, current] + ) + const minDay = useMemo( + () => (props.min ? dayjs(props.min) : current), + [props.min, current] + ) + + function renderBody() { + const cells: ReactNode[] = [] + let monthIterator = minDay + // 遍历月份 + while (monthIterator.isSameOrBefore(maxDay, 'month')) { + const year = monthIterator.year() + const month = monthIterator.month() + const renderMap = { + year, + month: month + 1, + } + + cells.push( +
+
+ {locale.Calendar.yearAndMonth?.replace( + /\${(.*?)}/g, + (_, variable: keyof typeof renderMap) => { + return renderMap[variable]?.toString() + } + )} +
+
+ {/* 空格填充 */} + {Array( + props.weekStartsOn === 'Monday' + ? monthIterator.date(1).isoWeekday() - 1 + : monthIterator.date(1).isoWeekday() + ) + .fill(null) + .map((_, index) => ( +
+ ))} + {/* 遍历每月 */} + {Array(monthIterator.daysInMonth()) + .fill(null) + .map((_, index) => { + const d = monthIterator.date(index + 1) + let isSelect = false + let isBegin = false + let isEnd = false + let isSelectRowBegin = false + let isSelectRowEnd = 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')) + if (isSelect) { + isSelectRowBegin = + (cells.length % 7 === 0 || + d.isSame(d.startOf('month'), 'day')) && + !isBegin + isSelectRowEnd = + (cells.length % 7 === 6 || + d.isSame(d.endOf('month'), 'day')) && + !isEnd + } + } + const disabled = props.shouldDisableDate + ? props.shouldDisableDate(d.toDate()) + : (maxDay && d.isAfter(maxDay, 'day')) || + (minDay && d.isBefore(minDay, 'day')) + + const renderTop = () => { + const top = props.renderTop?.(d.toDate()) + + if (top) { + return top + } + + if (props.selectionMode === 'range') { + if (isBegin) { + return locale.Calendar.start + } + + if (isEnd) { + return locale.Calendar.end + } + } + + if (d.isSame(today, 'day') && !isSelect) { + return locale.Calendar.today + } + } + return ( +
{ + if (!props.selectionMode) return + if (disabled) return + const date = d.toDate() + function shouldClear() { + if (!props.allowClear) return false + if (!dateRange) return false + const [begin, end] = dateRange + return d.isSame(begin, 'date') && d.isSame(end, 'day') + } + if (props.selectionMode === 'single') { + if (props.allowClear && shouldClear()) { + setDateRange(null) + return + } + setDateRange([date, date]) + } else if (props.selectionMode === 'range') { + if (!dateRange) { + setDateRange([date, date]) + setIntermediate(true) + return + } + if (shouldClear()) { + setDateRange(null) + setIntermediate(false) + return + } + if (intermediate) { + const another = dateRange[0] + setDateRange( + another > date ? [date, another] : [another, date] + ) + setIntermediate(false) + } else { + setDateRange([date, date]) + setIntermediate(true) + } + } + }} + > +
+ {renderTop()} +
+
+ {props.renderDate + ? props.renderDate(d.toDate()) + : d.date()} +
+
+ {props.renderBottom?.(d.toDate())} +
+
+ ) + })} +
+
+ ) + + monthIterator = monthIterator.add(1, 'month') + } + + return cells + } + const body =
{renderBody()}
+ + const mark = ( +
+ {markItems.map((item, index) => ( +
+ {item} +
+ ))} +
+ ) + + return withNativeProps( + props, +
+ {header} + {mark} + {body} +
+ ) + } +) diff --git a/src/components/calendar-view/calendar-view.zh.md b/src/components/calendar-view/calendar-view.zh.md new file mode 100644 index 0000000000..621dc08365 --- /dev/null +++ b/src/components/calendar-view/calendar-view.zh.md @@ -0,0 +1,63 @@ +# CalendarView 日历 + +用于选择日期或日期区间。 + +CalendarView 是 [Calendar](/zh/components/calendar) 的内容区域。 + +## 示例 + +此处只展示了最简单的内容区域,其他更多用法可以参考 [Calendar](/zh/components/calendar) + + + +## CalendarView + +### 属性 + +| 属性 | 说明 | 类型 | 默认值 | 版本 | +| --- | --- | --- | --- | --- | +| allowClear | 是否允许再次点击后清除 | `boolean` | `true` | +| defaultValue | 默认选择的日期 | 同 `value` 属性 | - | +| max | 可选择范围的最大值 | `Date` | - | +| min | 可选择范围的最小值 | `Date` | - | +| onChange | 选择日期变化时触发 | 单选模式下为 `(val: Date \| null) => void`,多选模式下为 `(val: [Date, Date] \| null) => void` | - | +| renderTop | 日期顶部信息的渲染函数 | `(date: Date) => ReactNode \| null \| undefined` | - | +| renderBottom | 日期底部信息的渲染函数 | `(date: Date) => ReactNode \| null \| undefined` | - | +| selectionMode | 选择模式,不设置的话表示不支持选择 | `'single' \| 'range'` | - | +| shouldDisableDate | 判断日期是否可选,使用后会忽略 min 和 max 设置 | `(date: Date) => boolean` | - | +| title | 日期选择器的标题 | `React.ReactNode` | `日期选择` | +| value | 选择的日期 | 单选模式下为 `Date \| null`,多选模式下为 `[Date, Date] \| null` | - | +| weekStartsOn | 每周以周几作为第一天 | `'Monday' \| 'Sunday'` | `'Sunday'` | +| renderDate | 自定义日期渲染 | `(date: Date) => ReactNode` | - | 5.28.0 | + +### CSS 变量 + +暂无 + +### Ref + +| 属性 | 说明 | 类型 | +| --- | --- | --- | +| jumpTo | 跳转至指定日期的区间 | `(page: Page \| ((page: Page) => Page)) => void` | +| jumpToToday | 跳转至今日 | `() => void` | +| getDateRange | 获取日期 | `[Date, Date]` | + +```ts +type Page = { month: number; year: number } +``` + +你可以通过 Ref 手动控制日历的翻页,例如: + +```ts +// 跳回当月 +ref.current.jumpToToday() + +// 跳转至指定年月 +ref.current.jumpTo({ year: 2021, month: 1 }) + +// 跳转到三年之后 +ref.current.jumpTo(page => ({ + year: page.year + 3, + month: page.month, +})) +``` diff --git a/src/components/calendar/convert.ts b/src/components/calendar-view/convert.ts similarity index 100% rename from src/components/calendar/convert.ts rename to src/components/calendar-view/convert.ts diff --git a/src/components/calendar-view/demos/demo1.tsx b/src/components/calendar-view/demos/demo1.tsx new file mode 100644 index 0000000000..eab94397bd --- /dev/null +++ b/src/components/calendar-view/demos/demo1.tsx @@ -0,0 +1,11 @@ +import React from 'react' +import { CalendarView } from 'antd-mobile' +import { DemoBlock } from 'demos' + +export default () => { + return ( + + + + ) +} diff --git a/src/components/calendar-view/index.ts b/src/components/calendar-view/index.ts new file mode 100644 index 0000000000..f7f5369f3a --- /dev/null +++ b/src/components/calendar-view/index.ts @@ -0,0 +1,6 @@ +import './calendar-view.less' +import { CalendarView } from './calendar-view' + +export type { CalendarViewProps, CalendarViewRef } from './calendar-view' + +export default CalendarView diff --git a/src/components/calendar-view/tests/__snapshots__/calendar-view.test.tsx.snap b/src/components/calendar-view/tests/__snapshots__/calendar-view.test.tsx.snap new file mode 100644 index 0000000000..0cbe7a9e29 --- /dev/null +++ b/src/components/calendar-view/tests/__snapshots__/calendar-view.test.tsx.snap @@ -0,0 +1,9019 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Calendar custom top 1`] = ` +
+
+
+
+ 日期选择 +
+
+
+
+ 日 +
+
+ 一 +
+
+ 二 +
+
+ 三 +
+
+ 四 +
+
+ 五 +
+
+ 六 +
+
+
+
+
+ 2023年5月 +
+
+
+
+
+
+ 1 +
+
+
+
+
+
+ 2 +
+
+
+
+
+
+ 3 +
+
+
+
+
+
+ 4 +
+
+
+
+
+
+ 5 +
+
+
+
+
+ 周末 +
+
+ 6 +
+
+
+
+
+ 周末 +
+
+ 7 +
+
+
+
+
+
+ 8 +
+
+
+
+
+
+ 9 +
+
+
+
+
+
+ 10 +
+
+
+
+
+
+ 11 +
+
+
+
+
+
+ 12 +
+
+
+
+
+ 周末 +
+
+ 13 +
+
+
+
+
+ 周末 +
+
+ 14 +
+
+
+
+
+
+ 15 +
+
+
+
+
+
+ 16 +
+
+
+
+
+
+ 17 +
+
+
+
+
+
+ 18 +
+
+
+
+
+
+ 19 +
+
+
+
+
+ 周末 +
+
+ 20 +
+
+
+
+
+ 周末 +
+
+ 21 +
+
+
+
+
+ 今天 +
+
+ 22 +
+
+
+
+
+
+ 23 +
+
+
+
+
+
+ 24 +
+
+
+
+
+
+ 25 +
+
+
+
+
+
+ 26 +
+
+
+
+
+ 周末 +
+
+ 27 +
+
+
+
+
+ 周末 +
+
+ 28 +
+
+
+
+
+
+ 29 +
+
+
+
+
+
+ 30 +
+
+
+
+
+
+ 31 +
+
+
+
+
+
+
+
+`; + +exports[`Calendar jump to a day 1`] = ` +
+ + +
+
+
+ 日期选择 +
+
+
+
+ 日 +
+
+ 一 +
+
+ 二 +
+
+ 三 +
+
+ 四 +
+
+ 五 +
+
+ 六 +
+
+
+
+
+ 2021年1月 +
+
+
+
+
+
+
+
+
+
+ 1 +
+
+
+
+
+
+ 2 +
+
+
+
+
+
+ 3 +
+
+
+
+
+
+ 4 +
+
+
+
+
+
+ 5 +
+
+
+
+
+
+ 6 +
+
+
+
+
+
+ 7 +
+
+
+
+
+
+ 8 +
+
+
+
+
+
+ 9 +
+
+
+
+
+
+ 10 +
+
+
+
+
+
+ 11 +
+
+
+
+
+
+ 12 +
+
+
+
+
+
+ 13 +
+
+
+
+
+
+ 14 +
+
+
+
+
+
+ 15 +
+
+
+
+
+
+ 16 +
+
+
+
+
+
+ 17 +
+
+
+
+
+
+ 18 +
+
+
+
+
+
+ 19 +
+
+
+
+
+
+ 20 +
+
+
+
+
+
+ 21 +
+
+
+
+
+
+ 22 +
+
+
+
+
+
+ 23 +
+
+
+
+
+
+ 24 +
+
+
+
+
+
+ 25 +
+
+
+
+
+
+ 26 +
+
+
+
+
+
+ 27 +
+
+
+
+
+
+ 28 +
+
+
+
+
+
+ 29 +
+
+
+
+
+
+ 30 +
+
+
+
+
+
+ 31 +
+
+
+
+
+
+
+ 2021年2月 +
+
+
+
+
+
+ 1 +
+
+
+
+
+
+ 2 +
+
+
+
+
+
+ 3 +
+
+
+
+
+
+ 4 +
+
+
+
+
+
+ 5 +
+
+
+
+
+
+ 6 +
+
+
+
+
+
+ 7 +
+
+
+
+
+
+ 8 +
+
+
+
+
+
+ 9 +
+
+
+
+
+
+ 10 +
+
+
+
+
+
+ 11 +
+
+
+
+
+
+ 12 +
+
+
+
+
+
+ 13 +
+
+
+
+
+
+ 14 +
+
+
+
+
+
+ 15 +
+
+
+
+
+
+ 16 +
+
+
+
+
+
+ 17 +
+
+
+
+
+
+ 18 +
+
+
+
+
+
+ 19 +
+
+
+
+
+
+ 20 +
+
+
+
+
+
+ 21 +
+
+
+
+
+
+ 22 +
+
+
+
+
+
+ 23 +
+
+
+
+
+
+ 24 +
+
+
+
+
+
+ 25 +
+
+
+
+
+
+ 26 +
+
+
+
+
+
+ 27 +
+
+
+
+
+
+ 28 +
+
+
+
+
+
+
+ 2021年3月 +
+
+
+
+
+
+ 1 +
+
+
+
+
+
+ 2 +
+
+
+
+
+
+ 3 +
+
+
+
+
+
+ 4 +
+
+
+
+
+
+ 5 +
+
+
+
+
+
+ 6 +
+
+
+
+
+
+ 7 +
+
+
+
+
+
+ 8 +
+
+
+
+
+
+ 9 +
+
+
+
+
+
+ 10 +
+
+
+
+
+
+ 11 +
+
+
+
+
+
+ 12 +
+
+
+
+
+
+ 13 +
+
+
+
+
+
+ 14 +
+
+
+
+
+
+ 15 +
+
+
+
+
+
+ 16 +
+
+
+
+
+
+ 17 +
+
+
+
+
+
+ 18 +
+
+
+
+
+
+ 19 +
+
+
+
+
+
+ 20 +
+
+
+
+
+
+ 21 +
+
+
+
+
+
+ 22 +
+
+
+
+
+
+ 23 +
+
+
+
+
+
+ 24 +
+
+
+
+
+
+ 25 +
+
+
+
+
+
+ 26 +
+
+
+
+
+
+ 27 +
+
+
+
+
+
+ 28 +
+
+
+
+
+
+ 29 +
+
+
+
+
+
+ 30 +
+
+
+
+
+
+ 31 +
+
+
+
+
+
+
+ 2021年4月 +
+
+
+
+
+
+
+
+
+ 1 +
+
+
+
+
+
+ 2 +
+
+
+
+
+
+ 3 +
+
+
+
+
+
+ 4 +
+
+
+
+
+
+ 5 +
+
+
+
+
+
+ 6 +
+
+
+
+
+
+ 7 +
+
+
+
+
+
+ 8 +
+
+
+
+
+
+ 9 +
+
+
+
+
+
+ 10 +
+
+
+
+
+
+ 11 +
+
+
+
+
+
+ 12 +
+
+
+
+
+
+ 13 +
+
+
+
+
+
+ 14 +
+
+
+
+
+
+ 15 +
+
+
+
+
+
+ 16 +
+
+
+
+
+
+ 17 +
+
+
+
+
+
+ 18 +
+
+
+
+
+
+ 19 +
+
+
+
+
+
+ 20 +
+
+
+
+
+
+ 21 +
+
+
+
+
+
+ 22 +
+
+
+
+
+
+ 23 +
+
+
+
+
+
+ 24 +
+
+
+
+
+
+ 25 +
+
+
+
+
+
+ 26 +
+
+
+
+
+
+ 27 +
+
+
+
+
+
+ 28 +
+
+
+
+
+
+ 29 +
+
+
+
+
+
+ 30 +
+
+
+
+
+
+
+ 2021年5月 +
+
+
+
+
+
+
+
+
+
+
+ 1 +
+
+
+
+
+
+ 2 +
+
+
+
+
+
+ 3 +
+
+
+
+
+
+ 4 +
+
+
+
+
+
+ 5 +
+
+
+
+
+
+ 6 +
+
+
+
+
+
+ 7 +
+
+
+
+
+
+ 8 +
+
+
+
+
+
+ 9 +
+
+
+
+
+
+ 10 +
+
+
+
+
+
+ 11 +
+
+
+
+
+
+ 12 +
+
+
+
+
+
+ 13 +
+
+
+
+
+
+ 14 +
+
+
+
+
+
+ 15 +
+
+
+
+
+
+ 16 +
+
+
+
+
+
+ 17 +
+
+
+
+
+
+ 18 +
+
+
+
+
+
+ 19 +
+
+
+
+
+
+ 20 +
+
+
+
+
+
+ 21 +
+
+
+
+
+
+ 22 +
+
+
+
+
+
+ 23 +
+
+
+
+
+
+ 24 +
+
+
+
+
+
+ 25 +
+
+
+
+
+
+ 26 +
+
+
+
+
+
+ 27 +
+
+
+
+
+
+ 28 +
+
+
+
+
+
+ 29 +
+
+
+
+
+
+ 30 +
+
+
+
+
+
+ 31 +
+
+
+
+
+
+
+ 2021年6月 +
+
+
+
+
+
+
+ 1 +
+
+
+
+
+
+ 2 +
+
+
+
+
+
+ 3 +
+
+
+
+
+
+ 4 +
+
+
+
+
+
+ 5 +
+
+
+
+
+
+ 6 +
+
+
+
+
+
+ 7 +
+
+
+
+
+
+ 8 +
+
+
+
+
+
+ 9 +
+
+
+
+
+
+ 10 +
+
+
+
+
+
+ 11 +
+
+
+
+
+
+ 12 +
+
+
+
+
+
+ 13 +
+
+
+
+
+
+ 14 +
+
+
+
+
+
+ 15 +
+
+
+
+
+
+ 16 +
+
+
+
+
+
+ 17 +
+
+
+
+
+
+ 18 +
+
+
+
+
+
+ 19 +
+
+
+
+
+
+ 20 +
+
+
+
+
+
+ 21 +
+
+
+
+
+
+ 22 +
+
+
+
+
+
+ 23 +
+
+
+
+
+
+ 24 +
+
+
+
+
+
+ 25 +
+
+
+
+
+
+ 26 +
+
+
+
+
+
+ 27 +
+
+
+
+
+
+ 28 +
+
+
+
+
+
+ 29 +
+
+
+
+
+
+ 30 +
+
+
+
+
+
+
+ 2021年7月 +
+
+
+
+
+
+
+
+
+ 1 +
+
+
+
+
+
+ 2 +
+
+
+
+
+
+ 3 +
+
+
+
+
+
+ 4 +
+
+
+
+
+
+ 5 +
+
+
+
+
+
+ 6 +
+
+
+
+
+
+ 7 +
+
+
+
+
+
+ 8 +
+
+
+
+
+
+ 9 +
+
+
+
+
+
+ 10 +
+
+
+
+
+
+ 11 +
+
+
+
+
+
+ 12 +
+
+
+
+
+
+ 13 +
+
+
+
+
+
+ 14 +
+
+
+
+
+
+ 15 +
+
+
+
+
+
+ 16 +
+
+
+
+
+
+ 17 +
+
+
+
+
+
+ 18 +
+
+
+
+
+
+ 19 +
+
+
+
+
+
+ 20 +
+
+
+
+
+
+ 21 +
+
+
+
+
+
+ 22 +
+
+
+
+
+
+ 23 +
+
+
+
+
+
+ 24 +
+
+
+
+
+
+ 25 +
+
+
+
+
+
+ 26 +
+
+
+
+
+
+ 27 +
+
+
+
+
+
+ 28 +
+
+
+
+
+
+ 29 +
+
+
+
+
+
+ 30 +
+
+
+
+
+
+ 31 +
+
+
+
+
+
+
+
+`; + +exports[`Calendar jump to a day 2`] = ` +
+ + +
+
+
+ 日期选择 +
+
+
+
+ 日 +
+
+ 一 +
+
+ 二 +
+
+ 三 +
+
+ 四 +
+
+ 五 +
+
+ 六 +
+
+
+
+
+ 2023年5月 +
+
+
+
+
+
+ 1 +
+
+
+
+
+
+ 2 +
+
+
+
+
+
+ 3 +
+
+
+
+
+
+ 4 +
+
+
+
+
+
+ 5 +
+
+
+
+
+
+ 6 +
+
+
+
+
+
+ 7 +
+
+
+
+
+
+ 8 +
+
+
+
+
+
+ 9 +
+
+
+
+
+
+ 10 +
+
+
+
+
+
+ 11 +
+
+
+
+
+
+ 12 +
+
+
+
+
+
+ 13 +
+
+
+
+
+
+ 14 +
+
+
+
+
+
+ 15 +
+
+
+
+
+
+ 16 +
+
+
+
+
+
+ 17 +
+
+
+
+
+
+ 18 +
+
+
+
+
+
+ 19 +
+
+
+
+
+
+ 20 +
+
+
+
+
+
+ 21 +
+
+
+
+
+ 今日 +
+
+ 22 +
+
+
+
+
+
+ 23 +
+
+
+
+
+
+ 24 +
+
+
+
+
+
+ 25 +
+
+
+
+
+
+ 26 +
+
+
+
+
+
+ 27 +
+
+
+
+
+
+ 28 +
+
+
+
+
+
+ 29 +
+
+
+
+
+
+ 30 +
+
+
+
+
+
+ 31 +
+
+
+
+
+
+
+ 2023年6月 +
+
+
+
+
+
+
+
+
+ 1 +
+
+
+
+
+
+ 2 +
+
+
+
+
+
+ 3 +
+
+
+
+
+
+ 4 +
+
+
+
+
+
+ 5 +
+
+
+
+
+
+ 6 +
+
+
+
+
+
+ 7 +
+
+
+
+
+
+ 8 +
+
+
+
+
+
+ 9 +
+
+
+
+
+
+ 10 +
+
+
+
+
+
+ 11 +
+
+
+
+
+
+ 12 +
+
+
+
+
+
+ 13 +
+
+
+
+
+
+ 14 +
+
+
+
+
+
+ 15 +
+
+
+
+
+
+ 16 +
+
+
+
+
+
+ 17 +
+
+
+
+
+
+ 18 +
+
+
+
+
+
+ 19 +
+
+
+
+
+
+ 20 +
+
+
+
+
+
+ 21 +
+
+
+
+
+
+ 22 +
+
+
+
+
+
+ 23 +
+
+
+
+
+
+ 24 +
+
+
+
+
+
+ 25 +
+
+
+
+
+
+ 26 +
+
+
+
+
+
+ 27 +
+
+
+
+
+
+ 28 +
+
+
+
+
+
+ 29 +
+
+
+
+
+
+ 30 +
+
+
+
+
+
+
+ 2023年7月 +
+
+
+
+
+
+
+
+
+
+
+ 1 +
+
+
+
+
+
+ 2 +
+
+
+
+
+
+ 3 +
+
+
+
+
+
+ 4 +
+
+
+
+
+
+ 5 +
+
+
+
+
+
+ 6 +
+
+
+
+
+
+ 7 +
+
+
+
+
+
+ 8 +
+
+
+
+
+
+ 9 +
+
+
+
+
+
+ 10 +
+
+
+
+
+
+ 11 +
+
+
+
+
+
+ 12 +
+
+
+
+
+
+ 13 +
+
+
+
+
+
+ 14 +
+
+
+
+
+
+ 15 +
+
+
+
+
+
+ 16 +
+
+
+
+
+
+ 17 +
+
+
+
+
+
+ 18 +
+
+
+
+
+
+ 19 +
+
+
+
+
+
+ 20 +
+
+
+
+
+
+ 21 +
+
+
+
+
+
+ 22 +
+
+
+
+
+
+ 23 +
+
+
+
+
+
+ 24 +
+
+
+
+
+
+ 25 +
+
+
+
+
+
+ 26 +
+
+
+
+
+
+ 27 +
+
+
+
+
+
+ 28 +
+
+
+
+
+
+ 29 +
+
+
+
+
+
+ 30 +
+
+
+
+
+
+ 31 +
+
+
+
+
+
+
+ 2023年8月 +
+
+
+
+
+
+
+ 1 +
+
+
+
+
+
+ 2 +
+
+
+
+
+
+ 3 +
+
+
+
+
+
+ 4 +
+
+
+
+
+
+ 5 +
+
+
+
+
+
+ 6 +
+
+
+
+
+
+ 7 +
+
+
+
+
+
+ 8 +
+
+
+
+
+
+ 9 +
+
+
+
+
+
+ 10 +
+
+
+
+
+
+ 11 +
+
+
+
+
+
+ 12 +
+
+
+
+
+
+ 13 +
+
+
+
+
+
+ 14 +
+
+
+
+
+
+ 15 +
+
+
+
+
+
+ 16 +
+
+
+
+
+
+ 17 +
+
+
+
+
+
+ 18 +
+
+
+
+
+
+ 19 +
+
+
+
+
+
+ 20 +
+
+
+
+
+
+ 21 +
+
+
+
+
+
+ 22 +
+
+
+
+
+
+ 23 +
+
+
+
+
+
+ 24 +
+
+
+
+
+
+ 25 +
+
+
+
+
+
+ 26 +
+
+
+
+
+
+ 27 +
+
+
+
+
+
+ 28 +
+
+
+
+
+
+ 29 +
+
+
+
+
+
+ 30 +
+
+
+
+
+
+ 31 +
+
+
+
+
+
+
+ 2023年9月 +
+
+
+
+
+
+
+
+
+
+ 1 +
+
+
+
+
+
+ 2 +
+
+
+
+
+
+ 3 +
+
+
+
+
+
+ 4 +
+
+
+
+
+
+ 5 +
+
+
+
+
+
+ 6 +
+
+
+
+
+
+ 7 +
+
+
+
+
+
+ 8 +
+
+
+
+
+
+ 9 +
+
+
+
+
+
+ 10 +
+
+
+
+
+
+ 11 +
+
+
+
+
+
+ 12 +
+
+
+
+
+
+ 13 +
+
+
+
+
+
+ 14 +
+
+
+
+
+
+ 15 +
+
+
+
+
+
+ 16 +
+
+
+
+
+
+ 17 +
+
+
+
+
+
+ 18 +
+
+
+
+
+
+ 19 +
+
+
+
+
+
+ 20 +
+
+
+
+
+
+ 21 +
+
+
+
+
+
+ 22 +
+
+
+
+
+
+ 23 +
+
+
+
+
+
+ 24 +
+
+
+
+
+
+ 25 +
+
+
+
+
+
+ 26 +
+
+
+
+
+
+ 27 +
+
+
+
+
+
+ 28 +
+
+
+
+
+
+ 29 +
+
+
+
+
+
+ 30 +
+
+
+
+
+
+
+ 2023年10月 +
+
+
+
+
+
+
+
+
+
+
+
+ 1 +
+
+
+
+
+
+ 2 +
+
+
+
+
+
+ 3 +
+
+
+
+
+
+ 4 +
+
+
+
+
+
+ 5 +
+
+
+
+
+
+ 6 +
+
+
+
+
+
+ 7 +
+
+
+
+
+
+ 8 +
+
+
+
+
+
+ 9 +
+
+
+
+
+
+ 10 +
+
+
+
+
+
+ 11 +
+
+
+
+
+
+ 12 +
+
+
+
+
+
+ 13 +
+
+
+
+
+
+ 14 +
+
+
+
+
+
+ 15 +
+
+
+
+
+
+ 16 +
+
+
+
+
+
+ 17 +
+
+
+
+
+
+ 18 +
+
+
+
+
+
+ 19 +
+
+
+
+
+
+ 20 +
+
+
+
+
+
+ 21 +
+
+
+
+
+
+ 22 +
+
+
+
+
+
+ 23 +
+
+
+
+
+
+ 24 +
+
+
+
+
+
+ 25 +
+
+
+
+
+
+ 26 +
+
+
+
+
+
+ 27 +
+
+
+
+
+
+ 28 +
+
+
+
+
+
+ 29 +
+
+
+
+
+
+ 30 +
+
+
+
+
+
+ 31 +
+
+
+
+
+
+
+ 2023年11月 +
+
+
+
+
+
+
+
+ 1 +
+
+
+
+
+
+ 2 +
+
+
+
+
+
+ 3 +
+
+
+
+
+
+ 4 +
+
+
+
+
+
+ 5 +
+
+
+
+
+
+ 6 +
+
+
+
+
+
+ 7 +
+
+
+
+
+
+ 8 +
+
+
+
+
+
+ 9 +
+
+
+
+
+
+ 10 +
+
+
+
+
+
+ 11 +
+
+
+
+
+
+ 12 +
+
+
+
+
+
+ 13 +
+
+
+
+
+
+ 14 +
+
+
+
+
+
+ 15 +
+
+
+
+
+
+ 16 +
+
+
+
+
+
+ 17 +
+
+
+
+
+
+ 18 +
+
+
+
+
+
+ 19 +
+
+
+
+
+
+ 20 +
+
+
+
+
+
+ 21 +
+
+
+
+
+
+ 22 +
+
+
+
+
+
+ 23 +
+
+
+
+
+
+ 24 +
+
+
+
+
+
+ 25 +
+
+
+
+
+
+ 26 +
+
+
+
+
+
+ 27 +
+
+
+
+
+
+ 28 +
+
+
+
+
+
+ 29 +
+
+
+
+
+
+ 30 +
+
+
+
+
+
+
+
+`; + +exports[`Calendar range mode 1`] = ` +
+
+
+
+ 日期选择 +
+
+
+
+ 日 +
+
+ 一 +
+
+ 二 +
+
+ 三 +
+
+ 四 +
+
+ 五 +
+
+ 六 +
+
+
+
+
+ 2023年5月 +
+
+
+
+
+
+ 1 +
+
+
+
+
+
+ 2 +
+
+
+
+
+
+ 3 +
+
+
+
+
+ 开始 +
+
+ 4 +
+
+
+
+
+
+ 5 +
+
+
+
+
+
+ 6 +
+
+
+
+
+ 结束 +
+
+ 7 +
+
+
+
+
+
+ 8 +
+
+
+
+
+
+ 9 +
+
+
+
+
+
+ 10 +
+
+
+
+
+
+ 11 +
+
+
+
+
+
+ 12 +
+
+
+
+
+
+ 13 +
+
+
+
+
+
+ 14 +
+
+
+
+
+
+ 15 +
+
+
+
+
+
+ 16 +
+
+
+
+
+
+ 17 +
+
+
+
+
+
+ 18 +
+
+
+
+
+
+ 19 +
+
+
+
+
+
+ 20 +
+
+
+
+
+
+ 21 +
+
+
+
+
+ 今日 +
+
+ 22 +
+
+
+
+
+
+ 23 +
+
+
+
+
+
+ 24 +
+
+
+
+
+
+ 25 +
+
+
+
+
+
+ 26 +
+
+
+
+
+
+ 27 +
+
+
+
+
+
+ 28 +
+
+
+
+
+
+ 29 +
+
+
+
+
+
+ 30 +
+
+
+
+
+
+ 31 +
+
+
+
+
+
+
+
+`; + +exports[`Calendar single mode 1`] = ` +
+
+
+
+ 日期选择 +
+
+
+
+ 日 +
+
+ 一 +
+
+ 二 +
+
+ 三 +
+
+ 四 +
+
+ 五 +
+
+ 六 +
+
+
+
+
+ 2023年5月 +
+
+
+
+
+
+ 1 +
+
+
+
+
+
+ 2 +
+
+
+
+
+
+ 3 +
+
+
+
+
+
+ 4 +
+
+
+
+
+
+ 5 +
+
+
+
+
+
+ 6 +
+
+
+
+
+
+ 7 +
+
+
+
+
+
+ 8 +
+
+
+
+
+
+ 9 +
+
+
+
+
+
+ 10 +
+
+
+
+
+
+ 11 +
+
+
+
+
+
+ 12 +
+
+
+
+
+
+ 13 +
+
+
+
+
+
+ 14 +
+
+
+
+
+
+ 15 +
+
+
+
+
+
+ 16 +
+
+
+
+
+
+ 17 +
+
+
+
+
+
+ 18 +
+
+
+
+
+
+ 19 +
+
+
+
+
+
+ 20 +
+
+
+
+
+
+ 21 +
+
+
+
+
+ 今日 +
+
+ 22 +
+
+
+
+
+
+ 23 +
+
+
+
+
+
+ 24 +
+
+
+
+
+
+ 25 +
+
+
+
+
+
+ 26 +
+
+
+
+
+
+ 27 +
+
+
+
+
+
+ 28 +
+
+
+
+
+
+ 29 +
+
+
+
+
+
+ 30 +
+
+
+
+
+
+ 31 +
+
+
+
+
+
+
+
+`; + +exports[`Calendar week start on Monday 1`] = ` +
+
+
+
+ 日期选择 +
+
+
+
+ 一 +
+
+ 二 +
+
+ 三 +
+
+ 四 +
+
+ 五 +
+
+ 六 +
+
+ 日 +
+
+
+
+
+ 2023年5月 +
+
+
+
+
+ 1 +
+
+
+
+
+
+ 2 +
+
+
+
+
+
+ 3 +
+
+
+
+
+
+ 4 +
+
+
+
+
+
+ 5 +
+
+
+
+
+
+ 6 +
+
+
+
+
+
+ 7 +
+
+
+
+
+
+ 8 +
+
+
+
+
+
+ 9 +
+
+
+
+
+
+ 10 +
+
+
+
+
+
+ 11 +
+
+
+
+
+
+ 12 +
+
+
+
+
+
+ 13 +
+
+
+
+
+
+ 14 +
+
+
+
+
+
+ 15 +
+
+
+
+
+
+ 16 +
+
+
+
+
+
+ 17 +
+
+
+
+
+
+ 18 +
+
+
+
+
+
+ 19 +
+
+
+
+
+
+ 20 +
+
+
+
+
+
+ 21 +
+
+
+
+
+ 今日 +
+
+ 22 +
+
+
+
+
+
+ 23 +
+
+
+
+
+
+ 24 +
+
+
+
+
+
+ 25 +
+
+
+
+
+
+ 26 +
+
+
+
+
+
+ 27 +
+
+
+
+
+
+ 28 +
+
+
+
+
+
+ 29 +
+
+
+
+
+
+ 30 +
+
+
+
+
+
+ 31 +
+
+
+
+
+
+
+
+`; diff --git a/src/components/calendar-view/tests/calendar-view.test.tsx b/src/components/calendar-view/tests/calendar-view.test.tsx new file mode 100644 index 0000000000..c91e57f2fc --- /dev/null +++ b/src/components/calendar-view/tests/calendar-view.test.tsx @@ -0,0 +1,163 @@ +import React, { useRef } from 'react' +import { render, testA11y, fireEvent } from 'testing' +import CalendarView, { CalendarViewRef } from '..' +import dayjs from 'dayjs' +import MockDate from 'mockdate' + +const classPrefix = `adm-calendar-view` + +// mock today +MockDate.set(new Date('2023-05-22')) + +const mixDate: Date = new Date('2023-05-01') +const maxDate: Date = new Date('2023-05-31') +const singleDate: Date = new Date('2023-05-03') +const rangeDate: [Date, Date] = [new Date('2023-05-04'), new Date('2023-05-07')] + +describe('Calendar', () => { + test('a11y', async () => { + await testA11y() + }) + + test('single mode', async () => { + const fn = jest.fn() + const { container, getAllByText } = render( + + ) + + expect(container).toMatchSnapshot() + const dateEl = getAllByText(15)[0] + fireEvent.click(dateEl) + expect(dateEl.parentElement).toHaveClass(`${classPrefix}-cell-selected`) + expect(fn).toBeCalled() + }) + + test('range mode', async () => { + const fn = jest.fn() + const { container, getByText } = render( + + ) + + expect(container).toMatchSnapshot() + const [startEl, endEl] = [getByText(20), getByText(26)] + fireEvent.click(startEl) + fireEvent.click(endEl) + expect( + document.querySelectorAll(`.${classPrefix}-cell-selected`).length + ).toBe(7) + expect(fn.mock.calls[1][0].map((d: Date) => d.toDateString())).toEqual([ + 'Sat May 20 2023', + 'Fri May 26 2023', + ]) + }) + + test('jump to a day', async () => { + const App = () => { + const ref = useRef(null) + return ( + <> + + + + + ) + } + const { container, getByText } = render() + + fireEvent.click(getByText('jumpTo')) + expect(container).toMatchSnapshot() + + fireEvent.click(getByText('jumpToToday')) + expect(container).toMatchSnapshot() + }) + + test('week start on Monday', async () => { + const { container } = render( + + ) + expect(container).toMatchSnapshot() + }) + + test(`can't allow to clear`, async () => { + const { getByText } = render( + + ) + + const dateEl = getByText(16) + fireEvent.click(dateEl) + fireEvent.click(dateEl) + expect(dateEl.parentElement).toHaveClass(`${classPrefix}-cell-selected`) + }) + + test('custom top', async () => { + const today = dayjs() + const { container } = render( + { + if (dayjs(date).isSame(today, 'day')) return '今天' + if (date.getDay() === 0 || date.getDay() === 6) { + return '周末' + } + }} + /> + ) + expect(container).toMatchSnapshot() + }) + + test('custom date', () => { + render( + { + return
{dayjs(date).date()}
+ }} + /> + ) + expect(document.getElementsByClassName('custom-cell').length).toBe(31) + }) + + test('custom bottom', () => { + render( + { + return
{dayjs(date).date()}
+ }} + /> + ) + expect(document.getElementsByClassName('custom-cell').length).toBe(31) + }) +}) diff --git a/src/components/calendar/calendar.en.md b/src/components/calendar/calendar.en.md index b5d4c4974e..41320924b0 100644 --- a/src/components/calendar/calendar.en.md +++ b/src/components/calendar/calendar.en.md @@ -16,29 +16,32 @@ When the user needs to enter a date, he can select it in the pop-up date panel. - - ## Calendar ### Props | Name | Description | Type | Default | Version | | --- | --- | --- | --- | --- | +| visible | To show or hide the Cclendar | `boolean` | `true` | +| confirmText | The text of confirm button | `string` | `Confirm` | +| popupClassName | The custom class name of the popup | `string` | - | +| popupStyle | The custom style of the popup | `React.CSSProperties` | - | +| popupBodyStyle | The custom style of the popup body | `React.CSSProperties` | - | +| forceRender | Render content forcely,When ref is passed,always be true | `boolean` | `false` | +| closeOnMaskClick | Whether to close after clicking the mask layer | `boolean` | `true` | +| onClose | Triggered when closed | `() => void` | - | +| onMaskClick | Triggered when the mask is clicked | `() => void` | - | | allowClear | Whether to allow clearing after another click. | `boolean` | `true` | | defaultValue | The default selected date or date range. | Same as `value` prop. | - | | max | Maximum value of a selectable range. | `Date` | - | | min | Minimum value of a selectable range. | `Date` | - | - | -| maxPage | Maximum visible page of date. | `Page` | -| minPage | Minimum visible page of date. | `Page` | - | -| nextMonthButton | Contents of the Next Month button on the navigation pane | `React.ReactNode` | `>` | -| nextYearButton | Contents of the next Year button on the navigation pane | `React.ReactNode` | `>>` | | 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` | - | -| prevMonthButton | Contents of the Last Month button on the navigation pane | `React.ReactNode` | `<` | -| prevYearButton | Contents of the Last year button on the navigation pane | `React.ReactNode` | `<<` | -| renderLabel | The label render function. | `(date: Date) => ReactNode \| null \| undefined` | - | +| onConfirm | Trigger when confirm button is clicked. | `(val: Date \| null) => void` when selection mode is "single",`(val: [Date, Date] \| null) => void` when selection mode is "range" | - | +| renderTop | The top information of date render function. | `(date: Date) => ReactNode \| null \| undefined` | - | +| renderBottom | The bottom information of date render function. | `(date: Date) => ReactNode \| null \| undefined` | - | | selectionMode | The selection mode. Disable selection when this prop is not set. | `'single' \| 'range'` | - | | shouldDisableDate | Set whether the date is disable selection. The min and max Settings are ignored | `(date: Date) => boolean` | - | +| title | The title of calendar | `React.ReactNode` | `Date selection` | | value | The selected date or date range. | `Date \| null` when selection mode is "single". `[Date, Date] \| null` when selection mode is "range" | - | | weekStartsOn | Week starts on which day. | `'Monday' \| 'Sunday'` | `'Sunday'` | | renderDate | Custom date rendering. | `(date: Date) => ReactNode` | - | 5.28.0 | @@ -53,6 +56,7 @@ Not supported yet. | --- | --- | --- | | jumpTo | Jump to specified page | `(page: Page \| ((page: Page) => Page)) => void` | | jumpToToday | Jump to today's page | `() => void` | +| getDateRange | get date | `[Date, Date]` | ```ts type Page = { month: number; year: number } diff --git a/src/components/calendar/calendar.tsx b/src/components/calendar/calendar.tsx index 641267eae3..057dabb8d1 100644 --- a/src/components/calendar/calendar.tsx +++ b/src/components/calendar/calendar.tsx @@ -1,333 +1,123 @@ -import React, { - forwardRef, - useState, - useImperativeHandle, - useMemo, -} from 'react' -import type { ReactNode } from 'react' -import { NativeProps, withNativeProps } from '../../utils/native-props' -import dayjs from 'dayjs' +import React, { forwardRef, useRef } from 'react' +import { withNativeProps } from '../../utils/native-props' import classNames from 'classnames' +import Button from '../button' +import Divider from '../divider' +import Popup from '../popup' 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 { useUpdateEffect } from 'ahooks' -import { usePropsValue } from '../../utils/use-props-value' -import { replaceMessage } from '../../utils/replace-message' -import { - convertValueToRange, - convertPageToDayjs, - DateRange, - Page, -} from './convert' - -dayjs.extend(isoWeek) +import CalendarView, { + CalendarViewProps, + CalendarViewRef, +} from '../calendar-view' const classPrefix = 'adm-calendar' -export type CalendarRef = { - jumpTo: (page: Page | ((page: Page) => Page)) => void - jumpToToday: () => void -} - -export type CalendarProps = { - prevMonthButton?: ReactNode - prevYearButton?: ReactNode - nextMonthButton?: ReactNode - nextYearButton?: ReactNode - onPageChange?: (year: number, month: number) => void - weekStartsOn?: 'Monday' | 'Sunday' - renderLabel?: (date: Date) => ReactNode - renderDate?: (date: Date) => ReactNode - allowClear?: boolean - max?: Date - min?: Date - shouldDisableDate?: (date: Date) => boolean - minPage?: Page - maxPage?: Page +export type CalendarRef = CalendarViewRef + +export type CalendarProps = CalendarViewProps & { + visible?: boolean + confirmText?: string + popupClassName?: string + popupStyle?: React.CSSProperties + popupBodyStyle?: React.CSSProperties + forceRender?: true + closeOnMaskClick?: boolean + onClose?: () => void + onMaskClick?: () => void } & ( - | { - selectionMode?: undefined - value?: undefined - defaultValue?: undefined - onChange?: undefined - } - | { - selectionMode: 'single' - value?: Date | null - defaultValue?: Date | null - onChange?: (val: Date | null) => void - } - | { - selectionMode: 'range' - value?: [Date, Date] | null - defaultValue?: [Date, Date] | null - onChange?: (val: [Date, Date] | null) => void - } -) & - NativeProps + | { + selectionMode?: undefined + onConfirm?: undefined + } + | { + selectionMode: 'single' + onConfirm?: (val: Date | null) => void + } + | { + selectionMode: 'range' + onConfirm?: (val: [Date, Date] | null) => void + } + ) const defaultProps = { weekStartsOn: 'Sunday', defaultValue: null, allowClear: true, - prevMonthButton: , - prevYearButton: , - nextMonthButton: , - nextYearButton: , + usePopup: true, + selectionMode: 'single', } export const Calendar = forwardRef((p, ref) => { - const today = dayjs() const props = mergeProps(defaultProps, p) const { locale } = useConfig() - const markItems = [...locale.Calendar.markItems] - if (props.weekStartsOn === 'Sunday') { - const item = markItems.pop() - if (item) markItems.unshift(item) - } - - const [dateRange, setDateRange] = usePropsValue({ - 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 [intermediate, setIntermediate] = useState(false) - - const [current, setCurrent] = useState(() => - dayjs(dateRange ? dateRange[0] : today).date(1) - ) - - useUpdateEffect(() => { - props.onPageChange?.(current.year(), current.month() + 1) - }, [current]) - - useImperativeHandle(ref, () => ({ - jumpTo: pageOrPageGenerator => { - let page: Page - if (typeof pageOrPageGenerator === 'function') { - page = pageOrPageGenerator({ - year: current.year(), - month: current.month() + 1, - }) - } else { - page = pageOrPageGenerator - } - setCurrent(convertPageToDayjs(page)) - }, - jumpToToday: () => { - setCurrent(dayjs().date(1)) - }, - })) - - const handlePageChange = ( - action: 'subtract' | 'add', - num: number, - type: 'month' | 'year' - ) => { - const nxtCurrent = current[action](num, type) - if (action === 'subtract' && props.minPage) { - const minPage = convertPageToDayjs(props.minPage) - if (nxtCurrent.isBefore(minPage, type)) { - return - } - } - if (action === 'add' && props.maxPage) { - const maxPage = convertPageToDayjs(props.maxPage) - if (nxtCurrent.isAfter(maxPage, type)) { - return - } - } - setCurrent(nxtCurrent) - } - - const header = ( - - ) - - const maxDay = useMemo(() => props.max && dayjs(props.max), [props.max]) - const minDay = useMemo(() => props.min && dayjs(props.min), [props.min]) - - function renderCells() { - const cells: ReactNode[] = [] - let iterator = current.subtract(current.isoWeekday(), 'day') - if (props.weekStartsOn === 'Monday') { - iterator = iterator.add(1, 'day') - } - while (cells.length < 6 * 7) { - const d = iterator - let isSelect = false - let isBegin = false - let isEnd = false - let isSelectRowBegin = false - let isSelectRowEnd = 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')) - if (isSelect) { - isSelectRowBegin = - (cells.length % 7 === 0 || d.isSame(d.startOf('month'), 'day')) && - !isBegin - isSelectRowEnd = - (cells.length % 7 === 6 || d.isSame(d.endOf('month'), 'day')) && - !isEnd - } - } - const inThisMonth = d.month() === current.month() - const disabled = props.shouldDisableDate - ? props.shouldDisableDate(d.toDate()) - : (maxDay && d.isAfter(maxDay, 'day')) || - (minDay && d.isBefore(minDay, 'day')) - cells.push( -
(null)) as React.RefObject + + const { + visible, + confirmText, + popupClassName, + popupStyle, + popupBodyStyle, + forceRender, + closeOnMaskClick, + onClose, + onConfirm, + onMaskClick, + ...calendarViewProps + } = props + + const footer = ( +
+ +
+
- ) - iterator = iterator.add(1, 'day') - } - return cells - } - const body =
{renderCells()}
- - const mark = ( -
- {markItems.map((item, index) => ( -
- {item} -
- ))} + {confirmText ?? locale.Calendar.confirm} + +
) return withNativeProps( props,
- {header} - {mark} - {body} + { + onMaskClick?.() + if (closeOnMaskClick) { + onClose?.() + } + }} + > + + {footer} +
) }) diff --git a/src/components/calendar/calendar.zh.md b/src/components/calendar/calendar.zh.md index ed01567c4a..9fe79f0ba2 100644 --- a/src/components/calendar/calendar.zh.md +++ b/src/components/calendar/calendar.zh.md @@ -16,29 +16,32 @@ - - ## Calendar ### 属性 | 属性 | 说明 | 类型 | 默认值 | 版本 | | --- | --- | --- | --- | --- | +| visible | 显示隐藏 | `boolean` | `true` | +| confirmText | 确认按钮文案 | `string` | `确认` | +| popupClassName | 弹出层类名 | `string` | - | +| popupStyle | 弹层容器的自定义样式 | `React.CSSProperties` | - | +| popupBodyStyle | 容器内部自定义样式 | `React.CSSProperties` | - | +| forceRender | 强制渲染内容,当传递 ref 时,会强制设为 true | `boolean` | `false` | +| closeOnMaskClick | 点击背景蒙层后是否关闭 | `boolean` | `true` | +| onClose | 关闭时触发 | `() => void` | - | +| onMaskClick | 点击背景蒙层时触发 | `() => void` | - | | allowClear | 是否允许再次点击后清除 | `boolean` | `true` | | defaultValue | 默认选择的日期 | 同 `value` 属性 | - | | max | 可选择范围的最大值 | `Date` | - | | min | 可选择范围的最小值 | `Date` | - | -| maxPage | 可切换到的最晚日期 | `Page` | - | -| minPage | 可切换到的最早日期 | `Page` | - | -| nextMonthButton | 导航窗格上的“下一月”按钮的内容 | `React.ReactNode` | `>` | -| nextYearButton | 导航窗格上的“下一年”按钮的内容 | `React.ReactNode` | `>>` | | onChange | 选择日期变化时触发 | 单选模式下为 `(val: Date \| null) => void`,多选模式下为 `(val: [Date, Date] \| null) => void` | - | -| onPageChange | 切换月或年时触发 | `(year: number, month: number) => void` | - | -| prevMonthButton | 导航窗格上的“上一月”按钮的内容 | `React.ReactNode` | `<` | -| prevYearButton | 导航窗格上的“上一年”按钮的内容 | `React.ReactNode` | `<<` | -| renderLabel | 标注信息的渲染函数 | `(date: Date) => ReactNode \| null \| undefined` | - | +| onConfirm | 点击确认按钮时触发 | 单选模式下为 `(val: Date \| null) => void`,多选模式下为 `(val: [Date, Date] \| null) => void` | - | +| renderTop | 日期顶部信息的渲染函数 | `(date: Date) => ReactNode \| null \| undefined` | - | +| renderBottom | 日期底部信息的渲染函数 | `(date: Date) => ReactNode \| null \| undefined` | - | | selectionMode | 选择模式,不设置的话表示不支持选择 | `'single' \| 'range'` | - | | shouldDisableDate | 判断日期是否可选,使用后会忽略 min 和 max 设置 | `(date: Date) => boolean` | - | +| title | 日期选择器的标题 | `React.ReactNode` | `日期选择` | | value | 选择的日期 | 单选模式下为 `Date \| null`,多选模式下为 `[Date, Date] \| null` | - | | weekStartsOn | 每周以周几作为第一天 | `'Monday' \| 'Sunday'` | `'Sunday'` | | renderDate | 自定义日期渲染 | `(date: Date) => ReactNode` | - | 5.28.0 | @@ -51,8 +54,9 @@ | 属性 | 说明 | 类型 | | --- | --- | --- | -| jumpTo | 跳转至指定日期的页面 | `(page: Page \| ((page: Page) => Page)) => void` | +| jumpTo | 跳转至指定日期的区间 | `(page: Page \| ((page: Page) => Page)) => void` | | jumpToToday | 跳转至今日 | `() => void` | +| getDateRange | 获取日期 | `[Date, Date]` | ```ts type Page = { month: number; year: number } diff --git a/src/components/calendar/demos/demo1.tsx b/src/components/calendar/demos/demo1.tsx index 19d00b9aeb..528bc15b59 100644 --- a/src/components/calendar/demos/demo1.tsx +++ b/src/components/calendar/demos/demo1.tsx @@ -1,50 +1,73 @@ -import React from 'react' -import { Calendar } from 'antd-mobile' -import { DemoBlock, DemoDescription } from 'demos' +import dayjs from 'dayjs' +import React, { useState } from 'react' +import { Calendar, List } from 'antd-mobile' -const defaultSingle = new Date('2022-03-09') const defaultRange: [Date, Date] = [ - new Date('2022-03-09'), - new Date('2022-03-21'), + dayjs().toDate(), + dayjs().add(2, 'day').toDate(), ] export default () => { - return ( - <> - - - - 如果你不设置 selectionMode 属性,那么日历默认是不支持进行选择操作的 - - + const [val, setVal] = useState<[Date, Date] | null>(() => [ + dayjs().subtract(2, 'day').toDate(), + dayjs().add(2, 'day').toDate(), + ]) + const [visible1, setVisible1] = useState(false) + const [visible2, setVisible2] = useState(false) + const [visible3, setVisible3] = useState(false) - - 上一月} - nextMonthButton={下一月} - prevYearButton={上一年} - nextYearButton={下一年} - /> - + const singleDate: Date = new Date('2023-06-03') - + return ( + + { + setVisible1(true) + }} + > + 选择单个日期 setVisible1(false)} + onMaskClick={() => setVisible1(false)} + /> + + { + setVisible2(true) + }} + > + 选择日期范围 + setVisible2(false)} + onMaskClick={() => setVisible2(false)} onChange={val => { console.log(val) }} /> - - + + { + setVisible3(true) + }} + > + 受控日期选择 setVisible3(false)} + onMaskClick={() => setVisible3(false)} onChange={val => { - console.log(val) + setVal(val) }} /> - - + + ) } diff --git a/src/components/calendar/demos/demo2.tsx b/src/components/calendar/demos/demo2.tsx index 9c4027e352..d9f7c7d9c0 100644 --- a/src/components/calendar/demos/demo2.tsx +++ b/src/components/calendar/demos/demo2.tsx @@ -1,25 +1,73 @@ -import dayjs from 'dayjs' import React, { useState } from 'react' -import { Calendar } from 'antd-mobile' -import { DemoBlock } from 'demos' +import { Calendar, List } from 'antd-mobile' + +const min = new Date() +min.setDate(5) +const max = new Date() +max.setDate(20) export default () => { - const today = dayjs() - const [val, setVal] = useState<[Date, Date] | null>(() => [ - today.subtract(2, 'day').toDate(), - today.add(2, 'day').toDate(), - ]) + const [visible1, setVisible1] = useState(false) + const [visible2, setVisible2] = useState(false) + const [visible3, setVisible3] = useState(false) + const [visible4, setVisible4] = useState(false) + return ( - <> - + + { + setVisible1(true) + }} + > + 自定义标题 + setVisible1(false)} + onMaskClick={() => setVisible1(false)} + /> + + { + setVisible2(true) + }} + > + 自定义确认文案 + setVisible2(false)} + onMaskClick={() => setVisible2(false)} + /> + + { + setVisible3(true) + }} + > + 周一作为每周的第一天 + setVisible3(false)} + onMaskClick={() => setVisible3(false)} + /> + + { + setVisible4(true) + }} + > + 限制日期范围 { - setVal(val) - }} + visible={visible4} + onClose={() => setVisible4(false)} + onMaskClick={() => setVisible4(false)} /> - - + + ) } diff --git a/src/components/calendar/demos/demo4.less b/src/components/calendar/demos/demo3.less similarity index 89% rename from src/components/calendar/demos/demo4.less rename to src/components/calendar/demos/demo3.less index f0bc9bdd99..49cf099068 100644 --- a/src/components/calendar/demos/demo4.less +++ b/src/components/calendar/demos/demo3.less @@ -1,6 +1,7 @@ @prefix: ~'adm-calendar'; -.calendar-custom { +.calendar-custom, +.calendar-popup-custom { .@{prefix}-cell { justify-content: center; height: 38px; @@ -84,3 +85,17 @@ } } } + +.custom-cell { + width: 38px; + height: 38px; + border-radius: 50%; + display: flex; + justify-content: center; + align-items: center; + + &-selected { + background: var(--adm-color-primary); + color: #fff; + } +} diff --git a/src/components/calendar/demos/demo3.tsx b/src/components/calendar/demos/demo3.tsx index 0a82f6ffd4..7a1a7697e7 100644 --- a/src/components/calendar/demos/demo3.tsx +++ b/src/components/calendar/demos/demo3.tsx @@ -1,52 +1,111 @@ +import classnames from 'classnames' import dayjs from 'dayjs' -import React, { useEffect, useRef } from 'react' -import { Calendar } from 'antd-mobile' -import { DemoBlock } from 'demos' -import { CalendarRef } from 'antd-mobile/es/components/calendar' - -const min = new Date() -min.setDate(5) -const max = new Date() -max.setDate(20) +import React, { useState } from 'react' +import { Calendar, List } from 'antd-mobile' +import './demo3.less' export default () => { const today = dayjs() - const calendarRef = useRef(null) - useEffect(() => { - if (calendarRef.current) { - calendarRef.current.jumpTo({ - year: 2022, - month: 4, - }) - } - }, []) + const [val, setVal] = useState<[Date, Date] | null>(() => [ + today.subtract(2, 'day').toDate(), + today.add(2, 'day').toDate(), + ]) + + const [visible1, setVisible1] = useState(false) + const [visible2, setVisible2] = useState(false) + const [visible3, setVisible3] = useState(false) + const [visible4, setVisible4] = useState(false) + return ( - <> - - - - + + { + setVisible1(true) + }} + > + 自定义日期顶部信息 + setVisible1(false)} + onMaskClick={() => setVisible1(false)} + renderTop={date => { + const map = { + 1: '初一', + 2: '初二', + 3: '初三', + } + const dates = [1, 2, 3] + const d = dayjs(date).date() as keyof typeof map + + if (dates.includes(d)) { + return map[d] + } + }} + /> + + { + setVisible2(true) + }} + > + 自定义日期底部信息 { - if (dayjs(date).isSame(today, 'day')) return '今天' - if (date.getDay() === 0 || date.getDay() === 6) { - return '周末' + visible={visible2} + onClose={() => setVisible2(false)} + onMaskClick={() => setVisible2(false)} + renderBottom={date => { + const dates = [16, 17, 18, 19] + const d = dayjs(date).date() + + if (dates.includes(d)) { + return '¥100' } }} /> - - - - - + + { + setVisible3(true) + }} + > + 自定义日期渲染 setVisible3(false)} + onMaskClick={() => setVisible3(false)} + renderDate={date => { + const dates = [16, 17, 18, 19] + const d = dayjs(date).date() + return ( +
+ {d} +
+ ) + }} + /> +
+ { + setVisible4(true) + }} + > + 高级自定义样式 + { + setVal(val) + }} + visible={visible4} + onClose={() => setVisible4(false)} + onMaskClick={() => setVisible4(false)} /> -
- - - - + + ) } diff --git a/src/components/calendar/demos/demo4.tsx b/src/components/calendar/demos/demo4.tsx index 93adfc363d..82ee73197f 100644 --- a/src/components/calendar/demos/demo4.tsx +++ b/src/components/calendar/demos/demo4.tsx @@ -1,27 +1,48 @@ -import dayjs from 'dayjs' -import React, { useState } from 'react' -import { Calendar } from 'antd-mobile' -import { DemoBlock } from 'demos' -import './demo4.less' +import React, { useState, useRef } from 'react' +import { Calendar, List, CalendarRef } from 'antd-mobile' export default () => { - const today = dayjs() - const [val, setVal] = useState<[Date, Date] | null>(() => [ - today.subtract(2, 'day').toDate(), - today.add(2, 'day').toDate(), - ]) + const ref1 = useRef(null) + const ref2 = useRef(null) + const [visible1, setVisible1] = useState(false) + const [visible2, setVisible2] = useState(false) + return ( - <> - + + { + setVisible1(true) + ref1.current?.jumpTo(page => ({ + year: page.year, + month: page.month + 3, + })) + }} + > + 跳转到 3 月后 + setVisible1(false)} + onMaskClick={() => setVisible1(false)} + /> + + { + setVisible2(true) + ref2.current?.jumpTo(page => ({ + year: page.year + 3, + month: page.month, + })) + }} + > + 跳转到 3 年后 { - setVal(val) - }} + ref={ref2} + visible={visible2} + onClose={() => setVisible2(false)} + onMaskClick={() => setVisible2(false)} /> - - + + ) } diff --git a/src/components/calendar/demos/demo5.less b/src/components/calendar/demos/demo5.less deleted file mode 100644 index 3286137d79..0000000000 --- a/src/components/calendar/demos/demo5.less +++ /dev/null @@ -1,13 +0,0 @@ -.custom-cell { - width: 38px; - height: 38px; - border-radius: 50%; - display: flex; - justify-content: center; - align-items: center; - - &-selected { - background: var(--adm-color-primary); - color: #fff; - } -} diff --git a/src/components/calendar/demos/demo5.tsx b/src/components/calendar/demos/demo5.tsx deleted file mode 100644 index 7f614be14e..0000000000 --- a/src/components/calendar/demos/demo5.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import dayjs from 'dayjs' -import React from 'react' -import { Calendar } from 'antd-mobile' -import { DemoBlock } from 'demos' -import classNames from 'classnames' -import './demo5.less' - -export default () => { - return ( - <> - - { - const dates = [16, 17, 18, 19] - const d = dayjs(date).date() - return ( -
- {d} -
- ) - }} - /> -
- - ) -} diff --git a/src/components/calendar/index.ts b/src/components/calendar/index.ts index cff7d5a2ec..7cf42a25f9 100644 --- a/src/components/calendar/index.ts +++ b/src/components/calendar/index.ts @@ -1,4 +1,3 @@ -import './calendar.less' import { Calendar } from './calendar' export type { CalendarProps, CalendarRef } from './calendar' diff --git a/src/components/calendar/tests/__snapshots__/calendar.test.tsx.snap b/src/components/calendar/tests/__snapshots__/calendar.test.tsx.snap index c33e4205dd..a3644cc699 100644 --- a/src/components/calendar/tests/__snapshots__/calendar.test.tsx.snap +++ b/src/components/calendar/tests/__snapshots__/calendar.test.tsx.snap @@ -1,5128 +1,9 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Calendar controlled mode 1`] = ` -
-
- -
-
- 日 -
-
- 一 -
-
- 二 -
-
- 三 -
-
- 四 -
-
- 五 -
-
- 六 -
-
-
-
-
- 27 -
-
-
-
-
- 28 -
-
-
-
-
- 29 -
-
-
-
-
- 30 -
-
-
-
-
- 31 -
-
-
-
-
- 1 -
-
-
-
-
- 2 -
-
-
-
-
- 3 -
-
-
-
-
- 4 -
-
-
-
-
- 5 -
-
-
-
-
- 6 -
-
-
-
-
- 7 -
-
-
-
-
- 8 -
-
-
-
-
- 9 -
-
-
-
-
- 10 -
-
-
-
-
- 11 -
-
-
-
-
- 12 -
-
-
-
-
- 13 -
-
-
-
-
- 14 -
-
-
-
-
- 15 -
-
-
-
-
- 16 -
-
-
-
-
- 17 -
-
-
-
-
- 18 -
-
-
-
-
- 19 -
-
-
-
-
- 20 -
-
-
-
-
- 21 -
-
-
-
-
- 22 -
-
-
-
-
- 23 -
-
-
-
-
- 24 -
-
-
-
-
- 25 -
-
-
-
-
- 26 -
-
-
-
-
- 27 -
-
-
-
-
- 28 -
-
-
-
-
- 29 -
-
-
-
-
- 30 -
-
-
-
-
- 1 -
-
-
-
-
- 2 -
-
-
-
-
- 3 -
-
-
-
-
- 4 -
-
-
-
-
- 5 -
-
-
-
-
- 6 -
-
-
-
-
- 7 -
-
-
-
-
-
-`; - -exports[`Calendar custom label 1`] = ` -
-
- -
-
- 日 -
-
- 一 -
-
- 二 -
-
- 三 -
-
- 四 -
-
- 五 -
-
- 六 -
-
-
-
-
- 27 -
-
- 周末 -
-
-
-
- 28 -
-
-
-
-
- 1 -
-
-
-
-
- 2 -
-
-
-
-
- 3 -
-
-
-
-
- 4 -
-
-
-
-
- 5 -
-
- 周末 -
-
-
-
- 6 -
-
- 周末 -
-
-
-
- 7 -
-
-
-
-
- 8 -
-
-
-
-
- 9 -
-
-
-
-
- 10 -
-
-
-
-
- 11 -
-
-
-
-
- 12 -
-
- 周末 -
-
-
-
- 13 -
-
- 周末 -
-
-
-
- 14 -
-
-
-
-
- 15 -
-
-
-
-
- 16 -
-
-
-
-
- 17 -
-
-
-
-
- 18 -
-
-
-
-
- 19 -
-
- 周末 -
-
-
-
- 20 -
-
- 周末 -
-
-
-
- 21 -
-
-
-
-
- 22 -
-
- 今天 -
-
-
-
- 23 -
-
-
-
-
- 24 -
-
-
-
-
- 25 -
-
-
-
-
- 26 -
-
- 周末 -
-
-
-
- 27 -
-
- 周末 -
-
-
-
- 28 -
-
-
-
-
- 29 -
-
-
-
-
- 30 -
-
-
-
-
- 31 -
-
-
-
-
- 1 -
-
-
-
-
- 2 -
-
- 周末 -
-
-
-
- 3 -
-
- 周末 -
-
-
-
- 4 -
-
-
-
-
- 5 -
-
-
-
-
- 6 -
-
-
-
-
- 7 -
-
-
-
-
- 8 -
-
-
-
-
- 9 -
-
- 周末 -
-
-
-
-
-`; - -exports[`Calendar jump to a day 1`] = ` -
- - -
- -
-
- 日 -
-
- 一 -
-
- 二 -
-
- 三 -
-
- 四 -
-
- 五 -
-
- 六 -
-
-
-
-
- 27 -
-
-
-
-
- 28 -
-
-
-
-
- 29 -
-
-
-
-
- 30 -
-
-
-
-
- 31 -
-
-
-
-
- 1 -
-
-
-
-
- 2 -
-
-
-
-
- 3 -
-
-
-
-
- 4 -
-
-
-
-
- 5 -
-
-
-
-
- 6 -
-
-
-
-
- 7 -
-
-
-
-
- 8 -
-
-
-
-
- 9 -
-
-
-
-
- 10 -
-
-
-
-
- 11 -
-
-
-
-
- 12 -
-
-
-
-
- 13 -
-
-
-
-
- 14 -
-
-
-
-
- 15 -
-
-
-
-
- 16 -
-
-
-
-
- 17 -
-
-
-
-
- 18 -
-
-
-
-
- 19 -
-
-
-
-
- 20 -
-
-
-
-
- 21 -
-
-
-
-
- 22 -
-
-
-
-
- 23 -
-
-
-
-
- 24 -
-
-
-
-
- 25 -
-
-
-
-
- 26 -
-
-
-
-
- 27 -
-
-
-
-
- 28 -
-
-
-
-
- 29 -
-
-
-
-
- 30 -
-
-
-
-
- 31 -
-
-
-
-
- 1 -
-
-
-
-
- 2 -
-
-
-
-
- 3 -
-
-
-
-
- 4 -
-
-
-
-
- 5 -
-
-
-
-
- 6 -
-
-
-
-
-
-`; - -exports[`Calendar jump to a day 2`] = ` -
- - -
- -
-
- 日 -
-
- 一 -
-
- 二 -
-
- 三 -
-
- 四 -
-
- 五 -
-
- 六 -
-
-
-
-
- 27 -
-
-
-
-
- 28 -
-
-
-
-
- 1 -
-
-
-
-
- 2 -
-
-
-
-
- 3 -
-
-
-
-
- 4 -
-
-
-
-
- 5 -
-
-
-
-
- 6 -
-
-
-
-
- 7 -
-
-
-
-
- 8 -
-
-
-
-
- 9 -
-
-
-
-
- 10 -
-
-
-
-
- 11 -
-
-
-
-
- 12 -
-
-
-
-
- 13 -
-
-
-
-
- 14 -
-
-
-
-
- 15 -
-
-
-
-
- 16 -
-
-
-
-
- 17 -
-
-
-
-
- 18 -
-
-
-
-
- 19 -
-
-
-
-
- 20 -
-
-
-
-
- 21 -
-
-
-
-
- 22 -
-
-
-
-
- 23 -
-
-
-
-
- 24 -
-
-
-
-
- 25 -
-
-
-
-
- 26 -
-
-
-
-
- 27 -
-
-
-
-
- 28 -
-
-
-
-
- 29 -
-
-
-
-
- 30 -
-
-
-
-
- 31 -
-
-
-
-
- 1 -
-
-
-
-
- 2 -
-
-
-
-
- 3 -
-
-
-
-
- 4 -
-
-
-
-
- 5 -
-
-
-
-
- 6 -
-
-
-
-
- 7 -
-
-
-
-
- 8 -
-
-
-
-
- 9 -
-
-
-
-
-
-`; - -exports[`Calendar range mode 1`] = ` -
-
- -
-
- 日 -
-
- 一 -
-
- 二 -
-
- 三 -
-
- 四 -
-
- 五 -
-
- 六 -
-
-
-
-
- 27 -
-
-
-
-
- 28 -
-
-
-
-
- 1 -
-
-
-
-
- 2 -
-
-
-
-
- 3 -
-
-
-
-
- 4 -
-
-
-
-
- 5 -
-
-
-
-
- 6 -
-
-
-
-
- 7 -
-
-
-
-
- 8 -
-
-
-
-
- 9 -
-
-
-
-
- 10 -
-
-
-
-
- 11 -
-
-
-
-
- 12 -
-
-
-
-
- 13 -
-
-
-
-
- 14 -
-
-
-
-
- 15 -
-
-
-
-
- 16 -
-
-
-
-
- 17 -
-
-
-
-
- 18 -
-
-
-
-
- 19 -
-
-
-
-
- 20 -
-
-
-
-
- 21 -
-
-
-
-
- 22 -
-
-
-
-
- 23 -
-
-
-
-
- 24 -
-
-
-
-
- 25 -
-
-
-
-
- 26 -
-
-
-
-
- 27 -
-
-
-
-
- 28 -
-
-
-
-
- 29 -
-
-
-
-
- 30 -
-
-
-
-
- 31 -
-
-
-
-
- 1 -
-
-
-
-
- 2 -
-
-
-
-
- 3 -
-
-
-
-
- 4 -
-
-
-
-
- 5 -
-
-
-
-
- 6 -
-
-
-
-
- 7 -
-
-
-
-
- 8 -
-
-
-
-
- 9 -
-
-
-
-
-
-`; - exports[`Calendar single mode 1`] = `
- -
-
- 日 -
-
- 一 -
-
- 二 -
-
- 三 -
-
- 四 -
-
- 五 -
-
- 六 -
-
-
-
-
- 27 -
-
-
-
-
- 28 -
-
-
-
-
- 1 -
-
-
-
-
- 2 -
-
-
-
-
- 3 -
-
-
-
-
- 4 -
-
-
-
-
- 5 -
-
-
-
-
- 6 -
-
-
-
-
- 7 -
-
-
-
-
- 8 -
-
-
-
-
- 9 -
-
-
-
-
- 10 -
-
-
-
-
- 11 -
-
-
-
-
- 12 -
-
-
-
-
- 13 -
-
-
-
-
- 14 -
-
-
-
-
- 15 -
-
-
-
-
- 16 -
-
-
-
-
- 17 -
-
-
-
-
- 18 -
-
-
-
-
- 19 -
-
-
-
-
- 20 -
-
-
-
-
- 21 -
-
-
-
-
- 22 -
-
-
-
-
- 23 -
-
-
-
-
- 24 -
-
-
-
-
- 25 -
-
-
-
-
- 26 -
-
-
-
-
- 27 -
-
-
-
-
- 28 -
-
-
-
-
- 29 -
-
-
-
-
- 30 -
-
-
-
-
- 31 -
-
-
-
-
- 1 -
-
-
-
-
- 2 -
-
-
-
-
- 3 -
-
-
-
-
- 4 -
-
-
-
-
- 5 -
-
-
-
-
- 6 -
-
-
-
-
- 7 -
-
-
-
-
- 8 -
-
-
-
-
- 9 -
-
-
-
-
-
-`; - -exports[`Calendar week start on Monday 1`] = ` -
-
- -
-
- 一 -
-
- 二 -
-
- 三 -
-
- 四 -
-
- 五 -
-
- 六 -
-
- 日 -
-
-
-
-
- 28 -
-
-
-
-
- 1 -
-
-
-
-
- 2 -
-
-
-
-
- 3 -
-
-
-
-
- 4 -
-
-
-
-
- 5 -
-
-
-
-
- 6 -
-
-
-
-
- 7 -
-
-
-
-
- 8 -
-
-
-
-
- 9 -
-
-
-
-
- 10 -
-
-
-
-
- 11 -
-
-
-
-
- 12 -
-
-
-
-
- 13 -
-
-
-
-
- 14 -
-
-
-
-
- 15 -
-
-
-
-
- 16 -
-
-
-
-
- 17 -
-
-
-
-
- 18 -
-
-
-
-
- 19 -
-
-
-
-
- 20 -
-
-
-
-
- 21 -
-
-
-
-
- 22 -
-
-
-
-
- 23 -
-
-
-
-
- 24 -
-
-
-
-
- 25 -
-
-
-
-
- 26 -
-
-
-
-
- 27 -
-
-
-
-
- 28 -
-
-
-
-
- 29 -
-
-
-
-
- 30 -
-
-
-
-
- 31 -
-
-
-
-
- 1 -
-
-
-
-
- 2 -
-
-
-
-
- 3 -
-
-
-
-
- 4 -
-
-
-
-
- 5 -
-
-
-
-
- 6 -
-
-
-
-
- 7 -
-
-
-
-
- 8 -
-
-
-
-
- 9 -
-
-
-
-
- 10 -
-
-
-
-
+ />
`; diff --git a/src/components/calendar/tests/calendar.test.tsx b/src/components/calendar/tests/calendar.test.tsx index 231c3640d4..fa43a79f95 100644 --- a/src/components/calendar/tests/calendar.test.tsx +++ b/src/components/calendar/tests/calendar.test.tsx @@ -1,16 +1,16 @@ -import React, { useState, useRef } from 'react' +import React from 'react' import { render, testA11y, fireEvent } from 'testing' -import Calendar, { CalendarRef } from '..' -import dayjs from 'dayjs' +import Calendar from '..' import MockDate from 'mockdate' -const classPrefix = `adm-calendar` +const classPrefix = `adm-calendar-view` // mock today -MockDate.set(new Date('2022-03-22')) +MockDate.set(new Date('2023-05-22')) -const singleDate: Date = new Date('2022-03-09') -const rangeDate: [Date, Date] = [new Date('2022-03-09'), new Date('2022-03-21')] +const mixDate: Date = new Date('2023-05-01') +const maxDate: Date = new Date('2023-05-31') +const singleDate: Date = new Date('2023-05-03') describe('Calendar', () => { test('a11y', async () => { @@ -19,155 +19,21 @@ describe('Calendar', () => { test('single mode', async () => { const fn = jest.fn() - const { container, getByText } = render( + const { container, getAllByText } = render( ) expect(container).toMatchSnapshot() - const dateEl = getByText(15) + const dateEl = getAllByText(15)[0] fireEvent.click(dateEl) expect(dateEl.parentElement).toHaveClass(`${classPrefix}-cell-selected`) expect(fn).toBeCalled() }) - - test('range mode', async () => { - const fn = jest.fn() - const { container, getByText } = render( - - ) - - expect(container).toMatchSnapshot() - const [startEl, endEl] = [getByText(20), getByText(26)] - fireEvent.click(startEl) - fireEvent.click(endEl) - expect( - document.querySelectorAll(`.${classPrefix}-cell-selected`).length - ).toBe(7) - expect(fn.mock.calls[1][0].map((d: Date) => d.toDateString())).toEqual([ - 'Sun Mar 20 2022', - 'Sat Mar 26 2022', - ]) - }) - - test('controlled mode', async () => { - const today = dayjs('2022-05-01') - - const App = () => { - const [val, setVal] = useState<[Date, Date] | null>(() => [ - today.subtract(2, 'day').toDate(), - today.add(2, 'day').toDate(), - ]) - - return ( - { - setVal(val) - }} - /> - ) - } - - const { container, getByText } = render() - const [startEl, endEl] = [getByText(8), getByText(15)] - fireEvent.click(startEl) - fireEvent.click(endEl) - expect(container).toMatchSnapshot() - }) - - test('page change', async () => { - const fn = jest.fn() - render() - - const btns = document.querySelectorAll(`.${classPrefix}-arrow-button-right`) - const titleEl = document.querySelector(`.${classPrefix}-title`) - // month - fireEvent.click(btns[0]) - expect(titleEl?.innerHTML).toContain('4月') - expect(fn.mock.calls[0]).toEqual([2022, 4]) - - // year - fireEvent.click(btns[1]) - expect(titleEl?.innerHTML).toContain('2023') - expect(fn.mock.calls[1]).toEqual([2023, 4]) - }) - - test('jump to a day', async () => { - const App = () => { - const ref = useRef(null) - return ( - <> - - - - - ) - } - const { container, getByText } = render() - - fireEvent.click(getByText('jumpTo')) - expect(container).toMatchSnapshot() - - fireEvent.click(getByText('jumpToToday')) - expect(container).toMatchSnapshot() - }) - - test('week start on Monday', async () => { - const { container } = render() - expect(container).toMatchSnapshot() - }) - - test(`can't allow to clear`, async () => { - const { getByText } = render( - - ) - - const dateEl = getByText(16) - fireEvent.click(dateEl) - fireEvent.click(dateEl) - expect(dateEl.parentElement).toHaveClass(`${classPrefix}-cell-selected`) - }) - - test('custom label', async () => { - const today = dayjs() - const { container } = render( - { - if (dayjs(date).isSame(today, 'day')) return '今天' - if (date.getDay() === 0 || date.getDay() === 6) { - return '周末' - } - }} - /> - ) - expect(container).toMatchSnapshot() - }) - - test('custom date render', () => { - render( - { - return
{dayjs(date).date()}
- }} - /> - ) - expect(document.getElementsByClassName('custom-cell').length).toBe(42) - }) }) diff --git a/src/components/config-provider/tests/__snapshots__/config-provider.test.tsx.snap b/src/components/config-provider/tests/__snapshots__/config-provider.test.tsx.snap index 2d97539be6..f3d3ae8a48 100644 --- a/src/components/config-provider/tests/__snapshots__/config-provider.test.tsx.snap +++ b/src/components/config-provider/tests/__snapshots__/config-provider.test.tsx.snap @@ -3,8032 +3,26960 @@ exports[`ConfigProvider should display the text as da-DK 1`] = `
ma
ti
on
to
fr
-
-
- 27 -
-
-
-
-
- 28 -
-
-
-
-
- 1 -
-
-
-
-
- 2 -
-
-
-
-
- 3 -
-
-
-
-
- 4 -
-
-
-
-
- 5 -
-
-
-
-
- 6 -
-
-
-
-
- 7 -
-
-
-
-
- 8 -
-
-
-
-
- 9 -
-
-
-
-
- 10 -
-
-
-
-
- 11 -
-
-
-
-
- 12 -
-
-
-
-
- 13 -
-
-
-
-
- 14 -
-
-
-
-
- 15 -
-
-
-
-
- 16 -
-
-
-
+
- 17 + 3.2022
-
-
-
- 18 -
-
-
-
-
- 19 -
-
-
-
-
- 20 -
-
-
-
-
- 21 -
-
-
-
-
- 22 -
-
-
-
-
- 23 -
-
-
-
-
- 24 -
-
-
-
-
- 25 -
-
-
-
-
- 26 -
-
-
-
-
- 27 -
-
-
-
-
- 28 -
-
-
-
-
- 29 -
-
-
-
-
- 30 -
-
-
-
-
- 31 -
-
-
-
-
- 1 -
-
-
-
-
- 2 -
-
-
-
-
- 3 -
-
-
-
-
- 4 -
-
-
-
-
- 5 -
-
-
-
-
- 6 -
-
-
-
-
- 7 -
-
-
-
-
- 8 -
-
-
-
-
- 9 -
-
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - +
- +
+
- + 2 +
+
- - - +
- +
- - - +
- +
+
- + 4 +
+
- +
+
- + 5 +
+
- +
+
- + 6 +
+
- +
+
- + 7 +
+
- +
+
- + 8 +
+
- - - - -
-
-
- Oops, noget gik galt -
-
- Vent et minut og prøv igen -
-
-
-
-
-
-
+
+
-
-
- -
-
-
- -
-
-
-
+ 9
+
-
-
-
- -