docs: blog picker (#50936)

* chore: blog picker

* docs: trigger update

* docs: add ref faq

* docs: update
This commit is contained in:
二货爱吃白萝卜 2024-09-23 11:09:46 +08:00 committed by GitHub
parent fdc30e7e64
commit e3063f244d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 209 additions and 2 deletions

View File

@ -98,7 +98,7 @@ The following APIs are shared by DatePicker, RangePicker.
| cellRender | Custom rendering function for picker cells | (current: dayjs, info: { originNode: React.ReactElement,today: DateType, range?: 'start' \| 'end', type: PanelMode, locale?: Locale, subType?: 'hour' \| 'minute' \| 'second' \| 'meridiem' }) => React.ReactNode | - | 5.4.0 |
| components | Custom panels | Record<Panel \| 'input', React.ComponentType> | - | 5.14.0 |
| disabled | Determine whether the DatePicker is disabled | boolean | false | |
| disabledDate | Specify the date that cannot be selected | (currentDate: dayjs, info: { from?: dayjs }) => boolean | - | `info`: 5.14.0 |
| disabledDate | Specify the date that cannot be selected | (currentDate: dayjs, info: { from?: dayjs, type: Picker }) => boolean | - | `info`: 5.14.0 |
| format | To set the date format, support multi-format matching when it is an array, display the first one shall prevail. refer to [dayjs#format](https://day.js.org/docs/en/display/format). for example: [Custom Format](#date-picker-demo-format) | [formatType](#formattype) | [rc-picker](https://github.com/react-component/picker/blob/f512f18ed59d6791280d1c3d7d37abbb9867eb0b/src/utils/uiUtil.ts#L155-L177) | |
| order | Auto order date when multiple or range selection | boolean | true | 5.14.0 |
| popupClassName | To customize the className of the popup calendar | string | - | 4.23.0 |
@ -295,3 +295,7 @@ dayjs.updateLocale('zh-cn', {
### Why origin panel don't switch when using `panelRender`?
When you change the layout of nodes by `panelRender`, React will unmount and re-mount it which reset the component state. You should keep the layout stable. Please ref [#27263](https://github.com/ant-design/ant-design/issues/27263) for more info.
### How to understand disabled time and date?
Please refer to the blog ['Why is it so hard to disable the date?'](/docs/blog/picker), to learn how to use it.

View File

@ -99,7 +99,7 @@ dayjs.locale('zh-cn');
| cellRender | 自定义单元格的内容 | (current: dayjs, info: { originNode: React.ReactElement,today: DateType, range?: 'start' \| 'end', type: PanelMode, locale?: Locale, subType?: 'hour' \| 'minute' \| 'second' \| 'meridiem' }) => React.ReactNode | - | 5.4.0 |
| components | 自定义面板 | Record<Panel \| 'input', React.ComponentType> | - | 5.14.0 |
| disabled | 禁用 | boolean | false | |
| disabledDate | 不可选择的日期 | (currentDate: dayjs, info: { from?: dayjs }) => boolean | - | `info`: 5.14.0 |
| disabledDate | 不可选择的日期 | (currentDate: dayjs, info: { from?: dayjs, type: Picker }) => boolean | - | `info`: 5.14.0 |
| format | 设置日期格式,为数组时支持多格式匹配,展示以第一个为准。配置参考 [dayjs#format](https://day.js.org/docs/zh-CN/display/format#%E6%94%AF%E6%8C%81%E7%9A%84%E6%A0%BC%E5%BC%8F%E5%8C%96%E5%8D%A0%E4%BD%8D%E7%AC%A6%E5%88%97%E8%A1%A8)。示例:[自定义格式](#date-picker-demo-format) | [formatType](#formattype) | [rc-picker](https://github.com/react-component/picker/blob/f512f18ed59d6791280d1c3d7d37abbb9867eb0b/src/utils/uiUtil.ts#L155-L177) | |
| order | 多选、范围时是否自动排序 | boolean | true | 5.14.0 |
| preserveInvalidOnBlur | 失去焦点是否要清空输入框内无效内容 | boolean | false | 5.14.0 |
@ -290,3 +290,7 @@ dayjs.updateLocale('zh-cn', {
### 为何使用 `panelRender` 时,原来面板无法切换?
当你通过 `panelRender` 动态改变层级结构时,会使得原本的 Panel 被当做新的节点删除并创建。这使得其原本的状态会被重置,保持结构稳定即可。详情请参考 [#27263](https://github.com/ant-design/ant-design/issues/27263)。
### 如何理解禁用时间日期?
欢迎阅读博客[《为什么禁用日期这么难?》](/docs/blog/picker-cn)了解如何使用。

100
docs/blog/picker.en-US.md Normal file
View File

@ -0,0 +1,100 @@
---
title: Why is it so hard to disable the date?
date: 2024-09-23
author: zombieJ
---
In antd, there are many question about DatePicker. One is due to the commonality of date selection requirements, and the other is that there are various combinations of disabled selections for business requirements. Today, let's talk about the `disabledDate` API.
### disableDate cannot control time
As its name suggests, `disabledDate` is used to disable dates. So when using DateTimePicker, the time part is not controlled by `disabledDate`. Instead, it needs to be controlled through the `disabledTime` method. This seems a bit counterintuitive. Why do antd need two APIs to manage it?
#### How to determine if a date can be selected?
In terms of intuition, we only need to execute `disabledDate` once for a date to determine if it is disabled. However, if we switch the panel to the month panel, how do we know if the current month is selectable? We must execute `disabledDate` for each date in that month to determine if there are selectable dates in that month, so that month can be selected.
This logic seems fine for now. A month has about 30 days, and there are 12 months in the month panel. In the worst case, we only need to iterate 365 times to know that 12 months are not selectable. But as the panel switches to years and decades, the number of iterations required will increase exponentially, leading to serious performance issues ([#39991](https://github.com/ant-design/ant-design/issues/39991))
So after the DatePicker refactoring, `disabledDate` provides an additional parameter `info.type`, which tells you which Panel the provided date object comes from. This allows developers to provide `disabled` information based on the Panel, thus avoiding the terrifying call loop.
But as a fallback, DatePicker will call `disabledDate` for the first and last days of the current Panel unit. This ensures that common range disable scenarios are still met.
#### disabledTime
Back to time disable, this is the same problem. If we use `disabledDate` to disable time dimensions, then whether a day is selectable in DateTimePicker, we need to check each second of that day with `disabledDate`. In the worst case, whether a day is selectable needs to be checked 86400 times. The Date Panel needs to execute ~2 million times. Obviously, this is unacceptable.
So for the time dimension, we provide the `disabledTime` method. This method requires more granular time disable information than `disabledDate`:
```tsx
type DisabledTime = (now: Dayjs) => {
disabledHours?: () => number[];
disabledMinutes?: (selectedHour: number) => number[];
disabledSeconds?: (selectedHour: number, selectedMinute: number) => number[];
disabledMilliseconds?: (
selectedHour: number,
selectedMinute: number,
selectedSecond: number,
) => number[];
};
```
(Each unit of the time selection panel is equivalent to the Panel of the date panel, and the latter infers the current disabled unit from the information of the former unit)
### Some examples
After understanding the context, we will find that `disabledDate` and `disabledTime` are designed to be reasonable, but they are somewhat low-level. It is more troublesome to use them in business. Let's look at a few examples (of course, you need to consider encapsulating them through HOC in business):
#### Working hours
Temporarily ignore the situation of holidays and choose 9:00 ~ 17:00 on working days as the selectable time:
```tsx
const disabledDate = (date, info) => {
if (info.type === 'date') {
return date.day() === 0 || date.day() === 6;
}
return false;
};
const disabledTime = () => ({
disabledHours: () => {
return Array.from({ length: 24 }, (_, i) => i).filter((hour) => hour < 9 || hour > 17);
},
});
```
#### Date and time range
In DatePicker, there are `minDate` and `maxDate` to limit the date selection range. But they only limit to date range. Now, suppose we have a scenario that requires a date range selection with time, such as `2024-01-01 09:00:00` ~ `2024-01-02 17:00:00`, then we can do this:
```tsx
const disabledDate = (date, info) => {
if (info.type === 'date') {
return date.isBefore('2024-01-01', 'day') || date.isAfter('2024-01-02', 'day');
}
return !date.isSame('2024-01-01', info.type);
};
const disabledTime = (date) => {
if (date.isSame('2024-01-01', 'day')) {
return {
disabledHours: () => Array.from({ length: 24 }, (_, i) => i).filter((hour) => hour < 9),
};
}
if (date.isSame('2024-01-02', 'day')) {
return {
disabledHours: () => Array.from({ length: 24 }, (_, i) => i).filter((hour) => hour > 17),
};
}
// Only need to consider the start and end time
// the range itself has been disabled by `disabledDate`
return {};
};
```
### Summary
Through `disabledDate` and `disabledTime`, we can control the date and time more finely to meet different business requirements. With the examples above, I believe you have a deeper understanding of these two APIs. In actual business, you can combine these two APIs to achieve more functions.

99
docs/blog/picker.zh-CN.md Normal file
View File

@ -0,0 +1,99 @@
---
title: 为什么禁用日期这么难?
date: 2024-09-23
author: zombieJ
---
在 antd 中DatePicker 的 issue 非常多。一来是由于日期选择需求的常见性,另一来是针对业务的需求会有各种各样的禁用选择组合。今天,我们就来聊聊 `disabledDate` 这个 API。
### disabledDate 不能控制时间
如其名,`disabledDate` 用于对日期进行禁用。所以当使用 DateTimePicker 时,时间部分并不会被 `disabledDate` 进行控制。而是需要通过 `disabledTime` 方法进行控制。这似乎有点反直觉,为什么需要用两个 API 管理呢?
#### 如何确定日期能选?
从直觉上看,一个日期是否禁用我们只需要将当前日期执行一次 `disabledDate` 即可知道。但是,如果当我们把面板切换成月份面板的时候,我们怎么知道当前月份是否可选呢?我们必须要对该月下的每个日期进行一次 `disabledDate` 才能确定该月下有可选日期,因此该月才能选择。
从目前看,这个逻辑似乎没问题。一个月 30 来天,月份面板 12 个月,最坏情况下,我们也只需要遍历 365 次就能知道 12 个月份是不可选的。但是以此类推,当面板切换为年、十年时。我们需要遍历的次数就会呈指数级增长,导致严重的性能问题([#39991](https://github.com/ant-design/ant-design/issues/39991))。
所以在 DatePicker 重构后,`disabledDate` 提供了额外的参数 `info.type`,告知提供的 date 对象来自于哪个 Panel。这样开发者就可以根据 Panel 来提供 `disabled` 信息,从而避免恐怖的调用循环。
但是作为兜底DatePicker 会对当前 Panel 单元的第一天和最后一天进行 `disabledDate` 的调用。这样可以确保在常见的范围禁用场景下,仍能满足需求。
#### disabledTime
回到时间禁用,这是相同的问题。如果我们通过 `disabledDate` 进行时间维度的禁用,那么在 DateTimePicker 下该天是否可选,我们就需要对该天的每一秒进行 `disabledDate` 校验。在最坏情况下,一天是否可选就需要校验 86400 次。Date Panel 就需要执行 ~200 万次。显然这是不可接受的。
所以对于时间维度,我们提供了 `disabledTime` 方法。这个方法相比 `disabledDate` 会要求提供更细细粒度的时间禁用信息:
```tsx
type DisabledTime = (now: Dayjs) => {
disabledHours?: () => number[];
disabledMinutes?: (selectedHour: number) => number[];
disabledSeconds?: (selectedHour: number, selectedMinute: number) => number[];
disabledMilliseconds?: (
selectedHour: number,
selectedMinute: number,
selectedSecond: number,
) => number[];
};
```
(时间选择面板的每个单位都相当于日期面板的 Panel后者通过前者单位的信息来推出当前禁用单位
### 一些例子
在了解了上下文后,我们会发现 `disabledDate``disabledTime` 虽然设计是合理的,但是却有些偏底层。在业务中进行使用会比较麻烦,我们来看几个例子(当然,在业务中你需要考虑通过 HOC 来进行封装):
#### 工作时间
暂时不考虑节假日的情况,我们选择工作日的 9:00 ~ 17:00 为可选时间:
```tsx
const disabledDate = (date, info) => {
if (info.type === 'date') {
return date.day() === 0 || date.day() === 6;
}
return false;
};
const disabledTime = () => ({
disabledHours: () => {
return Array.from({ length: 24 }, (_, i) => i).filter((hour) => hour < 9 || hour > 17);
},
});
```
#### 时间日期范围
在 DatePicker 中有 `minDate``maxDate` 用于限制日期的选择范围,但是如果它们仅限于日期的限制。现在,假设我们有种场景需要带有时间的日期范围选择,比如 `2024-01-01 09:00:00` ~ `2024-01-02 17:00:00`,那么我们可以这样做:
```tsx
const disabledDate = (date, info) => {
if (info.type === 'date') {
return date.isBefore('2024-01-01', 'day') || date.isAfter('2024-01-02', 'day');
}
return !date.isSame('2024-01-01', info.type);
};
const disabledTime = (date) => {
if (date.isSame('2024-01-01', 'day')) {
return {
disabledHours: () => Array.from({ length: 24 }, (_, i) => i).filter((hour) => hour < 9),
};
}
if (date.isSame('2024-01-02', 'day')) {
return {
disabledHours: () => Array.from({ length: 24 }, (_, i) => i).filter((hour) => hour > 17),
};
}
// 只需要考虑开始和结束时间,范围外的本身已经被 `disabledDate` 禁用了
return {};
};
```
### 总结
通过 `disabledDate``disabledTime`,我们可以对日期和时间进行更细粒度的控制,以实现不同的业务需求。通过以上示例,相信你已经对这两个 API 有了更深入的了解。在实际业务中,你可以根据具体需求,结合这两个 API 来实现更多的功能。