Compare commits

..

25 Commits

Author SHA1 Message Date
Wei Zhu
eac43ed892 Update release date 2018-04-28 11:06:01 +08:00
Wei Zhu
f5250cabe1 Revert "Bump 2.13.15"
This reverts commit 9c78bfaee4.
2018-04-28 11:05:28 +08:00
Wei Zhu
125085398c Remoove arguments, fix lint 2018-04-28 11:04:10 +08:00
Wei Zhu
9c78bfaee4 Bump 2.13.15 2018-04-01 21:23:48 +08:00
Wei Zhu
35d11d1cb1 docs: Add release date 2018-04-01 21:20:04 +08:00
ojab
992c8d1562 Release 2.13.14 2018-03-27 08:08:57 +00:00
ojab
a3fc715d3a Compat less 3 (#9633)
Fix #7850
2018-03-12 16:08:14 +08:00
愚道
03da3efae7 fix pub script error 2018-03-09 19:23:21 +08:00
愚指导
97d3422043 release 2.13.13 (#9590) 2018-03-09 18:04:06 +08:00
Wei Zhu
a609518d28 Remove unsed variable 2018-03-08 17:52:58 +08:00
Wei Zhu
ef6d7a5d90 Fix upload list update logic when beforeUpload return false
Porting from 626ebf2063
2018-03-08 17:43:24 +08:00
Wei Zhu
5be1bad67f Update snapshot 2018-03-08 14:24:37 +08:00
Wei Zhu
b70fede5de correct grammar 2018-02-26 20:36:53 +08:00
Wei Zhu
358aa84fef Bump 2.13.12 2018-02-26 20:01:00 +08:00
Wei Zhu
6f716d1003 Add 2.13.12 change log 2018-02-26 20:00:36 +08:00
Wei Zhu
739c6ead54 Lock @types/react and @types/react-dom version 2018-02-26 19:46:55 +08:00
Wei Zhu
a7255ee885 Fix #8885 2018-02-26 19:21:24 +08:00
junjing.zhang
06ff0d44db fix lint fail - use babel-eslint-8.0.2 2018-01-03 19:06:37 +08:00
junjing.zhang
e96b0fc768 fix components/collapse test fail 2018-01-03 19:06:37 +08:00
junjing.zhang
8489fedb4c anchor scroll supports complete href link - test
anchor scroll supports complete href link, e.g. http://www.example.com/#id, not just #id
2018-01-03 19:06:37 +08:00
junjing.zhang
adf98d3c19 anchor scroll supports complete href link
anchor scroll supports complete href link, e.g. http://www.example.com/#id, not just #id
2018-01-03 19:06:37 +08:00
afc163
ba87b45903 Fix snapshot for moment@2.20 2017-12-17 18:57:10 +08:00
afc163
1e8830637f Fix submenu popup issue when specified unexisted defaultOpenKeys
close #8475
2017-12-17 18:54:31 +08:00
nikogu
2275195e15 bump 2.13.11 2017-12-02 17:47:19 +08:00
niko
dcf9d4c294 2.13.11 changelog (#8403) 2017-12-02 17:38:41 +08:00
1149 changed files with 23472 additions and 251508 deletions

10
.babelrc Normal file
View File

@@ -0,0 +1,10 @@
{
"env": {
"test": {
"presets": ["es2015", "react", "stage-0"],
"plugins": [
"add-module-exports"
]
}
}
}

View File

@@ -1,236 +0,0 @@
version: 2
references:
container_config: &container_config
docker:
- image: circleci/node:8
working_directory: ~/ant-design
attach_workspace: &attach_workspace
attach_workspace:
at: ~/ant-design
install_react: &install_react
run: REACT=15 ./scripts/install-react.sh
react_15: &react_15
environment:
REACT: 15
react_16: &react_16
environment:
REACT: 16
workflow: &workflow
jobs:
- setup:
filters:
branches:
ignore: gh-pages
- dist:
requires:
- setup
- compile:
requires:
- setup
- lint:
requires:
- setup
- test_dist:
requires:
- dist
- test_lib:
requires:
- compile
- test_es:
requires:
- compile
- test_dom:
requires:
- setup
- test_node:
requires:
- setup
- test_dist_15:
requires:
- dist
- test_lib_15:
requires:
- compile
- test_es_15:
requires:
- compile
- test_dom_15:
requires:
- setup
- test_node_15:
requires:
- setup
jobs:
setup:
<<: *container_config
steps:
- checkout
- run: node -v
- run: npm -v
- run: npm install
- run:
command: |
set +eo
npm ls
true
- persist_to_workspace:
root: ~/ant-design
paths:
- node_modules
dist:
<<: *container_config
steps:
- checkout
- *attach_workspace
- run: npm run dist
- run: node ./tests/dekko/dist.test.js
- persist_to_workspace:
root: ~/ant-design
paths:
- dist
compile:
<<: *container_config
steps:
- checkout
- *attach_workspace
- run: npm run compile
- run: node ./tests/dekko/lib.test.js
- persist_to_workspace:
root: ~/ant-design
paths:
- lib
- es
lint:
<<: *container_config
steps:
- checkout
- *attach_workspace
- run: npm run lint
test_dist:
<<: *container_config
<<: *react_16
steps:
- checkout
- *attach_workspace
- run:
command: npm test -- -w 1
environment:
LIB_DIR: dist
test_lib:
<<: *container_config
<<: *react_16
steps:
- checkout
- *attach_workspace
- run:
command: npm test -- -w 1
environment:
LIB_DIR: lib
test_es:
<<: *container_config
<<: *react_16
steps:
- checkout
- *attach_workspace
- run:
command: npm test -- -w 1
environment:
LIB_DIR: es
test_dom:
<<: *container_config
<<: *react_16
steps:
- checkout
- *attach_workspace
- run: npm test -- -w 1 --coverage
- run: bash <(curl -s https://codecov.io/bash)
test_node:
<<: *container_config
<<: *react_16
steps:
- checkout
- *attach_workspace
- run: npm run test-node -- -w 1
test_dist_15:
<<: *container_config
<<: *react_15
steps:
- checkout
- *attach_workspace
- *install_react
- run:
command: npm test -- -w 1 -u
environment:
LIB_DIR: dist
test_lib_15:
<<: *container_config
<<: *react_15
steps:
- checkout
- *attach_workspace
- *install_react
- run:
command: npm test -- -w 1 -u
environment:
LIB_DIR: lib
test_es_15:
<<: *container_config
<<: *react_15
steps:
- checkout
- *attach_workspace
- *install_react
- run:
command: npm test -- -w 1 -u
environment:
LIB_DIR: es
test_dom_15:
<<: *container_config
<<: *react_15
steps:
- checkout
- *attach_workspace
- *install_react
- run: npm test -- -w 1 -u
test_node_15:
<<: *container_config
<<: *react_15
steps:
- checkout
- *attach_workspace
- *install_react
- run: npm run test-node -- -w 1 -u
workflows:
version: 2
build_test:
<<: *workflow
nightly:
<<: *workflow
triggers:
- schedule:
cron: "0 0 * * *"
filters:
branches:
only:
- master

View File

@@ -1,10 +1,6 @@
components/**/*.js
components/**/*.jsx
components/*/__tests__/type.tsx
!.eslintrc.js
!components/*/__tests__/**/*.js
!components/*/demo/*
!.*.js
# Docs templates
site/theme/template/IconDisplay/*.js
site/theme/template/IconDisplay/*.jsx

View File

@@ -8,6 +8,13 @@ const eslintrc = {
es6: true,
},
parser: 'babel-eslint',
parserOptions: {
ecmaVersion: 6,
ecmaFeatures: {
jsx: true,
experimentalObjectRestSpread: true,
},
},
plugins: [
'markdown',
'react',
@@ -19,8 +26,6 @@ const eslintrc = {
'react/sort-comp': 0,
'react/prop-types': 0,
'react/jsx-first-prop-new-line': 0,
'react/jsx-one-expression-per-line': 0,
'react/forbid-prop-types': 0,
'react/jsx-filename-extension': [1, { extensions: ['.js', '.jsx', '.md'] }],
'import/extensions': 0,
'import/no-unresolved': 0,
@@ -56,12 +61,8 @@ if (process.env.RUN_ENV === 'DEMO') {
'no-console': 0,
'no-plusplus': 0,
'eol-last': 0,
'no-script-url': 0,
'prefer-rest-params': 0,
'react/no-access-state-in-setstate': 0,
'react/destructuring-assignment': 0,
'react/no-multi-comp': 0,
'react/prefer-stateless-function': 0,
'jsx-a11y/href-no-hash': 0,
'import/newline-after-import': 0,
});

View File

@@ -1,13 +1,58 @@
# Contributing to Ant Design
Want to contribute to Ant Design? There are a few things you need to know.
The following is a set of guidelines for contributing to Ant Design. Please spend several minutes in reading these guidelines before you create an issue or pull request.
We wrote a **[contribution guide](https://ant.design/docs/react/contributing)** to help you get started.
Anyway, these are just guidelines, not rules, use your best judgment and feel free to propose changes to this document in a pull request.
---
# 参与共建
## Do your homework before asking a question
想要给 Ant Design 贡献自己的一份力量?
It's a great idea to read Eric Steven Raymond's [How To Ask Questions The Smart Way](http://www.catb.org/esr/faqs/smart-questions.html) twice before asking a question. But if you are busy now, I recommend to read [Don't post homework questions](http://www.catb.org/esr/faqs/smart-questions.html#homework) first.
我们写了一份 **[贡献指南](https://ant.design/docs/react/contributing-cn)** 来帮助你开始。
The following guidelines are about *How to avoid Homework Questions*.
### 1. Read the documentation.
It sad but true that someone just glance(not read) [Ant Design's documentation](http://ant.design/). Please read the documentation closely. What's more, you can modify and run our demo with [CodePen](http://codepen.io/benjycui/pen/KgPZrE?editors=001). It's helpful to understand our documentation.
Tips: choose the corresponding documentation with versions selector which in the bottom-right corner.
### 2. Make sure that your question is about Ant Design, not React
Someone may think all of the questions that he/she meets in developing are about Ant Design, but it's not true. So, please read [React's documentation](http://facebook.github.io/react/docs/getting-started.html) or just Google (not Baidu, seriously) your questions with keyword *React* first. If you are sure that your question is about Ant Design, go ahead.
### 3. Read the FAQ and search the issues list of Ant Design
Your questions may be asked and solved by others. So please spend several minutes on searching. Remember [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself), both code and questions.
P.S.
1. [FAQ](https://github.com/ant-design/ant-design/wiki/FAQ)
1. [Issues list](https://github.com/ant-design/ant-design/issues)
## Close your issue if it's solved
It is a good habit which will save maintainers' time. Thank you!
## Providing a demo while reporting a bug
It would be helpful to provide a demo which can re-produce the bug 100%. Please fork this [CodePen](http://codepen.io/benjycui/pen/KgPZrE?editors=001) and re-produce the bug you met. Then, create an issue. The most important thing is: double check before claiming that you have found a bug.
## Tips about Feature Request
If you believe that Ant Design should provide some features, but it does not. You could create an issue to discuss. However, Ant Design is not Swiss Army Knife, there are some features which Ant Design will not support:
1. Request or operate data
## Tips about Pull Request
**Working on your first Pull Request?** You can learn how from this *free* series [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github)
It's welcomed to pull request. And there are some tips about that:
1. It is a good habit to create a feature request issue to discuss whether the feature is necessary before you implement it. However, it's unnecessary to create an issue to claim that you found a typo or improved the readability of documentation, just create a pull request.
1. Run `npm run lint` and fix those errors before committing in order to keep consistent code style.
1. Rebase before creating a PR to keep commit history clear.
1. Add some descriptions and refer relative issues for you PR.

View File

@@ -2,7 +2,7 @@ First of all, thank you for your contribution! :-)
Please makes sure that these checkboxes are checked before submitting your PR, thank you!
* [ ] Make sure that you propose PR to right branch: bugfix for `master`, feature for branch `feature`.
* [ ] Make sure that you propose PR to right branch: bugfix for `master`, feature for latest active branch `feature-x.x`.
* [ ] Make sure that you follow antd's [code convention](https://github.com/ant-design/ant-design/wiki/Code-convention-for-antd).
* [ ] Run `npm run lint` and fix those errors before submitting in order to keep consistent code style.
* [ ] Rebase before creating a PR to keep commit history clear.

14
.github/lock.yml vendored
View File

@@ -1,14 +0,0 @@
# Configuration for lock-threads - https://github.com/dessant/lock-threads
# Number of days of inactivity before a closed issue or pull request is locked
daysUntilLock: 365
# Comment to post before locking. Set to `false` to disable
lockComment: >
This thread has been automatically locked because it has not had recent
activity. Please open a new issue for related bugs and link to relevant
comments in this thread.
# Issues or pull requests with these labels will not be locked
# exemptLabels:
# - no-locking
# Limit to only `issues` or `pulls`
only: issues

5
.gitignore vendored
View File

@@ -38,8 +38,3 @@ components/**/*.jsx
!components/**/__tests__/*.js
!components/**/__tests__/*.js.snap
/.history
# Docs templates
site/theme/template/IconDisplay/*.js
site/theme/template/IconDisplay/*.jsx
site/theme/template/IconDisplay/fields.js

View File

@@ -2,11 +2,10 @@ const libDir = process.env.LIB_DIR;
const transformIgnorePatterns = [
'/dist/',
'node_modules/[^/]+?/(?!(es|node_modules)/)', // Ignore modules without es dir
'node_modules\/[^/]+?\/(?!(es|node_modules)\/)', // Ignore modules without es dir
];
module.exports = {
verbose: true,
setupFiles: [
'./tests/setup.js',
],
@@ -38,16 +37,9 @@ module.exports = {
'!components/style/index.tsx',
'!components/*/locale/index.tsx',
'!components/*/__tests__/**/type.tsx',
'!components/**/*/interface.{ts,tsx}',
],
transformIgnorePatterns,
snapshotSerializers: [
'enzyme-to-json/serializer',
],
globals: {
'ts-jest': {
tsConfigFile: './tsconfig.test.json',
},
},
testURL: 'http://localhost',
};

View File

@@ -16,11 +16,6 @@ module.exports = {
testRegex: 'demo\\.test\\.js$',
testEnvironment: 'node',
snapshotSerializers: [
'enzyme-to-json/serializer',
'enzyme-to-json/serializer'
],
globals: {
'ts-jest': {
tsConfigFile: './tsconfig.test.json',
},
},
};

View File

@@ -16,8 +16,6 @@
"selector-list-comma-newline-after": null,
"selector-pseudo-element-colon-notation": null,
"unit-no-unknown": null,
"value-list-max-empty-lines": null,
"font-family-no-missing-generic-family-keyword": null,
"no-descending-specificity": null
"value-list-max-empty-lines": null
}
}

View File

