fix merge

This commit is contained in:
iamkun 2022-03-18 16:57:11 +08:00
commit f8dc201218
366 changed files with 43060 additions and 8834 deletions

View File

@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: check-inactive
uses: actions-cool/issues-helper@v2
uses: actions-cool/issues-helper@v3
with:
actions: 'check-inactive'
inactive-label: 'Inactive'

View File

@ -9,14 +9,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: need reproduce
uses: actions-cool/issues-helper@v2
uses: actions-cool/issues-helper@v3
with:
actions: 'close-issues'
labels: '🤔 Need Reproduce'
inactive-day: 3
- name: needs more info
uses: actions-cool/issues-helper@v2
uses: actions-cool/issues-helper@v3
with:
actions: 'close-issues'
labels: 'needs-more-info'

View File

@ -12,7 +12,7 @@ jobs:
steps:
- name: help wanted
if: github.event.label.name == 'help wanted'
uses: actions-cool/issues-helper@v2
uses: actions-cool/issues-helper@v3
with:
actions: 'create-comment'
token: ${{ secrets.GITHUB_TOKEN }}
@ -26,7 +26,7 @@ jobs:
- name: 🤔 Need Reproduce
if: github.event.label.name == '🤔 Need Reproduce'
uses: actions-cool/issues-helper@v2
uses: actions-cool/issues-helper@v3
with:
actions: 'create-comment'
token: ${{ secrets.GITHUB_TOKEN }}
@ -40,7 +40,7 @@ jobs:
- name: Usage
if: github.event.label.name == 'Usage' || github.event.label.name == 'Question'
uses: actions-cool/issues-helper@v2
uses: actions-cool/issues-helper@v3
with:
actions: 'create-comment,close-issue'
token: ${{ secrets.GITHUB_TOKEN }}
@ -52,7 +52,7 @@ jobs:
- name: 3.x
if: github.event.label.name == '3.x'
uses: actions-cool/issues-helper@v2
uses: actions-cool/issues-helper@v3
with:
actions: 'create-comment,close-issue'
token: ${{ secrets.GITHUB_TOKEN }}
@ -64,7 +64,7 @@ jobs:
- name: invalid
if: github.event.label.name == 'Invalid'
uses: actions-cool/issues-helper@v2
uses: actions-cool/issues-helper@v3
with:
actions: 'create-comment,close-issue'
token: ${{ secrets.GITHUB_TOKEN }}
@ -76,7 +76,7 @@ jobs:
- name: rtl
if: github.event.label.name == 'rtl'
uses: actions-cool/issues-helper@v2
uses: actions-cool/issues-helper@v3
with:
actions: 'add-assignees'
assignees: 'xrkffgg'

View File