@@ -5,27 +5,33 @@ language: node_js
node_js:
- 8
cache:
directories:
- $HOME/.npm
matrix:
fast_finish: true
include:
- env: TEST_TYPE=lint
- env: REACT=16 TEST_TYPE=test:dist
- env: REACT=16 TEST_TYPE=test:lib
- env: REACT=16 TEST_TYPE=test:es
- env: REACT=16 TEST_TYPE=test:dom
- env: REACT=16 TEST_TYPE=test:node
- env: REACT=15 TEST_TYPE=test:dist
- env: REACT=15 TEST_TYPE=test:lib
- env: REACT=15 TEST_TYPE=test:es
- env: REACT=15 TEST_TYPE=test:dom
- env: REACT=15 TEST_TYPE=test:node
before_script:
- scripts/install-react.sh
env:
matrix:
- TEST_TYPE=lint
- TEST_TYPE=test:dist
- TEST_TYPE=test:lib
- TEST_TYPE=test:es
- TEST_TYPE=test:dom
- TEST_TYPE=test:node
script:
- scripts/travis-script.sh
- |
if [ "$TEST_TYPE" = lint ]; then
npm run lint
elif [ "$TEST_TYPE" = test:dist ]; then
npm run dist && \
node ./tests/dekko/dist.test.js && \
LIB_DIR=dist npm test -- -w 2
elif [ "$TEST_TYPE" = test:lib ]; then
npm run compile && \
node ./tests/dekko/lib.test.js && \
LIB_DIR=lib npm test -- -w 2
elif [ "$TEST_TYPE" = test:es ]; then
npm run compile && \
LIB_DIR=es npm test -- -w 2
elif [ "$TEST_TYPE" = test:dom ]; then
npm test -- --coverage -w 2 && \
bash <(curl -s https://codecov.io/bash)
elif [ "$TEST_TYPE" = test:node ]; then
npm run test-node -- -w 2
fi

View File

@@ -1,26 +1,18 @@
778758944 <778758944@qq.com>
Aaron Planell López <aaronplanell@gmail.com>
Adrian Dimitrov <dimitrov.adrian@gmail.com>
Albert Zheng <lisong.zheng@gmail.com>
Albert 理斯特 <shuaizhexu@gmail.com>
Aleck Landgraf <aleck.landgraf@gmail.com>
Alexander <labriko@yandex.ru>
Alexander Suevalov <suevalov.work@gmail.com>
Alexandre Kirszenberg <a.kirszenberg@gmail.com>
Amorites <751809522@qq.com>
Anas Tawfeek <anas.tawfeek@outlook.com>
Andrew Murray <radarhere@gmail.com>
Andrey G <plandem@gmail.com>
Arnab Sen <arnabsen@gmail.com>
Arthur Denner Oliveira Santos <arthurdenner7@gmail.com>
Arvin Xu <arvinx@foxmail.com>
BK Heleth <bon.hoo@hotmail.com>
Babajide Fowotade <jide.b.tade@gmail.com>
Bartek <bartek.kozera@gmail.com>
Benjamin Kniffler <bkniffler@me.com>
Benjy Cui <benjytrys@gmail.com>
Bernie <bernie.wangbj@gmail.com>
Bilal Sirazitdinov <bilalsir@yandex.ru>
Bill Sheikh <bilawals22@gmail.com>
Bo Chen <bochen2014@yahoo.com>
Bozhao <yubz86@gmail.com>
@@ -28,188 +20,113 @@ Brett Lamy <bel423@me.com>
Brook Shi <iwxiaot@gmail.com>
Bruce Mitchener <bruce.mitchener@gmail.com>
Bruno Maia <bruno.mm.maia@gmail.com>
Bryan Berger <bb@ga.co>
C <4019980@qq.com>
Cam Song <neosoyn@gmail.com>
Camol <kwwnjujlc@sina.com>
Canwen Xu <canwenxu@126.com>
Catalin Miron <mironcatalin@gmail.com>
Cee Cirno <i@cee.moe>
Chang Wang <cheapsteak@gmail.com>
Chikara Chan <chenhongtu@51xianqu.net>
Chris Kelly <cjke.7777@gmail.com>
ChrisFan <chris.fan.dev@gmail.com>
Christopher Deutsch <cd@cdeutsch.com>
Chuang Yu <cyu9960@gmail.com>
Claudio Restifo <claudio.restifo@gmail.com>
Cody Chan <int64ago@gmail.com>
Colton Pierson <colton@coltonpierson.com>
Confiks <confiks@scriptbase.org>
Conway Anderson <hello@conwayanderson.com>
Cordaro <elvis07@163.com>
D & R <jdz321@qq.com>
Damian Green <damian.green@microlease.com>
Dan Minshew <ofenixculpa@gmail.com>
Dane David <dndavid102@gmail.com>
Danny Hoower Antonio Viasus Avila <danjavia@gmail.com>
Daqi Song <dqaria@gmail.com>
Darren Poon <dyhpoon@gmail.com>
David Schneider <davschne@gmail.com>
DengYun <tdzl2003@gmail.com>
Dimitri Mitropoulos <dimitrimitropoulos@gmail.com>
Dmitry Bolotin <bolotin.dmitriy@gmail.com>
Dorian <dorian@doma.io>
DosLin <doslino@gmail.com>
EcmaProSrc.P/ka <asoiso@foxmail.com>
Edd Hannay <accounts@edd.fm>
Eddie Xie <oeddyo@gmail.com>
Eden Wang <Eden.Wang@Akmii.com>
Eden Wang <yociduo@vip.qq.com>
Egor Yurtaev <yurtaev.egor@gmail.com>
Eli White <github@eli-white.com>
Emerson Laurentino <emersonlaurentino@hotmail.com>
Emma <sima.zhang1990@gmail.com>
Eric <84263800@qq.com>
Erwann Mest <m+github@kud.io>
Evgeny Kuznetsov <jackk@ya.ru>
Eward Song <eward.song@gmail.com>
Flynn <li.fulin@foxmail.com>
For177 <mengqiang.q@gmail.com>
Geoff Holden <geoff@brightloudnoise.com>
George Gray <george@ummodesign.com>
Graeme Yeates <gyeates@clearpath.ai>
Graeme Yeates <yeatesgraeme@gmail.com>
Gray Choi <gray.choi.1988@gmail.com>
Guan Hao <raptium@gmail.com>
Guan Yu Pan (Jacky) <jackypan1989@gmail.com>
HJin.me <hjin.me@gmail.com>
Hai Phan Nguyen <pnghai@gmail.com>
Haibin Yu <haibin.yu@oceanwing.com>
Hanai <ihanai1991@gmail.com>
Haroen Viaene <fingebimus@me.com>
Harshit Mehrotra <harshitmehrotra@hotmail.com>
Henri Normak <henri.normak@gmail.com>
Hubert Argasinski <argasinski.hubert@gmail.com>
Hughen <446370503@163.com>
Hugo LEHMANN <shogi31@gmail.com>
Igor G <i.gaidai4uk@gmail.com>
ImJoeHs <865439601@qq.com>
Inclined.Z <zjq0717@163.com>
Infinity <305870677@qq.com>
Ivan Kravets <me@ikravets.com>
Ivo Stratev <ivo.stratev.tues@gmail.com>
JaePil Jung <jjp5023@gmail.com>
James <james@schoolshape.com>
Jeffrey Carl Faden <jeffreyatw@gmail.com>
Jerry Bendy <jerry@icewingcc.com>
Jesper We <jesper@journeyman.se>
Jiabin Peng <png.inside@gmail.com>
Jialei <jialeicui@126.com>
Jieraaa <842533841@qq.com>
Jing Ma <mjingm87@qq.com>
Joe <qiaolibo@126.com>
Joe Hsu <jhsu.x1@gmail.com>
John Johnson III <john@johnjohnson.cc>
John Nguyen <jtnguyen236@gmail.com>
Jonatas Walker <jonataswalker@gmail.com>
Jonny Buchanan <jonathan.buchanan@gmail.com>
Jordan Hornblow <jordan@jch254.com>
Josué <ujosuegt@outlook.com>
Juan Rodrigo Venegas Boesch <jrvboesch@gmail.com>
Junyu Zhan <irrigator@yeah.net>
Kaien Liao <liaokaien@gmail.com>
Kenaniah Cerny <kenaniah@gmail.com>
Kenneth Truong <kenneth.e.truong@gmail.com>
Kevin Ivan <info@kevinivan.com>
KgTong <kgtong1992@gmail.com>
Khalifa Lame <khalibloo@gmail.com>
Kiho · Cham <monkindey@163.com>
Kimmo Saari <kimmo.saari@revolt.fi>
Kirill Stiopin <kirill.stiopin@hotmail.com>
Knacktus <knacktus@gmail.com>
Kyle Kelley <rgbkrk@gmail.com>
Kyle Rosenberg <kyle.rosenberg@gmail.com>
Larry Laski <larry.laski@gmail.com>
LeeHarlan <709886167@qq.com>
LeezQ <lizhenq2009@gmail.com>
Leo <clinyong@gmail.com>
Leon Shi <superRaytin@163.com>
Leon Shi <superRaytin@gmail.com>
Liu Yang <zation1@gmail.com>
LongYinan <lynweklm@gmail.com>
Lyndon001 <lld207@126.com>
MG12 <wuzhao.mail@gmail.com>
Ma Tianxiao <matx2215@outlook.com>
Madis Väin <madisvain@gmail.com>
Manjit Kumar <manjit1727@gmail.com>
Manweill <mic.liangwenwei@foxmail.com>
Marco Afonso <mafonso333@gmail.com>
Marcus Bransbury <marcus.bransbury@gmail.com>
Marius Ileana <visvadw@gmail.com>
Mars Wong <marswong618@gmail.com>
Marshall Chen <Juniors.fei@gmail.com>
Martin Litvaj <kamahl19@gmail.com>
Martin Novák <martinnovak@outlook.com>
Mathew <khayaanimations@gmail.com>
Maximilian Meyer <Maximilian.Meyer@br.de>
Meck <yesmeck@gmail.com>
MeiLin <postget.me@gmail.com>
Meow-z <372086270@qq.com>
Miaow <i@zfeng.net>
Michalis Macheras <diodosier@gmail.com>
Min <dicklwm@163.com>
MinJeong Kim <min7859@gmail.com>
Minqi Pan <pmq2001@gmail.com>
Minsung Ryu <ryums0227@gmail.com>
Mitchell Demler <mitchell.demler@harcourts.net>
Mohamed Seada <mohamed.seada.1994@gmail.com>
Mr.Tone <vector@malubei.com>
MuYu <mr.muzea@gmail.com>
Mário Gonçalves <mario.mc.goncalves@gmail.com>
Nathan Griffin <nathan@gatherhere.com>
Nathan Tavares Nascimento <nathan.tnascimento@gmail.com>
Nathan Wells <nwwells@gmail.com>
Neekey <ni184775761@gmail.com>
Nekron <nekron.hyt@gmail.com>
Neverland <chenjiahan@buaa.edu.cn>
Nico <nicolas@freddelacompta.com>
Nidhi Agarwal <nidhi.agarwal@zomato.com>
Nimo <nimo.jser@gmail.com>
Nishant Arora <na.nishantarora@gmail.com>
Nokecy <Nokecy@163.com>
OAwan <georgio.wan@gmail.com>
Oleksandr Kovalchuk <me.olexandr.kovalchuk@gmail.com>
OuYancey <ou.yancey@gmail.com>
Panjie Setiawan Wicaksono <panjie@panjiesw.com>
Patrick Gidich <patrick.gidich@simnova.com>
Patryk <longer44@gmail.com>
Peter <usstpeter@gmail.com>
Peter Berg <atticusberg@gmail.com>
Pierre <pierre@bazoge.com>
Pierre Neter <pierreneter@gmail.com>
Piper Chester <piperchester@gmail.com>
Pixy Yuan <pixy.bupt@gmail.com>
Pyiner <lijiuyang1992@gmail.com>
Pyroboomka <qwaarty@mail.ru>
Qiaosen Huang <joesonw@gmail.com>
Qingrong Ke <keqingrong1992@gmail.com>
Rafael Cosman <rafaelcosman@alumni.stanford.edu>
Ramsés Moreno <ramses@cuatromedios.com>
Randy <randypriv@gmail.com>
RaoHai <surgesoft@gmail.com>
Reed Sun <superreedsun@gmail.com>
Rex <zhangzilong.zzl@163.com>
Ricardo Raphael Joson <rrjoson08@gmail.com>
Richard D. Worth <rdworth@gmail.com>
Robert Wilkinson <wilkinson.robert.a@gmail.com>
Rohan Malhotra <rohan.root@gmail.com>
Rrrandom <emanonhere@gmail.com>
SHEN Lin <shenlin192@gmail.com>
Sakol Assawasagool <koobitor@gmail.com>
Sam Chen <chenxsan@gmail.com>
Sam Maxwell <sam@paybase.io>
Sangle <whb97@163.com>
Sanjay Kumar <kris.gooner@gmail.com>
Sanjay Kumar <sk@tectusdreamlab.com>
Sean Lin <sean@ejoy.com>
Sean Sun <pinggodstudio@gmail.com>
@@ -217,74 +134,39 @@ Sebastian Blade <blade254353074@hotmail.com>
Sergio Crisostomo <sergiosbox@gmail.com>
Shawn Sit <xueqingxiao@gmail.com>
ShiTengFei <shitengfei@goyoo.com>
Shubham Kanodia <shubhamsizzles@gmail.com>
SimaQ <sima.zhang1990@gmail.com>
Spencer <spjy@hawaii.edu>
Stephen Esser <Stephen.Esser@gmail.com>
Tao <magicdawn@qq.com>
Tao Zhang <windse7en@gmail.com>
Taylor Sabell <taylorsabell@gmail.com>
Tengjiao Cai <caitengjiao1987@gmail.com>
Terence <trence320@163.com>
The Rock <zhoguoxin@126.com>
Tino D <ginodeis@gmail.com>
Tom Gao <tom@zoomsoft.cc>
Trotyl Yu <trotyl@qq.com>
Troy Thompson <troynt@gmail.com>
Tyler <chaotyler@gmail.com>
Ubaldo Quintana <blkdr@hotmail.com>
Vadim Macagon <vadim.macagon@gmail.com>
Valentin Vichnal <valentin@vichnal.com>
Vemund Santi <vemund@santi.no>
Vic <709147950@qq.com>
Vemund Santi <veund@santi.no>
Vincent Zhang <vxzhong@qq.com>
ViviaRui <zr1450995198@163.com>
Walter Barbagallo <turbometalskater@gmail.com>
Wang Jun <amos.callmexyz@gmail.com>
Warren Seymour <warren@fountainhead.tech>
Wei Zhu <yesmeck@gmail.com>
Wenchao Hu <zjuhwc@gmail.com>
Will Chen <willchen90@gmail.com>
WingGao <wing.gao@live.com>
Wu Haotian <whtsky@gmail.com>
XBTop1! <xbtop1@gmail.com>
Xie Guanglei <xieguanglei@hotmail.com>
XuMM_12 <owiatsq@sina.cn>
Yang Bin <yangkghjh@gmail.com>
Yasin Uslu <nepjua@gmail.com>
Yiming <ymjrcc@qq.com>
Yogesh <yogeshkumar180592@gmail.com>
YuChao Liang <l.yuch@foxmail.com>
Yunus EŞ <yunus@yunuses.com>
Yury Kozyrev <urakozz@me.com>
Yusuke Ito <novi.mad@gmail.com>
Yuwei Ba <i@xiaoba.me>
YuyingWu <wuyuying1128@gmail.com>
Zack Craig <zack@zack6849.com>
Zap <a124116186@qq.com>
Zhang Zhi <fytriht@gmail.com>
Zheeeng <hi@zheeeng.me>
Ziluo <gyfzzu@gmail.com>
afc163 <afc163@gmail.com>
ahalimkara <ahalimkara@gmail.com>
ascoders <576625322@qq.com>
ashishg-qburst <ashishg@qburst.com>
bang <sqibang@gmail.com>
bang88 <sqibang@gmail.com>
blankzust <450811238@qq.com>
byuanama <byuan@ama.com.au>
bzone <yarnbcoder@gmail.com>
caoyi <caoyi0905@mail.hfut.edu.cn>
cathayandy <wzm_andy@126.com>
chaofeis <408067385@qq.com>
chchen <cc272309126@gmail.com>
chencheng (云谦) <sorrycc@gmail.com>
chencheng <sorrycc@gmail.com>
cjahv <cjahv@qq.com>
clinyong <clinyong@gmail.com>
corneyl <cornieljoosse@gmail.com>
ddcat1115 <ddcat1115@gmail.com>
delesseps <andrewlessels@gmail.com>
denzw <denzw@21cn.com>
detailyang <detailyang@gmail.com>
devqin <devqin@gmail.com>
@@ -299,67 +181,40 @@ feng zhi hao <fzhihao@outlook.com>
fengmk2 <m@fengmk2.com>
flashback313 <windmark2012@gmail.com>
genie <genie88@163.com>
gregahren <grega.hren@gmail.com>
handycode <lihandi@gmail.com>
hansnow <hansnow2012@gmail.com>
haoxin <coderhaoxin@outlook.com>
hardfist <yangjianzju@gmail.com>
hauwa123 <hauwa.aminu@outlook.com>
henryv0 <henryvo94@gmail.com>
hi-caicai <hi@cai-cai.me>
huzzbuzz <huzzbuzz@outlook.com>
iamcastelli <sowed@cyberdude.com>
imosapatryk <imosa.patryk@gmail.com>
infeng <fzhihao@outlook.com>
int2d <int2d@qq.com>
ioldfish <fish.wangl@gmail.com>
jasonslyvia <jasonslyvia@gmail.com>
jasonxia23 <xia.jason23@gmail.com>
jiang <155259966@qq.com>
jim <wasd2144@hotmail.com>
jinouwuque <ee2win@gmail.com>
junjing.zhang <zhangjunjing@gmail.com>
kacjay <45483388@qq.com>
kagawagao <kingsongao1221@gmail.com>
kaifei <150641329@qq.com>
kasinooya <kasinooya@gmail.com>
kayw <kayw@outlook.com>
kdenz <ksnz93@gmail.com>
kdepp <kdepp.cd@gmail.com>
keng <keng@renderinghouse.com>
keqingrong <keqingrong1992@gmail.com>
ko <git@yaksok.net>
konakona <lovekonakona@gmail.com>
kossel <lis.yichao@gmail.com>
kuang <p2227@hotmail.com>
kun sam <kunsam624@icloud.com>
leadream <857098475@qq.com>
lehug <zcszuo5811@126.com>
leon.shi <superRaytin@163.com>
lgmcolin <gengmin.lgm@gmail.com>
lgmcolin <lgmcolin@gmail.com>
liangfei <njliangfei@gmail.com>
liekkas <zjq0717@163.com>
lihqi <455711093@qq.com>
lixiaochou077 <qi.liqi07@gmail.com>
lixiaoyang <lixiaoyang2345@gmail.com>
lixiaoyang1992 <lixiaoyang2345@gmail.com>
lizhaocai <lzc09008@gmail.com>
lizhen <lizhen@youzan.com>
loganpowell <loganp@tepper.cmu.edu>
luyiming <luyimingchn@gmail.com>
lvren <luren6049@qq.com>
mArker <252133226@qq.com>
memoryza <jincai.wang@foxmail.com>
mgrdevport <mgrdevport@gmail.com>
mitchell.demler <mitchell.demler@harcourts.net>
mkermani144 <mkermani144@gmail.com>
monkindey <monkindey@163.com>
muzuiget <muzuiget@gmail.com>
natergj <nater_nater@me.com>
neekey <ni184775761@gmail.com>
niko <644506165@qq.com>
nikogu <644506165@qq.com>
paranoidjk <hust2012jiangkai@gmail.com>
parlop <parlop@gmail.com>
pd4d10 <pd4d10@gmail.com>
@@ -369,11 +224,8 @@ pizn <pizner@gmail.com>
plandem <plandem@gmail.com>
popomore <sakura9515@gmail.com>
qubaoming <qubaoming@didichuxing.com>
ravirambles <ravirambles@gmail.com>
ryangun <ryangun@foxmail.com>
ryanhoho <hswacoal@gmail.com>
sadmark <zhoubin@laidian360.com>
sallen450 <jqh101@sina.com>
shelwin <wxfans@gmail.com>
shenlin192@gmail.com <shenlin192@gmail.com>
shlice <licesh@gmail.com>
@@ -384,26 +236,17 @@ snadn <snadn@snadn.cn>
sojournerc <cmeyer@zvelo.com>
sorrycc <sorrycc@gmail.com>
swindme <swindme@163.com>
syssam <s.y.s.sam.sys@gmail.com>
thegatheringstorm <tgs@tgs.blue>
tianli.zhao <275287902@qq.com>
tom <caolvchong@gmail.com>
twobin <twobin@live.com>
u3u <qwq@qwq.cat>
ustccjw <317713370@qq.com>
valleykid <valleykiddy@gmail.com>
vgeyi <vgeyiz@126.com>
wangtao0101 <yuecjn@gmail.com>
wangxiaolei <fatelei@gmail.com>
warmhug <hualei5280@gmail.com>
whtang906 <whtang906@gmail.com>
wizawu <wizawu@gmail.com>
wonyun <wy393767068@163.com>
xiaofan2406 <xiaofan2406@gmail.com>
yangwukang <yangwukang@boco.com.cn>
yeliex <yeliex@yeliex.com>
yiminghe <yiminghe@gmail.com>
yociduo <yociduo@vip.qq.com>
yubozhao <yubz86@gmail.com>
yuche <i@yuche.me>
z <haig8@msn.com>
@@ -412,46 +255,26 @@ zerob4wl <zerob4wl@gmail.com>
zhangpc <zhangpc@tenxcloud.com>
zhaocai <lzc09008@gmail.com>
zhujun24 <zhujun87654321@gmail.com>
zhuyue <fuping.dfp@antfin.com>
zilong <jzlxiaohei@163.com>
zinkey <yaya@uloveit.com.cn>
zollero <corona7@163.com>
zombiej <smith3816@gmail.com>
zuiidea <zuiiidea@gmail.com>
邦 <sqibang@gmail.com>
爱but的苍蝇 <354788473@qq.com>
拷钉 <41830859@qq.com>
竹尔 <Juelchiang@gmail.com>
偏右 <afc163@gmail.com>
英布 <chaoren1641@gmail.com>
朮厃 <cn.ah.liu@gmail.com>
诸岳 <dengfuping_develop@163.com>
逸达 <dqaria@gmail.com>
蔡伦 <sliuqin@gmail.com>
陆离 <surgesoft@gmail.com>
愚道 <tingzhao.ytz@antfin.com>
陈帅 <wasd2144@hotmail.com>
松子 <window.pibarr@gmail.com>
可乐 <zaxlct@foxmail.com>
山客 <zeakhold@gmail.com>
曾凯 <zengkai2009@foxmail.com>
低位 <zhujun87654321@gmail.com>
广彬-梁 <326741518@qq.com>
徐坤龙 <272992168@qq.com>
黄子毅 <576625322@qq.com>
翁润雨 <593110501@qq.com>
崔宏森 <948346354@qq.com>
闲耘™ <hotoo.cn@gmail.com>
一喵呜 <hyb628@gmail.com>
吕立青 <jimmy.jinglv@gmail.com>
隋鑫磊 <joshuasui@gmail.com>
乔奕轩 <qiao_yixuan@163.com>
马斯特 <sd4399340@126.com>
王集鹄 <wjhu111@21cn.com>
低位 <zhujun87654321@gmail.com>
偏右 <afc163@gmail.com>
可乐 <zaxlct@foxmail.com>
吕立青 <jimmy.jinglv@gmail.com>
广彬-梁 <326741518@qq.com>
柚子男 <yozman@sina.com>
愚指导 <yutingzhao1991@sina.com>
愚指导-TZ <yutingzhao1991@sina.com>
超能刚哥 <margox@foxmail.com>
白羊座小葛 <abeyuhang@gmail.com>
英布 <chaoren1641@gmail.com>
蔡伦 <sliuqin@gmail.com>
逸达 <dqaria@gmail.com>
邦 <sqibang@gmail.com>
闲耘™ <hotoo.cn@gmail.com>
陆离 <surgesoft@gmail.com>
陈帅 <wasd2144@hotmail.com>
马斯特 <sd4399340@126.com>
马金花儿 <o.o@mug.dog>
रोहन मल्होत्रा <rohan.malhotra@adwyze.com>
白羊座小葛 <abeyuhang@gmail.com>
黄子毅 <576625322@qq.com>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,55 +1,49 @@
<p align="center">
<a href="http://ant.design">
<img width="200" src="https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg">
<img width="320" src="https://t.alipayobjects.com/images/rmsweb/T1B9hfXcdvXXXXXXXX.svg">
</a>
</p>
<h1 align="center">Ant Design</h1>
# Ant Design
<div align="center">
一套企业级的 UI 设计语言和 React 实现。
[![](https://img.shields.io/travis/ant-design/ant-design/master.svg?style=flat-square)](https://travis-ci.org/ant-design/ant-design)
[![CircleCI](https://circleci.com/gh/ant-design/ant-design/tree/master.svg?style=svg)](https://circleci.com/gh/ant-design/ant-design/tree/master)
[![](https://img.shields.io/travis/ant-design/ant-design.svg?style=flat-square)](https://travis-ci.org/ant-design/ant-design)
[![Codecov](https://img.shields.io/codecov/c/github/ant-design/ant-design/master.svg?style=flat-square)](https://codecov.io/gh/ant-design/ant-design/branch/master)
[![Dependencies](https://img.shields.io/david/ant-design/ant-design.svg)](https://david-dm.org/ant-design/ant-design)
[![DevDependencies](https://img.shields.io/david/dev/ant-design/ant-design.svg)](https://david-dm.org/ant-design/ant-design?type=dev)
[![Dependency Status](https://img.shields.io/gemnasium/react-component/trigger.svg?style=flat-square)](https://gemnasium.com/ant-design/ant-design)
[![npm package](https://img.shields.io/npm/v/antd.svg?style=flat-square)](https://www.npmjs.org/package/antd)
[![NPM downloads](http://img.shields.io/npm/dm/antd.svg?style=flat-square)](http://www.npmtrends.com/antd)
[![NPM downloads](http://img.shields.io/npm/dm/antd.svg?style=flat-square)](https://npmjs.org/package/antd)
[![Percentage of issues still open](http://isitmaintained.com/badge/open/ant-design/ant-design.svg)](http://isitmaintained.com/project/ant-design/ant-design "Percentage of issues still open")
[![Gitter](https://badges.gitter.im/ant-design/ant-design-english.svg)](https://gitter.im/ant-design/ant-design-english?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) (English)
[![Join the chat at https://gitter.im/ant-design/ant-design](https://img.shields.io/gitter/room/ant-design/ant-design.svg?style=flat-square)](https://gitter.im/ant-design/ant-design?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)(中文)
</div>
一套企业级的 UI 设计语言和 React 实现。
[English](./README.md) | 简体中文
[README in English](README.md)
## 特性
## 特性
- 提炼自企业级中后台产品的交互语言和视觉风格。
- 开箱即用的高质量 React 组件。
- 使用 TypeScript 构建,提供完整的类型定义文件。
- 全链路开发和设计工具体系
- 基于 npm + webpack + [dva](https://github.com/dvajs/dva) 的企业级开发框架
## 🖥 支持环境
## 支持环境
* 现代浏览器和 IE9 及以上。
* 支持服务端渲染。
* [Electron](http://electron.atom.io/)
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/opera/opera_48x48.png" alt="Opera" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Opera | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/electron/electron_48x48.png" alt="Electron" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Electron |
| --------- | --------- | --------- | --------- | --------- | --------- |
| IE9, IE10, IE11, Edge| last 2 versions| last 2 versions| last 2 versions| last 2 versions| last 2 versions
## 参与共建 [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com)
## 📦 安装
`antd` 是一个开源项目,我们欢迎社区参与共建。如果你对此项目感兴趣,有 [很多方式](https://opensource.guide/how-to-contribute/) 进行参与。你可以 watch 这个仓库,加入 [issue 中的讨论](https://github.com/ant-design/ant-design/issues?q=is%3Aopen+is%3Aissue+label%3ADiscussion),以及尝试实现一些 [已接受的特性](https://github.com/ant-design/ant-design/issues?q=is%3Aopen+is%3Aissue+label%3A%22PR+welcome%22)。我们会给予有活跃贡献的社区成员 [collaborator 权限](https://github.com/ant-design/ant-design/issues/3222)。
## 安装
```bash
npm install antd --save
npm install antd
```
## 🔨 示例
## 示例
```jsx
import { DatePicker } from 'antd';
@@ -62,17 +56,30 @@ ReactDOM.render(<DatePicker />, mountNode);
import 'antd/dist/antd.css'; // or 'antd/dist/antd.less'
```
你也可以[按需加载组件](https://ant.design/docs/react/getting-started-cn#按需加载)。
按需加载可通过此写法 `import DatePicker from 'antd/lib/date-picker'` 或使用 Babel 插件 [babel-plugin-import](https://github.com/ant-design/babel-plugin-import),或使用 TypeScript 插件 [ts-import-plugin](https://github.com/Brooooooklyn/ts-import-plugin)。
### TypeScript
## TypeScript
参考 [在 TypeScript 中使用](https://ant.design/docs/react/use-in-typescript-cn)
```js
// tsconfig.json
{
"compilerOptions": {
"moduleResolution": "node",
"jsx": "preserve",
"allowSyntheticDefaultImports": true
}
}
```
## 🌍 国际化
> 注意:
> - 设置 `allowSyntheticDefaultImports` 避免 `error TS1192: Module 'react' has no default export` 的错误。
> - 不要使用 @types/antd, antd 已经自带了 TypeScript 定义。
参考 [国际化文档](http://ant.design/docs/react/i18n-cn)。
## 国际化
## 🔗 链接
参考 [国际化文档](http://ant.design/docs/react/i18n)。
## 链接
- [首页](http://ant.design/)
- [组件库](http://ant.design/docs/react/introduce)
@@ -86,24 +93,23 @@ import 'antd/dist/antd.css'; // or 'antd/dist/antd.less'
- [开发者说明](https://github.com/ant-design/ant-design/wiki/Development)
- [版本发布规则](https://github.com/ant-design/ant-design/wiki/%E8%BD%AE%E5%80%BC%E8%A7%84%E5%88%99%E5%92%8C%E7%89%88%E6%9C%AC%E5%8F%91%E5%B8%83%E6%B5%81%E7%A8%8B)
- [常见问题](https://github.com/ant-design/ant-design/wiki/FAQ)
- [CodeSandbox 模板](https://u.ant.design/codesandbox-repro) for bug reports
- [CodePen 模板](http://codepen.io/benjycui/pen/KgPZrE?editors=001) for bug reports
- [Awesome Ant Design](https://github.com/websemantics/awesome-ant-design)
- [定制主题](http://ant.design/docs/react/customize-theme-cn)
## ⌨️ 本地开发
## 本地开发
```bash
$ git clone git@github.com:ant-design/ant-design.git
$ cd ant-design
$ npm install
$ npm start
```
打开浏览器访问 http://127.0.0.1:8001 ,更多[本地开发文档](https://github.com/ant-design/ant-design/wiki/Development)
打开浏览器访问 http://127.0.0.1:8001 ,更多本地开发文档参见: https://github.com/ant-design/ant-design/wiki/Development
## 🤝 参与共建 [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com)
## 如何贡献
请参考[贡献指南](https://ant.design/docs/react/contributing-cn).
在任何形式的参与前,请先阅读 [贡献者文档](https://github.com/ant-design/ant-design/blob/master/.github/CONTRIBUTING.md)。如果你希望参与贡献,欢迎 [Pull Request](https://github.com/ant-design/ant-design/pulls),或给我们 [报告 Bug](http://new-issue.ant.design/)
> 强烈推荐阅读 [《提问的智慧》](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way)、[《如何向开源社区提问题》](https://github.com/seajs/seajs/issues/545) 和 [《如何有效地报告 Bug》](http://www.chiark.greenend.org.uk/%7Esgtatham/bugs-cn.html)、[《如何向开源项目提交无法解答的问题》](https://zhuanlan.zhihu.com/p/25795393),更好的问题更容易获得帮助。

113
README.md
View File

@@ -1,55 +1,49 @@
<p align="center">
<a href="http://ant.design">
<img width="200" src="https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg">
<img width="320" src="https://t.alipayobjects.com/images/rmsweb/T1B9hfXcdvXXXXXXXX.svg">
</a>
</p>
<h1 align="center">Ant Design</h1>
# Ant Design
<div align="center">
An enterprise-class UI design language and React-based implementation.
[![](https://img.shields.io/travis/ant-design/ant-design/master.svg?style=flat-square)](https://travis-ci.org/ant-design/ant-design)
[![CircleCI](https://circleci.com/gh/ant-design/ant-design/tree/master.svg?style=svg)](https://circleci.com/gh/ant-design/ant-design/tree/master)
[![](https://img.shields.io/travis/ant-design/ant-design.svg?style=flat-square)](https://travis-ci.org/ant-design/ant-design)
[![Codecov](https://img.shields.io/codecov/c/github/ant-design/ant-design/master.svg?style=flat-square)](https://codecov.io/gh/ant-design/ant-design/branch/master)
[![Dependencies](https://img.shields.io/david/ant-design/ant-design.svg)](https://david-dm.org/ant-design/ant-design)
[![DevDependencies](https://img.shields.io/david/dev/ant-design/ant-design.svg)](https://david-dm.org/ant-design/ant-design?type=dev)
[![Dependency Status](https://img.shields.io/gemnasium/react-component/trigger.svg?style=flat-square)](https://gemnasium.com/ant-design/ant-design)
[![npm package](https://img.shields.io/npm/v/antd.svg?style=flat-square)](https://www.npmjs.org/package/antd)
[![NPM downloads](http://img.shields.io/npm/dm/antd.svg?style=flat-square)](http://www.npmtrends.com/antd)
[![NPM downloads](http://img.shields.io/npm/dm/antd.svg?style=flat-square)](https://npmjs.org/package/antd)
[![Percentage of issues still open](http://isitmaintained.com/badge/open/ant-design/ant-design.svg)](http://isitmaintained.com/project/ant-design/ant-design "Percentage of issues still open")
[![Gitter](https://badges.gitter.im/ant-design/ant-design-english.svg)](https://gitter.im/ant-design/ant-design-english?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) (English)
[![Join the chat at https://gitter.im/ant-design/ant-design](https://img.shields.io/gitter/room/ant-design/ant-design.svg?style=flat-square)](https://gitter.im/ant-design/ant-design?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)(中文)
</div>
An enterprise-class UI design language and React-based implementation.
English | [简体中文](./README-zh_CN.md)
[中文 README](README-zh_CN.md)
## Features
## Features
- An enterprise-class UI design system for desktop applications.
- An enterprise-class UI design language for web applications.
- A set of high-quality React components out of the box.
- Written in TypeScript with predictable static types.
- The whole package of development and design resources and tools.
- Written in TypeScript with complete define types.
- A npm + webpack + [dva](https://github.com/dvajs/dva) front-end development workflow.
## 🖥 Environment Support
## Environment Support
* Modern browsers and Internet Explorer 9+ (with [polyfills](https://ant.design/docs/react/getting-started#Compatibility))
* Server-side Rendering
* [Electron](http://electron.atom.io/)
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/opera/opera_48x48.png" alt="Opera" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Opera | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/electron/electron_48x48.png" alt="Electron" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Electron |
| --------- | --------- | --------- | --------- | --------- | --------- |
| IE9, IE10, IE11, Edge| last 2 versions| last 2 versions| last 2 versions| last 2 versions| last 2 versions
## Let's build a better antd together [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com)
## 📦 Install
`antd` is an open source project; improvements are welcomed. If you are interested in contributing to `antd`, you can watch this repository, join in [discussion](https://github.com/ant-design/ant-design/issues?q=is%3Aopen+is%3Aissue+label%3ADiscussion), or try to implement some [features which have been accepted](https://github.com/ant-design/ant-design/issues?q=is%3Aopen+is%3Aissue+label%3A%22PR+welcome%22). Actually, there are [many ways](https://opensource.guide/how-to-contribute/) to contribute. And we are always happy to [offer collaborator permission](https://github.com/ant-design/ant-design/issues/3222) for some active contributors.
## Install
```bash
npm install antd --save
npm install antd
```
## 🔨 Usage
## Usage
```jsx
import { DatePicker } from 'antd';
@@ -62,17 +56,69 @@ And import style manually:
import 'antd/dist/antd.css'; // or 'antd/dist/antd.less'
```
Or [import components on demand](https://ant.design/docs/react/getting-started#Import-on-Demand).
### Use modularized antd
- Manually import
```jsx
import DatePicker from 'antd/lib/date-picker'; // for js
import 'antd/lib/date-picker/style/css'; // for css
// import 'antd/lib/date-picker/style'; // that will import less
```
- Use [babel-plugin-import](https://github.com/ant-design/babel-plugin-import)
```js
// .babelrc or babel-loader option
{
"plugins": [
["import", { "libraryName": "antd", "style": "css" }] // `style: true` for less
]
}
```
Then you can import components from antd, equivalent to import manually below.
```jsx
// import js and css modularly, parsed by babel-plugin-import
import { DatePicker } from 'antd';
```
### TypeScript
See [Use in TypeScript](https://ant.design/docs/react/use-in-typescript).
```js
// tsconfig.json
{
"compilerOptions": {
"moduleResolution": "node",
"jsx": "preserve",
"allowSyntheticDefaultImports": true
}
}
```
## 🌍 Internationalization
> Note:
> - set `allowSyntheticDefaultImports` to prevent `error TS1192: Module 'react' has no default export`.
> - Don't use @types/antd, antd provide a built-in ts definition already.
#### Use [ts-import-plugin](https://github.com/Brooooooklyn/ts-import-plugin) with modularized antd
```js
{
loader: "ts-loader", // or awesome-typescript-loader
options {
getCustomTransformers: () => ({
before: [ tsImportPluginFactory({ libraryName: "antd", style: "css" }) ]
})
}
}
```
## Internationalization
See [i18n](http://ant.design/docs/react/i18n).
## 🔗 Links
## Links
- [Home page](http://ant.design/)
- [Components](http://ant.design/docs/react/introduce)
@@ -85,23 +131,20 @@ See [i18n](http://ant.design/docs/react/i18n).
- [Developer Instruction](https://github.com/ant-design/ant-design/wiki/Development)
- [Versioning Release Note](https://github.com/ant-design/ant-design/wiki/%E8%BD%AE%E5%80%BC%E8%A7%84%E5%88%99%E5%92%8C%E7%89%88%E6%9C%AC%E5%8F%91%E5%B8%83%E6%B5%81%E7%A8%8B)
- [FAQ](https://github.com/ant-design/ant-design/wiki/FAQ)
- [CodeSandbox Template](https://u.ant.design/codesandbox-repro) for bug reports
- [CodePen boilerplate](http://codepen.io/benjycui/pen/KgPZrE?editors=001) for bug reports
- [Awesome Ant Design](https://github.com/websemantics/awesome-ant-design)
- [Customize Theme](http://ant.design/docs/react/customize-theme)
## ⌨️ Development
## Development
```bash
$ git clone git@github.com:ant-design/ant-design.git
$ cd ant-design
$ npm install
$ npm start
```
Open your browser and visit http://127.0.0.1:8001 , see more at [Development](https://github.com/ant-design/ant-design/wiki/Development).
Open your browser and visit http://127.0.0.1:8001 , see more at https://github.com/ant-design/ant-design/wiki/Development .
## 🤝 Contributing [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com)
Read our [contributing guide](https://ant.design/docs/react/contributing) and let's build a better antd together.
## Contributing
We welcome all contributions. Please read our [CONTRIBUTING.md](https://github.com/ant-design/ant-design/blob/master/.github/CONTRIBUTING.md) first. You can submit any ideas as [pull requests](https://github.com/ant-design/ant-design/pulls) or as [GitHub issues](https://github.com/ant-design/ant-design/issues). If you'd like to improve code, check out the [Development Instructions](https://github.com/ant-design/ant-design/wiki/Development) and have a good time! :)

View File

@@ -19,15 +19,12 @@ Array [
"Checkbox",
"Col",
"DatePicker",
"Divider",
"Dropdown",
"Drawer",
"Form",
"Icon",
"Input",
"InputNumber",
"Layout",
"List",
"LocaleProvider",
"message",
"Menu",
@@ -41,7 +38,6 @@ Array [
"Rate",
"Row",
"Select",
"Skeleton",
"Slider",
"Spin",
"Steps",

View File

@@ -1,21 +1,7 @@
const OLD_NODE_ENV = process.env.NODE_ENV;
process.env.NODE_ENV = 'development';
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
const antd = require('..');
import * as antd from '..';
describe('antd', () => {
afterAll(() => {
process.env.NODE_ENV = OLD_NODE_ENV;
});
it('exports modules correctly', () => {
expect(Object.keys(antd)).toMatchSnapshot();
});
it('should hint when import all components in dev mode', () => {
expect(warnSpy).toBeCalledWith(
'You are using a whole package of antd, please use https://www.npmjs.com/package/babel-plugin-import to reduce app bundle size.'
);
warnSpy.mockRestore();
});
});

View File

@@ -1,5 +1,4 @@
import throttleByAnimationFrame from '../throttleByAnimationFrame';
import getDataOrAriaProps from '../getDataOrAriaProps';
describe('Test utils function', () => {
beforeAll(() => {
@@ -33,55 +32,4 @@ describe('Test utils function', () => {
jest.runAllTimers();
expect(callback).not.toBeCalled();
});
describe('getDataOrAriaProps', () => {
it('returns all data-* properties from an object', () => {
const props = {
onClick: () => {},
isOpen: true,
'data-test': 'test-id',
'data-id': 1234,
};
const results = getDataOrAriaProps(props);
expect(results).toEqual({
'data-test': 'test-id',
'data-id': 1234,
});
});
it('does not return data-__ properties from an object', () => {
const props = {
onClick: () => {},
isOpen: true,
'data-__test': 'test-id',
'data-__id': 1234,
};
const results = getDataOrAriaProps(props);
expect(results).toEqual({});
});
it('returns all aria-* properties from an object', () => {
const props = {
onClick: () => {},
isOpen: true,
'aria-labelledby': 'label-id',
'aria-label': 'some-label',
};
const results = getDataOrAriaProps(props);
expect(results).toEqual({
'aria-labelledby': 'label-id',
'aria-label': 'some-label',
});
});
it('returns role property from an object', () => {
const props = {
onClick: () => {},
isOpen: true,
role: 'search',
};
const results = getDataOrAriaProps(props);
expect(results).toEqual({ role: 'search' });
});
});
});

View File

@@ -1,11 +0,0 @@
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-__'
) {
prev[key] = props[key];
}
return prev;
}, {});
}

View File

@@ -0,0 +1,30 @@
export function getComponentLocale(props, context, componentName, getDefaultLocale) {
let locale: any = {};
if (context && context.antLocale && context.antLocale[componentName]) {
locale = context.antLocale[componentName];
} else {
const defaultLocale = getDefaultLocale();
// TODO: make default lang of antd be English
// https://github.com/ant-design/ant-design/issues/6334
locale = defaultLocale.default || defaultLocale;
}
const result = {
...locale,
...props.locale,
};
result.lang = {
...locale.lang,
...props.locale.lang,
};
return result;
}
export function getLocaleCode(context) {
const localeCode = context.antLocale && context.antLocale.locale;
// Had use LocaleProvide but didn't set locale
if (context.antLocale && context.antLocale.exist && !localeCode) {
return 'zh-cn';
}
return localeCode;
}

View File

@@ -0,0 +1,44 @@
const availablePrefixs = ['moz', 'ms', 'webkit'];
function requestAnimationFramePolyfill() {
let lastTime = 0;
return function(callback) {
const currTime = new Date().getTime();
const timeToCall = Math.max(0, 16 - (currTime - lastTime));
const id = window.setTimeout(function() { callback(currTime + timeToCall); }, timeToCall);
lastTime = currTime + timeToCall;
return id;
};
}
export default function getRequestAnimationFrame() {
if (typeof window === 'undefined') {
return () => {};
}
if (window.requestAnimationFrame) {
// https://github.com/vuejs/vue/issues/4465
return window.requestAnimationFrame.bind(window);
}
const prefix = availablePrefixs.filter(key => `${key}RequestAnimationFrame` in window)[0];
return prefix
? window[`${prefix}RequestAnimationFrame`]
: requestAnimationFramePolyfill();
}
export function cancelRequestAnimationFrame(id) {
if (typeof window === 'undefined') {
return null;
}
if (window.cancelAnimationFrame) {
return window.cancelAnimationFrame(id);
}
const prefix = availablePrefixs.filter(key =>
`${key}CancelAnimationFrame` in window || `${key}CancelRequestAnimationFrame` in window,
)[0];
return prefix ?
(window[`${prefix}CancelAnimationFrame`] || window[`${prefix}CancelRequestAnimationFrame`]).call(this, id)
: clearTimeout(id);
}

View File

@@ -1,4 +1,4 @@
export default function getScroll(target: any, top: boolean): number {
export default function getScroll(target, top): number {
if (typeof window === 'undefined') {
return 0;
}

View File

@@ -1,4 +0,0 @@
// https://github.com/moment/moment/issues/3650
export default function interopDefault(m: any) {
return m.default || m;
}

View File

@@ -0,0 +1,24 @@
let animation;
function isCssAnimationSupported() {
if (animation !== undefined) {
return animation;
}
const domPrefixes = 'Webkit Moz O ms Khtml'.split(' ');
const elm = document.createElement('div');
if (elm.style.animationName !== undefined) {
animation = true;
}
if (animation !== undefined) {
for (let i = 0; i < domPrefixes.length; i++) {
if (elm.style[`${domPrefixes[i]}AnimationName`] !== undefined) {
animation = true;
break;
}
}
}
animation = animation || false;
return animation;
}
export default isCssAnimationSupported;

View File

@@ -1,5 +0,0 @@
const isNumeric = (value: any): boolean => {
return !isNaN(parseFloat(value)) && isFinite(value);
};
export default isNumeric;

View File

@@ -1,32 +1,34 @@
import cssAnimation from 'css-animation';
import raf from 'raf';
import getRequestAnimationFrame, { cancelRequestAnimationFrame } from './getRequestAnimationFrame';
function animate(node: HTMLElement, show: boolean, done: () => void) {
let height: number;
let requestAnimationFrameId: number;
const reqAnimFrame = getRequestAnimationFrame();
function animate(node, show, done) {
let height;
let requestAnimationFrameId;
return cssAnimation(node, 'ant-motion-collapse', {
start() {
if (!show) {
node.style.height = `${node.offsetHeight}px`;
node.style.opacity = '1';
node.style.opacity = 1;
} else {
height = node.offsetHeight;
node.style.height = '0px';
node.style.opacity = '0';
node.style.height = 0;
node.style.opacity = 0;
}
},
active() {
if (requestAnimationFrameId) {
raf.cancel(requestAnimationFrameId);
cancelRequestAnimationFrame(requestAnimationFrameId);
}
requestAnimationFrameId = raf(() => {
requestAnimationFrameId = reqAnimFrame(() => {
node.style.height = `${show ? height : 0}px`;
node.style.opacity = show ? '1' : '0';
node.style.opacity = show ? 1 : 0;
});
},
end() {
if (requestAnimationFrameId) {
raf.cancel(requestAnimationFrameId);
cancelRequestAnimationFrame(requestAnimationFrameId);
}
node.style.height = '';
node.style.opacity = '';
@@ -36,13 +38,13 @@ function animate(node: HTMLElement, show: boolean, done: () => void) {
}
const animation = {
enter(node: HTMLElement, done: () => void) {
enter(node, done) {
return animate(node, true, done);
},
leave(node: HTMLElement, done: () => void) {
leave(node, done) {
return animate(node, false, done);
},
appear(node: HTMLElement, done: () => void) {
appear(node, done) {
return animate(node, true, done);
},
};

View File

@@ -1,26 +1,28 @@
import raf from 'raf';
import getRequestAnimationFrame, { cancelRequestAnimationFrame } from '../_util/getRequestAnimationFrame';
export default function throttleByAnimationFrame(fn: (...args: any[]) => void) {
let requestId: number | null;
const reqAnimFrame = getRequestAnimationFrame();
const later = (args: any[]) => () => {
export default function throttleByAnimationFrame(fn) {
let requestId;
const later = args => () => {
requestId = null;
fn(...args);
};
const throttled = (...args: any[]) => {
const throttled = (...args) => {
if (requestId == null) {
requestId = raf(later(args));
requestId = reqAnimFrame(later(args));
}
};
(throttled as any).cancel = () => raf.cancel(requestId!);
(throttled as any).cancel = () => cancelRequestAnimationFrame(requestId);
return throttled;
}
export function throttleByAnimationFrameDecorator() {
return function(target: any, key: string, descriptor: any) {
return function(target, key, descriptor) {
let fn = descriptor.value;
let definingProperty = false;
return {

View File

@@ -1,4 +1,4 @@
export default function triggerEvent(el: Element, type: string) {
export default function triggerEvent(el, type) {
if ('createEvent' in document) {
// modern browsers, IE9+
const e = document.createEvent('HTMLEvents');

View File

@@ -1 +0,0 @@
export type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;

View File

@@ -1,131 +0,0 @@
import * as React from 'react';
import { findDOMNode } from 'react-dom';
import TransitionEvents from 'css-animation/lib/Event';
let styleForPesudo: HTMLStyleElement | null;
export default class Wave extends React.Component<{insertExtraNode?: boolean}> {
private instance?: {
cancel: () => void;
};
private extraNode: HTMLDivElement;
private clickWaveTimeoutId: number;
isNotGrey(color: string) {
const match = (color || '').match(/rgba?\((\d*), (\d*), (\d*)(, [\.\d]*)?\)/);
if (match && match[1] && match[2] && match[3]) {
return !(match[1] === match[2] && match[2] === match[3]);
}
return true;
}
onClick = (node: HTMLElement, waveColor: string) => {
if (node.className.indexOf('-leave') >= 0) {
return;
}
const { insertExtraNode } = this.props;
this.extraNode = document.createElement('div');
const extraNode = this.extraNode;
extraNode.className = 'ant-click-animating-node';
const attributeName = this.getAttributeName();
node.removeAttribute(attributeName);
node.setAttribute(attributeName, 'true');
// Not white or transparnt or grey
styleForPesudo = styleForPesudo || document.createElement('style');
if (waveColor &&
waveColor !== '#ffffff' &&
waveColor !== 'rgb(255, 255, 255)' &&
this.isNotGrey(waveColor) &&
!/rgba\(\d*, \d*, \d*, 0\)/.test(waveColor) && // any transparent rgba color
waveColor !== 'transparent') {
extraNode.style.borderColor = waveColor;
styleForPesudo.innerHTML =
`[ant-click-animating-without-extra-node]:after { border-color: ${waveColor}; }`;
if (!document.body.contains(styleForPesudo)) {
document.body.appendChild(styleForPesudo);
}
}
if (insertExtraNode) {
node.appendChild(extraNode);
}
TransitionEvents.addEndEventListener(node, this.onTransitionEnd);
}
bindAnimationEvent = (node: HTMLElement) => {
if (!node ||
!node.getAttribute ||
node.getAttribute('disabled') ||
node.className.indexOf('disabled') >= 0) {
return;
}
const onClick = (e: MouseEvent) => {
// Fix radio button click twice
if ((e.target as HTMLElement).tagName === 'INPUT') {
return;
}
this.resetEffect(node);
// Get wave color from target
const waveColor =
getComputedStyle(node).getPropertyValue('border-top-color') || // Firefox Compatible
getComputedStyle(node).getPropertyValue('border-color') ||
getComputedStyle(node).getPropertyValue('background-color');
this.clickWaveTimeoutId = window.setTimeout(() => this.onClick(node, waveColor), 0);
};
node.addEventListener('click', onClick, true);
return {
cancel: () => {
node.removeEventListener('click', onClick, true);
},
};
}
getAttributeName() {
const { insertExtraNode } = this.props;
return insertExtraNode ? 'ant-click-animating' : 'ant-click-animating-without-extra-node';
}
resetEffect(node: HTMLElement) {
if (!node || node === this.extraNode) {
return;
}
const { insertExtraNode } = this.props;
const attributeName = this.getAttributeName();
node.removeAttribute(attributeName);
this.removeExtraStyleNode();
if (insertExtraNode && this.extraNode && node.contains(this.extraNode)) {
node.removeChild(this.extraNode);
}
TransitionEvents.removeEndEventListener(node, this.onTransitionEnd);
}
onTransitionEnd = (e: AnimationEvent) => {
if (!e || e.animationName !== 'fadeEffect') {
return;
}
this.resetEffect(e.target as HTMLElement);
}
removeExtraStyleNode() {
if (styleForPesudo) {
styleForPesudo.innerHTML = '';
}
}
componentDidMount() {
this.instance = this.bindAnimationEvent(findDOMNode(this) as HTMLElement);
}
componentWillUnmount() {
if (this.instance) {
this.instance.cancel();
}
if (this.clickWaveTimeoutId) {
clearTimeout(this.clickWaveTimeoutId);
}
}
render() {
return this.props.children;
}
}

View File

@@ -7,15 +7,14 @@ const events = {};
class AffixMounter extends React.Component {
componentDidMount() {
this.container.scrollTop = 100;
this.container.addEventListener = jest.fn().mockImplementation((event, cb) => {
events[event] = cb;
});
}
getTarget = () => {
return this.container;
}
render() {
return (
<div
@@ -35,9 +34,8 @@ class AffixMounter extends React.Component {
<Affix
target={() => this.container}
ref={ele => this.affix = ele}
{...this.props}
>
<Button type="primary">
<Button type="primary" >
Fixed at the top of container
</Button>
</Affix>
@@ -48,8 +46,6 @@ class AffixMounter extends React.Component {
}
describe('Affix Render', () => {
let wrapper;
beforeAll(() => {
jest.useFakeTimers();
});
@@ -58,63 +54,23 @@ describe('Affix Render', () => {
jest.useRealTimers();
});
const scrollTo = (top) => {
wrapper.instance().affix.fixedNode.parentNode.getBoundingClientRect = jest.fn(() => {
return {
bottom: 100, height: 28, left: 0, right: 0, top: 50 - top, width: 195,
};
});
wrapper.instance().container.scrollTop = top;
events.scroll({
type: 'scroll',
});
jest.runAllTimers();
};
it('Anchor render perfectly', () => {
document.body.innerHTML = '<div id="mounter" />';
wrapper = mount(<AffixMounter />, { attachTo: document.getElementById('mounter') });
const wrapper = mount(<AffixMounter />, { attachTo: document.getElementById('mounter') });
jest.runAllTimers();
scrollTo(0);
expect(wrapper.instance().affix.state.affixStyle).toBe(null);
scrollTo(100);
expect(wrapper.instance().affix.state.affixStyle).not.toBe(null);
scrollTo(0);
expect(wrapper.instance().affix.state.affixStyle).toBe(null);
});
it('support offsetBottom', () => {
document.body.innerHTML = '<div id="mounter" />';
wrapper = mount(<AffixMounter offsetBottom={0} />, { attachTo: document.getElementById('mounter') });
jest.runAllTimers();
scrollTo(0);
expect(wrapper.instance().affix.state.affixStyle).not.toBe(null);
scrollTo(100);
expect(wrapper.instance().affix.state.affixStyle).toBe(null);
scrollTo(0);
expect(wrapper.instance().affix.state.affixStyle).not.toBe(null);
});
it('updatePosition when offsetTop changed', () => {
document.body.innerHTML = '<div id="mounter" />';
wrapper = mount(<AffixMounter offsetTop={0} />, { attachTo: document.getElementById('mounter') });
jest.runAllTimers();
scrollTo(100);
expect(wrapper.instance().affix.state.affixStyle.top).toBe(0);
wrapper.setProps({
offsetTop: 10,
wrapper.node.affix.refs.fixedNode.parentNode.getBoundingClientRect = jest.fn(() => {
return {
bottom: 100, height: 28, left: 0, right: 0, top: -50, width: 195,
};
});
events.scroll({
type: 'scroll',
});
jest.runAllTimers();
expect(wrapper.instance().affix.state.affixStyle.top).toBe(10);
expect(wrapper.node.affix.state.affixStyle).not.toBe(null);
});
});

View File

@@ -16,44 +16,16 @@ The simplest usage.
````jsx
import { Affix, Button } from 'antd';
class Demo extends React.Component {
state = {
top: 10,
bottom: 10,
}
render() {
return (
<div>
<Affix offsetTop={this.state.top}>
<Button
type="primary"
onClick={() => {
this.setState({
top: this.state.top + 10,
});
}}
>
Affix top
</Button>
</Affix>
<br />
<Affix offsetBottom={this.state.bottom}>
<Button
type="primary"
onClick={() => {
this.setState({
bottom: this.state.bottom + 10,
});
}}
>
Affix bottom
</Button>
</Affix>
</div>
);
}
}
ReactDOM.render(<Demo />, mountNode);
ReactDOM.render(
<div>
<Affix>
<Button type="primary">Affix top</Button>
</Affix>
<br />
<Affix offsetBottom={0}>
<Button type="primary">Affix bottom</Button>
</Affix>
</div>,
mountNode
);
````

View File

@@ -1,6 +1,6 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import * as PropTypes from 'prop-types';
import React from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import addEventListener from 'rc-util/lib/Dom/addEventListener';
import classNames from 'classnames';
import shallowequal from 'shallowequal';
@@ -8,13 +8,13 @@ import omit from 'omit.js';
import getScroll from '../_util/getScroll';
import { throttleByAnimationFrameDecorator } from '../_util/throttleByAnimationFrame';
function getTargetRect(target: HTMLElement | Window | null): ClientRect {
function getTargetRect(target): ClientRect {
return target !== window ?
(target as HTMLElement).getBoundingClientRect() :
{ top: 0, left: 0, bottom: 0 } as ClientRect;
target.getBoundingClientRect() :
{ top: 0, left: 0, bottom: 0 };
}
function getOffset(element: HTMLElement, target: HTMLElement | Window | null) {
function getOffset(element: HTMLElement, target) {
const elemRect = element.getBoundingClientRect();
const targetRect = getTargetRect(target);
@@ -38,7 +38,8 @@ function getOffset(element: HTMLElement, target: HTMLElement | Window | null) {
function noop() {}
function getDefaultTarget() {
return typeof window !== 'undefined' ? window : null;
return typeof window !== 'undefined' ?
window : null;
}
// Affix
@@ -54,16 +55,11 @@ export interface AffixProps {
/** 固定状态改变时触发的回调函数 */
onChange?: (affixed?: boolean) => void;
/** 设置 Affix 需要监听其滚动事件的元素,值为一个返回对应 DOM 元素的函数 */
target?: () => Window | HTMLElement | null;
target?: () => Window | HTMLElement;
prefixCls?: string;
}
export interface AffixState {
affixStyle: React.CSSProperties | undefined;
placeholderStyle: React.CSSProperties | undefined;
}
export default class Affix extends React.Component<AffixProps, AffixState> {
export default class Affix extends React.Component<AffixProps, any> {
static propTypes = {
offsetTop: PropTypes.number,
offsetBottom: PropTypes.number,
@@ -73,6 +69,9 @@ export default class Affix extends React.Component<AffixProps, AffixState> {
scrollEvent: any;
resizeEvent: any;
timeout: any;
refs: {
fixedNode: HTMLElement;
};
events = [
'resize',
@@ -84,19 +83,17 @@ export default class Affix extends React.Component<AffixProps, AffixState> {
'load',
];
eventHandlers: {
[key: string]: any;
} = {};
eventHandlers = {};
state: AffixState = {
affixStyle: undefined,
placeholderStyle: undefined,
};
constructor(props) {
super(props);
this.state = {
affixStyle: null,
placeholderStyle: null,
};
}
private fixedNode: HTMLElement;
private placeholderNode: HTMLElement;
setAffixStyle(e: any, affixStyle: React.CSSProperties | null) {
setAffixStyle(e, affixStyle) {
const { onChange = noop, target = getDefaultTarget } = this.props;
const originalAffixStyle = this.state.affixStyle;
const isWindow = target() === window;
@@ -106,7 +103,7 @@ export default class Affix extends React.Component<AffixProps, AffixState> {
if (shallowequal(affixStyle, originalAffixStyle)) {
return;
}
this.setState({ affixStyle: affixStyle as React.CSSProperties }, () => {
this.setState({ affixStyle }, () => {
const affixed = !!this.state.affixStyle;
if ((affixStyle && !originalAffixStyle) ||
(!affixStyle && originalAffixStyle)) {
@@ -115,44 +112,27 @@ export default class Affix extends React.Component<AffixProps, AffixState> {
});
}
setPlaceholderStyle(placeholderStyle: React.CSSProperties | null) {
setPlaceholderStyle(placeholderStyle) {
const originalPlaceholderStyle = this.state.placeholderStyle;
if (shallowequal(placeholderStyle, originalPlaceholderStyle)) {
return;
}
this.setState({ placeholderStyle: placeholderStyle as React.CSSProperties });
}
syncPlaceholderStyle(e: any) {
const { affixStyle } = this.state;
if (!affixStyle) {
return;
}
this.placeholderNode.style.cssText = '';
this.setAffixStyle(e, {
...affixStyle,
width: this.placeholderNode.offsetWidth,
});
this.setPlaceholderStyle({
width: this.placeholderNode.offsetWidth,
});
this.setState({ placeholderStyle });
}
@throttleByAnimationFrameDecorator()
updatePosition(e: any) {
updatePosition(e) {
let { offsetTop, offsetBottom, offset, target = getDefaultTarget } = this.props;
const targetNode = target();
// Backwards support
// Fix: if offsetTop === 0, it will get undefined,
// if offsetBottom is type of number, offsetMode will be { top: false, ... }
offsetTop = typeof offsetTop === 'undefined' ? offset : offsetTop;
offsetTop = offsetTop || offset;
const scrollTop = getScroll(targetNode, true);
const affixNode = ReactDOM.findDOMNode(this) as HTMLElement;
const elemOffset = getOffset(affixNode, targetNode);
const elemSize = {
width: this.fixedNode.offsetWidth,
height: this.fixedNode.offsetHeight,
width: this.refs.fixedNode.offsetWidth,
height: this.refs.fixedNode.offsetHeight,
};
const offsetMode = {
@@ -174,10 +154,9 @@ export default class Affix extends React.Component<AffixProps, AffixState> {
if (scrollTop > elemOffset.top - (offsetTop as number) && offsetMode.top) {
// Fixed Top
const width = elemOffset.width;
const top = targetRect.top + (offsetTop as number);
this.setAffixStyle(e, {
position: 'fixed',
top,
top: targetRect.top + (offsetTop as number),
left: targetRect.left + elemOffset.left,
width,
});
@@ -211,10 +190,6 @@ export default class Affix extends React.Component<AffixProps, AffixState> {
}
this.setPlaceholderStyle(null);
}
if (e.type === 'resize') {
this.syncPlaceholderStyle(e);
}
}
componentDidMount() {
@@ -225,20 +200,14 @@ export default class Affix extends React.Component<AffixProps, AffixState> {
});
}
componentWillReceiveProps(nextProps: AffixProps) {
componentWillReceiveProps(nextProps) {
if (this.props.target !== nextProps.target) {
this.clearEventListeners();
this.setTargetEventListeners(nextProps.target!);
this.setTargetEventListeners(nextProps.target);
// Mock Event object.
this.updatePosition({});
}
if (
this.props.offsetTop !== nextProps.offsetTop ||
this.props.offsetBottom !== nextProps.offsetBottom
) {
this.updatePosition({});
}
}
componentWillUnmount() {
@@ -247,7 +216,7 @@ export default class Affix extends React.Component<AffixProps, AffixState> {
(this.updatePosition as any).cancel();
}
setTargetEventListeners(getTarget: () => HTMLElement | Window | null) {
setTargetEventListeners(getTarget) {
const target = getTarget();
if (!target) {
return;
@@ -268,14 +237,6 @@ export default class Affix extends React.Component<AffixProps, AffixState> {
});
}
saveFixedNode = (node: HTMLDivElement) => {
this.fixedNode = node;
}
savePlaceholderNode = (node: HTMLDivElement) => {
this.placeholderNode = node;
}
render() {
const className = classNames({
[this.props.prefixCls || 'ant-affix']: this.state.affixStyle,
@@ -284,8 +245,8 @@ export default class Affix extends React.Component<AffixProps, AffixState> {
const props = omit(this.props, ['prefixCls', 'offsetTop', 'offsetBottom', 'target', 'onChange']);
const placeholderStyle = { ...this.state.placeholderStyle, ...this.props.style };
return (
<div {...props} style={placeholderStyle} ref={this.savePlaceholderNode}>
<div className={className} ref={this.saveFixedNode} style={this.state.affixStyle}>
<div {...props} style={placeholderStyle}>
<div className={className} ref="fixedNode" style={this.state.affixStyle}>
{this.props.children}
</div>
</div>

View File

@@ -8,21 +8,7 @@ exports[`renders ./components/alert/demo/banner.md correctly 1`] = `
>
<i
class="anticon anticon-exclamation-circle ant-alert-icon"
>
<svg
aria-hidden="true"
class=""
data-icon="exclamation-circle"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm-32 232c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v272c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V296zm32 440a48.01 48.01 0 0 1 0-96 48.01 48.01 0 0 1 0 96z"
/>
</svg>
</i>
/>
<span
class="ant-alert-message"
>
@@ -39,21 +25,7 @@ exports[`renders ./components/alert/demo/banner.md correctly 1`] = `
>
<i
class="anticon anticon-exclamation-circle ant-alert-icon"
>
<svg
aria-hidden="true"
class=""
data-icon="exclamation-circle"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm-32 232c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v272c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V296zm32 440a48.01 48.01 0 0 1 0-96 48.01 48.01 0 0 1 0 96z"
/>
</svg>
</i>
/>
<span
class="ant-alert-message"
>
@@ -66,22 +38,8 @@ exports[`renders ./components/alert/demo/banner.md correctly 1`] = `
class="ant-alert-close-icon"
>
<i
class="anticon anticon-close"
>
<svg
aria-hidden="true"
class=""
data-icon="close"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 0 0 203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"
/>
</svg>
</i>
class="anticon anticon-cross"
/>
</a>
</div>
<br />
@@ -104,22 +62,8 @@ exports[`renders ./components/alert/demo/banner.md correctly 1`] = `
data-show="true"
>
<i
class="anticon anticon-close-circle ant-alert-icon"
>
<svg
aria-hidden="true"
class=""
data-icon="close-circle"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 0 1-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
</i>
class="anticon anticon-cross-circle ant-alert-icon"
/>
<span
class="ant-alert-message"
>
@@ -166,22 +110,8 @@ exports[`renders ./components/alert/demo/closable.md correctly 1`] = `
class="ant-alert-close-icon"
>
<i
class="anticon anticon-close"
>
<svg
aria-hidden="true"
class=""
data-icon="close"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 0 0 203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"
/>
</svg>
</i>
class="anticon anticon-cross"
/>
</a>
</div>
<div
@@ -202,22 +132,8 @@ exports[`renders ./components/alert/demo/closable.md correctly 1`] = `
class="ant-alert-close-icon"
>
<i
class="anticon anticon-close"
>
<svg
aria-hidden="true"
class=""
data-icon="close"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 0 0 203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"
/>
</svg>
</i>
class="anticon anticon-cross"
/>
</a>
</div>
</div>
@@ -317,21 +233,7 @@ exports[`renders ./components/alert/demo/icon.md correctly 1`] = `
>
<i
class="anticon anticon-check-circle ant-alert-icon"
>
<svg
aria-hidden="true"
class=""
data-icon="check-circle"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm193.5 301.7l-210.6 292a31.8 31.8 0 0 1-51.7 0L318.5 484.9c-3.8-5.3 0-12.7 6.5-12.7h46.9c10.2 0 19.9 4.9 25.9 13.3l71.2 98.8 157.2-218c6-8.3 15.6-13.3 25.9-13.3H699c6.5 0 10.3 7.4 6.5 12.7z"
/>
</svg>
</i>
/>
<span
class="ant-alert-message"
>
@@ -347,21 +249,7 @@ exports[`renders ./components/alert/demo/icon.md correctly 1`] = `
>
<i
class="anticon anticon-info-circle ant-alert-icon"
>
<svg
aria-hidden="true"
class=""
data-icon="info-circle"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm32 664c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V456c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v272zm-32-344a48.01 48.01 0 0 1 0-96 48.01 48.01 0 0 1 0 96z"
/>
</svg>
</i>
/>
<span
class="ant-alert-message"
>
@@ -377,21 +265,7 @@ exports[`renders ./components/alert/demo/icon.md correctly 1`] = `
>
<i
class="anticon anticon-exclamation-circle ant-alert-icon"
>
<svg
aria-hidden="true"
class=""
data-icon="exclamation-circle"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm-32 232c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v272c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V296zm32 440a48.01 48.01 0 0 1 0-96 48.01 48.01 0 0 1 0 96z"
/>
</svg>
</i>
/>
<span
class="ant-alert-message"
>
@@ -406,22 +280,8 @@ exports[`renders ./components/alert/demo/icon.md correctly 1`] = `
data-show="true"
>
<i
class="anticon anticon-close-circle ant-alert-icon"
>
<svg
aria-hidden="true"
class=""
data-icon="close-circle"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 0 1-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
</i>
class="anticon anticon-cross-circle ant-alert-icon"
/>
<span
class="ant-alert-message"
>
@@ -436,29 +296,12 @@ exports[`renders ./components/alert/demo/icon.md correctly 1`] = `
data-show="true"
>
<i
class="anticon anticon-check-circle ant-alert-icon"
>
<svg
aria-hidden="true"
class=""
data-icon="check-circle"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M699 353h-46.9c-10.2 0-19.9 4.9-25.9 13.3L469 584.3l-71.2-98.8c-6-8.3-15.6-13.3-25.9-13.3H325c-6.5 0-10.3 7.4-6.5 12.7l124.6 172.8a31.8 31.8 0 0 0 51.7 0l210.6-292c3.9-5.3.1-12.7-6.4-12.7z"
/>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"
/>
</svg>
</i>
class="anticon anticon-check-circle-o ant-alert-icon"
/>
<span
class="ant-alert-message"
>
Success Tips
success tips
</span>
<span
class="ant-alert-description"
@@ -471,25 +314,8 @@ exports[`renders ./components/alert/demo/icon.md correctly 1`] = `
data-show="true"
>
<i
class="anticon anticon-info-circle ant-alert-icon"
>
<svg
aria-hidden="true"
class=""
data-icon="info-circle"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"
/>
<path
d="M464 336a48 48 0 1 0 96 0 48 48 0 1 0-96 0zM536 448h-48c-4.4 0-8 3.6-8 8v272c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V456c0-4.4-3.6-8-8-8z"
/>
</svg>
</i>
class="anticon anticon-info-circle-o ant-alert-icon"
/>
<span
class="ant-alert-message"
>
@@ -506,25 +332,8 @@ exports[`renders ./components/alert/demo/icon.md correctly 1`] = `
data-show="true"
>
<i
class="anticon anticon-exclamation-circle ant-alert-icon"
>
<svg
aria-hidden="true"
class=""
data-icon="exclamation-circle"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"
/>
<path
d="M464 688a48 48 0 1 0 96 0 48 48 0 1 0-96 0zM488 576h48c4.4 0 8-3.6 8-8V296c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v272c0 4.4 3.6 8 8 8z"
/>
</svg>
</i>
class="anticon anticon-exclamation-circle-o ant-alert-icon"
/>
<span
class="ant-alert-message"
>
@@ -541,25 +350,8 @@ exports[`renders ./components/alert/demo/icon.md correctly 1`] = `
data-show="true"
>
<i
class="anticon anticon-close-circle ant-alert-icon"
>
<svg
aria-hidden="true"
class=""
data-icon="close-circle"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M685.4 354.8c0-4.4-3.6-8-8-8l-66 .3L512 465.6l-99.3-118.4-66.1-.3c-4.4 0-8 3.5-8 8 0 1.9.7 3.7 1.9 5.2l130.1 155L340.5 670a8.32 8.32 0 0 0-1.9 5.2c0 4.4 3.6 8 8 8l66.1-.3L512 564.4l99.3 118.4 66 .3c4.4 0 8-3.5 8-8 0-1.9-.7-3.7-1.9-5.2L553.5 515l130.1-155c1.2-1.4 1.8-3.3 1.8-5.2z"
/>
<path
d="M512 65C264.6 65 64 265.6 64 513s200.6 448 448 448 448-200.6 448-448S759.4 65 512 65zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"
/>
</svg>
</i>
class="anticon anticon-cross-circle-o ant-alert-icon"
/>
<span
class="ant-alert-message"
>
@@ -574,48 +366,6 @@ exports[`renders ./components/alert/demo/icon.md correctly 1`] = `
</div>
`;
exports[`renders ./components/alert/demo/smooth-closed.md correctly 1`] = `
<div>
<div
class="ant-alert ant-alert-success ant-alert-no-icon"
data-show="true"
>
<span
class="ant-alert-message"
>
Alert Message Text
</span>
<span
class="ant-alert-description"
/>
<a
class="ant-alert-close-icon"
>
<i
class="anticon anticon-close"
>
<svg
aria-hidden="true"
class=""
data-icon="close"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 0 0 203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"
/>
</svg>
</i>
</a>
</div>
<p>
placeholder text here
</p>
</div>
`;
exports[`renders ./components/alert/demo/style.md correctly 1`] = `
<div>
<div

View File

@@ -1,58 +0,0 @@
import React from 'react';
import { mount } from 'enzyme';
import Alert from '..';
describe('Alert', () => {
beforeAll(() => {
jest.useFakeTimers();
});
afterAll(() => {
jest.useRealTimers();
});
it('could be closed', () => {
const onClose = jest.fn();
const afterClose = jest.fn();
const wrapper = mount(
<Alert
message="Warning Text Warning Text Warning TextW arning Text Warning Text Warning TextWarning Text"
type="warning"
closable
onClose={onClose}
afterClose={afterClose}
/>
);
wrapper.find('.ant-alert-close-icon').simulate('click');
expect(onClose).toBeCalled();
jest.runAllTimers();
expect(afterClose).toBeCalled();
});
describe('data and aria props', () => {
it('sets data attributes on input', () => {
const wrapper = mount(
<Alert data-test="test-id" data-id="12345" />
);
const input = wrapper.find('.ant-alert').getDOMNode();
expect(input.getAttribute('data-test')).toBe('test-id');
expect(input.getAttribute('data-id')).toBe('12345');
});
it('sets aria attributes on input', () => {
const wrapper = mount(
<Alert aria-describedby="some-label" />
);
const input = wrapper.find('.ant-alert').getDOMNode();
expect(input.getAttribute('aria-describedby')).toBe('some-label');
});
it('sets role attribute on input', () => {
const wrapper = mount(
<Alert role="status" />
);
const input = wrapper.find('.ant-alert').getDOMNode();
expect(input.getAttribute('role')).toBe('status');
});
});
});

View File

@@ -26,6 +26,6 @@ ReactDOM.render(
<Alert showIcon={false} message="Warning text without icon" banner />
<br />
<Alert type="error" message="Error text" banner />
</div>,
mountNode);
</div>
, mountNode);
````

View File

@@ -17,8 +17,8 @@ The simplest usage for short messages.
import { Alert } from 'antd';
ReactDOM.render(
<Alert message="Success Text" type="success" />,
mountNode);
<Alert message="Success Text" type="success" />
, mountNode);
````
<style>

View File

@@ -35,6 +35,6 @@ ReactDOM.render(
closable
onClose={onClose}
/>
</div>,
mountNode);
</div>
, mountNode);
````

View File

@@ -17,6 +17,6 @@ Replace the default icon with customized text.
import { Alert } from 'antd';
ReactDOM.render(
<Alert message="Info Text" type="info" closeText="Close Now" />,
mountNode);
<Alert message="Info Text" type="info" closeText="Close Now" />
, mountNode);
````

View File

@@ -38,6 +38,6 @@ ReactDOM.render(
description="Error Description Error Description Error Description Error Description"
type="error"
/>
</div>,
mountNode);
</div>
, mountNode);
````

View File

@@ -23,7 +23,7 @@ ReactDOM.render(
<Alert message="Warning" type="warning" showIcon />
<Alert message="Error" type="error" showIcon />
<Alert
message="Success Tips"
message="success tips"
description="Detailed description and advices about successful copywriting."
type="success"
showIcon
@@ -46,6 +46,6 @@ ReactDOM.render(
type="error"
showIcon
/>
</div>,
mountNode);
</div>
, mountNode);
````

View File

@@ -1,50 +0,0 @@
---
order: 7
title:
zh-CN: 平滑地卸载
en-US: Smoothly Unmount
---
## zh-CN
平滑、自然的卸载提示
## en-US
Smoothly and unaffectedly unmount Alert.
````jsx
import { Alert } from 'antd';
class App extends React.Component {
state = {
visible: true,
}
handleClose = () => {
this.setState({ visible: false });
}
render() {
return (
<div>
{
this.state.visible ? (
<Alert
message="Alert Message Text"
type="success"
closable
afterClose={this.handleClose}
/>
) : null
}
<p>placeholder text here</p>
</div>
);
}
}
ReactDOM.render(
<App />,
mountNode);
````

View File

@@ -22,6 +22,6 @@ ReactDOM.render(
<Alert message="Info Text" type="info" />
<Alert message="Warning Text" type="warning" />
<Alert message="Error Text" type="error" />
</div>,
mountNode);
</div>
, mountNode);
````

View File

@@ -15,13 +15,11 @@ Alert component for feedback.
| Property | Description | Type | Default |
| -------- | ----------- | ---- | ------- |
| afterClose | Called when close animation is finished | () => void | - |
| banner | Whether to show as banner | boolean | false |
| closable | Whether Alert can be closed | boolean | - |
| closeText | Close text to show | string\|ReactNode | - |
| description | Additional content of Alert | string\|ReactNode | - |
| iconType | Icon type, effective when `showIcon` is `true` | string | - |
| message | Content of Alert | string\|ReactNode | - |
| showIcon | Whether to show icon | boolean | false, in `banner` mode default is true |
| type | Type of Alert styles, options: `success`, `info`, `warning`, `error` | string | `info`, in `banner` mode default is `warning` |
| onClose | Callback when Alert is closed | (e: MouseEvent) => void | - |
| onClose | Callback when Alert is closed | Function | - |

View File

@@ -1,9 +1,8 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import React from 'react';
import ReactDOM from 'react-dom';
import Animate from 'rc-animate';
import Icon, { ThemeType } from '../icon';
import Icon from '../icon';
import classNames from 'classnames';
import getDataOrAriaProps from '../_util/getDataOrAriaProps';
function noop() { }
@@ -22,11 +21,8 @@ export interface AlertProps {
description?: React.ReactNode;
/** Callback when close Alert */
onClose?: React.MouseEventHandler<HTMLAnchorElement>;
/** Trigger when animation ending of Alert */
afterClose?: () => void;
/** Whether to show icon */
showIcon?: boolean;
iconType?: string;
style?: React.CSSProperties;
prefixCls?: string;
className?: string;
@@ -59,12 +55,11 @@ export default class Alert extends React.Component<AlertProps, any> {
closed: true,
closing: true,
});
(this.props.afterClose || noop)();
}
render() {
let {
closable, description, type, prefixCls = 'ant-alert', message, closeText, showIcon, banner,
className = '', style, iconType,
className = '', style,
} = this.props;
// banner模式默认有 Icon
@@ -72,29 +67,27 @@ export default class Alert extends React.Component<AlertProps, any> {
// banner模式默认为警告
type = banner && type === undefined ? 'warning' : type || 'info';
let iconTheme: ThemeType = 'filled';
if (!iconType) {
switch (type) {
case 'success':
iconType = 'check-circle';
break;
case 'info':
iconType = 'info-circle';
break;
case 'error':
iconType = 'close-circle';
break;
case 'warning':
iconType = 'exclamation-circle';
break;
default:
iconType = 'default';
}
let iconType = '';
switch (type) {
case 'success':
iconType = 'check-circle';
break;
case 'info':
iconType = 'info-circle';
break;
case 'error':
iconType = 'cross-circle';
break;
case 'warning':
iconType = 'exclamation-circle';
break;
default:
iconType = 'default';
}
// use outline icon in alert with description
if (!!description) {
iconTheme = 'outlined';
}
// use outline icon in alert with description
if (!!description) {
iconType += '-o';
}
let alertCls = classNames(prefixCls, {
@@ -112,14 +105,10 @@ export default class Alert extends React.Component<AlertProps, any> {
const closeIcon = closable ? (
<a onClick={this.handleClose} className={`${prefixCls}-close-icon`}>
{closeText || <Icon type="close" />}
{closeText || <Icon type="cross" />}
</a>
) : null;
const dataOrAriaProps = getDataOrAriaProps(this.props);
const iconNode = <Icon className={`${prefixCls}-icon`} type={iconType} theme={iconTheme} />;
return this.state.closed ? null : (
<Animate
component=""
@@ -127,8 +116,8 @@ export default class Alert extends React.Component<AlertProps, any> {
transitionName={`${prefixCls}-slide-up`}
onEnd={this.animationEnd}
>
<div data-show={this.state.closing} className={alertCls} style={style} {...dataOrAriaProps}>
{showIcon ? iconNode : null}
<div data-show={this.state.closing} className={alertCls} style={style}>
{showIcon ? <Icon className={`${prefixCls}-icon`} type={iconType} /> : null}
<span className={`${prefixCls}-message`}>{message}</span>
<span className={`${prefixCls}-description`}>{description}</span>
{closeIcon}

View File

@@ -16,13 +16,11 @@ title: Alert
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| afterClose | 关闭动画结束后触发的回调函数 | () => void | - |
| banner | 是否用作顶部公告 | boolean | false |
| closable | 默认不显示关闭按钮 | boolean | 无 |
| closeText | 自定义关闭按钮 | string\|ReactNode | 无 |
| description | 警告提示的辅助性文字介绍 | string\|ReactNode | 无 |
| iconType | 自定义图标类型,`showIcon``true` 时有效 | string | - |
| message | 警告提示内容 | string\|ReactNode | 无 |
| showIcon | 是否显示辅助图标 | boolean | false`banner` 模式下默认值为 true |
| type | 指定警告提示的样式,有四种选择 `success``info``warning``error` | string | `info``banner` 模式下默认值为 `warning` |
| onClose | 关闭时触发的回调函数 | (e: MouseEvent) => void | 无 |
| onClose | 关闭时触发的回调函数 | Function | 无 |

View File

@@ -1,78 +1,80 @@
@import "../../style/themes/default";
@import "../../style/mixins/index";
@alert-prefix-cls: ~"@{ant-prefix}-alert";
@alert-message-color: @heading-color;
@alert-text-color: @text-color;
@alert-close-color: @text-color-secondary;
.@{alert-prefix-cls} {
.reset-component;
position: relative;
padding: 8px 15px 8px 37px;
padding: 8px 48px 8px 38px;
border-radius: @border-radius-base;
color: @alert-text-color;
font-size: @font-size-base;
line-height: @line-height-base;
&&-no-icon {
padding: 8px 15px;
padding: 8px 48px 8px 16px;
}
&-icon {
top: 8px + @font-size-base * @line-height-base / 2 - @font-size-base / 2;
font-size: @font-size-lg;
top: 8px + @font-size-base * @line-height-base / 2 - @font-size-lg / 2;
left: 16px;
position: absolute;
}
&-description {
font-size: @font-size-base;
line-height: 22px;
line-height: 21px;
display: none;
}
&-success {
border: @border-width-base @border-style-base ~`colorPalette("@{success-color}", 3)`;
background-color: ~`colorPalette("@{success-color}", 1)`;
border: @border-width-base @border-style-base @green-2;
background-color: @green-1;
.@{alert-prefix-cls}-icon {
color: @success-color;
}
}
&-info {
border: @border-width-base @border-style-base ~`colorPalette("@{info-color}", 3)`;
background-color: ~`colorPalette("@{info-color}", 1)`;
border: @border-width-base @border-style-base @primary-2;
background-color: @primary-1;
.@{alert-prefix-cls}-icon {
color: @info-color;
}
}
&-warning {
border: @border-width-base @border-style-base ~`colorPalette("@{warning-color}", 3)`;
background-color: ~`colorPalette("@{warning-color}", 1)`;
border: @border-width-base @border-style-base @yellow-2;
background-color: @yellow-1;
.@{alert-prefix-cls}-icon {
color: @warning-color;
}
}
&-error {
border: @border-width-base @border-style-base ~`colorPalette("@{error-color}", 3)`;
background-color: ~`colorPalette("@{error-color}", 1)`;
border: @border-width-base @border-style-base @red-2;
background-color: @red-1;
.@{alert-prefix-cls}-icon {
color: @error-color;
}
}
&-close-icon {
font-size: @font-size-sm;
font-size: @font-size-base;
position: absolute;
right: 16px;
top: 8px;
line-height: 22px;
top: 10px;
height: 12px;
line-height: 12px;
overflow: hidden;
cursor: pointer;
.@{iconfont-css-prefix}-close {
color: @alert-close-color;
transition: color .3s;
.@{iconfont-css-prefix}-cross {
color: @text-color-secondary;
transition: color .3s ease;
&:hover {
color: #404040;
}
@@ -85,21 +87,21 @@
}
&-with-description {
padding: 15px 15px 15px 64px;
padding: 16px 16px 16px 60px;
position: relative;
border-radius: @border-radius-base;
color: @alert-text-color;
line-height: @line-height-base;
color: @text-color;
line-height: 1.5;
}
&-with-description&-no-icon {
padding: 15px;
padding: 16px;
}
&-with-description &-icon {
position: absolute;
top: 16px;
left: 24px;
left: 20px;
font-size: 24px;
}

View File

@@ -1,18 +1,18 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import * as PropTypes from 'prop-types';
import React from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import addEventListener from 'rc-util/lib/Dom/addEventListener';
import Affix from '../affix';
import AnchorLink from './AnchorLink';
import getScroll from '../_util/getScroll';
import raf from 'raf';
import getRequestAnimationFrame from '../_util/getRequestAnimationFrame';
function getDefaultContainer() {
function getDefaultTarget() {
return window;
}
function getOffsetTop(element: HTMLElement, container: AnchorContainer): number {
function getOffsetTop(element: HTMLElement): number {
if (!element) {
return 0;
}
@@ -24,11 +24,9 @@ function getOffsetTop(element: HTMLElement, container: AnchorContainer): number
const rect = element.getBoundingClientRect();
if (rect.width || rect.height) {
if (container === window) {
container = element.ownerDocument.documentElement;
return rect.top - container.clientTop;
}
return rect.top - (container as HTMLElement).getBoundingClientRect().top;
const doc = element.ownerDocument;
const docElem = doc.documentElement;
return rect.top - docElem.clientTop;
}
return rect.top;
@@ -43,44 +41,38 @@ function easeInOutCubic(t: number, b: number, c: number, d: number) {
return cc / 2 * ((t -= 2) * t * t + 2) + b;
}
const reqAnimFrame = getRequestAnimationFrame();
const sharpMatcherRegx = /#([^#]+)$/;
function scrollTo(href: string, offsetTop = 0, getContainer: () => AnchorContainer, callback = () => { }) {
const container = getContainer();
const scrollTop = getScroll(container, true);
function scrollTo(href: string, offsetTop = 0, target, callback = () => { }) {
const scrollTop = getScroll(target(), true);
const sharpLinkMatch = sharpMatcherRegx.exec(href);
if (!sharpLinkMatch) { return; }
const targetElement = document.getElementById(sharpLinkMatch[1]);
if (!targetElement) {
return;
}
const eleOffsetTop = getOffsetTop(targetElement, container);
const eleOffsetTop = getOffsetTop(targetElement);
const targetScrollTop = scrollTop + eleOffsetTop - offsetTop;
const startTime = Date.now();
const frameFunc = () => {
const timestamp = Date.now();
const time = timestamp - startTime;
const nextScrollTop = easeInOutCubic(time, scrollTop, targetScrollTop, 450);
if (container === window) {
window.scrollTo(window.pageXOffset, nextScrollTop);
} else {
(container as HTMLElement).scrollTop = nextScrollTop;
}
window.scrollTo(window.pageXOffset, easeInOutCubic(time, scrollTop, targetScrollTop, 450));
if (time < 450) {
raf(frameFunc);
reqAnimFrame(frameFunc);
} else {
callback();
}
};
raf(frameFunc);
reqAnimFrame(frameFunc);
history.pushState(null, '', href);
}
type Section = {
link: string;
link: String;
top: number;
};
export type AnchorContainer = HTMLElement | Window;
export interface AnchorProps {
prefixCls?: string;
className?: string;
@@ -90,76 +82,61 @@ export interface AnchorProps {
bounds?: number;
affix?: boolean;
showInkInFixed?: boolean;
getContainer?: () => AnchorContainer;
onClick?: (e: React.MouseEvent<HTMLElement>, link: { title: React.ReactNode, href: string }) => void;
target?: () => HTMLElement | Window;
}
export interface AnchorState {
activeLink: null | string;
}
export interface AnchorDefaultProps extends AnchorProps {
prefixCls: string;
affix: boolean;
showInkInFixed: boolean;
getContainer: () => AnchorContainer;
}
export interface AntAnchor {
registerLink: (link: string) => void;
unregisterLink: (link: string) => void;
activeLink: string | null;
scrollTo: (link: string) => void;
onClick?: (e: React.MouseEvent<HTMLElement>, link: { title: React.ReactNode, href: string }) => void;
}
export default class Anchor extends React.Component<AnchorProps, AnchorState> {
export default class Anchor extends React.Component<AnchorProps, any> {
static Link: typeof AnchorLink;
static defaultProps = {
prefixCls: 'ant-anchor',
affix: true,
showInkInFixed: false,
getContainer: getDefaultContainer,
};
static childContextTypes = {
antAnchor: PropTypes.object,
};
state = {
activeLink: null,
refs: {
ink?: any;
};
private inkNode: HTMLSpanElement;
private links: string[] = [];
private links: String[];
private scrollEvent: any;
private animating: boolean;
getChildContext() {
const antAnchor: AntAnchor = {
registerLink: (link: string) => {
if (!this.links.includes(link)) {
this.links.push(link);
}
},
unregisterLink: (link: string) => {
const index = this.links.indexOf(link);
if (index !== -1) {
this.links.splice(index, 1);
}
},
activeLink: this.state.activeLink,
scrollTo: this.handleScrollTo,
onClick: this.props.onClick,
constructor(props: AnchorProps) {
super(props);
this.state = {
activeLink: null,
};
this.links = [];
}
getChildContext() {
return {
antAnchor: {
registerLink: (link: String) => {
if (!this.links.includes(link)) {
this.links.push(link);
}
},
unregisterLink: (link: String) => {
const index = this.links.indexOf(link);
if (index !== -1) {
this.links.splice(index, 1);
}
},
activeLink: this.state.activeLink,
scrollTo: this.handleScrollTo,
},
};
return { antAnchor };
}
componentDidMount() {
const { getContainer } = this.props as AnchorDefaultProps;
this.scrollEvent = addEventListener(getContainer(), 'scroll', this.handleScroll);
const getTarget = this.props.target || getDefaultTarget;
this.scrollEvent = addEventListener(getTarget(), 'scroll', this.handleScroll);
this.handleScroll();
}
@@ -183,36 +160,32 @@ export default class Anchor extends React.Component<AnchorProps, AnchorState> {
});
}
handleScrollTo = (link: string) => {
const { offsetTop, getContainer } = this.props as AnchorDefaultProps;
handleScrollTo = (link) => {
const { offsetTop, target = getDefaultTarget } = this.props;
this.animating = true;
this.setState({ activeLink: link });
scrollTo(link, offsetTop, getContainer, () => {
scrollTo(link, offsetTop, target, () => {
this.animating = false;
});
}
getCurrentAnchor(offsetTop = 0, bounds = 5): string {
getCurrentAnchor(offsetTop = 0, bounds = 5) {
let activeLink = '';
if (typeof document === 'undefined') {
return activeLink;
}
const linkSections: Array<Section> = [];
const { getContainer } = this.props as AnchorDefaultProps;
const container = getContainer();
this.links.forEach(link => {
const sharpLinkMatch = sharpMatcherRegx.exec(link.toString());
if (!sharpLinkMatch) { return; }
const target = document.getElementById(sharpLinkMatch[1]);
if (target) {
const top = getOffsetTop(target, container);
if (top < offsetTop + bounds) {
linkSections.push({
link,
top,
});
}
if (target && getOffsetTop(target) < offsetTop + bounds) {
const top = getOffsetTop(target);
linkSections.push({
link,
top,
});
}
});
@@ -228,17 +201,12 @@ export default class Anchor extends React.Component<AnchorProps, AnchorState> {
return;
}
const { prefixCls } = this.props;
const anchorNode = ReactDOM.findDOMNode(this) as Element;
const linkNode = anchorNode.getElementsByClassName(`${prefixCls}-link-title-active`)[0];
const linkNode = ReactDOM.findDOMNode(this as any).getElementsByClassName(`${prefixCls}-link-title-active`)[0];
if (linkNode) {
this.inkNode.style.top = `${(linkNode as any).offsetTop + linkNode.clientHeight / 2 - 4.5}px`;
this.refs.ink.style.top = `${(linkNode as any).offsetTop + linkNode.clientHeight / 2 - 4.5}px`;
}
}
saveInkNode = (node: HTMLSpanElement) => {
this.inkNode = node;
}
render() {
const {
prefixCls,
@@ -248,7 +216,6 @@ export default class Anchor extends React.Component<AnchorProps, AnchorState> {
affix,
showInkInFixed,
children,
getContainer,
} = this.props;
const { activeLink } = this.state;
@@ -262,19 +229,11 @@ export default class Anchor extends React.Component<AnchorProps, AnchorState> {
'fixed': !affix && !showInkInFixed,
});
const wrapperStyle = {
maxHeight: offsetTop ? `calc(100vh - ${offsetTop}px)` : '100vh',
...style,
};
const anchorContent = (
<div
className={wrapperClass}
style={wrapperStyle}
>
<div className={wrapperClass} style={style}>
<div className={anchorClass}>
<div className={`${prefixCls}-ink`} >
<span className={inkClass} ref={this.saveInkNode} />
<span className={inkClass} ref="ink" />
</div>
{children}
</div>
@@ -282,7 +241,7 @@ export default class Anchor extends React.Component<AnchorProps, AnchorState> {
);
return !affix ? anchorContent : (
<Affix offsetTop={offsetTop} target={getContainer}>
<Affix offsetTop={offsetTop}>
{anchorContent}
</Affix>
);

View File

@@ -1,7 +1,6 @@
import * as React from 'react';
import * as PropTypes from 'prop-types';
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { AntAnchor } from './Anchor';
export interface AnchorLinkProps {
prefixCls?: string;
@@ -21,32 +20,19 @@ export default class AnchorLink extends React.Component<AnchorLinkProps, any> {
};
context: {
antAnchor: AntAnchor;
antAnchor: any;
};
componentDidMount() {
this.context.antAnchor.registerLink(this.props.href);
}
componentWillReceiveProps(nextProps: AnchorLinkProps) {
const { href } = nextProps;
if (this.props.href !== href) {
this.context.antAnchor.unregisterLink(this.props.href);
this.context.antAnchor.registerLink(href);
}
}
componentWillUnmount() {
this.context.antAnchor.unregisterLink(this.props.href);
}
handleClick = (e: React.MouseEvent<HTMLElement>) => {
const { scrollTo, onClick } = this.context.antAnchor;
const { href, title } = this.props;
if (onClick) {
onClick(e, { title, href });
}
scrollTo(href);
handleClick = () => {
this.context.antAnchor.scrollTo(this.props.href);
}
render() {

View File

@@ -14,8 +14,8 @@ describe('Anchor Render', () => {
wrapper.find('a[href="#API"]').simulate('click');
wrapper.instance().handleScroll();
expect(wrapper.instance().state).not.toBe(null);
wrapper.node.handleScroll();
expect(wrapper.node.state).not.toBe(null);
});
it('Anchor render perfectly for complete href - click', () => {
@@ -25,10 +25,10 @@ describe('Anchor Render', () => {
</Anchor>
);
wrapper.find('a[href="http://www.example.com/#API"]').simulate('click');
expect(wrapper.instance().state.activeLink).toBe('http://www.example.com/#API');
expect(wrapper.node.state.activeLink).toBe('http://www.example.com/#API');
});
it('Anchor render perfectly for complete href - scroll', () => {
it('Anchor render perfectly for complete href - scoll', () => {
let root = document.getElementById('root');
if (!root) {
root = document.createElement('div', { id: 'root' });
@@ -41,12 +41,11 @@ describe('Anchor Render', () => {
<Link href="http://www.example.com/#API" title="API" />
</Anchor>
);
wrapper.instance().handleScroll();
expect(wrapper.instance().state.activeLink).toBe('http://www.example.com/#API');
wrapper.node.handleScroll();
expect(wrapper.node.state.activeLink).toBe('http://www.example.com/#API');
});
it('Anchor render perfectly for complete href - scrollTo', async () => {
const scrollToSpy = jest.spyOn(window, 'scrollTo');
it('Anchor render perfectly for complete href - scollTo', () => {
let root = document.getElementById('root');
if (!root) {
root = document.createElement('div', { id: 'root' });
@@ -59,69 +58,7 @@ describe('Anchor Render', () => {
<Link href="##API" title="API" />
</Anchor>
);
wrapper.instance().handleScrollTo('##API');
expect(wrapper.instance().state.activeLink).toBe('##API');
expect(scrollToSpy).not.toHaveBeenCalled();
await new Promise(resolve => setTimeout(resolve, 1000));
expect(scrollToSpy).toHaveBeenCalled();
});
it('should remove listener when unmount', async () => {
const wrapper = mount(
<Anchor>
<Link href="#API" title="API" />
</Anchor>
);
const removeListenerSpy = jest.spyOn(wrapper.instance().scrollEvent, 'remove');
wrapper.unmount();
expect(removeListenerSpy).toHaveBeenCalled();
});
it('should unregister link when unmount children', async () => {
const wrapper = mount(
<Anchor>
<Link href="#API" title="API" />
</Anchor>
);
expect(wrapper.instance().links).toEqual(['#API']);
wrapper.setProps({ children: null });
expect(wrapper.instance().links).toEqual([]);
});
it('should update links when link href update', async () => {
let anchorInstance = null;
function AnchorUpdate({ href }) {
return (
<Anchor ref={c => anchorInstance = c}>
<Link href={href} title="API" />
</Anchor>
);
}
const wrapper = mount(<AnchorUpdate href="#API" />);
expect(anchorInstance.links).toEqual(['#API']);
wrapper.setProps({ href: '#API_1' });
expect(anchorInstance.links).toEqual(['#API_1']);
});
it('Anchor onClick event', () => {
let event;
let link;
const handleClick = (...arg) => ([event, link] = arg);
const href = '#API';
const title = 'API';
const wrapper = mount(
<Anchor onClick={handleClick}>
<Link href={href} title={title} />
</Anchor>
);
wrapper.find(`a[href="${href}"]`).simulate('click');
wrapper.instance().handleScroll();
expect(event).not.toBe(undefined);
expect(link).toEqual({ href, title });
wrapper.node.handleScrollTo('##API');
expect(wrapper.node.state.activeLink).toBe('##API');
});
});

View File

@@ -7,7 +7,6 @@ exports[`renders ./components/anchor/demo/basic.md correctly 1`] = `
>
<div
class="ant-anchor-wrapper"
style="max-height:100vh"
>
<div
class="ant-anchor"
@@ -80,84 +79,9 @@ exports[`renders ./components/anchor/demo/basic.md correctly 1`] = `
</div>
`;
exports[`renders ./components/anchor/demo/onClick.md correctly 1`] = `
exports[`renders ./components/anchor/demo/fixed.md correctly 1`] = `
<div
class="ant-anchor-wrapper"
style="max-height:100vh"
>
<div
class="ant-anchor fixed"
>
<div
class="ant-anchor-ink"
>
<span
class="ant-anchor-ink-ball"
/>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#components-anchor-demo-basic"
title="Basic demo"
>
Basic demo
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#components-anchor-demo-fixed"
title="Fixed demo"
>
Fixed demo
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#API"
title="API"
>
API
</a>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#Anchor-Props"
title="Anchor Props"
>
Anchor Props
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#Link-Props"
title="Link Props"
>
Link Props
</a>
</div>
</div>
</div>
</div>
`;
exports[`renders ./components/anchor/demo/static.md correctly 1`] = `
<div
class="ant-anchor-wrapper"
style="max-height:100vh"
>
<div
class="ant-anchor fixed"

View File

@@ -15,7 +15,6 @@ The simplest usage.
```jsx
import { Anchor } from 'antd';
const { Link } = Anchor;
ReactDOM.render(
@@ -26,8 +25,8 @@ ReactDOM.render(
<Link href="#Anchor-Props" title="Anchor Props" />
<Link href="#Link-Props" title="Link Props" />
</Link>
</Anchor>,
mountNode);
</Anchor>
, mountNode);
```
<style>

View File

@@ -1,8 +1,8 @@
---
order: 2
title:
zh-CN: 静态位置
en-US: Static Anchor
zh-CN: 固定
en-US: Fixed Anchor
---
## zh-CN
@@ -15,7 +15,6 @@ Do not change state when page is scrolling.
```jsx
import { Anchor } from 'antd';
const { Link } = Anchor;
ReactDOM.render(
@@ -26,6 +25,6 @@ ReactDOM.render(
<Link href="#Anchor-Props" title="Anchor Props" />
<Link href="#Link-Props" title="Link Props" />
</Link>
</Anchor>,
mountNode);
</Anchor>
, mountNode);
```

View File

@@ -1,36 +0,0 @@
---
order: 3
title:
zh-CN: 自定义 onClick 事件
en-US: Customize the onClick event
---
## zh-CN
点击锚点不记录历史。
## en-US
Clicking on an anchor does not record history.
```jsx
import { Anchor } from 'antd';
const { Link } = Anchor;
const handleClick = (e, link) => {
e.preventDefault();
console.log(link);
};
ReactDOM.render(
<Anchor affix={false} onClick={handleClick}>
<Link href="#components-anchor-demo-basic" title="Basic demo" />
<Link href="#components-anchor-demo-fixed" title="Fixed demo" />
<Link href="#API" title="API">
<Link href="#Anchor-Props" title="Anchor Props" />
<Link href="#Link-Props" title="Link Props" />
</Link>
</Anchor>,
mountNode);
```

View File

@@ -17,13 +17,11 @@ For displaying anchor hyperlinks on page and jumping between them.
| Property | Description | Type | Default |
| -------- | ----------- | ---- | ------- |
| affix | Fixed mode of Anchor | boolean | true |
| affix | Fixed mode of Anchor | boolean | false |
| bounds | Bounding distance of anchor area | number | 5(px) |
| getContainer | Scrolling container | () => HTMLElement | () => window |
| offsetBottom | Pixels to offset from bottom when calculating position of scroll | number | - |
| offsetTop | Pixels to offset from top when calculating position of scroll | number | 0 |
| showInkInFixed | Whether show ink-balls in Fixed mode | boolean | false |
| onClick | set the handler to handle `click` event | Function(e: Event, link: Object) | - |
### Link Props

View File

@@ -1,8 +1,5 @@
import Anchor from './Anchor';
import AnchorLink from './AnchorLink';
export { AnchorProps } from './Anchor';
export { AnchorLinkProps } from './AnchorLink';
Anchor.Link = AnchorLink;
export default Anchor;

View File

@@ -18,13 +18,11 @@ title: Anchor
| 成员 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| affix | 固定模式 | boolean | true |
| affix | 固定模式 | boolean | false |
| bounds | 锚点区域边界 | number | 5(px) |
| getContainer | 指定滚动的容器 | () => HTMLElement | () => window |
| offsetBottom | 距离窗口底部达到指定偏移量后触发 | number | |
| offsetTop | 距离窗口顶部达到指定偏移量后触发 | number | |
| showInkInFixed | 固定模式是否显示小圆点 | boolean | false |
| onClick | `click` 事件的 handler | Function(e: Event, link: Object) | - |
### Link Props

View File

@@ -1,18 +1,9 @@
@import "../../style/themes/default";
@import "../../style/mixins/index";
@anchor-border-width: 2px;
.@{ant-prefix}-anchor {
.reset-component;
position: relative;
padding-left: @anchor-border-width;
&-wrapper {
background-color: @component-background;
overflow: auto;
padding-left: 4px;
margin-left: -4px;
}
&-ink {
@@ -23,7 +14,7 @@
&:before {
content: ' ';
position: relative;
width: @anchor-border-width;
width: 2px;
height: 100%;
display: block;
background-color: @border-color-split;
@@ -32,10 +23,10 @@
&-ball {
display: none;
position: absolute;
width: 8px;
height: 8px;
border-radius: 8px;
border: 2px solid @primary-color;
width: 9px;
height: 9px;
border-radius: 9px;
border: 3px solid @primary-color;
background-color: @component-background;
left: 50%;
transition: top .3s ease-in-out;
@@ -51,7 +42,7 @@
}
&-link {
padding: 8px 0 8px 16px;
padding: 8px 0 8px 18px;
line-height: 1;
&-title {

View File

@@ -1,22 +1,18 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import React from 'react';
import { findDOMNode } from 'react-dom';
export interface InputElementProps {
children: React.ReactElement<any>;
}
export default class InputElement extends React.Component<InputElementProps, any> {
export default class InputElement extends React.Component<any, any> {
private ele: HTMLInputElement;
focus = () => {
this.ele.focus ? this.ele.focus() : (ReactDOM.findDOMNode(this.ele) as HTMLInputElement).focus();
this.ele.focus ? this.ele.focus() : (findDOMNode(this.ele) as HTMLInputElement).focus();
}
blur = () => {
this.ele.blur ? this.ele.blur() : (ReactDOM.findDOMNode(this.ele) as HTMLInputElement).blur();
this.ele.blur ? this.ele.blur() : (findDOMNode(this.ele) as HTMLInputElement).blur();
}
saveRef = (ele: HTMLInputElement) => {
this.ele = ele;
const { ref: childRef } = this.props.children as any;
const childRef = this.props.children.ref;
if (typeof childRef === 'function') {
childRef(ele);
}

View File

@@ -3,7 +3,7 @@
exports[`renders ./components/auto-complete/demo/basic.md correctly 1`] = `
<div
class="ant-select-show-search ant-select-auto-complete ant-select ant-select-combobox ant-select-enabled"
style="width:200px"
style="width:200px;"
>
<div
aria-autocomplete="list"
@@ -18,8 +18,8 @@ exports[`renders ./components/auto-complete/demo/basic.md correctly 1`] = `
>
<div
class="ant-select-selection__placeholder"
style="display:block;user-select:none;-webkit-user-select:none"
unselectable="on"
style="display:block;user-select:none;-webkit-user-select:none;"
unselectable="unselectable"
>
input here
</div>
@@ -46,26 +46,10 @@ exports[`renders ./components/auto-complete/demo/basic.md correctly 1`] = `
</div>
<span
class="ant-select-arrow"
style="user-select:none;-webkit-user-select:none"
unselectable="on"
style="user-select:none;-webkit-user-select:none;"
unselectable="unselectable"
>
<i
class="anticon anticon-down ant-select-arrow-icon"
>
<svg
aria-hidden="true"
class=""
data-icon="down"
fill="currentColor"
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>
</i>
<b />
</span>
</div>
</div>
@@ -74,11 +58,11 @@ exports[`renders ./components/auto-complete/demo/basic.md correctly 1`] = `
exports[`renders ./components/auto-complete/demo/certain-category.md correctly 1`] = `
<div
class="certain-category-search-wrapper"
style="width:250px"
style="width:250px;"
>
<div
class="ant-select-lg ant-select-lg certain-category-search ant-select-show-search ant-select-auto-complete ant-select ant-select-combobox ant-select-enabled"
style="width:100%"
style="width:100%;"
>
<div
aria-autocomplete="list"
@@ -93,8 +77,8 @@ exports[`renders ./components/auto-complete/demo/certain-category.md correctly 1
>
<div
class="ant-select-selection__placeholder"
style="display:block;user-select:none;-webkit-user-select:none"
unselectable="on"
style="display:block;user-select:none;-webkit-user-select:none;"
unselectable="unselectable"
>
input here
</div>
@@ -118,21 +102,7 @@ exports[`renders ./components/auto-complete/demo/certain-category.md correctly 1
>
<i
class="anticon anticon-search certain-category-icon"
>
<svg
aria-hidden="true"
class=""
data-icon="search"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M608 112c-167.9 0-304 136.1-304 304 0 70.3 23.9 135 63.9 186.5L114.3 856.1a8.03 8.03 0 0 0 0 11.3l42.3 42.3c3.1 3.1 8.2 3.1 11.3 0l253.6-253.6C473 696.1 537.7 720 608 720c167.9 0 304-136.1 304-304S775.9 112 608 112zm161.2 465.2C726.2 620.3 668.9 644 608 644s-118.2-23.7-161.2-66.8C403.7 534.2 380 476.9 380 416s23.7-118.2 66.8-161.2c43-43.1 100.3-66.8 161.2-66.8s118.2 23.7 161.2 66.8c43.1 43 66.8 100.3 66.8 161.2s-23.7 118.2-66.8 161.2z"
/>
</svg>
</i>
/>
</span>
</span>
<span
@@ -146,26 +116,10 @@ exports[`renders ./components/auto-complete/demo/certain-category.md correctly 1
</div>
<span
class="ant-select-arrow"
style="user-select:none;-webkit-user-select:none"
unselectable="on"
style="user-select:none;-webkit-user-select:none;"
unselectable="unselectable"
>
<i
class="anticon anticon-down ant-select-arrow-icon"
>
<svg
aria-hidden="true"
class=""
data-icon="down"
fill="currentColor"
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>
</i>
<b />
</span>
</div>
</div>
@@ -175,7 +129,7 @@ exports[`renders ./components/auto-complete/demo/certain-category.md correctly 1
exports[`renders ./components/auto-complete/demo/custom.md correctly 1`] = `
<div
class="ant-select-show-search ant-select-auto-complete ant-select ant-select-combobox ant-select-enabled"
style="width:200px"
style="width:200px;"
>
<div
aria-autocomplete="list"
@@ -198,7 +152,7 @@ exports[`renders ./components/auto-complete/demo/custom.md correctly 1`] = `
<textarea
class="ant-input custom ant-select-search__field"
placeholder="input here"
style="height:50px"
style="height:50px;"
/>
<span
class="ant-select-search__field__mirror"
@@ -211,26 +165,10 @@ exports[`renders ./components/auto-complete/demo/custom.md correctly 1`] = `
</div>
<span
class="ant-select-arrow"
style="user-select:none;-webkit-user-select:none"
unselectable="on"
style="user-select:none;-webkit-user-select:none;"
unselectable="unselectable"
>
<i
class="anticon anticon-down ant-select-arrow-icon"
>
<svg
aria-hidden="true"
class=""
data-icon="down"
fill="currentColor"
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>
</i>
<b />
</span>
</div>
</div>
@@ -239,7 +177,7 @@ exports[`renders ./components/auto-complete/demo/custom.md correctly 1`] = `
exports[`renders ./components/auto-complete/demo/non-case-sensitive.md correctly 1`] = `
<div
class="ant-select-show-search ant-select-auto-complete ant-select ant-select-combobox ant-select-enabled"
style="width:200px"
style="width:200px;"
>
<div
aria-autocomplete="list"
@@ -254,8 +192,8 @@ exports[`renders ./components/auto-complete/demo/non-case-sensitive.md correctly
>
<div
class="ant-select-selection__placeholder"
style="display:block;user-select:none;-webkit-user-select:none"
unselectable="on"
style="display:block;user-select:none;-webkit-user-select:none;"
unselectable="unselectable"
>
try to type \`b\`
</div>
@@ -282,26 +220,10 @@ exports[`renders ./components/auto-complete/demo/non-case-sensitive.md correctly
</div>
<span
class="ant-select-arrow"
style="user-select:none;-webkit-user-select:none"
unselectable="on"
style="user-select:none;-webkit-user-select:none;"
unselectable="unselectable"
>
<i
class="anticon anticon-down ant-select-arrow-icon"
>
<svg
aria-hidden="true"
class=""
data-icon="down"
fill="currentColor"
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>
</i>
<b />
</span>
</div>
</div>
@@ -310,7 +232,7 @@ exports[`renders ./components/auto-complete/demo/non-case-sensitive.md correctly
exports[`renders ./components/auto-complete/demo/options.md correctly 1`] = `
<div
class="ant-select-show-search ant-select-auto-complete ant-select ant-select-combobox ant-select-enabled"
style="width:200px"
style="width:200px;"
>
<div
aria-autocomplete="list"
@@ -325,8 +247,8 @@ exports[`renders ./components/auto-complete/demo/options.md correctly 1`] = `
>
<div
class="ant-select-selection__placeholder"
style="display:block;user-select:none;-webkit-user-select:none"
unselectable="on"
style="display:block;user-select:none;-webkit-user-select:none;"
unselectable="unselectable"
>
input here
</div>
@@ -353,26 +275,10 @@ exports[`renders ./components/auto-complete/demo/options.md correctly 1`] = `
</div>
<span
class="ant-select-arrow"
style="user-select:none;-webkit-user-select:none"
unselectable="on"
style="user-select:none;-webkit-user-select:none;"
unselectable="unselectable"
>
<i
class="anticon anticon-down ant-select-arrow-icon"
>
<svg
aria-hidden="true"
class=""
data-icon="down"
fill="currentColor"
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>
</i>
<b />
</span>
</div>
</div>
@@ -381,11 +287,11 @@ exports[`renders ./components/auto-complete/demo/options.md correctly 1`] = `
exports[`renders ./components/auto-complete/demo/uncertain-category.md correctly 1`] = `
<div
class="global-search-wrapper"
style="width:300px"
style="width:300px;"
>
<div
class="ant-select-lg ant-select-lg global-search ant-select-show-search ant-select-auto-complete ant-select ant-select-combobox ant-select-enabled"
style="width:100%"
style="width:100%;"
>
<div
aria-autocomplete="list"
@@ -400,8 +306,8 @@ exports[`renders ./components/auto-complete/demo/uncertain-category.md correctly
>
<div
class="ant-select-selection__placeholder"
style="display:block;user-select:none;-webkit-user-select:none"
unselectable="on"
style="display:block;user-select:none;-webkit-user-select:none;"
unselectable="unselectable"
>
input here
</div>
@@ -429,21 +335,7 @@ exports[`renders ./components/auto-complete/demo/uncertain-category.md correctly
>
<i
class="anticon anticon-search"
>
<svg
aria-hidden="true"
class=""
data-icon="search"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M608 112c-167.9 0-304 136.1-304 304 0 70.3 23.9 135 63.9 186.5L114.3 856.1a8.03 8.03 0 0 0 0 11.3l42.3 42.3c3.1 3.1 8.2 3.1 11.3 0l253.6-253.6C473 696.1 537.7 720 608 720c167.9 0 304-136.1 304-304S775.9 112 608 112zm161.2 465.2C726.2 620.3 668.9 644 608 644s-118.2-23.7-161.2-66.8C403.7 534.2 380 476.9 380 416s23.7-118.2 66.8-161.2c43-43.1 100.3-66.8 161.2-66.8s118.2 23.7 161.2 66.8c43.1 43 66.8 100.3 66.8 161.2s-23.7 118.2-66.8 161.2z"
/>
</svg>
</i>
/>
</button>
</span>
</span>
@@ -458,26 +350,10 @@ exports[`renders ./components/auto-complete/demo/uncertain-category.md correctly
</div>
<span
class="ant-select-arrow"
style="user-select:none;-webkit-user-select:none"
unselectable="on"
style="user-select:none;-webkit-user-select:none;"
unselectable="unselectable"
>
<i
class="anticon anticon-down ant-select-arrow-icon"
>
<svg
aria-hidden="true"
class=""
data-icon="down"
fill="currentColor"
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>
</i>
<b />
</span>
</div>
</div>

View File

@@ -1,11 +1,8 @@
import React from 'react';
import { mount } from 'enzyme';
import AutoComplete from '..';
import focusTest from '../../../tests/shared/focusTest';
describe('AutoComplete with Custom Input Element Render', () => {
focusTest(AutoComplete);
it('AutoComplete with custom Input render perfectly', () => {
const wrapper = mount(
<AutoComplete dataSource={['12345', '23456', '34567']}>
@@ -15,7 +12,7 @@ describe('AutoComplete with Custom Input Element Render', () => {
expect(wrapper.find('textarea').length).toBe(1);
wrapper.find('textarea').simulate('change', { target: { value: '123' } });
const dropdownWrapper = mount(wrapper.find('Trigger').instance().getComponent());
const dropdownWrapper = mount(wrapper.find('Trigger').node.getComponent());
// should not filter data source defaultly
expect(dropdownWrapper.find('MenuItem').length).toBe(3);

View File

@@ -16,7 +16,6 @@ Basic Usage, set datasource of autocomplete with `dataSource` property.
````jsx
import { Icon, Input, AutoComplete } from 'antd';
const Option = AutoComplete.Option;
const OptGroup = AutoComplete.OptGroup;

View File

@@ -15,7 +15,6 @@ Customize Input Component
````jsx
import { AutoComplete, Input } from 'antd';
const { TextArea } = Input;
function onSelect(value) {

View File

@@ -16,7 +16,6 @@ Basic Usage, set datasource of autocomplete with `dataSource` property.
````jsx
import { Icon, Button, Input, AutoComplete } from 'antd';
const Option = AutoComplete.Option;
function onSelect(value) {

View File

@@ -21,7 +21,6 @@ const dataSource = ['12345', '23456', '34567'];
| Property | Description | Type | Default |
| -------- | ----------- | ---- | ------- |
| allowClear | Show clear button, effective in multiple mode only. | boolean | false |
| autoFocus | get focus when component mounted | boolean | false |
| backfill | backfill selected item the input when using keyboard | boolean | false |
| children (for customize input element) | customize input element | HTMLInputElement / HTMLTextAreaElement / React.ReactElement<InputProps> | `<Input />` |
| children (for dataSource) | Data source for autocomplet | React.ReactElement<OptionProps> / Array&lt;React.ReactElement<OptionProps>> | - |
@@ -33,15 +32,6 @@ const dataSource = ['12345', '23456', '34567'];
| optionLabelProp | Which prop value of option will render as content of select. | string | `children` |
| placeholder | placeholder of input | string | - |
| value | selected option | string\|string\[]\|{ key: string, label: string\|ReactNode }\|Array&lt;{ key: string, label: string\|ReactNode }> | - |
| 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) | - |
| onFocus | Called when entering the component | function() | - |
| onChange | Called when select an option or input value change, or value of input is changed | function(value, label) | - |
| 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) | - |
## Methods
| Name | Description |
| ---- | ----------- |
| blur() | remove focus |
| focus() | get focus |

View File

@@ -1,4 +1,4 @@
import * as React from 'react';
import React from 'react';
import { Option, OptGroup } from 'rc-select';
import classNames from 'classnames';
import Select, { AbstractSelectProps, SelectValue, OptionProps, OptGroupProps } from '../select';
@@ -6,13 +6,9 @@ import Input from '../input';
import InputElement from './InputElement';
export interface DataSourceItemObject { value: string; text: string; }
export type DataSourceItemType =
string |
DataSourceItemObject |
React.ReactElement<OptionProps> |
React.ReactElement<OptGroupProps>;
export type DataSourceItemType = string | DataSourceItemObject;
export interface AutoCompleteInputProps {
export interface InputProps {
onChange?: React.FormEventHandler<any>;
value: any;
}
@@ -20,14 +16,12 @@ export interface AutoCompleteInputProps {
export type ValidInputElement =
HTMLInputElement |
HTMLTextAreaElement |
React.ReactElement<AutoCompleteInputProps>;
React.ReactElement<InputProps>;
export interface AutoCompleteProps extends AbstractSelectProps {
value?: SelectValue;
defaultValue?: SelectValue;
dataSource?: DataSourceItemType[];
autoFocus?: boolean;
backfill?: boolean;
dataSource: DataSourceItemType[];
optionLabelProp?: string;
onChange?: (value: SelectValue) => void;
onSelect?: (value: SelectValue, option: Object) => any;
@@ -40,7 +34,7 @@ function isSelectOptionOrSelectOptGroup(child: any): Boolean {
return child && child.type && (child.type.isSelectOption || child.type.isSelectOptGroup);
}
export default class AutoComplete extends React.Component<AutoCompleteProps, {}> {
export default class AutoComplete extends React.Component<AutoCompleteProps, any> {
static Option = Option as React.ClassicComponentClass<OptionProps>;
static OptGroup = OptGroup as React.ClassicComponentClass<OptGroupProps>;
@@ -53,8 +47,6 @@ export default class AutoComplete extends React.Component<AutoCompleteProps, {}>
filterOption: false,
};
private select: any;
getInputElement = () => {
const { children } = this.props;
const element = children && React.isValidElement(children) && children.type !== Option ?
@@ -67,18 +59,6 @@ export default class AutoComplete extends React.Component<AutoCompleteProps, {}>
);
}
focus() {
this.select.focus();
}
blur() {
this.select.blur();
}
saveSelect = (node: any) => {
this.select = node;
}
render() {
let {
size, className = '', notFoundContent, prefixCls, optionLabelProp, dataSource, children,
@@ -122,11 +102,10 @@ export default class AutoComplete extends React.Component<AutoCompleteProps, {}>
<Select
{...this.props}
className={cls}
mode={Select.SECRET_COMBOBOX_MODE_DO_NOT_USE}
mode="combobox"
optionLabelProp={optionLabelProp}
getInputElement={this.getInputElement}
notFoundContent={notFoundContent}
ref={this.saveSelect}
>
{options}
</Select>

View File

@@ -22,7 +22,6 @@ const dataSource = ['12345', '23456', '34567'];
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| allowClear | 支持清除, 单选模式有效 | boolean | false |
| autoFocus | 自动获取焦点 | boolean | false |
| backfill | 使用键盘选择选项的时候把选中项回填到输入框中 | boolean | false |
| children (自动完成的数据源) | 自动完成的数据源 | React.ReactElement<OptionProps> / Array&lt;React.ReactElement<OptionProps>> | - |
| children (自定义输入框) | 自定义输入框 | HTMLInputElement / HTMLTextAreaElement / React.ReactElement<InputProps> | `<Input />` |
@@ -34,15 +33,6 @@ const dataSource = ['12345', '23456', '34567'];
| optionLabelProp | 回填到选择框的 Option 的属性值,默认是 Option 的子元素。比如在子元素需要高亮效果时,此值可以设为 `value`。 | string | `children` |
| placeholder | 输入框提示 | string | - |
| value | 指定当前选中的条目 | string\|string\[]\|{ key: string, label: string\|ReactNode }\|Array&lt;{ key: string, label: string\|ReactNode }> | 无 |
| onBlur | 失去焦点时的回调 | function() | - |
| onChange | 选中 option或 input 的 value 变化时,调用此函数 | function(value) | 无 |
| onFocus | 获得焦点时的回调 | function() | - |
| onSearch | 搜索补全项的时候调用 | function(value) | 无 |
| onSelect | 被选中时调用,参数为选中项的 value 值 | function(value, option) | 无 |
## 方法
| 名称 | 描述 |
| --- | --- |
| blur() | 移除焦点 |
| focus() | 获取焦点 |

View File

@@ -7,8 +7,6 @@
@autocomplete-prefix-cls: ~"@{select-prefix-cls}-auto-complete";
.@{autocomplete-prefix-cls} {
.reset-component;
&.@{select-prefix-cls} {
.@{select-prefix-cls} {
&-selection {

View File

@@ -8,60 +8,4 @@ describe('Avatar Render', () => {
const children = wrapper.find('.ant-avatar-string');
expect(children.length).toBe(1);
});
it('should render fallback string correctly', () => {
const div = global.document.createElement('div');
global.document.body.appendChild(div);
const wrapper = mount(<Avatar src="http://error.url">Fallback</Avatar>, { attachTo: div });
wrapper.instance().setScale = jest.fn(() => wrapper.instance().setState({ scale: 0.5 }));
wrapper.find('img').simulate('error');
const children = wrapper.find('.ant-avatar-string');
expect(children.length).toBe(1);
expect(children.text()).toBe('Fallback');
expect(wrapper.instance().setScale).toBeCalled();
expect(div.querySelector('.ant-avatar-string').style.transform).toBe('scale(0.5)');
wrapper.detach();
global.document.body.removeChild(div);
});
it('should handle onError correctly', () => {
const LOAD_FAILURE_SRC = 'http://error.url';
const LOAD_SUCCESS_SRC = 'https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png';
const div = global.document.createElement('div');
global.document.body.appendChild(div);
class Foo extends React.Component {
state = {
src: LOAD_FAILURE_SRC,
}
handleImgError = () => {
this.setState({
src: LOAD_SUCCESS_SRC,
});
return false;
}
render() {
const { src } = this.state;
return <Avatar src={src} onError={this.handleImgError} />;
}
}
const wrapper = mount(<Foo />, { attachTo: div });
// mock img load Error, since jsdom do not load resource by default
// https://github.com/jsdom/jsdom/issues/1816
wrapper.find('img').simulate('error');
expect(wrapper.find(Avatar).instance().state.isImgExist).toBe(true);
expect(div.querySelector('img').getAttribute('src')).toBe(LOAD_SUCCESS_SRC);
wrapper.detach();
global.document.body.removeChild(div);
});
});

View File

@@ -3,7 +3,7 @@
exports[`renders ./components/avatar/demo/badge.md correctly 1`] = `
<div>
<span
style="margin-right:24px"
style="margin-right:24px;"
>
<span
class="ant-badge"
@@ -13,21 +13,7 @@ exports[`renders ./components/avatar/demo/badge.md correctly 1`] = `
>
<i
class="anticon anticon-user"
>
<svg
aria-hidden="true"
class=""
data-icon="user"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M858.5 763.6a374 374 0 0 0-80.6-119.5 375.63 375.63 0 0 0-119.5-80.6c-.4-.2-.8-.3-1.2-.5C719.5 518 760 444.7 760 362c0-137-111-248-248-248S264 225 264 362c0 82.7 40.5 156 102.8 201.1-.4.2-.8.3-1.2.5-44.8 18.9-85 46-119.5 80.6a375.63 375.63 0 0 0-80.6 119.5A371.7 371.7 0 0 0 136 901.8a8 8 0 0 0 8 8.2h60c4.4 0 7.9-3.5 8-7.8 2-77.2 33-149.5 87.8-204.3 56.7-56.7 132-87.9 212.2-87.9s155.5 31.2 212.2 87.9C779 752.7 810 825 812 902.2c.1 4.4 3.6 7.8 8 7.8h60a8 8 0 0 0 8-8.2c-1-47.8-10.9-94.3-29.5-138.2zM512 534c-45.9 0-89.1-17.9-121.6-50.4S340 407.9 340 362c0-45.9 17.9-89.1 50.4-121.6S466.1 190 512 190s89.1 17.9 121.6 50.4S684 316.1 684 362c0 45.9-17.9 89.1-50.4 121.6S557.9 534 512 534z"
/>
</svg>
</i>
/>
</span>
<sup
class="ant-scroll-number ant-badge-count"
@@ -36,7 +22,7 @@ exports[`renders ./components/avatar/demo/badge.md correctly 1`] = `
>
<span
class="ant-scroll-number-only"
style="transition:none;-ms-transform:translateY(-1100%);-webkit-transform:translateY(-1100%);transform:translateY(-1100%)"
style="transition:none;-ms-transform:translateY(-1100%);-webkit-transform:translateY(-1100%);transform:translateY(-1100%);"
>
<p
class=""
@@ -201,21 +187,7 @@ exports[`renders ./components/avatar/demo/badge.md correctly 1`] = `
>
<i
class="anticon anticon-user"
>
<svg
aria-hidden="true"
class=""
data-icon="user"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M858.5 763.6a374 374 0 0 0-80.6-119.5 375.63 375.63 0 0 0-119.5-80.6c-.4-.2-.8-.3-1.2-.5C719.5 518 760 444.7 760 362c0-137-111-248-248-248S264 225 264 362c0 82.7 40.5 156 102.8 201.1-.4.2-.8.3-1.2.5-44.8 18.9-85 46-119.5 80.6a375.63 375.63 0 0 0-80.6 119.5A371.7 371.7 0 0 0 136 901.8a8 8 0 0 0 8 8.2h60c4.4 0 7.9-3.5 8-7.8 2-77.2 33-149.5 87.8-204.3 56.7-56.7 132-87.9 212.2-87.9s155.5 31.2 212.2 87.9C779 752.7 810 825 812 902.2c.1 4.4 3.6 7.8 8 7.8h60a8 8 0 0 0 8-8.2c-1-47.8-10.9-94.3-29.5-138.2zM512 534c-45.9 0-89.1-17.9-121.6-50.4S340 407.9 340 362c0-45.9 17.9-89.1 50.4-121.6S466.1 190 512 190s89.1 17.9 121.6 50.4S684 316.1 684 362c0 45.9-17.9 89.1-50.4 121.6S557.9 534 512 534z"
/>
</svg>
</i>
/>
</span>
<sup
class="ant-scroll-number ant-badge-dot"
@@ -229,177 +201,49 @@ exports[`renders ./components/avatar/demo/badge.md correctly 1`] = `
exports[`renders ./components/avatar/demo/basic.md correctly 1`] = `
<div>
<div>
<span
class="ant-avatar ant-avatar-circle ant-avatar-icon"
style="width:64px;height:64px;line-height:64px;font-size:32px"
>
<i
class="anticon anticon-user"
>
<svg
aria-hidden="true"
class=""
data-icon="user"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M858.5 763.6a374 374 0 0 0-80.6-119.5 375.63 375.63 0 0 0-119.5-80.6c-.4-.2-.8-.3-1.2-.5C719.5 518 760 444.7 760 362c0-137-111-248-248-248S264 225 264 362c0 82.7 40.5 156 102.8 201.1-.4.2-.8.3-1.2.5-44.8 18.9-85 46-119.5 80.6a375.63 375.63 0 0 0-80.6 119.5A371.7 371.7 0 0 0 136 901.8a8 8 0 0 0 8 8.2h60c4.4 0 7.9-3.5 8-7.8 2-77.2 33-149.5 87.8-204.3 56.7-56.7 132-87.9 212.2-87.9s155.5 31.2 212.2 87.9C779 752.7 810 825 812 902.2c.1 4.4 3.6 7.8 8 7.8h60a8 8 0 0 0 8-8.2c-1-47.8-10.9-94.3-29.5-138.2zM512 534c-45.9 0-89.1-17.9-121.6-50.4S340 407.9 340 362c0-45.9 17.9-89.1 50.4-121.6S466.1 190 512 190s89.1 17.9 121.6 50.4S684 316.1 684 362c0 45.9-17.9 89.1-50.4 121.6S557.9 534 512 534z"
/>
</svg>
</i>
</span>
<span
class="ant-avatar ant-avatar-lg ant-avatar-circle ant-avatar-icon"
>
<i
class="anticon anticon-user"
>
<svg
aria-hidden="true"
class=""
data-icon="user"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M858.5 763.6a374 374 0 0 0-80.6-119.5 375.63 375.63 0 0 0-119.5-80.6c-.4-.2-.8-.3-1.2-.5C719.5 518 760 444.7 760 362c0-137-111-248-248-248S264 225 264 362c0 82.7 40.5 156 102.8 201.1-.4.2-.8.3-1.2.5-44.8 18.9-85 46-119.5 80.6a375.63 375.63 0 0 0-80.6 119.5A371.7 371.7 0 0 0 136 901.8a8 8 0 0 0 8 8.2h60c4.4 0 7.9-3.5 8-7.8 2-77.2 33-149.5 87.8-204.3 56.7-56.7 132-87.9 212.2-87.9s155.5 31.2 212.2 87.9C779 752.7 810 825 812 902.2c.1 4.4 3.6 7.8 8 7.8h60a8 8 0 0 0 8-8.2c-1-47.8-10.9-94.3-29.5-138.2zM512 534c-45.9 0-89.1-17.9-121.6-50.4S340 407.9 340 362c0-45.9 17.9-89.1 50.4-121.6S466.1 190 512 190s89.1 17.9 121.6 50.4S684 316.1 684 362c0 45.9-17.9 89.1-50.4 121.6S557.9 534 512 534z"
/>
</svg>
</i>
/>
</span>
<span
class="ant-avatar ant-avatar-circle ant-avatar-icon"
>
<i
class="anticon anticon-user"
>
<svg
aria-hidden="true"
class=""
data-icon="user"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M858.5 763.6a374 374 0 0 0-80.6-119.5 375.63 375.63 0 0 0-119.5-80.6c-.4-.2-.8-.3-1.2-.5C719.5 518 760 444.7 760 362c0-137-111-248-248-248S264 225 264 362c0 82.7 40.5 156 102.8 201.1-.4.2-.8.3-1.2.5-44.8 18.9-85 46-119.5 80.6a375.63 375.63 0 0 0-80.6 119.5A371.7 371.7 0 0 0 136 901.8a8 8 0 0 0 8 8.2h60c4.4 0 7.9-3.5 8-7.8 2-77.2 33-149.5 87.8-204.3 56.7-56.7 132-87.9 212.2-87.9s155.5 31.2 212.2 87.9C779 752.7 810 825 812 902.2c.1 4.4 3.6 7.8 8 7.8h60a8 8 0 0 0 8-8.2c-1-47.8-10.9-94.3-29.5-138.2zM512 534c-45.9 0-89.1-17.9-121.6-50.4S340 407.9 340 362c0-45.9 17.9-89.1 50.4-121.6S466.1 190 512 190s89.1 17.9 121.6 50.4S684 316.1 684 362c0 45.9-17.9 89.1-50.4 121.6S557.9 534 512 534z"
/>
</svg>
</i>
/>
</span>
<span
class="ant-avatar ant-avatar-sm ant-avatar-circle ant-avatar-icon"
>
<i
class="anticon anticon-user"
>
<svg
aria-hidden="true"
class=""
data-icon="user"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M858.5 763.6a374 374 0 0 0-80.6-119.5 375.63 375.63 0 0 0-119.5-80.6c-.4-.2-.8-.3-1.2-.5C719.5 518 760 444.7 760 362c0-137-111-248-248-248S264 225 264 362c0 82.7 40.5 156 102.8 201.1-.4.2-.8.3-1.2.5-44.8 18.9-85 46-119.5 80.6a375.63 375.63 0 0 0-80.6 119.5A371.7 371.7 0 0 0 136 901.8a8 8 0 0 0 8 8.2h60c4.4 0 7.9-3.5 8-7.8 2-77.2 33-149.5 87.8-204.3 56.7-56.7 132-87.9 212.2-87.9s155.5 31.2 212.2 87.9C779 752.7 810 825 812 902.2c.1 4.4 3.6 7.8 8 7.8h60a8 8 0 0 0 8-8.2c-1-47.8-10.9-94.3-29.5-138.2zM512 534c-45.9 0-89.1-17.9-121.6-50.4S340 407.9 340 362c0-45.9 17.9-89.1 50.4-121.6S466.1 190 512 190s89.1 17.9 121.6 50.4S684 316.1 684 362c0 45.9-17.9 89.1-50.4 121.6S557.9 534 512 534z"
/>
</svg>
</i>
/>
</span>
</div>
<div>
<span
class="ant-avatar ant-avatar-square ant-avatar-icon"
style="width:64px;height:64px;line-height:64px;font-size:32px"
>
<i
class="anticon anticon-user"
>
<svg
aria-hidden="true"
class=""
data-icon="user"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M858.5 763.6a374 374 0 0 0-80.6-119.5 375.63 375.63 0 0 0-119.5-80.6c-.4-.2-.8-.3-1.2-.5C719.5 518 760 444.7 760 362c0-137-111-248-248-248S264 225 264 362c0 82.7 40.5 156 102.8 201.1-.4.2-.8.3-1.2.5-44.8 18.9-85 46-119.5 80.6a375.63 375.63 0 0 0-80.6 119.5A371.7 371.7 0 0 0 136 901.8a8 8 0 0 0 8 8.2h60c4.4 0 7.9-3.5 8-7.8 2-77.2 33-149.5 87.8-204.3 56.7-56.7 132-87.9 212.2-87.9s155.5 31.2 212.2 87.9C779 752.7 810 825 812 902.2c.1 4.4 3.6 7.8 8 7.8h60a8 8 0 0 0 8-8.2c-1-47.8-10.9-94.3-29.5-138.2zM512 534c-45.9 0-89.1-17.9-121.6-50.4S340 407.9 340 362c0-45.9 17.9-89.1 50.4-121.6S466.1 190 512 190s89.1 17.9 121.6 50.4S684 316.1 684 362c0 45.9-17.9 89.1-50.4 121.6S557.9 534 512 534z"
/>
</svg>
</i>
</span>
<span
class="ant-avatar ant-avatar-lg ant-avatar-square ant-avatar-icon"
>
<i
class="anticon anticon-user"
>
<svg
aria-hidden="true"
class=""
data-icon="user"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M858.5 763.6a374 374 0 0 0-80.6-119.5 375.63 375.63 0 0 0-119.5-80.6c-.4-.2-.8-.3-1.2-.5C719.5 518 760 444.7 760 362c0-137-111-248-248-248S264 225 264 362c0 82.7 40.5 156 102.8 201.1-.4.2-.8.3-1.2.5-44.8 18.9-85 46-119.5 80.6a375.63 375.63 0 0 0-80.6 119.5A371.7 371.7 0 0 0 136 901.8a8 8 0 0 0 8 8.2h60c4.4 0 7.9-3.5 8-7.8 2-77.2 33-149.5 87.8-204.3 56.7-56.7 132-87.9 212.2-87.9s155.5 31.2 212.2 87.9C779 752.7 810 825 812 902.2c.1 4.4 3.6 7.8 8 7.8h60a8 8 0 0 0 8-8.2c-1-47.8-10.9-94.3-29.5-138.2zM512 534c-45.9 0-89.1-17.9-121.6-50.4S340 407.9 340 362c0-45.9 17.9-89.1 50.4-121.6S466.1 190 512 190s89.1 17.9 121.6 50.4S684 316.1 684 362c0 45.9-17.9 89.1-50.4 121.6S557.9 534 512 534z"
/>
</svg>
</i>
/>
</span>
<span
class="ant-avatar ant-avatar-square ant-avatar-icon"
>
<i
class="anticon anticon-user"
>
<svg
aria-hidden="true"
class=""
data-icon="user"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M858.5 763.6a374 374 0 0 0-80.6-119.5 375.63 375.63 0 0 0-119.5-80.6c-.4-.2-.8-.3-1.2-.5C719.5 518 760 444.7 760 362c0-137-111-248-248-248S264 225 264 362c0 82.7 40.5 156 102.8 201.1-.4.2-.8.3-1.2.5-44.8 18.9-85 46-119.5 80.6a375.63 375.63 0 0 0-80.6 119.5A371.7 371.7 0 0 0 136 901.8a8 8 0 0 0 8 8.2h60c4.4 0 7.9-3.5 8-7.8 2-77.2 33-149.5 87.8-204.3 56.7-56.7 132-87.9 212.2-87.9s155.5 31.2 212.2 87.9C779 752.7 810 825 812 902.2c.1 4.4 3.6 7.8 8 7.8h60a8 8 0 0 0 8-8.2c-1-47.8-10.9-94.3-29.5-138.2zM512 534c-45.9 0-89.1-17.9-121.6-50.4S340 407.9 340 362c0-45.9 17.9-89.1 50.4-121.6S466.1 190 512 190s89.1 17.9 121.6 50.4S684 316.1 684 362c0 45.9-17.9 89.1-50.4 121.6S557.9 534 512 534z"
/>
</svg>
</i>
/>
</span>
<span
class="ant-avatar ant-avatar-sm ant-avatar-square ant-avatar-icon"
>
<i
class="anticon anticon-user"
>
<svg
aria-hidden="true"
class=""
data-icon="user"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M858.5 763.6a374 374 0 0 0-80.6-119.5 375.63 375.63 0 0 0-119.5-80.6c-.4-.2-.8-.3-1.2-.5C719.5 518 760 444.7 760 362c0-137-111-248-248-248S264 225 264 362c0 82.7 40.5 156 102.8 201.1-.4.2-.8.3-1.2.5-44.8 18.9-85 46-119.5 80.6a375.63 375.63 0 0 0-80.6 119.5A371.7 371.7 0 0 0 136 901.8a8 8 0 0 0 8 8.2h60c4.4 0 7.9-3.5 8-7.8 2-77.2 33-149.5 87.8-204.3 56.7-56.7 132-87.9 212.2-87.9s155.5 31.2 212.2 87.9C779 752.7 810 825 812 902.2c.1 4.4 3.6 7.8 8 7.8h60a8 8 0 0 0 8-8.2c-1-47.8-10.9-94.3-29.5-138.2zM512 534c-45.9 0-89.1-17.9-121.6-50.4S340 407.9 340 362c0-45.9 17.9-89.1 50.4-121.6S466.1 190 512 190s89.1 17.9 121.6 50.4S684 316.1 684 362c0 45.9-17.9 89.1-50.4 121.6S557.9 534 512 534z"
/>
</svg>
</i>
/>
</span>
</div>
</div>
@@ -409,7 +253,7 @@ exports[`renders ./components/avatar/demo/dynamic.md correctly 1`] = `
<div>
<span
class="ant-avatar ant-avatar-lg ant-avatar-circle"
style="background-color:#f56a00;vertical-align:middle"
style="background-color:#f56a00;"
>
<span
class="ant-avatar-string"
@@ -419,7 +263,7 @@ exports[`renders ./components/avatar/demo/dynamic.md correctly 1`] = `
</span>
<button
class="ant-btn ant-btn-sm"
style="margin-left:16px;vertical-align:middle"
style="margin-left:16px;"
type="button"
>
<span>
@@ -436,21 +280,7 @@ exports[`renders ./components/avatar/demo/type.md correctly 1`] = `
>
<i
class="anticon anticon-user"
>
<svg
aria-hidden="true"
class=""
data-icon="user"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M858.5 763.6a374 374 0 0 0-80.6-119.5 375.63 375.63 0 0 0-119.5-80.6c-.4-.2-.8-.3-1.2-.5C719.5 518 760 444.7 760 362c0-137-111-248-248-248S264 225 264 362c0 82.7 40.5 156 102.8 201.1-.4.2-.8.3-1.2.5-44.8 18.9-85 46-119.5 80.6a375.63 375.63 0 0 0-80.6 119.5A371.7 371.7 0 0 0 136 901.8a8 8 0 0 0 8 8.2h60c4.4 0 7.9-3.5 8-7.8 2-77.2 33-149.5 87.8-204.3 56.7-56.7 132-87.9 212.2-87.9s155.5 31.2 212.2 87.9C779 752.7 810 825 812 902.2c.1 4.4 3.6 7.8 8 7.8h60a8 8 0 0 0 8-8.2c-1-47.8-10.9-94.3-29.5-138.2zM512 534c-45.9 0-89.1-17.9-121.6-50.4S340 407.9 340 362c0-45.9 17.9-89.1 50.4-121.6S466.1 190 512 190s89.1 17.9 121.6 50.4S684 316.1 684 362c0 45.9-17.9 89.1-50.4 121.6S557.9 534 512 534z"
/>
</svg>
</i>
/>
</span>
<span
class="ant-avatar ant-avatar-circle"
@@ -479,7 +309,7 @@ exports[`renders ./components/avatar/demo/type.md correctly 1`] = `
</span>
<span
class="ant-avatar ant-avatar-circle"
style="color:#f56a00;background-color:#fde3cf"
style="color:#f56a00;background-color:#fde3cf;"
>
<span
class="ant-avatar-string"
@@ -489,25 +319,11 @@ exports[`renders ./components/avatar/demo/type.md correctly 1`] = `
</span>
<span
class="ant-avatar ant-avatar-circle ant-avatar-icon"
style="background-color:#87d068"
style="background-color:#87d068;"
>
<i
class="anticon anticon-user"
>
<svg
aria-hidden="true"
class=""
data-icon="user"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M858.5 763.6a374 374 0 0 0-80.6-119.5 375.63 375.63 0 0 0-119.5-80.6c-.4-.2-.8-.3-1.2-.5C719.5 518 760 444.7 760 362c0-137-111-248-248-248S264 225 264 362c0 82.7 40.5 156 102.8 201.1-.4.2-.8.3-1.2.5-44.8 18.9-85 46-119.5 80.6a375.63 375.63 0 0 0-80.6 119.5A371.7 371.7 0 0 0 136 901.8a8 8 0 0 0 8 8.2h60c4.4 0 7.9-3.5 8-7.8 2-77.2 33-149.5 87.8-204.3 56.7-56.7 132-87.9 212.2-87.9s155.5 31.2 212.2 87.9C779 752.7 810 825 812 902.2c.1 4.4 3.6 7.8 8 7.8h60a8 8 0 0 0 8-8.2c-1-47.8-10.9-94.3-29.5-138.2zM512 534c-45.9 0-89.1-17.9-121.6-50.4S340 407.9 340 362c0-45.9 17.9-89.1 50.4-121.6S466.1 190 512 190s89.1 17.9 121.6 50.4S684 316.1 684 362c0 45.9-17.9 89.1-50.4 121.6S557.9 534 512 534z"
/>
</svg>
</i>
/>
</span>
</div>
`;

View File

@@ -24,6 +24,6 @@ ReactDOM.render(
<span>
<Badge dot><Avatar shape="square" icon="user" /></Badge>
</span>
</div>,
mountNode);
</div>
, mountNode);
````

View File

@@ -19,19 +19,17 @@ import { Avatar } from 'antd';
ReactDOM.render(
<div>
<div>
<Avatar size={64} icon="user" />
<Avatar size="large" icon="user" />
<Avatar icon="user" />
<Avatar size="small" icon="user" />
</div>
<div>
<Avatar shape="square" size={64} icon="user" />
<Avatar shape="square" size="large" icon="user" />
<Avatar shape="square" icon="user" />
<Avatar shape="square" size="small" icon="user" />
</div>
</div>,
mountNode);
</div>
, mountNode);
````
<style>

View File

@@ -27,7 +27,6 @@ class Autoset extends React.Component {
color: colorList[0],
};
}
changeUser = () => {
const index = UserList.indexOf(this.state.user);
this.setState({
@@ -35,21 +34,16 @@ class Autoset extends React.Component {
color: index < colorList.length - 1 ? colorList[index + 1] : colorList[0],
});
}
render() {
return (
<div>
<Avatar style={{ backgroundColor: this.state.color, verticalAlign: 'middle' }} size="large">
{this.state.user}
</Avatar>
<Button size="small" style={{ marginLeft: 16, verticalAlign: 'middle' }} onClick={this.changeUser}>
Change
</Button>
<Avatar style={{ backgroundColor: this.state.color }} size="large">{this.state.user}</Avatar>
<Button size="small" style={{ marginLeft: 16 }} onClick={this.changeUser}>Change</Button>
</div>
);
}
}
ReactDOM.render(<Autoset />,
mountNode);
ReactDOM.render(<Autoset />
, mountNode);
````

View File

@@ -24,8 +24,8 @@ ReactDOM.render(
<Avatar src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" />
<Avatar style={{ color: '#f56a00', backgroundColor: '#fde3cf' }}>U</Avatar>
<Avatar style={{ backgroundColor: '#87d068' }} icon="user" />
</div>,
mountNode);
</div>
, mountNode);
````
<style>

View File

@@ -12,7 +12,5 @@ Avatars can be used to represent people or objects. It supports images, `Icon`s,
| -------- | ----------- | ---- | ------- |
| icon | the `Icon` type for an icon avatar, see `Icon` Component | string | - |
| shape | the shape of avatar | `circle` \| `square` | `circle` |
| size | the size of the avatar | number \| string: `large` `small` `default` | `default` |
| size | the size of the avatar | `large` \| `small` \| `default` | `default` |
| src | the address of the image for an image avatar | string | - |
| alt | This attribute defines the alternative text describing the image | string | - |
| onError | handler when img load errorreturn false to prevent default fallback behavior | () => boolean | - |

View File

@@ -1,16 +1,13 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import React from 'react';
import ReactDOM from 'react-dom';
import Icon from '../icon';
import classNames from 'classnames';
export interface AvatarProps {
/** Shape of avatar, options:`circle`, `square` */
shape?: 'circle' | 'square';
/*
* Size of avatar, options: `large`, `small`, `default`
* or a custom number size
* */
size?: 'large' | 'small' | 'default' | number;
/** Size of avatar, options:`large`, `small`, `default` */
size?: 'large' | 'small' | 'default';
/** Src of image avatar */
src?: string;
/** Type of the Icon to be used in avatar */
@@ -19,10 +16,6 @@ export interface AvatarProps {
prefixCls?: string;
className?: string;
children?: any;
alt?: string;
/* callback when img load error */
/* return false to prevent Avatar show default fallback behavior, then you can do fallback by your self*/
onError?: () => boolean;
}
export interface AvatarState {
@@ -53,8 +46,7 @@ export default class Avatar extends React.Component<AvatarProps, AvatarState> {
componentDidUpdate(prevProps: AvatarProps, prevState: AvatarState) {
if (prevProps.children !== this.props.children
|| (prevState.scale !== this.state.scale && this.state.scale === 1)
|| (prevState.isImgExist !== this.state.isImgExist)) {
|| (prevState.scale !== this.state.scale && this.state.scale === 1)) {
this.setScale();
}
}
@@ -63,8 +55,7 @@ export default class Avatar extends React.Component<AvatarProps, AvatarState> {
const childrenNode = this.avatarChildren;
if (childrenNode) {
const childrenWidth = childrenNode.offsetWidth;
const avatarNode = ReactDOM.findDOMNode(this) as Element;
const avatarWidth = avatarNode.getBoundingClientRect().width;
const avatarWidth = ReactDOM.findDOMNode(this).getBoundingClientRect().width;
// add 4px gap for each side to get better performance
if (avatarWidth - 8 < childrenWidth) {
this.setState({
@@ -78,21 +69,13 @@ export default class Avatar extends React.Component<AvatarProps, AvatarState> {
}
}
handleImgLoadError = () => {
const { onError } = this.props;
const errorFlag = onError ? onError() : undefined;
if (errorFlag !== false) {
this.setState({ isImgExist: false });
}
}
handleImgLoadError = () => this.setState({ isImgExist: false });
render() {
const {
prefixCls, shape, size, src, icon, className, alt, ...others
prefixCls, shape, size, src, icon, className, ...others,
} = this.props;
const { isImgExist, scale } = this.state;
const sizeCls = classNames({
[`${prefixCls}-lg`]: size === 'large',
[`${prefixCls}-sm`]: size === 'small',
@@ -100,48 +83,36 @@ export default class Avatar extends React.Component<AvatarProps, AvatarState> {
const classString = classNames(prefixCls, className, sizeCls, {
[`${prefixCls}-${shape}`]: shape,
[`${prefixCls}-image`]: src && isImgExist,
[`${prefixCls}-image`]: src,
[`${prefixCls}-icon`]: icon,
});
const sizeStyle: React.CSSProperties = typeof size === 'number' ? {
width: size,
height: size,
lineHeight: `${size}px`,
fontSize: icon ? size / 2 : 18,
} : {};
let children = this.props.children;
if (src && isImgExist) {
if (src && this.state.isImgExist) {
children = (
<img
src={src}
onError={this.handleImgLoadError}
alt={alt}
/>
);
} else if (icon) {
children = <Icon type={icon} />;
} else {
const childrenNode = this.avatarChildren;
if (childrenNode || scale !== 1) {
if (childrenNode || this.state.scale !== 1) {
const childrenStyle: React.CSSProperties = {
msTransform: `scale(${scale})`,
WebkitTransform: `scale(${scale})`,
transform: `scale(${scale})`,
msTransform: `scale(${this.state.scale})`,
WebkitTransform: `scale(${this.state.scale})`,
transform: `scale(${this.state.scale})`,
position: 'absolute',
display: 'inline-block',
left: `calc(50% - ${Math.round(childrenNode.offsetWidth / 2)}px)`,
};
const sizeChildrenStyle: React.CSSProperties =
typeof size === 'number' ? {
lineHeight: `${size}px`,
} : {};
children = (
<span
className={`${prefixCls}-string`}
ref={span => this.avatarChildren = span}
style={{ ...sizeChildrenStyle, ...childrenStyle }}
style={childrenStyle}
>
{children}
</span>
@@ -158,11 +129,7 @@ export default class Avatar extends React.Component<AvatarProps, AvatarState> {
}
}
return (
<span
{...others}
style={{ ...sizeStyle, ...others.style }}
className={classString}
>
<span {...others} className={classString}>
{children}
</span>
);

View File

@@ -7,10 +7,6 @@ title: Avatar
用来代表用户或事物,支持图片、图标或字符展示。
## 设计师专属
安装 [Kitchen Sketch 插件 💎](https://kitchen.alipay.com),一键填充高逼格头像和文本.
## API
| 参数 | 说明 | 类型 | 默认值 |
@@ -19,5 +15,3 @@ title: Avatar
| shape | 指定头像的形状 | Enum{ 'circle', 'square' } | `circle` |
| size | 设置头像的大小 | Enum{ 'large', 'small', 'default' } | `default` |
| src | 图片类头像的资源地址 | string | - |
| alt | 图像无法显示时的替代文本 | string | - |
| onError | 图片加载失败的事件,返回 false 会关闭组件默认的 fallback 行为 | () => boolean | - |

View File

@@ -1,10 +1,8 @@
@import "../../style/themes/default";
@import "../../style/mixins/index";
@avatar-prefix-cls: ~"@{ant-prefix}-avatar";
.@{avatar-prefix-cls} {
.reset-component;
display: inline-block;
text-align: center;
background: @avatar-bg;
@@ -12,11 +10,6 @@
white-space: nowrap;
position: relative;
overflow: hidden;
vertical-align: middle;
&-image {
background: transparent;
}
.avatar-size(@avatar-size-base, @avatar-font-size-base);
@@ -43,7 +36,7 @@
width: @size;
height: @size;
line-height: @size;
border-radius: 50%;
border-radius: @size / 2;
& > * {
line-height: @size;

View File

@@ -4,7 +4,7 @@ exports[`renders ./components/back-top/demo/basic.md correctly 1`] = `
<div>
Scroll down to see the bottom-right
<strong
style="color:rgba(64, 64, 64, 0.6)"
style="color:rgba(64, 64, 64, 0.6);"
>
gray
</strong>
@@ -16,7 +16,7 @@ exports[`renders ./components/back-top/demo/custom.md correctly 1`] = `
<div>
Scroll down to see the bottom-right
<strong
style="color:#1088e9"
style="color:#1088e9;"
>
blue
</strong>

View File

@@ -15,10 +15,10 @@ describe('BackTop', () => {
const wrapper = mount(<BackTop visibilityHeight={-1} />);
document.documentElement.scrollTop = 400;
// trigger scroll manually
wrapper.instance().handleScroll();
wrapper.node.handleScroll();
jest.runAllTimers();
wrapper.find('.ant-back-top').simulate('click');
jest.runAllTimers();
expect(Math.abs(Math.round(document.documentElement.scrollTop))).toBe(0);
expect(Math.round(document.documentElement.scrollTop)).toBe(0);
});
});

View File

@@ -1,10 +1,13 @@
import * as React from 'react';
import React from 'react';
import Animate from 'rc-animate';
import addEventListener from 'rc-util/lib/Dom/addEventListener';
import classNames from 'classnames';
import omit from 'omit.js';
import Icon from '../icon';
import getScroll from '../_util/getScroll';
import raf from 'raf';
import getRequestAnimationFrame from '../_util/getRequestAnimationFrame';
const reqAnimFrame = getRequestAnimationFrame();
const easeInOutCubic = (t: number, b: number, c: number, d: number) => {
const cc = c - b;
@@ -62,12 +65,10 @@ export default class BackTop extends React.Component<BackTopProps, any> {
const time = timestamp - startTime;
this.setScrollTop(easeInOutCubic(time, scrollTop, 0, 450));
if (time < 450) {
raf(frameFunc);
} else {
this.setScrollTop(0);
reqAnimFrame(frameFunc);
}
};
raf(frameFunc);
reqAnimFrame(frameFunc);
(this.props.onClick || noop)(e);
}
@@ -108,7 +109,7 @@ export default class BackTop extends React.Component<BackTopProps, any> {
const defaultElement = (
<div className={`${prefixCls}-content`}>
<div className={`${prefixCls}-icon`} />
<Icon className={`${prefixCls}-icon`} type="to-top" />
</div>
);
@@ -118,7 +119,6 @@ export default class BackTop extends React.Component<BackTopProps, any> {
'className',
'children',
'visibilityHeight',
'target',
]);
const backTopBtn = this.state.visible ? (

View File

@@ -1,10 +1,8 @@
@import "../../style/themes/default";
@import "../../style/mixins/index";
@backtop-prefix-cls: ~"@{ant-prefix}-back-top";
.@{backtop-prefix-cls} {
.reset-component;
z-index: @zindex-back-top;
position: fixed;
right: 100px;
@@ -21,7 +19,6 @@
color: @back-top-color;
text-align: center;
transition: all .3s @ease-in-out;
overflow: hidden;
&:hover {
background-color: @back-top-hover-bg;
@@ -30,11 +27,7 @@
}
&-icon {
margin: 12px auto;
width: 14px;
height: 16px;
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAoCAYAAACWwljjAAAABGdBTUEAALGPC/xhBQAAAbtJREFUWAntmMtKw0AUhhMvS5cuxILgQlRUpIggIoKIIoigG1eC+AA+jo+i6FIXBfeuXIgoeKVeitVWJX5HWhhDksnUpp3FDPyZk3Nm5nycmZKkXhAEOXSA3lG7muTeRzmfy6HneUvIhnYkQK+Q9NhAA0Opg0vBEhjBKHiyb8iGMyQMOYuK41BcBSypAL+MYXSKjtFAW7EAGEO3qN4uMQbbAkXiSfRQJ1H6a+yhlkKRcAoVFYiweYNjtCVQJJpBz2GCiPt7fBOZQpFgDpUikse5HgnkM4Fi4QX0Fpc5wf9EbLqpUCy4jMoJSXWhFwbMNgWKhVbRhy5jirhs9fy/oFhgHVVTJEs7RLZ8sSEoJm6iz7SZDMbJ+/OKERQTttCXQRLToRUmrKWCYuA2+jbN0MB4OQobYShfdTCgn/sL1K36M7TLrN3n+758aPy2rrpR6+/od5E8tf/A1uLS9aId5T7J3CNYihkQ4D9PiMdMC7mp4rjB9kjFjZp8BlnVHJBuO1yFXIV0FdDF3RlyFdJVQBdv5AxVdIsq8apiZ2PyYO1EVykesGfZEESsCkweyR8MUW+V8uJ1gkYipmpdP1pm2aJVPEGzAAAAAElFTkSuQmCC) ~"100%/100%" no-repeat;
font-size: 20px;
margin-top: 10px;
}
}
@import './responsive';

View File

@@ -1,11 +0,0 @@
@media screen and (max-width: @screen-md) {
.@{backtop-prefix-cls} {
right: 60px;
}
}
@media screen and (max-width: @screen-xs) {
.@{backtop-prefix-cls} {
right: 20px;
}
}

View File

@@ -1,9 +1,9 @@
import * as React from 'react';
import React from 'react';
import { createElement, Component } from 'react';
import omit from 'omit.js';
import classNames from 'classnames';
function getNumberArray(num: string | number | undefined | null) {
function getNumberArray(num) {
return num ?
num.toString()
.split('')
@@ -14,19 +14,14 @@ function getNumberArray(num: string | number | undefined | null) {
export interface ScrollNumberProps {
prefixCls?: string;
className?: string;
count?: string | number | null;
count?: string | number;
component?: string;
onAnimated?: Function;
style?: React.CSSProperties;
title?: string | number | null;
title?: string | number;
}
export interface ScrollNumberState {
animateStarted?: boolean;
count?: string | number | null;
}
export default class ScrollNumber extends Component<ScrollNumberProps, ScrollNumberState> {
export default class ScrollNumber extends Component<ScrollNumberProps, any> {
static defaultProps = {
prefixCls: 'ant-scroll-number',
count: null,
@@ -36,7 +31,7 @@ export default class ScrollNumber extends Component<ScrollNumberProps, ScrollNum
lastCount: any;
constructor(props: ScrollNumberProps) {
constructor(props) {
super(props);
this.state = {
animateStarted: true,
@@ -44,14 +39,14 @@ export default class ScrollNumber extends Component<ScrollNumberProps, ScrollNum
};
}
getPositionByNum(num: number, i: number) {
getPositionByNum(num, i) {
if (this.state.animateStarted) {
return 10 + num;
}
const currentDigit = getNumberArray(this.state.count)[i];
const lastDigit = getNumberArray(this.lastCount)[i];
// 同方向则在同一侧切换数字
if (this.state.count! > this.lastCount) {
if (this.state.count > this.lastCount) {
if (currentDigit >= lastDigit) {
return 10 + num;
}
@@ -63,7 +58,7 @@ export default class ScrollNumber extends Component<ScrollNumberProps, ScrollNum
return num;
}
componentWillReceiveProps(nextProps: ScrollNumberProps) {
componentWillReceiveProps(nextProps) {
if ('count' in nextProps) {
if (this.state.count === nextProps.count) {
return;
@@ -90,7 +85,7 @@ export default class ScrollNumber extends Component<ScrollNumberProps, ScrollNum
}
}
renderNumberList(position: number) {
renderNumberList(position) {
const childrenToReturn: React.ReactElement<any>[] = [];
for (let i = 0; i < 30; i++) {
const currentClassName = (position === i) ? 'current' : '';
@@ -99,14 +94,14 @@ export default class ScrollNumber extends Component<ScrollNumberProps, ScrollNum
return childrenToReturn;
}
renderCurrentNumber(num: number, i: number) {
renderCurrentNumber(num, i) {
const position = this.getPositionByNum(num, i);
const removeTransition = this.state.animateStarted ||
(getNumberArray(this.lastCount)[i] === undefined);
return createElement('span', {
className: `${this.props.prefixCls}-only`,
style: {
transition: removeTransition ? 'none' : undefined,
transition: removeTransition && 'none',
msTransform: `translateY(${-position * 100}%)`,
WebkitTransform: `translateY(${-position * 100}%)`,
transform: `translateY(${-position * 100}%)`,
@@ -117,7 +112,7 @@ export default class ScrollNumber extends Component<ScrollNumberProps, ScrollNum
renderNumberElement() {
const state = this.state;
if (!state.count || isNaN(state.count as number)) {
if (!state.count || isNaN(state.count)) {
return state.count;
}
return getNumberArray(state.count)

View File

@@ -16,7 +16,7 @@ exports[`renders ./components/badge/demo/basic.md correctly 1`] = `
>
<span
class="ant-scroll-number-only"
style="transition:none;-ms-transform:translateY(-1500%);-webkit-transform:translateY(-1500%);transform:translateY(-1500%)"
style="transition:none;-ms-transform:translateY(-1500%);-webkit-transform:translateY(-1500%);transform:translateY(-1500%);"
>
<p
class=""
@@ -206,7 +206,7 @@ exports[`renders ./components/badge/demo/change.md correctly 1`] = `
>
<span
class="ant-scroll-number-only"
style="transition:none;-ms-transform:translateY(-1500%);-webkit-transform:translateY(-1500%);transform:translateY(-1500%)"
style="transition:none;-ms-transform:translateY(-1500%);-webkit-transform:translateY(-1500%);transform:translateY(-1500%);"
>
<p
class=""
@@ -370,21 +370,7 @@ exports[`renders ./components/badge/demo/change.md correctly 1`] = `
>
<i
class="anticon anticon-minus"
>
<svg
aria-hidden="true"
class=""
data-icon="minus"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M872 474H152c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h720c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z"
/>
</svg>
</i>
/>
</button>
<button
class="ant-btn"
@@ -392,26 +378,12 @@ exports[`renders ./components/badge/demo/change.md correctly 1`] = `
>
<i
class="anticon anticon-plus"
>
<svg
aria-hidden="true"
class=""
data-icon="plus"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M848 474H550V152h-76v322H176c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h298v322h76V550h298c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z"
/>
</svg>
</i>
/>
</button>
</div>
</div>
<div
style="margin-top:10px"
style="margin-top:10px;"
>
<span
class="ant-badge"
@@ -445,47 +417,12 @@ exports[`renders ./components/badge/demo/dot.md correctly 1`] = `
>
<i
class="anticon anticon-notification"
>
<svg
aria-hidden="true"
class=""
data-icon="notification"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M880 112c-3.8 0-7.7.7-11.6 2.3L292 345.9H128c-8.8 0-16 7.4-16 16.6v299c0 9.2 7.2 16.6 16 16.6h101.7c-3.7 11.6-5.7 23.9-5.7 36.4 0 65.9 53.8 119.5 120 119.5 55.4 0 102.1-37.6 115.9-88.4l408.6 164.2c3.9 1.5 7.8 2.3 11.6 2.3 16.9 0 32-14.2 32-33.2V145.2C912 126.2 897 112 880 112zM344 762.3c-26.5 0-48-21.4-48-47.8 0-11.2 3.9-21.9 11-30.4l84.9 34.1c-2 24.6-22.7 44.1-47.9 44.1zm496 58.4L318.8 611.3l-12.9-5.2H184V417.9h121.9l12.9-5.2L840 203.3v617.4z"
/>
</svg>
</i>
/>
<sup
class="ant-scroll-number ant-badge-dot"
data-show="true"
/>
</span>
<span
class="ant-badge"
>
<i
class="anticon anticon-notification"
>
<svg
aria-hidden="true"
class=""
data-icon="notification"
fill="currentColor"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M880 112c-3.8 0-7.7.7-11.6 2.3L292 345.9H128c-8.8 0-16 7.4-16 16.6v299c0 9.2 7.2 16.6 16 16.6h101.7c-3.7 11.6-5.7 23.9-5.7 36.4 0 65.9 53.8 119.5 120 119.5 55.4 0 102.1-37.6 115.9-88.4l408.6 164.2c3.9 1.5 7.8 2.3 11.6 2.3 16.9 0 32-14.2 32-33.2V145.2C912 126.2 897 112 880 112zM344 762.3c-26.5 0-48-21.4-48-47.8 0-11.2 3.9-21.9 11-30.4l84.9 34.1c-2 24.6-22.7 44.1-47.9 44.1zm496 58.4L318.8 611.3l-12.9-5.2H184V417.9h121.9l12.9-5.2L840 203.3v617.4z"
/>
</svg>
</i>
</span>
<span
class="ant-badge"
>
@@ -519,7 +456,7 @@ exports[`renders ./components/badge/demo/link.md correctly 1`] = `
>
<span
class="ant-scroll-number-only"
style="transition:none;-ms-transform:translateY(-1500%);-webkit-transform:translateY(-1500%);transform:translateY(-1500%)"
style="transition:none;-ms-transform:translateY(-1500%);-webkit-transform:translateY(-1500%);transform:translateY(-1500%);"
>
<p
class=""
@@ -683,13 +620,13 @@ exports[`renders ./components/badge/demo/no-wrapper.md correctly 1`] = `
class="ant-badge ant-badge-not-a-wrapper"
>
<sup
class="ant-scroll-number ant-badge-count ant-badge-multiple-words"
class="ant-scroll-number ant-badge-count"
data-show="true"
title="25"
>
<span
class="ant-scroll-number-only"
style="transition:none;-ms-transform:translateY(-1200%);-webkit-transform:translateY(-1200%);transform:translateY(-1200%)"
style="transition:none;-ms-transform:translateY(-1200%);-webkit-transform:translateY(-1200%);transform:translateY(-1200%);"
>
<p
class=""
@@ -844,7 +781,7 @@ exports[`renders ./components/badge/demo/no-wrapper.md correctly 1`] = `
</span>
<span
class="ant-scroll-number-only"
style="transition:none;-ms-transform:translateY(-1500%);-webkit-transform:translateY(-1500%);transform:translateY(-1500%)"
style="transition:none;-ms-transform:translateY(-1500%);-webkit-transform:translateY(-1500%);transform:translateY(-1500%);"
>
<p
class=""
@@ -1005,12 +942,12 @@ exports[`renders ./components/badge/demo/no-wrapper.md correctly 1`] = `
<sup
class="ant-scroll-number ant-badge-count"
data-show="true"
style="background-color:#fff;color:#999;box-shadow:0 0 0 1px #d9d9d9 inset"
style="background-color:#fff;color:#999;box-shadow:0 0 0 1px #d9d9d9 inset;"
title="4"
>
<span
class="ant-scroll-number-only"
style="transition:none;-ms-transform:translateY(-1400%);-webkit-transform:translateY(-1400%);transform:translateY(-1400%)"
style="transition:none;-ms-transform:translateY(-1400%);-webkit-transform:translateY(-1400%);transform:translateY(-1400%);"
>
<p
class=""
@@ -1169,9 +1106,9 @@ exports[`renders ./components/badge/demo/no-wrapper.md correctly 1`] = `
class="ant-badge ant-badge-not-a-wrapper"
>
<sup
class="ant-scroll-number ant-badge-count ant-badge-multiple-words"
class="ant-scroll-number ant-badge-count"
data-show="true"
style="background-color:#52c41a"
style="background-color:#87d068;"
title="109"
>
99+
@@ -1190,13 +1127,13 @@ exports[`renders ./components/badge/demo/overflow.md correctly 1`] = `
href="#"
/>
<sup
class="ant-scroll-number ant-badge-count ant-badge-multiple-words"
class="ant-scroll-number ant-badge-count"
data-show="true"
title="99"
>
<span
class="ant-scroll-number-only"
style="transition:none;-ms-transform:translateY(-1900%);-webkit-transform:translateY(-1900%);transform:translateY(-1900%)"
style="transition:none;-ms-transform:translateY(-1900%);-webkit-transform:translateY(-1900%);transform:translateY(-1900%);"
>
<p
class=""
@@ -1351,7 +1288,7 @@ exports[`renders ./components/badge/demo/overflow.md correctly 1`] = `
</span>
<span
class="ant-scroll-number-only"
style="transition:none;-ms-transform:translateY(-1900%);-webkit-transform:translateY(-1900%);transform:translateY(-1900%)"
style="transition:none;-ms-transform:translateY(-1900%);-webkit-transform:translateY(-1900%);transform:translateY(-1900%);"
>
<p
class=""
@@ -1514,7 +1451,7 @@ exports[`renders ./components/badge/demo/overflow.md correctly 1`] = `
href="#"
/>
<sup
class="ant-scroll-number ant-badge-count ant-badge-multiple-words"
class="ant-scroll-number ant-badge-count"
data-show="true"
title="100"
>
@@ -1529,7 +1466,7 @@ exports[`renders ./components/badge/demo/overflow.md correctly 1`] = `
href="#"
/>
<sup
class="ant-scroll-number ant-badge-count ant-badge-multiple-words"
class="ant-scroll-number ant-badge-count"
data-show="true"
title="99"
>
@@ -1544,7 +1481,7 @@ exports[`renders ./components/badge/demo/overflow.md correctly 1`] = `
href="#"
/>
<sup
class="ant-scroll-number ant-badge-count ant-badge-multiple-words"
class="ant-scroll-number ant-badge-count"
data-show="true"
title="1000"
>
@@ -1673,177 +1610,3 @@ exports[`renders ./components/badge/demo/status.md correctly 1`] = `
</span>
</div>
`;
exports[`renders ./components/badge/demo/title.md correctly 1`] = `
<div>
<span
class="ant-badge"
>
<a
class="head-example"
href="#"
/>
<sup
class="ant-scroll-number ant-badge-count"
data-show="true"
title="Custom hover text"
>
<span
class="ant-scroll-number-only"
style="transition:none;-ms-transform:translateY(-1500%);-webkit-transform:translateY(-1500%);transform:translateY(-1500%)"
>
<p
class=""
>
0
</p>
<p
class=""
>
1
</p>
<p
class=""
>
2
</p>
<p
class=""
>
3
</p>
<p
class=""
>
4
</p>
<p
class=""
>
5
</p>
<p
class=""
>
6
</p>
<p
class=""
>
7
</p>
<p
class=""
>
8
</p>
<p
class=""
>
9
</p>
<p
class=""
>
0
</p>
<p
class=""
>
1
</p>
<p
class=""
>
2
</p>
<p
class=""
>
3
</p>
<p
class=""
>
4
</p>
<p
class="current"
>
5
</p>
<p
class=""
>
6
</p>
<p
class=""
>
7
</p>
<p
class=""
>
8
</p>
<p
class=""
>
9
</p>
<p
class=""
>
0
</p>
<p
class=""
>
1
</p>
<p
class=""
>
2
</p>
<p
class=""
>
3
</p>
<p
class=""
>
4
</p>
<p
class=""
>
5
</p>
<p
class=""
>
6
</p>
<p
class=""
>
7
</p>
<p
class=""
>
8
</p>
<p
class=""
>
9
</p>
</span>
</sup>
</span>
</div>
`;

File diff suppressed because it is too large Load Diff

View File

@@ -1,65 +0,0 @@
import React from 'react';
import { mount, render } from 'enzyme';
import Badge from '../index';
import Tooltip from '../../tooltip';
describe('Badge', () => {
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.useRealTimers();
});
it('badge dot not scaling count > 9', () => {
const badge = mount(<Badge count={10} dot />);
expect(badge.find('.ant-card-multiple-words').length).toBe(0);
});
it('badge dot not showing count == 0', () => {
const badge = mount(<Badge count={0} dot />);
expect(badge.find('.ant-badge-dot').length).toBe(0);
});
it('should have an overriden title attribute', () => {
const badge = mount(<Badge count={10} title="Custom title" />);
expect(badge.find('.ant-scroll-number').getDOMNode().attributes.getNamedItem('title').value).toEqual('Custom title');
});
// https://github.com/ant-design/ant-design/issues/10626
it('should be composable with Tooltip', () => {
const wrapper = mount(
<Tooltip title="Fix the error">
<Badge status="error" />
</Tooltip>
);
wrapper.find('Badge').simulate('mouseenter');
jest.runAllTimers();
expect(wrapper.instance().tooltip.props.visible).toBe(true);
});
it('should render when count is changed', () => {
const wrapper = mount(<Badge count={9} />);
wrapper.setProps({ count: 10 });
jest.runAllTimers();
expect(wrapper).toMatchSnapshot();
wrapper.setProps({ count: 11 });
jest.runAllTimers();
expect(wrapper).toMatchSnapshot();
wrapper.setProps({ count: 11 });
jest.runAllTimers();
expect(wrapper).toMatchSnapshot();
wrapper.setProps({ count: 10 });
jest.runAllTimers();
expect(wrapper).toMatchSnapshot();
jest.runAllTimers();
wrapper.setProps({ count: 9 });
expect(wrapper).toMatchSnapshot();
});
it('should be compatible with borderColor style', () => {
const wrapper = render(<Badge count={4} style={{ backgroundColor: '#fff', color: '#999', borderColor: '#d9d9d9' }} />);
expect(wrapper).toMatchSnapshot();
});
});

View File

@@ -24,18 +24,18 @@ ReactDOM.render(
<Badge count={0} showZero>
<a href="#" className="head-example" />
</Badge>
</div>,
mountNode);
</div>
, mountNode);
````
<style>
.ant-badge:not(.ant-badge-not-a-wrapper) {
margin-right: 20px;
.ant-badge:not(.ant-badge-status) {
margin-right: 16px;
}
.head-example {
width: 42px;
height: 42px;
border-radius: 4px;
border-radius: 6px;
background: #eee;
display: inline-block;
}

View File

@@ -15,7 +15,6 @@ The count will be animated as it changes.
````jsx
import { Badge, Button, Icon, Switch } from 'antd';
const ButtonGroup = Button.Group;
class Demo extends React.Component {

View File

@@ -12,7 +12,6 @@ title:
## en-US
This will simply display a red badge, without a specific count.
If count equals 0, it won't display the dot.
````jsx
import { Badge, Icon } from 'antd';
@@ -22,14 +21,11 @@ ReactDOM.render(
<Badge dot>
<Icon type="notification" />
</Badge>
<Badge count={0} dot>
<Icon type="notification" />
</Badge>
<Badge dot>
<a href="#">Link something</a>
</Badge>
</div>,
mountNode);
</div>
, mountNode);
````
<style>

View File

@@ -21,6 +21,6 @@ ReactDOM.render(
<Badge count={5}>
<span className="head-example" />
</Badge>
</a>,
mountNode);
</a>
, mountNode);
````

View File

@@ -22,13 +22,7 @@ ReactDOM.render(
<div>
<Badge count={25} />
<Badge count={4} style={{ backgroundColor: '#fff', color: '#999', boxShadow: '0 0 0 1px #d9d9d9 inset' }} />
<Badge count={109} style={{ backgroundColor: '#52c41a' }} />
</div>,
mountNode);
<Badge count={109} style={{ backgroundColor: '#87d068' }} />
</div>
, mountNode);
````
<style>
.ant-badge-not-a-wrapper:not(.ant-badge-status) {
margin-right: 8px;
}
</style>

View File

@@ -30,6 +30,6 @@ ReactDOM.render(
<Badge count={1000} overflowCount={999}>
<a href="#" className="head-example" />
</Badge>
</div>,
mountNode);
</div>
, mountNode);
````

View File

@@ -33,6 +33,6 @@ ReactDOM.render(
<Badge status="processing" text="Processing" />
<br />
<Badge status="warning" text="Warning" />
</div>,
mountNode);
</div>
, mountNode);
````

View File

@@ -1,39 +0,0 @@
---
order: 7
title:
zh-CN: 自定义标题
en-US: Title
---
## zh-CN
设置鼠标放在状态点上时显示的文字
## en-US
The badge will display `title` when hovered over, instead of `count`.
````jsx
import { Badge } from 'antd';
ReactDOM.render(
<div>
<Badge count={5} title="Custom hover text">
<a href="#" className="head-example" />
</Badge>
</div>,
mountNode);
````
<style>
.ant-badge:not(.ant-badge-not-a-wrapper) {
margin-right: 20px;
}
.head-example {
width: 42px;
height: 42px;
border-radius: 4px;
background: #eee;
display: inline-block;
}
</style>

View File

@@ -24,11 +24,9 @@ Badge normally appears in proximity to notifications or user avatars with eye-ca
| Property | Description | Type | Default |
| -------- | ----------- | ---- | ------- |
| count | Number to show in badge | number\|ReactNode | |
| count | Number to show in badge | number | |
| dot | Whether to display a red dot instead of `count` | boolean | `false` |
| offset | set offset of the badge dot, like`[x, y]` | `[number, number]` | - |
| overflowCount | Max count to show | number | 99 |
| showZero | Whether to show badge when `count` is zero | boolean | `false` |
| status | Set Badge as a status dot | `success` \| `processing` \| `default` \| `error` \| `warning` | `''` |
| text | If `status` is set, `text` sets the display text of the status `dot` | string | `''` |
| title | Text to show when hovering over the badge | string | `count` |

View File

@@ -1,14 +1,13 @@
import * as React from 'react';
import * as PropTypes from 'prop-types';
import React from 'react';
import PropTypes from 'prop-types';
import Animate from 'rc-animate';
import ScrollNumber from './ScrollNumber';
import classNames from 'classnames';
export { ScrollNumberProps } from './ScrollNumber';
import warning from '../_util/warning';
export interface BadgeProps {
/** Number to show in badge */
count?: number | string | null;
count?: number | string;
showZero?: boolean;
/** Max count to show */
overflowCount?: number;
@@ -20,8 +19,6 @@ export interface BadgeProps {
className?: string;
status?: 'success' | 'processing' | 'default' | 'error' | 'warning';
text?: string;
offset?: [number | string, number | string];
title?: string;
}
export default class Badge extends React.Component<BadgeProps, any> {
@@ -57,42 +54,39 @@ export default class Badge extends React.Component<BadgeProps, any> {
dot,
status,
text,
offset,
title,
...restProps
...restProps,
} = this.props;
const isDot = dot || status;
let displayCount = (count as number) > (overflowCount as number) ? `${overflowCount}+` : count;
const isZero = displayCount === '0' || displayCount === 0;
const isDot = (dot && !isZero) || status;
// dot mode don't need count
if (isDot) {
displayCount = '';
}
const isZero = displayCount === '0' || displayCount === 0;
const isEmpty = displayCount === null || displayCount === undefined || displayCount === '';
const hidden = (isEmpty || (isZero && !showZero)) && !isDot;
const statusCls = classNames({
[`${prefixCls}-status-dot`]: !!status,
[`${prefixCls}-status-${status}`]: !!status,
});
const scrollNumberCls = classNames({
[`${prefixCls}-dot`]: isDot,
[`${prefixCls}-count`]: !isDot,
[`${prefixCls}-multiple-words`]: !isDot && count && count.toString && count.toString().length > 1,
[`${prefixCls}-status-${status}`]: !!status,
});
const badgeCls = classNames(className, prefixCls, {
[`${prefixCls}-status`]: !!status,
[`${prefixCls}-not-a-wrapper`]: !children,
});
const styleWithOffset = offset ? {
marginLeft: offset[0],
marginTop: offset[1],
...style,
} : style;
warning(
!(children && status),
'`Badge[children]` and `Badge[status]` cannot be used at the same time.',
);
// <Badge status="success" />
if (!children && status) {
const statusCls = classNames({
[`${prefixCls}-status-dot`]: !!status,
[`${prefixCls}-status-${status}`]: true,
});
return (
<span {...restProps} className={badgeCls} style={styleWithOffset}>
<span className={badgeCls}>
<span className={statusCls} />
<span className={`${prefixCls}-status-text`}>{text}</span>
</span>
@@ -105,9 +99,8 @@ export default class Badge extends React.Component<BadgeProps, any> {
data-show={!hidden}
className={scrollNumberCls}
count={displayCount}
title={title || count}
style={styleWithOffset}
key="scrollNumber"
title={count}
style={style}
/>
);

View File

@@ -25,11 +25,9 @@ title: Badge
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| count | 展示的数字,大于 overflowCount 时显示为 `${overflowCount}+`,为 0 时隐藏 | number\|ReactNode | |
| count | 展示的数字,大于 overflowCount 时显示为 `${overflowCount}+`,为 0 时隐藏 | number | |
| dot | 不展示数字,只有一个小红点 | boolean | false |
| offset | 设置状态点的位置偏移,格式为 `[x, y]` | `[number, number]` | - |
| overflowCount | 展示封顶的数字值 | number | 99 |
| showZero | 当数值为 0 时,是否展示 Badge | boolean | false |
| status | 设置 Badge 为状态点 | Enum{ 'success', 'processing, 'default', 'error', 'warning' } | '' |
| text | 在设置了 `status` 的前提下有效,设置状态点的文本 | string | '' |
| title | 设置鼠标放在状态点上时显示的文字 | string | `count` |

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