@ -16,7 +16,7 @@ jobs:
- name: check invalid
if: (contains(github.event.issue.body, 'ant-design-issue-helper') == false) && (steps.checkUser.outputs.result == 'false')
uses: actions-cool/issues-helper@v2
uses: actions-cool/issues-helper@v3
with:
actions: 'create-comment,add-labels,close-issue'
issue-number: ${{ github.event.issue.number }}
@ -27,7 +27,7 @@ jobs:
你好 @${{ github.event.issue.user.login }},为了能够进行高效沟通,我们对 issue 有一定的格式要求,你的 issue 因为不符合要求而被自动关闭。你可以通过 [issue 助手](http://new-issue.ant.design) 来创建 issue 以方便我们定位错误。谢谢配合!
- name: check website
uses: actions-cool/issues-helper@v2
uses: actions-cool/issues-helper@v3
id: checkid
with:
actions: 'check-issue'
@ -37,7 +37,7 @@ jobs:
- name: deal website
if: steps.checkid.outputs.check-result == 'true'
uses: actions-cool/issues-helper@v2
uses: actions-cool/issues-helper@v3
with:
actions: 'create-comment,close-issue'
issue-number: ${{ github.event.issue.number }}
@ -63,15 +63,15 @@ jobs:
- name: check ie
if: contains(github.event.issue.body, 'ant-design-issue-helper') == true && contains(github.event.issue.title, 'IE9') == true || contains(github.event.issue.title, 'IE 9') == true || contains(github.event.issue.title, 'IE10') == true || contains(github.event.issue.title, 'IE 10') == true || contains(github.event.issue.title, 'IE11') == true || contains(github.event.issue.title, 'IE 11') == true || contains(github.event.issue.title, 'Internet Explorer') == true || contains(github.event.issue.body, 'IE9') == true || contains(github.event.issue.body, 'IE 9') == true || contains(github.event.issue.body, 'IE10') == true || contains(github.event.issue.body, 'IE 10') == true || contains(github.event.issue.body, 'IE11') == true || contains(github.event.issue.body, 'IE 11') == true || contains(github.event.issue.body, 'Internet Explorer') == true
uses: actions-cool/issues-helper@v2
uses: actions-cool/issues-helper@v3
with:
actions: 'add-labels'
issue-number: ${{ github.event.issue.number }}
labels: 'IE | Firefox | Safari,Internet Explorer'
- name: check ie
if: contains(github.event.issue.body, 'ant-design-issue-helper') == true && contains(github.event.issue.title, 'IE9') == true || contains(github.event.issue.title, 'IE 9') == true || contains(github.event.issue.title, 'IE10') == true || contains(github.event.issue.title, 'IE 10') == true || contains(github.event.issue.title, 'IE11') == true || contains(github.event.issue.title, 'IE 11') == true || contains(github.event.issue.title, 'Internet Explorer') == true || contains(github.event.issue.body, 'IE9') == true || contains(github.event.issue.body, 'IE 9') == true || contains(github.event.issue.body, 'IE10') == true || contains(github.event.issue.body, 'IE 10') == true
uses: actions-cool/issues-helper@v2
- name: check ie11-
if: contains(github.event.issue.body, 'ant-design-issue-helper') == true && contains(github.event.issue.title, 'IE9') == true || contains(github.event.issue.title, 'IE 9') == true || contains(github.event.issue.title, 'IE10') == true || contains(github.event.issue.title, 'IE 10') == true || contains(github.event.issue.body, 'IE9') == true || contains(github.event.issue.body, 'IE 9') == true || contains(github.event.issue.body, 'IE10') == true || contains(github.event.issue.body, 'IE 10') == true
uses: actions-cool/issues-helper@v3
with:
actions: 'create-comment, close-issue'
issue-number: ${{ github.event.issue.number }}

View File

@ -12,7 +12,7 @@ jobs:
steps:
- name: remove inactive
if: github.event.issue.state == 'open' && github.actor == github.event.issue.user.login
uses: actions-cool/issues-helper@v2
uses: actions-cool/issues-helper@v3
with:
actions: 'remove-labels'
issue-number: ${{ github.event.issue.number }}

View File

@ -13,7 +13,7 @@ jobs:
with:
filter-label: 'BranchAutoMerge'
filter-creator-authority: 'write'
filter-head-ref: 'master, feature, master-merge-feature, feature-merge-master'
filter-head-ref: 'master, feature, next, master-merge-feature, feature-merge-master, next-merge-master'
filter-support-fork: false
skip-run-names: 'deploy preview, pr-check-ci, build preview failed, suggest-related-links'
conflict-review-body: '😅 This branch has conflicts that must be resolved!'

View File

@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
if: (github.event.pull_request.head.ref == 'feature' || github.event.pull_request.head.ref == 'master') && github.event.pull_request.head.user.login == 'ant-design'
steps:
- uses: actions-cool/issues-helper@v2
- uses: actions-cool/issues-helper@v3
with:
actions: 'create-comment'
issue-number: ${{ github.event.number }}

View File

@ -16,25 +16,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: make release
uses: actions-cool/release-helper@v1
uses: actions-cool/release-helper@v2
with:
triger: 'tag'
changelogs: 'CHANGELOG.en-US.md, CHANGELOG.zh-CN.md'
branch: 'master'
dingding-token: ${{ secrets.DINGDING_BOT_TOKEN }}
dingding-msg: 'CHANGELOG.zh-CN.md'
msg-poster: 'https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*zx7LTI_ECSAAAAAAAAAAAABkARQnAQ'
msg-footer: '💬 前往 [**Ant Design Releases**]({{url}}) 查看更新日志'
prettier: true
prerelease-filter: '-, a, b, A, B'
- name: make release - Bigfish
uses: actions-cool/release-helper@v1
with:
triger: 'tag'
changelogs: 'CHANGELOG.en-US.md, CHANGELOG.zh-CN.md'
branch: 'master'
dingding-token: ${{ secrets.DINGDING_BOT_BIGFISH_TOKEN }}
dingding-token: ${{ secrets.DINGDING_BOT_TOKEN }} ${{ secrets.DINGDING_BOT_BIGFISH_TOKEN }}
dingding-msg: 'CHANGELOG.zh-CN.md'
msg-poster: 'https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*zx7LTI_ECSAAAAAAAAAAAABkARQnAQ'
msg-footer: '💬 前往 [**Ant Design Releases**]({{url}}) 查看更新日志'

View File

@ -492,7 +492,7 @@ jobs:
run: npx lessc --js ./dist/antd.variable.less
- name: lessc component
run: npx lessc --js ./es/button/style/index.less
run: npx lessc --js ./es/input/style/index.less
- name: lessc mixins
run: npx lessc --js ./es/style/mixins/index.less

View File

@ -14,10 +14,11 @@ jobs:
forbid-paths: '.github/, scripts/'
forbid-files: 'CHANGELOG.zh-CN.md, CHANGELOG.en-US.md, LICENSE'
skip-verify-authority: 'write'
assignees: 'afc163, zombieJ, xrkffgg'
assignees: 'afc163, zombieJ, xrkffgg, MadCcc'
comment: |
Hi @${{ github.event.pull_request.user.login }}. Thanks for your contribution. The path `.github/` or `scripts/` and `CHANGELOG` `package.json` is only maintained by team members. This current PR will be closed and team members will help on this.
close: true
set-failed: false
- name: verify-less
uses: actions-cool/verify-files-modify@v1

2
.gitignore vendored
View File

@ -67,3 +67,5 @@ __image_snapshots__/
/jest-stare
/imageSnapshots
/imageDiffSnapshots
.devcontainer*

View File

@ -8,6 +8,25 @@
"plugins": ["stylelint-declaration-block-no-ignored-properties"],
"rules": {
"function-name-case": ["lower", { "ignoreFunctions": ["/colorPalette/"] }],
"function-no-unknown": [
true,
{
"ignoreFunctions": [
"fade",
"tint",
"darken",
"ceil",
"fadein",
"floor",
"unit",
"shade",
"lighten",
"percentage",
"-",
"~`colorPalette"
]
}
],
"no-descending-specificity": null,
"no-invalid-position-at-import-rule": null,
"declaration-empty-line-before": null,

View File

@ -15,6 +15,125 @@ timeline: true
---
## 4.19.2
`2022-03-13`
- 🐞 Fix Dropdown not auto adjust placement when position on the edge of window. [#34390](https://github.com/ant-design/ant-design/pull/34390)
- 💄 Change PageHeader elements margin from `12px` to `8px` inside `extra`. [#34428](https://github.com/ant-design/ant-design/pull/34428)
- 🛠 Export css variable function in `antd/es/config-provider` folder to enable ssr requirement. [#34436](https://github.com/ant-design/ant-design/pull/34436)
- 🛠 Refactor Menu with React hooks. [#34433](https://github.com/ant-design/ant-design/pull/34433)
- Input
- 💄 Fix Input font-size when `size` is large. [#34381](https://github.com/ant-design/ant-design/pull/34381)
- 💄 Fix Input.Group wrong border when status is error. [#34412](https://github.com/ant-design/ant-design/pull/34412)
- Form
- 🐞 Fix Form.Item removed in `form.validateFields` throw `Can't perform a React state update on an unmounted component` warning. [#34405](https://github.com/ant-design/ant-design/pull/34405)
- 🐞 Fix to Form that `initialValues` would change if `preserve` is false. [#34411](https://github.com/ant-design/ant-design/pull/34411)
- Tooltip
- 💄 Fix Tooltip width in Safari. [#34415](https://github.com/ant-design/ant-design/pull/34415) [@jiandandkl](https://github.com/jiandandkl)
- 💄 Fix arrow size of Tooltip/Popover/Popconfirm. [#34407](https://github.com/ant-design/ant-design/pull/34407)
- 💄 Remove Collapse bottom border in simple style. [#34366](https://github.com/ant-design/ant-design/pull/34366) [@PanStar](https://github.com/PanStar)
- TypeScript
- 🤖 Fix Input `data-*` type definition. [#34410](https://github.com/ant-design/ant-design/pull/34410) [@GitKou](https://github.com/GitKou)
- 🤖 Fix Transfer `footer` type definition. [#34337](https://github.com/ant-design/ant-design/pull/34337) [@zomixi](https://github.com/zomixi)
## 4.19.1
`2022-03-08`
- 🐞 Fix less compile error related to custom status. [#34350](https://github.com/ant-design/ant-design/pull/34350)
- 🐞 Fix error `ReferenceError: colorPalette is not defined` when customize theme.
- 🐞 Fix error `Error: Invalid class or id selector syntax` when import `antd/dist/antd.css`.
- 🐞 Fix Input.Passowrd icon color in site. [#34354](https://github.com/ant-design/ant-design/pull/34354)
- 🐞 Fix ConfigProvider `csp` sometime not effect on Icon. [#34356](https://github.com/ant-design/ant-design/pull/34356)
## 4.19.0
`2022-03-08`
- 💄 Optimize arrow style for some components. [#33710](https://github.com/ant-design/ant-design/pull/33710)
<img src="https://user-images.githubusercontent.com/27722486/157088587-ca49cc29-bf25-42d1-8c14-020b5501c62e.png" width="500" />
- Input
- 🛠 Refactor Input with rc-input. [#34206](https://github.com/ant-design/ant-design/pull/34206)
- Attention: The type and value of `ref` is modified because of refactoring from class component to function component. You can still get DOM node from `input`, and other methods such as `focus` and `blur` mentioned in document are also supported.
- 🆕 Support `clearIcon` prop for customizing clear icon. [#34325](https://github.com/ant-design/ant-design/pull/34325)
- Table
- 🆕 `filterSearch` now support passing function to customize search. [#34085](https://github.com/ant-design/ant-design/pull/34085) [@heiyu4585](https://github.com/heiyu4585)
- 🆕 `column.filterDropdown({ clearFilters })` support `clearFilters({ confirm: false, closeDropdown: false })`. [#34120](https://github.com/ant-design/ant-design/pull/34120) [@heiyu4585](https://github.com/heiyu4585)
- ⌨️ Table adds `aria-sort` attribute for screen readers. [#33603](https://github.com/ant-design/ant-design/pull/33603) [@dgreene1](https://github.com/dgreene1)
- 🐞 Fix Table filters select-all Checkbox not changed when select item. [#34295](https://github.com/ant-design/ant-design/pull/34295)
- 🆕 Data entry components add `status` prop to support custom status.
- Transfer [#34098](https://github.com/ant-design/ant-design/pull/34098)
- AutoComplete [#34096](https://github.com/ant-design/ant-design/pull/34096)
- TreeSelect [#34093](https://github.com/ant-design/ant-design/pull/34093)
- Cascader [#34086](https://github.com/ant-design/ant-design/pull/34086)
- Select [#34084](https://github.com/ant-design/ant-design/pull/34084)
- DatePicker and TimePicker [#34073](https://github.com/ant-design/ant-design/pull/34073)
- Mentions [#34071](https://github.com/ant-design/ant-design/pull/34071)
- InputNumber [#34042](https://github.com/ant-design/ant-design/pull/34042)
- Input [#33995](https://github.com/ant-design/ant-design/pull/33995)
<img src="https://user-images.githubusercontent.com/27722486/157089015-f96b0153-2cc4-4e04-94d6-e0e4b195d5d1.png" width="500" />
- 🆕 InputNumber supports `controls={{ upIcon, downIcon }}` to customize icon up and down. [#33914](https://github.com/ant-design/ant-design/pull/33914) [@heiyu4585](https://github.com/heiyu4585)
- 🆕 Notification `placement` support `top` / `bottom` [#33871](https://github.com/ant-design/ant-design/pull/33871) [@heiyu4585](https://github.com/heiyu4585)
- 🆕 Select, Cascades, DatePicker now support `placement` property. [#33641](https://github.com/ant-design/ant-design/pull/33541) [@ONLY-yours](https://github.com/ONLY-yours)
- 🆕 Dropdown support `arrow={{ pointAtCenter: true }}` to make arrow point at center. And `top` `bottom` placement are also supported. [#33658](https://github.com/ant-design/ant-design/pull/33658)
- 🆕 Skeleton.Input Adds `block` prop. [#33672](https://github.com/ant-design/ant-design/pull/33672) [@woochanleee](https://github.com/woochanleee)
- 🆕 Move TimePicker `disabledHours`, `disabledMinutes`, `disabledSeconds` into `disabledTime` to align with DatePicker. [#33503](https://github.com/ant-design/ant-design/pull/33503)
- 💄 Modify the color of some borders and the background color of the progress bar to be transparent to suit the colored background. [#33506](https://github.com/ant-design/ant-design/pull/33506)
- 💄 Space support custom children `key`. [#33607](https://github.com/ant-design/ant-design/pull/33607) [@qin20](https://github.com/qin20)
- 🐞 Fix `Typography.Title` didn't keep font size when become editable. [#34169](https://github.com/ant-design/ant-design/pull/34169) [@heiyu4585](https://github.com/heiyu4585)
- 🐞 Fix Form.Item throw warning `React does not recognize the requiredMark prop on a DOM element`. [#34323](https://github.com/ant-design/ant-design/pull/34323)
## 4.18.9
`2022-02-28`
- 🆕 New theme less variable for Radio, Divider, Modal, Dropdown, Drawer. [#34194](https://github.com/ant-design/ant-design/pull/34194) [#34187](https://github.com/ant-design/ant-design/pull/34187) [#34191](https://github.com/ant-design/ant-design/pull/34191) [#34189](https://github.com/ant-design/ant-design/pull/34189) [#34188](https://github.com/ant-design/ant-design/pull/34188) [@qdzhaoxiaodao](https://github.com/qdzhaoxiaodao)
- 💄 Fix Dropdown item wrap style when text is too long. [#34177](https://github.com/ant-design/ant-design/pull/34177)
- TypeScript
- 🐞 Fix Upload `onChange` parameter generic passing. [#34161](https://github.com/ant-design/ant-design/pull/34161) [@wangcch](https://github.com/wangcch)
## 4.18.8
`2022-02-21`
- 🐞 Fix `getContainer` config not working bug when called multi-times via `message.config`. [#34123](https://github.com/ant-design/ant-design/pull/34123) [@TrickyPi](https://github.com/TrickyPi)
- 🐞 Fix invalid context value cache in Menu component. [#34121](https://github.com/ant-design/ant-design/pull/34121) [@mrwd2009](https://github.com/mrwd2009)
- 🐞 Fix ConfigProvider config theme on server side crash, and warning for useless in SSR instead. [#34118](https://github.com/ant-design/ant-design/pull/34118)
- Table
- ⚡️ Fix Table render twice on first mount. [#34106](https://github.com/ant-design/ant-design/pull/34106)
- ⚡️ Optimized Table rendering performance, now will skip useless rendering when deprecated `column.render: () => { children, props }` method is not used. [#34075](https://github.com/ant-design/ant-design/pull/34075)
- 🐞 Fix incorrect copy text of Typography after children is updated when enable `copyable`. [#34034](https://github.com/ant-design/ant-design/pull/34034) [@opopeieie](https://github.com/opopeieie)
- ⚡️ Optimize Avatar, List, Pagination, Steps to avoid additional render on mount if unnecessary. [34122](https://github.com/ant-design/ant-design/pull/34122)
- 💄 Fix Form broken style when Select item is too long in horizontal layout. [#34117](https://github.com/ant-design/ant-design/pull/34117)
- 🇸🇰 Improve texts for Table, Form and Modal in `sk_SK`. [#34061](https://github.com/ant-design/ant-design/pull/34061) [@xseman](https://github.com/xseman)
- TypeScript
- 🤖 Export `SiderProps` type from Layout component. [#34137](https://github.com/ant-design/ant-design/pull/34137) [@Picsong](https://github.com/Picsong)
## 4.18.7
`2022-02-14`
- Typography
- 🛠 Fix Typography `useLayoutEffect` warning in SSR. [#33818](https://github.com/ant-design/ant-design/pull/33818) [@SoYoung210](https://github.com/SoYoung210)
- 🐞 Fix Typography with `ellipsis` makes screen show the scroll bar in some case. [#34007](https://github.com/ant-design/ant-design/pull/34007)
- 🐞 Typography copy click event is now `stopPropagation` by default. [#33998](https://github.com/ant-design/ant-design/pull/33998) [@linxianxi](https://github.com/linxianxi)
- 🐞 Fix Typography edit & copy button not trigger by enter key. [#33976](https://github.com/ant-design/ant-design/pull/33976) [@mrwd2009](https://github.com/mrwd2009)
- 🐞 Fix Form `undefined` text of min/max validators in pl_PL locale. [#34024](https://github.com/ant-design/ant-design/pull/34024) [@MichalPodeszwa](https://github.com/MichalPodeszwa)
- 🐞 Fix Input.TextArea cut text logic when `maxLength` configured. [#33910](https://github.com/ant-design/ant-design/pull/33910) [@chenyizhongx](https://github.com/chenyizhongx)
- 💄 Button with `type=default` will provide `.ant-btn-default` className. [#34013](https://github.com/ant-design/ant-design/pull/34013)
- 💄 Improve Menu `:focus-visible` style. [#34008](https://github.com/ant-design/ant-design/pull/34008)
- 💄 Fix Pagination and Rate style problem in Safari. [#34002](https://github.com/ant-design/ant-design/pull/34002)
- 💄 Fix Row and Col component styles when using prefixCls. [#33969](https://github.com/ant-design/ant-design/pull/33969) [@mic-web](https://github.com/mic-web)
- 🐞 Fix Timeline icons with custom color not working. [#33951](https://github.com/ant-design/ant-design/pull/33951) [@MadCcc](https://github.com/MadCcc)
- TypeScript
- 🤖 Optimize Cascader `onChange` definition with `multiple` prop. [#33947](https://github.com/ant-design/ant-design/pull/33947) [@babycannotsay](https://github.com/babycannotsay)
## 4.18.6
`2022-02-07`

View File

@ -13,7 +13,127 @@ timeline: true
- 次版本号:每月发布一个带有新特性的向下兼容的版本。
- 主版本号:含有破坏性更新和新特性,不在发布周期内。
---
--
## 4.19.2
`2022-03-13`
- 🐞 修复 Dropdown 在边界情况下不会自动调整展示位置的问题。[#34390](https://github.com/ant-design/ant-design/pull/34390)
- 💄 缩小 PageHeader `extra` 内元素间距为 `8px`。[#34428](https://github.com/ant-design/ant-design/pull/34428)
- 🛠 导出 antd/es/config-provider 目录下的 css variable 函数以支持 ssr 的需求。[#34436](https://github.com/ant-design/ant-design/pull/34436)
- 🛠 使用 React hooks 重构 Menu。[#34433](https://github.com/ant-design/ant-design/pull/34433)
- Input
- 💄 修复大尺寸 Input 的字号问题。[#34381](https://github.com/ant-design/ant-design/pull/34381)
- 💄 修复 Input.Group 多余的错误边框样式。[#34412](https://github.com/ant-design/ant-design/pull/34412)
- Form
- 🐞 修复 Form.Item 在 `form.validateFields` 中移除时抛出 `Can't perform a React state update on an unmounted component` 警告的问题。[#34405](https://github.com/ant-design/ant-design/pull/34405)
- 🐞 修复 Form 组件当 `preserve``false``initialValues` 会被更改的问题。[#34411](https://github.com/ant-design/ant-design/pull/34411)
- Tooltip
- 💄 修复 Tooltip 在 Safari 下的内容宽度问题。[#34415](https://github.com/ant-design/ant-design/pull/34415) [@jiandandkl](https://github.com/jiandandkl)
- 💄 修复 Tooltip/Popover/Popconfirm 等组件箭头大小问题。[#34407](https://github.com/ant-design/ant-design/pull/34407)
- 💄 优化 Collapse 简洁模式的底边框。[#34366](https://github.com/ant-design/ant-design/pull/34366) [@PanStar](https://github.com/PanStar)
- TypeScript
- 🤖 修复 Input 不支持 `data-*` TS 定义的问题。[#34410](https://github.com/ant-design/ant-design/pull/34410) [@GitKou](https://github.com/GitKou)
- 🤖 修复 Transfer 的 `footer` 类型定义。[#34337](https://github.com/ant-design/ant-design/pull/34337) [@zomixi](https://github.com/zomixi)
## 4.19.1
`2022-03-08`
- 🐞 修复自定义状态相关的 less 编译错误。[#34350](htps://github.com/ant-dign/ant-design/pull/34350)
- 🐞 修复使用定制主题时 less 编译提示 `ReferenceError: colorPalette is not defined` 错误。
- 🐞 修复引入 `antd/dist/antd.css` 时提示 `Error: Invalid class or id selector syntax` 错误。
- 🐞 修复 Input.Passowrd 图标颜色错误。[#34354](https://github.com/ant-design/ant-design/pull/34354)
- 🐞 修复 ConfigProvider `csp` 有时在 Icon 上不会生效的问题。[#34356](https://github.com/ant-design/ant-design/pull/34356)
## 4.19.0
`2022-03-08`
- 💄 优化部分组件箭头样式。[#33710](https://github.com/ant-design/ant-design/pull/33710)
<img src="https://user-images.githubusercontent.com/27722486/157088587-ca49cc29-bf25-42d1-8c14-020b5501c62e.png" width="500" />
- Input
- 🛠 引入 rc-input 重构 Input 组件为 function component。[#34206](https://github.com/ant-design/ant-design/pull/34206)
- 注意:由于从 class component 变为 function componentInput 组件的 `ref` 类型及内容已经更新,可以通过 `import { InputRef } from 'antd'` 引入。其中的 `input` 属性作为获取 DOM 的途径被保留,同时支持 `focus``blur` 等文档中支持的方法。
- 🆕 新增 `clearIcon` 属性,支持自定义清除按钮。[#34325](https://github.com/ant-design/ant-design/pull/34325)
- Table
- 🆕 `column.filterSearch` 属性现在支持返回一个函数用于自定义搜索条件。[#34085](https://github.com/ant-design/ant-design/pull/34085) [@heiyu4585](https://github.com/heiyu4585)
- 🆕 `column.filterDropdown({ clearFilters })` 支持参数 `clearFilters({ confirm: false, closeDropdown: false })` 控制筛选。[#34120](https://github.com/ant-design/ant-design/pull/34120) [@heiyu4585](https://github.com/heiyu4585)
- ⌨️ 增加 `aria-sort` 属性以优化屏幕阅读器的使用体验。[#33603](https://github.com/ant-design/ant-design/pull/33603) [@dgreene1](https://github.com/dgreene1)
- 🐞 修复 Table 列筛选器中选择全部 Checkbox 状态问题。[#34295](https://github.com/ant-design/ant-design/pull/34295)
- 🆕 表单组件新增 `status` 属性以支持自定义状态。
- Transfer [#34098](https://github.com/ant-design/ant-design/pull/34098)
- AutoComplete [#34096](https://github.com/ant-design/ant-design/pull/34096)
- TreeSelect [#34093](https://github.com/ant-design/ant-design/pull/34093)
- Cascader [#34086](https://github.com/ant-design/ant-design/pull/34086)
- Select [#34084](https://github.com/ant-design/ant-design/pull/34084)
- DatePicker 和 TimePicker [#34073](https://github.com/ant-design/ant-design/pull/34073)
- Mentions [#34071](https://github.com/ant-design/ant-design/pull/34071)
- InputNumber [#34042](https://github.com/ant-design/ant-design/pull/34042)
- Input [#33995](https://github.com/ant-design/ant-design/pull/33995)
<img src="https://user-images.githubusercontent.com/27722486/157089015-f96b0153-2cc4-4e04-94d6-e0e4b195d5d1.png" width="500" />
- 🆕 InputNumber 组件支持 `controls={{ upIcon, downIcon }}` 用于自定义上下图标。[#33914](https://github.com/ant-design/ant-design/pull/33914) [@heiyu4585](https://github.com/heiyu4585)
- 🆕 Notification 组件弹窗位置新增支持 `top` / `bottom`。[#33871](https://github.com/ant-design/ant-design/pull/33871) [@heiyu4585](https://github.com/heiyu4585)
- 🆕 Select、Cascader、DatePicker 等组件新增 `placement` 用于自定义弹层方向。[#33641](https://github.com/ant-design/ant-design/pull/33541) [@ONLY-yours](https://github.com/ONLY-yours)
- 🆕 Dropdown 组件支持 `arrow={{ pointAtCenter: true }}` 用于指向元素正中间,并且新增 `top` `bottom` 两种 `placement` 位置。[#33658](https://github.com/ant-design/ant-design/pull/33658)
- 🆕 Skeleton.Input 添加 `block` 属性。[#33672](https://github.com/ant-design/ant-design/pull/33672) [@woochanleee](https://github.com/woochanleee)
- 🆕 合并 TimePicker `disabledHours`、`disabledMinutes`、`disabledSeconds` 至 `disabledTime` 以保持与 DatePicker 接口一致性。[#33503](https://github.com/ant-design/ant-design/pull/33503)
- 💄 修改部分边框颜色和进度条的背景色为透明色以适应有色背景。[#33506](https://github.com/ant-design/ant-design/pull/33506)
- 💄 Space 支持自定义 children 的 `key`。[#33607](https://github.com/ant-design/ant-design/pull/33607) [@qin20](https://github.com/qin20)
- 🐞 修复 Typography.Title 进入编辑模式时大小不一致的问题。[#34169](https://github.com/ant-design/ant-design/pull/34169) [@heiyu4585](https://github.com/heiyu4585)
- 🐞 修复 Form.Item 抛出 `React does not recognize the requiredMark prop on a DOM element` 的问题。[#34323](https://github.com/ant-design/ant-design/pull/34323)
## 4.18.9
`2022-02-28`
- 🆕 新增 Radio、Divider、Modal、Dropdown、Drawer 主题变量。[#34194](https://github.com/ant-design/ant-design/pull/34194) [#34187](https://github.com/ant-design/ant-design/pull/34187) [#34191](https://github.com/ant-design/ant-design/pull/34191) [#34189](https://github.com/ant-design/ant-design/pull/34189) [#34188](https://github.com/ant-design/ant-design/pull/34188) [@qdzhaoxiaodao](https://github.com/qdzhaoxiaodao)
- 🐞 修复 Form 组件当 `preserve``false``initialValues` 会被更改的问题。[#34153](https://github.com/ant-design/ant-design/pull/34153)
- 💄 修复 Dropdown 菜单项文本太长没有换行的问题。[#34177](https://github.com/ant-design/ant-design/pull/3417)
- TypeScript
- 🐞 修复 Upload `onChange` 参数泛型传递。[#34161](https://github.com/ant-design/ant-design/pull/34161) [@wangcch](https://github.com/wangcch)
## 4.18.8
`2022-02-21`
- 🐞 修复 `message.config` 多次配置 `getContainer` 时无法生效的问题。[#34123](https://github.com/ant-design/ant-design/pull/34123) [@TrickyPi](https://github.com/TrickyPi)
- 🐞 修复 Menu 组件中无效的缓存逻辑。[#34121](https://github.com/ant-design/ant-design/pull/34121) [@mrwd2009](https://github.com/mrwd2009)
- 🐞 修复 ConfigProvider 在服务端配置主题会崩溃的问题,同时现在会提示动态主题于 SSR 上无效。[#34118](https://github.com/ant-design/ant-design/pull/34118)
- Table
- ⚡️ 修复 Table 在首次加载时会渲染两次的问题。[#34106](https://github.com/ant-design/ant-design/pull/34106)
- ⚡️ 优化 Table 渲染性能,现在不使用废弃 `column.render: () => { children, props }` 方法时默认会跳过无用渲染。[#34075](https://github.com/ant-design/ant-design/pull/34075)
- 🐞 修复 Typography 启用 `copyable``children` 内容变化后复制内容没变的问题。[#34034](https://github.com/ant-design/ant-design/pull/34034) [@opopeieie](https://github.com/opopeieie)
- ⚡️ 优化 Avatar、List、Pagination、Steps 以防止初始化时非必要的额外渲染。[34122](https://github.com/ant-design/ant-design/pull/34122)
- 💄 修复 Form 下 Select 内容太长导致布局换行的问题。[#34117](https://github.com/ant-design/ant-design/pull/34117)
- 🇸🇰 完善 `sk-SK` 中 Table、Form、Modal 的文案。[#34061](https://github.com/ant-design/ant-design/pull/34061) [@xseman](https://github.com/xseman)
- TypeScript
- 🤖 导出 Layout 组件的 `SiderProps` 类型。[#34137](https://github.com/ant-design/ant-design/pull/34137) [@Picsong](https://github.com/Picsong)
## 4.18.7
`2022-02-14`
- Typography
- 🛠 修复 Typography 在 SSR 渲染时警告 `useLayoutEffect` 的问题。[#33818](https://github.com/ant-design/ant-design/pull/33818) [@SoYoung210](https://github.com/SoYoung210)
- 🐞 修复 Typography 配置 `ellipsis` 后在某些情况下会出现滚动条的问题。[#34007](https://github.com/ant-design/ant-design/pull/34007)
- 🐞 Typography 复制按钮点击事件不在冒泡。[#33998](https://github.com/ant-design/ant-design/pull/33998) [@linxianxi](https://github.com/linxianxi)
- 🐞 修复 Typography 中编辑和拷贝按钮无法响应 Enter 按键的问题。[#33976](https://github.com/ant-design/ant-design/pull/33976) [@mrwd2009](https://github.com/mrwd2009)
- 🐞 修复 Form 波兰语中表单校验部分文案未定义的问题。[#34024](https://github.com/ant-design/ant-design/pull/34024) [@MichalPodeszwa](https://github.com/MichalPodeszwa)
- 🐞 修复 Input.TextArea 设置 `maxLength` 时光标位置会影响超出部分截取的问题。[#33910](https://github.com/ant-design/ant-design/pull/33910) [@chenyizhongx](https://github.com/chenyizhongx)
- 💄 Button 对于 `type=default` 也会提供 `.ant-btn-default` 的样式类名。[#34013](https://github.com/ant-design/ant-design/pull/34013)
- 💄 优化 Menu `:focus-visible` 的样式。[#34008](https://github.com/ant-design/ant-design/pull/34008)
- 💄 修复 Pagination 和 Rate 在 Safari 下部分样式丢失的问题,比如分页按钮禁用样式失效。[#34002](https://github.com/ant-design/ant-design/pull/34002)
- 💄 修复 Row 与 Col 在配置 `prefixCls` 的样式问题。[#33969](https://github.com/ant-design/ant-design/pull/33969) [@mic-web](https://github.com/mic-web)
- 🐞 修复 Timeline 的自定义图标颜色无效的问题。[#33951](https://github.com/ant-design/ant-design/pull/33951) [@MadCcc](https://github.com/MadCcc)
- TypeScript
- 🤖 优化 Cascader `multiple` 属性对应的 `onChange` 类型推断。[#33947](https://github.com/ant-design/ant-design/pull/33947) [@babycannotsay](https://github.com/babycannotsay)
## 4.18.6
@ -48,7 +168,7 @@ timeline: true
- Typography
- ⚡️ 优化 Typography 在配置 `tooltip` 时优先使用原生省略样式以提升性能。[#33669](https://github.com/ant-design/ant-design/pull/33669)
- 🐞 重构 Typography `ellipsis` 逻辑以修复 `children` 如果消费上游 Context 会报错的问题。 [#33725](https://github.com/ant-design/ant-design/pull/33725)
- 🐞 重构 Typography `ellipsis` 逻辑以修复 `children` 如果消费上游 Context 会报错的问题。[#33725](https://github.com/ant-design/ant-design/pull/33725)
- Icon
- 🐞 修复 `<Icon component={HomeOutlined} />``<HomeOutlined />` 不对齐的问题。[#33709](https://github.com/ant-design/ant-design/pull/33709)
- 🐞 修复 `<Icon component={SyncOutlined} spin />` 抖动的问题。[#33726](https://github.com/ant-design/ant-design/pull/33726) [@JX-Zhuang](https://github.com/JX-Zhuang)
@ -274,7 +394,7 @@ timeline: true
- 🐞 修复 Button `ghost` 鼠标悬停样式。[#32289](https://github.com/ant-design/ant-design/pull/32289)
- 🐞 修复 Button 配置 `loading` 时,无法触发 Tooltip 的问题。[#32158](https://github.com/ant-design/ant-design/pull/32158)
- Pagination
- 🆕 Pagination 支持定制 `selectComponentClass` [#32132](https://github.com/ant-design/ant-design/pull/32132) [@JounQin](https://github.com/JounQin)
- 🆕 Pagination 支持定制 `selectComponentClass`。[#32132](https://github.com/ant-design/ant-design/pull/32132) [@JounQin](https://github.com/JounQin)
- 💄 Pagination `simple` 属性下中翻页 input 增加 box-shadow。[#32528](https://github.com/ant-design/ant-design/pull/32528) [@chen-jingjie](https://github.com/chen-jingjie)
- Upload
- 🐞 修复 Upload `listStyle="picture"` 下加载中样式错位的问题。[#32664](https://github.com/ant-design/ant-design/pull/32664)
@ -327,7 +447,7 @@ timeline: true
- 🤖 修复 Switch `id` 属性定义。[#32237](https://github.com/ant-design/ant-design/pull/32237) [@M-ZubairAhmed](https://github.com/M-ZubairAhmed)
- 🤖 修复 Button 的 `type` 的 TS 类型定义。[#32004](https://github.com/ant-design/ant-design/pull/32004) [@jaredleechn](https://github.com/jaredleechn)
- 🤖 完备 Pagination 的 `locale` TS 类型定义。[[#32128](https://github.com/ant-design/ant-design/pull/32128) [@JounQin](https://github.com/JounQin)
- 🤖 完善并导出 DropdownButton 的 `DropdownButtonType` TS 类型定义。 [[#31957](https://github.com/ant-design/ant-design/pull/31957) [@Dreamerryao](https://github.com/Dreamerryao)
- 🤖 完善并导出 DropdownButton 的 `DropdownButtonType` TS 类型定义。[[#31957](https://github.com/ant-design/ant-design/pull/31957) [@Dreamerryao](https://github.com/Dreamerryao)
- 🤖 调整 List 组件 `rowKey` 类型为 React.key。[#32033](https://github.com/ant-design/ant-design/pull/32033) [@lironhl](https://github.com/lironhl)
- 🐞 修复 DatePicker `ref` 类型。[#31993](https://github.com/ant-design/ant-design/pull/31993) [@acfasj](https://github.com/acfasj)
- 🤖 更新 Drawer 中 `levelMove` 类型定义。[#30714](https://github.com/ant-design/ant-design/pull/30714) [@g0shed](https://github.com/g0shed)
@ -376,7 +496,7 @@ timeline: true
- 🐞 修复 Progress 环形进度条 `success.strokeColor` 不生效的问题。[#31589](https://github.com/ant-design/ant-design/pull/31589)
- 🐞 修复 Select 组件没有忽略 `getRawInputElement` 属性导致的类型报错问题。[#31566](https://github.com/ant-design/ant-design/pull/31566) [@aoilti](https://github.com/aoilti)
- 🐞 修复 Pagination 的 `totalBoundaryShowSizeChanger` 属性类型错误。[#31549](https://github.com/ant-design/ant-design/pull/31549) [@Monty-Ma](https://github.com/Monty-Ma)
- 🐞 修复 Skeleton.Avatar `className` 重复应用的问题。 [#31536](https://github.com/ant-design/ant-design/pull/31536) [@Greatshock](https://github.com/Greatshock)
- 🐞 修复 Skeleton.Avatar `className` 重复应用的问题。[#31536](https://github.com/ant-design/ant-design/pull/31536) [@Greatshock](https://github.com/Greatshock)
- 🌐 国际化
- 🇹🇷 为 Image 组件中 `预览` 文案增加土耳其语翻译。[#31593](https://github.com/ant-design/ant-design/pull/31593) [@mburakkalkan](https://github.com/mburakkalkan)
- 🇰🇷 修复韩语中的错别字。[#31575](https://github.com/ant-design/ant-design/pull/31575) [@chatoo2412](https://github.com/chatoo2412)
@ -697,7 +817,7 @@ timeline: true
- 🐞 修复 Modal 页脚里使用 href 按钮导致的间距丢失问题。[#29681](https://github.com/ant-design/ant-design/pull/29681) [@n0ruSh](https://github.com/n0ruSh)
- 💄 修复 Input 组件配置附件元素时禁用样式异常的问题。[#29670](https://github.com/ant-design/ant-design/pull/29670)
- 💄 优化 Form.Item 提示信息的鼠标显示样式。[#29650](https://github.com/ant-design/ant-design/pull/29650)
- 🇨🇿 修复 cs_CZ 语言环境中的错字。 [#29675](https://github.com/ant-design/ant-design/pull/29675) [@jvaclavik](https://github.com/jvaclavik)
- 🇨🇿 修复 cs_CZ 语言环境中的错字。[#29675](https://github.com/ant-design/ant-design/pull/29675) [@jvaclavik](https://github.com/jvaclavik)
- 🇨🇦 添加 fr_CA 语言。[#29748](https://github.com/ant-design/ant-design/pull/29748) [@liufenghua808](https://github.com/liufenghua808)
## 4.13.1
@ -797,7 +917,7 @@ timeline: true
- TypeScript
- 🤖 更新 Table TypeScript 定义 `dataSource``readonly`。[#29084](https://github.com/ant-design/ant-design/pull/29084)
- Less
- 💄 增加 less 变量 `@progress-info-text-color` [#28981](https://github.com/ant-design/ant-design/pull/28981) [@yuxuan](https://github.com/yuxuan)
- 💄 增加 less 变量 `@progress-info-text-color`。[#28981](https://github.com/ant-design/ant-design/pull/28981) [@yuxuan](https://github.com/yuxuan)
## 4.11.3
@ -936,7 +1056,7 @@ timeline: true
- 🆕 多选模式下 `maxTagCount` 支持 `responsive`。[#28520](https://github.com/ant-design/ant-design/pull/28520)
- 🆕 Slider 新增 range.draggableTrack 以支持范围刻度整体可拖拽。[#28592](https://github.com/ant-design/ant-design/pull/28592)
- 🆕 `message` 新增 `onClick` 回调,会在消息被点击时触发。[#28148](https://github.com/ant-design/ant-design/pull/28148) [@ZeroTo0ne](https://github.com/ant-design/ant-design/pull/28148)
- 🆕 Descriptions 上可以统一设置 `labelStyle``contentStyle` [#28613](https://github.com/ant-design/ant-design/pull/28613)
- 🆕 Descriptions 上可以统一设置 `labelStyle``contentStyle`。[#28613](https://github.com/ant-design/ant-design/pull/28613)
- 🆕 Form 的 `scrollToFirstError` 属性支持设置滚动的位置参数。[#28272](https://github.com/ant-design/ant-design/pull/28272) [@vouis](https://github.com/vouis)
- 🆕 Steps 新增 reponsive 属性用于关闭响应式样式。[#28459](https://github.com/ant-design/ant-design/pull/28459)
- 🌐 国际化
@ -1327,7 +1447,7 @@ timeline: true
- 🐞 修复 Form 使用 `help` 时出现的同构问题。[#26542](https://github.com/ant-design/ant-design/pull/26542)
- 🐞 修复 Avatar 在 `display: none` 时不会正确缩放 fallback 文字的问题。[#26522](https://github.com/ant-design/ant-design/pull/26522) [@zhangyu1818](https://github.com/zhangyu1818)
- TypeScript
- 🤖 Col 增加 `ColSize` 增加 `flex` 的定义。 [#26578](https://github.com/ant-design/ant-design/pull/26578) [@blaiz](https://github.com/blaiz)
- 🤖 Col 增加 `ColSize` 增加 `flex` 的定义。[#26578](https://github.com/ant-design/ant-design/pull/26578) [@blaiz](https://github.com/blaiz)
- 🤖 修复 Tooltip/Popover `children` 定义不接受 ReactNode 的问题。[#26534](https://github.com/ant-design/ant-design/pull/26534)
## 4.6.2

View File

@ -1,7 +1,7 @@
import * as React from 'react';
import useState from 'rc-util/lib/hooks/useState';
import Button from '../button';
import { LegacyButtonType, ButtonProps, convertLegacyProps } from '../button/button';
import useDestroyed from './hooks/useDestroyed';
export interface ActionButtonProps {
type?: LegacyButtonType;
@ -21,8 +21,7 @@ function isThenable(thing?: PromiseLike<any>): boolean {
const ActionButton: React.FC<ActionButtonProps> = props => {
const clickedRef = React.useRef<boolean>(false);
const ref = React.useRef<any>();
const isDestroyed = useDestroyed();
const [loading, setLoading] = React.useState<ButtonProps['loading']>(false);
const [loading, setLoading] = useState<ButtonProps['loading']>(false);
React.useEffect(() => {
let timeoutId: any;
@ -45,9 +44,7 @@ const ActionButton: React.FC<ActionButtonProps> = props => {
setLoading(true);
returnValueOfOnOk!.then(
(...args: any[]) => {
if (!isDestroyed()) {
setLoading(false);
}
setLoading(false, true);
close(...args);
clickedRef.current = false;
},
@ -56,9 +53,7 @@ const ActionButton: React.FC<ActionButtonProps> = props => {
// eslint-disable-next-line no-console
console.error(e);
// See: https://github.com/ant-design/ant-design/issues/6183
if (!isDestroyed()) {
setLoading(false);
}
setLoading(false, true);
clickedRef.current = false;
},
);

View File

@ -0,0 +1,17 @@
import capitalize from '../capitalize';
describe('capitalize', () => {
it('should capitalize the first character of a string', () => {
expect(capitalize('antd')).toBe('Antd');
expect(capitalize('Antd')).toBe('Antd');
expect(capitalize(' antd')).toBe(' antd');
expect(capitalize('')).toBe('');
});
it('should return the original value when is not a string', () => {
expect(capitalize(1 as any)).toBe(1);
expect(capitalize(true as any)).toBe(true);
expect(capitalize(undefined as any)).toBe(undefined);
expect(capitalize(null as any)).toBe(null);
});
});

View File

@ -1,20 +0,0 @@
import { mount } from 'enzyme';
import React from 'react';
import useDestroyed from '../hooks/useDestroyed';
describe('useMounted', () => {
it('should work properly', () => {
let isDestroyed = null;
const AutoUnmounted = () => {
isDestroyed = useDestroyed();
return <div>Mounted</div>;
};
const wrapper = mount(<AutoUnmounted />);
expect(isDestroyed()).toBeFalsy();
wrapper.unmount();
expect(isDestroyed()).toBeTruthy();
});
});

View File

@ -0,0 +1,8 @@
export default function capitalize<T extends string>(str: T): Capitalize<T> {
if (typeof str !== 'string') {
return str;
}
const ret = str.charAt(0).toUpperCase() + str.slice(1);
return ret as Capitalize<T>;
}

View File

@ -1,8 +1,8 @@
export default function getDataOrAriaProps(props: any) {
return Object.keys(props).reduce((prev: any, key: string) => {
if (
(key.substr(0, 5) === 'data-' || key.substr(0, 5) === 'aria-' || key === 'role') &&
key.substr(0, 7) !== 'data-__'
(key.startsWith('data-') || key.startsWith('aria-') || key === 'role') &&
!key.startsWith('data-__')
) {
prev[key] = props[key];
}

View File

@ -1,14 +0,0 @@
import * as React from 'react';
export default function useDestroyed() {
const mountedRef = React.useRef<boolean>(true);
React.useEffect(
() => () => {
mountedRef.current = false;
},
[],
);
return () => !mountedRef.current;
}

View File

@ -1,5 +1,6 @@
import { CSSMotionProps, MotionEventHandler, MotionEndEventHandler } from 'rc-motion';
import { MotionEvent } from 'rc-motion/lib/interface';
import { tuple } from './type';
// ================== Collapse Motion ==================
const getCollapsedHeight: MotionEventHandler = () => ({ height: 0, opacity: 0 });
@ -25,11 +26,22 @@ const collapseMotion: CSSMotionProps = {
motionDeadline: 500,
};
const SelectPlacements = tuple('bottomLeft', 'bottomRight', 'topLeft', 'topRight');
export type SelectCommonPlacement = typeof SelectPlacements[number];
const getTransitionDirection = (placement: SelectCommonPlacement | undefined) => {
if (placement !== undefined && (placement === 'topLeft' || placement === 'topRight')) {
return `slide-down`;
}
return `slide-up`;
};
const getTransitionName = (rootPrefixCls: string, motion: string, transitionName?: string) => {
if (transitionName !== undefined) {
return transitionName;
}
return `${rootPrefixCls}-${motion}`;
};
export { getTransitionName };
export { getTransitionName, getTransitionDirection };
export default collapseMotion;

View File

@ -42,6 +42,7 @@ export default function getPlacements(config: PlacementsConfig) {
horizontalArrowShift = 16,
verticalArrowShift = 8,
autoAdjustOverflow,
arrowPointAtCenter,
} = config;
const placementMap: BuildInPlacements = {
left: {
@ -94,7 +95,7 @@ export default function getPlacements(config: PlacementsConfig) {
},
};
Object.keys(placementMap).forEach(key => {
placementMap[key] = config.arrowPointAtCenter
placementMap[key] = arrowPointAtCenter
? {
...placementMap[key],
overflow: getOverflowOptions(autoAdjustOverflow),

View File

@ -0,0 +1,44 @@
import React from 'react';
import CheckCircleFilled from '@ant-design/icons/CheckCircleFilled';
import ExclamationCircleFilled from '@ant-design/icons/ExclamationCircleFilled';
import CloseCircleFilled from '@ant-design/icons/CloseCircleFilled';
import LoadingOutlined from '@ant-design/icons/LoadingOutlined';
import classNames from 'classnames';
import { ValidateStatus } from '../form/FormItem';
import { tuple } from './type';
const InputStatuses = tuple('warning', 'error', '');
export type InputStatus = typeof InputStatuses[number];
const iconMap = {
success: CheckCircleFilled,
warning: ExclamationCircleFilled,
error: CloseCircleFilled,
validating: LoadingOutlined,
};
export const getFeedbackIcon = (prefixCls: string, status?: ValidateStatus) => {
const IconNode = status && iconMap[status];
return IconNode ? (
<span className={`${prefixCls}-feedback-icon`}>
<IconNode />
</span>
) : null;
};
export function getStatusClassNames(
prefixCls: string,
status?: ValidateStatus,
hasFeedback?: boolean,
) {
return classNames({
[`${prefixCls}-status-success`]: status === 'success',
[`${prefixCls}-status-warning`]: status === 'warning',
[`${prefixCls}-status-error`]: status === 'error',
[`${prefixCls}-status-validating`]: status === 'validating',
[`${prefixCls}-has-feedback`]: hasFeedback,
});
}
export const getMergedStatus = (contextStatus?: ValidateStatus, customStatus?: InputStatus) =>
customStatus || contextStatus;

View File

@ -0,0 +1,86 @@
import { TinyColor } from '@ctrl/tinycolor';
import type { DesignToken, PresetColorType } from '.';
const presetColors: PresetColorType = {
blue: '#1890FF',
purple: '#722ED1',
cyan: '#13C2C2',
green: '#52C41A',
magenta: '#EB2F96',
pink: '#eb2f96',
red: '#F5222D',
orange: '#FA8C16',
yellow: '#FADB14',
volcano: '#FA541C',
geekblue: '#2F54EB',
gold: '#FAAD14',
lime: '#A0D911',
};
const defaultDesignToken: DesignToken = {
primaryColor: '#1890ff',
successColor: '#52c41a',
warningColor: '#faad14',
errorColor: '#ff4d4f',
infoColor: '#1890ff',
// https://github.com/ant-design/ant-design/issues/20210
lineHeight: 1.5715,
borderWidth: 1,
borderStyle: 'solid',
borderRadius: 2,
borderColor: new TinyColor({ h: 0, s: 0, v: 85 }).toHexString(),
borderColorSplit: new TinyColor({ h: 0, s: 0, v: 94 }).toHexString(),
easeInOut: `cubic-bezier(0.645, 0.045, 0.355, 1)`,
easeInOutCirc: `cubic-bezier(0.78, 0.14, 0.15, 0.86)`,
easeOutBack: `cubic-bezier(0.12, 0.4, 0.29, 1.46)`,
easeInQuint: `cubic-bezier(0.755, 0.05, 0.855, 0.06)`,
easeOutQuint: `cubic-bezier(0.23, 1, 0.32, 1)`,
outlineWidth: 2,
outlineBlurSize: 0,
fontSize: 14,
fontFamily: `-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
'Noto Color Emoji'`,
textColor: new TinyColor('#000').setAlpha(0.85).toRgbString(),
textColorSecondary: new TinyColor('#000').setAlpha(0.45).toRgbString(),
textColorDisabled: new TinyColor('#000').setAlpha(0.25).toRgbString(),
textColorInverse: '#fff',
placeholderColor: new TinyColor({ h: 0, s: 0, v: 75 }).setAlpha(0.5).toRgbString(),
disabledColor: new TinyColor('#000').setAlpha(0.25).toRgbString(),
headingColor: new TinyColor('#000').setAlpha(0.85).toRgbString(),
iconColorHover: new TinyColor('#000').setAlpha(0.75).toRgbString(),
itemHoverBackground: '#f5f5f5',
controlHeight: 32,
padding: 16,
margin: 16,
// Default grey background color
background: new TinyColor({ h: 0, s: 0, v: 96 }).toHexString(),
// background of header and selected item
backgroundLight: new TinyColor({ h: 0, s: 0, v: 98 }).toHexString(),
componentBackground: '#fff',
componentBackgroundDisabled: new TinyColor({ h: 0, s: 0, v: 96 }).toHexString(),
duration: 0.3,
zIndexDropdown: 1050,
// preset color palettes
...presetColors,
};
export default defaultDesignToken;

View File

@ -0,0 +1,364 @@
import React from 'react';
import { generate } from '@ant-design/colors';
import { TinyColor } from '@ctrl/tinycolor';
import {
CSSInterpolation,
CSSObject,
Theme,
useCacheToken,
useStyleRegister,
} from '@ant-design/cssinjs';
import defaultDesignToken from './default';
import version from '../../version';
import { resetComponent, resetIcon, clearFix } from './util';
import {
initSlideMotion,
slideUpIn,
slideUpOut,
slideDownIn,
slideDownOut,
slideLeftIn,
slideLeftOut,
slideRightIn,
slideRightOut,
} from './util/slide';
export {
resetComponent,
resetIcon,
clearFix,
initSlideMotion,
slideUpIn,
slideUpOut,
slideDownIn,
slideDownOut,
slideLeftIn,
slideLeftOut,
slideRightIn,
slideRightOut,
};
export interface PresetColorType {
blue: string;
purple: string;
cyan: string;
green: string;
magenta: string;
pink: string;
red: string;
orange: string;
yellow: string;
volcano: string;
geekblue: string;
lime: string;
gold: string;
}
export const PresetColorKeys: ReadonlyArray<keyof PresetColorType> = [
'blue',
'purple',
'cyan',
'green',
'magenta',
'pink',
'red',
'orange',
'yellow',
'volcano',
'geekblue',
'lime',
'gold',
];
export interface DesignToken extends PresetColorType {
primaryColor: string;
successColor: string;
warningColor: string;
errorColor: string;
infoColor: string;
lineHeight: number;
borderWidth: number;
borderStyle: string;
borderRadius: number;
borderColor: string;
borderColorSplit: string;
easeInOut: string;
easeInOutCirc: string;
easeOutBack: string;
easeInQuint: string;
easeOutQuint: string;
outlineWidth: number;
outlineBlurSize: number;
fontSize: number;
fontFamily: string;
textColor: string;
textColorSecondary: string;
textColorDisabled: string;
textColorInverse: string;
placeholderColor: string;
disabledColor: string;
iconColorHover: string;
headingColor: string;
itemHoverBackground: string;
controlHeight: number;
padding: number;
margin: number;
background: string;
backgroundLight: string;
componentBackground: string;
componentBackgroundDisabled: string;
duration: number;
zIndexDropdown: number;
boxShadow?: string;
}
type ColorPaletteKeyIndexes = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
type ColorPalettes = {
[key in `${keyof PresetColorType}-${ColorPaletteKeyIndexes[number]}`]: string;
};
/** This is temporary token definition since final token definition is not ready yet. */
export interface DerivativeToken extends ColorPalettes, Omit<DesignToken, 'duration'> {
primaryHoverColor: string;
primaryActiveColor: string;
primaryOutlineColor: string;
errorHoverColor: string;
errorActiveColor: string;
errorOutlineColor: string;
warningHoverColor: string;
warningOutlineColor: string;
itemActiveBackground: string;
highlightColor: string;
linkColor: string;
linkHoverColor: string;
linkActiveColor: string;
linkDecoration: CSSObject['textDecoration'];
linkHoverDecoration: CSSObject['textDecoration'];
linkFocusDecoration: CSSObject['textDecoration'];
fontSizeSM: number;
fontSizeLG: number;
/** @private Only Used for control inside component like Multiple Select inner selection item */
controlHeightXS: number;
controlHeightSM: number;
controlHeightLG: number;
controlPaddingHorizontal: number;
controlPaddingHorizontalSM: number;
paddingSM: number;
paddingXS: number;
paddingXXS: number;
paddingLG: number;
marginXS: number;
marginLG: number;
marginXXS: number;
duration: string;
durationMid: string;
durationFast: string;
heading1Size: number;
heading2Size: number;
heading3Size: number;
heading4Size: number;
heading5Size: number;
primaryColors: string[];
errorColors: string[];
warningColors: string[];
// TMP
tmpPrimaryColorWeak: string;
tmpPrimaryHoverColorWeak: string;
// Checked background for Checkable Tag
tmpPrimaryColor6: string;
// Active background for Checkable Tag
tmpPrimaryColor7: string;
tmpSuccessColorDeprecatedBg: string;
tmpWarningColorDeprecatedBg: string;
tmpErrorColorDeprecatedBg: string;
tmpInfoColorDeprecatedBg: string;
tmpSuccessColorDeprecatedBorder: string;
tmpWarningColorDeprecatedBorder: string;
tmpErrorColorDeprecatedBorder: string;
tmpInfoColorDeprecatedBorder: string;
}
export { useStyleRegister };
// =============================== Derivative ===============================
function derivative(designToken: DesignToken): DerivativeToken {
const { primaryColor, errorColor, warningColor, infoColor, successColor } = designToken;
const primaryColors = generate(primaryColor);
const errorColors = generate(errorColor);
const warningColors = generate(warningColor);
const infoColors = generate(infoColor);
const successColors = generate(successColor);
const paddingSM = (designToken.padding / 4) * 3;
const paddingXS = designToken.padding * 0.5;
const colorPalettes = PresetColorKeys.map((colorKey: keyof PresetColorType) => {
const colors = generate(designToken[colorKey]);
const ret = new Array(10).fill(1).reduce((prev, _, i) => {
prev[`${colorKey}-${i + 1}`] = colors[i];
return prev;
}, {}) as ColorPalettes;
return ret;
}).reduce((prev, cur) => {
prev = {
...prev,
...cur,
};
return prev;
}, {} as ColorPalettes);
return {
// FIXME: Need design token
boxShadow: `
0 3px 6px -4px rgba(0, 0, 0, 0.12),
0 6px 16px 0 rgba(0, 0, 0, 0.08),
0 9px 28px 8px rgba(0, 0, 0, 0.05)`,
...designToken,
primaryHoverColor: primaryColors[4],
primaryActiveColor: primaryColors[6],
primaryOutlineColor: new TinyColor(primaryColor).setAlpha(0.2).toRgbString(),
errorHoverColor: errorColors[4],
errorActiveColor: errorColors[6],
errorOutlineColor: new TinyColor(errorColor).setAlpha(0.2).toRgbString(),
warningHoverColor: warningColors[4],
warningOutlineColor: new TinyColor(warningColor).setAlpha(0.2).toRgbString(),
highlightColor: errorColors[4], // FIXME: Should not align with error color
itemActiveBackground: primaryColors[0],
linkColor: primaryColor,
linkHoverColor: primaryColors[4],
linkActiveColor: primaryColors[6],
linkDecoration: 'none',
linkHoverDecoration: 'none',
linkFocusDecoration: 'none',
fontSizeSM: designToken.fontSize - 2,
fontSizeLG: designToken.fontSize + 2,
controlHeightXS: designToken.controlHeight / 2,
controlHeightSM: designToken.controlHeight * 0.75,
controlHeightLG: designToken.controlHeight * 1.25,
controlPaddingHorizontal: paddingSM,
controlPaddingHorizontalSM: paddingXS,
paddingSM,
paddingXS,
paddingXXS: designToken.padding * 0.25,
paddingLG: designToken.padding * 1.5,
marginXS: designToken.margin * 0.5,
marginLG: designToken.margin * 1.5,
marginXXS: designToken.margin * 0.25,
duration: `${designToken.duration}s`,
durationMid: `${(designToken.duration / 3) * 2}s`,
durationFast: `${designToken.duration / 3}s`,
...colorPalettes,
heading1Size: Math.ceil(designToken.fontSize * 2.71),
heading2Size: Math.ceil(designToken.fontSize * 2.14),
heading3Size: Math.ceil(designToken.fontSize * 1.71),
heading4Size: Math.ceil(designToken.fontSize * 1.42),
heading5Size: Math.ceil(designToken.fontSize * 1.14),
primaryColors,
errorColors,
warningColors,
// TMP
tmpPrimaryColorWeak: primaryColors[2],
tmpPrimaryHoverColorWeak: primaryColors[0],
tmpPrimaryColor6: primaryColors[5],
tmpPrimaryColor7: primaryColors[6],
tmpSuccessColorDeprecatedBg: successColors[0],
tmpWarningColorDeprecatedBg: warningColors[0],
tmpErrorColorDeprecatedBg: errorColors[0],
tmpInfoColorDeprecatedBg: infoColors[0],
tmpSuccessColorDeprecatedBorder: successColors[2],
tmpWarningColorDeprecatedBorder: warningColors[2],
tmpErrorColorDeprecatedBorder: errorColors[2],
tmpInfoColorDeprecatedBorder: infoColors[2],
};
}
// ================================ Context =================================
export const ThemeContext = React.createContext(
new Theme<DesignToken, DerivativeToken>(derivative),
);
export const DesignTokenContext = React.createContext<{
token: Partial<DesignToken>;
hashed?: string | boolean;
}>({
token: defaultDesignToken,
});
// ================================== Hook ==================================
export function useToken(): [Theme<DesignToken, DerivativeToken>, DerivativeToken, string] {
const { token: rootDesignToken, hashed } = React.useContext(DesignTokenContext);
const theme = React.useContext(ThemeContext);
const salt = `${version}-${hashed || ''}`;
const [token, hashId] = useCacheToken<DerivativeToken, DesignToken>(
theme,
[defaultDesignToken, rootDesignToken],
{
salt,
},
);
return [theme, token, hashed ? hashId : ''];
}
export type UseComponentStyleResult = [(node: React.ReactNode) => React.ReactElement, string];
export type GenerateStyle<ComponentToken extends object, ReturnType = CSSInterpolation> = (
token: ComponentToken,
hashId?: string,
) => ReturnType;
// ================================== Util ==================================
export function withPrefix(
style: CSSObject,
prefixCls: string,
additionalClsList: string[] = [],
): CSSObject {
const fullClsList = [prefixCls, ...additionalClsList].filter(cls => cls).map(cls => `.${cls}`);
return {
[fullClsList.join('')]: style,
};
}

View File

@ -0,0 +1,56 @@
/* eslint-disable import/prefer-default-export */
import { CSSObject } from '@ant-design/cssinjs';
import type { DerivativeToken } from '..';
export const resetComponent = (token: DerivativeToken): CSSObject => ({
boxSizing: 'border-box',
margin: 0,
padding: 0,
color: token.textColor,
fontSize: token.fontSize,
// font-variant: @font-variant-base;
lineHeight: token.lineHeight,
listStyle: 'none',
// font-feature-settings: @font-feature-settings-base;
});
export const resetIcon = (): CSSObject => ({
display: 'inline-block',
color: 'inherit',
fontStyle: 'normal',
lineHeight: 0,
textAlign: 'center',
textTransform: 'none',
// for SVG icon, see https://blog.prototypr.io/align-svg-icons-to-text-and-say-goodbye-to-font-icons-d44b3d7b26b4
verticalAlign: '-0.125em',
textRendering: 'optimizeLegibility',
'-webkit-font-smoothing': 'antialiased',
'-moz-osx-font-smoothing': 'grayscale',
'> *': {
lineHeight: 1,
},
svg: {
display: 'inline-block',
},
'& &-icon': {
display: 'block',
},
});
export const clearFix = (): CSSObject => ({
// https://github.com/ant-design/ant-design/issues/21301#issuecomment-583955229
'&::before': {
display: 'table',
content: '""',
},
'&::after': {
// https://github.com/ant-design/ant-design/issues/21864
display: 'table',
clear: 'both',
content: '""',
},
});

View File

@ -0,0 +1,52 @@
/* eslint-disable import/prefer-default-export */
import { CSSObject, Keyframes } from '@ant-design/cssinjs';
const initMotionCommon = (duration: string): CSSObject => ({
animationDuration: duration,
animationFillMode: 'both',
});
// FIXME: origin less code seems same as initMotionCommon. Maybe we can safe remove
const initMotionCommonLeave = (duration: string): CSSObject => ({
animationDuration: duration,
animationFillMode: 'both',
});
export const initMotion = (
hashId: string,
motionName: string,
inKeyframes: Keyframes,
outKeyframes: Keyframes,
duration: string,
): CSSObject => {
const motionCls = `.${motionName}`;
return {
[`
${motionCls}-enter,
${motionCls}-appear
`]: {
...initMotionCommon(duration),
animationPlayState: 'paused',
},
[`${motionCls}-leave`]: {
...initMotionCommonLeave(duration),
animationPlayState: 'paused',
},
[`
${motionCls}-enter${motionCls}-enter-active,
${motionCls}-appear${motionCls}-appear-active
`]: {
animationName: inKeyframes.getName(hashId),
animationPlayState: 'running',
},
[`${motionCls}-leave${motionCls}-leave-active`]: {
animationName: outKeyframes.getName(hashId),
animationPlayState: 'running',
pointerEvents: 'none',
},
};
};

View File

@ -0,0 +1,19 @@
import type { CSSObject } from '@ant-design/cssinjs';
import { DerivativeToken } from '..';
// eslint-disable-next-line import/prefer-default-export
export const operationUnit = (token: DerivativeToken): CSSObject => ({
color: token.linkColor,
textDecoration: 'none',
outline: 'none',
cursor: 'pointer',
transition: `color ${token.duration}`,
'&:focus, &:hover': {
color: token.linkHoverColor,
},
'&:active': {
color: token.linkActiveColor,
},
});

View File

@ -0,0 +1,145 @@
import { CSSInterpolation, Keyframes } from '@ant-design/cssinjs';
import type { DerivativeToken } from '..';
import { initMotion } from './motion';
export const initSlideMotion = (
hashId: string,
rootPrefixCls: string,
motionName: string,
inKeyframes: Keyframes,
outKeyframes: Keyframes,
token: DerivativeToken,
): CSSInterpolation => {
const rootMotionName = `${rootPrefixCls}-${motionName}`;
const motionCls = `.${rootMotionName}`;
return [
initMotion(hashId, rootMotionName, inKeyframes, outKeyframes, token.durationMid),
{
[`
${motionCls}-enter,
${motionCls}-appear
`]: {
opacity: 0,
animationTimingFunction: token.easeOutQuint,
},
[`${motionCls}-leave`]: {
animationTimingFunction: token.easeInQuint,
},
},
];
};
export const slideUpIn = new Keyframes('antSlideUpIn', {
'0%': {
transform: 'scaleY(0.8)',
transformOrigin: '0% 0%',
opacity: 0,
},
'100%': {
transform: 'scaleY(1)',
transformOrigin: '0% 0%',
opacity: 1,
},
});
export const slideUpOut = new Keyframes('antSlideUpOut', {
'0%': {
transform: 'scaleY(1)',
transformOrigin: '0% 0%',
opacity: 1,
},
'100%': {
transform: 'scaleY(0.8)',
transformOrigin: '0% 0%',
opacity: 0,
},
});
export const slideDownIn = new Keyframes('antSlideDownIn', {
'0%': {
transform: 'scaleY(0.8)',
transformOrigin: '100% 100%',
opacity: 0,
},
'100%': {
transform: 'scaleY(1)',
transformOrigin: '100% 100%',
opacity: 1,
},
});
export const slideDownOut = new Keyframes('antSlideDownOut', {
'0%': {
transform: 'scaleY(1)',
transformOrigin: '100% 100%',
opacity: 1,
},
'100%': {
transform: 'scaleY(0.8)',
transformOrigin: '100% 100%',
opacity: 0,
},
});
export const slideLeftIn = new Keyframes('antSlideLeftIn', {
'0%': {
transform: 'scaleX(0.8)',
transformOrigin: '0% 0%',
opacity: 0,
},
'100%': {
transform: 'scaleX(1)',
transformOrigin: '0% 0%',
opacity: 1,
},
});
export const slideLeftOut = new Keyframes('antSlideLeftOut', {
'0%': {
transform: 'scaleX(1)',
transformOrigin: '0% 0%',
opacity: 1,
},
'100%': {
transform: 'scaleX(0.8)',
transformOrigin: '0% 0%',
opacity: 0,
},
});
export const slideRightIn = new Keyframes('antSlideRightIn', {
'0%': {
transform: 'scaleX(0.8)',
transformOrigin: '100% 0%',
opacity: 0,
},
'100%': {
transform: 'scaleX(1)',
transformOrigin: '100% 0%',
opacity: 1,
},
});
export const slideRightOut = new Keyframes('antSlideRightOut', {
'0%': {
transform: 'scaleX(1)',
transformOrigin: '100% 0%',
opacity: 1,
},
'100%': {
transform: 'scaleX(0.8)',
transformOrigin: '100% 0%',
opacity: 0,
},
});

View File

@ -17,7 +17,7 @@ class AffixMounter extends React.Component<{
}> {
private container: HTMLDivElement;
public affix: Affix;
public affix: React.Component<AffixProps, AffixState>;
componentDidMount() {
this.container.addEventListener = jest
@ -58,7 +58,7 @@ describe('Affix Render', () => {
const domMock = jest.spyOn(HTMLElement.prototype, 'getBoundingClientRect');
let affixMounterWrapper: ReactWrapper<unknown, unknown, AffixMounter>;
let affixWrapper: ReactWrapper<AffixProps, AffixState, Affix>;
let affixWrapper: ReactWrapper<AffixProps, AffixState, React.Component<AffixProps, AffixState>>;
const classRect: Record<string, DOMRect> = {
container: {
@ -157,9 +157,9 @@ describe('Affix Render', () => {
const getTarget = () => container;
affixWrapper = mount(<Affix target={getTarget}>{null}</Affix>);
affixWrapper.setProps({ target: () => null });
expect(affixWrapper.instance().state.status).toBe(0);
expect(affixWrapper.instance().state.affixStyle).toBe(undefined);
expect(affixWrapper.instance().state.placeholderStyle).toBe(undefined);
expect(affixWrapper.find('Affix').last().state().status).toBe(0);
expect(affixWrapper.find('Affix').last().state().affixStyle).toBe(undefined);
expect(affixWrapper.find('Affix').last().state().placeholderStyle).toBe(undefined);
});
it('instance change', async () => {

View File

@ -33,6 +33,10 @@ export interface AffixProps {
children: React.ReactNode;
}
export interface InternalAffixProps extends AffixProps {
affixPrefixCls: string;
}
enum AffixStatus {
None,
Prepare,
@ -47,7 +51,7 @@ export interface AffixState {
prevTarget: Window | HTMLElement | null;
}
class Affix extends React.Component<AffixProps, AffixState> {
class Affix extends React.Component<InternalAffixProps, AffixState> {
static contextType = ConfigContext;
state: AffixState = {
@ -126,7 +130,7 @@ class Affix extends React.Component<AffixProps, AffixState> {
getOffsetTop = () => {
const { offsetBottom, offsetTop } = this.props;
return (offsetBottom === undefined && offsetTop === undefined) ? 0 : offsetTop;
return offsetBottom === undefined && offsetTop === undefined ? 0 : offsetTop;
};
getOffsetBottom = () => this.props.offsetBottom;
@ -250,14 +254,20 @@ class Affix extends React.Component<AffixProps, AffixState> {
// =================== Render ===================
render() {
const { getPrefixCls } = this.context;
const { affixStyle, placeholderStyle } = this.state;
const { prefixCls, children } = this.props;
const { affixPrefixCls, children } = this.props;
const className = classNames({
[getPrefixCls('affix', prefixCls)]: !!affixStyle,
[affixPrefixCls]: !!affixStyle,
});
let props = omit(this.props, ['prefixCls', 'offsetTop', 'offsetBottom', 'target', 'onChange']);
let props = omit(this.props, [
'prefixCls',
'offsetTop',
'offsetBottom',
'target',
'onChange',
'affixPrefixCls',
]);
// Omit this since `onTestUpdatePosition` only works on test.
if (process.env.NODE_ENV === 'test') {
props = omit(props as typeof props & { onTestUpdatePosition: any }, ['onTestUpdatePosition']);
@ -286,4 +296,23 @@ class Affix extends React.Component<AffixProps, AffixState> {
}
}
export default Affix;
const AffixFC = React.forwardRef<Affix, AffixProps>((props, ref) => {
const { prefixCls: customizePrefixCls } = props;
const { getPrefixCls } = React.useContext(ConfigContext);
const affixPrefixCls = getPrefixCls('affix', customizePrefixCls);
const AffixProps: InternalAffixProps = {
...props,
affixPrefixCls,
};
return <Affix {...AffixProps} ref={ref} />;
});
if (process.env.NODE_ENV !== 'production') {
AffixFC.displayName = 'Affix';
}
export default AffixFC;

View File

@ -1,5 +1,4 @@
import addEventListener from 'rc-util/lib/Dom/addEventListener';
import Affix from '.';
export type BindElement = HTMLElement | Window | null | undefined;
@ -41,7 +40,7 @@ const TRIGGER_EVENTS = [
interface ObserverEntity {
target: HTMLElement | Window;
affixList: Affix[];
affixList: any[];
eventHandlers: { [eventName: string]: any };
}
@ -52,7 +51,7 @@ export function getObserverEntities() {
return observerEntities;
}
export function addObserveTarget(target: HTMLElement | Window | null, affix: Affix): void {
export function addObserveTarget<T>(target: HTMLElement | Window | null, affix: T): void {
if (!target) return;
let entity: ObserverEntity | undefined = observerEntities.find(item => item.target === target);
@ -78,7 +77,7 @@ export function addObserveTarget(target: HTMLElement | Window | null, affix: Aff
}
}
export function removeObserveTarget(affix: Affix): void {
export function removeObserveTarget<T>(affix: T): void {
const observerEntity = observerEntities.find(oriObserverEntity => {
const hasAffix = oriObserverEntity.affixList.some(item => item === affix);
if (hasAffix) {

View File

@ -21,7 +21,7 @@ Alert component for feedback.
| banner | Whether to show as banner | boolean | false | |
| closable | Whether Alert can be closed | boolean | - | |
| closeText | Close text to show | ReactNode | - | |
| closeIcon | Custom close icon | ReactNode | <CloseOutlined /> | 4.17.0 |
| closeIcon | Custom close icon | ReactNode | `<CloseOutlined />` | 4.17.0 |
| description | Additional content of Alert | ReactNode | - | |
| icon | Custom icon, effective when `showIcon` is true | ReactNode | - | |
| message | Content of Alert | ReactNode | - | |

View File

@ -16,6 +16,9 @@ import getDataOrAriaProps from '../_util/getDataOrAriaProps';
import ErrorBoundary from './ErrorBoundary';
import { replaceElement } from '../_util/reactNode';
// CSSINJS
import useStyle from './style';
export interface AlertProps {
/** Type of Alert styles, options:`success`, `info`, `warning`, `error` */
type?: 'success' | 'info' | 'warning' | 'error';
@ -87,8 +90,9 @@ const Alert: AlertInterface = ({
const [closed, setClosed] = React.useState(false);
const ref = React.useRef<HTMLElement>();
const { getPrefixCls, direction } = React.useContext(ConfigContext);
const { getPrefixCls, direction, iconPrefixCls } = React.useContext(ConfigContext);
const prefixCls = getPrefixCls('alert', customizePrefixCls);
const [wrapSSR, hashId] = useStyle(prefixCls, iconPrefixCls);
const handleClose = (e: React.MouseEvent<HTMLButtonElement>) => {
setClosed(true);
@ -147,11 +151,12 @@ const Alert: AlertInterface = ({
[`${prefixCls}-rtl`]: direction === 'rtl',
},
className,
hashId,
);
const dataOrAriaProps = getDataOrAriaProps(props);
return (
return wrapSSR(
<CSSMotion
visible={!closed}
motionName={`${prefixCls}-motion`}
@ -183,7 +188,7 @@ const Alert: AlertInterface = ({
{renderCloseIcon()}
</div>
)}
</CSSMotion>
</CSSMotion>,
);
};

View File

@ -22,7 +22,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/8emPa3fjl/Alert.svg
| banner | 是否用作顶部公告 | boolean | false | |
| closable | 默认不显示关闭按钮 | boolean | - | |
| closeText | 自定义关闭按钮 | ReactNode | - | |
| closeIcon | 自定义关闭 Icon | ReactNode | <CloseOutlined /> | 4.17.0 |
| closeIcon | 自定义关闭 Icon | ReactNode | `<CloseOutlined />` | 4.17.0 |
| description | 警告提示的辅助性文字介绍 | ReactNode | - | |
| icon | 自定义图标,`showIcon` 为 true 时有效 | ReactNode | - | |
| message | 警告提示内容 | ReactNode | - | |

View File

@ -1,155 +1,155 @@
@import '../../style/themes/index';
@import '../../style/mixins/index';
// @import '../../style/themes/index';
// @import '../../style/mixins/index';
@alert-prefix-cls: ~'@{ant-prefix}-alert';
// @alert-prefix-cls: ~'@{ant-prefix}-alert';
.@{alert-prefix-cls} {
.reset-component();
// .@{alert-prefix-cls} {
// .reset-component();
position: relative;
display: flex;
align-items: center;
padding: 8px 15px;
word-wrap: break-word;
border-radius: @border-radius-base;
// position: relative;
// display: flex;
// align-items: center;
// padding: 8px 15px;
// word-wrap: break-word;
// border-radius: @border-radius-base;
&-content {
flex: 1;
min-width: 0;
}
// &-content {
// flex: 1;
// min-width: 0;
// }
&-icon {
margin-right: @margin-xs;
}
// &-icon {
// margin-right: @margin-xs;
// }
&-description {
display: none;
font-size: @font-size-base;
line-height: @font-size-base + 8px;
}
// &-description {
// display: none;
// font-size: @font-size-base;
// line-height: @font-size-base + 8px;
// }
&-success {
background-color: @alert-success-bg-color;
border: @border-width-base @border-style-base @alert-success-border-color;
.@{alert-prefix-cls}-icon {
color: @alert-success-icon-color;
}
}
// &-success {
// background-color: @alert-success-bg-color;
// border: @border-width-base @border-style-base @alert-success-border-color;
// .@{alert-prefix-cls}-icon {
// color: @alert-success-icon-color;
// }
// }
&-info {
background-color: @alert-info-bg-color;
border: @border-width-base @border-style-base @alert-info-border-color;
.@{alert-prefix-cls}-icon {
color: @alert-info-icon-color;
}
}
// &-info {
// background-color: @alert-info-bg-color;
// border: @border-width-base @border-style-base @alert-info-border-color;
// .@{alert-prefix-cls}-icon {
// color: @alert-info-icon-color;
// }
// }
&-warning {
background-color: @alert-warning-bg-color;
border: @border-width-base @border-style-base @alert-warning-border-color;
.@{alert-prefix-cls}-icon {
color: @alert-warning-icon-color;
}
}
// &-warning {
// background-color: @alert-warning-bg-color;
// border: @border-width-base @border-style-base @alert-warning-border-color;
// .@{alert-prefix-cls}-icon {
// color: @alert-warning-icon-color;
// }
// }
&-error {
background-color: @alert-error-bg-color;
border: @border-width-base @border-style-base @alert-error-border-color;
// &-error {
// background-color: @alert-error-bg-color;
// border: @border-width-base @border-style-base @alert-error-border-color;
.@{alert-prefix-cls}-icon {
color: @alert-error-icon-color;
}
// .@{alert-prefix-cls}-icon {
// color: @alert-error-icon-color;
// }
.@{alert-prefix-cls}-description > pre {
margin: 0;
padding: 0;
}
}
// .@{alert-prefix-cls}-description > pre {
// margin: 0;
// padding: 0;
// }
// }
&-action {
margin-left: @margin-xs;
}
// &-action {
// margin-left: @margin-xs;
// }
&-close-icon {
margin-left: @margin-xs;
padding: 0;
overflow: hidden;
font-size: @font-size-sm;
line-height: @font-size-sm;
background-color: transparent;
border: none;
outline: none;
cursor: pointer;
// &-close-icon {
// margin-left: @margin-xs;
// padding: 0;
// overflow: hidden;
// font-size: @font-size-sm;
// line-height: @font-size-sm;
// background-color: transparent;
// border: none;
// outline: none;
// cursor: pointer;
.@{iconfont-css-prefix}-close {
color: @alert-close-color;
transition: color 0.3s;
// .@{iconfont-css-prefix}-close {
// color: @alert-close-color;
// transition: color 0.3s;
&:hover {
color: @alert-close-hover-color;
}
}
}
// &:hover {
// color: @alert-close-hover-color;
// }
// }
// }
&-close-text {
color: @alert-close-color;
transition: color 0.3s;
// &-close-text {
// color: @alert-close-color;
// transition: color 0.3s;
&:hover {
color: @alert-close-hover-color;
}
}
// &:hover {
// color: @alert-close-hover-color;
// }
// }
&-with-description {
align-items: flex-start;
padding: @alert-with-description-padding;
}
// &-with-description {
// align-items: flex-start;
// padding: @alert-with-description-padding;
// }
&-with-description&-no-icon {
padding: @alert-with-description-no-icon-padding-vertical 15px;
}
// &-with-description&-no-icon {
// padding: @alert-with-description-no-icon-padding-vertical 15px;
// }
&-with-description &-icon {
margin-right: @alert-with-description-padding-vertical;
font-size: @alert-with-description-icon-size;
}
// &-with-description &-icon {
// margin-right: @alert-with-description-padding-vertical;
// font-size: @alert-with-description-icon-size;
// }
&-with-description &-message {
display: block;
margin-bottom: 4px;
color: @alert-message-color;
font-size: @font-size-lg;
}
// &-with-description &-message {
// display: block;
// margin-bottom: 4px;
// color: @alert-message-color;
// font-size: @font-size-lg;
// }
&-message {
color: @alert-message-color;
}
// &-message {
// color: @alert-message-color;
// }
&-with-description &-description {
display: block;
}
// &-with-description &-description {
// display: block;
// }
&&-motion-leave {
overflow: hidden;
opacity: 1;
transition: max-height 0.3s @ease-in-out-circ, opacity 0.3s @ease-in-out-circ,
padding-top 0.3s @ease-in-out-circ, padding-bottom 0.3s @ease-in-out-circ,
margin-bottom 0.3s @ease-in-out-circ;
}
// &&-motion-leave {
// overflow: hidden;
// opacity: 1;
// transition: max-height 0.3s @ease-in-out-circ, opacity 0.3s @ease-in-out-circ,
// padding-top 0.3s @ease-in-out-circ, padding-bottom 0.3s @ease-in-out-circ,
// margin-bottom 0.3s @ease-in-out-circ;
// }
&&-motion-leave-active {
max-height: 0;
margin-bottom: 0 !important;
padding-top: 0;
padding-bottom: 0;
opacity: 0;
}
// &&-motion-leave-active {
// max-height: 0;
// margin-bottom: 0 !important;
// padding-top: 0;
// padding-bottom: 0;
// opacity: 0;
// }
&-banner {
margin-bottom: 0;
border: 0;
border-radius: 0;
}
}
// &-banner {
// margin-bottom: 0;
// border: 0;
// border-radius: 0;
// }
// }
@import './rtl';
// @import './rtl';

View File

@ -1,2 +1,342 @@
import '../../style/index.less';
import './index.less';
// import '../../style/index.less';
// import './index.less';
// deps-lint-skip-all
import { generate } from '@ant-design/colors';
import { CSSInterpolation, CSSObject } from '@ant-design/cssinjs';
import {
DerivativeToken,
useStyleRegister,
useToken,
resetComponent,
UseComponentStyleResult,
GenerateStyle,
} from '../../_util/theme';
// FIXME: missing token
type AlertToken = DerivativeToken & {
alertCls: string;
iconPrefixCls: string;
alertMessageColor: string;
alertCloseColor: string;
alertCloseHoverColor: string;
alertInfoBgColor: string;
alertInfoIconColor: string;
alertInfoBorderColor: string;
alertSuccessBgColor: string;
alertSuccessIconColor: string;
alertSuccessBorderColor: string;
alertWarningBgColor: string;
alertWarningIconColor: string;
alertWarningBorderColor: string;
alertErrorBgColor: string;
alertErrorIconColor: string;
alertErrorBorderColor: string;
alertWithDescriptionIconSize: number;
alertWithDescriptionPaddingVertical: number;
alertWithDescriptionNoIconPaddingVertical: number;
};
const genAlertTypeStyle = (
bgColor: string,
borderColor: string,
iconColor: string,
token: AlertToken,
alertCls: string,
): CSSObject => ({
backgroundColor: bgColor,
border: `${token.borderWidth}px ${token.borderStyle} ${borderColor}`,
[`${alertCls}-icon`]: {
color: iconColor,
},
});
export const genBaseStyle: GenerateStyle<AlertToken> = (token: AlertToken): CSSObject => {
const {
alertCls,
duration,
marginXS,
fontSize,
fontSizeLG,
borderRadius,
easeInOutCirc,
alertMessageColor,
alertWithDescriptionIconSize,
alertWithDescriptionPaddingVertical,
alertWithDescriptionNoIconPaddingVertical,
} = token;
return {
[alertCls]: {
...resetComponent(token),
position: 'relative',
display: 'flex',
alignItems: 'center',
padding: '8px 15px',
wordWrap: 'break-word',
borderRadius,
'&&-rtl': {
direction: 'rtl',
},
[`${alertCls}-content`]: {
flex: 1,
minWidth: 0,
},
[`${alertCls}-icon`]: {
marginInlineEnd: marginXS,
},
[`&-description`]: {
display: 'none',
fontSize,
lineHeight: `${fontSize + 8}px`,
},
'&-message': {
color: alertMessageColor,
},
'&&-motion-leave': {
overflow: 'hidden',
opacity: 1,
transition: `max-height ${duration} ${easeInOutCirc}, opacity ${duration} ${easeInOutCirc},
padding-top ${duration} ${easeInOutCirc}, padding-bottom ${duration} ${easeInOutCirc},
margin-bottom ${duration} ${easeInOutCirc}`,
},
'&&-motion-leave-active': {
maxHeight: 0,
marginBottom: '0 !important',
paddingTop: 0,
paddingBottom: 0,
opacity: 0,
},
},
[`${alertCls}-with-description`]: {
alignItems: 'flex-start',
paddingInlineStart: alertWithDescriptionIconSize,
paddingInlineEnd: alertWithDescriptionPaddingVertical,
paddingBlock: alertWithDescriptionPaddingVertical,
[`&${alertCls}-no-icon`]: {
padding: `${alertWithDescriptionNoIconPaddingVertical}px 15px`,
},
[`${alertCls}-icon`]: {
marginInlineEnd: alertWithDescriptionPaddingVertical,
fontSize: alertWithDescriptionIconSize,
},
[`${alertCls}-message`]: {
display: 'block',
marginBottom: '4px',
color: alertMessageColor,
fontSize: fontSizeLG,
},
[`${alertCls}-description`]: {
display: 'block',
},
},
[`${alertCls}-banner`]: {
marginBottom: 0,
border: '0 !important',
borderRadius: 0,
},
};
};
export const genTypeStyle: GenerateStyle<AlertToken> = (token: AlertToken): CSSObject => {
const {
alertCls,
alertInfoBgColor,
alertInfoIconColor,
alertInfoBorderColor,
alertSuccessBgColor,
alertSuccessIconColor,
alertSuccessBorderColor,
alertWarningBgColor,
alertWarningIconColor,
alertWarningBorderColor,
alertErrorBgColor,
alertErrorIconColor,
alertErrorBorderColor,
} = token;
return {
[alertCls]: {
'&-success': genAlertTypeStyle(
alertSuccessBgColor,
alertSuccessBorderColor,
alertSuccessIconColor,
token,
alertCls,
),
'&-info': genAlertTypeStyle(
alertInfoBgColor,
alertInfoBorderColor,
alertInfoIconColor,
token,
alertCls,
),
'&-warning': genAlertTypeStyle(
alertWarningBgColor,
alertWarningBorderColor,
alertWarningIconColor,
token,
alertCls,
),
'&-error': {
...genAlertTypeStyle(
alertErrorBgColor,
alertErrorBorderColor,
alertErrorIconColor,
token,
alertCls,
),
[`${alertCls}-description > pre`]: {
margin: 0,
padding: 0,
},
},
},
};
};
export const genActionStyle: GenerateStyle<AlertToken> = (token: AlertToken): CSSObject => {
const {
alertCls,
iconPrefixCls,
duration,
marginXS,
fontSizeSM,
alertCloseColor,
alertCloseHoverColor,
} = token;
return {
[alertCls]: {
[`&-action`]: {
marginInlineStart: marginXS,
},
[`${alertCls}-close-icon`]: {
marginInlineStart: marginXS,
padding: 0,
overflow: 'hidden',
fontSize: fontSizeSM,
lineHeight: `${fontSizeSM}px`,
backgroundColor: 'transparent',
border: 'none',
outline: 'none',
cursor: 'pointer',
[`.${iconPrefixCls}-close`]: {
color: alertCloseColor,
transition: `color ${duration}`,
'&:hover': {
color: alertCloseHoverColor,
},
},
},
'&-close-text': {
color: alertCloseColor,
transition: `color ${duration}`,
'&:hover': {
color: alertCloseHoverColor,
},
},
},
};
};
export const genAlertStyle: GenerateStyle<AlertToken> = (token: AlertToken): CSSInterpolation => [
genBaseStyle(token),
genTypeStyle(token),
genActionStyle(token),
];
export default function useStyle(
prefixCls: string,
iconPrefixCls: string,
): UseComponentStyleResult {
const [theme, token, hashId] = useToken();
const alertCls = `.${prefixCls}`;
const alertMessageColor = token.headingColor;
const alertCloseColor = token.textColorSecondary;
const alertCloseHoverColor = token.iconColorHover;
// FIXME
const alertWithDescriptionIconSize = 24;
const alertWithDescriptionPaddingVertical = token.padding - 1;
const alertWithDescriptionNoIconPaddingVertical = token.padding - 1;
// FIXME
const infoColors = generate(token.infoColor);
const alertInfoBgColor = infoColors[0];
const alertInfoIconColor = token.infoColor;
const alertInfoBorderColor = infoColors[2];
const successColors = generate(token.successColor);
const alertSuccessBgColor = successColors[0];
const alertSuccessIconColor = token.successColor;
const alertSuccessBorderColor = successColors[2];
const warningColors = generate(token.warningColor);
const alertWarningBgColor = warningColors[0];
const alertWarningIconColor = token.warningColor;
const alertWarningBorderColor = warningColors[2];
const errorColors = generate(token.errorColor);
const alertErrorBgColor = errorColors[0];
const alertErrorIconColor = token.errorColor;
const alertErrorBorderColor = errorColors[2];
const alertToken: AlertToken = {
...token,
alertCls,
iconPrefixCls,
alertInfoBgColor,
alertInfoIconColor,
alertInfoBorderColor,
alertSuccessBgColor,
alertSuccessIconColor,
alertSuccessBorderColor,
alertWarningBgColor,
alertWarningIconColor,
alertWarningBorderColor,
alertErrorBgColor,
alertErrorIconColor,
alertErrorBorderColor,
alertMessageColor,
alertCloseColor,
alertCloseHoverColor,
alertWithDescriptionIconSize,
alertWithDescriptionPaddingVertical,
alertWithDescriptionNoIconPaddingVertical,
};
return [
useStyleRegister({ theme, token, hashId, path: [prefixCls] }, () => [
genAlertStyle(alertToken),
]),
hashId,
];
}

View File

@ -107,7 +107,7 @@ exports[`renders ./components/auto-complete/demo/certain-category.md extend cont
class="ant-select-selection-search"
>
<span
class="ant-input-group-wrapper ant-input-group-wrapper-lg ant-input-search ant-input-search-large ant-select-selection-search-input"
class="ant-input-group-wrapper ant-input-search ant-input-search-large ant-select-selection-search-input ant-input-group-wrapper-lg"
>
<span
class="ant-input-wrapper ant-input-group"
@ -1876,6 +1876,107 @@ exports[`renders ./components/auto-complete/demo/options.md extend context corre
</div>
`;
exports[`renders ./components/auto-complete/demo/status.md extend context correctly 1`] = `
<div
class="ant-space ant-space-vertical"
style="width:100%"
>
<div
class="ant-space-item"
style="margin-bottom:8px"
>
<div
class="ant-select ant-select-status-error ant-select-auto-complete ant-select-single ant-select-show-search"
style="width:200px"
>
<div
class="ant-select-selector"
>
<span
class="ant-select-selection-search"
>
<input
aria-activedescendant="undefined_list_0"
aria-autocomplete="list"
aria-controls="undefined_list"
aria-haspopup="listbox"
aria-owns="undefined_list"
autocomplete="off"
class="ant-select-selection-search-input"
role="combobox"
type="search"
value=""
/>
</span>
<span
class="ant-select-selection-placeholder"
/>
</div>
<div>
<div
class="ant-select-dropdown ant-select-dropdown-empty"
style="opacity:0;pointer-events:none"
>
<div>
<div
class="ant-select-item-empty"
id="undefined_list"
role="listbox"
/>
</div>
</div>
</div>
</div>
</div>
<div
class="ant-space-item"
>
<div
class="ant-select ant-select-status-warning ant-select-auto-complete ant-select-single ant-select-show-search"
style="width:200px"
>
<div
class="ant-select-selector"
>
<span
class="ant-select-selection-search"
>
<input
aria-activedescendant="undefined_list_0"
aria-autocomplete="list"
aria-controls="undefined_list"
aria-haspopup="listbox"
aria-owns="undefined_list"
autocomplete="off"
class="ant-select-selection-search-input"
role="combobox"
type="search"
value=""
/>
</span>
<span
class="ant-select-selection-placeholder"
/>
</div>
<div>
<div
class="ant-select-dropdown ant-select-dropdown-empty"
style="opacity:0;pointer-events:none"
>
<div>
<div
class="ant-select-item-empty"
id="undefined_list"
role="listbox"
/>
</div>
</div>
</div>
</div>
</div>
</div>
`;
exports[`renders ./components/auto-complete/demo/uncertain-category.md extend context correctly 1`] = `
<div
class="ant-select ant-select-auto-complete ant-select-single ant-select-customize-input ant-select-show-search"
@ -1888,7 +1989,7 @@ exports[`renders ./components/auto-complete/demo/uncertain-category.md extend co
class="ant-select-selection-search"
>
<span
class="ant-input-group-wrapper ant-input-group-wrapper-lg ant-input-search ant-input-search-large ant-input-search-with-button ant-select-selection-search-input"
class="ant-input-group-wrapper ant-input-search ant-input-search-large ant-input-search-with-button ant-select-selection-search-input ant-input-group-wrapper-lg"
>
<span
class="ant-input-wrapper ant-input-group"

View File

@ -79,7 +79,7 @@ exports[`renders ./components/auto-complete/demo/certain-category.md correctly 1
class="ant-select-selection-search"
>
<span
class="ant-input-group-wrapper ant-input-group-wrapper-lg ant-input-search ant-input-search-large ant-select-selection-search-input"
class="ant-input-group-wrapper ant-input-search ant-input-search-large ant-select-selection-search-input ant-input-group-wrapper-lg"
>
<span
class="ant-input-wrapper ant-input-group"
@ -1033,6 +1033,79 @@ exports[`renders ./components/auto-complete/demo/options.md correctly 1`] = `
</div>
`;
exports[`renders ./components/auto-complete/demo/status.md correctly 1`] = `
<div
class="ant-space ant-space-vertical"
style="width:100%"
>
<div
class="ant-space-item"
style="margin-bottom:8px"
>
<div
class="ant-select ant-select-status-error ant-select-auto-complete ant-select-single ant-select-show-search"
style="width:200px"
>
<div
class="ant-select-selector"
>
<span
class="ant-select-selection-search"
>
<input
aria-activedescendant="undefined_list_0"
aria-autocomplete="list"
aria-controls="undefined_list"
aria-haspopup="listbox"
aria-owns="undefined_list"
autocomplete="off"
class="ant-select-selection-search-input"
role="combobox"
type="search"
value=""
/>
</span>
<span
class="ant-select-selection-placeholder"
/>
</div>
</div>
</div>
<div
class="ant-space-item"
>
<div
class="ant-select ant-select-status-warning ant-select-auto-complete ant-select-single ant-select-show-search"
style="width:200px"
>
<div
class="ant-select-selector"
>
<span
class="ant-select-selection-search"
>
<input
aria-activedescendant="undefined_list_0"
aria-autocomplete="list"
aria-controls="undefined_list"
aria-haspopup="listbox"
aria-owns="undefined_list"
autocomplete="off"
class="ant-select-selection-search-input"
role="combobox"
type="search"
value=""
/>
</span>
<span
class="ant-select-selection-placeholder"
/>
</div>
</div>
</div>
</div>
`;
exports[`renders ./components/auto-complete/demo/uncertain-category.md correctly 1`] = `
<div
class="ant-select ant-select-auto-complete ant-select-single ant-select-customize-input ant-select-show-search"
@ -1045,7 +1118,7 @@ exports[`renders ./components/auto-complete/demo/uncertain-category.md correctly
class="ant-select-selection-search"
>
<span
class="ant-input-group-wrapper ant-input-group-wrapper-lg ant-input-search ant-input-search-large ant-input-search-with-button ant-select-selection-search-input"
class="ant-input-group-wrapper ant-input-search ant-input-search-large ant-input-search-with-button ant-select-selection-search-input ant-input-group-wrapper-lg"
>
<span
class="ant-input-wrapper ant-input-group"

View File

@ -0,0 +1,42 @@
---
order: 19
version: 4.19.0
title:
zh-CN: 自定义状态
en-US: Status
---
## zh-CN
使用 `status` 为 AutoComplete 添加状态,可选 `error` 或者 `warning`
## en-US
Add status to AutoComplete with `status`, which could be `error` or `warning`.
```tsx
import React, { useState } from 'react';
import { AutoComplete, Space } from 'antd';
const mockVal = (str: string, repeat: number = 1) => ({
value: str.repeat(repeat),
});
const ValidateInputs: React.FC = () => {
const [options, setOptions] = useState<{ value: string }[]>([]);
const onSearch = (searchText: string) => {
setOptions(
!searchText ? [] : [mockVal(searchText), mockVal(searchText, 2), mockVal(searchText, 3)],
);
};
return (
<Space direction="vertical" style={{ width: '100%' }}>
<AutoComplete options={options} onSearch={onSearch} status="error" style={{ width: 200 }} />
<AutoComplete options={options} onSearch={onSearch} status="warning" style={{ width: 200 }} />
</Space>
);
};
ReactDOM.render(<ValidateInputs />, mountNode);
```

View File

@ -38,6 +38,7 @@ The differences with Select are:
| open | Controlled open state of dropdown | boolean | - | |
| options | Select options. Will get better perf than jsx definition | { label, value }\[] | - | |
| placeholder | The placeholder of input | string | - | |
| status | Set validation status | 'error' \| 'warning' | - | 4.19.0 |
| value | Selected option | string | - | |
| onBlur | Called when leaving the component | function() | - | |
| onChange | Called when select an option or input value change, or value of input is changed | function(value) | - | |
@ -45,13 +46,14 @@ The differences with Select are:
| onFocus | Called when entering the component | function() | - | |
| onSearch | Called when searching items | function(value) | - | |
| onSelect | Called when a option is selected. param is option's value and option instance | function(value, option) | - | |
| onClear | Called when clear | function | - | 4.6.0 |
## Methods
| Name | Description | Version |
| --- | --- | --- |
| blur() | Remove focus | |
| focus() | Get focus | |
| Name | Description | Version |
| ------- | ------------ | ------- |
| blur() | Remove focus | |
| focus() | Get focus | |
## FAQ

View File

@ -20,6 +20,7 @@ import Select, {
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
import devWarning from '../_util/devWarning';
import { isValidElement } from '../_util/reactNode';
import { InputStatus } from '../_util/statusUtils';
const { Option } = Select;
@ -37,6 +38,7 @@ export interface AutoCompleteProps<
'inputIcon' | 'loading' | 'mode' | 'optionLabelProp' | 'labelInValue'
> {
dataSource?: DataSourceItemType[];
status?: InputStatus;
}
function isSelectOptionOrSelectOptGroup(child: any): Boolean {

View File

@ -40,6 +40,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/qtJm4yt45/AutoComplete.svg
| open | 是否展开下拉菜单 | boolean | - | |
| options | 数据化配置选项内容,相比 jsx 定义会获得更好的渲染性能 | { label, value }\[] | - | |
| placeholder | 输入框提示 | string | - | |
| status | 设置校验状态 | 'error' \| 'warning' | - | 4.19.0 |
| value | 指定当前选中的条目 | string | - | |
| onBlur | 失去焦点时的回调 | function() | - | |
| onChange | 选中 option或 input 的 value 变化时,调用此函数 | function(value) | - | |
@ -47,13 +48,14 @@ cover: https://gw.alipayobjects.com/zos/alicdn/qtJm4yt45/AutoComplete.svg
| onFocus | 获得焦点时的回调 | function() | - | |
| onSearch | 搜索补全项的时候调用 | function(value) | - | |
| onSelect | 被选中时调用,参数为选中项的 value 值 | function(value, option) | - | |
| onClear | 清除内容时回调 | function | - | 4.6.0 |
## 方法
| 名称 | 描述 | 版本 |
| --- | --- | --- |
| blur() | 移除焦点 | |
| focus() | 获取焦点 | |
| 名称 | 描述 | 版本 |
| ------- | -------- | ---- |
| blur() | 移除焦点 | |
| focus() | 获取焦点 | |
## FAQ

View File

@ -102,7 +102,10 @@ const InternalAvatar: React.ForwardRefRenderFunction<unknown, AvatarProps> = (pr
const size = customSize === 'default' ? groupSize : customSize;
const screens = useBreakpoint();
const needResponsive = Object.keys(typeof size === 'object' ? size || {} : {}).some(key =>
['xs', 'sm', 'md', 'lg', 'xl', 'xxl'].includes(key),
);
const screens = useBreakpoint(needResponsive);
const responsiveSizeStyle: React.CSSProperties = React.useMemo(() => {
if (typeof size !== 'object') {
return {};

View File

@ -30,7 +30,7 @@ const BreadcrumbItem: BreadcrumbItemInterface = ({
const renderBreadcrumbNode = (breadcrumbItem: React.ReactNode) => {
if (overlay) {
return (
<DropDown overlay={overlay} placement="bottomCenter" {...dropdownProps}>
<DropDown overlay={overlay} placement="bottom" {...dropdownProps}>
<span className={`${prefixCls}-overlay-link`}>
{breadcrumbItem}
<DownOutlined />
@ -62,9 +62,7 @@ const BreadcrumbItem: BreadcrumbItemInterface = ({
return (
<span>
{link}
{separator && (
<span className={`${prefixCls}-separator`}>{separator}</span>
)}
{separator && <span className={`${prefixCls}-separator`}>{separator}</span>}
</span>
);
}

View File

@ -0,0 +1,2 @@
import '../../style/index.less';
import './index.less';

View File

@ -12,6 +12,9 @@ import SizeContext, { SizeType } from '../config-provider/SizeContext';
import LoadingIcon from './LoadingIcon';
import { cloneElement } from '../_util/reactNode';
// CSSINJS
import useStyle from './style';
const rxTwoCNChar = /^[\u4e00-\u9fa5]{2}$/;
const isTwoCNChar = rxTwoCNChar.test.bind(rxTwoCNChar);
function isString(str: any) {
@ -151,10 +154,16 @@ const InternalButton: React.ForwardRefRenderFunction<unknown, ButtonProps> = (pr
...rest
} = props;
const { getPrefixCls, autoInsertSpaceInButton, direction, iconPrefixCls } =
React.useContext(ConfigContext);
const prefixCls = getPrefixCls('btn', customizePrefixCls);
// Style
const [wrapSSR, hashId] = useStyle(prefixCls, iconPrefixCls);
const size = React.useContext(SizeContext);
const [innerLoading, setLoading] = React.useState<Loading>(!!loading);
const [hasTwoCNChar, setHasTwoCNChar] = React.useState(false);
const { getPrefixCls, autoInsertSpaceInButton, direction } = React.useContext(ConfigContext);
const buttonRef = (ref as any) || React.createRef<HTMLElement>();
const isNeedInserted = () =>
@ -225,7 +234,6 @@ const InternalButton: React.ForwardRefRenderFunction<unknown, ButtonProps> = (pr
"`link` or `text` button can't be a `ghost` button.",
);
const prefixCls = getPrefixCls('btn', customizePrefixCls);
const autoInsertSpace = autoInsertSpaceInButton !== false;
const sizeClassNameMap = { large: 'lg', small: 'sm', middle: undefined };
@ -236,6 +244,7 @@ const InternalButton: React.ForwardRefRenderFunction<unknown, ButtonProps> = (pr
const classes = classNames(
prefixCls,
hashId,
{
[`${prefixCls}-${shape}`]: shape !== 'default' && shape, // Note: Shape also has `default`
[`${prefixCls}-${type}`]: type,
@ -265,15 +274,15 @@ const InternalButton: React.ForwardRefRenderFunction<unknown, ButtonProps> = (pr
const linkButtonRestProps = omit(rest as AnchorButtonProps & { navigate: any }, ['navigate']);
if (linkButtonRestProps.href !== undefined) {
return (
return wrapSSR(
<a {...linkButtonRestProps} className={classes} onClick={handleClick} ref={buttonRef}>
{iconNode}
{kids}
</a>
</a>,
);
}
const buttonNode = (
let buttonNode = (
<button
{...(rest as NativeButtonProps)}
type={htmlType}
@ -286,11 +295,11 @@ const InternalButton: React.ForwardRefRenderFunction<unknown, ButtonProps> = (pr
</button>
);
if (isUnborderedButtonType(type)) {
return buttonNode;
if (!isUnborderedButtonType(type)) {
buttonNode = <Wave disabled={!!innerLoading}>{buttonNode}</Wave>;
}
return <Wave disabled={!!innerLoading}>{buttonNode}</Wave>;
return wrapSSR(buttonNode);
};
const Button = React.forwardRef<unknown, ButtonProps>(InternalButton) as CompoundedComponent;

View File

@ -1,2 +1,403 @@
import '../../style/index.less';
import './index.less';
// deps-lint-skip-all
import { CSSInterpolation, CSSObject } from '@ant-design/cssinjs';
import { TinyColor } from '@ctrl/tinycolor';
import {
DerivativeToken,
UseComponentStyleResult,
useStyleRegister,
useToken,
withPrefix,
} from '../../_util/theme';
// ============================== Shared ==============================
const genSharedButtonStyle = (
prefixCls: string,
iconPrefixCls: string,
token: DerivativeToken,
): CSSObject => ({
outline: 'none',
position: 'relative',
display: 'inline-block',
fontWeight: 400,
whiteSpace: 'nowrap',
textAlign: 'center',
backgroundImage: 'none',
backgroundColor: 'transparent',
border: `${token.borderWidth}px ${token.borderStyle} transparent`,
cursor: 'pointer',
transition: `all ${token.duration} ${token.easeInOut}`,
userSelect: 'none',
touchAction: 'manipulation',
lineHeight: token.lineHeight,
color: token.textColor,
'> span': {
display: 'inline-block',
},
// Leave a space between icon and text.
[`> .${iconPrefixCls} + span, > span + .${iconPrefixCls}`]: {
marginInlineStart: token.marginXS,
},
[`&.${prefixCls}-block`]: {
width: '100%',
},
});
const genHoverActiveButtonStyle = (hoverStyle: CSSObject, activeStyle: CSSObject): CSSObject => ({
'&:not(:disabled)': {
'&:hover, &:focus': hoverStyle,
'&:active': activeStyle,
},
});
// ============================== Shape ===============================
const genCircleButtonStyle = (token: DerivativeToken): CSSObject => ({
minWidth: token.controlHeight,
paddingInlineStart: 0,
paddingInlineEnd: 0,
borderRadius: '50%',
});
const genRoundButtonStyle = (token: DerivativeToken): CSSObject => ({
borderRadius: token.controlHeight,
paddingInlineStart: token.controlHeight / 2,
paddingInlineEnd: token.controlHeight / 2,
width: 'auto',
});
// =============================== Type ===============================
const genGhostButtonStyle = (
prefixCls: string,
textColor: string | false,
borderColor: string | false,
textColorDisabled: string | false,
borderColorDisabled: string | false,
): CSSObject => ({
[`&.${prefixCls}-background-ghost`]: {
color: textColor || undefined,
backgroundColor: 'transparent',
borderColor: borderColor || undefined,
'&:disabled': {
cursor: 'not-allowed',
color: textColorDisabled || undefined,
borderColor: borderColorDisabled || undefined,
},
},
});
const genSolidDisabledButtonStyle = (token: DerivativeToken): CSSObject => ({
'&:disabled': {
cursor: 'not-allowed',
borderColor: token.borderColor,
color: token.textColorDisabled,
backgroundColor: token.componentBackgroundDisabled,
boxShadow: 'none',
},
});
const genSolidButtonStyle = (token: DerivativeToken): CSSObject => ({
borderRadius: token.borderRadius,
...genSolidDisabledButtonStyle(token),
});
const genPureDisabledButtonStyle = (token: DerivativeToken): CSSObject => ({
'&:disabled': {
cursor: 'not-allowed',
color: token.textColorDisabled,
},
});
// Type: Default
const genDefaultButtonStyle = (prefixCls: string, token: DerivativeToken): CSSObject => ({
...genSolidButtonStyle(token),
backgroundColor: token.componentBackground,
borderColor: token.borderColor,
boxShadow: '0 2px 0 rgba(0, 0, 0, 0.015)',
...genHoverActiveButtonStyle(
{
color: token.primaryHoverColor,
borderColor: token.primaryHoverColor,
},
{
color: token.primaryActiveColor,
borderColor: token.primaryActiveColor,
},
),
...genGhostButtonStyle(
prefixCls,
token.componentBackground,
token.componentBackground,
token.textColorDisabled,
token.borderColor,
),
[`&.${prefixCls}-dangerous`]: {
color: token.errorColor,
borderColor: token.errorColor,
...genHoverActiveButtonStyle(
{
color: token.errorHoverColor,
borderColor: token.errorHoverColor,
},
{
color: token.errorActiveColor,
borderColor: token.errorActiveColor,
},
),
...genGhostButtonStyle(
prefixCls,
token.errorColor,
token.errorColor,
token.textColorDisabled,
token.borderColor,
),
...genSolidDisabledButtonStyle(token),
},
});
// Type: Primary
const genPrimaryButtonStyle = (prefixCls: string, token: DerivativeToken): CSSObject => ({
...genSolidButtonStyle(token),
color: '#FFF',
backgroundColor: token.primaryColor,
boxShadow: '0 2px 0 rgba(0, 0, 0, 0.045)',
...genHoverActiveButtonStyle(
{
backgroundColor: token.primaryHoverColor,
},
{
backgroundColor: token.primaryActiveColor,
},
),
...genGhostButtonStyle(
prefixCls,
token.primaryColor,
token.primaryColor,
token.textColorDisabled,
token.borderColor,
),
[`&.${prefixCls}-dangerous`]: {
backgroundColor: token.errorColor,
...genHoverActiveButtonStyle(
{
backgroundColor: token.errorHoverColor,
},
{
backgroundColor: token.errorActiveColor,
},
),
...genGhostButtonStyle(
prefixCls,
token.errorColor,
token.errorColor,
token.textColorDisabled,
token.borderColor,
),
...genSolidDisabledButtonStyle(token),
},
});
// Type: Dashed
const genDashedButtonStyle = (prefixCls: string, token: DerivativeToken): CSSObject => ({
...genDefaultButtonStyle(prefixCls, token),
borderStyle: 'dashed',
});
// Type: Link
const genLinkButtonStyle = (prefixCls: string, token: DerivativeToken): CSSObject => ({
color: token.linkColor,
...genHoverActiveButtonStyle(
{
color: token.primaryHoverColor,
},
{
color: token.primaryActiveColor,
},
),
...genPureDisabledButtonStyle(token),
[`&.${prefixCls}-dangerous`]: {
color: token.errorColor,
...genHoverActiveButtonStyle(
{
color: token.errorHoverColor,
},
{
color: token.errorActiveColor,
},
),
...genPureDisabledButtonStyle(token),
},
});
// Type: Text
const genTextButtonStyle = (prefixCls: string, token: DerivativeToken): CSSObject => {
const backgroundColor = new TinyColor({ r: 0, g: 0, b: 0, a: 0.018 });
return {
...genHoverActiveButtonStyle(
{
backgroundColor: backgroundColor.toRgbString(),
},
{
backgroundColor: backgroundColor
.clone()
.setAlpha(backgroundColor.getAlpha() * 1.5)
.toRgbString(),
},
),
...genPureDisabledButtonStyle(token),
[`&.${prefixCls}-dangerous`]: {
color: token.errorColor,
...genPureDisabledButtonStyle(token),
},
};
};
const genTypeButtonStyle = (prefixCls: string, token: DerivativeToken): CSSInterpolation => [
withPrefix(genDefaultButtonStyle(prefixCls, token), `${prefixCls}-default`, []),
withPrefix(genPrimaryButtonStyle(prefixCls, token), `${prefixCls}-primary`, []),
withPrefix(genDashedButtonStyle(prefixCls, token), `${prefixCls}-dashed`, []),
withPrefix(genLinkButtonStyle(prefixCls, token), `${prefixCls}-link`, []),
withPrefix(genTextButtonStyle(prefixCls, token), `${prefixCls}-text`, []),
];
// =============================== Size ===============================
const genSizeButtonStyle = (
prefixCls: string,
iconPrefixCls: string,
sizePrefixCls: string,
token: DerivativeToken,
): CSSInterpolation => {
const paddingVertical = Math.max(
0,
(token.controlHeight - token.fontSize * token.lineHeight) / 2 - token.borderWidth,
);
const paddingHorizontal = token.padding - token.borderWidth;
const iconOnlyCls = `.${prefixCls}-icon-only`;
return [
// Size
withPrefix(
{
fontSize: token.fontSize,
height: token.controlHeight,
padding: `${paddingVertical}px ${paddingHorizontal}px`,
[`&${iconOnlyCls}`]: {
width: token.controlHeight,
paddingInlineStart: 0,
paddingInlineEnd: 0,
'> span': {
transform: 'scale(1.143)', // 14px -> 16px
},
},
// Loading
[`&.${prefixCls}-loading`]: {
opacity: 0.65,
cursor: 'default',
},
[`.${prefixCls}-loading-icon`]: {
transition: `width ${token.duration} ${token.easeInOut}, opacity ${token.duration} ${token.easeInOut}`,
},
[`&:not(${iconOnlyCls}) .${prefixCls}-loading-icon > .${iconPrefixCls}`]: {
marginInlineEnd: token.marginXS,
},
},
prefixCls,
[sizePrefixCls],
),
// Shape - patch prefixCls again to override solid border radius style
withPrefix(genCircleButtonStyle(token), `${prefixCls}-circle`, [prefixCls, sizePrefixCls]),
withPrefix(genRoundButtonStyle(token), `${prefixCls}-round`, [prefixCls, sizePrefixCls]),
];
};
const genSizeBaseButtonStyle = (
prefixCls: string,
iconPrefixCls: string,
token: DerivativeToken,
): CSSInterpolation => genSizeButtonStyle(prefixCls, iconPrefixCls, '', token);
const genSizeSmallButtonStyle = (
prefixCls: string,
iconPrefixCls: string,
token: DerivativeToken,
): CSSInterpolation => {
const largeToken: DerivativeToken = {
...token,
controlHeight: token.controlHeightSM,
padding: token.paddingXS,
};
return genSizeButtonStyle(prefixCls, iconPrefixCls, `${prefixCls}-sm`, largeToken);
};
const genSizeLargeButtonStyle = (
prefixCls: string,
iconPrefixCls: string,
token: DerivativeToken,
): CSSInterpolation => {
const largeToken: DerivativeToken = {
...token,
controlHeight: token.controlHeightLG,
fontSize: token.fontSizeLG,
};
return genSizeButtonStyle(prefixCls, iconPrefixCls, `${prefixCls}-lg`, largeToken);
};
// ============================== Export ==============================
export default function useStyle(
prefixCls: string,
iconPrefixCls: string,
): UseComponentStyleResult {
const [theme, token, hashId] = useToken();
return [
useStyleRegister({ theme, token, hashId, path: [prefixCls] }, () => [
// Shared
withPrefix(genSharedButtonStyle(prefixCls, iconPrefixCls, token), prefixCls),
// Size
genSizeSmallButtonStyle(prefixCls, iconPrefixCls, token),
genSizeBaseButtonStyle(prefixCls, iconPrefixCls, token),
genSizeLargeButtonStyle(prefixCls, iconPrefixCls, token),
// Group (type, ghost, danger, disabled, loading)
genTypeButtonStyle(prefixCls, token),
]),
hashId,
];
}

View File

@ -1674,6 +1674,241 @@ exports[`renders ./components/cascader/demo/multiple.md extend context correctly
</div>
`;
exports[`renders ./components/cascader/demo/placement.md extend context correctly 1`] = `
Array [
<div
class="ant-radio-group ant-radio-group-outline"
>
<label
class="ant-radio-button-wrapper ant-radio-button-wrapper-checked"
>
<span
class="ant-radio-button ant-radio-button-checked"
>
<input
checked=""
class="ant-radio-button-input"
type="radio"
value="topLeft"
/>
<span
class="ant-radio-button-inner"
/>
</span>
<span>
topLeft
</span>
</label>
<label
class="ant-radio-button-wrapper"
>
<span
class="ant-radio-button"
>
<input
class="ant-radio-button-input"
type="radio"
value="topRight"
/>
<span
class="ant-radio-button-inner"
/>
</span>
<span>
topRight
</span>
</label>
<label
class="ant-radio-button-wrapper"
>
<span
class="ant-radio-button"
>
<input
class="ant-radio-button-input"
type="radio"
value="bottomLeft"
/>
<span
class="ant-radio-button-inner"
/>
</span>
<span>
bottomLeft
</span>
</label>
<label
class="ant-radio-button-wrapper"
>
<span
class="ant-radio-button"
>
<input
class="ant-radio-button-input"
type="radio"
value="bottomRight"
/>
<span
class="ant-radio-button-inner"
/>
</span>
<span>
bottomRight
</span>
</label>
</div>,
<br />,
<br />,
<div
class="ant-select ant-cascader ant-select-single ant-select-allow-clear ant-select-show-arrow"
>
<div
class="ant-select-selector"
>
<span
class="ant-select-selection-search"
>
<input
aria-autocomplete="list"
aria-controls="undefined_list"
aria-haspopup="listbox"
aria-owns="undefined_list"
autocomplete="off"
class="ant-select-selection-search-input"
readonly=""
role="combobox"
style="opacity:0"
type="search"
unselectable="on"
value=""
/>
</span>
<span
class="ant-select-selection-placeholder"
>
Please select
</span>
</div>
<div>
<div
class="ant-select-dropdown ant-cascader-dropdown"
style="opacity:0;pointer-events:none;min-width:auto"
>
<div>
<div
class="ant-cascader-menus"
>
<ul
class="ant-cascader-menu"
role="menu"
>
<li
aria-checked="false"
class="ant-cascader-menu-item ant-cascader-menu-item-expand"
data-path-key="zhejiang"
role="menuitemcheckbox"
title="Zhejiang"
>
<div
class="ant-cascader-menu-item-content"
>
Zhejiang
</div>
<div
class="ant-cascader-menu-item-expand-icon"
>
<span
aria-label="right"
class="anticon anticon-right"
role="img"
>
<svg
aria-hidden="true"
data-icon="right"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M765.7 486.8L314.9 134.7A7.97 7.97 0 00302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 000-50.4z"
/>
</svg>
</span>
</div>
</li>
<li
aria-checked="false"
class="ant-cascader-menu-item ant-cascader-menu-item-expand"
data-path-key="jiangsu"
role="menuitemcheckbox"
title="Jiangsu"
>
<div
class="ant-cascader-menu-item-content"
>
Jiangsu
</div>
<div
class="ant-cascader-menu-item-expand-icon"
>
<span
aria-label="right"
class="anticon anticon-right"
role="img"
>
<svg
aria-hidden="true"
data-icon="right"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M765.7 486.8L314.9 134.7A7.97 7.97 0 00302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 000-50.4z"
/>
</svg>
</span>
</div>
</li>
</ul>
</div>
</div>
</div>
</div>
<span
aria-hidden="true"
class="ant-select-arrow"
style="user-select:none;-webkit-user-select:none"
unselectable="on"
>
<span
aria-label="down"
class="anticon anticon-down ant-select-suffix"
role="img"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</span>
</div>,
]
`;
exports[`renders ./components/cascader/demo/search.md extend context correctly 1`] = `
<div
class="ant-select ant-cascader ant-select-single ant-select-allow-clear ant-select-show-arrow ant-select-show-search"
@ -2267,6 +2502,276 @@ Array [
]
`;
exports[`renders ./components/cascader/demo/status.md extend context correctly 1`] = `
<div
class="ant-space ant-space-vertical"
>
<div
class="ant-space-item"
style="margin-bottom:8px"
>
<div
class="ant-select ant-cascader ant-select-status-error ant-select-single ant-select-allow-clear ant-select-show-arrow"
>
<div
class="ant-select-selector"
>
<span
class="ant-select-selection-search"
>
<input
aria-autocomplete="list"
aria-controls="undefined_list"
aria-haspopup="listbox"
aria-owns="undefined_list"
autocomplete="off"
class="ant-select-selection-search-input"
readonly=""
role="combobox"
style="opacity:0"
type="search"
unselectable="on"
value=""
/>
</span>
<span
class="ant-select-selection-placeholder"
>
Error
</span>
</div>
<div>
<div
class="ant-select-dropdown ant-cascader-dropdown ant-select-dropdown-empty"
style="opacity:0;pointer-events:none"
>
<div>
<div
class="ant-cascader-menus ant-cascader-menu-empty"
>
<ul
class="ant-cascader-menu"
role="menu"
>
<li
aria-checked="false"
class="ant-cascader-menu-item ant-cascader-menu-item-disabled"
data-path-key="__EMPTY__"
role="menuitemcheckbox"
>
<div
class="ant-cascader-menu-item-content"
>
<div
class="ant-empty ant-empty-normal ant-empty-small"
>
<div
class="ant-empty-image"
>
<svg
class="ant-empty-img-simple"
height="41"
viewBox="0 0 64 41"
width="64"
xmlns="http://www.w3.org/2000/svg"
>
<g
fill="none"
fill-rule="evenodd"
transform="translate(0 1)"
>
<ellipse
class="ant-empty-img-simple-ellipse"
cx="32"
cy="33"
rx="32"
ry="7"
/>
<g
class="ant-empty-img-simple-g"
fill-rule="nonzero"
>
<path
d="M55 12.76L44.854 1.258C44.367.474 43.656 0 42.907 0H21.093c-.749 0-1.46.474-1.947 1.257L9 12.761V22h46v-9.24z"
/>
<path
class="ant-empty-img-simple-path"
d="M41.613 15.931c0-1.605.994-2.93 2.227-2.931H55v18.137C55 33.26 53.68 35 52.05 35h-40.1C10.32 35 9 33.259 9 31.137V13h11.16c1.233 0 2.227 1.323 2.227 2.928v.022c0 1.605 1.005 2.901 2.237 2.901h14.752c1.232 0 2.237-1.308 2.237-2.913v-.007z"
/>
</g>
</g>
</svg>
</div>
<div
class="ant-empty-description"
>
No Data
</div>
</div>
</div>
</li>
</ul>
</div>
</div>
</div>
</div>
<span
aria-hidden="true"
class="ant-select-arrow"
style="user-select:none;-webkit-user-select:none"
unselectable="on"
>
<span
aria-label="down"
class="anticon anticon-down ant-select-suffix"
role="img"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</span>
</div>
</div>
<div
class="ant-space-item"
>
<div
class="ant-select ant-cascader ant-select-status-warning ant-select-multiple ant-select-allow-clear"
>
<div
class="ant-select-selector"
>
<div
class="ant-select-selection-overflow"
>
<div
class="ant-select-selection-overflow-item ant-select-selection-overflow-item-suffix"
style="opacity:1"
>
<div
class="ant-select-selection-search"
style="width:0"
>
<input
aria-autocomplete="list"
aria-controls="undefined_list"
aria-haspopup="listbox"
aria-owns="undefined_list"
autocomplete="off"
class="ant-select-selection-search-input"
readonly=""
role="combobox"
style="opacity:0"
type="search"
unselectable="on"
value=""
/>
<span
aria-hidden="true"
class="ant-select-selection-search-mirror"
>
 
</span>
</div>
</div>
</div>
<span
class="ant-select-selection-placeholder"
>
Warning multiple
</span>
</div>
<div>
<div
class="ant-select-dropdown ant-cascader-dropdown ant-select-dropdown-empty"
style="opacity:0;pointer-events:none"
>
<div>
<div
class="ant-cascader-menus ant-cascader-menu-empty"
>
<ul
class="ant-cascader-menu"
role="menu"
>
<li
aria-checked="false"
class="ant-cascader-menu-item ant-cascader-menu-item-disabled"
data-path-key="__EMPTY__"
role="menuitemcheckbox"
>
<div
class="ant-cascader-menu-item-content"
>
<div
class="ant-empty ant-empty-normal ant-empty-small"
>
<div
class="ant-empty-image"
>
<svg
class="ant-empty-img-simple"
height="41"
viewBox="0 0 64 41"
width="64"
xmlns="http://www.w3.org/2000/svg"
>
<g
fill="none"
fill-rule="evenodd"
transform="translate(0 1)"
>
<ellipse
class="ant-empty-img-simple-ellipse"
cx="32"
cy="33"
rx="32"
ry="7"
/>
<g
class="ant-empty-img-simple-g"
fill-rule="nonzero"
>
<path
d="M55 12.76L44.854 1.258C44.367.474 43.656 0 42.907 0H21.093c-.749 0-1.46.474-1.947 1.257L9 12.761V22h46v-9.24z"
/>
<path
class="ant-empty-img-simple-path"
d="M41.613 15.931c0-1.605.994-2.93 2.227-2.931H55v18.137C55 33.26 53.68 35 52.05 35h-40.1C10.32 35 9 33.259 9 31.137V13h11.16c1.233 0 2.227 1.323 2.227 2.928v.022c0 1.605 1.005 2.901 2.237 2.901h14.752c1.232 0 2.237-1.308 2.237-2.913v-.007z"
/>
</g>
</g>
</svg>
</div>
<div
class="ant-empty-description"
>
No Data
</div>
</div>
</div>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
`;
exports[`renders ./components/cascader/demo/suffix.md extend context correctly 1`] = `
Array [
<div

View File

@ -658,6 +658,151 @@ exports[`renders ./components/cascader/demo/multiple.md correctly 1`] = `
</div>
`;
exports[`renders ./components/cascader/demo/placement.md correctly 1`] = `
Array [
<div
class="ant-radio-group ant-radio-group-outline"
>
<label
class="ant-radio-button-wrapper ant-radio-button-wrapper-checked"
>
<span
class="ant-radio-button ant-radio-button-checked"
>
<input
checked=""
class="ant-radio-button-input"
type="radio"
value="topLeft"
/>
<span
class="ant-radio-button-inner"
/>
</span>
<span>
topLeft
</span>
</label>
<label
class="ant-radio-button-wrapper"
>
<span
class="ant-radio-button"
>
<input
class="ant-radio-button-input"
type="radio"
value="topRight"
/>
<span
class="ant-radio-button-inner"
/>
</span>
<span>
topRight
</span>
</label>
<label
class="ant-radio-button-wrapper"
>
<span
class="ant-radio-button"
>
<input
class="ant-radio-button-input"
type="radio"
value="bottomLeft"
/>
<span
class="ant-radio-button-inner"
/>
</span>
<span>
bottomLeft
</span>
</label>
<label
class="ant-radio-button-wrapper"
>
<span
class="ant-radio-button"
>
<input
class="ant-radio-button-input"
type="radio"
value="bottomRight"
/>
<span
class="ant-radio-button-inner"
/>
</span>
<span>
bottomRight
</span>
</label>
</div>,
<br />,
<br />,
<div
class="ant-select ant-cascader ant-select-single ant-select-allow-clear ant-select-show-arrow"
>
<div
class="ant-select-selector"
>
<span
class="ant-select-selection-search"
>
<input
aria-autocomplete="list"
aria-controls="undefined_list"
aria-haspopup="listbox"
aria-owns="undefined_list"
autocomplete="off"
class="ant-select-selection-search-input"
readonly=""
role="combobox"
style="opacity:0"
type="search"
unselectable="on"
value=""
/>
</span>
<span
class="ant-select-selection-placeholder"
>
Please select
</span>
</div>
<span
aria-hidden="true"
class="ant-select-arrow"
style="user-select:none;-webkit-user-select:none"
unselectable="on"
>
<span
aria-label="down"
class="anticon anticon-down ant-select-suffix"
role="img"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</span>
</div>,
]
`;
exports[`renders ./components/cascader/demo/search.md correctly 1`] = `
<div
class="ant-select ant-cascader ant-select-single ant-select-allow-clear ant-select-show-arrow ant-select-show-search"
@ -891,6 +1036,126 @@ Array [
]
`;
exports[`renders ./components/cascader/demo/status.md correctly 1`] = `
<div
class="ant-space ant-space-vertical"
>
<div
class="ant-space-item"
style="margin-bottom:8px"
>
<div
class="ant-select ant-cascader ant-select-status-error ant-select-single ant-select-allow-clear ant-select-show-arrow"
>
<div
class="ant-select-selector"
>
<span
class="ant-select-selection-search"
>
<input
aria-autocomplete="list"
aria-controls="undefined_list"
aria-haspopup="listbox"
aria-owns="undefined_list"
autocomplete="off"
class="ant-select-selection-search-input"
readonly=""
role="combobox"
style="opacity:0"
type="search"
unselectable="on"
value=""
/>
</span>
<span
class="ant-select-selection-placeholder"
>
Error
</span>
</div>
<span
aria-hidden="true"
class="ant-select-arrow"
style="user-select:none;-webkit-user-select:none"
unselectable="on"
>
<span
aria-label="down"
class="anticon anticon-down ant-select-suffix"
role="img"
>
<svg
aria-hidden="true"
data-icon="down"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
/>
</svg>
</span>
</span>
</div>
</div>
<div
class="ant-space-item"
>
<div
class="ant-select ant-cascader ant-select-status-warning ant-select-multiple ant-select-allow-clear"
>
<div
class="ant-select-selector"
>
<div
class="ant-select-selection-overflow"
>
<div
class="ant-select-selection-overflow-item ant-select-selection-overflow-item-suffix"
style="opacity:1"
>
<div
class="ant-select-selection-search"
style="width:0"
>
<input
aria-autocomplete="list"
aria-controls="undefined_list"
aria-haspopup="listbox"
aria-owns="undefined_list"
autocomplete="off"
class="ant-select-selection-search-input"
readonly=""
role="combobox"
style="opacity:0"
type="search"
unselectable="on"
value=""
/>
<span
aria-hidden="true"
class="ant-select-selection-search-mirror"
>
 
</span>
</div>
</div>
</div>
<span
class="ant-select-selection-placeholder"
>
Warning multiple
</span>
</div>
</div>
</div>
</div>
`;
exports[`renders ./components/cascader/demo/suffix.md correctly 1`] = `
Array [
<div

View File

@ -368,6 +368,23 @@ describe('Cascader', () => {
expect(wrapper.find('.ant-select-selection-placeholder').text()).toEqual(customPlaceholder);
});
it('placement work correctly', () => {
const customerOptions = [
{
value: 'zhejiang',
label: 'Zhejiang',
children: [
{
value: 'hangzhou',
label: 'Hangzhou',
},
],
},
];
const wrapper = mount(<Cascader options={customerOptions} placement="topRight" />);
expect(wrapper.find('Trigger').prop('popupPlacement')).toEqual('topRight');
});
it('popup correctly with defaultValue RTL', () => {
const wrapper = mount(
<ConfigProvider direction="rtl">
@ -484,10 +501,12 @@ describe('Cascader', () => {
describe('legacy props', () => {
it('popupClassName', () => {
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
const wrapper = mount(<Cascader open popupPlacement="topRight" popupClassName="mock-cls" />);
const wrapper = mount(
<Cascader open popupPlacement="bottomLeft" popupClassName="mock-cls" />,
);
expect(wrapper.exists('.mock-cls')).toBeTruthy();
expect(wrapper.find('Trigger').prop('popupPlacement')).toEqual('topRight');
expect(wrapper.find('Trigger').prop('popupPlacement')).toEqual('bottomLeft');
expect(errorSpy).toHaveBeenCalledWith(
'Warning: [antd: Cascader] `popupClassName` is deprecated. Please use `dropdownClassName` instead.',

View File

@ -74,4 +74,18 @@ describe('Cascader.typescript', () => {
);
expect(wrapper).toBeTruthy();
});
it('single onChange', () => {
const wrapper = mount(
<Cascader multiple={false} onChange={(values: (string | number)[]) => values} />,
);
expect(wrapper).toBeTruthy();
});
it('multiple onChange', () => {
const wrapper = mount(
<Cascader multiple onChange={(values: (string | number)[][]) => values} />,
);
expect(wrapper).toBeTruthy();
});
});

View File

@ -0,0 +1,77 @@
---
order: 13
title:
zh-CN: 弹出位置
en-US: Placement
---
## zh-CN
可以通过 `placement` 手动指定弹出的位置。
## en-US
You can manually specify the position of the popup via `placement`.
```jsx
import { Cascader, Radio } from 'antd';
const options = [
{
value: 'zhejiang',
label: 'Zhejiang',
children: [
{
value: 'hangzhou',
label: 'Hangzhou',
children: [
{
value: 'xihu',
label: 'West Lake',
},
],
},
],
},
{
value: 'jiangsu',
label: 'Jiangsu',
children: [
{
value: 'nanjing',
label: 'Nanjing',
children: [
{
value: 'zhonghuamen',
label: 'Zhong Hua Men',
},
],
},
],
},
];
const SetPlacementDemo = () => {
const [placement, SetPlacement] = React.useState('topLeft');
const placementChange = e => {
SetPlacement(e.target.value);
};
return (
<>
<Radio.Group value={placement} onChange={placementChange}>
<Radio.Button value="topLeft">topLeft</Radio.Button>
<Radio.Button value="topRight">topRight</Radio.Button>
<Radio.Button value="bottomLeft">bottomLeft</Radio.Button>
<Radio.Button value="bottomRight">bottomRight</Radio.Button>
</Radio.Group>
<br />
<br />
<Cascader options={options} placeholder="Please select" placement={placement} />
</>
);
};
ReactDOM.render(<SetPlacementDemo />, mountNode);
```

View File

@ -0,0 +1,28 @@
---
order: 16
version: 4.19.0
title:
zh-CN: 自定义状态
en-US: Status
---
## zh-CN
使用 `status` 为 Cascader 添加状态,可选 `error` 或者 `warning`
## en-US
Add status to Cascader with `status`, which could be `error` or `warning`.
```tsx
import { Cascader, Space } from 'antd';
const Validation: React.FC = () => (
<Space direction="vertical">
<Cascader status="error" placeholder="Error" />
<Cascader status="warning" multiple placeholder="Warning multiple" />
</Space>
);
ReactDOM.render(<Validation />, mountNode);
```

View File

@ -24,11 +24,12 @@ Cascade selection box.
| allowClear | Whether allow clear | boolean | true | |
| autoFocus | If get focus when component mounted | boolean | false | |
| bordered | Whether has border style | boolean | true | |
| clearIcon | The custom clear icon | ReactNode | - | |
| changeOnSelect | (Work on single select) Change value on each selection if set to true, see above demo for details | boolean | false | |
| className | The additional css class | string | - | |
| defaultValue | Initial selected value | string\[] \| number\[] | \[] | |
| disabled | Whether disabled select | boolean | false | |
| displayRender | The render function of displaying single selected options. You can use tagRender for multiple mode | (label, selectedOptions) => ReactNode | label => label.join(`/`) | |
| displayRender | The render function of displaying selected options | (label, selectedOptions) => ReactNode | label => label.join(`/`) | `multiple`: 4.18.0 |
| dropdownClassName | The additional className of popup overlay | string | - | 4.17.0 |
| dropdownRender | Customize dropdown content | (menus: ReactNode) => ReactNode | - | 4.4.0 |
| expandIcon | Customize the current item expand icon | ReactNode | - | 4.4.0 |
@ -42,9 +43,10 @@ Cascade selection box.
| open | Set visible of cascader popup | boolean | - | 4.17.0 |
| options | The data options of cascade | [Option](#Option)\[] | - | |
| placeholder | The input placeholder | string | `Please select` | |
| placement | Use preset popup align config from builtinPlacements`bottomLeft` `bottomRight` `topLeft` `topRight` | string | `bottomLeft` | 4.17.0 |
| placement | Use preset popup align config from builtinPlacements | `bottomLeft` `bottomRight` `topLeft` `topRight` | `bottomLeft` | 4.17.0 |
| showSearch | Whether show search input in single mode | boolean \| [Object](#showSearch) | false | |
| size | The input size | `large` \| `middle` \| `small` | - | |
| status | Set validation status | 'error' \| 'warning' | - | 4.19.0 |
| style | The additional style | CSSProperties | - | |
| suffixIcon | The custom suffix icon | ReactNode | - | |
| tagRender | Customize tag render when `multiple` | (props) => ReactNode | - | 4.17.0 |
@ -52,6 +54,7 @@ Cascade selection box.
| onChange | Callback when finishing cascader select | (value, selectedOptions) => void | - | |
| onDropdownVisibleChange | Callback when popup shown or hidden | (value) => void | - | 4.17.0 |
| multiple | Support multiple or not | boolean | - | 4.17.0 |
| removeIcon | The custom remove icon | ReactNode | - | |
| searchValue | Set search valueNeed work with `showSearch` | string | - | 4.17.0 |
| onSearch | The callback function triggered when input changed | (search: string) => void | - | 4.17.0 |
| dropdownMenuColumnStyle | The style of the drop-down menu column | CSSProperties | - | |
@ -75,6 +78,10 @@ interface Option {
label?: React.ReactNode;
disabled?: boolean;
children?: Option[];
// Determines if this is a leaf node(effective when `loadData` is specified).
// `false` will force trade TreeNode as a parent node.
// Show expand icon even if the current node has no children.
isLeaf?: boolean;
}
```

View File

@ -2,7 +2,8 @@ import * as React from 'react';
import classNames from 'classnames';
import RcCascader from 'rc-cascader';
import type {
CascaderProps as RcCascaderProps,
SingleCascaderProps as RcSingleCascaderProps,
MultipleCascaderProps as RcMultipleCascaderProps,
ShowSearchType,
FieldNames,
BaseOptionType,
@ -12,12 +13,17 @@ import omit from 'rc-util/lib/omit';
import RightOutlined from '@ant-design/icons/RightOutlined';
import LoadingOutlined from '@ant-design/icons/LoadingOutlined';
import LeftOutlined from '@ant-design/icons/LeftOutlined';
import { useContext } from 'react';
import devWarning from '../_util/devWarning';
import { ConfigContext } from '../config-provider';
import type { SizeType } from '../config-provider/SizeContext';
import SizeContext from '../config-provider/SizeContext';
import getIcons from '../select/utils/iconUtil';
import { getTransitionName } from '../_util/motion';
import { getTransitionName, getTransitionDirection, SelectCommonPlacement } from '../_util/motion';
import { FormItemStatusContext } from '../form/context';
import { getMergedStatus, getStatusClassNames, InputStatus } from '../_util/statusUtils';
import useStyle from './style';
import useSelectStyle from '../select/style';
// Align the design since we use `rc-select` in root. This help:
// - List search content will show all content
@ -80,15 +86,24 @@ const defaultSearchRender: ShowSearchType['render'] = (inputValue, path, prefixC
return optionList;
};
export interface CascaderProps<DataNodeType>
extends Omit<RcCascaderProps, 'checkable' | 'options'> {
type SingleCascaderProps = Omit<RcSingleCascaderProps, 'checkable' | 'options'> & {
multiple?: false;
};
type MultipleCascaderProps = Omit<RcMultipleCascaderProps, 'checkable' | 'options'> & {
multiple: true;
};
type UnionCascaderProps = SingleCascaderProps | MultipleCascaderProps;
export type CascaderProps<DataNodeType> = UnionCascaderProps & {
multiple?: boolean;
size?: SizeType;
bordered?: boolean;
placement?: SelectCommonPlacement;
suffixIcon?: React.ReactNode;
options?: DataNodeType[];
}
status?: InputStatus;
};
export interface CascaderRef {
focus: () => void;
@ -107,11 +122,14 @@ const Cascader = React.forwardRef((props: CascaderProps<any>, ref: React.Ref<Cas
popupClassName,
dropdownClassName,
expandIcon,
placement,
showSearch,
allowClear = true,
notFoundContent,
direction,
getPopupContainer,
status: customStatus,
showArrow,
...rest
} = props;
@ -124,11 +142,16 @@ const Cascader = React.forwardRef((props: CascaderProps<any>, ref: React.Ref<Cas
direction: rootDirection,
// virtual,
// dropdownMatchSelectWidth,
} = React.useContext(ConfigContext);
iconPrefixCls,
} = useContext(ConfigContext);
const mergedDirection = direction || rootDirection;
const isRtl = mergedDirection === 'rtl';
// =================== Status =====================
const { status: contextStatus, hasFeedback } = useContext(FormItemStatusContext);
const mergedStatus = getMergedStatus(contextStatus, customStatus);
// =================== Warning =====================
if (process.env.NODE_ENV !== 'production') {
devWarning(
@ -152,6 +175,9 @@ const Cascader = React.forwardRef((props: CascaderProps<any>, ref: React.Ref<Cas
const prefixCls = getPrefixCls('select', customizePrefixCls);
const cascaderPrefixCls = getPrefixCls('cascader', customizePrefixCls);
const [wrapSelectSSR, hashId] = useSelectStyle(rootPrefixCls, prefixCls, iconPrefixCls);
const [wrapCascaderSSR] = useStyle(cascaderPrefixCls);
// =================== Dropdown ====================
const mergedDropdownClassName = classNames(
dropdownClassName || popupClassName,
@ -159,6 +185,7 @@ const Cascader = React.forwardRef((props: CascaderProps<any>, ref: React.Ref<Cas
{
[`${cascaderPrefixCls}-dropdown-rtl`]: mergedDirection === 'rtl',
},
hashId,
);
// ==================== Search =====================
@ -204,14 +231,28 @@ const Cascader = React.forwardRef((props: CascaderProps<any>, ref: React.Ref<Cas
);
// ===================== Icons =====================
const mergedShowArrow = showArrow !== undefined ? showArrow : props.loading || !multiple;
const { suffixIcon, removeIcon, clearIcon } = getIcons({
...props,
status: mergedStatus,
hasFeedback,
showArrow: mergedShowArrow,
multiple,
prefixCls,
});
// ===================== Placement =====================
const getPlacement = () => {
if (placement !== undefined) {
return placement;
}
return direction === 'rtl'
? ('bottomRight' as SelectCommonPlacement)
: ('bottomLeft' as SelectCommonPlacement);
};
// ==================== Render =====================
return (
const renderNode = (
<RcCascader
prefixCls={prefixCls}
className={classNames(
@ -222,10 +263,13 @@ const Cascader = React.forwardRef((props: CascaderProps<any>, ref: React.Ref<Cas
[`${prefixCls}-rtl`]: isRtl,
[`${prefixCls}-borderless`]: !bordered,
},
getStatusClassNames(prefixCls, mergedStatus, hasFeedback),
className,
hashId,
)}
{...(restProps as any)}
direction={mergedDirection}
placement={getPlacement()}
notFoundContent={mergedNotFoundContent}
allowClear={allowClear}
showSearch={mergedShowSearch}
@ -238,11 +282,18 @@ const Cascader = React.forwardRef((props: CascaderProps<any>, ref: React.Ref<Cas
dropdownClassName={mergedDropdownClassName}
dropdownPrefixCls={customizePrefixCls || cascaderPrefixCls}
choiceTransitionName={getTransitionName(rootPrefixCls, '', choiceTransitionName)}
transitionName={getTransitionName(rootPrefixCls, 'slide-up', transitionName)}
transitionName={getTransitionName(
rootPrefixCls,
getTransitionDirection(placement),
transitionName,
)}
getPopupContainer={getPopupContainer || getContextPopupContainer}
ref={ref}
showArrow={hasFeedback || showArrow}
/>
);
return wrapCascaderSSR(wrapSelectSSR(renderNode));
}) as (<OptionType extends BaseOptionType | DefaultOptionType = DefaultOptionType>(
props: React.PropsWithChildren<CascaderProps<OptionType>> & { ref?: React.Ref<CascaderRef> },
) => React.ReactElement) & {

View File

@ -25,11 +25,12 @@ cover: https://gw.alipayobjects.com/zos/alicdn/UdS8y8xyZ/Cascader.svg
| allowClear | 是否支持清除 | boolean | true | |
| autoFocus | 自动获取焦点 | boolean | false | |
| bordered | 是否有边框 | boolean | true | |
| clearIcon | 自定义的选择框清空图标 | ReactNode | - | |
| changeOnSelect | (单选时生效)当此项为 true 时,点选每级菜单选项值都会发生变化,具体见上面的演示 | boolean | false | |
| className | 自定义类名 | string | - | |
| defaultValue | 默认的选中项 | string\[] \| number\[] | \[] | |
| disabled | 禁用 | boolean | false | |
| displayRender | 单选模式下选择后展示的渲染函数,多选请使用 tagRender | (label, selectedOptions) => ReactNode | label => label.join(`/`) | |
| displayRender | 选择后展示的渲染函数 | (label, selectedOptions) => ReactNode | label => label.join(`/`) | `multiple`: 4.18.0 |
| dropdownClassName | 自定义浮层类名 | string | - | 4.17.0 |
| dropdownRender | 自定义下拉框内容 | (menus: ReactNode) => ReactNode | - | 4.4.0 |
| expandIcon | 自定义次级菜单展开图标 | ReactNode | - | 4.4.0 |
@ -43,9 +44,10 @@ cover: https://gw.alipayobjects.com/zos/alicdn/UdS8y8xyZ/Cascader.svg
| open | 控制浮层显隐 | boolean | - | 4.17.0 |
| options | 可选项数据源 | [Option](#Option)\[] | - | |
| placeholder | 输入框占位文本 | string | `请选择` | |
| placement | 浮层预设位置`bottomLeft` `bottomRight` `topLeft` `topRight` | string | `bottomLeft` | 4.17.0 |
| placement | 浮层预设位置 | `bottomLeft` `bottomRight` `topLeft` `topRight` | `bottomLeft` | 4.17.0 |
| showSearch | 在选择框中显示搜索框 | boolean \| [Object](#showSearch) | false | |
| size | 输入框大小 | `large` \| `middle` \| `small` | - | |
| status | 设置校验状态 | 'error' \| 'warning' | - | 4.19.0 |
| style | 自定义样式 | CSSProperties | - | |
| suffixIcon | 自定义的选择框后缀图标 | ReactNode | - | |
| tagRender | 自定义 tag 内容,多选时生效 | (props) => ReactNode | - | 4.17.0 |
@ -53,6 +55,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/UdS8y8xyZ/Cascader.svg
| onChange | 选择完成后的回调 | (value, selectedOptions) => void | - | |
| onDropdownVisibleChange | 显示/隐藏浮层的回调 | (value) => void | - | 4.17.0 |
| multiple | 支持多选节点 | boolean | - | 4.17.0 |
| removeIcon | 自定义的多选框清除图标 | ReactNode | - | |
| searchValue | 设置搜索的值,需要与 `showSearch` 配合使用 | string | - | 4.17.0 |
| onSearch | 监听搜索,返回输入的值 | (search: string) => void | - | 4.17.0 |
| dropdownMenuColumnStyle | 下拉菜单列的样式 | CSSProperties | - | |
@ -77,6 +80,9 @@ interface Option {
label?: React.ReactNode;
disabled?: boolean;
children?: Option[];
// 标记是否为叶子节点,设置了 `loadData` 时有效
// 设为 `false` 时会强制标记为父节点,即使当前节点没有 children也会显示展开图标
isLeaf?: boolean;
}
```

View File

@ -1,104 +1,104 @@
@import '../../style/themes/index';
@import '../../style/mixins/index';
@import '../../input/style/mixin';
@import '../../checkbox/style/mixin';
// @import '../../style/themes/index';
// @import '../../style/mixins/index';
// @import '../../input/style/mixin';
// @import '../../checkbox/style/mixin';
@cascader-prefix-cls: ~'@{ant-prefix}-cascader';
// @cascader-prefix-cls: ~'@{ant-prefix}-cascader';
.antCheckboxFn(@checkbox-prefix-cls: ~'@{cascader-prefix-cls}-checkbox');
// .antCheckboxFn(@checkbox-prefix-cls: ~'@{cascader-prefix-cls}-checkbox');
.@{cascader-prefix-cls} {
width: 184px;
// .@{cascader-prefix-cls} {
// width: 184px;
&-checkbox {
top: 0;
margin-right: @padding-xs;
}
// &-checkbox {
// top: 0;
// margin-right: @padding-xs;
// }
&-menus {
display: flex;
flex-wrap: nowrap;
align-items: flex-start;
// &-menus {
// display: flex;
// flex-wrap: nowrap;
// align-items: flex-start;
&.@{cascader-prefix-cls}-menu-empty {
.@{cascader-prefix-cls}-menu {
width: 100%;
height: auto;
}
}
}
// &.@{cascader-prefix-cls}-menu-empty {
// .@{cascader-prefix-cls}-menu {
// width: 100%;
// height: auto;
// }
// }
// }
&-menu {
min-width: 111px;
height: 180px;
margin: 0;
margin: -@dropdown-edge-child-vertical-padding 0;
padding: @cascader-dropdown-edge-child-vertical-padding 0;
overflow: auto;
vertical-align: top;
list-style: none;
border-right: @border-width-base @border-style-base @cascader-menu-border-color-split;
-ms-overflow-style: -ms-autohiding-scrollbar; // https://github.com/ant-design/ant-design/issues/11857
// &-menu {
// min-width: 111px;
// height: 180px;
// margin: 0;
// margin: -@dropdown-edge-child-vertical-padding 0;
// padding: @cascader-dropdown-edge-child-vertical-padding 0;
// overflow: auto;
// vertical-align: top;
// list-style: none;
// border-right: @border-width-base @border-style-base @cascader-menu-border-color-split;
// -ms-overflow-style: -ms-autohiding-scrollbar; // https://github.com/ant-design/ant-design/issues/11857
&-item {
display: flex;
flex-wrap: nowrap;
align-items: center;
padding: @cascader-dropdown-vertical-padding @control-padding-horizontal;
overflow: hidden;
line-height: @cascader-dropdown-line-height;
white-space: nowrap;
text-overflow: ellipsis;
cursor: pointer;
transition: all 0.3s;
// &-item {
// display: flex;
// flex-wrap: nowrap;
// align-items: center;
// padding: @cascader-dropdown-vertical-padding @control-padding-horizontal;
// overflow: hidden;
// line-height: @cascader-dropdown-line-height;
// white-space: nowrap;
// text-overflow: ellipsis;
// cursor: pointer;
// transition: all 0.3s;
&:hover {
background: @item-hover-bg;
}
// &:hover {
// background: @item-hover-bg;
// }
&-disabled {
color: @disabled-color;
cursor: not-allowed;
// &-disabled {
// color: @disabled-color;
// cursor: not-allowed;
&:hover {
background: transparent;
}
}
// &:hover {
// background: transparent;
// }
// }
.@{cascader-prefix-cls}-menu-empty & {
color: @disabled-color;
cursor: default;
pointer-events: none;
}
// .@{cascader-prefix-cls}-menu-empty & {
// color: @disabled-color;
// cursor: default;
// pointer-events: none;
// }
&-active:not(&-disabled) {
&,
&:hover {
font-weight: @select-item-selected-font-weight;
background-color: @cascader-item-selected-bg;
}
}
// &-active:not(&-disabled) {
// &,
// &:hover {
// font-weight: @select-item-selected-font-weight;
// background-color: @cascader-item-selected-bg;
// }
// }
&-content {
flex: auto;
}
// &-content {
// flex: auto;
// }
&-expand &-expand-icon,
&-loading-icon {
margin-left: @padding-xss;
color: @text-color-secondary;
font-size: 10px;
// &-expand &-expand-icon,
// &-loading-icon {
// margin-left: @padding-xss;
// color: @text-color-secondary;
// font-size: 10px;
.@{cascader-prefix-cls}-menu-item-disabled& {
color: @disabled-color;
}
}
// .@{cascader-prefix-cls}-menu-item-disabled& {
// color: @disabled-color;
// }
// }
&-keyword {
color: @highlight-color;
}
}
}
}
// &-keyword {
// color: @highlight-color;
// }
// }
// }
// }
@import './rtl';
// @import './rtl';

View File

@ -1,6 +1,177 @@
import '../../style/index.less';
import './index.less';
// import '../../style/index.less';
// import './index.less';
// style dependencies
import '../../empty/style';
import '../../select/style';
// // style dependencies
// import '../../empty/style';
// import '../../select/style';
// // deps-lint-skip: form
// deps-lint-skip-all
import {
DerivativeToken,
useStyleRegister,
useToken,
UseComponentStyleResult,
GenerateStyle,
} from '../../_util/theme';
import { getStyle as getCheckboxStyle } from '../../checkbox/style';
interface CascaderToken extends DerivativeToken {
prefixCls: string;
cascaderCls: string;
}
// =============================== Base ===============================
const genBaseStyle: GenerateStyle<CascaderToken> = (token, hashId) => {
const { prefixCls, cascaderCls } = token;
const cascaderMenuItemCls = `${cascaderCls}-menu-item`;
const iconCls = `
${cascaderMenuItemCls}-expand ${cascaderMenuItemCls}-expand-icon,
${cascaderMenuItemCls}-loading-icon
`;
const itemPaddingVertical = Math.round(
(token.controlHeight - token.fontSize * token.lineHeight) / 2,
);
return [
// =====================================================
// == Control ==
// =====================================================
{
[cascaderCls]: {
width: 184, // FIXME: hardcode in v4
},
},
// =====================================================
// == Popup ==
// =====================================================
{
[`${cascaderCls}-dropdown`]: [
// ==================== Checkbox ====================
getCheckboxStyle(`${prefixCls}-checkbox`, token, hashId!),
{
[cascaderCls]: {
// ================== Checkbox ==================
'&-checkbox': {
top: 0,
marginInlineEnd: token.paddingXS,
},
// ==================== Menu ====================
// >>> Menus
'&-menus': {
display: 'flex',
flexWrap: 'nowrap',
alignItems: 'flex-start',
[`&${cascaderCls}-menu-empty`]: {
[`${cascaderCls}-menu`]: {
width: '100%',
height: 'auto',
[cascaderMenuItemCls]: {
color: token.textColorDisabled,
cursor: 'default',
pointerEvents: 'none',
},
},
},
},
// >>> Menu
'&-menu': {
minWidth: 111, // FIXME: hardcode in v4
height: 180, // FIXME: hardcode in v4
margin: `-${token.paddingXS}px 0`,
padding: `${token.paddingXS}px 0`,
overflow: 'auto',
verticalAlign: 'top',
listStyle: 'none',
borderInlineEnd: `${token.borderWidth}px ${token.borderStyle} ${token.borderColorSplit}`,
'-ms-overflow-style': '-ms-autohiding-scrollbar', // https://github.com/ant-design/ant-design/issues/11857
'&-item': {
display: 'flex',
flexWrap: 'nowrap',
alignItems: 'center',
padding: `${itemPaddingVertical}px ${token.paddingSM}px`,
overflow: 'hidden',
lineHeight: token.lineHeight,
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
cursor: 'pointer',
transition: `all ${token.duration}`,
'&:hover': {
background: token.itemHoverBackground,
},
' &-disabled': {
color: token.textColorDisabled,
cursor: 'not-allowed',
'&:hover': {
background: 'transparent',
},
[iconCls]: {
color: token.textColorDisabled,
},
},
[`&-active:not(${cascaderMenuItemCls}-disabled)`]: {
[`&, &:hover`]: {
fontWeight: 600, // FIXME: hardcode
backgroundColor: token.itemActiveBackground,
},
},
'&-content': {
flex: 'auto',
},
[iconCls]: {
marginInlineStart: token.paddingXXS,
color: token.textColorSecondary,
fontSize: 10, // FIXME: hardcode in v4
},
'&-keyword': {
color: token.highlightColor,
},
},
},
},
},
],
},
// =====================================================
// == RTL ==
// =====================================================
{
[`${cascaderCls}-dropdown-rtl`]: {
direction: 'rtl',
},
},
];
};
// ============================== Export ==============================
export default function useStyle(prefixCls: string): UseComponentStyleResult {
const [theme, token, hashId] = useToken();
const cascaderToken: CascaderToken = {
...token,
prefixCls,
cascaderCls: `.${prefixCls}`,
};
return [
useStyleRegister({ theme, token, hashId, path: [prefixCls] }, () => [
genBaseStyle(cascaderToken, hashId),
]),
hashId,
];
}

View File

@ -4,6 +4,7 @@ import RcCheckbox from 'rc-checkbox';
import { GroupContext } from './Group';
import { ConfigContext } from '../config-provider';
import devWarning from '../_util/devWarning';
import useStyle from './style';
export interface AbstractCheckboxProps<T> {
prefixCls?: string;
@ -84,6 +85,8 @@ const InternalCheckbox: React.ForwardRefRenderFunction<HTMLInputElement, Checkbo
}, [restProps.value]);
const prefixCls = getPrefixCls('checkbox', customizePrefixCls);
const [wrapSSR, hashId] = useStyle(prefixCls);
const checkboxProps: CheckboxProps = { ...restProps };
if (checkboxGroup && !skipGroup) {
checkboxProps.onChange = (...args) => {
@ -106,11 +109,15 @@ const InternalCheckbox: React.ForwardRefRenderFunction<HTMLInputElement, Checkbo
[`${prefixCls}-wrapper-disabled`]: checkboxProps.disabled,
},
className,
hashId,
);
const checkboxClass = classNames({
[`${prefixCls}-indeterminate`]: indeterminate,
});
return (
const checkboxClass = classNames(
{
[`${prefixCls}-indeterminate`]: indeterminate,
},
hashId,
);
return wrapSSR(
// eslint-disable-next-line jsx-a11y/label-has-associated-control
<label
className={classString}
@ -120,7 +127,7 @@ const InternalCheckbox: React.ForwardRefRenderFunction<HTMLInputElement, Checkbo
>
<RcCheckbox {...checkboxProps} prefixCls={prefixCls} className={checkboxClass} ref={ref} />
{children !== undefined && <span>{children}</span>}
</label>
</label>,
);
};

View File

@ -3,6 +3,7 @@ import classNames from 'classnames';
import omit from 'rc-util/lib/omit';
import Checkbox, { CheckboxChangeEvent } from './Checkbox';
import { ConfigContext } from '../config-provider';
import useStyle from './style';
export type CheckboxValueType = string | number | boolean;
@ -112,6 +113,8 @@ const InternalCheckboxGroup: React.ForwardRefRenderFunction<HTMLDivElement, Chec
const prefixCls = getPrefixCls('checkbox', customizePrefixCls);
const groupPrefixCls = `${prefixCls}-group`;
const [wrapSSR, hashId] = useStyle(prefixCls);
const domProps = omit(restProps, ['value', 'disabled']);
if (options && options.length > 0) {
@ -147,11 +150,12 @@ const InternalCheckboxGroup: React.ForwardRefRenderFunction<HTMLDivElement, Chec
[`${groupPrefixCls}-rtl`]: direction === 'rtl',
},
className,
hashId,
);
return (
return wrapSSR(
<div className={classString} style={style} {...domProps} ref={ref}>
<GroupContext.Provider value={context}>{children}</GroupContext.Provider>
</div>
</div>,
);
};

View File

@ -1,6 +1,6 @@
@import '../../style/themes/index';
@import './mixin';
// @import '../../style/themes/index';
// @import './mixin';
.antCheckboxFn();
// .antCheckboxFn();
@import './rtl';
// @import './rtl';

View File

@ -1,2 +1,256 @@
import '../../style/index.less';
import './index.less';
// deps-lint-skip-all
import { Keyframes } from '@ant-design/cssinjs';
import {
DerivativeToken,
useStyleRegister,
useToken,
resetComponent,
UseComponentStyleResult,
GenerateStyle,
} from '../../_util/theme';
interface CheckboxToken extends DerivativeToken {
checkboxCls: string;
}
// ============================== Motion ==============================
const antCheckboxEffect = new Keyframes('antCheckboxEffect', {
'0%': {
transform: 'scale(1)',
opacity: 0.5,
},
'100%': {
transform: 'scale(1.6)',
opacity: 0,
},
});
// ============================== Styles ==============================
export const genCheckboxStyle: GenerateStyle<CheckboxToken> = (token, hashId) => {
const { checkboxCls } = token;
const wrapperCls = `${checkboxCls}-wrapper`;
return [
// ===================== Basic =====================
{
// Group
[`${checkboxCls}-group`]: {
...resetComponent(token),
display: 'inline-flex',
},
// Wrapper
[wrapperCls]: {
...resetComponent(token),
display: 'inline-flex',
alignItems: 'baseline',
lineHeight: 'unset',
cursor: 'pointer',
// Fix checkbox & radio in flex align #30260
'&:after': {
display: 'inline-block',
width: 0,
overflow: 'hidden',
content: "'\\a0'",
},
// Checkbox near checkbox
'& + &': {
marginInlineStart: token.marginXS,
},
},
// Wrapper > Checkbox
[checkboxCls]: {
...resetComponent(token),
top: '0.2em',
position: 'relative',
whiteSpace: 'nowrap',
lineHeight: 1,
cursor: 'pointer',
// Wrapper > Checkbox > input
[`${checkboxCls}-input`]: {
position: 'absolute',
inset: 0,
zIndex: 1,
width: '100%',
height: '100%',
cursor: 'pointer',
opacity: 0,
},
// Wrapper > Checkbox > inner
[`${checkboxCls}-inner`]: {
position: 'relative',
top: 0,
insetInlineStart: 0,
display: 'block',
width: token.fontSizeLG,
height: token.fontSizeLG,
direction: 'ltr',
backgroundColor: token.componentBackground,
border: `${token.borderWidth}px ${token.borderStyle} ${token.borderColor}`,
borderRadius: token.borderRadius,
borderCollapse: 'separate',
transition: `all ${token.duration}`,
'&:after': {
position: 'absolute',
top: '50%',
insetInlineStart: '21.5%',
display: 'table',
width: (token.fontSizeLG / 14) * 5,
height: (token.fontSizeLG / 14) * 8,
border: `2px solid ${token.componentBackground}`,
borderTop: 0,
borderInlineStart: 0,
transform: 'rotate(45deg) scale(0) translate(-50%,-50%)',
opacity: 0,
transition: `all ${token.durationFast} cubic-bezier(.71,-.46,.88,.6), opacity ${token.durationFast}`,
content: '""',
},
},
// Wrapper > Checkbox + Text
'& + span': {
paddingInlineStart: token.paddingXS,
paddingInlineEnd: token.paddingXS,
},
},
},
// ================= Indeterminate =================
{
[checkboxCls]: {
'&-indeterminate': {
// Wrapper > Checkbox > inner
[`${checkboxCls}-inner`]: {
'&:after': {
top: '50%',
insetInlineStart: '50%',
width: token.fontSizeLG / 2,
height: token.fontSizeLG / 2,
backgroundColor: token.primaryColor,
border: 0,
transform: 'translate(-50%, -50%) scale(1)',
opacity: 1,
content: '""',
},
},
},
},
},
// ===================== Hover =====================
{
// Wrapper
[`${wrapperCls}:hover ${checkboxCls}:after`]: {
visibility: 'visible',
},
// Wrapper & Wrapper > Checkbox
[`
${wrapperCls}:hover:not(${wrapperCls}-disabled),
${checkboxCls}:hover:not(${checkboxCls}-disabled),
${checkboxCls}-input:focus +
`]: {
[`${checkboxCls}-inner`]: {
borderColor: token.primaryColor,
},
},
},
// ==================== Checked ====================
{
// Wrapper > Checkbox
[`${checkboxCls}-checked`]: {
[`${checkboxCls}-inner`]: {
backgroundColor: token.primaryColor,
borderColor: token.primaryColor,
'&:after': {
opacity: 1,
transform: 'rotate(45deg) scale(1) translate(-50%,-50%)',
transition: `all ${token.duration} ${token.easeOutBack} 0.1s`,
},
},
// Checked Effect
'&:after': {
position: 'absolute',
top: 0,
insetInlineStart: 0,
width: '100%',
height: '100%',
border: `${token.borderWidth}px ${token.borderStyle} ${token.primaryColor}`,
borderRadius: token.borderRadius,
visibility: 'hidden',
animation: `${antCheckboxEffect.getName(hashId)} ${token.duration} ease-in-out`,
animationFillMode: 'backwards',
content: '""',
},
},
},
// ==================== Disable ====================
{
// Wrapper
[`${wrapperCls}-disabled`]: {
cursor: 'not-allowed',
},
// Wrapper > Checkbox
[`${checkboxCls}-disabled`]: {
// Wrapper > Checkbox > input
[`&, ${checkboxCls}-input`]: {
cursor: 'not-allowed',
},
// Wrapper > Checkbox > inner
[`${checkboxCls}-inner`]: {
background: token.background,
borderColor: token.borderColor,
'&:after': {
borderColor: token.borderColor,
},
},
'&:after': {
display: 'none',
},
'& + span': {
color: token.textColorDisabled,
},
},
},
];
};
// ============================== Export ==============================
export function getStyle(prefixCls: string, token: DerivativeToken, hashId: string) {
const checkboxToken: CheckboxToken = {
...token,
checkboxCls: `.${prefixCls}`,
};
return [genCheckboxStyle(checkboxToken, hashId), antCheckboxEffect];
}
export default function useStyle(prefixCls: string): UseComponentStyleResult {
const [theme, token, hashId] = useToken();
return [
useStyleRegister({ theme, token, hashId, path: [prefixCls] }, () =>
getStyle(prefixCls, token, hashId),
),
hashId,
];
}

View File

@ -120,6 +120,11 @@
border-radius: 0;
}
// hide the last border-bottom in borderless mode
&-borderless > &-item:last-child {
border-bottom: 0;
}
&-borderless > &-item > &-content {
background-color: transparent;
border-top: 0;

View File

@ -13367,7 +13367,7 @@ exports[`ConfigProvider components Form configProvider 1`] = `
class="config-form-item-control-input-content"
>
<input
class="config-input"
class="config-input config-input-status-error"
type="text"
value=""
/>
@ -13405,7 +13405,7 @@ exports[`ConfigProvider components Form configProvider componentSize large 1`] =
class="config-form-item-control-input-content"
>
<input
class="config-input config-input-lg"
class="config-input config-input-lg config-input-status-error"
type="text"
value=""
/>
@ -13443,7 +13443,7 @@ exports[`ConfigProvider components Form configProvider componentSize middle 1`]
class="config-form-item-control-input-content"
>
<input
class="config-input"
class="config-input config-input-status-error"
type="text"
value=""
/>
@ -13481,7 +13481,7 @@ exports[`ConfigProvider components Form configProvider virtual and dropdownMatch
class="ant-form-item-control-input-content"
>
<input
class="ant-input"
class="ant-input ant-input-status-error"
type="text"
value=""
/>
@ -13519,7 +13519,7 @@ exports[`ConfigProvider components Form normal 1`] = `
class="ant-form-item-control-input-content"
>
<input
class="ant-input"
class="ant-input ant-input-status-error"
type="text"
value=""
/>
@ -13557,7 +13557,7 @@ exports[`ConfigProvider components Form prefixCls 1`] = `
class="prefix-Form-item-control-input-content"
>
<input
class="prefix-Form"
class="prefix-Form prefix-Form-status-error"
type="text"
value=""
/>
@ -13744,7 +13744,7 @@ exports[`ConfigProvider components Input configProvider componentSize large 1`]
value=""
/>
<span
class="config-input-group-wrapper config-input-group-wrapper-lg config-input-search config-input-search-large"
class="config-input-group-wrapper config-input-search config-input-search-large config-input-group-wrapper-lg"
>
<span
class="config-input-wrapper config-input-group"
@ -13786,7 +13786,7 @@ exports[`ConfigProvider components Input configProvider componentSize large 1`]
</span>
</span>
<span
class="config-input-affix-wrapper config-input-affix-wrapper-lg config-input-password"
class="config-input-affix-wrapper config-input-password config-input-affix-wrapper-lg"
>
<input
action="click"
@ -21043,6 +21043,7 @@ exports[`ConfigProvider components Select configProvider 1`] = `
>
<div
class="config-select-item config-select-item-group"
title="grp"
>
grp
</div>
@ -21170,6 +21171,7 @@ exports[`ConfigProvider components Select configProvider componentSize large 1`]
>
<div
class="config-select-item config-select-item-group"
title="grp"
>
grp
</div>
@ -21297,6 +21299,7 @@ exports[`ConfigProvider components Select configProvider componentSize middle 1`
>
<div
class="config-select-item config-select-item-group"
title="grp"
>
grp
</div>
@ -21424,6 +21427,7 @@ exports[`ConfigProvider components Select configProvider virtual and dropdownMat
>
<div
class="ant-select-item ant-select-item-group"
title="grp"
>
grp
</div>
@ -21551,6 +21555,7 @@ exports[`ConfigProvider components Select normal 1`] = `
>
<div
class="ant-select-item ant-select-item-group"
title="grp"
>
grp
</div>
@ -21678,6 +21683,7 @@ exports[`ConfigProvider components Select prefixCls 1`] = `
>
<div
class="prefix-Select-item prefix-Select-item-group"
title="grp"
>
grp
</div>
@ -21904,14 +21910,14 @@ exports[`ConfigProvider components Skeleton prefixCls 1`] = `
exports[`ConfigProvider components Slider configProvider 1`] = `
<div
class="config-slider"
class="config-slider config-slider-horizontal"
>
<div
class="config-slider-rail"
/>
<div
class="config-slider-track"
style="left:0%;right:auto;width:0%"
style="left:0%;width:0%"
/>
<div
class="config-slider-step"
@ -21923,7 +21929,7 @@ exports[`ConfigProvider components Slider configProvider 1`] = `
aria-valuenow="0"
class="config-slider-handle config-tooltip-open"
role="slider"
style="left:0%;right:auto;transform:translateX(-50%)"
style="left:0%;transform:translateX(-50%)"
tabindex="0"
/>
<div>
@ -21950,22 +21956,19 @@ exports[`ConfigProvider components Slider configProvider 1`] = `
</div>
</div>
</div>
<div
class="config-slider-mark"
/>
</div>
`;
exports[`ConfigProvider components Slider configProvider componentSize large 1`] = `
<div
class="config-slider"
class="config-slider config-slider-horizontal"
>
<div
class="config-slider-rail"
/>
<div
class="config-slider-track"
style="left:0%;right:auto;width:0%"
style="left:0%;width:0%"
/>
<div
class="config-slider-step"
@ -21977,7 +21980,7 @@ exports[`ConfigProvider components Slider configProvider componentSize large 1`]
aria-valuenow="0"
class="config-slider-handle config-tooltip-open"
role="slider"
style="left:0%;right:auto;transform:translateX(-50%)"
style="left:0%;transform:translateX(-50%)"
tabindex="0"
/>
<div>
@ -22004,22 +22007,19 @@ exports[`ConfigProvider components Slider configProvider componentSize large 1`]
</div>
</div>
</div>
<div
class="config-slider-mark"
/>
</div>
`;
exports[`ConfigProvider components Slider configProvider componentSize middle 1`] = `
<div
class="config-slider"
class="config-slider config-slider-horizontal"
>
<div
class="config-slider-rail"
/>
<div
class="config-slider-track"
style="left:0%;right:auto;width:0%"
style="left:0%;width:0%"
/>
<div
class="config-slider-step"
@ -22031,7 +22031,7 @@ exports[`ConfigProvider components Slider configProvider componentSize middle 1`
aria-valuenow="0"
class="config-slider-handle config-tooltip-open"
role="slider"
style="left:0%;right:auto;transform:translateX(-50%)"
style="left:0%;transform:translateX(-50%)"
tabindex="0"
/>
<div>
@ -22058,22 +22058,19 @@ exports[`ConfigProvider components Slider configProvider componentSize middle 1`
</div>
</div>
</div>
<div
class="config-slider-mark"
/>
</div>
`;
exports[`ConfigProvider components Slider configProvider virtual and dropdownMatchSelectWidth 1`] = `
<div
class="ant-slider"
class="ant-slider ant-slider-horizontal"
>
<div
class="ant-slider-rail"
/>
<div
class="ant-slider-track"
style="left:0%;right:auto;width:0%"
style="left:0%;width:0%"
/>
<div
class="ant-slider-step"
@ -22085,7 +22082,7 @@ exports[`ConfigProvider components Slider configProvider virtual and dropdownMat
aria-valuenow="0"
class="ant-slider-handle ant-tooltip-open"
role="slider"
style="left:0%;right:auto;transform:translateX(-50%)"
style="left:0%;transform:translateX(-50%)"
tabindex="0"
/>
<div>
@ -22112,22 +22109,19 @@ exports[`ConfigProvider components Slider configProvider virtual and dropdownMat
</div>
</div>
</div>
<div
class="ant-slider-mark"
/>
</div>
`;
exports[`ConfigProvider components Slider normal 1`] = `
<div
class="ant-slider"
class="ant-slider ant-slider-horizontal"
>
<div
class="ant-slider-rail"
/>
<div
class="ant-slider-track"
style="left:0%;right:auto;width:0%"
style="left:0%;width:0%"
/>
<div
class="ant-slider-step"
@ -22139,7 +22133,7 @@ exports[`ConfigProvider components Slider normal 1`] = `
aria-valuenow="0"
class="ant-slider-handle ant-tooltip-open"
role="slider"
style="left:0%;right:auto;transform:translateX(-50%)"
style="left:0%;transform:translateX(-50%)"
tabindex="0"
/>
<div>
@ -22166,22 +22160,19 @@ exports[`ConfigProvider components Slider normal 1`] = `
</div>
</div>
</div>
<div
class="ant-slider-mark"
/>
</div>
`;
exports[`ConfigProvider components Slider prefixCls 1`] = `
<div
class="prefix-Slider"
class="prefix-Slider prefix-Slider-horizontal"
>
<div
class="prefix-Slider-rail"
/>
<div
class="prefix-Slider-track"
style="left:0%;right:auto;width:0%"
style="left:0%;width:0%"
/>
<div
class="prefix-Slider-step"
@ -22193,7 +22184,7 @@ exports[`ConfigProvider components Slider prefixCls 1`] = `
aria-valuenow="0"
class="prefix-Slider-handle prefix-Slider-tooltip-open"
role="slider"
style="left:0%;right:auto;transform:translateX(-50%)"
style="left:0%;transform:translateX(-50%)"
tabindex="0"
/>
<div>
@ -22220,9 +22211,6 @@ exports[`ConfigProvider components Slider prefixCls 1`] = `
</div>
</div>
</div>
<div
class="prefix-Slider-mark"
/>
</div>
`;

View File

@ -0,0 +1,49 @@
import * as React from 'react';
import { mount } from 'enzyme';
import ConfigProvider from '..';
import Button from '../../button';
describe('ConfigProvider.DynamicTheme', () => {
beforeEach(() => {
Array.from(document.querySelectorAll('style')).forEach(style => {
style.parentNode?.removeChild(style);
});
});
it('customize primary color', () => {
mount(
<ConfigProvider
theme={{
token: {
primaryColor: '#f00',
},
}}
>
<Button />
</ConfigProvider>,
);
const dynamicStyles = Array.from(document.querySelectorAll('style[data-css-hash]'));
expect(
dynamicStyles.some(style => {
const { innerHTML } = style;
return (
innerHTML.includes('.ant-btn-primary') && innerHTML.includes('background-color:#f00')
);
}),
).toBeTruthy();
});
it('not crash on null token', () => {
expect(() => {
mount(
<ConfigProvider
theme={{
token: null as any,
}}
/>,
);
}).not.toThrow();
});
});

View File

@ -16,17 +16,30 @@ describe('ConfigProvider.Icon', () => {
});
});
it('basic', () => {
const wrapper = mount(
<ConfigProvider iconPrefixCls="bamboo" csp={{ nonce: 'light' }}>
<SmileOutlined />
</ConfigProvider>,
);
describe('csp', () => {
it('raw', () => {
mount(
<ConfigProvider csp={{ nonce: 'little' }}>
<SmileOutlined />
</ConfigProvider>,
);
const styleNode = document.querySelector('style');
const styleNode = document.querySelector('style');
expect(styleNode.nonce).toEqual('little');
});
expect(wrapper.exists('.bamboo-smile')).toBeTruthy();
expect(styleNode.nonce).toEqual('light');
it('mix with iconPrefixCls', () => {
const wrapper = mount(
<ConfigProvider iconPrefixCls="bamboo" csp={{ nonce: 'light' }}>
<SmileOutlined />
</ConfigProvider>,
);
const styleNode = document.querySelector('style');
expect(wrapper.exists('.bamboo-smile')).toBeTruthy();
expect(styleNode.nonce).toEqual('light');
});
});
it('nest', () => {

View File

@ -1,7 +1,17 @@
import { kebabCase } from 'lodash';
import canUseDom from 'rc-util/lib/Dom/canUseDom';
import ConfigProvider from '..';
import { resetWarned } from '../../_util/devWarning';
let mockCanUseDom = true;
jest.mock('rc-util/lib/Dom/canUseDom', () => () => mockCanUseDom);
describe('ConfigProvider.Theme', () => {
beforeEach(() => {
mockCanUseDom = true;
});
const colorList = ['primaryColor', 'successColor', 'warningColor', 'errorColor', 'infoColor'];
colorList.forEach(colorName => {
@ -14,10 +24,29 @@ describe('ConfigProvider.Theme', () => {
});
const styles: any[] = Array.from(document.querySelectorAll('style'));
const themeStyle = styles.find(style => style['rc-util-key'].includes('-dynamic-theme'));
const themeStyle = styles.find(style =>
style.getAttribute('rc-util-key').includes('-dynamic-theme'),
);
expect(themeStyle).toBeTruthy();
expect(themeStyle.innerHTML).toContain(`--bamboo-${kebabCase(colorName)}: rgb(0, 0, 255)`);
});
});
it('warning for SSR', () => {
resetWarned();
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
mockCanUseDom = false;
expect(canUseDom()).toBeFalsy();
ConfigProvider.config({
theme: {},
});
expect(errorSpy).toHaveBeenCalledWith(
'Warning: [antd: ConfigProvider] SSR do not support dynamic theme with css variables.',
);
errorSpy.mockRestore();
});
});

View File

@ -4,6 +4,8 @@ import { Locale } from '../locale-provider';
import { SizeType } from './SizeContext';
import { RequiredMark } from '../form/Form';
export const defaultIconPrefixCls = 'anticon';
export interface Theme {
primaryColor?: string;
infoColor?: string;
@ -23,7 +25,7 @@ export interface ConfigConsumerProps {
getTargetContainer?: () => HTMLElement;
getPopupContainer?: (triggerNode?: HTMLElement) => HTMLElement;
rootPrefixCls?: string;
iconPrefixCls?: string;
iconPrefixCls: string;
getPrefixCls: (suffixCls?: string, customizePrefixCls?: string) => string;
renderEmpty: RenderEmptyHandler;
csp?: CSPConfig;
@ -58,6 +60,8 @@ export const ConfigContext = React.createContext<ConfigConsumerProps>({
getPrefixCls: defaultGetPrefixCls,
renderEmpty: defaultRenderEmpty,
iconPrefixCls: defaultIconPrefixCls,
});
export const ConfigConsumer = ConfigContext.Consumer;

View File

@ -1,13 +1,15 @@
/* eslint-disable import/prefer-default-export, prefer-destructuring */
import { updateCSS } from 'rc-util/lib/Dom/dynamicCSS';
import canUseDom from 'rc-util/lib/Dom/canUseDom';
import { TinyColor } from '@ctrl/tinycolor';
import { generate } from '@ant-design/colors';
import { Theme } from './context';
import devWarning from '../_util/devWarning';
const dynamicStyleMark = `-ant-${Date.now()}-${Math.random()}`;
export function registerTheme(globalPrefixCls: string, theme: Theme) {
export function getStyle(globalPrefixCls: string, theme: Theme) {
const variables: Record<string, string> = {};
const formatColor = (
@ -86,12 +88,19 @@ export function registerTheme(globalPrefixCls: string, theme: Theme) {
key => `--${globalPrefixCls}-${key}: ${variables[key]};`,
);
updateCSS(
`
return `
:root {
${cssList.join('\n')}
}
`,
`${dynamicStyleMark}-dynamic-theme`,
);
`.trim();
}
export function registerTheme(globalPrefixCls: string, theme: Theme) {
const style = getStyle(globalPrefixCls, theme);
if (canUseDom()) {
updateCSS(style, `${dynamicStyleMark}-dynamic-theme`);
} else {
devWarning(false, 'ConfigProvider', 'SSR do not support dynamic theme with css variables.');
}
}

View File

@ -13,6 +13,7 @@ import {
DirectionType,
ConfigConsumerProps,
Theme,
defaultIconPrefixCls,
} from './context';
import SizeContext, { SizeContextProvider, SizeType } from './SizeContext';
import message from '../message';
@ -20,6 +21,8 @@ import notification from '../notification';
import { RequiredMark } from '../form/Form';
import { registerTheme } from './cssVariables';
import defaultLocale from '../locale/default';
import { DesignToken, DesignTokenContext } from '../_util/theme';
import defaultThemeToken from '../_util/theme/default';
export {
RenderEmptyHandler,
@ -80,6 +83,10 @@ export interface ConfigProviderProps {
};
virtual?: boolean;
dropdownMatchSelectWidth?: boolean;
theme?: {
token?: Partial<DesignToken>;
hashed?: boolean;
};
}
interface ProviderChildrenProps extends ConfigProviderProps {
@ -88,7 +95,7 @@ interface ProviderChildrenProps extends ConfigProviderProps {
}
export const defaultPrefixCls = 'ant';
export const defaultIconPrefixCls = 'anticon';
export { defaultIconPrefixCls };
let globalPrefixCls: string;
let globalIconPrefixCls: string;
@ -159,6 +166,7 @@ const ProviderChildren: React.FC<ProviderChildrenProps> = props => {
legacyLocale,
parentContext,
iconPrefixCls,
theme = {},
} = props;
const getPrefixCls = React.useCallback(
@ -211,7 +219,7 @@ const ProviderChildren: React.FC<ProviderChildrenProps> = props => {
const memoIconContextValue = React.useMemo(
() => ({ prefixCls: iconPrefixCls, csp }),
[iconPrefixCls],
[iconPrefixCls, csp],
);
let childNode = children;
@ -238,7 +246,7 @@ const ProviderChildren: React.FC<ProviderChildrenProps> = props => {
);
}
if (iconPrefixCls) {
if (iconPrefixCls || csp) {
childNode = (
<IconContext.Provider value={memoIconContextValue}>{childNode}</IconContext.Provider>
);
@ -248,10 +256,30 @@ const ProviderChildren: React.FC<ProviderChildrenProps> = props => {
childNode = <SizeContextProvider size={componentSize}>{childNode}</SizeContextProvider>;
}
// ================================ Dynamic theme ================================
const memoTheme = React.useMemo(
() => ({
token: {
...defaultThemeToken,
...theme?.token,
},
hashed: theme?.hashed,
}),
[theme?.token, theme?.hashed],
);
if (theme?.token || theme?.hashed) {
childNode = (
<DesignTokenContext.Provider value={memoTheme}>{childNode}</DesignTokenContext.Provider>
);
}
// =================================== Render ===================================
return <ConfigContext.Provider value={memoedConfig}>{childNode}</ConfigContext.Provider>;
};
const ConfigProvider: React.FC<ConfigProviderProps> & {
/** @private internal Usage. do not use in your production */
ConfigContext: typeof ConfigContext;
SizeContext: typeof SizeContext;
config: typeof setGlobalConfig;
@ -284,7 +312,6 @@ const ConfigProvider: React.FC<ConfigProviderProps> & {
);
};
/** @private internal Usage. do not use in your production */
ConfigProvider.ConfigContext = ConfigContext;
ConfigProvider.SizeContext = SizeContext;
ConfigProvider.config = setGlobalConfig;

View File

@ -202,4 +202,36 @@ describe('DatePicker', () => {
expect(year).toBe(startDate.format('YYYY'));
expect(wrapper.find('.ant-picker-time-panel').length).toBe(1);
});
it('placement api work correctly ', () => {
const popupAlignDefault = (points = ['tl', 'bl'], offset = [0, 4]) => ({
points,
offset,
overflow: {
adjustX: 1,
adjustY: 1,
},
});
const wrapper = mount(
<DatePicker.RangePicker defaultValue={moment()} placement="bottomLeft" />,
);
expect(wrapper.find('Trigger').prop('popupAlign')).toEqual(popupAlignDefault(['tl', 'bl']));
wrapper.setProps({
placement: 'bottomRight',
});
expect(wrapper.find('Trigger').prop('popupAlign')).toEqual(popupAlignDefault(['tr', 'br']));
wrapper.setProps({
placement: 'topLeft',
});
expect(wrapper.find('Trigger').prop('popupAlign')).toEqual(
popupAlignDefault(['bl', 'tl'], [0, -4]),
);
wrapper.setProps({
placement: 'topRight',
});
expect(wrapper.find('Trigger').prop('popupAlign')).toEqual(
popupAlignDefault(['br', 'tr'], [0, -4]),
);
});
});

View File

@ -6,6 +6,7 @@ import DatePicker from '..';
import { setMockDate, resetMockDate } from '../../../tests/utils';
import { openPicker, selectCell, closePicker } from './utils';
import focusTest from '../../../tests/shared/focusTest';
import enUS from '../locale/en_US';
dayjs.extend(customParseFormat);
@ -99,4 +100,10 @@ describe('RangePicker', () => {
expect(wrapper.find('input').first().props().placeholder).toEqual('Start date');
expect(wrapper.find('input').last().props().placeholder).toEqual('End date');
});
it('RangePicker picker quarter placeholder', () => {
const wrapper = mount(<RangePicker picker="quarter" locale={enUS} />);
expect(wrapper.find('input').at(0).props().placeholder).toEqual('Start quarter');
expect(wrapper.find('input').at(1).props().placeholder).toEqual('End quarter');
});
});

View File

@ -2349,6 +2349,216 @@ exports[`renders ./components/date-picker/demo/mode.md correctly 1`] = `
</div>
`;
exports[`renders ./components/date-picker/demo/placement.md correctly 1`] = `
Array [
<div
class="ant-radio-group ant-radio-group-outline"
>
<label
class="ant-radio-button-wrapper ant-radio-button-wrapper-checked"
>
<span
class="ant-radio-button ant-radio-button-checked"
>
<input
checked=""
class="ant-radio-button-input"
type="radio"
value="topLeft"
/>
<span
class="ant-radio-button-inner"
/>
</span>
<span>
topLeft
</span>
</label>
<label
class="ant-radio-button-wrapper"
>
<span
class="ant-radio-button"
>
<input
class="ant-radio-button-input"
type="radio"
value="topRight"
/>
<span
class="ant-radio-button-inner"
/>
</span>
<span>
topRight
</span>
</label>
<label
class="ant-radio-button-wrapper"
>
<span
class="ant-radio-button"
>
<input
class="ant-radio-button-input"
type="radio"
value="bottomLeft"
/>
<span
class="ant-radio-button-inner"
/>
</span>
<span>
bottomLeft
</span>
</label>
<label
class="ant-radio-button-wrapper"
>
<span
class="ant-radio-button"
>
<input
class="ant-radio-button-input"
type="radio"
value="bottomRight"
/>
<span
class="ant-radio-button-inner"
/>
</span>
<span>
bottomRight
</span>
</label>
</div>,
<br />,
<br />,
<div
class="ant-picker"
>
<div
class="ant-picker-input"
>
<input
autocomplete="off"
placeholder="Select date"
readonly=""
size="12"
title=""
value=""
/>
<span
class="ant-picker-suffix"
>
<span
aria-label="calendar"
class="anticon anticon-calendar"
role="img"
>
<svg
aria-hidden="true"
data-icon="calendar"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M880 184H712v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H384v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H144c-17.7 0-32 14.3-32 32v664c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V216c0-17.7-14.3-32-32-32zm-40 656H184V460h656v380zM184 392V256h128v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h256v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h128v136H184z"
/>
</svg>
</span>
</span>
</div>
</div>,
<br />,
<br />,
<div
class="ant-picker ant-picker-range"
>
<div
class="ant-picker-input ant-picker-input-active"
>
<input
autocomplete="off"
placeholder="Start date"
readonly=""
size="12"
value=""
/>
</div>
<div
class="ant-picker-range-separator"
>
<span
aria-label="to"
class="ant-picker-separator"
>
<span
aria-label="swap-right"
class="anticon anticon-swap-right"
role="img"
>
<svg
aria-hidden="true"
data-icon="swap-right"
fill="currentColor"
focusable="false"
height="1em"
viewBox="0 0 1024 1024"
width="1em"
>
<path
d="M873.1 596.2l-164-208A32 32 0 00684 376h-64.8c-6.7 0-10.4 7.7-6.3 13l144.3 183H152c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h695.9c26.8 0 41.7-30.8 25.2-51.8z"
/>
</svg>
</span>
</span>
</div>
<div
class="ant-picker-input"
>
<input
autocomplete="off"
placeholder="End date"
readonly=""
size="12"
value=""
/>
</div>
<div
class="ant-picker-active-bar"
style="left:0;width:0;position:absolute"
/>
<span
class="ant-picker-suffix"
>
<span
aria-label="calendar"
class="anticon anticon-calendar"
role="img"
>
<svg
aria-hidden="true"
data-icon="calendar"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M880 184H712v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H384v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H144c-17.7 0-32 14.3-32 32v664c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V216c0-17.7-14.3-32-32-32zm-40 656H184V460h656v380zM184 392V256h128v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h256v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h128v136H184z"
/>
</svg>
</span>
</span>
</div>,
]
`;
exports[`renders ./components/date-picker/demo/presetted-ranges.md correctly 1`] = `
<div
class="ant-space ant-space-vertical"
@ -2875,6 +3085,92 @@ exports[`renders ./components/date-picker/demo/range-picker.md correctly 1`] = `
</span>
</div>
</div>
<div
class="ant-space-item"
style="margin-bottom:12px"
>
<div
class="ant-picker ant-picker-range"
>
<div
class="ant-picker-input ant-picker-input-active"
>
<input
autocomplete="off"
placeholder="Start quarter"
readonly=""
size="12"
value=""
/>
</div>
<div
class="ant-picker-range-separator"
>
<span
aria-label="to"
class="ant-picker-separator"
>
<span
aria-label="swap-right"
class="anticon anticon-swap-right"
role="img"
>
<svg
aria-hidden="true"
data-icon="swap-right"
fill="currentColor"
focusable="false"
height="1em"
viewBox="0 0 1024 1024"
width="1em"
>
<path
d="M873.1 596.2l-164-208A32 32 0 00684 376h-64.8c-6.7 0-10.4 7.7-6.3 13l144.3 183H152c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h695.9c26.8 0 41.7-30.8 25.2-51.8z"
/>
</svg>
</span>
</span>
</div>
<div
class="ant-picker-input"
>
<input
autocomplete="off"
placeholder="End quarter"
readonly=""
size="12"
value=""
/>
</div>
<div
class="ant-picker-active-bar"
style="left:0;width:0;position:absolute"
/>
<span
class="ant-picker-suffix"
>
<span
aria-label="calendar"
class="anticon anticon-calendar"
role="img"
>
<svg
aria-hidden="true"
data-icon="calendar"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M880 184H712v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H384v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H144c-17.7 0-32 14.3-32 32v664c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V216c0-17.7-14.3-32-32-32zm-40 656H184V460h656v380zM184 392V256h128v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h256v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h128v136H184z"
/>
</svg>
</span>
</span>
</div>
</div>
<div
class="ant-space-item"
>
@ -3432,6 +3728,277 @@ exports[`renders ./components/date-picker/demo/start-end.md correctly 1`] = `
</div>
`;
exports[`renders ./components/date-picker/demo/status.md correctly 1`] = `
<div
class="ant-space ant-space-vertical"
style="width:100%"
>
<div
class="ant-space-item"
style="margin-bottom:8px"
>
<div
class="ant-picker ant-picker-status-error"
style="width:100%"
>
<div
class="ant-picker-input"
>
<input
autocomplete="off"
placeholder="Select date"
readonly=""
size="12"
title=""
value=""
/>
<span
class="ant-picker-suffix"
>
<span
aria-label="calendar"
class="anticon anticon-calendar"
role="img"
>
<svg
aria-hidden="true"
data-icon="calendar"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M880 184H712v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H384v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H144c-17.7 0-32 14.3-32 32v664c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V216c0-17.7-14.3-32-32-32zm-40 656H184V460h656v380zM184 392V256h128v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h256v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h128v136H184z"
/>
</svg>
</span>
</span>
</div>
</div>
</div>
<div
class="ant-space-item"
style="margin-bottom:8px"
>
<div
class="ant-picker ant-picker-status-warning"
style="width:100%"
>
<div
class="ant-picker-input"
>
<input
autocomplete="off"
placeholder="Select date"
readonly=""
size="12"
title=""
value=""
/>
<span
class="ant-picker-suffix"
>
<span
aria-label="calendar"
class="anticon anticon-calendar"
role="img"
>
<svg
aria-hidden="true"
data-icon="calendar"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M880 184H712v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H384v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H144c-17.7 0-32 14.3-32 32v664c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V216c0-17.7-14.3-32-32-32zm-40 656H184V460h656v380zM184 392V256h128v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h256v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h128v136H184z"
/>
</svg>
</span>
</span>
</div>
</div>
</div>
<div
class="ant-space-item"
style="margin-bottom:8px"
>
<div
class="ant-picker ant-picker-range ant-picker-status-error"
style="width:100%"
>
<div
class="ant-picker-input ant-picker-input-active"
>
<input
autocomplete="off"
placeholder="Start date"
readonly=""
size="12"
value=""
/>
</div>
<div
class="ant-picker-range-separator"
>
<span
aria-label="to"
class="ant-picker-separator"
>
<span
aria-label="swap-right"
class="anticon anticon-swap-right"
role="img"
>
<svg
aria-hidden="true"
data-icon="swap-right"
fill="currentColor"
focusable="false"
height="1em"
viewBox="0 0 1024 1024"
width="1em"
>
<path
d="M873.1 596.2l-164-208A32 32 0 00684 376h-64.8c-6.7 0-10.4 7.7-6.3 13l144.3 183H152c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h695.9c26.8 0 41.7-30.8 25.2-51.8z"
/>
</svg>
</span>
</span>
</div>
<div
class="ant-picker-input"
>
<input
autocomplete="off"
placeholder="End date"
readonly=""
size="12"
value=""
/>
</div>
<div
class="ant-picker-active-bar"
style="left:0;width:0;position:absolute"
/>
<span
class="ant-picker-suffix"
>
<span
aria-label="calendar"
class="anticon anticon-calendar"
role="img"
>
<svg
aria-hidden="true"
data-icon="calendar"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M880 184H712v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H384v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H144c-17.7 0-32 14.3-32 32v664c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V216c0-17.7-14.3-32-32-32zm-40 656H184V460h656v380zM184 392V256h128v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h256v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h128v136H184z"
/>
</svg>
</span>
</span>
</div>
</div>
<div
class="ant-space-item"
>
<div
class="ant-picker ant-picker-range ant-picker-status-warning"
style="width:100%"
>
<div
class="ant-picker-input ant-picker-input-active"
>
<input
autocomplete="off"
placeholder="Start date"
readonly=""
size="12"
value=""
/>
</div>
<div
class="ant-picker-range-separator"
>
<span
aria-label="to"
class="ant-picker-separator"
>
<span
aria-label="swap-right"
class="anticon anticon-swap-right"
role="img"
>
<svg
aria-hidden="true"
data-icon="swap-right"
fill="currentColor"
focusable="false"
height="1em"
viewBox="0 0 1024 1024"
width="1em"
>
<path
d="M873.1 596.2l-164-208A32 32 0 00684 376h-64.8c-6.7 0-10.4 7.7-6.3 13l144.3 183H152c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h695.9c26.8 0 41.7-30.8 25.2-51.8z"
/>
</svg>
</span>
</span>
</div>
<div
class="ant-picker-input"
>
<input
autocomplete="off"
placeholder="End date"
readonly=""
size="12"
value=""
/>
</div>
<div
class="ant-picker-active-bar"
style="left:0;width:0;position:absolute"
/>
<span
class="ant-picker-suffix"
>
<span
aria-label="calendar"
class="anticon anticon-calendar"
role="img"
>
<svg
aria-hidden="true"
data-icon="calendar"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M880 184H712v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H384v-64c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v64H144c-17.7 0-32 14.3-32 32v664c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V216c0-17.7-14.3-32-32-32zm-40 656H184V460h656v380zM184 392V256h128v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h256v48c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-48h128v136H184z"
/>
</svg>
</span>
</span>
</div>
</div>
</div>
`;
exports[`renders ./components/date-picker/demo/suffix.md correctly 1`] = `
<div
class="ant-space ant-space-vertical"

View File

@ -0,0 +1,47 @@
---
order: 28
title:
zh-CN: 基本
en-US: Basic
---
## zh-CN
可以通过 `placement` 手动指定弹出的位置。
## en-US
You can manually specify the position of the popup via `placement`.
```jsx
import { DatePicker, Space, Radio } from 'antd';
const { RangePicker } = DatePicker;
const SetPlacementDemo = () => {
const [placement, SetPlacement] = React.useState('topLeft');
const placementChange = e => {
SetPlacement(e.target.value);
};
return (
<>
<Radio.Group value={placement} onChange={placementChange}>
<Radio.Button value="topLeft">topLeft</Radio.Button>
<Radio.Button value="topRight">topRight</Radio.Button>
<Radio.Button value="bottomLeft">bottomLeft</Radio.Button>
<Radio.Button value="bottomRight">bottomRight</Radio.Button>
</Radio.Group>
<br />
<br />
<DatePicker placement={placement} />
<br />
<br />
<RangePicker placement={placement} />
</>
);
};
ReactDOM.render(<SetPlacementDemo />, mountNode);
```

View File

@ -24,6 +24,7 @@ ReactDOM.render(
<RangePicker showTime />
<RangePicker picker="week" />
<RangePicker picker="month" />
<RangePicker picker="quarter" />
<RangePicker picker="year" />
</Space>,
mountNode,

View File

@ -0,0 +1,30 @@
---
order: 19
version: 4.19.0
title:
zh-CN: 自定义状态
en-US: Status
---
## zh-CN
使用 `status` 为 DatePicker 添加状态,可选 `error` 或者 `warning`
## en-US
Add status to DatePicker with `status`, which could be `error` or `warning`.
```tsx
import { DatePicker, Space } from 'antd';
const Status: React.FC = () => (
<Space direction="vertical" style={{ width: '100%' }}>
<DatePicker status="error" style={{ width: '100%' }} />
<DatePicker status="warning" style={{ width: '100%' }} />
<DatePicker.RangePicker status="error" style={{ width: '100%' }} />
<DatePicker.RangePicker status="warning" style={{ width: '100%' }} />
</Space>
);
ReactDOM.render(<Status />, mountNode);
```

View File

@ -6,13 +6,16 @@ import CloseCircleFilled from '@ant-design/icons/CloseCircleFilled';
import SwapRightOutlined from '@ant-design/icons/SwapRightOutlined';
import { RangePicker as RCRangePicker } from 'rc-picker';
import { GenerateConfig } from 'rc-picker/lib/generate/index';
import { PickerMode } from 'rc-picker/lib/interface';
import enUS from '../locale/en_US';
import { ConfigContext, ConfigConsumerProps } from '../../config-provider';
import SizeContext from '../../config-provider/SizeContext';
import LocaleReceiver from '../../locale-provider/LocaleReceiver';
import { getRangePlaceholder } from '../util';
import { getRangePlaceholder, transPlacement2DropdownAlign } from '../util';
import { RangePickerProps, PickerLocale, getTimeProps, Components } from '.';
import { PickerComponentClass } from './interface';
import { FormItemStatusContext } from '../../form/context';
import { getFeedbackIcon, getMergedStatus, getStatusClassNames } from '../../_util/statusUtils';
export default function generateRangePicker<DateType>(
generateConfig: GenerateConfig<DateType>,
@ -36,6 +39,23 @@ export default function generateRangePicker<DateType>(
}
};
renderFeedback = (prefixCls: string) => (
<FormItemStatusContext.Consumer>
{({ hasFeedback, status: contextStatus }) => {
const { status: customStatus } = this.props;
const status = getMergedStatus(contextStatus, customStatus);
return hasFeedback && getFeedbackIcon(prefixCls, status);
}}
</FormItemStatusContext.Consumer>
);
renderSuffix = (prefixCls: string, mergedPicker?: PickerMode) => (
<>
{mergedPicker === 'time' ? <ClockCircleOutlined /> : <CalendarOutlined />}
{this.renderFeedback(prefixCls)}
</>
);
renderPicker = (contextLocale: PickerLocale) => {
const locale = { ...contextLocale, ...this.props.locale };
const { getPrefixCls, direction, getPopupContainer } = this.context;
@ -43,9 +63,11 @@ export default function generateRangePicker<DateType>(
prefixCls: customizePrefixCls,
getPopupContainer: customGetPopupContainer,
className,
placement,
size: customizeSize,
bordered = true,
placeholder,
status: customStatus,
...restProps
} = this.props;
const { format, showTime, picker } = this.props as any;
@ -66,38 +88,48 @@ export default function generateRangePicker<DateType>(
const mergedSize = customizeSize || size;
return (
<RCRangePicker<DateType>
separator={
<span aria-label="to" className={`${prefixCls}-separator`}>
<SwapRightOutlined />
</span>
}
ref={this.pickerRef}
placeholder={getRangePlaceholder(picker, locale, placeholder)}
suffixIcon={picker === 'time' ? <ClockCircleOutlined /> : <CalendarOutlined />}
clearIcon={<CloseCircleFilled />}
prevIcon={<span className={`${prefixCls}-prev-icon`} />}
nextIcon={<span className={`${prefixCls}-next-icon`} />}
superPrevIcon={<span className={`${prefixCls}-super-prev-icon`} />}
superNextIcon={<span className={`${prefixCls}-super-next-icon`} />}
allowClear
transitionName={`${rootPrefixCls}-slide-up`}
{...restProps}
{...additionalOverrideProps}
className={classNames(
{
[`${prefixCls}-${mergedSize}`]: mergedSize,
[`${prefixCls}-borderless`]: !bordered,
},
className,
<FormItemStatusContext.Consumer>
{({ hasFeedback, status: contextStatus }) => (
<RCRangePicker<DateType>
separator={
<span aria-label="to" className={`${prefixCls}-separator`}>
<SwapRightOutlined />
</span>
}
ref={this.pickerRef}
dropdownAlign={transPlacement2DropdownAlign(direction, placement)}
placeholder={getRangePlaceholder(picker, locale, placeholder)}
suffixIcon={this.renderSuffix(prefixCls, picker)}
clearIcon={<CloseCircleFilled />}
prevIcon={<span className={`${prefixCls}-prev-icon`} />}
nextIcon={<span className={`${prefixCls}-next-icon`} />}
superPrevIcon={<span className={`${prefixCls}-super-prev-icon`} />}
superNextIcon={<span className={`${prefixCls}-super-next-icon`} />}
allowClear
transitionName={`${rootPrefixCls}-slide-up`}
{...restProps}
{...additionalOverrideProps}
className={classNames(
{
[`${prefixCls}-${mergedSize}`]: mergedSize,
[`${prefixCls}-borderless`]: !bordered,
},
getStatusClassNames(
prefixCls,
getMergedStatus(contextStatus, customStatus),
hasFeedback,
),
className,
)}
locale={locale!.lang}
prefixCls={prefixCls}
getPopupContainer={customGetPopupContainer || getPopupContainer}
generateConfig={generateConfig}
components={Components}
direction={direction}
/>
)}
locale={locale!.lang}
prefixCls={prefixCls}
getPopupContainer={customGetPopupContainer || getPopupContainer}
generateConfig={generateConfig}
components={Components}
direction={direction}
/>
</FormItemStatusContext.Consumer>
);
}}
</SizeContext.Consumer>

View File

@ -7,7 +7,7 @@ import RCPicker from 'rc-picker';
import { PickerMode } from 'rc-picker/lib/interface';
import { GenerateConfig } from 'rc-picker/lib/generate/index';
import enUS from '../locale/en_US';
import { getPlaceholder } from '../util';
import { getPlaceholder, transPlacement2DropdownAlign } from '../util';
import devWarning from '../../_util/devWarning';
import { ConfigContext, ConfigConsumerProps } from '../../config-provider';
import LocaleReceiver from '../../locale-provider/LocaleReceiver';
@ -21,9 +21,18 @@ import {
Components,
} from '.';
import { PickerComponentClass } from './interface';
import { FormItemStatusContext } from '../../form/context';
import {
getFeedbackIcon,
getMergedStatus,
getStatusClassNames,
InputStatus,
} from '../../_util/statusUtils';
export default function generatePicker<DateType>(generateConfig: GenerateConfig<DateType>) {
type DatePickerProps = PickerProps<DateType>;
type DatePickerProps = PickerProps<DateType> & {
status?: InputStatus;
};
function getPicker<InnerPickerProps extends DatePickerProps>(
picker?: PickerMode,
@ -59,6 +68,23 @@ export default function generatePicker<DateType>(generateConfig: GenerateConfig<
}
};
renderFeedback = (prefixCls: string) => (
<FormItemStatusContext.Consumer>
{({ hasFeedback, status: contextStatus }) => {
const { status: customStatus } = this.props;
const status = getMergedStatus(contextStatus, customStatus);
return hasFeedback && getFeedbackIcon(prefixCls, status);
}}
</FormItemStatusContext.Consumer>
);
renderSuffix = (prefixCls: string, mergedPicker?: PickerMode) => (
<>
{mergedPicker === 'time' ? <ClockCircleOutlined /> : <CalendarOutlined />}
{this.renderFeedback(prefixCls)}
</>
);
renderPicker = (contextLocale: PickerLocale) => {
const locale = { ...contextLocale, ...this.props.locale };
const { getPrefixCls, direction, getPopupContainer } = this.context;
@ -68,7 +94,9 @@ export default function generatePicker<DateType>(generateConfig: GenerateConfig<
className,
size: customizeSize,
bordered = true,
placement,
placeholder,
status: customStatus,
...restProps
} = this.props;
const { format, showTime } = this.props as any;
@ -99,36 +127,44 @@ export default function generatePicker<DateType>(generateConfig: GenerateConfig<
const mergedSize = customizeSize || size;
return (
<RCPicker<DateType>
ref={this.pickerRef}
placeholder={getPlaceholder(mergedPicker, locale, placeholder)}
suffixIcon={
mergedPicker === 'time' ? <ClockCircleOutlined /> : <CalendarOutlined />
}
clearIcon={<CloseCircleFilled />}
prevIcon={<span className={`${prefixCls}-prev-icon`} />}
nextIcon={<span className={`${prefixCls}-next-icon`} />}
superPrevIcon={<span className={`${prefixCls}-super-prev-icon`} />}
superNextIcon={<span className={`${prefixCls}-super-next-icon`} />}
allowClear
transitionName={`${rootPrefixCls}-slide-up`}
{...additionalProps}
{...restProps}
{...additionalOverrideProps}
locale={locale!.lang}
className={classNames(
{
[`${prefixCls}-${mergedSize}`]: mergedSize,
[`${prefixCls}-borderless`]: !bordered,
},
className,
<FormItemStatusContext.Consumer>
{({ hasFeedback, status: contextStatus }) => (
<RCPicker<DateType>
ref={this.pickerRef}
placeholder={getPlaceholder(mergedPicker, locale, placeholder)}
suffixIcon={this.renderSuffix(prefixCls, mergedPicker)}
dropdownAlign={transPlacement2DropdownAlign(direction, placement)}
clearIcon={<CloseCircleFilled />}
prevIcon={<span className={`${prefixCls}-prev-icon`} />}
nextIcon={<span className={`${prefixCls}-next-icon`} />}
superPrevIcon={<span className={`${prefixCls}-super-prev-icon`} />}
superNextIcon={<span className={`${prefixCls}-super-next-icon`} />}
allowClear
transitionName={`${rootPrefixCls}-slide-up`}
{...additionalProps}
{...restProps}
{...additionalOverrideProps}
locale={locale!.lang}
className={classNames(
{
[`${prefixCls}-${mergedSize}`]: mergedSize,
[`${prefixCls}-borderless`]: !bordered,
},
getStatusClassNames(
prefixCls,
getMergedStatus(contextStatus, customStatus),
hasFeedback,
),
className,
)}
prefixCls={prefixCls}
getPopupContainer={customizeGetPopupContainer || getPopupContainer}
generateConfig={generateConfig}
components={Components}
direction={direction}
/>
)}
prefixCls={prefixCls}
getPopupContainer={customizeGetPopupContainer || getPopupContainer}
generateConfig={generateConfig}
components={Components}
direction={direction}
/>
</FormItemStatusContext.Consumer>
);
}}
</SizeContext.Consumer>

View File

@ -17,6 +17,8 @@ import PickerTag from '../PickerTag';
import { TimePickerLocale } from '../../time-picker';
import generateSinglePicker from './generateSinglePicker';
import generateRangePicker from './generateRangePicker';
import { tuple } from '../../_util/type';
import { InputStatus } from '../../_util/statusUtils';
export const Components = { button: PickerButton, rangeItem: PickerTag };
@ -27,13 +29,18 @@ function toArray<T>(list: T | T[]): T[] {
return Array.isArray(list) ? list : [list];
}
export function getTimeProps<DateType>(
props: { format?: string; picker?: PickerMode } & SharedTimeProps<DateType>,
export function getTimeProps<DateType, DisabledTime>(
props: { format?: string; picker?: PickerMode } & Omit<
SharedTimeProps<DateType>,
'disabledTime'
> & {
disabledTime?: DisabledTime;
},
) {
const { format, picker, showHour, showMinute, showSecond, use12Hours } = props;
const firstFormat = toArray(format)[0];
const showTimeObj: SharedTimeProps<DateType> = { ...props };
const showTimeObj = { ...props };
if (firstFormat && typeof firstFormat === 'string') {
if (!firstFormat.includes('s') && showSecond === undefined) {
@ -64,17 +71,18 @@ export function getTimeProps<DateType>(
showTime: showTimeObj,
};
}
const DataPickerPlacements = tuple('bottomLeft', 'bottomRight', 'topLeft', 'topRight');
type DataPickerPlacement = typeof DataPickerPlacements[number];
type InjectDefaultProps<Props> = Omit<
Props,
| 'locale'
| 'generateConfig'
| 'hideHeader'
| 'components'
'locale' | 'generateConfig' | 'hideHeader' | 'components'
> & {
locale?: PickerLocale;
size?: SizeType;
placement?: DataPickerPlacement;
bordered?: boolean;
status?: InputStatus;
};
export type PickerLocale = {
@ -96,6 +104,7 @@ export type AdditionalPickerLocaleLangProps = {
monthPlaceholder?: string;
weekPlaceholder?: string;
rangeYearPlaceholder?: [string, string];
rangeQuarterPlaceholder?: [string, string];
rangeMonthPlaceholder?: [string, string];
rangeWeekPlaceholder?: [string, string];
rangePlaceholder?: [string, string];

View File

@ -69,9 +69,11 @@ The following APIs are shared by DatePicker, RangePicker.
| panelRender | Customize panel render | (panelNode) => ReactNode | - | 4.5.0 |
| picker | Set picker type | `date` \| `week` \| `month` \| `quarter` \| `year` | `date` | `quarter`: 4.1.0 |
| placeholder | The placeholder of date input | string \| \[string,string] | - | |
| placement | The position where the selection box pops up | `bottomLeft` `bottomRight` `topLeft` `topRight` | bottomLeft | |
| popupStyle | To customize the style of the popup calendar | CSSProperties | {} | |
| prevIcon | The custom prev icon | ReactNode | - | 4.17.0 |
| size | To determine the size of the input box, the height of `large` and `small`, are 40px and 24px respectively, while default size is 32px | `large` \| `middle` \| `small` | - | |
| status | Set validation status | 'error' \| 'warning' | - | 4.19.0 |
| style | To customize the style of the input box | CSSProperties | {} | |
| suffixIcon | The custom suffix icon | ReactNode | - | |
| superNextIcon | The custom super next icon | ReactNode | - | 4.17.0 |

View File

@ -70,9 +70,11 @@ import locale from 'antd/lib/locale/zh_CN';
| panelRender | 自定义渲染面板 | (panelNode) => ReactNode | - | 4.5.0 |
| picker | 设置选择器类型 | `date` \| `week` \| `month` \| `quarter` \| `year` | `date` | `quarter`: 4.1.0 |
| placeholder | 输入框提示文字 | string \| \[string, string] | - | |
| placement | 选择框弹出的位置 | `bottomLeft` `bottomRight` `topLeft` `topRight` | bottomLeft | |
| popupStyle | 额外的弹出日历样式 | CSSProperties | {} | |
| prevIcon | 自定义上一个图标 | ReactNode | - | 4.17.0 |
| size | 输入框大小,`large` 高度为 40px`small` 为 24px默认是 32px | `large` \| `middle` \| `small` | - | |
| status | 设置校验状态 | 'error' \| 'warning' | - | 4.19.0 |
| style | 自定义输入框样式 | CSSProperties | {} | |
| suffixIcon | 自定义的选择框后缀图标 | ReactNode | - | |
| superNextIcon | 自定义 `<<` 切换图标 | ReactNode | - | 4.17.0 |

View File

@ -12,6 +12,7 @@ const locale: PickerLocale = {
weekPlaceholder: 'Select week',
rangePlaceholder: ['Start date', 'End date'],
rangeYearPlaceholder: ['Start year', 'End year'],
rangeQuarterPlaceholder: ['Start quarter', 'End quarter'],
rangeMonthPlaceholder: ['Start month', 'End month'],
rangeWeekPlaceholder: ['Start week', 'End week'],
...CalendarLocale,

View File

@ -12,6 +12,7 @@ const locale: PickerLocale = {
weekPlaceholder: 'Select week',
rangePlaceholder: ['Start date', 'End date'],
rangeYearPlaceholder: ['Start year', 'End year'],
rangeQuarterPlaceholder: ['Start quarter', 'End quarter'],
rangeMonthPlaceholder: ['Start month', 'End month'],
rangeWeekPlaceholder: ['Start week', 'End week'],
...CalendarLocale,

View File

@ -13,6 +13,7 @@ const locale: PickerLocale = {
rangePlaceholder: ['开始日期', '结束日期'],
rangeYearPlaceholder: ['开始年份', '结束年份'],
rangeMonthPlaceholder: ['开始月份', '结束月份'],
rangeQuarterPlaceholder: ['开始季度', '结束季度'],
rangeWeekPlaceholder: ['开始周', '结束周'],
...CalendarLocale,
},

View File

@ -13,6 +13,7 @@ const locale: PickerLocale = {
rangePlaceholder: ['開始日期', '結束日期'],
rangeYearPlaceholder: ['開始年份', '結束年份'],
rangeMonthPlaceholder: ['開始月份', '結束月份'],
rangeQuarterPlaceholder: ['開始季度', '結束季度'],
rangeWeekPlaceholder: ['開始周', '結束周'],
...CalendarLocale,
},

View File

@ -1,6 +1,7 @@
@import '../../style/themes/index';
@import '../../style/mixins/index';
@import '../../input/style/mixin';
@import './status';
@picker-prefix-cls: ~'@{ant-prefix}-picker';
@ -13,7 +14,7 @@
}
.@{picker-prefix-cls} {
@arrow-size: 10px;
@arrow-size: @popover-arrow-width;
.reset-component();
.picker-padding(@input-height-base, @font-size-base, @input-padding-horizontal-base);
@ -106,6 +107,8 @@
}
&-suffix {
display: flex;
flex: none;
align-self: center;
margin-left: (@padding-xs / 2);
color: @disabled-color;
@ -114,6 +117,10 @@
> * {
vertical-align: top;
&:not(:last-child) {
margin-right: 8px;
}
}
}
@ -221,17 +228,17 @@
&-placement-bottomLeft {
.@{picker-prefix-cls}-range-arrow {
top: (@arrow-size / 2) - (@arrow-size / 3);
top: (@arrow-size / 2) - (@arrow-size / 3) + 0.7px;
display: block;
transform: rotate(-45deg);
transform: rotate(-135deg) translateY(1px);
}
}
&-placement-topLeft {
.@{picker-prefix-cls}-range-arrow {
bottom: (@arrow-size / 2) - (@arrow-size / 3);
bottom: (@arrow-size / 2) - (@arrow-size / 3) + 0.7px;
display: block;
transform: rotate(135deg);
transform: rotate(45deg);
}
}
@ -311,19 +318,14 @@
width: @arrow-size;
height: @arrow-size;
margin-left: @input-padding-horizontal-base * 1.5;
box-shadow: 2px -2px 6px fade(@black, 6%);
background: linear-gradient(
135deg,
transparent 40%,
@calendar-bg 40%
); // Use linear-gradient to prevent arrow from covering text
box-shadow: 2px 2px 6px -2px fade(@black, 10%); // use spread radius to hide shadow over popover
transition: left @animation-duration-slow ease-out;
&::after {
position: absolute;
top: @border-width-base;
right: @border-width-base;
width: @arrow-size;
height: @arrow-size;
border: (@arrow-size / 2) solid @border-color-split;
border-color: @calendar-bg @calendar-bg transparent transparent;
content: '';
}
.roundedArrow(@arrow-size, 5px, @calendar-bg);
}
&-panel-container {

View File

@ -3,3 +3,5 @@ import './index.less';
// style dependencies
import '../../tag/style';
import '../../button/style';
// deps-lint-skip: form

View File

@ -0,0 +1,52 @@
@import '../../input/style/mixin';
@picker-prefix-cls: ~'@{ant-prefix}-picker';
.picker-status-color(
@text-color: @input-color;
@border-color: @input-border-color;
@background-color: @input-bg;
@hoverBorderColor: @primary-color-hover;
@outlineColor: @primary-color-outline;
) {
&.@{picker-prefix-cls} {
&,
&:not([disabled]):hover {
background-color: @background-color;
border-color: @border-color;
}
&-focused,
&:focus {
.active(@text-color, @hoverBorderColor, @outlineColor);
}
}
.@{picker-prefix-cls}-feedback-icon {
color: @text-color;
}
}
.@{picker-prefix-cls} {
&-status-error {
.picker-status-color(@error-color, @error-color, @input-bg, @error-color-hover, @error-color-outline);
}
&-status-warning {
.picker-status-color(@warning-color, @warning-color, @input-bg, @warning-color-hover, @warning-color-outline);
}
&-status-validating {
.@{picker-prefix-cls}-feedback-icon {
display: inline-block;
color: @primary-color;
}
}
&-status-success {
.@{picker-prefix-cls}-feedback-icon {
color: @success-color;
animation-name: diffZoomIn1 !important;
}
}
}

View File

@ -1,4 +1,6 @@
import { PickerMode } from 'rc-picker/lib/interface';
import { DirectionType } from '../config-provider';
import { SelectCommonPlacement } from '../_util/motion';
import { PickerLocale } from './generatePicker';
export function getPlaceholder(
@ -40,6 +42,9 @@ export function getRangePlaceholder(
if (picker === 'year' && locale.lang.yearPlaceholder) {
return locale.lang.rangeYearPlaceholder;
}
if (picker === 'quarter' && locale.lang.quarterPlaceholder) {
return locale.lang.rangeQuarterPlaceholder;
}
if (picker === 'month' && locale.lang.monthPlaceholder) {
return locale.lang.rangeMonthPlaceholder;
}
@ -51,3 +56,56 @@ export function getRangePlaceholder(
}
return locale.lang.rangePlaceholder;
}
export function transPlacement2DropdownAlign(
direction: DirectionType,
placement?: SelectCommonPlacement,
) {
const overflow = {
adjustX: 1,
adjustY: 1,
};
switch (placement) {
case 'bottomLeft': {
return {
points: ['tl', 'bl'],
offset: [0, 4],
overflow,
};
}
case 'bottomRight': {
return {
points: ['tr', 'br'],
offset: [0, 4],
overflow,
};
}
case 'topLeft': {
return {
points: ['bl', 'tl'],
offset: [0, -4],
overflow,
};
}
case 'topRight': {
return {
points: ['br', 'tr'],
offset: [0, -4],
overflow,
};
}
default: {
return direction === 'rtl'
? {
points: ['tr', 'br'],
offset: [0, 4],
overflow,
}
: {
points: ['tl', 'bl'],
offset: [0, 4],
overflow,
};
}
}
}

View File

@ -1,6 +1,8 @@
import * as React from 'react';
import classNames from 'classnames';
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
import { ConfigContext } from '../config-provider';
import useStyle from './style';
export interface DividerProps {
prefixCls?: string;
@ -14,56 +16,57 @@ export interface DividerProps {
plain?: boolean;
}
const Divider: React.FC<DividerProps> = props => (
<ConfigConsumer>
{({ getPrefixCls, direction }: ConfigConsumerProps) => {
const {
prefixCls: customizePrefixCls,
type = 'horizontal',
orientation = 'center',
orientationMargin,
className,
children,
dashed,
plain,
...restProps
} = props;
const prefixCls = getPrefixCls('divider', customizePrefixCls);
const orientationPrefix = orientation.length > 0 ? `-${orientation}` : orientation;
const hasChildren = !!children;
const hasCustomMarginLeft = orientation === 'left' && orientationMargin != null;
const hasCustomMarginRight = orientation === 'right' && orientationMargin != null;
const classString = classNames(
prefixCls,
`${prefixCls}-${type}`,
{
[`${prefixCls}-with-text`]: hasChildren,
[`${prefixCls}-with-text${orientationPrefix}`]: hasChildren,
[`${prefixCls}-dashed`]: !!dashed,
[`${prefixCls}-plain`]: !!plain,
[`${prefixCls}-rtl`]: direction === 'rtl',
[`${prefixCls}-no-default-orientation-margin-left`]: hasCustomMarginLeft,
[`${prefixCls}-no-default-orientation-margin-right`]: hasCustomMarginRight,
},
className,
);
const Divider: React.FC<DividerProps> = props => {
const { getPrefixCls, direction } = React.useContext(ConfigContext);
const innerStyle = {
...(hasCustomMarginLeft && { marginLeft: orientationMargin }),
...(hasCustomMarginRight && { marginRight: orientationMargin }),
};
const {
prefixCls: customizePrefixCls,
type = 'horizontal',
orientation = 'center',
orientationMargin,
className,
children,
dashed,
plain,
...restProps
} = props;
const prefixCls = getPrefixCls('divider', customizePrefixCls);
const [wrapSSR, hashId] = useStyle(prefixCls);
return (
<div className={classString} {...restProps} role="separator">
{children && (
<span className={`${prefixCls}-inner-text`} style={innerStyle}>
{children}
</span>
)}
</div>
);
}}
</ConfigConsumer>
);
const orientationPrefix = orientation.length > 0 ? `-${orientation}` : orientation;
const hasChildren = !!children;
const hasCustomMarginLeft = orientation === 'left' && orientationMargin != null;
const hasCustomMarginRight = orientation === 'right' && orientationMargin != null;
const classString = classNames(
prefixCls,
hashId,
`${prefixCls}-${type}`,
{
[`${prefixCls}-with-text`]: hasChildren,
[`${prefixCls}-with-text${orientationPrefix}`]: hasChildren,
[`${prefixCls}-dashed`]: !!dashed,
[`${prefixCls}-plain`]: !!plain,
[`${prefixCls}-rtl`]: direction === 'rtl',
[`${prefixCls}-no-default-orientation-margin-left`]: hasCustomMarginLeft,
[`${prefixCls}-no-default-orientation-margin-right`]: hasCustomMarginRight,
},
className,
);
const innerStyle = {
...(hasCustomMarginLeft && { marginLeft: orientationMargin }),
...(hasCustomMarginRight && { marginRight: orientationMargin }),
};
return wrapSSR(
<div className={classString} {...restProps} role="separator">
{children && (
<span className={`${prefixCls}-inner-text`} style={innerStyle}>
{children}
</span>
)}
</div>,
);
};
export default Divider;

View File

@ -1,137 +1,137 @@
@import '../../style/themes/index';
@import '../../style/mixins/index';
// @import '../../style/themes/index';
// @import '../../style/mixins/index';
@divider-prefix-cls: ~'@{ant-prefix}-divider';
// @divider-prefix-cls: ~'@{ant-prefix}-divider';
.@{divider-prefix-cls} {
.reset-component();
// .@{divider-prefix-cls} {
// .reset-component();
border-top: @border-width-base solid @divider-color;
// border-top: @border-width-base solid @divider-color;
&-vertical {
position: relative;
top: -0.06em;
display: inline-block;
height: 0.9em;
margin: 0 8px;
vertical-align: middle;
border-top: 0;
border-left: @border-width-base solid @divider-color;
}
// &-vertical {
// position: relative;
// top: -0.06em;
// display: inline-block;
// height: 0.9em;
// margin: 0 @divider-vertical-gutter;
// vertical-align: middle;
// border-top: 0;
// border-left: @border-width-base solid @divider-color;
// }
&-horizontal {
display: flex;
clear: both;
width: 100%;
min-width: 100%; // Fix https://github.com/ant-design/ant-design/issues/10914
margin: 24px 0;
}
// &-horizontal {
// display: flex;
// clear: both;
// width: 100%;
// min-width: 100%; // Fix https://github.com/ant-design/ant-design/issues/10914
// margin: 24px 0;
// }
&-horizontal&-with-text {
display: flex;
margin: 16px 0;
color: @heading-color;
font-weight: 500;
font-size: @font-size-lg;
white-space: nowrap;
text-align: center;
border-top: 0;
border-top-color: @divider-color;
// &-horizontal&-with-text {
// display: flex;
// margin: 16px 0;
// color: @heading-color;
// font-weight: 500;
// font-size: @font-size-lg;
// white-space: nowrap;
// text-align: center;
// border-top: 0;
// border-top-color: @divider-color;
&::before,
&::after {
position: relative;
top: 50%;
width: 50%;
border-top: @border-width-base solid transparent;
// Chrome not accept `inherit` in `border-top`
border-top-color: inherit;
border-bottom: 0;
transform: translateY(50%);
content: '';
}
}
// &::before,
// &::after {
// position: relative;
// top: 50%;
// width: 50%;
// border-top: @border-width-base solid transparent;
// // Chrome not accept `inherit` in `border-top`
// border-top-color: inherit;
// border-bottom: 0;
// transform: translateY(50%);
// content: '';
// }
// }
&-horizontal&-with-text-left {
&::before {
top: 50%;
width: @divider-orientation-margin;
}
// &-horizontal&-with-text-left {
// &::before {
// top: 50%;
// width: @divider-orientation-margin;
// }
&::after {
top: 50%;
width: 100% - @divider-orientation-margin;
}
}
// &::after {
// top: 50%;
// width: 100% - @divider-orientation-margin;
// }
// }
&-horizontal&-with-text-right {
&::before {
top: 50%;
width: 100% - @divider-orientation-margin;
}
// &-horizontal&-with-text-right {
// &::before {
// top: 50%;
// width: 100% - @divider-orientation-margin;
// }
&::after {
top: 50%;
width: @divider-orientation-margin;
}
}
// &::after {
// top: 50%;
// width: @divider-orientation-margin;
// }
// }
&-inner-text {
display: inline-block;
padding: 0 @divider-text-padding;
}
// &-inner-text {
// display: inline-block;
// padding: 0 @divider-text-padding;
// }
&-dashed {
background: none;
border-color: @divider-color;
border-style: dashed;
border-width: @border-width-base 0 0;
}
// &-dashed {
// background: none;
// border-color: @divider-color;
// border-style: dashed;
// border-width: @border-width-base 0 0;
// }
&-horizontal&-with-text&-dashed {
&::before,
&::after {
border-style: dashed none none;
}
}
// &-horizontal&-with-text&-dashed {
// &::before,
// &::after {
// border-style: dashed none none;
// }
// }
&-vertical&-dashed {
border-width: 0 0 0 @border-width-base;
}
// &-vertical&-dashed {
// border-width: 0 0 0 @border-width-base;
// }
&-plain&-with-text {
color: @text-color;
font-weight: normal;
font-size: @font-size-base;
}
// &-plain&-with-text {
// color: @text-color;
// font-weight: normal;
// font-size: @font-size-base;
// }
&-horizontal&-with-text-left&-no-default-orientation-margin-left {
&::before {
width: 0;
}
// &-horizontal&-with-text-left&-no-default-orientation-margin-left {
// &::before {
// width: 0;
// }
&::after {
width: 100%;
}
// &::after {
// width: 100%;
// }
.ant-divider-inner-text {
padding-left: 0;
}
}
// .ant-divider-inner-text {
// padding-left: 0;
// }
// }
&-horizontal&-with-text-right&-no-default-orientation-margin-right {
&::before {
width: 100%;
}
// &-horizontal&-with-text-right&-no-default-orientation-margin-right {
// &::before {
// width: 100%;
// }
&::after {
width: 0;
}
// &::after {
// width: 0;
// }
.ant-divider-inner-text {
padding-right: 0;
}
}
}
// .ant-divider-inner-text {
// padding-right: 0;
// }
// }
// }
@import './rtl';
// @import './rtl';

View File

@ -1,2 +1,195 @@
import '../../style/index.less';
import './index.less';
// deps-lint-skip-all
import { CSSObject } from '@ant-design/cssinjs';
import {
DerivativeToken,
useStyleRegister,
useToken,
UseComponentStyleResult,
resetComponent,
GenerateStyle,
} from '../../_util/theme';
interface DividerToken extends DerivativeToken {
dividerCls: string;
dividerBorderColor: string;
dividerBorderWidth: number;
dividerNotDefaultTextPadding: number;
dividerVerticalGutterMargin: number;
dividerHorizontalWithTextGutterMargin: number;
dividerHorizontalGutterMargin: number;
}
// ============================== Shared ==============================
const genSharedDividerStyle: GenerateStyle<DividerToken> = (token): CSSObject => {
const { dividerCls } = token;
return {
[dividerCls]: {
...resetComponent(token),
borderBlockStart: `${token.dividerBorderWidth}px solid ${token.dividerBorderColor}`,
// vertical
'&-vertical': {
position: 'relative',
top: '-0.06em',
display: 'inline-block',
height: '0.9em',
margin: `0 ${token.dividerVerticalGutterMargin}px`,
verticalAlign: 'middle',
borderTop: 0,
borderInlineStart: `${token.dividerBorderWidth}px solid ${token.dividerBorderColor}`,
},
'&-horizontal': {
display: 'flex',
clear: 'both',
width: '100%',
minWidth: '100%', // Fix https://github.com/ant-design/ant-design/issues/10914
margin: `${token.dividerHorizontalGutterMargin}px 0`,
},
'&-horizontal&-with-text': {
display: 'flex',
margin: `${token.dividerHorizontalWithTextGutterMargin}px 0`,
color: token.headingColor,
fontWeight: 500,
fontSize: token.fontSizeLG,
whiteSpace: 'nowrap',
textAlign: 'center',
borderBlockStart: `0 ${token.dividerBorderColor}`,
'&::before, &::after': {
position: 'relative',
top: '50%',
width: '50%',
borderBlockStart: `${token.dividerBorderWidth}px solid transparent`,
// Chrome not accept `inherit` in `border-top`
borderBlockStartColor: 'inherit',
borderBlockEnd: 0,
transform: 'translateY(50%)',
content: "''",
},
},
'&-horizontal&-with-text-left': {
'&::before': {
top: '50%',
width: '5%',
},
'&::after': {
top: '50%',
width: '95%',
},
},
'&-horizontal&-with-text-right': {
'&::before': {
top: '50%',
width: '95%',
},
'&::after': {
top: '50%',
width: '5%',
},
},
[`${dividerCls}-inner-text`]: {
display: 'inline-block',
padding: '0 1em',
},
'&-dashed': {
background: 'none',
borderColor: token.dividerBorderColor,
borderStyle: 'dashed',
borderWidth: 0,
borderBlockStart: `${token.dividerBorderWidth}px`,
},
'&-horizontal&-with-text&-dashed': {
'&::before, &::after': {
borderStyle: 'dashed none none',
},
},
'&-vertical&-dashed': {
borderWidth: `0 0 0 ${token.dividerBorderWidth}px`,
},
'&-plain&-with-text': {
color: token.textColor,
fontWeight: 'normal',
fontSize: token.fontSize,
},
'&-horizontal&-with-text-left&-no-default-orientation-margin-left': {
'&::before': {
width: 0,
},
'&::after': {
width: '100%',
},
'.ant-divider-inner-text': {
paddingInlineStart: `${token.dividerNotDefaultTextPadding}px`,
},
},
'&-horizontal&-with-text-right&-no-default-orientation-margin-right': {
'&::before': {
width: '100%',
},
'&::after': {
width: 0,
},
'.ant-divider-inner-text': {
paddingInlineEnd: `${token.dividerNotDefaultTextPadding}px`,
},
},
},
};
};
// ============================== Export ==============================
export default function useStyle(prefixCls: string): UseComponentStyleResult {
const [theme, token, hashId] = useToken();
// FIXME
const dividerBorderColor = 'rgba(0, 0, 0, 6%)';
const dividerBorderWidth = token.borderWidth;
const dividerNotDefaultTextPadding = 0;
const dividerVerticalGutterMargin = token.marginXS;
const dividerHorizontalWithTextGutterMargin = token.margin;
const dividerHorizontalGutterMargin = token.marginLG;
const dividerToken: DividerToken = {
...token,
dividerCls: `.${prefixCls}`,
dividerBorderColor,
dividerBorderWidth,
dividerNotDefaultTextPadding,
dividerVerticalGutterMargin,
dividerHorizontalWithTextGutterMargin,
dividerHorizontalGutterMargin,
};
return [
useStyleRegister({ theme, token, hashId, path: [prefixCls] }, () => [
genSharedDividerStyle(dividerToken),
]),
hashId,
];
}

View File

@ -134,8 +134,8 @@
margin: 0;
color: @heading-color;
font-weight: 500;
font-size: @font-size-lg;
line-height: 22px;
font-size: @drawer-title-font-size;
line-height: @drawer-title-line-height;
}
&-content {

View File

@ -15,7 +15,7 @@ Array [
type="button"
>
<span>
bottomCenter
bottom
</span>
</button>,
<button
@ -40,7 +40,61 @@ Array [
type="button"
>
<span>
topCenter
top
</span>
</button>,
<button
class="ant-btn ant-btn-default ant-dropdown-trigger"
type="button"
>
<span>
topRight
</span>
</button>,
]
`;
exports[`renders ./components/dropdown/demo/arrow-center.md correctly 1`] = `
Array [
<button
class="ant-btn ant-btn-default ant-dropdown-trigger"
type="button"
>
<span>
bottomLeft
</span>
</button>,
<button
class="ant-btn ant-btn-default ant-dropdown-trigger"
type="button"
>
<span>
bottom
</span>
</button>,
<button
class="ant-btn ant-btn-default ant-dropdown-trigger"
type="button"
>
<span>
bottomRight
</span>
</button>,
<br />,
<button
class="ant-btn ant-btn-default ant-dropdown-trigger"
type="button"
>
<span>
topLeft
</span>
</button>,
<button
class="ant-btn ant-btn-default ant-dropdown-trigger"
type="button"
>
<span>
top
</span>
</button>,
<button
@ -658,7 +712,7 @@ exports[`renders ./components/dropdown/demo/placement.md correctly 1`] = `
type="button"
>
<span>
bottomCenter
bottom
</span>
</button>
</div>
@ -706,7 +760,7 @@ exports[`renders ./components/dropdown/demo/placement.md correctly 1`] = `
type="button"
>
<span>
topCenter
top
</span>
</button>
</div>

Some files were not shown because too many files have changed in this diff Show More