mirror of
https://github.com/ant-design/ant-design.git
synced 2026-02-09 10:59:19 +08:00
Compare commits
348 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
40a987e059 | ||
|
|
705a6d63a1 | ||
|
|
79289fa708 | ||
|
|
4dccbd8eab | ||
|
|
2ab7c3f182 | ||
|
|
313f2df632 | ||
|
|
626f918597 | ||
|
|
04eb91dc8e | ||
|
|
e188537c00 | ||
|
|
e26eb5f892 | ||
|
|
d7c7b86f31 | ||
|
|
0a70563e44 | ||
|
|
7f1e7a3120 | ||
|
|
8cce0383a1 | ||
|
|
237c205169 | ||
|
|
2beb28518c | ||
|
|
28f71921ea | ||
|
|
338733d70f | ||
|
|
dd6c855028 | ||
|
|
60d45d8d37 | ||
|
|
9c0f12c7e8 | ||
|
|
e6a8736b9d | ||
|
|
c393ca700e | ||
|
|
ee58a34175 | ||
|
|
015109b42b | ||
|
|
cc11b95194 | ||
|
|
333e8ffeb2 | ||
|
|
48c7011f67 | ||
|
|
d221458939 | ||
|
|
612bcdf4a6 | ||
|
|
3d578c87be | ||
|
|
f8c1e794da | ||
|
|
22f615200a | ||
|
|
dfe2477b87 | ||
|
|
c4b602e835 | ||
|
|
5f825d8172 | ||
|
|
c9fada3222 | ||
|
|
1abe5495a2 | ||
|
|
d643d113ff | ||
|
|
968e7edec3 | ||
|
|
9a9aa94cfd | ||
|
|
22c313d032 | ||
|
|
540fc211df | ||
|
|
b0593c8a41 | ||
|
|
cf01c8649f | ||
|
|
2ba90f7ff5 | ||
|
|
ee80a2f710 | ||
|
|
c102053f3a | ||
|
|
1444043fd8 | ||
|
|
ce17f19ca0 | ||
|
|
701f35d0eb | ||
|
|
2961dda311 | ||
|
|
6e8e36080e | ||
|
|
41bfb79cb7 | ||
|
|
012969e742 | ||
|
|
fdf0f2e585 | ||
|
|
aa3413b086 | ||
|
|
0d46d07ad1 | ||
|
|
633d064640 | ||
|
|
6ded8588ed | ||
|
|
2a0df4f465 | ||
|
|
5c8ffd473b | ||
|
|
97fde3a304 | ||
|
|
cffcd0378a | ||
|
|
2b2a6a3d22 | ||
|
|
dff19af744 | ||
|
|
b9e7d853a4 | ||
|
|
3d2ccd8081 | ||
|
|
91f2dc522f | ||
|
|
107731a988 | ||
|
|
e15773e25b | ||
|
|
72bb377628 | ||
|
|
39456263b5 | ||
|
|
74645b7baf | ||
|
|
e4a1a45b09 | ||
|
|
0f83783709 | ||
|
|
1489903d9d | ||
|
|
33cb4c9bb4 | ||
|
|
5c1257d726 | ||
|
|
83dcc0016f | ||
|
|
cbb46a86b3 | ||
|
|
009f2c8f33 | ||
|
|
34bf3fdb66 | ||
|
|
3d355fde08 | ||
|
|
50c3f89e19 | ||
|
|
ca896092e2 | ||
|
|
c9267469a0 | ||
|
|
bd692bef71 | ||
|
|
78b7cf22a1 | ||
|
|
58fcee34c1 | ||
|
|
5f2874b292 | ||
|
|
08dd4435df | ||
|
|
7d3cb95f17 | ||
|
|
df1555c6c7 | ||
|
|
7139493258 | ||
|
|
01b98e79d9 | ||
|
|
ba9d75e3bd | ||
|
|
9f2af262ef | ||
|
|
e34fc4a7c4 | ||
|
|
b57c000678 | ||
|
|
0b5d09dd4b | ||
|
|
10cf054b93 | ||
|
|
9c3bf77b92 | ||
|
|
815baee4d9 | ||
|
|
a243aba422 | ||
|
|
5ccf03f498 | ||
|
|
245deed4b6 | ||
|
|
9cd8f79366 | ||
|
|
935b0e5887 | ||
|
|
e1299bfcc9 | ||
|
|
bb1f157645 | ||
|
|
f606ca23f4 | ||
|
|
5c79e642ba | ||
|
|
4fdad9a2f5 | ||
|
|
0b61b13e56 | ||
|
|
e5c664f7f0 | ||
|
|
7f347ec5f2 | ||
|
|
80017a0f92 | ||
|
|
6391df163b | ||
|
|
2c531a827d | ||
|
|
e41340ac4d | ||
|
|
53a76f0bf7 | ||
|
|
7790046ce2 | ||
|
|
4119c9324a | ||
|
|
f0c82d5aa7 | ||
|
|
16a1feb8fb | ||
|
|
d37c16aeaf | ||
|
|
7bf44c417c | ||
|
|
699978bcf0 | ||
|
|
c3c2c157d0 | ||
|
|
e4bdfb218c | ||
|
|
b70d607b9f | ||
|
|
f6d12c5d88 | ||
|
|
190e2a28a7 | ||
|
|
c27a3a40a7 | ||
|
|
f5153ab950 | ||
|
|
3967f12f3d | ||
|
|
156ff7652c | ||
|
|
313a68c15f | ||
|
|
14417f1614 | ||
|
|
5183592ece | ||
|
|
62eaa4193b | ||
|
|
a9d44593b3 | ||
|
|
a534e4a56a | ||
|
|
5f1d5a17de | ||
|
|
a823400b79 | ||
|
|
3e38e4448c | ||
|
|
d1c39d7ec5 | ||
|
|
9868a4dc59 | ||
|
|
e199a7a301 | ||
|
|
5e4d8f10ac | ||
|
|
053497724d | ||
|
|
939660ec86 | ||
|
|
e12b560329 | ||
|
|
57d36f5b68 | ||
|
|
2da7e29198 | ||
|
|
2325a4438d | ||
|
|
1a428e852a | ||
|
|
df63f2567a | ||
|
|
0d927a18f1 | ||
|
|
8ae2b1f542 | ||
|
|
c2dc6305f3 | ||
|
|
90a12fa3d0 | ||
|
|
22b5f65830 | ||
|
|
8934bbfffa | ||
|
|
dabec4c833 | ||
|
|
48ee1a68c6 | ||
|
|
2cb635e921 | ||
|
|
c53329a27d | ||
|
|
9ff7f31dfe | ||
|
|
75440c47c8 | ||
|
|
7e53cc7f2a | ||
|
|
8d4ede4a5a | ||
|
|
a400014efe | ||
|
|
eda0064e83 | ||
|
|
89bccf5752 | ||
|
|
4c9d03874b | ||
|
|
f955620601 | ||
|
|
3ddc99c28f | ||
|
|
656b5abf65 | ||
|
|
c9c44c173f | ||
|
|
88cab9547b | ||
|
|
60f3e775d0 | ||
|
|
41da9de946 | ||
|
|
44da4125fa | ||
|
|
ac14f44f2b | ||
|
|
569b6539d3 | ||
|
|
454a02b4d9 | ||
|
|
8cdf1c017a | ||
|
|
8f30a14790 | ||
|
|
7379cfa4c2 | ||
|
|
2785d22e5c | ||
|
|
9963e515d1 | ||
|
|
5e9bf9fdc0 | ||
|
|
99bcad6dd6 | ||
|
|
336a6fba42 | ||
|
|
175e0aaa14 | ||
|
|
22ffa61da0 | ||
|
|
54f2abfe47 | ||
|
|
efbb2849dc | ||
|
|
d50cc55d87 | ||
|
|
6a49973bb9 | ||
|
|
d2140490d8 | ||
|
|
dfcaa408c2 | ||
|
|
fb46b8a864 | ||
|
|
d860358f94 | ||
|
|
289dd080b9 | ||
|
|
7cf20f18a1 | ||
|
|
6556b583bd | ||
|
|
f09686d3df | ||
|
|
4f1ad3ef4b | ||
|
|
eb83d6b341 | ||
|
|
f9c56b6eb4 | ||
|
|
6401da2082 | ||
|
|
d2c541b4e2 | ||
|
|
facbe46251 | ||
|
|
be72d1762a | ||
|
|
eb5c7ffc7e | ||
|
|
8ec4d3f64f | ||
|
|
fb2ef64036 | ||
|
|
cd34b624b3 | ||
|
|
835bcabdbc | ||
|
|
937beba421 | ||
|
|
d77c2e8ba7 | ||
|
|
17f7e2f4d2 | ||
|
|
800871166c | ||
|
|
b7e877aeef | ||
|
|
c550cb050b | ||
|
|
64cb9584ce | ||
|
|
55265ac4ef | ||
|
|
677168b007 | ||
|
|
7935651899 | ||
|
|
cf3d611f59 | ||
|
|
a4c07fc0af | ||
|
|
a1e0c41c8b | ||
|
|
d7e97aa996 | ||
|
|
4a3e01ff0b | ||
|
|
3e22b505a3 | ||
|
|
db97c5788e | ||
|
|
1163d8e23d | ||
|
|
5e09660a3e | ||
|
|
9a43e439b3 | ||
|
|
428b07625c | ||
|
|
17c63e52bf | ||
|
|
02faa4bcf1 | ||
|
|
4902c5a4f0 | ||
|
|
8bd8eee573 | ||
|
|
f467df5fe2 | ||
|
|
1dab17eef0 | ||
|
|
e206b4146b | ||
|
|
c3e263f2f2 | ||
|
|
2105a330dd | ||
|
|
6d238a734d | ||
|
|
2c86aebfa3 | ||
|
|
5a10d29796 | ||
|
|
3d76859bbc | ||
|
|
1d50004644 | ||
|
|
ac7865a6fc | ||
|
|
fc98ab7222 | ||
|
|
40afa44673 | ||
|
|
43bc7196ea | ||
|
|
089591ac34 | ||
|
|
f748c4ef17 | ||
|
|
0b6d34d474 | ||
|
|
e2dacde754 | ||
|
|
e797d44c9e | ||
|
|
62d51034c4 | ||
|
|
849ab34189 | ||
|
|
757c57622b | ||
|
|
fd38c347a8 | ||
|
|
54d050324a | ||
|
|
76ee9668ef | ||
|
|
fabec4831c | ||
|
|
7489786d22 | ||
|
|
31267c5648 | ||
|
|
6e1ffb96ca | ||
|
|
5ca5f44a7e | ||
|
|
50416b3a7b | ||
|
|
09e69c385e | ||
|
|
f4c489553f | ||
|
|
95f6f0f14a | ||
|
|
c70b49a709 | ||
|
|
5e7f3cc67e | ||
|
|
edcd1b2609 | ||
|
|
8ab146220e | ||
|
|
6c451865eb | ||
|
|
7d5910faf0 | ||
|
|
7e9fe271ed | ||
|
|
cff24d5dcb | ||
|
|
f9cb1522f3 | ||
|
|
0716d1498f | ||
|
|
f7d1d41220 | ||
|
|
ff509bccd0 | ||
|
|
bff09f8fde | ||
|
|
c7cc8d40c8 | ||
|
|
c2a1fe5b1f | ||
|
|
9c08bb2482 | ||
|
|
02d7292576 | ||
|
|
178ccbee6a | ||
|
|
d3b367ad04 | ||
|
|
ac5050f4ed | ||
|
|
87da7bfe67 | ||
|
|
efd25100a5 | ||
|
|
4ad5830eec | ||
|
|
54b11b2ae3 | ||
|
|
19a7b55a9f | ||
|
|
e3d2754b1d | ||
|
|
58c6900175 | ||
|
|
1855fef437 | ||
|
|
c338569cff | ||
|
|
2c6733b0c8 | ||
|
|
b1f3587e02 | ||
|
|
13fda44fb1 | ||
|
|
8c11676afa | ||
|
|
436d4b1be5 | ||
|
|
3f4f5864fe | ||
|
|
59c0cdf3a2 | ||
|
|
0ca9c69da2 | ||
|
|
8c3081788c | ||
|
|
f7ead77f88 | ||
|
|
ca93757747 | ||
|
|
fca1367085 | ||
|
|
d160016959 | ||
|
|
7bc3d4f222 | ||
|
|
653724b79e | ||
|
|
65293f62d6 | ||
|
|
9c2473ab25 | ||
|
|
3d9b891a26 | ||
|
|
de68e37da2 | ||
|
|
12ce6aba06 | ||
|
|
6bb6c4c243 | ||
|
|
5999aa7414 | ||
|
|
90e952a920 | ||
|
|
78ec790311 | ||
|
|
89ce33f5d8 | ||
|
|
eb90c5b0a0 | ||
|
|
deafd3fcce | ||
|
|
ff30366d22 | ||
|
|
f93f86123e | ||
|
|
ca616647ce | ||
|
|
50d908662e | ||
|
|
bfd5d7c910 | ||
|
|
49acc871b9 | ||
|
|
77d45690c0 | ||
|
|
77dd5af0eb | ||
|
|
939ea047ad | ||
|
|
678168a74e | ||
|
|
1cc9ffdd3e |
@@ -1,38 +1,20 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
const packageInfo = require('./package.json');
|
||||
const defaultVars = require('./scripts/default-vars');
|
||||
const darkVars = require('./scripts/dark-vars');
|
||||
const compactVars = require('./scripts/compact-vars');
|
||||
|
||||
function generateThemeFileContent(theme) {
|
||||
return `const { ${theme}ThemeSingle } = require('./theme');\nconst defaultTheme = require('./default-theme');\n
|
||||
module.exports = {
|
||||
...defaultTheme,
|
||||
...${theme}ThemeSingle
|
||||
}`;
|
||||
}
|
||||
|
||||
// We need compile additional content for antd user
|
||||
function finalizeCompile() {
|
||||
if (fs.existsSync(path.join(__dirname, './lib'))) {
|
||||
// Build package.json version to lib/version/index.js
|
||||
// prevent json-loader needing in user-side
|
||||
const versionFilePath = path.join(process.cwd(), 'lib', 'version', 'index.js');
|
||||
const versionFileContent = fs.readFileSync(versionFilePath).toString();
|
||||
fs.writeFileSync(
|
||||
versionFilePath,
|
||||
versionFileContent.replace(
|
||||
/require\(('|")\.\.\/\.\.\/package\.json('|")\)/,
|
||||
`{ version: '${packageInfo.version}' }`,
|
||||
),
|
||||
);
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Wrote version into lib/version/index.js');
|
||||
|
||||
// Build package.json version to lib/version/index.d.ts
|
||||
// prevent https://github.com/ant-design/ant-design/issues/4935
|
||||
const versionDefPath = path.join(process.cwd(), 'lib', 'version', 'index.d.ts');
|
||||
fs.writeFileSync(
|
||||
versionDefPath,
|
||||
`declare var _default: "${packageInfo.version}";\nexport default _default;\n`,
|
||||
);
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Wrote version into lib/version/index.d.ts');
|
||||
|
||||
// Build a entry less file to dist/antd.less
|
||||
const componentsPath = path.join(process.cwd(), 'components');
|
||||
let componentsLessContent = '';
|
||||
@@ -53,19 +35,34 @@ function finalizeCompile() {
|
||||
|
||||
function buildThemeFile(theme, vars) {
|
||||
// Build less entry file: dist/antd.${theme}.less
|
||||
fs.writeFileSync(
|
||||
path.join(process.cwd(), 'dist', `antd.${theme}.less`),
|
||||
`@import "../lib/style/${theme}.less";\n@import "../lib/style/components.less";`,
|
||||
);
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`Built a entry less file to dist/antd.${theme}.less`);
|
||||
if (theme !== 'default') {
|
||||
fs.writeFileSync(
|
||||
path.join(process.cwd(), 'dist', `antd.${theme}.less`),
|
||||
`@import "../lib/style/${theme}.less";\n@import "../lib/style/components.less";`,
|
||||
);
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`Built a entry less file to dist/antd.${theme}.less`);
|
||||
} else {
|
||||
fs.writeFileSync(
|
||||
path.join(process.cwd(), 'dist', `default-theme.js`),
|
||||
`module.exports = ${JSON.stringify(vars, null, 2)};\n`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Build ${theme}.js: dist/${theme}-theme.js, for less-loader
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(process.cwd(), 'dist', `theme.js`),
|
||||
`const ${theme}ThemeSingle = ${JSON.stringify(vars, null, 2)};\n`,
|
||||
{
|
||||
flag: 'a',
|
||||
},
|
||||
);
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(process.cwd(), 'dist', `${theme}-theme.js`),
|
||||
`module.exports = ${JSON.stringify(vars, null, 2)};`,
|
||||
generateThemeFileContent(theme),
|
||||
);
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
@@ -80,10 +77,47 @@ function finalizeDist() {
|
||||
'@import "../lib/style/index.less";\n@import "../lib/style/components.less";',
|
||||
);
|
||||
// eslint-disable-next-line no-console
|
||||
fs.writeFileSync(
|
||||
path.join(process.cwd(), 'dist', 'theme.js'),
|
||||
`const defaultTheme = require('./default-theme.js');\n`,
|
||||
);
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Built a entry less file to dist/antd.less');
|
||||
buildThemeFile('default', defaultVars);
|
||||
buildThemeFile('dark', darkVars);
|
||||
buildThemeFile('compact', compactVars);
|
||||
fs.writeFileSync(
|
||||
path.join(process.cwd(), 'dist', `theme.js`),
|
||||
`
|
||||
function getThemeVariables(options = {}) {
|
||||
let themeVar = {
|
||||
'hack': \`true;@import "\${require.resolve('antd/lib/style/color/colorPalette.less')}";\`,
|
||||
...defaultTheme
|
||||
};
|
||||
if(options.dark) {
|
||||
themeVar = {
|
||||
...themeVar,
|
||||
...darkThemeSingle
|
||||
}
|
||||
}
|
||||
if(options.compact){
|
||||
themeVar = {
|
||||
...themeVar,
|
||||
...compactThemeSingle
|
||||
}
|
||||
}
|
||||
return themeVar;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
darkThemeSingle,
|
||||
compactThemeSingle,
|
||||
getThemeVariables
|
||||
}`,
|
||||
{
|
||||
flag: 'a',
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,4 +128,5 @@ module.exports = {
|
||||
dist: {
|
||||
finalize: finalizeDist,
|
||||
},
|
||||
generateThemeFileContent,
|
||||
};
|
||||
|
||||
@@ -10,6 +10,10 @@ module.exports = {
|
||||
'**/*.json',
|
||||
],
|
||||
modulePattern: [
|
||||
{
|
||||
pattern: /ConfigContext.*renderEmpty/ms,
|
||||
module: '../empty',
|
||||
},
|
||||
{
|
||||
pattern: /ConfigConsumer.*renderEmpty/ms,
|
||||
module: '../empty',
|
||||
|
||||
@@ -24,3 +24,5 @@ node_modules
|
||||
_site
|
||||
dist
|
||||
**/*.d.ts
|
||||
# Scripts
|
||||
scripts/previewEditor/**/*
|
||||
59
.eslintrc.js
59
.eslintrc.js
@@ -1,4 +1,4 @@
|
||||
const eslintrc = {
|
||||
module.exports = {
|
||||
extends: [
|
||||
'airbnb',
|
||||
'prettier',
|
||||
@@ -31,6 +31,29 @@ const eslintrc = {
|
||||
'@typescript-eslint/no-unused-expressions': 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['*.md'],
|
||||
globals: {
|
||||
React: true,
|
||||
ReactDOM: true,
|
||||
mountNode: true,
|
||||
},
|
||||
rules: {
|
||||
indent: 0,
|
||||
'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,
|
||||
'jsx-a11y/href-no-hash': 0,
|
||||
'import/no-extraneous-dependencies': 0,
|
||||
'import/no-unresolved': 0,
|
||||
'jsx-a11y/control-has-associated-label': 0,
|
||||
},
|
||||
},
|
||||
],
|
||||
rules: {
|
||||
camelcase: 0,
|
||||
@@ -91,38 +114,12 @@ const eslintrc = {
|
||||
'jest/no-test-callback': 0,
|
||||
'jest/expect-expect': 0,
|
||||
'react-hooks/rules-of-hooks': 2, // Checks rules of Hooks
|
||||
"unicorn/better-regex": 2,
|
||||
"unicorn/prefer-trim-start-end": 2,
|
||||
"unicorn/expiring-todo-comments": 2,
|
||||
"unicorn/no-abusive-eslint-disable": 2,
|
||||
'unicorn/better-regex': 2,
|
||||
'unicorn/prefer-trim-start-end': 2,
|
||||
'unicorn/expiring-todo-comments': 2,
|
||||
'unicorn/no-abusive-eslint-disable': 2,
|
||||
},
|
||||
globals: {
|
||||
gtag: true,
|
||||
},
|
||||
};
|
||||
|
||||
if (process.env.RUN_ENV === 'DEMO') {
|
||||
eslintrc.globals = Object.assign(eslintrc.globals, {
|
||||
React: true,
|
||||
ReactDOM: true,
|
||||
mountNode: true,
|
||||
});
|
||||
|
||||
Object.assign(eslintrc.rules, {
|
||||
indent: 0,
|
||||
'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,
|
||||
'jsx-a11y/href-no-hash': 0,
|
||||
'import/no-extraneous-dependencies': 0,
|
||||
'import/no-unresolved': 0,
|
||||
'jsx-a11y/control-has-associated-label': 0,
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = eslintrc;
|
||||
|
||||
3
.github/FUNDING.yml
vendored
3
.github/FUNDING.yml
vendored
@@ -4,5 +4,6 @@ github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, u
|
||||
open_collective: ant-design
|
||||
patreon: ant_design
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
issuehunt: ant-design/ant-design
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
custom: # Replace with a single custom sponsorship URL
|
||||
custom: https://www.buymeacoffee.com/antdesign
|
||||
|
||||
17
.github/workflows/gitleaks.yml
vendored
Normal file
17
.github/workflows/gitleaks.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
name: gitleaks
|
||||
|
||||
on: [push,pull_request]
|
||||
|
||||
jobs:
|
||||
gitleaks:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: '1'
|
||||
- name: wget
|
||||
uses: wei/wget@v1
|
||||
with:
|
||||
args: -O .gitleaks.toml https://raw.githubusercontent.com/ycjcl868/gitleaks/master/.gitleaks.toml
|
||||
- name: gitleaks-action
|
||||
uses: zricethezav/gitleaks-action@master
|
||||
26
.github/workflows/rebase.yml
vendored
26
.github/workflows/rebase.yml
vendored
@@ -1,22 +1,20 @@
|
||||
on:
|
||||
name: Automatic Rebase
|
||||
# https://github.com/marketplace/actions/automatic-rebase
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
name: Automatic Rebase
|
||||
|
||||
jobs:
|
||||
rebase:
|
||||
name: Rebase
|
||||
if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/rebase')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: Automatic Rebase
|
||||
uses: cirrus-actions/rebase@master
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# https://github.community/t5/GitHub-Actions/Workflow-is-failing-if-no-job-can-be-ran-due-to-condition/m-p/38186#M3250
|
||||
always_job:
|
||||
name: Always run job
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Always run
|
||||
run: echo "This job is used to prevent the workflow to fail when all other jobs are skipped."
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Automatic Rebase
|
||||
uses: cirrus-actions/rebase@1.2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -25,6 +25,7 @@ nohup.out
|
||||
_site
|
||||
_data
|
||||
dist
|
||||
report.html
|
||||
/lib
|
||||
/es
|
||||
elasticsearch-*
|
||||
@@ -54,4 +55,6 @@ site/theme/template/Content/Article.jsx
|
||||
site/theme/template/Content/EditButton.jsx
|
||||
site/theme/template/Resources/*.jsx
|
||||
site/theme/template/Resources/**/*.jsx
|
||||
site/theme/template/NotFound.jsx
|
||||
site/theme/template/NotFound.jsx
|
||||
scripts/previewEditor/index.html
|
||||
components/version/version.tsx
|
||||
|
||||
13
.jest.js
13
.jest.js
@@ -1,12 +1,17 @@
|
||||
const libDir = process.env.LIB_DIR;
|
||||
|
||||
const transformIgnorePatterns = [
|
||||
'/dist/',
|
||||
// Ignore modules without es dir.
|
||||
// Update: @babel/runtime should also be transformed
|
||||
'node_modules/(?!.*@babel)[^/]+?/(?!(es|node_modules)/)',
|
||||
'node_modules/(?!.*@(babel|ant-design))[^/]+?/(?!(es|node_modules)/)',
|
||||
];
|
||||
|
||||
function getTestRegex(libDir) {
|
||||
if (libDir === 'dist') {
|
||||
return 'demo\\.test\\.js$';
|
||||
}
|
||||
return '.*\\.test\\.(j|t)sx?$';
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
verbose: true,
|
||||
setupFiles: ['./tests/setup.js'],
|
||||
@@ -28,7 +33,7 @@ module.exports = {
|
||||
'\\.md$': './node_modules/@ant-design/tools/lib/jest/demoPreprocessor',
|
||||
'\\.(jpg|png|gif|svg)$': './node_modules/@ant-design/tools/lib/jest/imagePreprocessor',
|
||||
},
|
||||
testRegex: `${libDir === 'dist' ? 'demo' : '.*'}\\.test\\.js$`,
|
||||
testRegex: getTestRegex(process.env.LIB_DIR),
|
||||
collectCoverageFrom: [
|
||||
'components/**/*.{ts,tsx}',
|
||||
'!components/*/style/index.tsx',
|
||||
|
||||
@@ -12,7 +12,7 @@ module.exports = {
|
||||
'\\.md$': './node_modules/@ant-design/tools/lib/jest/demoPreprocessor',
|
||||
'\\.(jpg|png|gif|svg)$': './node_modules/@ant-design/tools/lib/jest/imagePreprocessor',
|
||||
},
|
||||
testRegex: 'demo\\.test\\.js$',
|
||||
testRegex: 'demo\\.test\\.(j|t)s$',
|
||||
testEnvironment: 'node',
|
||||
transformIgnorePatterns,
|
||||
snapshotSerializers: ['enzyme-to-json/serializer'],
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
~*
|
||||
dist/report.html
|
||||
9
.remarkrc.js
Normal file
9
.remarkrc.js
Normal file
@@ -0,0 +1,9 @@
|
||||
const config = {
|
||||
plugins: [
|
||||
'remark-preset-lint-recommended',
|
||||
['remark-lint-list-item-indent', 'space'],
|
||||
['remark-lint-no-literal-urls', false],
|
||||
],
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
@@ -15,8 +15,191 @@ timeline: true
|
||||
|
||||
---
|
||||
|
||||
## 4.2.2
|
||||
|
||||
`2020-05-11`
|
||||
|
||||
- 🐞 Fix `npm run version` install error. [#24059](https://github.com/ant-design/ant-design/pull/24059)
|
||||
- 🐞 Fix Menu `@menu-item-font-size` not working. [#24052](https://github.com/ant-design/ant-design/pull/24052)
|
||||
- 💄 Add `@modal-close-color` less variable. [#24053](https://github.com/ant-design/ant-design/pull/24053)
|
||||
|
||||
## 4.2.1
|
||||
|
||||
`2020-05-11`
|
||||
|
||||
- Form
|
||||
- 🐞 Fix Form.Item get React warning for `getValueProps`. [#23875](https://github.com/ant-design/ant-design/pull/23875)
|
||||
- 🐞 Fix Form.Item `help` style issue when `validateStatus` is not `error`. [#23945](https://github.com/ant-design/ant-design/pull/23945)
|
||||
- Table
|
||||
- 🐞 Fix selection column width issue when fix header. [#23806](https://github.com/ant-design/ant-design/pull/23806)
|
||||
- 💄 Adjust selection column css selector priority to enable customize width. [#23914](https://github.com/ant-design/ant-design/pull/23914)
|
||||
- DatePicker
|
||||
- 🐞 Fix miss placeholder when `placeholder` is `undefined`. [#23818](https://github.com/ant-design/ant-design/pull/23818)
|
||||
- 🐞 Fix clear icon color style. [#23811](https://github.com/ant-design/ant-design/pull/23811)
|
||||
- Switch
|
||||
- 🐞 Fix loading style for the dark theme. [#23766](https://github.com/ant-design/ant-design/pull/23766) [@vsn4ik](https://github.com/vsn4ik)
|
||||
- 🐞 Fix `unCheckedChildren` not showing. [#23791](https://github.com/ant-design/ant-design/pull/23791)
|
||||
- 🐞 Fix Upload error message location to scroll in the float layer. [#24001](https://github.com/ant-design/ant-design/pull/24001) [@mraiguo](https://github.com/mraiguo)
|
||||
- 💄 Tweak Comment render unnecessary div style when `avatar` is empty. [#23994](https://github.com/ant-design/ant-design/pull/23994) [@Xuhao](https://github.com/Xuhao)
|
||||
- 🐞 Fix Select `focus` border style in Input.Group. [#23985](https://github.com/ant-design/ant-design/pull/23985)
|
||||
- 🐞 Fix Steps `subTitle` showing `[object Object]` title. [#23989](https://github.com/ant-design/ant-design/pull/23989)
|
||||
- 💄 Tweak Select close icon position. [#23963](https://github.com/ant-design/ant-design/pull/23963)
|
||||
- 🐞 Fix Drawer `width="50%"` hidden problem when no mask. [#23925](https://github.com/ant-design/ant-design/pull/23925)
|
||||
- 🐞 Fix Textarea with `allowClear` has error height style. [#23835](https://github.com/ant-design/ant-design/pull/23835)
|
||||
- 💄 Adjust Modal.xxx function async to avoid block React events. [#23826](https://github.com/ant-design/ant-design/pull/23826)
|
||||
- 🐞 Fix Menu with controlled `openKeys` abnormal behavior when `inlineCollapsed` changed. [#23822](https://github.com/ant-design/ant-design/pull/23822)
|
||||
- 🐞 Fix Button `loading` animation. [#23783](https://github.com/ant-design/ant-design/pull/23783)
|
||||
- 🐞 Fix Slider `marks` selected problem when dragging. [#23773](https://github.com/ant-design/ant-design/pull/23773)
|
||||
- 🛠 Timeline refactors with React Hooks. [#23631](https://github.com/ant-design/ant-design/pull/23631) [@hengkx](https://github.com/hengkx)
|
||||
- 🌎 Localization
|
||||
- 🌐 Add Farsi `fa_IR` default locale template localisations. [#23926](https://github.com/ant-design/ant-design/pull/23926) [@NarimanMov](https://github.com/NarimanMov)
|
||||
- 🌐 Add default `en` default locale template localisations for Form. [#23859](https://github.com/ant-design/ant-design/pull/23859) [@mjfwebb](https://github.com/mjfwebb)
|
||||
- 📦 Reduce bundle size
|
||||
- 🗑 Reduce bundle size via removing `react-lifecycles-compat`. [#23969](https://github.com/ant-design/ant-design/pull/23969)
|
||||
- 🛠 Reduce bundle size via excluding `package.json` from source code. [#23957](https://github.com/ant-design/ant-design/pull/23957)
|
||||
- 🛠 Upgrade `rc-animate` to 3.x to reduce bundle size. [#23937](https://github.com/ant-design/ant-design/pull/23937)
|
||||
- RTL
|
||||
- 🐞 Fix Input clear icon style in RTL. [#23999](https://github.com/ant-design/ant-design/pull/23999)
|
||||
- 🐞 Fix DatePicker panel style in RTL. [#24028](https://github.com/ant-design/ant-design/pull/24028) [@xrkffgg](https://github.com/xrkffgg)
|
||||
- 💄 Optimize DatePicker active bar style in `RTL`. [#23981](https://github.com/ant-design/ant-design/pull/23981)
|
||||
- 🐞 Fix Transfer search padding style in `RTL`. [#23962](https://github.com/ant-design/ant-design/pull/23962)
|
||||
- 💄 Optimize Layout style of RTL. [#23921](https://github.com/ant-design/ant-design/pull/23921)
|
||||
- 💄 Optimize Button `loading` style in RT. [#23776](https://github.com/ant-design/ant-design/pull/23776)
|
||||
- 💄 Optimize Input.Search style in RTL. [#23797](https://github.com/ant-design/ant-design/pull/23797)
|
||||
- TypeScript
|
||||
- 🐞 Fix InputNumber `onChange` type. [#23871](https://github.com/ant-design/ant-design/pull/23871) [@jjhbw](https://github.com/jjhbw)
|
||||
|
||||
## 4.2.0
|
||||
|
||||
`2020-04-29`
|
||||
|
||||
- 🆕 List `grid` support all column count like 5. [#23630](https://github.com/ant-design/ant-design/pull/23630)
|
||||
- 🆕 Divider add `plain` prop which allows a non-heading style divider text. [#23405](https://github.com/ant-design/ant-design/pull/23405)
|
||||
- 🆕 Typography `ellipsis` support `onEllipsis` event handler. [#23414](https://github.com/ant-design/ant-design/pull/23414)
|
||||
- 🆕 Space support `align` prop. [#23306](https://github.com/ant-design/ant-design/pull/23306)
|
||||
- 🆕 Upload support `isImageUrl` to force trade file as image. [#23248](https://github.com/ant-design/ant-design/pull/23248) [@onjuju](https://github.com/onjuju)
|
||||
- 🆕 Form.Item support `initialValue` and `getValueProps` props. [#22993](https://github.com/ant-design/ant-design/pull/22993)
|
||||
- ConfigProvider
|
||||
- 🆕 ConfigProvider support `getTargetContainer` to config Affix `target` props. [#23751](https://github.com/ant-design/ant-design/pull/23751)
|
||||
- 🆕 ConfigProvider support `input` prop to config Input `autoComplete`. [#23455](https://github.com/ant-design/ant-design/pull/23455)
|
||||
- 🐞 Fix ConfigProvider `getPopupContainer` not working on DatePicker and Slider. [#23594](https://github.com/ant-design/ant-design/pull/23594) [@hengkx](https://github.com/hengkx)
|
||||
- Table
|
||||
- 🆕 Table `summary` support fixed columns. [#23647](https://github.com/ant-design/ant-design/pull/23647)
|
||||
- 🆕 Table support responsive columns. [#23298](https://github.com/ant-design/ant-design/pull/23298) [@vbudovski](https://github.com/vbudovski)
|
||||
- 🐞 Fix Table pagination default position in RTL. [#23747](https://github.com/ant-design/ant-design/pull/23747)
|
||||
- 🐞 Fix Table crash when `pageSize` is `undefined`. [#23724](https://github.com/ant-design/ant-design/pull/23724)
|
||||
- 🐞 fix Table nested margin when size is `small` or `middle`. [#23602](https://github.com/ant-design/ant-design/pull/23602) [@hengkx](https://github.com/hengkx)
|
||||
- 🐞 Fix RangePicker `ranges` tag color to primary color. [#23705](https://github.com/ant-design/ant-design/pull/23705)
|
||||
- 🐞 Fix Transfer with custom empty style issue. [#23694](https://github.com/ant-design/ant-design/pull/23694) [@hengkx](https://github.com/hengkx)
|
||||
- Input
|
||||
- 🐞 Fix Password caret position. [#23633](https://github.com/ant-design/ant-design/pull/23633) [@huntdream](https://github.com/huntdream)
|
||||
- 💄 Adjust Input.Search icon style. [#23406](https://github.com/ant-design/ant-design/pull/23406)
|
||||
- Button
|
||||
- 🐞 Fix Button align problem of icon only. [#23671](https://github.com/ant-design/ant-design/pull/23671)
|
||||
- 🐞 Fix Button of icon only wrong `loading` style. [#23614](https://github.com/ant-design/ant-design/pull/23614)
|
||||
- 🐞 fix Button cannot be directly called by `react-dnd`. [#23571](https://github.com/ant-design/ant-design/pull/23571) [@hengkx](https://github.com/hengkx)
|
||||
- Menu
|
||||
- 🆕 Menu Item and SubMenu support `icon` prop. [#23629](https://github.com/ant-design/ant-design/pull/23629)
|
||||
- 🐞 Fix Menu duplicated shadow style. [#23664](https://github.com/ant-design/ant-design/pull/23664)
|
||||
- 🐞 Fix Tag cannot be directly called by `react-dnd`. [#23632](https://github.com/ant-design/ant-design/pull/23632) [@hengkx](https://github.com/hengkx)
|
||||
- Anchor
|
||||
- 🐞 Fix Anchor Link with multiple `#` can not jump correctly. [#23595](https://github.com/ant-design/ant-design/pull/23595) [@wuzekang](https://github.com/wuzekang)
|
||||
- 🐞 Fix Input with `suffix` align problem. [#23606](https://github.com/ant-design/ant-design/pull/23606)
|
||||
- 💄 Select arrow won't rotate when open. [#23468](https://github.com/ant-design/ant-design/pull/23468)
|
||||
- 💄 Rate support `direction`. [#23321](https://github.com/ant-design/ant-design/pull/23321)
|
||||
- 💄 Adjust font-size in compact mode. [#23135](https://github.com/ant-design/ant-design/pull/23135)
|
||||
- RTL
|
||||
- 💄 Optimize Result button style in RTL. [#23733](https://github.com/ant-design/ant-design/pull/23733)
|
||||
- 💄 Add Divider RTL support. [#23734](https://github.com/ant-design/ant-design/pull/23734)
|
||||
- 💄 Fix Alert style in RTL when no-icon. [#23714](https://github.com/ant-design/ant-design/pull/23714)
|
||||
- 💄 Optimize Table expand animation and pagination style in RTL. [#23706](https://github.com/ant-design/ant-design/pull/23706)
|
||||
- 💄 Fix Table filter dropdown position in RTL. [#23695](https://github.com/ant-design/ant-design/pull/23695)
|
||||
- 💄 Fix Table rowSelect icon style in RTL. [#23690](https://github.com/ant-design/ant-design/pull/23690)
|
||||
- 💄 Optimize List style in RTL. [#23676](https://github.com/ant-design/ant-design/pull/23676)
|
||||
- 💄 Add Calendar RTL. [#23394](https://github.com/ant-design/ant-design/pull/23394)
|
||||
- 💄 Optimize Input.Search style in RTL. [#23424](https://github.com/ant-design/ant-design/pull/23424)
|
||||
- 💄 Add Notification RTL config. [#23185](https://github.com/ant-design/ant-design/pull/23185)
|
||||
- TypeScript
|
||||
- 🐞 Fix PageHeader `tag` definition. [#23712](https://github.com/ant-design/ant-design/pull/23712) [@hengkx](https://github.com/hengkx)
|
||||
- 🗑 Remove Button deprecated `type="danger"` TypeScript definition and warn it. [#23709](https://github.com/ant-design/ant-design/pull/23709)
|
||||
- 🐞 Fix Table pagination `position` definition. [#23681](https://github.com/ant-design/ant-design/pull/23681) [@hengkx](https://github.com/hengkx)
|
||||
|
||||
## 4.1.5
|
||||
|
||||
`2020-04-25`
|
||||
|
||||
- 🐞 Fix Button.Group align style. [#23590](https://github.com/ant-design/ant-design/pull/23590)
|
||||
- 🐞 Fix Select cannot trigger open by clicking arrow icon. [#23448](https://github.com/ant-design/ant-design/pull/23448)
|
||||
- 🐞 Fix Form fields shake when `@form-item-margin-bottom` is customize and switching the validing info. [#23436](https://github.com/ant-design/ant-design/pull/23436) [@yoyo837](https://github.com/yoyo837)
|
||||
- 🐞 Fix the first Divider render differently with others. [#23438](https://github.com/ant-design/ant-design/pull/23438)
|
||||
- 🐞 Fix nest ConfigProvider missing `prefixCls` value. [#23423](https://github.com/ant-design/ant-design/pull/23423)
|
||||
- 🐞 Fix Carousel tabbed Radio/Checkbox to non-active slide. [#23380](https://github.com/ant-design/ant-design/pull/23380)
|
||||
- 🐞 Fix Tree with virtual scroll frozen by quick `loadData`. [#23581](https://github.com/ant-design/ant-design/pull/23581)
|
||||
- 🐞 Fix Steps style in IE11 when direction is vertical. [#23561](https://github.com/ant-design/ant-design/pull/23561) [@AdrianoRuberto](https://github.com/AdrianoRuberto)
|
||||
- 🐞 Fix Input.Search height affected by `suffix` and `react key` warning. [#23527](https://github.com/ant-design/ant-design/pull/23527)
|
||||
- 🐞 Fix Menu behavior when hover on submenu gap. [#23511](https://github.com/ant-design/ant-design/pull/23511)
|
||||
- 🐞 Fix Tree custom icon missing when node is loading data. [#23494](https://github.com/ant-design/ant-design/pull/23494)
|
||||
- RTL
|
||||
- 🐞 Fix Alert RTL style when set both `showIcon` and `closable`. [#23526](https://github.com/ant-design/ant-design/pull/23526)
|
||||
- 🐞 Fix Button RTL style when loading. [#23399](https://github.com/ant-design/ant-design/pull/23399)
|
||||
- 🐞 Fix Collapse that icon position is incorrect in RTL. [#23445](https://github.com/ant-design/ant-design/pull/23445)
|
||||
- 🐞 Fix Select group label style in RTL. [#23404](https://github.com/ant-design/ant-design/pull/23404)
|
||||
- 🐞 Fix Statistic RTL style. [#23397](https://github.com/ant-design/ant-design/pull/23397)
|
||||
- TypeScript
|
||||
- 🐞 Fix type definition of `selections` for Table. [#23462](https://github.com/ant-design/ant-design/pull/23462) [@xiaoxintang](https://github.com/xiaoxintang)
|
||||
|
||||
## 4.1.4
|
||||
|
||||
`2020-04-18`
|
||||
|
||||
- 🐞 Fix dark theme and compact theme not working. [#23243](https://github.com/ant-design/ant-design/pull/23243)
|
||||
- 🐞 Fix Modal.info executed only once when has argument. [#23360](https://github.com/ant-design/ant-design/pull/23360)
|
||||
- 🐞 Fix Dropdown submenu background missing. [#23296](https://github.com/ant-design/ant-design/pull/23296)
|
||||
- 💄 Optimize PageHeader responsive behavior. [#23277](https://github.com/ant-design/ant-design/pull/23277)
|
||||
- 🐞 Fix TreeSelect render blank in compact mode. [#23231](https://github.com/ant-design/ant-design/pull/23231)
|
||||
- 🛎 Fix Checkbox and Switch console warning typo (validate -> a valid). [#23240](https://github.com/ant-design/ant-design/pull/23240) [@evancharlton](https://github.com/evancharlton)
|
||||
- 🐞 Fix Table `rowSelection` params issue when `childrenColumnName` configured. [#23205](https://github.com/ant-design/ant-design/pull/23205)
|
||||
- Input
|
||||
- 🐞 Fix Input `type="color"` height issue. [#23351](https://github.com/ant-design/ant-design/pull/23351)
|
||||
- 🐞 Fix Input width shaking when trigger clear icon. [#23259](https://github.com/ant-design/ant-design/pull/23259)
|
||||
- 🐞 Fix Input.Search `size` not affected by ConfigProvider `componentSize`. [#23331](https://github.com/ant-design/ant-design/pull/23331)
|
||||
- Select
|
||||
- 🐞 Fix multiple Select show remove icon when `disabled`. [#23295](https://github.com/ant-design/ant-design/pull/23295)
|
||||
- 🐞 Fix Select custom `suffixIcon` cannot be access. [#23274](https://github.com/ant-design/ant-design/pull/23274)
|
||||
- 🐞 Fix Select search input caret missing in Collapse. [#23250](https://github.com/ant-design/ant-design/pull/23250)
|
||||
- Globalization
|
||||
- 🇨🇳 Form validation messages support internalization and add zh_CN locale. [#23165](https://github.com/ant-design/ant-design/pull/23165) [@hengkx](https://github.com/hengkx)
|
||||
- 🌐 Add missing translations in he_IL. [#23302](https://github.com/ant-design/ant-design/pull/23302) [@MishaKav](https://github.com/MishaKav)
|
||||
- 🌐 Add missing translations in ru_RU. [#23303](https://github.com/ant-design/ant-design/pull/23303) [@MishaKav](https://github.com/MishaKav)
|
||||
- TypeScript
|
||||
- 🔷 Form.Item type upgrade. [#22962](https://github.com/ant-design/ant-design/pull/22962) [@fa93hws](https://github.com/fa93hws)
|
||||
- 🔷 Tree type upgrade. [#23348](https://github.com/ant-design/ant-design/pull/23348) [@yoyo837](https://github.com/yoyo837)
|
||||
- 🐞 Pass `popupClassName` prop to `rc-picker`. [#23214](https://github.com/ant-design/ant-design/pull/23214) [@tanmoyopenroot](https://github.com/tanmoyopenroot)
|
||||
- RTL
|
||||
- 💄 Fix Select RTL style. [#23235](https://github.com/ant-design/ant-design/pull/23235)
|
||||
- 💄 Fix Menu RTL style. [#23319](https://github.com/ant-design/ant-design/pull/23319)
|
||||
|
||||
## 4.1.3
|
||||
|
||||
`2020-04-13`
|
||||
|
||||
- 💄 Adjust Form.Item `label` height style in vertical layout. [#23192](https://github.com/ant-design/ant-design/pull/23192)
|
||||
- 🐞 Fix `Variable is undefined` when importing dark or compact theme and provide a `getThemeVariables` methold for getting theme variables easily. [#23171](https://github.com/ant-design/ant-design/pull/23171)
|
||||
- 🐞 Fix PageHeader style breaks when `title` is too long and improve it's responsive design. [#23133](https://github.com/ant-design/ant-design/pull/23133)
|
||||
- Tabs
|
||||
- 🐞 Fix Tabs `@tabs-card-height` less variable not working. [#23168](https://github.com/ant-design/ant-design/pull/23168)
|
||||
- 🐞 Fix Tabs cannot be displayed in Safari 13. [#23151](https://github.com/ant-design/ant-design/pull/23151) [@imhxc](https://github.com/imhxc)
|
||||
- Table
|
||||
- 🐞 Fix Table fixed columns cannot pin in Safari 12. [#23161](https://github.com/ant-design/ant-design/pull/23161)
|
||||
- 🐞 Fix Table `summary` padding in small size. [#23140](https://github.com/ant-design/ant-design/pull/23140) [@someyoungideas](https://github.com/someyoungideas)
|
||||
- 🐞 Fix Select align style with different size. [#23160](https://github.com/ant-design/ant-design/pull/23160)
|
||||
- 🐞 Fix RangePicker under Input.Group style issue. [#23149](https://github.com/ant-design/ant-design/pull/23149)
|
||||
- 🐞 Fix Pagination missing TypeScript definition of `showTitle`. [#23144](https://github.com/ant-design/ant-design/pull/23144) [@DongchengWang](https://github.com/DongchengWang)
|
||||
|
||||
## 4.1.2
|
||||
|
||||
`2020-04-10`
|
||||
|
||||
- Menu
|
||||
- 🐞 Fix Menu SubMenu background in dark mode. [#22981](https://github.com/ant-design/ant-design/pull/22981) [@AshoneA](https://github.com/AshoneA)
|
||||
- 🐞 Fix long SubMenu title being overlayed by arrow icon. [#23028](https://github.com/ant-design/ant-design/pull/23028) [@wwyx778](https://github.com/wwyx778)
|
||||
@@ -52,6 +235,8 @@ timeline: true
|
||||
|
||||
## 4.1.1
|
||||
|
||||
`2020-04-05`
|
||||
|
||||
- 🐞 Fix Tabs panel focus outline style. [#22752](https://github.com/ant-design/ant-design/pull/22752) [@MrHeer](https://github.com/MrHeer)
|
||||
- 🐞 Fix Input affix with popup element can not get click focus. [#22887](https://github.com/ant-design/ant-design/pull/22887)
|
||||
- Table
|
||||
|
||||
@@ -15,8 +15,191 @@ timeline: true
|
||||
|
||||
---
|
||||
|
||||
## 4.2.2
|
||||
|
||||
`2020-05-11`
|
||||
|
||||
- 🐞 修复安装 antd `npm run version` 报错的问题。[#24059](https://github.com/ant-design/ant-design/pull/24059)
|
||||
- 🐞 修复 Menu `@menu-item-font-size` 变量失效的问题。[#24052](https://github.com/ant-design/ant-design/pull/24052)
|
||||
- 💄 新增 `@modal-close-color` less 变量。[#24053](https://github.com/ant-design/ant-design/pull/24053)
|
||||
|
||||
## 4.2.1
|
||||
|
||||
`2020-05-11`
|
||||
|
||||
- Form
|
||||
- 🐞 修复 Form.Item 使用 `getValueProps` React 会报警的问题。[#23875](https://github.com/ant-design/ant-design/pull/23875)
|
||||
- 🐞 修复 Form.Item `help` 在 `validateStatus` 不是 `error` 时的样式问题。[#23945](https://github.com/ant-design/ant-design/pull/23945)
|
||||
- Table
|
||||
- 🐞 修复 Table 固定表头时选择列的宽度问题。[#23806](https://github.com/ant-design/ant-design/pull/23806)
|
||||
- 💄 调整 Table 选择列 css 选择器优先级以支持自定义宽度。[#23914](https://github.com/ant-design/ant-design/pull/23914)
|
||||
- DatePicker
|
||||
- 🐞 修复在 `placeholder` 为 `undefined` 时不展示的问题。[#23818](https://github.com/ant-design/ant-design/pull/23818)
|
||||
- 🐞 修复清除图标的颜色。[#23811](https://github.com/ant-design/ant-design/pull/23811)
|
||||
- Switch
|
||||
- 🐞 修复暗色主题下的加载样式。[#23766](https://github.com/ant-design/ant-design/pull/23766) [@vsn4ik](https://github.com/vsn4ik)
|
||||
- 🐞 修复 `unCheckedChildren` 不显示的问题。[#23791](https://github.com/ant-design/ant-design/pull/23791)
|
||||
- 🐞 修复 Upload 在浮层中错误提示滚动定位问题。[#24001](https://github.com/ant-design/ant-design/pull/24001) [@mraiguo](https://github.com/mraiguo)
|
||||
- 💄 调整 Comment 中 `avatar` 为空时不必要的 div 样式[#23994](https://github.com/ant-design/ant-design/pull/23994) [@Xuhao](https://github.com/Xuhao)
|
||||
- 🐞 修复 Input.Group 中 Select 选项 `focus` 边框样式[#23985](https://github.com/ant-design/ant-design/pull/23985)
|
||||
- 🐞 修复 Steps `subTitle` 上会显示 `[object Object]` 提示的问题。[#23989](https://github.com/ant-design/ant-design/pull/23989)
|
||||
- 💄 微调 Select 移除图标的位置。[#23963](https://github.com/ant-design/ant-design/pull/23963)
|
||||
- 🐞 修复无遮罩的 Drawer 设置 `50%` 宽度时不显示的问题。[#23925](https://github.com/ant-design/ant-design/pull/23925)
|
||||
- 🐞 修复 Textarea 开启 `allowClear` 时高度错误的问题。[#23835](https://github.com/ant-design/ant-design/pull/23835)
|
||||
- 💄 调整 Modal.xxx 方法为异步以防止其影响 React 事件响应。[#23826](https://github.com/ant-design/ant-design/pull/23826)
|
||||
- 🐞 修复受控模式 Menu `inlineCollapsed` 折叠时的表现。[#23822](https://github.com/ant-design/ant-design/pull/23822)
|
||||
- 🐞 修复 Button `loading` 动画切换不平滑的问题。[#23783](https://github.com/ant-design/ant-design/pull/23783)
|
||||
- 🐞 修复 Slider 拖拽中选中 `marks` 文本的问题。[#23773](https://github.com/ant-design/ant-design/pull/23773)
|
||||
- 🛠 Timeline 使用 React Hooks 重构。[#23631](https://github.com/ant-design/ant-design/pull/23631) [@hengkx](https://github.com/hengkx)
|
||||
- 🌎 国际化
|
||||
- 🌐 增加波斯语 `fa_IR` 国际化默认提示模板。[#23926](https://github.com/ant-design/ant-design/pull/23926) [@NarimanMov](https://github.com/NarimanMov)
|
||||
- 🌐 增加 Form `en` 国际化默认提示模板[#23859](https://github.com/ant-design/ant-design/pull/23859) [@mjfwebb](https://github.com/mjfwebb)
|
||||
- 📦 包体积优化
|
||||
- 🗑 移除 `react-lifecycles-compat` 依赖以优化包体积。[#23969](https://github.com/ant-design/ant-design/pull/23969)
|
||||
- 🛠 源码中不再引用 `package.json` 从而优化了一点包体积。[#23957](https://github.com/ant-design/ant-design/pull/23957)
|
||||
- 🛠 更新 `rc-animate` 到 3.x 以优化包体积。[#23937](https://github.com/ant-design/ant-design/pull/23937)
|
||||
- RTL
|
||||
- 🐞 修复 Input 在 RTL 模式下清空按钮的样式。[#23999](https://github.com/ant-design/ant-design/pull/23999)
|
||||
- 🐞 修复 DatePicker 下拉框在 RTL 模式下样式。[#24028](https://github.com/ant-design/ant-design/pull/24028) [@xrkffgg](https://github.com/xrkffgg)
|
||||
- 💄 优化 DatePick 在 `RTL` 模式下的激活条样式。[#23981](https://github.com/ant-design/ant-design/pull/23981)
|
||||
- 🐞 修复 Transfer 查询框在 `RTL` 模式下的边距样式。[#23962](https://github.com/ant-design/ant-design/pull/23962)
|
||||
- 💄 优化 Layout RTL 样式。[#23921](https://github.com/ant-design/ant-design/pull/23921)
|
||||
- 💄 优化 Button `loading` 状态在 RTL 下样式。[#23776](https://github.com/ant-design/ant-design/pull/23776)
|
||||
- 💄 优化 Input.Search RTL 样式。[#23797](https://github.com/ant-design/ant-design/pull/23797)
|
||||
- TypeScript
|
||||
- 🐞 修复 InputNumber `onChange` 类型。[#23871](https://github.com/ant-design/ant-design/pull/23871) [@jjhbw](https://github.com/jjhbw)
|
||||
|
||||
## 4.2.0
|
||||
|
||||
`2020-04-29`
|
||||
|
||||
- 🆕 List `grid` 支持所有分栏数字,比如分为 5 栏。[#23630](https://github.com/ant-design/ant-design/pull/23630)
|
||||
- 🆕 Divider 新增 `plain` 属性,可用于设置一个非标题样式的分割文字。[#23405](https://github.com/ant-design/ant-design/pull/23405)
|
||||
- 🆕 Typography `ellipsis` 支持 `onEllipsis` 事件。[#23414](https://github.com/ant-design/ant-design/pull/23414)
|
||||
- 🆕 Space 支持 `align` 属性。[#23306](https://github.com/ant-design/ant-design/pull/23306)
|
||||
- 🆕 Upload 添加 `isImageUrl` 属性以强制将文件作为图标文件。[#23248](https://github.com/ant-design/ant-design/pull/23248) [@onjuju](https://github.com/onjuju)
|
||||
- 🆕 Form.Item 支持 `initialValue` 和 `getValueProps` 属性。[#22993](https://github.com/ant-design/ant-design/pull/22993)
|
||||
- ConfigProvider
|
||||
- 🆕 ConfigProvider 支持 `getTargetContainer` 以配置 Affix `target` 属性。[#23751](https://github.com/ant-design/ant-design/pull/23751)
|
||||
- 🆕 ConfigProvider 添加 `input` 属性以支持全局化配置 Input `autoComplete` 的默认值。[#23455](https://github.com/ant-design/ant-design/pull/23455)
|
||||
- 🐞 修复 ConfigProvider `getPopupContainer` 对 DatePicker 和 Slider 不生效的问题。[#23594](https://github.com/ant-design/ant-design/pull/23594) [@hengkx](https://github.com/hengkx)
|
||||
- Table
|
||||
- 🆕 Table `summary` 支持固定列。[#23647](https://github.com/ant-design/ant-design/pull/23647)
|
||||
- 🆕 Table 支持响应式展现列。[#23298](https://github.com/ant-design/ant-design/pull/23298) [@vbudovski](https://github.com/vbudovski)
|
||||
- 🐞 修复 Table pagination 在 RTL 下默认位置。[#23747](https://github.com/ant-design/ant-design/pull/23747)
|
||||
- 🐞 修复 Table 在 `pageSize` 是 `undefined` 时崩溃的问题。[#23724](https://github.com/ant-design/ant-design/pull/23724)
|
||||
- 🐞 修复 Table 大小为 `small` 和 `middle` 时嵌套表格错位的问题。[#23602](https://github.com/ant-design/ant-design/pull/23602) [@hengkx](https://github.com/hengkx)
|
||||
- 🐞 修正 RangePicker 范围标签的颜色为主色。[#23705](https://github.com/ant-design/ant-design/pull/23705)
|
||||
- 🐞 修复 Transfer 为空自定义图片样式问题。[#23694](https://github.com/ant-design/ant-design/pull/23694) [@hengkx](https://github.com/hengkx)
|
||||
- Input
|
||||
- 🐞 修复 Password 组件输入光标位置。[#23633](https://github.com/ant-design/ant-design/pull/23633) [@huntdream](https://github.com/huntdream)
|
||||
- 💄 调整 Input.Search 的搜索图标样式。[#23406](https://github.com/ant-design/ant-design/pull/23406)
|
||||
- Button
|
||||
- 🐞 修复 Button 图标类型按钮的对齐问题。[#23671](https://github.com/ant-design/ant-design/pull/23671)
|
||||
- 🐞 修复 Button 图标按钮 `loading` 样式错误的问题。[#23614](https://github.com/ant-design/ant-design/pull/23614)
|
||||
- 🐞 解决 Button 无法直接被 `react-dnd` 调用的问题。[#23571](https://github.com/ant-design/ant-design/pull/23571) [@hengkx](https://github.com/hengkx)
|
||||
- Menu
|
||||
- 🆕 Menu Item 和 SubMenu 新增 `icon` 属性。[#23629](https://github.com/ant-design/ant-design/pull/23629)
|
||||
- 🐞 修复 Menu 菜单重复阴影的问题。[#23664](https://github.com/ant-design/ant-design/pull/23664)
|
||||
- 🐞 解决 Tag 无法直接被 `react-dnd` 调用的问题。[#23632](https://github.com/ant-design/ant-design/pull/23632) [@hengkx](https://github.com/hengkx)
|
||||
- Anchor
|
||||
- 🐞 修复 Anchor Link 包含多个 `#` 时无法跳转的问题。[#23595](https://github.com/ant-design/ant-design/pull/23595) [@wuzekang](https://github.com/wuzekang)
|
||||
- 🐞 修复 Input 带 `suffix` 时的元素对齐问题。[#23606](https://github.com/ant-design/ant-design/pull/23606)
|
||||
- 💄 Select 箭头打开时不再翻转。[#23468](https://github.com/ant-design/ant-design/pull/23468)
|
||||
- 💄 新增 Rate 的 `direction` 支持优化。[#23321](https://github.com/ant-design/ant-design/pull/23321)
|
||||
- 💄 调整紧凑模式下默认的字体大小。[#23135](https://github.com/ant-design/ant-design/pull/23135)
|
||||
- RTL
|
||||
- 💄 优化 Result RTL 下按钮样式。[#23733](https://github.com/ant-design/ant-design/pull/23733)
|
||||
- 💄 新增 Divider RTL 支持。[#23734](https://github.com/ant-design/ant-design/pull/23734)
|
||||
- 💄 修复 Alert 在 RTL 下无 icon 的间隔问题。[#23714](https://github.com/ant-design/ant-design/pull/23714)
|
||||
- 💄 优化 Table RTL 模式下扩展按钮动画与分页样式问题。[#23706](https://github.com/ant-design/ant-design/pull/23706)
|
||||
- 💄 修复 Table 筛选下拉框在 RTL 下的位置。[#23695](https://github.com/ant-design/ant-design/pull/23695)
|
||||
- 💄 修复 Table 勾选框图标 RTL 样式。[#23690](https://github.com/ant-design/ant-design/pull/23690)
|
||||
- 💄 优化 List RTL 样式。[#23676](https://github.com/ant-design/ant-design/pull/23676)
|
||||
- 💄 新增 Calendar RTL 支持。[#23394](https://github.com/ant-design/ant-design/pull/23394)
|
||||
- 💄 优化 Input.Search RTL 样式。[#23424](https://github.com/ant-design/ant-design/pull/23424)
|
||||
- 💄 增加 Notification RTL 设置。[#23185](https://github.com/ant-design/ant-design/pull/23185)
|
||||
- TypeScript
|
||||
- 🐞 修复 PageHeader `tag` 属性定义错误。[#23712](https://github.com/ant-design/ant-design/pull/23712) [@hengkx](https://github.com/hengkx)
|
||||
- 🗑 移除已废弃的 Button `type="danger"` TypeScript 定义并增加警告信息。[#23709](https://github.com/ant-design/ant-design/pull/23709)
|
||||
- 🐞 修复 Table pagination `position` Typescript 定义错误。[#23681](https://github.com/ant-design/ant-design/pull/23681) [@hengkx](https://github.com/hengkx)
|
||||
|
||||
## 4.1.5
|
||||
|
||||
`2020-04-25`
|
||||
|
||||
- 🐞 修复 Button.Group 中按钮没有对齐的问题。[#23590](https://github.com/ant-design/ant-design/pull/23590)
|
||||
- 🐞 修复 Select 箭头图标点击无法触发下拉的问题。[#23448](https://github.com/ant-design/ant-design/pull/23448)
|
||||
- 🐞 修复 Form 自定义 `@form-item-margin-bottom` 变量时表单校验抖动的问题。[#23436](https://github.com/ant-design/ant-design/pull/23436) [@yoyo837](https://github.com/yoyo837)
|
||||
- 🐞 修复第一个 Divider 渲染时样式不一致的问题。[#23438](https://github.com/ant-design/ant-design/pull/23438)
|
||||
- 🐞 修复嵌套 ConfigProvider 会丢失 `prefixCls` 值的问题。[#23423](https://github.com/ant-design/ant-design/pull/23423)
|
||||
- 🐞 修复 Carousel 键盘切换到非活跃 slide 上的 Radio/Checkbox 的问题。[#23380](https://github.com/ant-design/ant-design/pull/23380)
|
||||
- 🐞 修复 Tree 使用虚拟滚动时会因为 `loadData` 更新过快而锁死的问题。[#23581](https://github.com/ant-design/ant-design/pull/23581)
|
||||
- 🐞 修复 Steps 组件竖直展示时在 IE11 下样式错误的问题。[#23561](https://github.com/ant-design/ant-design/pull/23561) [@AdrianoRuberto](https://github.com/AdrianoRuberto)
|
||||
- 🐞 修复 Input.Search 高度被 `suffix` 撑高的问题和报 `react key` 重复警告的问题。[#23527](https://github.com/ant-design/ant-design/pull/23527)
|
||||
- 🐞 修复 Menu 鼠标移到缝隙处子菜单会消失的问题。[#23511](https://github.com/ant-design/ant-design/pull/23511)
|
||||
- 🐞 修复 Tree 自定义图标在加载状态下消失的问题。[#23494](https://github.com/ant-design/ant-design/pull/23494)
|
||||
- RTL
|
||||
- 🐞 修复 Alert 在 `showIcon` 和 `closable` 都存在时的 RTL 样式问题。[#23526](https://github.com/ant-design/ant-design/pull/23526)
|
||||
- 🐞 修复 Button 在 RTL 下 loading 样式不正确的问题。[#23399](https://github.com/ant-design/ant-design/pull/23399)
|
||||
- 🐞 修复 Collapse 在 RTL 下切换图标位置不正确的问题。[#23445](https://github.com/ant-design/ant-design/pull/23445)
|
||||
- 🐞 修复 Select 分组名称的 RTL 样式问题。[#23404](https://github.com/ant-design/ant-design/pull/23404)
|
||||
- 🐞 修复 Statistic 的 RTL 样式不正确的问题。[#23397](https://github.com/ant-design/ant-design/pull/23397)
|
||||
- TypeScript
|
||||
- 🐞 修复 Table 的 `selections` 类型定义。[#23462](https://github.com/ant-design/ant-design/pull/23462) [@xiaoxintang](https://github.com/xiaoxintang)
|
||||
|
||||
## 4.1.4
|
||||
|
||||
`2020-04-18`
|
||||
|
||||
- 🐞 修复暗黑主题和紧凑主题不生效的问题。[#23243](https://github.com/ant-design/ant-design/pull/23243)
|
||||
- 🐞 修复 Modal.info 等方法的 `onOk` 函数有参数时只触发一次的问题。[#23360](https://github.com/ant-design/ant-design/pull/23360)
|
||||
- 🐞 修复 Dropdown 弹出菜单背景样式问题。[#23296](https://github.com/ant-design/ant-design/pull/23296)
|
||||
- 💄 优化 PageHeader 的响应式表现。[#23277](https://github.com/ant-design/ant-design/pull/23277)
|
||||
- 🐞 修复紧凑模式下树选择出现空白。[#23231](https://github.com/ant-design/ant-design/pull/23231)
|
||||
- 🛎 修改 Checkbox 和 Switch 中控制台输出的错别字 (validate -> a valid)。[#23240](https://github.com/ant-design/ant-design/pull/23240) [@evancharlton](https://github.com/evancharlton)
|
||||
- 🐞 修复 Table `rowSelection` 在设置 `childrenColumnName` 时事件参数不正确的问题。[#23205](https://github.com/ant-design/ant-design/pull/23205)
|
||||
- Input
|
||||
- 🐞 修复 Input `type="color"` 的高度问题。[#23351](https://github.com/ant-design/ant-design/pull/23351)
|
||||
- 🐞 修复 Input 设置 `allowClear` 内联展示时,触发清除按钮样式抖动的问题。[#23259](https://github.com/ant-design/ant-design/pull/23259)
|
||||
- 🐞 修复 Input.Search 全局设置 `size` 不生效问题。[#23331](https://github.com/ant-design/ant-design/pull/23331)
|
||||
- Select
|
||||
- 🐞 修复 Select 多选时设置 `disabled` 选项仍然会展示移除按钮的问题。[#23295](https://github.com/ant-design/ant-design/pull/23295)
|
||||
- 🐞 修复 Select 自定义 `suffixIcon` 无法交互的问题。[#23274](https://github.com/ant-design/ant-design/pull/23274)
|
||||
- 🐞 修复 Select 输入光标在 Collapse 内不显示的问题。[#23250](https://github.com/ant-design/ant-design/pull/23250)
|
||||
- 国际化
|
||||
- 🌐 Form 校验信息支持国际化并增加中文文案。[#23165](https://github.com/ant-design/ant-design/pull/23165) [@hengkx](https://github.com/hengkx)
|
||||
- 🌐 完善希伯来语(以色列) 国际化。[#23302](https://github.com/ant-design/ant-design/pull/23302) [@MishaKav](https://github.com/MishaKav)
|
||||
- 🌐 完善俄语国际化。[#23303](https://github.com/ant-design/ant-design/pull/23303) [@MishaKav](https://github.com/MishaKav)
|
||||
- TypeScript
|
||||
- 🔷 更新 Tree 的类型定义。[#23348](https://github.com/ant-design/ant-design/pull/23348) [@yoyo837](https://github.com/yoyo837)
|
||||
- 🔷 更新 Form Item 的类型定义。[#22962](https://github.com/ant-design/ant-design/pull/22962) [@fa93hws](https://github.com/fa93hws)
|
||||
- 🐞 修复 Slider 组件 `value` 和 `defaultValue` 文档与 TypeScript 定义不一致的问题。[#23252](https://github.com/ant-design/ant-design/pull/23252) [@DongchengWang](https://github.com/DongchengWang)
|
||||
- RTL
|
||||
- 🐞 修复 Menu RTL 样式。[#23319](https://github.com/ant-design/ant-design/pull/23319)
|
||||
- 💄 修复 Select 的 RTL 样式。[#23235](https://github.com/ant-design/ant-design/pull/23235)
|
||||
|
||||
## 4.1.3
|
||||
|
||||
`2020-04-13`
|
||||
|
||||
- 💄 调整 Form.Item `label` 在垂直布局下的高度样式。[#23192](https://github.com/ant-design/ant-design/pull/23192)
|
||||
- 🐞 修复引用暗黑或紧凑主题时提示 `Variable is undefined` 的问题,并提供 `getThemeVariables` 方便获取对应主题变量。[#23171](https://github.com/ant-design/ant-design/pull/23171)
|
||||
- 🐞 修复 PageHeader `title` 超长时布局被破坏的问题并优化响应式表现。[#23133](https://github.com/ant-design/ant-design/pull/23133)
|
||||
- Tabs
|
||||
- 🐞 修复 Tabs `@tabs-card-height` less 变量无效的问题。[#23168](https://github.com/ant-design/ant-design/pull/23168)
|
||||
- 🐞 修复 Tabs 在 Safair 浏览器下无法显示的问题。[#23151](https://github.com/ant-design/ant-design/pull/23151) [@imhxc](https://github.com/imhxc)
|
||||
- Table
|
||||
- 🐞 修复 Table 固定列在 Safari 12 中不能固定的问题。[#23161](https://github.com/ant-design/ant-design/pull/23161)
|
||||
- 🐞 修复 Table `summary` 在小尺寸下的内边距样式。[#23140](https://github.com/ant-design/ant-design/pull/23140) [@someyoungideas](https://github.com/someyoungideas)
|
||||
- 🐞 修复 Select 不同尺寸下的对齐样式问题。[#23160](https://github.com/ant-design/ant-design/pull/23160)
|
||||
- 🐞 修复 RangePicker 在 Input.Group 内的样式问题。[#23149](https://github.com/ant-design/ant-design/pull/23149)
|
||||
- 🐞 修复 Pagination 缺少 `showTitle` TypeScript 定义的问题。[#23144](https://github.com/ant-design/ant-design/pull/23144) [@DongchengWang](https://github.com/DongchengWang)
|
||||
|
||||
## 4.1.2
|
||||
|
||||
`2020-04-10`
|
||||
|
||||
- Menu
|
||||
- 🐞 修复暗色 Menu 弹出菜单背景色为白色的问题。[#22981](https://github.com/ant-design/ant-design/pull/22981) [@AshoneA](https://github.com/AshoneA)
|
||||
- 🐞 修复 SubMenu 标题过长而导致被箭头图标部分覆盖的问题。[#23028](https://github.com/ant-design/ant-design/pull/23028) [@wwyx778](https://github.com/wwyx778)
|
||||
@@ -52,6 +235,8 @@ timeline: true
|
||||
|
||||
## 4.1.1
|
||||
|
||||
`2020-04-05`
|
||||
|
||||
- 🐞 移除 Tabs 的内容区域的 focus 蓝色轮廓线。[#22752](https://github.com/ant-design/ant-design/pull/22752) [@MrHeer](https://github.com/MrHeer)
|
||||
- 🐞 修复 Input 前后缀添加弹出元素不能点击获得焦点的问题。[#22887](https://github.com/ant-design/ant-design/pull/22887)
|
||||
- Table
|
||||
|
||||
@@ -34,7 +34,7 @@ This Code of Conduct applies both within project spaces and in public spaces whe
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at xingmin.zhu@alipay.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [xingmin.zhu@alipay.com.](mailto:xingmin.zhu@alipay.com.) The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ Uma solução empresarial de design e biblioteca UI para React.
|
||||
|
||||
## 🖥 Suporte aos ambientes
|
||||
|
||||
- Navegadores modernos e Internet Explorer 11+ (com [polyfills](https://ant.design/docs/react/getting-started#Compatibility))
|
||||
- Navegadores modernos e Internet Explorer 11 (com [polyfills](https://ant.design/docs/react/getting-started#Compatibility))
|
||||
- Renderização no lado do servidor (server-side)
|
||||
- [Electron](https://www.electronjs.org/)
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
|
||||
## 🖥 支持环境
|
||||
|
||||
- 现代浏览器和 IE11 及以上。
|
||||
- 现代浏览器和 IE11(需要 [polyfills](https://ant.design/docs/react/getting-started-cn#兼容性))。
|
||||
- 支持服务端渲染。
|
||||
- [Electron](https://www.electronjs.org/)
|
||||
|
||||
|
||||
18
README.md
18
README.md
@@ -14,7 +14,7 @@ An enterprise-class UI design language and React UI library.
|
||||
|
||||
[![david deps][david-image]][david-url] [![david devDeps][david-dev-image]][david-dev-url] [![Total alerts][lgtm-image]][lgtm-url] [![FOSSA Status][fossa-image]][fossa-url] [![Issues need help][help-wanted-image]][help-wanted-url]
|
||||
|
||||
[![Follow Twitter][twitter-image]][twitter-url] [![Gitter][gitter-english-image]][gitter-english-url] [![Gitter][gitter-chinese-image]][gitter-chinese-url]
|
||||
[![Follow Twitter][twitter-image]][twitter-url] [![Gitter][gitter-english-image]][gitter-english-url] [![Gitter][gitter-chinese-image]][gitter-chinese-url] [![[SemVer stability]][semver-stability-image]][semver-stability-url]
|
||||
|
||||
[npm-image]: http://img.shields.io/npm/v/antd.svg?style=flat-square
|
||||
[npm-url]: http://npmjs.org/package/antd
|
||||
@@ -24,26 +24,26 @@ An enterprise-class UI design language and React UI library.
|
||||
[github-action-url]: https://github.com/ant-design/ant-design/actions?query=workflow%3Atest
|
||||
[codecov-image]: https://img.shields.io/codecov/c/github/ant-design/ant-design/master.svg?style=flat-square
|
||||
[codecov-url]: https://codecov.io/gh/ant-design/ant-design/branch/master
|
||||
[david-image]: https://david-dm.org/ant-design/ant-design/status.svg?style=flat-square
|
||||
[david-image]: https://img.shields.io/david/ant-design/ant-design?style=flat-square
|
||||
[david-dev-url]: https://david-dm.org/ant-design/ant-design?type=dev
|
||||
[david-dev-image]: https://david-dm.org/ant-design/ant-design/dev-status.svg?style=flat-square
|
||||
[david-dev-image]: https://img.shields.io/david/dev/ant-design/ant-design?style=flat-square
|
||||
[david-url]: https://david-dm.org/ant-design/ant-design
|
||||
[download-image]: https://img.shields.io/npm/dm/antd.svg?style=flat-square
|
||||
[download-url]: https://npmjs.org/package/antd
|
||||
[lgtm-image]: https://flat.badgen.net/lgtm/alerts/g/ant-design/ant-design
|
||||
[lgtm-url]: https://lgtm.com/projects/g/ant-design/ant-design/alerts/
|
||||
[lgtm-image]: https://flat.badgen.net/lgtm/alerts/g/ant-design/ant-design
|
||||
[lgtm-url]: https://lgtm.com/projects/g/ant-design/ant-design/alerts/
|
||||
[fossa-image]: https://app.fossa.io/api/projects/git%2Bgithub.com%2Fant-design%2Fant-design.svg?type=shield
|
||||
[fossa-url]: https://app.fossa.io/projects/git%2Bgithub.com%2Fant-design%2Fant-design?ref=badge_shield
|
||||
[help-wanted-image]: https://flat.badgen.net/github/label-issues/ant-design/ant-design/help%20wanted/open
|
||||
[help-wanted-url]: https://github.com/ant-design/ant-design/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22
|
||||
[twitter-image]: https://img.shields.io/twitter/follow/AntDesignUI.svg?label=Ant%20Design&style=social
|
||||
[twitter-url]: https://twitter.com/AntDesignUI
|
||||
[gitter-english-image]: https://img.shields.io/gitter/room/ant-design/ant-design-english.svg?style=flat-square&logoWidth=20&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgd2lkdGg9IjEyMzUiIGhlaWdodD0iNjUwIiB2aWV3Qm94PSIwIDAgNzQxMCAzOTAwIj4NCjxyZWN0IHdpZHRoPSI3NDEwIiBoZWlnaHQ9IjM5MDAiIGZpbGw9IiNiMjIyMzQiLz4NCjxwYXRoIGQ9Ik0wLDQ1MEg3NDEwbTAsNjAwSDBtMCw2MDBINzQxMG0wLDYwMEgwbTAsNjAwSDc0MTBtMCw2MDBIMCIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjMwMCIvPg0KPHJlY3Qgd2lkdGg9IjI5NjQiIGhlaWdodD0iMjEwMCIgZmlsbD0iIzNjM2I2ZSIvPg0KPGcgZmlsbD0iI2ZmZiI%2BDQo8ZyBpZD0iczE4Ij4NCjxnIGlkPSJzOSI%2BDQo8ZyBpZD0iczUiPg0KPGcgaWQ9InM0Ij4NCjxwYXRoIGlkPSJzIiBkPSJNMjQ3LDkwIDMxNy41MzQyMzAsMzA3LjA4MjAzOSAxMzIuODczMjE4LDE3Mi45MTc5NjFIMzYxLjEyNjc4MkwxNzYuNDY1NzcwLDMwNy4wODIwMzl6Ii8%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzIiB5PSI0MjAiLz4NCjx1c2UgeGxpbms6aHJlZj0iI3MiIHk9Ijg0MCIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgeT0iMTI2MCIvPg0KPC9nPg0KPHVzZSB4bGluazpocmVmPSIjcyIgeT0iMTY4MCIvPg0KPC9nPg0KPHVzZSB4bGluazpocmVmPSIjczQiIHg9IjI0NyIgeT0iMjEwIi8%2BDQo8L2c%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzOSIgeD0iNDk0Ii8%2BDQo8L2c%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzMTgiIHg9Ijk4OCIvPg0KPHVzZSB4bGluazpocmVmPSIjczkiIHg9IjE5NzYiLz4NCjx1c2UgeGxpbms6aHJlZj0iI3M1IiB4PSIyNDcwIi8%2BDQo8L2c%2BDQo8L3N2Zz4%3D
|
||||
[gitter-english-image]: https://img.shields.io/gitter/room/ant-design/ant-design-english.svg?style=flat-square&logoWidth=18&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgd2lkdGg9IjEyMzUiIGhlaWdodD0iNjUwIiB2aWV3Qm94PSIwIDAgNzQxMCAzOTAwIj4NCjxyZWN0IHdpZHRoPSI3NDEwIiBoZWlnaHQ9IjM5MDAiIGZpbGw9IiNiMjIyMzQiLz4NCjxwYXRoIGQ9Ik0wLDQ1MEg3NDEwbTAsNjAwSDBtMCw2MDBINzQxMG0wLDYwMEgwbTAsNjAwSDc0MTBtMCw2MDBIMCIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjMwMCIvPg0KPHJlY3Qgd2lkdGg9IjI5NjQiIGhlaWdodD0iMjEwMCIgZmlsbD0iIzNjM2I2ZSIvPg0KPGcgZmlsbD0iI2ZmZiI%2BDQo8ZyBpZD0iczE4Ij4NCjxnIGlkPSJzOSI%2BDQo8ZyBpZD0iczUiPg0KPGcgaWQ9InM0Ij4NCjxwYXRoIGlkPSJzIiBkPSJNMjQ3LDkwIDMxNy41MzQyMzAsMzA3LjA4MjAzOSAxMzIuODczMjE4LDE3Mi45MTc5NjFIMzYxLjEyNjc4MkwxNzYuNDY1NzcwLDMwNy4wODIwMzl6Ii8%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzIiB5PSI0MjAiLz4NCjx1c2UgeGxpbms6aHJlZj0iI3MiIHk9Ijg0MCIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgeT0iMTI2MCIvPg0KPC9nPg0KPHVzZSB4bGluazpocmVmPSIjcyIgeT0iMTY4MCIvPg0KPC9nPg0KPHVzZSB4bGluazpocmVmPSIjczQiIHg9IjI0NyIgeT0iMjEwIi8%2BDQo8L2c%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzOSIgeD0iNDk0Ii8%2BDQo8L2c%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzMTgiIHg9Ijk4OCIvPg0KPHVzZSB4bGluazpocmVmPSIjczkiIHg9IjE5NzYiLz4NCjx1c2UgeGxpbms6aHJlZj0iI3M1IiB4PSIyNDcwIi8%2BDQo8L2c%2BDQo8L3N2Zz4%3D
|
||||
[gitter-english-url]: https://gitter.im/ant-design/ant-design-english?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge
|
||||
[gitter-chinese-image]: https://img.shields.io/gitter/room/ant-design/ant-design.svg?style=flat-square&logoWidth=20&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgd2lkdGg9IjkwMCIgaGVpZ2h0PSI2MDAiIHZpZXdCb3g9IjAgMCAzMCAyMCI%2BDQo8ZGVmcz4NCjxwYXRoIGlkPSJzIiBkPSJNMCwtMSAwLjU4Nzc4NSwwLjgwOTAxNyAtMC45NTEwNTcsLTAuMzA5MDE3SDAuOTUxMDU3TC0wLjU4Nzc4NSwwLjgwOTAxN3oiIGZpbGw9IiNmZmRlMDAiLz4NCjwvZGVmcz4NCjxyZWN0IHdpZHRoPSIzMCIgaGVpZ2h0PSIyMCIgZmlsbD0iI2RlMjkxMCIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNSw1KSBzY2FsZSgzKSIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTAsMikgcm90YXRlKDIzLjAzNjI0MykiLz4NCjx1c2UgeGxpbms6aHJlZj0iI3MiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEyLDQpIHJvdGF0ZSg0NS44Njk4OTgpIi8%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMiw3KSByb3RhdGUoNjkuOTQ1Mzk2KSIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTAsOSkgcm90YXRlKDIwLjY1OTgwOCkiLz4NCjwvc3ZnPg%3D%3D
|
||||
[gitter-chinese-image]: https://img.shields.io/gitter/room/ant-design/ant-design.svg?style=flat-square&logoWidth=18&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgd2lkdGg9IjkwMCIgaGVpZ2h0PSI2MDAiIHZpZXdCb3g9IjAgMCAzMCAyMCI%2BDQo8ZGVmcz4NCjxwYXRoIGlkPSJzIiBkPSJNMCwtMSAwLjU4Nzc4NSwwLjgwOTAxNyAtMC45NTEwNTcsLTAuMzA5MDE3SDAuOTUxMDU3TC0wLjU4Nzc4NSwwLjgwOTAxN3oiIGZpbGw9IiNmZmRlMDAiLz4NCjwvZGVmcz4NCjxyZWN0IHdpZHRoPSIzMCIgaGVpZ2h0PSIyMCIgZmlsbD0iI2RlMjkxMCIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNSw1KSBzY2FsZSgzKSIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTAsMikgcm90YXRlKDIzLjAzNjI0MykiLz4NCjx1c2UgeGxpbms6aHJlZj0iI3MiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEyLDQpIHJvdGF0ZSg0NS44Njk4OTgpIi8%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMiw3KSByb3RhdGUoNjkuOTQ1Mzk2KSIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTAsOSkgcm90YXRlKDIwLjY1OTgwOCkiLz4NCjwvc3ZnPg%3D%3D
|
||||
[gitter-chinese-url]: https://gitter.im/ant-design/ant-design?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
|
||||
[semver-stability-url]: https://dependabot.com/compatibility-score.html/?dependency-name=antd&package-manager=npm_and_yarn&new-version=latest
|
||||
[semver-stability-image]: https://api.dependabot.com/badges/compatibility_score?dependency-name=antd&package-manager=npm_and_yarn&target-version=latest&version-scheme=semver
|
||||
|
||||
</div>
|
||||
|
||||
@@ -62,7 +62,7 @@ English | [Português](./README-pt_BR.md) | [简体中文](./README-zh_CN.md)
|
||||
|
||||
## 🖥 Environment Support
|
||||
|
||||
- Modern browsers and Internet Explorer 11+ (with [polyfills](https://ant.design/docs/react/getting-started#Compatibility))
|
||||
- Modern browsers and Internet Explorer 11 (with [polyfills](https://ant.design/docs/react/getting-started#Compatibility))
|
||||
- Server-side Rendering
|
||||
- [Electron](https://www.electronjs.org/)
|
||||
|
||||
@@ -112,7 +112,7 @@ See [i18n](http://ant.design/docs/react/i18n).
|
||||
## 🔗 Links
|
||||
|
||||
- [Home page](http://ant.design/)
|
||||
- [Components](http://ant.design/docs/react/introduce)
|
||||
- [Components](https://ant.design/components/button/)
|
||||
- [Ant Design Pro](http://pro.ant.design/)
|
||||
- [Change Log](CHANGELOG.en-US.md)
|
||||
- [rc-components](http://react-component.github.io/)
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
const __NULL__ = { notExist: true };
|
||||
|
||||
export function spyElementPrototypes(Element, properties) {
|
||||
const propNames = Object.keys(properties);
|
||||
const originDescriptors = {};
|
||||
|
||||
propNames.forEach(propName => {
|
||||
const originDescriptor = Object.getOwnPropertyDescriptor(Element.prototype, propName);
|
||||
originDescriptors[propName] = originDescriptor || __NULL__;
|
||||
|
||||
const spyProp = properties[propName];
|
||||
|
||||
if (typeof spyProp === 'function') {
|
||||
// If is a function
|
||||
Element.prototype[propName] = function spyFunc(...args) {
|
||||
return spyProp.call(this, originDescriptor, ...args);
|
||||
};
|
||||
} else {
|
||||
// Otherwise tread as a property
|
||||
Object.defineProperty(Element.prototype, propName, {
|
||||
...spyProp,
|
||||
set(value) {
|
||||
if (spyProp.set) {
|
||||
return spyProp.set.call(this, originDescriptor, value);
|
||||
}
|
||||
return originDescriptor.set(value);
|
||||
},
|
||||
get() {
|
||||
if (spyProp.get) {
|
||||
return spyProp.get.call(this, originDescriptor);
|
||||
}
|
||||
return originDescriptor.get();
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
mockRestore() {
|
||||
propNames.forEach(propName => {
|
||||
const originDescriptor = originDescriptors[propName];
|
||||
if (originDescriptor === __NULL__) {
|
||||
delete Element.prototype[propName];
|
||||
} else if (typeof originDescriptor === 'function') {
|
||||
Element.prototype[propName] = originDescriptor;
|
||||
} else {
|
||||
Object.defineProperty(Element.prototype, propName, originDescriptor);
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function spyElementPrototype(Element, propName, property) {
|
||||
return spyElementPrototypes(Element, {
|
||||
[propName]: property,
|
||||
});
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { tuple } from './type';
|
||||
import { ElementOf, tuple } from './type';
|
||||
|
||||
export const PresetStatusColorTypes = tuple('success', 'processing', 'error', 'default', 'warning');
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
@@ -18,5 +18,5 @@ export const PresetColorTypes = tuple(
|
||||
'lime',
|
||||
);
|
||||
|
||||
export type PresetColorType = typeof PresetColorTypes[number];
|
||||
export type PresetStatusColorType = typeof PresetStatusColorTypes[number];
|
||||
export type PresetColorType = ElementOf<typeof PresetColorTypes>;
|
||||
export type PresetStatusColorType = ElementOf<typeof PresetStatusColorTypes>;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
|
||||
export type RenderFunction = () => React.ReactNode;
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ interface Motion {
|
||||
motionEnter?: boolean;
|
||||
motionLeave?: boolean;
|
||||
motionLeaveImmediately?: boolean; // Trigger leave motion immediately
|
||||
motionDeadline?: number;
|
||||
removeOnLeave?: boolean;
|
||||
leavedClassName?: string;
|
||||
onAppearStart?: MotionFunc;
|
||||
@@ -35,6 +36,7 @@ const collapseMotion: Motion = {
|
||||
onEnterActive: getRealHeight,
|
||||
onLeaveStart: getCurrentHeight,
|
||||
onLeaveActive: getCollapsedHeight,
|
||||
motionDeadline: 500,
|
||||
};
|
||||
|
||||
export default collapseMotion;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Deprecated. We should replace the animation with pure react motion instead of modify style directly.
|
||||
* If you are creating new component with animation, please use `./motion`.
|
||||
*/
|
||||
import cssAnimation from 'css-animation';
|
||||
import cssAnimation from '@ant-design/css-animation';
|
||||
import raf from 'raf';
|
||||
|
||||
function animate(node: HTMLElement, show: boolean, done: () => void) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
|
||||
export function fillRef<T>(ref: React.Ref<T>, node: T) {
|
||||
if (typeof ref === 'function') {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { findDOMNode } from 'react-dom';
|
||||
import TransitionEvents from 'css-animation/lib/Event';
|
||||
import TransitionEvents from '@ant-design/css-animation/lib/Event';
|
||||
import raf from './raf';
|
||||
import { ConfigConsumer, ConfigConsumerProps, CSPConfig } from '../config-provider';
|
||||
|
||||
|
||||
@@ -1,201 +0,0 @@
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import Affix from '..';
|
||||
import { getObserverEntities } from '../utils';
|
||||
import Button from '../../button';
|
||||
import { spyElementPrototype } from '../../__tests__/util/domHook';
|
||||
import rtlTest from '../../../tests/shared/rtlTest';
|
||||
import { sleep } from '../../../tests/utils';
|
||||
|
||||
const events = {};
|
||||
|
||||
class AffixMounter extends React.Component {
|
||||
componentDidMount() {
|
||||
this.container.addEventListener = jest.fn().mockImplementation((event, cb) => {
|
||||
events[event] = cb;
|
||||
});
|
||||
}
|
||||
|
||||
getTarget = () => this.container;
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
ref={node => {
|
||||
this.container = node;
|
||||
}}
|
||||
className="container"
|
||||
>
|
||||
<Affix
|
||||
className="fixed"
|
||||
target={this.getTarget}
|
||||
ref={ele => {
|
||||
this.affix = ele;
|
||||
}}
|
||||
{...this.props}
|
||||
>
|
||||
<Button type="primary">Fixed at the top of container</Button>
|
||||
</Affix>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
describe('Affix Render', () => {
|
||||
rtlTest(Affix);
|
||||
|
||||
let wrapper;
|
||||
let domMock;
|
||||
|
||||
const classRect = {
|
||||
container: {
|
||||
top: 0,
|
||||
bottom: 100,
|
||||
},
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
domMock = spyElementPrototype(HTMLElement, 'getBoundingClientRect', function mockBounding() {
|
||||
return (
|
||||
classRect[this.className] || {
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
domMock.mockRestore();
|
||||
});
|
||||
|
||||
const movePlaceholder = async top => {
|
||||
classRect.fixed = {
|
||||
top,
|
||||
bottom: top,
|
||||
};
|
||||
events.scroll({
|
||||
type: 'scroll',
|
||||
});
|
||||
await sleep(20);
|
||||
};
|
||||
|
||||
it('Anchor render perfectly', async () => {
|
||||
document.body.innerHTML = '<div id="mounter" />';
|
||||
|
||||
wrapper = mount(<AffixMounter />, { attachTo: document.getElementById('mounter') });
|
||||
await sleep(20);
|
||||
|
||||
await movePlaceholder(0);
|
||||
expect(wrapper.instance().affix.state.affixStyle).toBeFalsy();
|
||||
|
||||
await movePlaceholder(-100);
|
||||
expect(wrapper.instance().affix.state.affixStyle).toBeTruthy();
|
||||
|
||||
await movePlaceholder(0);
|
||||
expect(wrapper.instance().affix.state.affixStyle).toBeFalsy();
|
||||
});
|
||||
|
||||
it('support offsetBottom', async () => {
|
||||
document.body.innerHTML = '<div id="mounter" />';
|
||||
|
||||
wrapper = mount(<AffixMounter offsetBottom={0} />, {
|
||||
attachTo: document.getElementById('mounter'),
|
||||
});
|
||||
|
||||
await sleep(20);
|
||||
|
||||
await movePlaceholder(300);
|
||||
expect(wrapper.instance().affix.state.affixStyle).toBeTruthy();
|
||||
|
||||
await movePlaceholder(0);
|
||||
expect(wrapper.instance().affix.state.affixStyle).toBeFalsy();
|
||||
|
||||
await movePlaceholder(300);
|
||||
expect(wrapper.instance().affix.state.affixStyle).toBeTruthy();
|
||||
});
|
||||
|
||||
it('updatePosition when offsetTop changed', async () => {
|
||||
document.body.innerHTML = '<div id="mounter" />';
|
||||
|
||||
wrapper = mount(<AffixMounter offsetTop={0} />, {
|
||||
attachTo: document.getElementById('mounter'),
|
||||
});
|
||||
await sleep(20);
|
||||
|
||||
await movePlaceholder(-100);
|
||||
expect(wrapper.instance().affix.state.affixStyle.top).toBe(0);
|
||||
wrapper.setProps({
|
||||
offsetTop: 10,
|
||||
});
|
||||
await sleep(20);
|
||||
expect(wrapper.instance().affix.state.affixStyle.top).toBe(10);
|
||||
});
|
||||
|
||||
describe('updatePosition when target changed', () => {
|
||||
it('function change', () => {
|
||||
document.body.innerHTML = '<div id="mounter" />';
|
||||
const container = document.querySelector('#id');
|
||||
const getTarget = () => container;
|
||||
wrapper = mount(<Affix target={getTarget} />);
|
||||
wrapper.setProps({ target: null });
|
||||
expect(wrapper.instance().state.status).toBe(0);
|
||||
expect(wrapper.instance().state.affixStyle).toBe(undefined);
|
||||
expect(wrapper.instance().state.placeholderStyle).toBe(undefined);
|
||||
});
|
||||
|
||||
it('instance change', async () => {
|
||||
const getObserverLength = () => Object.keys(getObserverEntities()).length;
|
||||
|
||||
const container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
let target = container;
|
||||
|
||||
const originLength = getObserverLength();
|
||||
const getTarget = () => target;
|
||||
wrapper = mount(<Affix target={getTarget} />);
|
||||
await sleep(50);
|
||||
|
||||
expect(getObserverLength()).toBe(originLength + 1);
|
||||
target = null;
|
||||
wrapper.setProps({});
|
||||
wrapper.update();
|
||||
await sleep(50);
|
||||
expect(getObserverLength()).toBe(originLength);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updatePosition when size changed', () => {
|
||||
function test(name, index) {
|
||||
it(name, async () => {
|
||||
document.body.innerHTML = '<div id="mounter" />';
|
||||
|
||||
const updateCalled = jest.fn();
|
||||
wrapper = mount(<AffixMounter offsetBottom={0} onTestUpdatePosition={updateCalled} />, {
|
||||
attachTo: document.getElementById('mounter'),
|
||||
});
|
||||
|
||||
await sleep(20);
|
||||
|
||||
await movePlaceholder(300);
|
||||
expect(wrapper.instance().affix.state.affixStyle).toBeTruthy();
|
||||
await sleep(20);
|
||||
wrapper.update();
|
||||
|
||||
// Mock trigger resize
|
||||
updateCalled.mockReset();
|
||||
wrapper
|
||||
.find('ResizeObserver')
|
||||
.at(index)
|
||||
.instance()
|
||||
.onResize([{ target: { getBoundingClientRect: () => ({ width: 99, height: 99 }) } }]);
|
||||
await sleep(20);
|
||||
|
||||
expect(updateCalled).toHaveBeenCalled();
|
||||
});
|
||||
}
|
||||
|
||||
test('inner', 0);
|
||||
test('outer', 1);
|
||||
});
|
||||
});
|
||||
230
components/affix/__tests__/Affix.test.tsx
Normal file
230
components/affix/__tests__/Affix.test.tsx
Normal file
@@ -0,0 +1,230 @@
|
||||
import React from 'react';
|
||||
import { mount, ReactWrapper, HTMLAttributes } from 'enzyme';
|
||||
import ResizeObserverImpl from 'rc-resize-observer';
|
||||
import Affix, { AffixProps, AffixState } from '..';
|
||||
import { getObserverEntities } from '../utils';
|
||||
import Button from '../../button';
|
||||
import rtlTest from '../../../tests/shared/rtlTest';
|
||||
import { sleep } from '../../../tests/utils';
|
||||
|
||||
const events: Partial<Record<keyof HTMLElementEventMap, (ev: Partial<Event>) => void>> = {};
|
||||
|
||||
class AffixMounter extends React.Component<{
|
||||
offsetBottom?: number;
|
||||
offsetTop?: number;
|
||||
onTestUpdatePosition?(): void;
|
||||
}> {
|
||||
private container: HTMLDivElement;
|
||||
|
||||
public affix: Affix;
|
||||
|
||||
componentDidMount() {
|
||||
this.container.addEventListener = jest
|
||||
.fn()
|
||||
.mockImplementation((event: keyof HTMLElementEventMap, cb: (ev: Partial<Event>) => void) => {
|
||||
events[event] = cb;
|
||||
});
|
||||
}
|
||||
|
||||
getTarget = () => this.container;
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
ref={node => {
|
||||
this.container = node!;
|
||||
}}
|
||||
className="container"
|
||||
>
|
||||
<Affix
|
||||
className="fixed"
|
||||
target={this.getTarget}
|
||||
ref={ele => {
|
||||
this.affix = ele!;
|
||||
}}
|
||||
{...this.props}
|
||||
>
|
||||
<Button type="primary">Fixed at the top of container</Button>
|
||||
</Affix>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
describe('Affix Render', () => {
|
||||
rtlTest(Affix);
|
||||
|
||||
const domMock = jest.spyOn(HTMLElement.prototype, 'getBoundingClientRect');
|
||||
let affixMounterWrapper: ReactWrapper<unknown, unknown, AffixMounter>;
|
||||
let affixWrapper: ReactWrapper<AffixProps, AffixState, Affix>;
|
||||
|
||||
const classRect: Record<string, DOMRect> = {
|
||||
container: {
|
||||
top: 0,
|
||||
bottom: 100,
|
||||
} as DOMRect,
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
domMock.mockImplementation(function fn(this: HTMLElement) {
|
||||
return (
|
||||
classRect[this.className] || {
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
domMock.mockRestore();
|
||||
});
|
||||
|
||||
const movePlaceholder = async (top: number) => {
|
||||
classRect.fixed = {
|
||||
top,
|
||||
bottom: top,
|
||||
} as DOMRect;
|
||||
if (events.scroll == null) {
|
||||
throw new Error('scroll should be set');
|
||||
}
|
||||
events.scroll({
|
||||
type: 'scroll',
|
||||
});
|
||||
await sleep(20);
|
||||
};
|
||||
|
||||
it('Anchor render perfectly', async () => {
|
||||
document.body.innerHTML = '<div id="mounter" />';
|
||||
|
||||
affixMounterWrapper = mount(<AffixMounter />, { attachTo: document.getElementById('mounter') });
|
||||
await sleep(20);
|
||||
|
||||
await movePlaceholder(0);
|
||||
expect(affixMounterWrapper.instance().affix.state.affixStyle).toBeFalsy();
|
||||
|
||||
await movePlaceholder(-100);
|
||||
expect(affixMounterWrapper.instance().affix.state.affixStyle).toBeTruthy();
|
||||
|
||||
await movePlaceholder(0);
|
||||
expect(affixMounterWrapper.instance().affix.state.affixStyle).toBeFalsy();
|
||||
});
|
||||
|
||||
it('support offsetBottom', async () => {
|
||||
document.body.innerHTML = '<div id="mounter" />';
|
||||
|
||||
affixMounterWrapper = mount(<AffixMounter offsetBottom={0} />, {
|
||||
attachTo: document.getElementById('mounter'),
|
||||
});
|
||||
|
||||
await sleep(20);
|
||||
|
||||
await movePlaceholder(300);
|
||||
expect(affixMounterWrapper.instance().affix.state.affixStyle).toBeTruthy();
|
||||
|
||||
await movePlaceholder(0);
|
||||
expect(affixMounterWrapper.instance().affix.state.affixStyle).toBeFalsy();
|
||||
|
||||
await movePlaceholder(300);
|
||||
expect(affixMounterWrapper.instance().affix.state.affixStyle).toBeTruthy();
|
||||
});
|
||||
|
||||
it('updatePosition when offsetTop changed', async () => {
|
||||
document.body.innerHTML = '<div id="mounter" />';
|
||||
|
||||
affixMounterWrapper = mount(<AffixMounter offsetTop={0} />, {
|
||||
attachTo: document.getElementById('mounter'),
|
||||
});
|
||||
await sleep(20);
|
||||
|
||||
await movePlaceholder(-100);
|
||||
expect(affixMounterWrapper.instance().affix.state.affixStyle?.top).toBe(0);
|
||||
affixMounterWrapper.setProps({
|
||||
offsetTop: 10,
|
||||
});
|
||||
await sleep(20);
|
||||
expect(affixMounterWrapper.instance().affix.state.affixStyle?.top).toBe(10);
|
||||
});
|
||||
|
||||
describe('updatePosition when target changed', () => {
|
||||
it('function change', () => {
|
||||
document.body.innerHTML = '<div id="mounter" />';
|
||||
const container = document.querySelector('#id') as HTMLDivElement;
|
||||
const getTarget = () => container;
|
||||
affixWrapper = mount(<Affix target={getTarget}>{null}</Affix>);
|
||||
affixWrapper.setProps({ target: () => null });
|
||||
expect(affixWrapper.instance().state.status).toBe(0);
|
||||
expect(affixWrapper.instance().state.affixStyle).toBe(undefined);
|
||||
expect(affixWrapper.instance().state.placeholderStyle).toBe(undefined);
|
||||
});
|
||||
|
||||
it('instance change', async () => {
|
||||
const getObserverLength = () => Object.keys(getObserverEntities()).length;
|
||||
|
||||
const container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
let target: HTMLDivElement | null = container;
|
||||
|
||||
const originLength = getObserverLength();
|
||||
const getTarget = () => target;
|
||||
affixWrapper = mount(<Affix target={getTarget}>{null}</Affix>);
|
||||
await sleep(50);
|
||||
|
||||
expect(getObserverLength()).toBe(originLength + 1);
|
||||
target = null;
|
||||
affixWrapper.setProps({});
|
||||
affixWrapper.update();
|
||||
await sleep(50);
|
||||
expect(getObserverLength()).toBe(originLength);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updatePosition when size changed', () => {
|
||||
it.each([
|
||||
{ name: 'inner', index: 0 },
|
||||
{ name: 'outer', index: 1 },
|
||||
])(name, async ({ index }) => {
|
||||
document.body.innerHTML = '<div id="mounter" />';
|
||||
|
||||
const updateCalled = jest.fn();
|
||||
affixMounterWrapper = mount(
|
||||
<AffixMounter offsetBottom={0} onTestUpdatePosition={updateCalled} />,
|
||||
{
|
||||
attachTo: document.getElementById('mounter'),
|
||||
},
|
||||
);
|
||||
|
||||
await sleep(20);
|
||||
|
||||
await movePlaceholder(300);
|
||||
expect(affixMounterWrapper.instance().affix.state.affixStyle).toBeTruthy();
|
||||
await sleep(20);
|
||||
affixMounterWrapper.update();
|
||||
|
||||
// Mock trigger resize
|
||||
updateCalled.mockReset();
|
||||
const resizeObserverInstance: ReactWrapper<
|
||||
HTMLAttributes,
|
||||
unknown,
|
||||
ResizeObserverImpl
|
||||
> = affixMounterWrapper.find('ResizeObserver') as any;
|
||||
resizeObserverInstance
|
||||
.at(index)
|
||||
.instance()
|
||||
.onResize(
|
||||
[
|
||||
{
|
||||
target: {
|
||||
getBoundingClientRect: () => ({ width: 99, height: 99 }),
|
||||
} as Element,
|
||||
contentRect: {} as DOMRect,
|
||||
},
|
||||
],
|
||||
({} as unknown) as ResizeObserver,
|
||||
);
|
||||
await sleep(20);
|
||||
|
||||
expect(updateCalled).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -2,7 +2,7 @@ import * as React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import omit from 'omit.js';
|
||||
import ResizeObserver from 'rc-resize-observer';
|
||||
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
|
||||
import { ConfigContext, ConfigConsumerProps } from '../config-provider';
|
||||
import { throttleByAnimationFrameDecorator } from '../_util/throttleByAnimationFrame';
|
||||
|
||||
import {
|
||||
@@ -32,7 +32,7 @@ export interface AffixProps {
|
||||
target?: () => Window | HTMLElement | null;
|
||||
prefixCls?: string;
|
||||
className?: string;
|
||||
children: React.ReactElement;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
enum AffixStatus {
|
||||
@@ -50,9 +50,7 @@ export interface AffixState {
|
||||
}
|
||||
|
||||
class Affix extends React.Component<AffixProps, AffixState> {
|
||||
static defaultProps = {
|
||||
target: getDefaultTarget,
|
||||
};
|
||||
static contextType = ConfigContext;
|
||||
|
||||
state: AffixState = {
|
||||
status: AffixStatus.None,
|
||||
@@ -66,14 +64,27 @@ class Affix extends React.Component<AffixProps, AffixState> {
|
||||
|
||||
private timeout: number;
|
||||
|
||||
context: ConfigConsumerProps;
|
||||
|
||||
private getTargetFunc() {
|
||||
const { getTargetContainer } = this.context;
|
||||
const { target } = this.props;
|
||||
|
||||
if (target !== undefined) {
|
||||
return target;
|
||||
}
|
||||
|
||||
return getTargetContainer || getDefaultTarget;
|
||||
}
|
||||
|
||||
// Event handler
|
||||
componentDidMount() {
|
||||
const { target } = this.props;
|
||||
if (target) {
|
||||
const targetFunc = this.getTargetFunc();
|
||||
if (targetFunc) {
|
||||
// [Legacy] Wait for parent component ref has its value.
|
||||
// We should use target as directly element instead of function which makes element check hard.
|
||||
this.timeout = setTimeout(() => {
|
||||
addObserveTarget(target(), this);
|
||||
addObserveTarget(targetFunc(), this);
|
||||
// Mock Event object.
|
||||
this.updatePosition();
|
||||
});
|
||||
@@ -82,10 +93,10 @@ class Affix extends React.Component<AffixProps, AffixState> {
|
||||
|
||||
componentDidUpdate(prevProps: AffixProps) {
|
||||
const { prevTarget } = this.state;
|
||||
const { target } = this.props;
|
||||
const targetFunc = this.getTargetFunc();
|
||||
let newTarget = null;
|
||||
if (target) {
|
||||
newTarget = target() || null;
|
||||
if (targetFunc) {
|
||||
newTarget = targetFunc() || null;
|
||||
}
|
||||
|
||||
if (prevTarget !== newTarget) {
|
||||
@@ -141,15 +152,16 @@ class Affix extends React.Component<AffixProps, AffixState> {
|
||||
// =================== Measure ===================
|
||||
measure = () => {
|
||||
const { status, lastAffix } = this.state;
|
||||
const { target, onChange } = this.props;
|
||||
if (status !== AffixStatus.Prepare || !this.fixedNode || !this.placeholderNode || !target) {
|
||||
const { onChange } = this.props;
|
||||
const targetFunc = this.getTargetFunc();
|
||||
if (status !== AffixStatus.Prepare || !this.fixedNode || !this.placeholderNode || !targetFunc) {
|
||||
return;
|
||||
}
|
||||
|
||||
const offsetTop = this.getOffsetTop();
|
||||
const offsetBottom = this.getOffsetBottom();
|
||||
|
||||
const targetNode = target();
|
||||
const targetNode = targetFunc();
|
||||
if (!targetNode) {
|
||||
return;
|
||||
}
|
||||
@@ -220,15 +232,15 @@ class Affix extends React.Component<AffixProps, AffixState> {
|
||||
|
||||
@throttleByAnimationFrameDecorator()
|
||||
lazyUpdatePosition() {
|
||||
const { target } = this.props;
|
||||
const targetFunc = this.getTargetFunc();
|
||||
const { affixStyle } = this.state;
|
||||
|
||||
// Check position change before measure to make Safari smooth
|
||||
if (target && affixStyle) {
|
||||
if (targetFunc && affixStyle) {
|
||||
const offsetTop = this.getOffsetTop();
|
||||
const offsetBottom = this.getOffsetBottom();
|
||||
|
||||
const targetNode = target();
|
||||
const targetNode = targetFunc();
|
||||
if (targetNode && this.placeholderNode) {
|
||||
const targetRect = getTargetRect(targetNode);
|
||||
const placeholderReact = getTargetRect(this.placeholderNode);
|
||||
@@ -249,7 +261,8 @@ class Affix extends React.Component<AffixProps, AffixState> {
|
||||
}
|
||||
|
||||
// =================== Render ===================
|
||||
renderAffix = ({ getPrefixCls }: ConfigConsumerProps) => {
|
||||
render = () => {
|
||||
const { getPrefixCls } = this.context;
|
||||
const { affixStyle, placeholderStyle } = this.state;
|
||||
const { prefixCls, children } = this.props;
|
||||
const className = classNames({
|
||||
@@ -283,10 +296,6 @@ class Affix extends React.Component<AffixProps, AffixState> {
|
||||
</ResizeObserver>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
return <ConfigConsumer>{this.renderAffix}</ConfigConsumer>;
|
||||
}
|
||||
}
|
||||
|
||||
export default Affix;
|
||||
|
||||
@@ -163,7 +163,7 @@ exports[`renders ./components/alert/demo/basic.md correctly 1`] = `
|
||||
`;
|
||||
|
||||
exports[`renders ./components/alert/demo/closable.md correctly 1`] = `
|
||||
<div>
|
||||
Array [
|
||||
<div
|
||||
class="ant-alert ant-alert-warning ant-alert-no-icon ant-alert-closable"
|
||||
data-show="true"
|
||||
@@ -202,7 +202,7 @@ exports[`renders ./components/alert/demo/closable.md correctly 1`] = `
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>,
|
||||
<div
|
||||
class="ant-alert ant-alert-error ant-alert-with-description ant-alert-no-icon ant-alert-closable"
|
||||
data-show="true"
|
||||
@@ -243,8 +243,8 @@ exports[`renders ./components/alert/demo/closable.md correctly 1`] = `
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`renders ./components/alert/demo/close-text.md correctly 1`] = `
|
||||
@@ -275,7 +275,7 @@ exports[`renders ./components/alert/demo/close-text.md correctly 1`] = `
|
||||
`;
|
||||
|
||||
exports[`renders ./components/alert/demo/custom-icon.md correctly 1`] = `
|
||||
<div>
|
||||
Array [
|
||||
<div
|
||||
class="ant-alert ant-alert-success ant-alert-no-icon"
|
||||
data-show="true"
|
||||
@@ -288,7 +288,7 @@ exports[`renders ./components/alert/demo/custom-icon.md correctly 1`] = `
|
||||
<span
|
||||
class="ant-alert-description"
|
||||
/>
|
||||
</div>
|
||||
</div>,
|
||||
<div
|
||||
class="ant-alert ant-alert-success"
|
||||
data-show="true"
|
||||
@@ -321,7 +321,7 @@ exports[`renders ./components/alert/demo/custom-icon.md correctly 1`] = `
|
||||
<span
|
||||
class="ant-alert-description"
|
||||
/>
|
||||
</div>
|
||||
</div>,
|
||||
<div
|
||||
class="ant-alert ant-alert-info"
|
||||
data-show="true"
|
||||
@@ -354,7 +354,7 @@ exports[`renders ./components/alert/demo/custom-icon.md correctly 1`] = `
|
||||
<span
|
||||
class="ant-alert-description"
|
||||
/>
|
||||
</div>
|
||||
</div>,
|
||||
<div
|
||||
class="ant-alert ant-alert-warning"
|
||||
data-show="true"
|
||||
@@ -387,7 +387,7 @@ exports[`renders ./components/alert/demo/custom-icon.md correctly 1`] = `
|
||||
<span
|
||||
class="ant-alert-description"
|
||||
/>
|
||||
</div>
|
||||
</div>,
|
||||
<div
|
||||
class="ant-alert ant-alert-error"
|
||||
data-show="true"
|
||||
@@ -420,7 +420,7 @@ exports[`renders ./components/alert/demo/custom-icon.md correctly 1`] = `
|
||||
<span
|
||||
class="ant-alert-description"
|
||||
/>
|
||||
</div>
|
||||
</div>,
|
||||
<div
|
||||
class="ant-alert ant-alert-success ant-alert-with-description"
|
||||
data-show="true"
|
||||
@@ -455,7 +455,7 @@ exports[`renders ./components/alert/demo/custom-icon.md correctly 1`] = `
|
||||
>
|
||||
Detailed description and advices about successful copywriting.
|
||||
</span>
|
||||
</div>
|
||||
</div>,
|
||||
<div
|
||||
class="ant-alert ant-alert-info ant-alert-with-description"
|
||||
data-show="true"
|
||||
@@ -490,7 +490,7 @@ exports[`renders ./components/alert/demo/custom-icon.md correctly 1`] = `
|
||||
>
|
||||
Additional description and informations about copywriting.
|
||||
</span>
|
||||
</div>
|
||||
</div>,
|
||||
<div
|
||||
class="ant-alert ant-alert-warning ant-alert-with-description"
|
||||
data-show="true"
|
||||
@@ -525,7 +525,7 @@ exports[`renders ./components/alert/demo/custom-icon.md correctly 1`] = `
|
||||
>
|
||||
This is a warning notice about copywriting.
|
||||
</span>
|
||||
</div>
|
||||
</div>,
|
||||
<div
|
||||
class="ant-alert ant-alert-error ant-alert-with-description"
|
||||
data-show="true"
|
||||
@@ -560,12 +560,12 @@ exports[`renders ./components/alert/demo/custom-icon.md correctly 1`] = `
|
||||
>
|
||||
This is an error message about copywriting.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`renders ./components/alert/demo/description.md correctly 1`] = `
|
||||
<div>
|
||||
Array [
|
||||
<div
|
||||
class="ant-alert ant-alert-success ant-alert-with-description ant-alert-no-icon"
|
||||
data-show="true"
|
||||
@@ -580,7 +580,7 @@ exports[`renders ./components/alert/demo/description.md correctly 1`] = `
|
||||
>
|
||||
Success Description Success Description Success Description
|
||||
</span>
|
||||
</div>
|
||||
</div>,
|
||||
<div
|
||||
class="ant-alert ant-alert-info ant-alert-with-description ant-alert-no-icon"
|
||||
data-show="true"
|
||||
@@ -595,7 +595,7 @@ exports[`renders ./components/alert/demo/description.md correctly 1`] = `
|
||||
>
|
||||
Info Description Info Description Info Description Info Description
|
||||
</span>
|
||||
</div>
|
||||
</div>,
|
||||
<div
|
||||
class="ant-alert ant-alert-warning ant-alert-with-description ant-alert-no-icon"
|
||||
data-show="true"
|
||||
@@ -610,7 +610,7 @@ exports[`renders ./components/alert/demo/description.md correctly 1`] = `
|
||||
>
|
||||
Warning Description Warning Description Warning Description Warning Description
|
||||
</span>
|
||||
</div>
|
||||
</div>,
|
||||
<div
|
||||
class="ant-alert ant-alert-error ant-alert-with-description ant-alert-no-icon"
|
||||
data-show="true"
|
||||
@@ -625,8 +625,8 @@ exports[`renders ./components/alert/demo/description.md correctly 1`] = `
|
||||
>
|
||||
Error Description Error Description Error Description Error Description
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`renders ./components/alert/demo/error-boundary.md correctly 1`] = `
|
||||
@@ -641,7 +641,7 @@ exports[`renders ./components/alert/demo/error-boundary.md correctly 1`] = `
|
||||
`;
|
||||
|
||||
exports[`renders ./components/alert/demo/icon.md correctly 1`] = `
|
||||
<div>
|
||||
Array [
|
||||
<div
|
||||
class="ant-alert ant-alert-success"
|
||||
data-show="true"
|
||||
@@ -674,7 +674,7 @@ exports[`renders ./components/alert/demo/icon.md correctly 1`] = `
|
||||
<span
|
||||
class="ant-alert-description"
|
||||
/>
|
||||
</div>
|
||||
</div>,
|
||||
<div
|
||||
class="ant-alert ant-alert-info"
|
||||
data-show="true"
|
||||
@@ -707,9 +707,9 @@ exports[`renders ./components/alert/demo/icon.md correctly 1`] = `
|
||||
<span
|
||||
class="ant-alert-description"
|
||||
/>
|
||||
</div>
|
||||
</div>,
|
||||
<div
|
||||
class="ant-alert ant-alert-warning"
|
||||
class="ant-alert ant-alert-warning ant-alert-closable"
|
||||
data-show="true"
|
||||
>
|
||||
<span
|
||||
@@ -740,7 +740,33 @@ exports[`renders ./components/alert/demo/icon.md correctly 1`] = `
|
||||
<span
|
||||
class="ant-alert-description"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
class="ant-alert-close-icon"
|
||||
tabindex="0"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-label="close"
|
||||
class="anticon anticon-close"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class=""
|
||||
data-icon="close"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
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 00203 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>
|
||||
</span>
|
||||
</button>
|
||||
</div>,
|
||||
<div
|
||||
class="ant-alert ant-alert-error"
|
||||
data-show="true"
|
||||
@@ -773,7 +799,7 @@ exports[`renders ./components/alert/demo/icon.md correctly 1`] = `
|
||||
<span
|
||||
class="ant-alert-description"
|
||||
/>
|
||||
</div>
|
||||
</div>,
|
||||
<div
|
||||
class="ant-alert ant-alert-success ant-alert-with-description"
|
||||
data-show="true"
|
||||
@@ -811,7 +837,7 @@ exports[`renders ./components/alert/demo/icon.md correctly 1`] = `
|
||||
>
|
||||
Detailed description and advice about successful copywriting.
|
||||
</span>
|
||||
</div>
|
||||
</div>,
|
||||
<div
|
||||
class="ant-alert ant-alert-info ant-alert-with-description"
|
||||
data-show="true"
|
||||
@@ -849,9 +875,9 @@ exports[`renders ./components/alert/demo/icon.md correctly 1`] = `
|
||||
>
|
||||
Additional description and information about copywriting.
|
||||
</span>
|
||||
</div>
|
||||
</div>,
|
||||
<div
|
||||
class="ant-alert ant-alert-warning ant-alert-with-description"
|
||||
class="ant-alert ant-alert-warning ant-alert-with-description ant-alert-closable"
|
||||
data-show="true"
|
||||
>
|
||||
<span
|
||||
@@ -887,7 +913,33 @@ exports[`renders ./components/alert/demo/icon.md correctly 1`] = `
|
||||
>
|
||||
This is a warning notice about copywriting.
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
class="ant-alert-close-icon"
|
||||
tabindex="0"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-label="close"
|
||||
class="anticon anticon-close"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class=""
|
||||
data-icon="close"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
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 00203 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>
|
||||
</span>
|
||||
</button>
|
||||
</div>,
|
||||
<div
|
||||
class="ant-alert ant-alert-error ant-alert-with-description"
|
||||
data-show="true"
|
||||
@@ -925,8 +977,8 @@ exports[`renders ./components/alert/demo/icon.md correctly 1`] = `
|
||||
>
|
||||
This is an error message about copywriting.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`renders ./components/alert/demo/loop-banner.md correctly 1`] = `
|
||||
@@ -1028,7 +1080,7 @@ exports[`renders ./components/alert/demo/smooth-closed.md correctly 1`] = `
|
||||
`;
|
||||
|
||||
exports[`renders ./components/alert/demo/style.md correctly 1`] = `
|
||||
<div>
|
||||
Array [
|
||||
<div
|
||||
class="ant-alert ant-alert-success ant-alert-no-icon"
|
||||
data-show="true"
|
||||
@@ -1041,7 +1093,7 @@ exports[`renders ./components/alert/demo/style.md correctly 1`] = `
|
||||
<span
|
||||
class="ant-alert-description"
|
||||
/>
|
||||
</div>
|
||||
</div>,
|
||||
<div
|
||||
class="ant-alert ant-alert-info ant-alert-no-icon"
|
||||
data-show="true"
|
||||
@@ -1054,7 +1106,7 @@ exports[`renders ./components/alert/demo/style.md correctly 1`] = `
|
||||
<span
|
||||
class="ant-alert-description"
|
||||
/>
|
||||
</div>
|
||||
</div>,
|
||||
<div
|
||||
class="ant-alert ant-alert-warning ant-alert-no-icon"
|
||||
data-show="true"
|
||||
@@ -1067,7 +1119,7 @@ exports[`renders ./components/alert/demo/style.md correctly 1`] = `
|
||||
<span
|
||||
class="ant-alert-description"
|
||||
/>
|
||||
</div>
|
||||
</div>,
|
||||
<div
|
||||
class="ant-alert ant-alert-error ant-alert-no-icon"
|
||||
data-show="true"
|
||||
@@ -1080,6 +1132,6 @@ exports[`renders ./components/alert/demo/style.md correctly 1`] = `
|
||||
<span
|
||||
class="ant-alert-description"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
`;
|
||||
@@ -22,6 +22,27 @@ exports[`Alert ErrorBoundary 1`] = `
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Alert could accept none react element icon 1`] = `
|
||||
<div
|
||||
class="ant-alert ant-alert-success"
|
||||
data-show="true"
|
||||
>
|
||||
<span
|
||||
class="ant-alert-icon"
|
||||
>
|
||||
icon
|
||||
</span>
|
||||
<span
|
||||
class="ant-alert-message"
|
||||
>
|
||||
Success Tips
|
||||
</span>
|
||||
<span
|
||||
class="ant-alert-description"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Alert rtl render component should be rendered correctly in RTL direction 1`] = `
|
||||
<div
|
||||
class="ant-alert ant-alert-info ant-alert-no-icon ant-alert-rtl"
|
||||
@@ -39,20 +39,20 @@ describe('Alert', () => {
|
||||
|
||||
describe('data and aria props', () => {
|
||||
it('sets data attributes on input', () => {
|
||||
const wrapper = mount(<Alert data-test="test-id" data-id="12345" />);
|
||||
const wrapper = mount(<Alert data-test="test-id" data-id="12345" message={null} />);
|
||||
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 wrapper = mount(<Alert aria-describedby="some-label" message={null} />);
|
||||
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 wrapper = mount(<Alert role="status" message={null} />);
|
||||
const input = wrapper.find('.ant-alert').getDOMNode();
|
||||
expect(input.getAttribute('role')).toBe('status');
|
||||
});
|
||||
@@ -60,6 +60,8 @@ describe('Alert', () => {
|
||||
|
||||
const testIt = process.env.REACT === '15' ? it.skip : it;
|
||||
testIt('ErrorBoundary', () => {
|
||||
// TODO: Change to @ts-expect-error once typescript is at 3.9
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line react/jsx-no-undef
|
||||
const ThrowError = () => <NotExisted />;
|
||||
const wrapper = mount(
|
||||
@@ -83,19 +85,15 @@ describe('Alert', () => {
|
||||
);
|
||||
wrapper.find('.ant-alert').simulate('mouseenter');
|
||||
await sleep(0);
|
||||
expect(
|
||||
wrapper
|
||||
.find(Tooltip)
|
||||
.instance()
|
||||
.getPopupDomNode(),
|
||||
).toBeTruthy();
|
||||
expect(wrapper.find<Tooltip>(Tooltip).instance().getPopupDomNode()).toBeTruthy();
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
it('could be used with Popconfirm', async () => {
|
||||
const ref = React.createRef<any>();
|
||||
jest.useRealTimers();
|
||||
const wrapper = mount(
|
||||
<Popconfirm title="xxx">
|
||||
<Popconfirm ref={ref} title="xxx">
|
||||
<Alert
|
||||
message="Warning Text Warning Text Warning TextW arning Text Warning Text Warning TextWarning Text"
|
||||
type="warning"
|
||||
@@ -104,12 +102,12 @@ describe('Alert', () => {
|
||||
);
|
||||
wrapper.find('.ant-alert').simulate('click');
|
||||
await sleep(0);
|
||||
expect(
|
||||
wrapper
|
||||
.find(Popconfirm)
|
||||
.instance()
|
||||
.getPopupDomNode(),
|
||||
).toBeTruthy();
|
||||
expect(ref.current.getPopupDomNode()).toBeTruthy();
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
it('could accept none react element icon', () => {
|
||||
const wrapper = mount(<Alert message="Success Tips" type="success" showIcon icon="icon" />);
|
||||
expect(wrapper).toMatchRenderedSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -21,7 +21,7 @@ const onClose = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
};
|
||||
|
||||
ReactDOM.render(
|
||||
<div>
|
||||
<>
|
||||
<Alert
|
||||
message="Warning Text Warning Text Warning TextW arning Text Warning Text Warning TextWarning Text"
|
||||
type="warning"
|
||||
@@ -35,7 +35,7 @@ ReactDOM.render(
|
||||
closable
|
||||
onClose={onClose}
|
||||
/>
|
||||
</div>,
|
||||
</>,
|
||||
mountNode,
|
||||
);
|
||||
```
|
||||
|
||||
@@ -21,7 +21,7 @@ import { SmileOutlined } from '@ant-design/icons';
|
||||
const icon = <SmileOutlined />;
|
||||
|
||||
ReactDOM.render(
|
||||
<div>
|
||||
<>
|
||||
<Alert icon={icon} message="showIcon = false" type="success" />
|
||||
<Alert icon={icon} message="Success Tips" type="success" showIcon />
|
||||
<Alert icon={icon} message="Informational Notes" type="info" showIcon />
|
||||
@@ -55,7 +55,7 @@ ReactDOM.render(
|
||||
type="error"
|
||||
showIcon
|
||||
/>
|
||||
</div>,
|
||||
</>,
|
||||
mountNode,
|
||||
);
|
||||
```
|
||||
|
||||
@@ -17,7 +17,7 @@ Additional description for alert message.
|
||||
import { Alert } from 'antd';
|
||||
|
||||
ReactDOM.render(
|
||||
<div>
|
||||
<>
|
||||
<Alert
|
||||
message="Success Text"
|
||||
description="Success Description Success Description Success Description"
|
||||
@@ -38,7 +38,7 @@ ReactDOM.render(
|
||||
description="Error Description Error Description Error Description Error Description"
|
||||
type="error"
|
||||
/>
|
||||
</div>,
|
||||
</>,
|
||||
mountNode,
|
||||
);
|
||||
```
|
||||
|
||||
@@ -17,10 +17,10 @@ A relevant icon will make information clearer and more friendly.
|
||||
import { Alert } from 'antd';
|
||||
|
||||
ReactDOM.render(
|
||||
<div>
|
||||
<>
|
||||
<Alert message="Success Tips" type="success" showIcon />
|
||||
<Alert message="Informational Notes" type="info" showIcon />
|
||||
<Alert message="Warning" type="warning" showIcon />
|
||||
<Alert message="Warning" type="warning" showIcon closable />
|
||||
<Alert message="Error" type="error" showIcon />
|
||||
<Alert
|
||||
message="Success Tips"
|
||||
@@ -39,6 +39,7 @@ ReactDOM.render(
|
||||
description="This is a warning notice about copywriting."
|
||||
type="warning"
|
||||
showIcon
|
||||
closable
|
||||
/>
|
||||
<Alert
|
||||
message="Error"
|
||||
@@ -46,7 +47,7 @@ ReactDOM.render(
|
||||
type="error"
|
||||
showIcon
|
||||
/>
|
||||
</div>,
|
||||
</>,
|
||||
mountNode,
|
||||
);
|
||||
```
|
||||
|
||||
@@ -17,12 +17,18 @@ There are 4 types of Alert: `success`, `info`, `warning`, `error`.
|
||||
import { Alert } from 'antd';
|
||||
|
||||
ReactDOM.render(
|
||||
<div>
|
||||
<>
|
||||
<Alert message="Success Text" type="success" />
|
||||
<Alert message="Info Text" type="info" />
|
||||
<Alert message="Warning Text" type="warning" />
|
||||
<Alert message="Error Text" type="error" />
|
||||
</div>,
|
||||
</>,
|
||||
mountNode,
|
||||
);
|
||||
```
|
||||
|
||||
<style>
|
||||
[data-theme="compact"] .code-box-demo .ant-alert {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -16,8 +16,6 @@ import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
|
||||
import getDataOrAriaProps from '../_util/getDataOrAriaProps';
|
||||
import ErrorBoundary from './ErrorBoundary';
|
||||
|
||||
function noop() {}
|
||||
|
||||
export interface AlertProps {
|
||||
/**
|
||||
* Type of Alert styles, options:`success`, `info`, `warning`, `error`
|
||||
@@ -37,6 +35,8 @@ export interface AlertProps {
|
||||
afterClose?: () => void;
|
||||
/** Whether to show icon */
|
||||
showIcon?: boolean;
|
||||
/** https://www.w3.org/TR/2014/REC-html5-20141028/dom.html#aria-role-attribute */
|
||||
role?: string;
|
||||
style?: React.CSSProperties;
|
||||
prefixCls?: string;
|
||||
className?: string;
|
||||
@@ -85,7 +85,7 @@ export default class Alert extends React.Component<AlertProps, AlertState> {
|
||||
this.setState({
|
||||
closing: true,
|
||||
});
|
||||
(this.props.onClose || noop)(e);
|
||||
this.props.onClose?.(e);
|
||||
};
|
||||
|
||||
animationEnd = () => {
|
||||
@@ -93,56 +93,56 @@ export default class Alert extends React.Component<AlertProps, AlertState> {
|
||||
closing: false,
|
||||
closed: true,
|
||||
});
|
||||
(this.props.afterClose || noop)();
|
||||
this.props.afterClose?.();
|
||||
};
|
||||
|
||||
renderAlert = ({ getPrefixCls, direction }: ConfigConsumerProps) => {
|
||||
const {
|
||||
description,
|
||||
prefixCls: customizePrefixCls,
|
||||
message,
|
||||
closeText,
|
||||
banner,
|
||||
className = '',
|
||||
style,
|
||||
icon,
|
||||
onMouseEnter,
|
||||
onMouseLeave,
|
||||
onClick,
|
||||
} = this.props;
|
||||
let { closable, type, showIcon } = this.props;
|
||||
const { closing, closed } = this.state;
|
||||
getShowIcon() {
|
||||
const { banner, showIcon } = this.props;
|
||||
// banner 模式默认有 Icon
|
||||
return banner && showIcon === undefined ? true : showIcon;
|
||||
}
|
||||
|
||||
const prefixCls = getPrefixCls('alert', customizePrefixCls);
|
||||
|
||||
// banner模式默认有 Icon
|
||||
showIcon = banner && showIcon === undefined ? true : showIcon;
|
||||
// banner模式默认为警告
|
||||
type = banner && type === undefined ? 'warning' : type || 'info';
|
||||
|
||||
// use outline icon in alert with description
|
||||
const iconType = (description ? iconMapOutlined : iconMapFilled)[type] || null;
|
||||
|
||||
// closeable when closeText is assigned
|
||||
if (closeText) {
|
||||
closable = true;
|
||||
getType() {
|
||||
const { banner, type } = this.props;
|
||||
if (type !== undefined) {
|
||||
return type;
|
||||
}
|
||||
// banner 模式默认为警告
|
||||
return banner ? 'warning' : 'info';
|
||||
}
|
||||
|
||||
const alertCls = classNames(
|
||||
prefixCls,
|
||||
`${prefixCls}-${type}`,
|
||||
{
|
||||
[`${prefixCls}-closing`]: closing,
|
||||
[`${prefixCls}-with-description`]: !!description,
|
||||
[`${prefixCls}-no-icon`]: !showIcon,
|
||||
[`${prefixCls}-banner`]: !!banner,
|
||||
[`${prefixCls}-closable`]: closable,
|
||||
[`${prefixCls}-rtl`]: direction === 'rtl',
|
||||
},
|
||||
className,
|
||||
);
|
||||
getClosable() {
|
||||
const { closable, closeText } = this.props;
|
||||
// closeable when closeText is assigned
|
||||
return closeText ? true : closable;
|
||||
}
|
||||
|
||||
const closeIcon = closable ? (
|
||||
getIconType() {
|
||||
const { description } = this.props;
|
||||
// use outline icon in alert with description
|
||||
return (description ? iconMapOutlined : iconMapFilled)[this.getType()] || null;
|
||||
}
|
||||
|
||||
renderIconNode({ prefixCls }: { prefixCls: string }) {
|
||||
const { icon } = this.props;
|
||||
const iconType = this.getIconType();
|
||||
if (icon) {
|
||||
return React.isValidElement<{ className?: string }>(icon) ? (
|
||||
React.cloneElement(icon, {
|
||||
className: classNames(`${prefixCls}-icon`, {
|
||||
[icon.props.className as string]: icon.props.className,
|
||||
}),
|
||||
})
|
||||
) : (
|
||||
<span className={`${prefixCls}-icon`}>{icon}</span>
|
||||
);
|
||||
}
|
||||
return React.createElement(iconType, { className: `${prefixCls}-icon` });
|
||||
}
|
||||
|
||||
renderCloseIcon({ prefixCls }: { prefixCls: string }) {
|
||||
const { closeText } = this.props;
|
||||
return this.getClosable() ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={this.handleClose}
|
||||
@@ -156,21 +156,47 @@ export default class Alert extends React.Component<AlertProps, AlertState> {
|
||||
)}
|
||||
</button>
|
||||
) : null;
|
||||
}
|
||||
|
||||
renderAlert = ({ getPrefixCls, direction }: ConfigConsumerProps) => {
|
||||
const {
|
||||
description,
|
||||
prefixCls: customizePrefixCls,
|
||||
message,
|
||||
banner,
|
||||
className = '',
|
||||
style,
|
||||
onMouseEnter,
|
||||
onMouseLeave,
|
||||
onClick,
|
||||
} = this.props;
|
||||
const { closing, closed } = this.state;
|
||||
|
||||
const prefixCls = getPrefixCls('alert', customizePrefixCls);
|
||||
|
||||
const isShowIcon = this.getShowIcon();
|
||||
const type = this.getType();
|
||||
const closable = this.getClosable();
|
||||
|
||||
const alertCls = classNames(
|
||||
prefixCls,
|
||||
`${prefixCls}-${type}`,
|
||||
{
|
||||
[`${prefixCls}-closing`]: closing,
|
||||
[`${prefixCls}-with-description`]: !!description,
|
||||
[`${prefixCls}-no-icon`]: !isShowIcon,
|
||||
[`${prefixCls}-banner`]: !!banner,
|
||||
[`${prefixCls}-closable`]: closable,
|
||||
[`${prefixCls}-rtl`]: direction === 'rtl',
|
||||
},
|
||||
className,
|
||||
);
|
||||
|
||||
const closeIcon = this.renderCloseIcon({ prefixCls });
|
||||
|
||||
const dataOrAriaProps = getDataOrAriaProps(this.props);
|
||||
|
||||
const iconNode =
|
||||
(icon &&
|
||||
(React.isValidElement<{ className?: string }>(icon) ? (
|
||||
React.cloneElement(icon, {
|
||||
className: classNames(`${prefixCls}-icon`, {
|
||||
[icon.props.className as string]: icon.props.className,
|
||||
}),
|
||||
})
|
||||
) : (
|
||||
<span className={`${prefixCls}-icon`}>{icon}</span>
|
||||
))) ||
|
||||
React.createElement(iconType, { className: `${prefixCls}-icon` });
|
||||
const iconNode = this.renderIconNode({ prefixCls });
|
||||
|
||||
return closed ? null : (
|
||||
<Animate
|
||||
@@ -188,7 +214,7 @@ export default class Alert extends React.Component<AlertProps, AlertState> {
|
||||
onClick={onClick}
|
||||
{...dataOrAriaProps}
|
||||
>
|
||||
{showIcon ? iconNode : null}
|
||||
{isShowIcon ? iconNode : null}
|
||||
<span className={`${prefixCls}-message`}>{message}</span>
|
||||
<span className={`${prefixCls}-description`}>{description}</span>
|
||||
{closeIcon}
|
||||
|
||||
@@ -13,6 +13,10 @@
|
||||
|
||||
&&-no-icon {
|
||||
padding: @alert-no-icon-padding-vertical 15px;
|
||||
.@{alert-prefix-cls}-close-icon {
|
||||
top: @alert-no-icon-padding-vertical + @font-size-base * @line-height-base / 2 -
|
||||
@font-size-base / 2;
|
||||
}
|
||||
}
|
||||
|
||||
&&-closable {
|
||||
@@ -21,14 +25,14 @@
|
||||
|
||||
&-icon {
|
||||
position: absolute;
|
||||
top: 8px + @font-size-base * @line-height-base / 2 - @font-size-base / 2;
|
||||
top: @alert-icon-top;
|
||||
left: 16px;
|
||||
}
|
||||
|
||||
&-description {
|
||||
display: none;
|
||||
font-size: @font-size-base;
|
||||
line-height: 22px;
|
||||
line-height: @font-size-base + 8px;
|
||||
}
|
||||
|
||||
&-success {
|
||||
@@ -71,12 +75,12 @@
|
||||
|
||||
&-close-icon {
|
||||
position: absolute;
|
||||
top: @padding-xs;
|
||||
top: 8px + @font-size-base * @line-height-base / 2 - @font-size-base / 2;
|
||||
right: 16px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
font-size: @font-size-sm;
|
||||
line-height: 22px;
|
||||
line-height: @font-size-sm;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
@@ -101,7 +105,7 @@
|
||||
|
||||
&-with-description {
|
||||
position: relative;
|
||||
padding: 15px 15px 15px 64px;
|
||||
padding: @alert-with-description-padding;
|
||||
color: @alert-text-color;
|
||||
line-height: @line-height-base;
|
||||
border-radius: @border-radius-base;
|
||||
@@ -113,15 +117,15 @@
|
||||
|
||||
&-with-description &-icon {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
left: 24px;
|
||||
font-size: 24px;
|
||||
top: @alert-with-description-icon-top;
|
||||
left: @alert-with-description-icon-size;
|
||||
font-size: @alert-with-description-icon-size;
|
||||
}
|
||||
|
||||
&-with-description &-close-icon {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
top: @padding-xs;
|
||||
right: @padding-xs;
|
||||
font-size: @font-size-base;
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -188,4 +192,4 @@
|
||||
}
|
||||
}
|
||||
|
||||
@import './rtl.less';
|
||||
@import './rtl';
|
||||
|
||||
@@ -4,13 +4,26 @@
|
||||
@alert-prefix-cls: ~'@{ant-prefix}-alert';
|
||||
|
||||
.@{alert-prefix-cls} {
|
||||
&-rtl {
|
||||
&&-rtl {
|
||||
padding: 8px 37px 8px 15px;
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
&&-closable {
|
||||
&&-no-icon {
|
||||
.@{alert-prefix-cls}-rtl& {
|
||||
padding: @alert-no-icon-padding-vertical 15px;
|
||||
}
|
||||
}
|
||||
|
||||
&&-closable {
|
||||
.@{alert-prefix-cls}.@{alert-prefix-cls}-rtl& {
|
||||
padding-right: 37px;
|
||||
padding-left: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
&&-no-icon&-closable {
|
||||
.@{alert-prefix-cls}.@{alert-prefix-cls}-rtl& {
|
||||
padding-right: 15px;
|
||||
padding-left: 30px;
|
||||
}
|
||||
@@ -30,14 +43,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
&-with-description {
|
||||
.@{alert-prefix-cls}-rtl& {
|
||||
&-with-description,
|
||||
&-with-description&-closable {
|
||||
.@{alert-prefix-cls}.@{alert-prefix-cls}-rtl& {
|
||||
padding: 15px 64px 15px 15px;
|
||||
}
|
||||
}
|
||||
|
||||
&-with-description&-no-icon {
|
||||
.@{alert-prefix-cls}-rtl& {
|
||||
.@{alert-prefix-cls}.@{alert-prefix-cls}-rtl& {
|
||||
padding: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,19 @@
|
||||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import * as 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 { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
|
||||
import { ConfigContext, ConfigConsumerProps } from '../config-provider';
|
||||
import scrollTo from '../_util/scrollTo';
|
||||
import getScroll from '../_util/getScroll';
|
||||
import AnchorContext from './context';
|
||||
|
||||
function getDefaultContainer() {
|
||||
return window;
|
||||
}
|
||||
|
||||
function getOffsetTop(element: HTMLElement, container: AnchorContainer): number {
|
||||
if (!element) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!element.getClientRects().length) {
|
||||
return 0;
|
||||
}
|
||||
@@ -35,7 +31,7 @@ function getOffsetTop(element: HTMLElement, container: AnchorContainer): number
|
||||
return rect.top;
|
||||
}
|
||||
|
||||
const sharpMatcherRegx = /#([^#]+)$/;
|
||||
const sharpMatcherRegx = /#(\S+)$/;
|
||||
|
||||
type Section = {
|
||||
link: string;
|
||||
@@ -88,23 +84,22 @@ export interface AntAnchor {
|
||||
) => void;
|
||||
}
|
||||
|
||||
export default class Anchor extends React.Component<AnchorProps, AnchorState> {
|
||||
export default class Anchor extends React.Component<AnchorProps, AnchorState, ConfigConsumerProps> {
|
||||
static Link: typeof AnchorLink;
|
||||
|
||||
static defaultProps = {
|
||||
affix: true,
|
||||
showInkInFixed: false,
|
||||
getContainer: getDefaultContainer,
|
||||
};
|
||||
|
||||
static childContextTypes = {
|
||||
antAnchor: PropTypes.object,
|
||||
};
|
||||
static contextType = ConfigContext;
|
||||
|
||||
state = {
|
||||
activeLink: null,
|
||||
};
|
||||
|
||||
content: ConfigConsumerProps;
|
||||
|
||||
private inkNode: HTMLSpanElement;
|
||||
|
||||
// scroll scope's container
|
||||
@@ -118,37 +113,38 @@ export default class Anchor extends React.Component<AnchorProps, AnchorState> {
|
||||
|
||||
private prefixCls?: string;
|
||||
|
||||
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,
|
||||
};
|
||||
return { antAnchor };
|
||||
}
|
||||
// Context
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
getContainer = () => {
|
||||
const { getTargetContainer } = this.context;
|
||||
const { getContainer } = this.props;
|
||||
|
||||
const getFunc = getContainer || getTargetContainer || getDefaultContainer;
|
||||
|
||||
return getFunc();
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
const { getContainer } = this.props as AnchorDefaultProps;
|
||||
this.scrollContainer = getContainer();
|
||||
this.scrollContainer = this.getContainer();
|
||||
this.scrollEvent = addEventListener(this.scrollContainer, 'scroll', this.handleScroll);
|
||||
this.handleScroll();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
if (this.scrollEvent) {
|
||||
const { getContainer } = this.props as AnchorDefaultProps;
|
||||
const currentContainer = getContainer();
|
||||
const currentContainer = this.getContainer();
|
||||
if (this.scrollContainer !== currentContainer) {
|
||||
this.scrollContainer = currentContainer;
|
||||
this.scrollEvent.remove();
|
||||
@@ -172,14 +168,8 @@ export default class Anchor extends React.Component<AnchorProps, AnchorState> {
|
||||
return getCurrentAnchor();
|
||||
}
|
||||
|
||||
const activeLink = '';
|
||||
if (typeof document === 'undefined') {
|
||||
return activeLink;
|
||||
}
|
||||
|
||||
const linkSections: Array<Section> = [];
|
||||
const { getContainer } = this.props as AnchorDefaultProps;
|
||||
const container = getContainer();
|
||||
const container = this.getContainer();
|
||||
this.links.forEach(link => {
|
||||
const sharpLinkMatch = sharpMatcherRegx.exec(link.toString());
|
||||
if (!sharpLinkMatch) {
|
||||
@@ -205,10 +195,10 @@ export default class Anchor extends React.Component<AnchorProps, AnchorState> {
|
||||
}
|
||||
|
||||
handleScrollTo = (link: string) => {
|
||||
const { offsetTop, getContainer, targetOffset } = this.props as AnchorDefaultProps;
|
||||
const { offsetTop, targetOffset } = this.props;
|
||||
|
||||
this.setCurrentActiveLink(link);
|
||||
const container = getContainer();
|
||||
const container = this.getContainer();
|
||||
const scrollTop = getScroll(container, true);
|
||||
const sharpLinkMatch = sharpMatcherRegx.exec(link);
|
||||
if (!sharpLinkMatch) {
|
||||
@@ -228,7 +218,7 @@ export default class Anchor extends React.Component<AnchorProps, AnchorState> {
|
||||
callback: () => {
|
||||
this.animating = false;
|
||||
},
|
||||
getContainer,
|
||||
getContainer: this.getContainer,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -263,9 +253,6 @@ export default class Anchor extends React.Component<AnchorProps, AnchorState> {
|
||||
};
|
||||
|
||||
updateInk = () => {
|
||||
if (typeof document === 'undefined') {
|
||||
return;
|
||||
}
|
||||
const { prefixCls } = this;
|
||||
const anchorNode = ReactDOM.findDOMNode(this) as Element;
|
||||
const linkNode = anchorNode.getElementsByClassName(`${prefixCls}-link-title-active`)[0];
|
||||
@@ -274,7 +261,9 @@ export default class Anchor extends React.Component<AnchorProps, AnchorState> {
|
||||
}
|
||||
};
|
||||
|
||||
renderAnchor = ({ getPrefixCls, direction }: ConfigConsumerProps) => {
|
||||
render = () => {
|
||||
const { getPrefixCls, direction } = this.context;
|
||||
|
||||
const {
|
||||
prefixCls: customizePrefixCls,
|
||||
className = '',
|
||||
@@ -283,7 +272,6 @@ export default class Anchor extends React.Component<AnchorProps, AnchorState> {
|
||||
affix,
|
||||
showInkInFixed,
|
||||
children,
|
||||
getContainer,
|
||||
} = this.props;
|
||||
const { activeLink } = this.state;
|
||||
|
||||
@@ -322,16 +310,24 @@ export default class Anchor extends React.Component<AnchorProps, AnchorState> {
|
||||
</div>
|
||||
);
|
||||
|
||||
return !affix ? (
|
||||
anchorContent
|
||||
) : (
|
||||
<Affix offsetTop={offsetTop} target={getContainer}>
|
||||
{anchorContent}
|
||||
</Affix>
|
||||
return (
|
||||
<AnchorContext.Provider
|
||||
value={{
|
||||
registerLink: this.registerLink,
|
||||
unregisterLink: this.unregisterLink,
|
||||
activeLink: this.state.activeLink,
|
||||
scrollTo: this.handleScrollTo,
|
||||
onClick: this.props.onClick,
|
||||
}}
|
||||
>
|
||||
{!affix ? (
|
||||
anchorContent
|
||||
) : (
|
||||
<Affix offsetTop={offsetTop} target={this.getContainer}>
|
||||
{anchorContent}
|
||||
</Affix>
|
||||
)}
|
||||
</AnchorContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
return <ConfigConsumer>{this.renderAnchor}</ConfigConsumer>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import * as React from 'react';
|
||||
import * as PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { AntAnchor } from './Anchor';
|
||||
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
|
||||
import AnchorContext from './context';
|
||||
|
||||
export interface AnchorLinkProps {
|
||||
prefixCls?: string;
|
||||
@@ -13,37 +13,33 @@ export interface AnchorLinkProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
class AnchorLink extends React.Component<AnchorLinkProps, any> {
|
||||
class AnchorLink extends React.Component<AnchorLinkProps, any, AntAnchor> {
|
||||
static defaultProps = {
|
||||
href: '#',
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
antAnchor: PropTypes.object,
|
||||
};
|
||||
static contextType = AnchorContext;
|
||||
|
||||
context: {
|
||||
antAnchor: AntAnchor;
|
||||
};
|
||||
context: AntAnchor;
|
||||
|
||||
componentDidMount() {
|
||||
this.context.antAnchor.registerLink(this.props.href);
|
||||
this.context.registerLink(this.props.href);
|
||||
}
|
||||
|
||||
componentDidUpdate({ href: prevHref }: AnchorLinkProps) {
|
||||
const { href } = this.props;
|
||||
if (prevHref !== href) {
|
||||
this.context.antAnchor.unregisterLink(prevHref);
|
||||
this.context.antAnchor.registerLink(href);
|
||||
this.context.unregisterLink(prevHref);
|
||||
this.context.registerLink(href);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.context.antAnchor.unregisterLink(this.props.href);
|
||||
this.context.unregisterLink(this.props.href);
|
||||
}
|
||||
|
||||
handleClick = (e: React.MouseEvent<HTMLElement>) => {
|
||||
const { scrollTo, onClick } = this.context.antAnchor;
|
||||
const { scrollTo, onClick } = this.context;
|
||||
const { href, title } = this.props;
|
||||
if (onClick) {
|
||||
onClick(e, { title, href });
|
||||
@@ -54,7 +50,7 @@ class AnchorLink extends React.Component<AnchorLinkProps, any> {
|
||||
renderAnchorLink = ({ getPrefixCls }: ConfigConsumerProps) => {
|
||||
const { prefixCls: customizePrefixCls, href, title, children, className, target } = this.props;
|
||||
const prefixCls = getPrefixCls('anchor', customizePrefixCls);
|
||||
const active = this.context.antAnchor.activeLink === href;
|
||||
const active = this.context.activeLink === href;
|
||||
const wrapperClassName = classNames(className, `${prefixCls}-link`, {
|
||||
[`${prefixCls}-link-active`]: active,
|
||||
});
|
||||
|
||||
@@ -1,336 +0,0 @@
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import Anchor from '..';
|
||||
import { spyElementPrototypes } from '../../__tests__/util/domHook';
|
||||
import { sleep } from '../../../tests/utils';
|
||||
|
||||
const { Link } = Anchor;
|
||||
|
||||
describe('Anchor Render', () => {
|
||||
const getBoundingClientRectMock = jest.fn(() => ({
|
||||
width: 100,
|
||||
height: 100,
|
||||
top: 1000,
|
||||
}));
|
||||
const getClientRectsMock = jest.fn(() => ({
|
||||
length: 1,
|
||||
}));
|
||||
const headingSpy = spyElementPrototypes(HTMLHeadingElement, {
|
||||
getBoundingClientRect: getBoundingClientRectMock,
|
||||
getClientRects: getClientRectsMock,
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
headingSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('Anchor render perfectly', () => {
|
||||
const wrapper = mount(
|
||||
<Anchor>
|
||||
<Link href="#API" title="API" />
|
||||
</Anchor>,
|
||||
);
|
||||
|
||||
wrapper.find('a[href="#API"]').simulate('click');
|
||||
|
||||
wrapper.instance().handleScroll();
|
||||
expect(wrapper.instance().state).not.toBe(null);
|
||||
});
|
||||
|
||||
it('Anchor render perfectly for complete href - click', () => {
|
||||
const wrapper = mount(
|
||||
<Anchor>
|
||||
<Link href="http://www.example.com/#API" title="API" />
|
||||
</Anchor>,
|
||||
);
|
||||
wrapper.find('a[href="http://www.example.com/#API"]').simulate('click');
|
||||
expect(wrapper.instance().state.activeLink).toBe('http://www.example.com/#API');
|
||||
});
|
||||
|
||||
it('Anchor render perfectly for complete href - scroll', () => {
|
||||
let root = document.getElementById('root');
|
||||
if (!root) {
|
||||
root = document.createElement('div', { id: 'root' });
|
||||
root.id = 'root';
|
||||
document.body.appendChild(root);
|
||||
}
|
||||
mount(<div id="API">Hello</div>, { attachTo: root });
|
||||
const wrapper = mount(
|
||||
<Anchor>
|
||||
<Link href="http://www.example.com/#API" title="API" />
|
||||
</Anchor>,
|
||||
);
|
||||
wrapper.instance().handleScroll();
|
||||
expect(wrapper.instance().state.activeLink).toBe('http://www.example.com/#API');
|
||||
});
|
||||
|
||||
it('Anchor render perfectly for complete href - scrollTo', async () => {
|
||||
const scrollToSpy = jest.spyOn(window, 'scrollTo');
|
||||
let root = document.getElementById('root');
|
||||
if (!root) {
|
||||
root = document.createElement('div', { id: 'root' });
|
||||
root.id = 'root';
|
||||
document.body.appendChild(root);
|
||||
}
|
||||
mount(<div id="API">Hello</div>, { attachTo: root });
|
||||
const wrapper = mount(
|
||||
<Anchor>
|
||||
<Link href="##API" title="API" />
|
||||
</Anchor>,
|
||||
);
|
||||
wrapper.instance().handleScrollTo('##API');
|
||||
expect(wrapper.instance().state.activeLink).toBe('##API');
|
||||
expect(scrollToSpy).not.toHaveBeenCalled();
|
||||
await sleep(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 });
|
||||
});
|
||||
|
||||
it('Different function returns the same DOM', async () => {
|
||||
let root = document.getElementById('root');
|
||||
if (!root) {
|
||||
root = document.createElement('div', { id: 'root' });
|
||||
root.id = 'root';
|
||||
document.body.appendChild(root);
|
||||
}
|
||||
mount(<div id="API">Hello</div>, { attachTo: root });
|
||||
const getContainerA = () => {
|
||||
return document.getElementById('API');
|
||||
};
|
||||
const getContainerB = () => {
|
||||
return document.getElementById('API');
|
||||
};
|
||||
|
||||
const wrapper = mount(
|
||||
<Anchor getContainer={getContainerA}>
|
||||
<Link href="#API" title="API" />
|
||||
</Anchor>,
|
||||
);
|
||||
const removeListenerSpy = jest.spyOn(wrapper.instance().scrollEvent, 'remove');
|
||||
await sleep(1000);
|
||||
wrapper.setProps({ getContainer: getContainerB });
|
||||
expect(removeListenerSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Different function returns different DOM', async () => {
|
||||
let root = document.getElementById('root');
|
||||
if (!root) {
|
||||
root = document.createElement('div', { id: 'root' });
|
||||
root.id = 'root';
|
||||
document.body.appendChild(root);
|
||||
}
|
||||
mount(
|
||||
<div>
|
||||
<div id="API1">Hello</div>
|
||||
<div id="API2">World</div>
|
||||
</div>,
|
||||
{ attachTo: root },
|
||||
);
|
||||
const getContainerA = () => {
|
||||
return document.getElementById('API1');
|
||||
};
|
||||
const getContainerB = () => {
|
||||
return document.getElementById('API2');
|
||||
};
|
||||
const wrapper = mount(
|
||||
<Anchor getContainer={getContainerA}>
|
||||
<Link href="#API1" title="API1" />
|
||||
<Link href="#API2" title="API2" />
|
||||
</Anchor>,
|
||||
);
|
||||
const removeListenerSpy = jest.spyOn(wrapper.instance().scrollEvent, 'remove');
|
||||
expect(removeListenerSpy).not.toHaveBeenCalled();
|
||||
await sleep(1000);
|
||||
wrapper.setProps({ getContainer: getContainerB });
|
||||
expect(removeListenerSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Same function returns the same DOM', () => {
|
||||
let root = document.getElementById('root');
|
||||
if (!root) {
|
||||
root = document.createElement('div', { id: 'root' });
|
||||
root.id = 'root';
|
||||
document.body.appendChild(root);
|
||||
}
|
||||
mount(<div id="API">Hello</div>, { attachTo: root });
|
||||
const getContainer = () => document.getElementById('API');
|
||||
const wrapper = mount(
|
||||
<Anchor getContainer={getContainer}>
|
||||
<Link href="#API" title="API" />
|
||||
</Anchor>,
|
||||
);
|
||||
wrapper.find('a[href="#API"]').simulate('click');
|
||||
wrapper.instance().handleScroll();
|
||||
expect(wrapper.instance().state).not.toBe(null);
|
||||
});
|
||||
|
||||
it('Same function returns different DOM', async () => {
|
||||
let root = document.getElementById('root');
|
||||
if (!root) {
|
||||
root = document.createElement('div', { id: 'root' });
|
||||
root.id = 'root';
|
||||
document.body.appendChild(root);
|
||||
}
|
||||
mount(
|
||||
<div>
|
||||
<div id="API1">Hello</div>
|
||||
<div id="API2">World</div>
|
||||
</div>,
|
||||
{ attachTo: root },
|
||||
);
|
||||
const holdContainer = {
|
||||
container: document.getElementById('API1'),
|
||||
};
|
||||
const getContainer = () => {
|
||||
return holdContainer.container;
|
||||
};
|
||||
const wrapper = mount(
|
||||
<Anchor getContainer={getContainer}>
|
||||
<Link href="#API1" title="API1" />
|
||||
<Link href="#API2" title="API2" />
|
||||
</Anchor>,
|
||||
);
|
||||
const removeListenerSpy = jest.spyOn(wrapper.instance().scrollEvent, 'remove');
|
||||
expect(removeListenerSpy).not.toHaveBeenCalled();
|
||||
await sleep(1000);
|
||||
holdContainer.container = document.getElementById('API2');
|
||||
wrapper.setProps({ 'data-only-trigger-re-render': true });
|
||||
expect(removeListenerSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Anchor getCurrentAnchor prop', () => {
|
||||
const getCurrentAnchor = () => '#API2';
|
||||
const wrapper = mount(
|
||||
<Anchor getCurrentAnchor={getCurrentAnchor}>
|
||||
<Link href="#API1" title="API1" />
|
||||
<Link href="#API2" title="API2" />
|
||||
</Anchor>,
|
||||
);
|
||||
expect(wrapper.instance().state.activeLink).toBe('#API2');
|
||||
});
|
||||
|
||||
it('Anchor targetOffset prop', async () => {
|
||||
let dateNowMock;
|
||||
|
||||
function dataNowMockFn() {
|
||||
let start = 0;
|
||||
|
||||
const handler = () => {
|
||||
return (start += 1000);
|
||||
};
|
||||
|
||||
return jest.spyOn(Date, 'now').mockImplementation(handler);
|
||||
}
|
||||
|
||||
dateNowMock = dataNowMockFn();
|
||||
|
||||
const scrollToSpy = jest.spyOn(window, 'scrollTo');
|
||||
let root = document.getElementById('root');
|
||||
if (!root) {
|
||||
root = document.createElement('div', { id: 'root' });
|
||||
root.id = 'root';
|
||||
document.body.appendChild(root);
|
||||
}
|
||||
mount(<h1 id="API">Hello</h1>, { attachTo: root });
|
||||
const wrapper = mount(
|
||||
<Anchor>
|
||||
<Link href="#API" title="API" />
|
||||
</Anchor>,
|
||||
);
|
||||
wrapper.instance().handleScrollTo('#API');
|
||||
await sleep(20);
|
||||
expect(scrollToSpy).toHaveBeenLastCalledWith(0, 1000);
|
||||
dateNowMock = dataNowMockFn();
|
||||
|
||||
wrapper.setProps({ offsetTop: 100 });
|
||||
wrapper.instance().handleScrollTo('#API');
|
||||
await sleep(20);
|
||||
expect(scrollToSpy).toHaveBeenLastCalledWith(0, 900);
|
||||
dateNowMock = dataNowMockFn();
|
||||
|
||||
wrapper.setProps({ targetOffset: 200 });
|
||||
wrapper.instance().handleScrollTo('#API');
|
||||
await sleep(20);
|
||||
expect(scrollToSpy).toHaveBeenLastCalledWith(0, 800);
|
||||
|
||||
dateNowMock.mockRestore();
|
||||
});
|
||||
|
||||
it('Anchor onChange prop', async () => {
|
||||
const onChange = jest.fn();
|
||||
const wrapper = mount(
|
||||
<Anchor onChange={onChange}>
|
||||
<Link href="#API1" title="API1" />
|
||||
<Link href="#API2" title="API2" />
|
||||
</Anchor>,
|
||||
);
|
||||
expect(onChange).toHaveBeenCalledTimes(1);
|
||||
wrapper.instance().handleScrollTo('#API2');
|
||||
expect(onChange).toHaveBeenCalledTimes(2);
|
||||
expect(onChange).toHaveBeenCalledWith('#API2');
|
||||
});
|
||||
});
|
||||
469
components/anchor/__tests__/Anchor.test.tsx
Normal file
469
components/anchor/__tests__/Anchor.test.tsx
Normal file
@@ -0,0 +1,469 @@
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import Anchor from '..';
|
||||
import { sleep } from '../../../tests/utils';
|
||||
|
||||
const { Link } = Anchor;
|
||||
|
||||
function createGetContainer(id: string) {
|
||||
return () => {
|
||||
const container = document.getElementById(id);
|
||||
if (container == null) {
|
||||
throw new Error();
|
||||
}
|
||||
return container;
|
||||
};
|
||||
}
|
||||
|
||||
function createDiv() {
|
||||
const root = document.createElement('div');
|
||||
document.body.appendChild(root);
|
||||
return root;
|
||||
}
|
||||
|
||||
let idCounter = 0;
|
||||
const getHashUrl = () => `Anchor-API-${idCounter++}`;
|
||||
|
||||
describe('Anchor Render', () => {
|
||||
const getBoundingClientRectMock = jest.spyOn(
|
||||
HTMLHeadingElement.prototype,
|
||||
'getBoundingClientRect',
|
||||
);
|
||||
const getClientRectsMock = jest.spyOn(HTMLHeadingElement.prototype, 'getClientRects');
|
||||
|
||||
beforeAll(() => {
|
||||
getBoundingClientRectMock.mockReturnValue({
|
||||
width: 100,
|
||||
height: 100,
|
||||
top: 1000,
|
||||
} as DOMRect);
|
||||
getClientRectsMock.mockReturnValue({ length: 1 } as DOMRectList);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
getBoundingClientRectMock.mockRestore();
|
||||
getClientRectsMock.mockRestore();
|
||||
});
|
||||
|
||||
it('Anchor render perfectly', () => {
|
||||
const hash = getHashUrl();
|
||||
const wrapper = mount<Anchor>(
|
||||
<Anchor>
|
||||
<Link href={`#${hash}`} title={hash} />
|
||||
</Anchor>,
|
||||
);
|
||||
|
||||
wrapper.find(`a[href="#${hash}"]`).simulate('click');
|
||||
|
||||
wrapper.instance().handleScroll();
|
||||
expect(wrapper.instance().state).not.toBe(null);
|
||||
});
|
||||
|
||||
it('Anchor render perfectly for complete href - click', () => {
|
||||
const hash = getHashUrl();
|
||||
const wrapper = mount<Anchor>(
|
||||
<Anchor>
|
||||
<Link href={`http://www.example.com/#${hash}`} title={hash} />
|
||||
</Anchor>,
|
||||
);
|
||||
wrapper.find(`a[href="http://www.example.com/#${hash}"]`).simulate('click');
|
||||
expect(wrapper.instance().state.activeLink).toBe(`http://www.example.com/#${hash}`);
|
||||
});
|
||||
|
||||
it('Anchor render perfectly for complete href - hash router', async () => {
|
||||
const root = createDiv();
|
||||
const scrollToSpy = jest.spyOn(window, 'scrollTo');
|
||||
mount(<div id="/faq?locale=en#Q1">Q1</div>, { attachTo: root });
|
||||
const wrapper = mount<Anchor>(
|
||||
<Anchor>
|
||||
<Link href="/#/faq?locale=en#Q1" title="Q1" />
|
||||
</Anchor>,
|
||||
);
|
||||
|
||||
wrapper.instance().handleScrollTo('/#/faq?locale=en#Q1');
|
||||
expect(wrapper.instance().state.activeLink).toBe('/#/faq?locale=en#Q1');
|
||||
expect(scrollToSpy).not.toHaveBeenCalled();
|
||||
await sleep(1000);
|
||||
expect(scrollToSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Anchor render perfectly for complete href - scroll', () => {
|
||||
const hash = getHashUrl();
|
||||
const root = createDiv();
|
||||
mount(<div id={hash}>Hello</div>, { attachTo: root });
|
||||
const wrapper = mount<Anchor>(
|
||||
<Anchor>
|
||||
<Link href={`http://www.example.com/#${hash}`} title={hash} />
|
||||
</Anchor>,
|
||||
);
|
||||
wrapper.instance().handleScroll();
|
||||
expect(wrapper.instance().state.activeLink).toBe(`http://www.example.com/#${hash}`);
|
||||
});
|
||||
|
||||
it('Anchor render perfectly for complete href - scrollTo', async () => {
|
||||
const hash = getHashUrl();
|
||||
const scrollToSpy = jest.spyOn(window, 'scrollTo');
|
||||
const root = createDiv();
|
||||
mount(<div id={`#${hash}`}>Hello</div>, { attachTo: root });
|
||||
const wrapper = mount<Anchor>(
|
||||
<Anchor>
|
||||
<Link href={`##${hash}`} title={hash} />
|
||||
</Anchor>,
|
||||
);
|
||||
wrapper.instance().handleScrollTo(`##${hash}`);
|
||||
expect(wrapper.instance().state.activeLink).toBe(`##${hash}`);
|
||||
const calls = scrollToSpy.mock.calls.length;
|
||||
await sleep(1000);
|
||||
expect(scrollToSpy.mock.calls.length).toBeGreaterThan(calls);
|
||||
});
|
||||
|
||||
it('should remove listener when unmount', async () => {
|
||||
const hash = getHashUrl();
|
||||
const wrapper = mount<Anchor>(
|
||||
<Anchor>
|
||||
<Link href={`#${hash}`} title={hash} />
|
||||
</Anchor>,
|
||||
);
|
||||
const removeListenerSpy = jest.spyOn((wrapper.instance() as any).scrollEvent, 'remove');
|
||||
wrapper.unmount();
|
||||
expect(removeListenerSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should unregister link when unmount children', async () => {
|
||||
const hash = getHashUrl();
|
||||
const wrapper = mount<Anchor>(
|
||||
<Anchor>
|
||||
<Link href={`#${hash}`} title={hash} />
|
||||
</Anchor>,
|
||||
);
|
||||
expect((wrapper.instance() as any).links).toEqual([`#${hash}`]);
|
||||
wrapper.setProps({ children: null });
|
||||
expect((wrapper.instance() as any).links).toEqual([]);
|
||||
});
|
||||
|
||||
it('should update links when link href update', async () => {
|
||||
const hash = getHashUrl();
|
||||
let anchorInstance: Anchor | null = null;
|
||||
function AnchorUpdate({ href }: { href: string }) {
|
||||
return (
|
||||
<Anchor
|
||||
ref={c => {
|
||||
anchorInstance = c;
|
||||
}}
|
||||
>
|
||||
<Link href={href} title={hash} />
|
||||
</Anchor>
|
||||
);
|
||||
}
|
||||
const wrapper = mount(<AnchorUpdate href={`#${hash}`} />);
|
||||
|
||||
if (anchorInstance == null) {
|
||||
throw new Error('anchorInstance should not be null');
|
||||
}
|
||||
expect((anchorInstance as any).links).toEqual([`#${hash}`]);
|
||||
wrapper.setProps({ href: `#${hash}_1` });
|
||||
expect((anchorInstance as any).links).toEqual([`#${hash}_1`]);
|
||||
});
|
||||
|
||||
it('Anchor onClick event', () => {
|
||||
const hash = getHashUrl();
|
||||
let event;
|
||||
let link;
|
||||
const handleClick = (
|
||||
e: React.MouseEvent<HTMLElement>,
|
||||
_link: { title: React.ReactNode; href: string },
|
||||
) => {
|
||||
event = e;
|
||||
link = _link;
|
||||
};
|
||||
|
||||
const href = `#${hash}`;
|
||||
const title = hash;
|
||||
|
||||
const wrapper = mount<Anchor>(
|
||||
<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 });
|
||||
});
|
||||
|
||||
it('Different function returns the same DOM', async () => {
|
||||
const hash = getHashUrl();
|
||||
const root = createDiv();
|
||||
mount(<div id={hash}>Hello</div>, { attachTo: root });
|
||||
const getContainerA = createGetContainer(hash);
|
||||
const getContainerB = createGetContainer(hash);
|
||||
|
||||
const wrapper = mount<Anchor>(
|
||||
<Anchor getContainer={getContainerA}>
|
||||
<Link href={`#${hash}`} title={hash} />
|
||||
</Anchor>,
|
||||
);
|
||||
const removeListenerSpy = jest.spyOn((wrapper.instance() as any).scrollEvent, 'remove');
|
||||
await sleep(1000);
|
||||
wrapper.setProps({ getContainer: getContainerB });
|
||||
expect(removeListenerSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Different function returns different DOM', async () => {
|
||||
const hash1 = getHashUrl();
|
||||
const hash2 = getHashUrl();
|
||||
const root = createDiv();
|
||||
mount(
|
||||
<div>
|
||||
<div id={hash1}>Hello</div>
|
||||
<div id={hash2}>World</div>
|
||||
</div>,
|
||||
{ attachTo: root },
|
||||
);
|
||||
const getContainerA = createGetContainer(hash1);
|
||||
const getContainerB = createGetContainer(hash2);
|
||||
const wrapper = mount<Anchor>(
|
||||
<Anchor getContainer={getContainerA}>
|
||||
<Link href={`#${hash1}`} title={hash1} />
|
||||
<Link href={`#${hash2}`} title={hash2} />
|
||||
</Anchor>,
|
||||
);
|
||||
const removeListenerSpy = jest.spyOn((wrapper.instance() as any).scrollEvent, 'remove');
|
||||
expect(removeListenerSpy).not.toHaveBeenCalled();
|
||||
await sleep(1000);
|
||||
wrapper.setProps({ getContainer: getContainerB });
|
||||
expect(removeListenerSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Same function returns the same DOM', () => {
|
||||
const hash = getHashUrl();
|
||||
const root = createDiv();
|
||||
mount(<div id={hash}>Hello</div>, { attachTo: root });
|
||||
const getContainer = createGetContainer(hash);
|
||||
const wrapper = mount(
|
||||
<Anchor getContainer={getContainer}>
|
||||
<Link href={`#${hash}`} title={hash} />
|
||||
</Anchor>,
|
||||
);
|
||||
wrapper.find(`a[href="#${hash}"]`).simulate('click');
|
||||
(wrapper.instance() as any).handleScroll();
|
||||
expect(wrapper.instance().state).not.toBe(null);
|
||||
});
|
||||
|
||||
it('Same function returns different DOM', async () => {
|
||||
const hash1 = getHashUrl();
|
||||
const hash2 = getHashUrl();
|
||||
const root = createDiv();
|
||||
mount(
|
||||
<div>
|
||||
<div id={hash1}>Hello</div>
|
||||
<div id={hash2}>World</div>
|
||||
</div>,
|
||||
{ attachTo: root },
|
||||
);
|
||||
const holdContainer = {
|
||||
container: document.getElementById(hash1),
|
||||
};
|
||||
const getContainer = () => {
|
||||
if (holdContainer.container == null) {
|
||||
throw new Error('container should not be null');
|
||||
}
|
||||
return holdContainer.container;
|
||||
};
|
||||
const wrapper = mount(
|
||||
<Anchor getContainer={getContainer}>
|
||||
<Link href={`#${hash1}`} title={hash1} />
|
||||
<Link href={`#${hash2}`} title={hash2} />
|
||||
</Anchor>,
|
||||
);
|
||||
const removeListenerSpy = jest.spyOn((wrapper.instance() as any).scrollEvent, 'remove');
|
||||
expect(removeListenerSpy).not.toHaveBeenCalled();
|
||||
await sleep(1000);
|
||||
holdContainer.container = document.getElementById(hash2);
|
||||
wrapper.setProps({ 'data-only-trigger-re-render': true });
|
||||
expect(removeListenerSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Anchor getCurrentAnchor prop', () => {
|
||||
const hash1 = getHashUrl();
|
||||
const hash2 = getHashUrl();
|
||||
const getCurrentAnchor = () => `#${hash2}`;
|
||||
const wrapper = mount<Anchor>(
|
||||
<Anchor getCurrentAnchor={getCurrentAnchor}>
|
||||
<Link href={`#${hash1}`} title={hash1} />
|
||||
<Link href={`#${hash2}`} title={hash2} />
|
||||
</Anchor>,
|
||||
);
|
||||
expect(wrapper.instance().state.activeLink).toBe(`#${hash2}`);
|
||||
});
|
||||
|
||||
it('Anchor targetOffset prop', async () => {
|
||||
const hash = getHashUrl();
|
||||
let dateNowMock;
|
||||
|
||||
function dataNowMockFn() {
|
||||
let start = 0;
|
||||
|
||||
const handler = () => {
|
||||
return (start += 1000);
|
||||
};
|
||||
|
||||
return jest.spyOn(Date, 'now').mockImplementation(handler);
|
||||
}
|
||||
|
||||
dateNowMock = dataNowMockFn();
|
||||
|
||||
const scrollToSpy = jest.spyOn(window, 'scrollTo');
|
||||
const root = createDiv();
|
||||
mount(<h1 id={hash}>Hello</h1>, { attachTo: root });
|
||||
const wrapper = mount<Anchor>(
|
||||
<Anchor>
|
||||
<Link href={`#${hash}`} title={hash} />
|
||||
</Anchor>,
|
||||
);
|
||||
wrapper.instance().handleScrollTo(`#${hash}`);
|
||||
await sleep(30);
|
||||
expect(scrollToSpy).toHaveBeenLastCalledWith(0, 1000);
|
||||
dateNowMock = dataNowMockFn();
|
||||
|
||||
wrapper.setProps({ offsetTop: 100 });
|
||||
wrapper.instance().handleScrollTo(`#${hash}`);
|
||||
await sleep(30);
|
||||
expect(scrollToSpy).toHaveBeenLastCalledWith(0, 900);
|
||||
dateNowMock = dataNowMockFn();
|
||||
|
||||
wrapper.setProps({ targetOffset: 200 });
|
||||
wrapper.instance().handleScrollTo(`#${hash}`);
|
||||
await sleep(30);
|
||||
expect(scrollToSpy).toHaveBeenLastCalledWith(0, 800);
|
||||
|
||||
dateNowMock.mockRestore();
|
||||
});
|
||||
|
||||
it('Anchor onChange prop', async () => {
|
||||
const hash1 = getHashUrl();
|
||||
const hash2 = getHashUrl();
|
||||
const onChange = jest.fn();
|
||||
const wrapper = mount<Anchor>(
|
||||
<Anchor onChange={onChange}>
|
||||
<Link href={`#${hash1}`} title={hash1} />
|
||||
<Link href={`#${hash2}`} title={hash2} />
|
||||
</Anchor>,
|
||||
);
|
||||
expect(onChange).toHaveBeenCalledTimes(1);
|
||||
wrapper.instance().handleScrollTo(hash2);
|
||||
expect(onChange).toHaveBeenCalledTimes(2);
|
||||
expect(onChange).toHaveBeenCalledWith(hash2);
|
||||
});
|
||||
|
||||
it('invalid hash', async () => {
|
||||
const wrapper = mount<Anchor>(
|
||||
<Anchor>
|
||||
<Link href="notexsited" title="title" />
|
||||
</Anchor>,
|
||||
);
|
||||
|
||||
wrapper.find(`a[href="notexsited"]`).simulate('click');
|
||||
|
||||
wrapper.instance().handleScrollTo('notexsited');
|
||||
expect(wrapper.instance().state).not.toBe(null);
|
||||
});
|
||||
|
||||
it('test edge case when getBoundingClientRect return zero size', async () => {
|
||||
getBoundingClientRectMock.mockReturnValue({
|
||||
width: 0,
|
||||
height: 0,
|
||||
top: 1000,
|
||||
} as DOMRect);
|
||||
const hash = getHashUrl();
|
||||
let dateNowMock;
|
||||
|
||||
function dataNowMockFn() {
|
||||
let start = 0;
|
||||
|
||||
const handler = () => {
|
||||
return (start += 1000);
|
||||
};
|
||||
|
||||
return jest.spyOn(Date, 'now').mockImplementation(handler);
|
||||
}
|
||||
|
||||
dateNowMock = dataNowMockFn();
|
||||
|
||||
const scrollToSpy = jest.spyOn(window, 'scrollTo');
|
||||
const root = createDiv();
|
||||
mount(<h1 id={hash}>Hello</h1>, { attachTo: root });
|
||||
const wrapper = mount<Anchor>(
|
||||
<Anchor>
|
||||
<Link href={`#${hash}`} title={hash} />
|
||||
</Anchor>,
|
||||
);
|
||||
wrapper.instance().handleScrollTo(`#${hash}`);
|
||||
await sleep(30);
|
||||
expect(scrollToSpy).toHaveBeenLastCalledWith(0, 1000);
|
||||
dateNowMock = dataNowMockFn();
|
||||
|
||||
wrapper.setProps({ offsetTop: 100 });
|
||||
wrapper.instance().handleScrollTo(`#${hash}`);
|
||||
await sleep(30);
|
||||
expect(scrollToSpy).toHaveBeenLastCalledWith(0, 900);
|
||||
dateNowMock = dataNowMockFn();
|
||||
|
||||
wrapper.setProps({ targetOffset: 200 });
|
||||
wrapper.instance().handleScrollTo(`#${hash}`);
|
||||
await sleep(30);
|
||||
expect(scrollToSpy).toHaveBeenLastCalledWith(0, 800);
|
||||
|
||||
dateNowMock.mockRestore();
|
||||
getBoundingClientRectMock.mockReturnValue({
|
||||
width: 100,
|
||||
height: 100,
|
||||
top: 1000,
|
||||
} as DOMRect);
|
||||
});
|
||||
|
||||
it('test edge case when container is not windows', async () => {
|
||||
const hash = getHashUrl();
|
||||
let dateNowMock;
|
||||
|
||||
function dataNowMockFn() {
|
||||
let start = 0;
|
||||
|
||||
const handler = () => {
|
||||
return (start += 1000);
|
||||
};
|
||||
|
||||
return jest.spyOn(Date, 'now').mockImplementation(handler);
|
||||
}
|
||||
|
||||
dateNowMock = dataNowMockFn();
|
||||
|
||||
const scrollToSpy = jest.spyOn(window, 'scrollTo');
|
||||
const root = createDiv();
|
||||
mount(<h1 id={hash}>Hello</h1>, { attachTo: root });
|
||||
const wrapper = mount<Anchor>(
|
||||
<Anchor getContainer={() => document.body}>
|
||||
<Link href={`#${hash}`} title={hash} />
|
||||
</Anchor>,
|
||||
);
|
||||
wrapper.instance().handleScrollTo(`#${hash}`);
|
||||
await sleep(30);
|
||||
expect(scrollToSpy).toHaveBeenLastCalledWith(0, 800);
|
||||
dateNowMock = dataNowMockFn();
|
||||
|
||||
wrapper.setProps({ offsetTop: 100 });
|
||||
wrapper.instance().handleScrollTo(`#${hash}`);
|
||||
await sleep(30);
|
||||
expect(scrollToSpy).toHaveBeenLastCalledWith(0, 800);
|
||||
dateNowMock = dataNowMockFn();
|
||||
|
||||
wrapper.setProps({ targetOffset: 200 });
|
||||
wrapper.instance().handleScrollTo(`#${hash}`);
|
||||
await sleep(30);
|
||||
expect(scrollToSpy).toHaveBeenLastCalledWith(0, 800);
|
||||
|
||||
dateNowMock.mockRestore();
|
||||
});
|
||||
});
|
||||
6
components/anchor/context.ts
Normal file
6
components/anchor/context.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { AntAnchor } from './Anchor';
|
||||
|
||||
const AnchorContext = React.createContext<AntAnchor>(null as any);
|
||||
|
||||
export default AnchorContext;
|
||||
@@ -52,7 +52,7 @@
|
||||
}
|
||||
|
||||
&-link {
|
||||
padding: 7px 0 7px 16px;
|
||||
padding: @anchor-link-padding;
|
||||
line-height: 1.143;
|
||||
|
||||
&-title {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders ./components/auto-complete/demo/basic.md correctly 1`] = `
|
||||
<div>
|
||||
Array [
|
||||
<div
|
||||
class="ant-select ant-select-auto-complete ant-select-single ant-select-show-search"
|
||||
style="width:200px"
|
||||
@@ -30,9 +30,9 @@ exports[`renders ./components/auto-complete/demo/basic.md correctly 1`] = `
|
||||
input here
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<br />
|
||||
</div>,
|
||||
<br />,
|
||||
<br />,
|
||||
<div
|
||||
class="ant-select ant-select-auto-complete ant-select-single ant-select-show-search"
|
||||
style="width:200px"
|
||||
@@ -61,8 +61,8 @@ exports[`renders ./components/auto-complete/demo/basic.md correctly 1`] = `
|
||||
control mode
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`renders ./components/auto-complete/demo/certain-category.md correctly 1`] = `
|
||||
|
||||
@@ -1,6 +1,86 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`AutoComplete with Custom Input Element Render rtl render component should be rendered correctly in RTL direction 1`] = `
|
||||
exports[`AutoComplete legacy dataSource should accept react element option 1`] = `
|
||||
<div
|
||||
class="ant-select ant-select-auto-complete ant-select-single ant-select-open ant-select-show-search"
|
||||
>
|
||||
<div
|
||||
class="ant-select-selector"
|
||||
>
|
||||
<span
|
||||
class="ant-select-selection-search"
|
||||
>
|
||||
<input
|
||||
aria-activedescendant="undefined_list_0"
|
||||
aria-autocomplete="list"
|
||||
aria-controls="undefined_list"
|
||||
aria-expanded="true"
|
||||
aria-haspopup="listbox"
|
||||
aria-owns="undefined_list"
|
||||
autocomplete="off"
|
||||
class="ant-select-selection-search-input"
|
||||
role="combobox"
|
||||
value=""
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="ant-select-selection-placeholder"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="ant-select-dropdown"
|
||||
style="opacity:0"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
id="undefined_list"
|
||||
role="listbox"
|
||||
style="height:0;width:0;overflow:hidden"
|
||||
>
|
||||
<div
|
||||
aria-selected="false"
|
||||
id="undefined_list_0"
|
||||
role="option"
|
||||
>
|
||||
key
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class=""
|
||||
style="height:256px"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class=""
|
||||
style="display:flex;flex-direction:column"
|
||||
>
|
||||
<div
|
||||
aria-selected="false"
|
||||
class="ant-select-item ant-select-item-option ant-select-item-option-active"
|
||||
>
|
||||
<div
|
||||
class="ant-select-item-option-content"
|
||||
>
|
||||
ReactNode
|
||||
</div>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="ant-select-item-option-state"
|
||||
style="user-select:none;-webkit-user-select:none"
|
||||
unselectable="on"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`AutoComplete rtl render component should be rendered correctly in RTL direction 1`] = `
|
||||
<div
|
||||
class="ant-select ant-select-auto-complete ant-select-rtl ant-select-single ant-select-show-search"
|
||||
>
|
||||
|
||||
@@ -4,7 +4,7 @@ import AutoComplete from '..';
|
||||
import mountTest from '../../../tests/shared/mountTest';
|
||||
import rtlTest from '../../../tests/shared/rtlTest';
|
||||
|
||||
describe('AutoComplete with Custom Input Element Render', () => {
|
||||
describe('AutoComplete', () => {
|
||||
mountTest(AutoComplete);
|
||||
rtlTest(AutoComplete);
|
||||
|
||||
@@ -52,4 +52,21 @@ describe('AutoComplete with Custom Input Element Render', () => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error.mockRestore();
|
||||
});
|
||||
|
||||
it('legacy dataSource should accept react element option', () => {
|
||||
const wrapper = mount(<AutoComplete open dataSource={[<span key="key">ReactNode</span>]} />);
|
||||
expect(wrapper).toMatchRenderedSnapshot();
|
||||
});
|
||||
|
||||
it('legacy AutoComplete.Option should be compatiable', () => {
|
||||
const wrapper = mount(
|
||||
<AutoComplete>
|
||||
<AutoComplete.Option value="111">111</AutoComplete.Option>
|
||||
<AutoComplete.Option value="222">222</AutoComplete.Option>
|
||||
</AutoComplete>,
|
||||
);
|
||||
expect(wrapper.find('input').length).toBe(1);
|
||||
wrapper.find('input').simulate('change', { target: { value: '1' } });
|
||||
expect(wrapper.find('.ant-select-item-option').length).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -37,7 +37,7 @@ const Complete: React.FC = () => {
|
||||
setValue(data);
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
<>
|
||||
<AutoComplete
|
||||
options={options}
|
||||
style={{ width: 200 }}
|
||||
@@ -56,7 +56,7 @@ const Complete: React.FC = () => {
|
||||
onChange={onChange}
|
||||
placeholder="control mode"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -67,18 +67,16 @@ const options = [
|
||||
},
|
||||
];
|
||||
|
||||
const Complete: React.FC = () => {
|
||||
return (
|
||||
<AutoComplete
|
||||
dropdownClassName="certain-category-search-dropdown"
|
||||
dropdownMatchSelectWidth={500}
|
||||
style={{ width: 250 }}
|
||||
options={options}
|
||||
>
|
||||
<Input.Search size="large" placeholder="input here" />
|
||||
</AutoComplete>
|
||||
);
|
||||
};
|
||||
const Complete: React.FC = () => (
|
||||
<AutoComplete
|
||||
dropdownClassName="certain-category-search-dropdown"
|
||||
dropdownMatchSelectWidth={500}
|
||||
style={{ width: 250 }}
|
||||
options={options}
|
||||
>
|
||||
<Input.Search size="large" placeholder="input here" />
|
||||
</AutoComplete>
|
||||
);
|
||||
|
||||
ReactDOM.render(<Complete />, mountNode);
|
||||
```
|
||||
|
||||
@@ -22,25 +22,25 @@ const formItemLayout = {
|
||||
};
|
||||
|
||||
ReactDOM.render(
|
||||
<Form style={{ margin: '0 auto' }}>
|
||||
<Form.Item label="单独 AutoComplete" {...formItemLayout}>
|
||||
<Form style={{ margin: '0 auto' }} {...formItemLayout}>
|
||||
<Form.Item label="单独 AutoComplete">
|
||||
<AutoComplete />
|
||||
</Form.Item>
|
||||
<Form.Item label="单独 TreeSelect" {...formItemLayout}>
|
||||
<Form.Item label="单独 TreeSelect">
|
||||
<TreeSelect />
|
||||
</Form.Item>
|
||||
<Form.Item label="添加 Input.Group 正常" {...formItemLayout}>
|
||||
<Form.Item label="添加 Input.Group 正常">
|
||||
<Input.Group compact>
|
||||
<TreeSelect style={{ width: '30%' }} />
|
||||
<AutoComplete />
|
||||
</Input.Group>
|
||||
</Form.Item>
|
||||
<Form.Item label="包含 search 图标正常" {...formItemLayout}>
|
||||
<Form.Item label="包含 search 图标正常">
|
||||
<AutoComplete>
|
||||
<Input suffix={<SearchOutlined />} />
|
||||
</AutoComplete>
|
||||
</Form.Item>
|
||||
<Form.Item label="同时有 Input.Group 和图标发生移位" {...formItemLayout}>
|
||||
<Form.Item label="同时有 Input.Group 和图标发生移位">
|
||||
<Input.Group compact>
|
||||
<TreeSelect style={{ width: '30%' }} />
|
||||
<AutoComplete>
|
||||
@@ -48,7 +48,7 @@ ReactDOM.render(
|
||||
</AutoComplete>
|
||||
</Input.Group>
|
||||
</Form.Item>
|
||||
<Form.Item label="同时有 Input.Group 和 Search 组件发生移位" {...formItemLayout}>
|
||||
<Form.Item label="同时有 Input.Group 和 Search 组件发生移位">
|
||||
<Input.Group compact>
|
||||
<TreeSelect style={{ width: '30%' }} />
|
||||
<AutoComplete>
|
||||
@@ -56,7 +56,7 @@ ReactDOM.render(
|
||||
</AutoComplete>
|
||||
</Input.Group>
|
||||
</Form.Item>
|
||||
<Form.Item label="Input Group 和 Button 结合" {...formItemLayout}>
|
||||
<Form.Item label="Input Group 和 Button 结合">
|
||||
<Input.Group compact>
|
||||
<TreeSelect style={{ width: '20%' }} />
|
||||
<AutoComplete>
|
||||
|
||||
@@ -22,18 +22,16 @@ const options = [
|
||||
{ value: 'Wall Street' },
|
||||
];
|
||||
|
||||
const Complete: React.FC = () => {
|
||||
return (
|
||||
<AutoComplete
|
||||
style={{ width: 200 }}
|
||||
options={options}
|
||||
placeholder="try to type `b`"
|
||||
filterOption={(inputValue, option) =>
|
||||
option.value.toUpperCase().indexOf(inputValue.toUpperCase()) !== -1
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
const Complete: React.FC = () => (
|
||||
<AutoComplete
|
||||
style={{ width: 200 }}
|
||||
options={options}
|
||||
placeholder="try to type `b`"
|
||||
filterOption={(inputValue, option) =>
|
||||
option.value.toUpperCase().indexOf(inputValue.toUpperCase()) !== -1
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
ReactDOM.render(<Complete />, mountNode);
|
||||
```
|
||||
|
||||
@@ -30,14 +30,13 @@ const Complete: React.FC = () => {
|
||||
}
|
||||
setResult(res);
|
||||
};
|
||||
const children = result.map((email: string) => (
|
||||
<Option key={email} value={email}>
|
||||
{email}
|
||||
</Option>
|
||||
));
|
||||
return (
|
||||
<AutoComplete style={{ width: 200 }} onSearch={handleSearch} placeholder="input here">
|
||||
{children}
|
||||
{result.map((email: string) => (
|
||||
<Option key={email} value={email}>
|
||||
{email}
|
||||
</Option>
|
||||
))}
|
||||
</AutoComplete>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -34,6 +34,7 @@ When there is a need for autocomplete functionality.
|
||||
| defaultOpen | Initial open state of dropdown | boolean | - | |
|
||||
| open | Controlled open state of dropdown | boolean | - | |
|
||||
| onDropdownVisibleChange | Call when dropdown open | function(open) | - | |
|
||||
| notFoundContent | Specify content to show when no result matches.. | string | 'Not Found' | |
|
||||
|
||||
## Methods
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ title: AutoComplete
|
||||
| defaultOpen | 是否默认展开下拉菜单 | boolean | - | |
|
||||
| open | 是否展开下拉菜单 | boolean | - | |
|
||||
| onDropdownVisibleChange | 展开下拉菜单的回调 | function(open) | - | |
|
||||
| notFoundContent | 当下拉列表为空时显示的内容 | ReactNode | - | |
|
||||
|
||||
## 方法
|
||||
|
||||
|
||||
@@ -41,4 +41,8 @@ ReactDOM.render(
|
||||
margin-top: 16px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
.ant-row-rtl #components-avatar-demo-basic .ant-avatar {
|
||||
margin-right: 0;
|
||||
margin-left: 16px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -35,4 +35,8 @@ ReactDOM.render(
|
||||
margin-top: 16px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
.ant-row-rtl #components-avatar-demo-type .ant-avatar {
|
||||
margin-right: 0;
|
||||
margin-left: 16px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -50,73 +50,50 @@ export interface ScrollNumberState {
|
||||
count?: string | number | null;
|
||||
}
|
||||
|
||||
class ScrollNumber extends React.Component<ScrollNumberProps, ScrollNumberState> {
|
||||
static defaultProps = {
|
||||
count: null,
|
||||
onAnimated() {},
|
||||
};
|
||||
const ScrollNumber: React.FC<ScrollNumberProps> = props => {
|
||||
const [animateStarted, setAnimateStarted] = React.useState(true);
|
||||
const [count, setCount] = React.useState(props.count);
|
||||
const [prevCount, setPrevCount] = React.useState(props.count);
|
||||
const [lastCount, setLastCount] = React.useState(props.count);
|
||||
|
||||
static getDerivedStateFromProps(nextProps: ScrollNumberProps, nextState: ScrollNumberState) {
|
||||
if ('count' in nextProps) {
|
||||
if (nextState.count === nextProps.count) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
animateStarted: true,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
if (prevCount !== props.count) {
|
||||
setAnimateStarted(true);
|
||||
setPrevCount(props.count);
|
||||
}
|
||||
|
||||
lastCount?: string | number | null;
|
||||
|
||||
private timeout?: number;
|
||||
|
||||
constructor(props: ScrollNumberProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
animateStarted: true,
|
||||
count: props.count,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate(_: any, prevState: ScrollNumberState) {
|
||||
this.lastCount = prevState.count;
|
||||
const { animateStarted } = this.state;
|
||||
React.useEffect(() => {
|
||||
setLastCount(count);
|
||||
let timeout: number;
|
||||
if (animateStarted) {
|
||||
this.clearTimeout();
|
||||
// Let browser has time to reset the scroller before actually
|
||||
// performing the transition.
|
||||
this.timeout = setTimeout(() => {
|
||||
// eslint-disable-next-line react/no-did-update-set-state
|
||||
this.setState(
|
||||
(__, props) => ({
|
||||
animateStarted: false,
|
||||
count: props.count,
|
||||
}),
|
||||
this.onAnimated,
|
||||
);
|
||||
timeout = setTimeout(() => {
|
||||
setAnimateStarted(false);
|
||||
setCount(props.count);
|
||||
if (props.onAnimated) {
|
||||
props.onAnimated();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return () => {
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
};
|
||||
}, [animateStarted, count, props.count, props.onAnimated]);
|
||||
|
||||
componentWillUnmount() {
|
||||
this.clearTimeout();
|
||||
}
|
||||
|
||||
getPositionByNum(num: number, i: number) {
|
||||
const { count } = this.state;
|
||||
const getPositionByNum = (num: number, i: number) => {
|
||||
const currentCount = Math.abs(Number(count));
|
||||
const lastCount = Math.abs(Number(this.lastCount));
|
||||
const currentDigit = Math.abs(getNumberArray(this.state.count)[i] as number);
|
||||
const lastDigit = Math.abs(getNumberArray(this.lastCount)[i] as number);
|
||||
const lstCount = Math.abs(Number(lastCount));
|
||||
const currentDigit = Math.abs(getNumberArray(count)[i] as number);
|
||||
const lastDigit = Math.abs(getNumberArray(lstCount)[i] as number);
|
||||
|
||||
if (this.state.animateStarted) {
|
||||
if (animateStarted) {
|
||||
return 10 + num;
|
||||
}
|
||||
|
||||
// 同方向则在同一侧切换数字
|
||||
if (currentCount > lastCount) {
|
||||
if (currentCount > lstCount) {
|
||||
if (currentDigit >= lastDigit) {
|
||||
return 10 + num;
|
||||
}
|
||||
@@ -126,20 +103,12 @@ class ScrollNumber extends React.Component<ScrollNumberProps, ScrollNumberState>
|
||||
return 10 + num;
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
onAnimated = () => {
|
||||
const { onAnimated } = this.props;
|
||||
if (onAnimated) {
|
||||
onAnimated();
|
||||
}
|
||||
};
|
||||
|
||||
renderCurrentNumber(prefixCls: string, num: number | string, i: number) {
|
||||
const renderCurrentNumber = (prefixCls: string, num: number | string, i: number) => {
|
||||
if (typeof num === 'number') {
|
||||
const position = this.getPositionByNum(num, i);
|
||||
const removeTransition =
|
||||
this.state.animateStarted || getNumberArray(this.lastCount)[i] === undefined;
|
||||
const position = getPositionByNum(num, i);
|
||||
const removeTransition = animateStarted || getNumberArray(lastCount)[i] === undefined;
|
||||
return React.createElement(
|
||||
'span',
|
||||
{
|
||||
@@ -161,19 +130,18 @@ class ScrollNumber extends React.Component<ScrollNumberProps, ScrollNumberState>
|
||||
{num}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
renderNumberElement(prefixCls: string) {
|
||||
const { count } = this.state;
|
||||
const renderNumberElement = (prefixCls: string) => {
|
||||
if (count && Number(count) % 1 === 0) {
|
||||
return getNumberArray(count)
|
||||
.map((num, i) => this.renderCurrentNumber(prefixCls, num, i))
|
||||
.map((num, i) => renderCurrentNumber(prefixCls, num, i))
|
||||
.reverse();
|
||||
}
|
||||
return count;
|
||||
}
|
||||
};
|
||||
|
||||
renderScrollNumber = ({ getPrefixCls }: ConfigConsumerProps) => {
|
||||
const renderScrollNumber = ({ getPrefixCls }: ConfigConsumerProps) => {
|
||||
const {
|
||||
prefixCls: customizePrefixCls,
|
||||
className,
|
||||
@@ -181,9 +149,9 @@ class ScrollNumber extends React.Component<ScrollNumberProps, ScrollNumberState>
|
||||
title,
|
||||
component = 'sup',
|
||||
displayComponent,
|
||||
} = this.props;
|
||||
} = props;
|
||||
// fix https://fb.me/react-unknown-prop
|
||||
const restProps = omit(this.props, [
|
||||
const restProps = omit(props, [
|
||||
'count',
|
||||
'onAnimated',
|
||||
'component',
|
||||
@@ -214,19 +182,15 @@ class ScrollNumber extends React.Component<ScrollNumberProps, ScrollNumberState>
|
||||
),
|
||||
});
|
||||
}
|
||||
return React.createElement(component as any, newProps, this.renderNumberElement(prefixCls));
|
||||
return React.createElement(component as any, newProps, renderNumberElement(prefixCls));
|
||||
};
|
||||
|
||||
render() {
|
||||
return <ConfigConsumer>{this.renderScrollNumber}</ConfigConsumer>;
|
||||
}
|
||||
return <ConfigConsumer>{renderScrollNumber}</ConfigConsumer>;
|
||||
};
|
||||
|
||||
private clearTimeout(): void {
|
||||
if (this.timeout) {
|
||||
clearTimeout(this.timeout);
|
||||
this.timeout = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
ScrollNumber.defaultProps = {
|
||||
count: null,
|
||||
onAnimated() {},
|
||||
};
|
||||
|
||||
export default ScrollNumber;
|
||||
|
||||
@@ -32,40 +32,47 @@ function isPresetColor(color?: string): boolean {
|
||||
return (PresetColorTypes as any[]).indexOf(color) !== -1;
|
||||
}
|
||||
|
||||
export default class Badge extends React.Component<BadgeProps, any> {
|
||||
static defaultProps = {
|
||||
count: null,
|
||||
showZero: false,
|
||||
dot: false,
|
||||
overflowCount: 99,
|
||||
};
|
||||
|
||||
getNumberedDisplayCount() {
|
||||
const { count, overflowCount } = this.props;
|
||||
const Badge: React.FC<BadgeProps> = props => {
|
||||
const getNumberedDisplayCount = () => {
|
||||
const { count, overflowCount } = props;
|
||||
const displayCount =
|
||||
(count as number) > (overflowCount as number) ? `${overflowCount}+` : count;
|
||||
return displayCount as string | number | null;
|
||||
}
|
||||
};
|
||||
|
||||
getDisplayCount() {
|
||||
const isDot = this.isDot();
|
||||
const hasStatus = (): boolean => {
|
||||
const { status, color } = props;
|
||||
return !!status || !!color;
|
||||
};
|
||||
|
||||
const isZero = () => {
|
||||
const numberedDisplayCount = getNumberedDisplayCount();
|
||||
return numberedDisplayCount === '0' || numberedDisplayCount === 0;
|
||||
};
|
||||
|
||||
const isDot = () => {
|
||||
const { dot } = props;
|
||||
return (dot && !isZero()) || hasStatus();
|
||||
};
|
||||
|
||||
const getDisplayCount = () => {
|
||||
// dot mode don't need count
|
||||
if (isDot) {
|
||||
if (isDot()) {
|
||||
return '';
|
||||
}
|
||||
return this.getNumberedDisplayCount();
|
||||
}
|
||||
return getNumberedDisplayCount();
|
||||
};
|
||||
|
||||
getScrollNumberTitle() {
|
||||
const { title, count } = this.props;
|
||||
const getScrollNumberTitle = () => {
|
||||
const { title, count } = props;
|
||||
if (title) {
|
||||
return title;
|
||||
}
|
||||
return typeof count === 'string' || typeof count === 'number' ? count : undefined;
|
||||
}
|
||||
};
|
||||
|
||||
getStyleWithOffset() {
|
||||
const { offset, style } = this.props;
|
||||
const getStyleWithOffset = () => {
|
||||
const { offset, style } = props;
|
||||
return offset
|
||||
? {
|
||||
right: -parseInt(offset[0] as string, 10),
|
||||
@@ -73,79 +80,61 @@ export default class Badge extends React.Component<BadgeProps, any> {
|
||||
...style,
|
||||
}
|
||||
: style;
|
||||
}
|
||||
};
|
||||
|
||||
getBadgeClassName(prefixCls: string, direction: string = 'ltr') {
|
||||
const { className, children } = this.props;
|
||||
const getBadgeClassName = (prefixCls: string, direction: string = 'ltr') => {
|
||||
const { className, children } = props;
|
||||
return classNames(className, prefixCls, {
|
||||
[`${prefixCls}-status`]: this.hasStatus(),
|
||||
[`${prefixCls}-status`]: hasStatus(),
|
||||
[`${prefixCls}-not-a-wrapper`]: !children,
|
||||
[`${prefixCls}-rtl`]: direction === 'rtl',
|
||||
}) as string;
|
||||
}
|
||||
};
|
||||
|
||||
hasStatus(): boolean {
|
||||
const { status, color } = this.props;
|
||||
return !!status || !!color;
|
||||
}
|
||||
|
||||
isZero() {
|
||||
const numberedDisplayCount = this.getNumberedDisplayCount();
|
||||
return numberedDisplayCount === '0' || numberedDisplayCount === 0;
|
||||
}
|
||||
|
||||
isDot() {
|
||||
const { dot } = this.props;
|
||||
const isZero = this.isZero();
|
||||
return (dot && !isZero) || this.hasStatus();
|
||||
}
|
||||
|
||||
isHidden() {
|
||||
const { showZero } = this.props;
|
||||
const displayCount = this.getDisplayCount();
|
||||
const isZero = this.isZero();
|
||||
const isDot = this.isDot();
|
||||
const isHidden = () => {
|
||||
const { showZero } = props;
|
||||
const displayCount = getDisplayCount();
|
||||
const isEmpty = displayCount === null || displayCount === undefined || displayCount === '';
|
||||
return (isEmpty || (isZero && !showZero)) && !isDot;
|
||||
}
|
||||
return (isEmpty || (isZero() && !showZero)) && !isDot();
|
||||
};
|
||||
|
||||
renderStatusText(prefixCls: string) {
|
||||
const { text } = this.props;
|
||||
const hidden = this.isHidden();
|
||||
const renderStatusText = (prefixCls: string) => {
|
||||
const { text } = props;
|
||||
const hidden = isHidden();
|
||||
return hidden || !text ? null : <span className={`${prefixCls}-status-text`}>{text}</span>;
|
||||
}
|
||||
};
|
||||
|
||||
renderDisplayComponent() {
|
||||
const { count } = this.props;
|
||||
const renderDisplayComponent = () => {
|
||||
const { count } = props;
|
||||
const customNode = count as React.ReactElement<any>;
|
||||
if (!customNode || typeof customNode !== 'object') {
|
||||
return undefined;
|
||||
}
|
||||
return React.cloneElement(customNode, {
|
||||
style: {
|
||||
...this.getStyleWithOffset(),
|
||||
...getStyleWithOffset(),
|
||||
...(customNode.props && customNode.props.style),
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
renderBadgeNumber(prefixCls: string, scrollNumberPrefixCls: string) {
|
||||
const { status, count, color } = this.props;
|
||||
const renderBadgeNumber = (prefixCls: string, scrollNumberPrefixCls: string) => {
|
||||
const { status, count, color } = props;
|
||||
|
||||
const displayCount = this.getDisplayCount();
|
||||
const isDot = this.isDot();
|
||||
const hidden = this.isHidden();
|
||||
const displayCount = getDisplayCount();
|
||||
const dot = isDot();
|
||||
const hidden = isHidden();
|
||||
|
||||
const scrollNumberCls = classNames({
|
||||
[`${prefixCls}-dot`]: isDot,
|
||||
[`${prefixCls}-count`]: !isDot,
|
||||
[`${prefixCls}-dot`]: dot,
|
||||
[`${prefixCls}-count`]: !dot,
|
||||
[`${prefixCls}-multiple-words`]:
|
||||
!isDot && count && count.toString && count.toString().length > 1,
|
||||
!dot && count && count.toString && count.toString().length > 1,
|
||||
[`${prefixCls}-status-${status}`]: !!status,
|
||||
[`${prefixCls}-status-${color}`]: isPresetColor(color),
|
||||
});
|
||||
|
||||
let statusStyle: React.CSSProperties | undefined = this.getStyleWithOffset();
|
||||
let statusStyle: React.CSSProperties | undefined = getStyleWithOffset();
|
||||
if (color && !isPresetColor(color)) {
|
||||
statusStyle = statusStyle || {};
|
||||
statusStyle.background = color;
|
||||
@@ -157,15 +146,15 @@ export default class Badge extends React.Component<BadgeProps, any> {
|
||||
data-show={!hidden}
|
||||
className={scrollNumberCls}
|
||||
count={displayCount}
|
||||
displayComponent={this.renderDisplayComponent()} // <Badge status="success" count={<Icon type="xxx" />}></Badge>
|
||||
title={this.getScrollNumberTitle()}
|
||||
displayComponent={renderDisplayComponent()} // <Badge status="success" count={<Icon type="xxx" />}></Badge>
|
||||
title={getScrollNumberTitle()}
|
||||
style={statusStyle}
|
||||
key="scrollNumber"
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
renderBadge = ({ getPrefixCls, direction }: ConfigConsumerProps) => {
|
||||
const renderBadge = ({ getPrefixCls, direction }: ConfigConsumerProps) => {
|
||||
const {
|
||||
prefixCls: customizePrefixCls,
|
||||
scrollNumberPrefixCls: customizeScrollNumberPrefixCls,
|
||||
@@ -174,7 +163,7 @@ export default class Badge extends React.Component<BadgeProps, any> {
|
||||
text,
|
||||
color,
|
||||
...restProps
|
||||
} = this.props;
|
||||
} = props;
|
||||
const omitArr = [
|
||||
'count',
|
||||
'showZero',
|
||||
@@ -189,11 +178,11 @@ export default class Badge extends React.Component<BadgeProps, any> {
|
||||
const prefixCls = getPrefixCls('badge', customizePrefixCls);
|
||||
const scrollNumberPrefixCls = getPrefixCls('scroll-number', customizeScrollNumberPrefixCls);
|
||||
|
||||
const scrollNumber = this.renderBadgeNumber(prefixCls, scrollNumberPrefixCls);
|
||||
const statusText = this.renderStatusText(prefixCls);
|
||||
const scrollNumber = renderBadgeNumber(prefixCls, scrollNumberPrefixCls);
|
||||
const statusText = renderStatusText(prefixCls);
|
||||
|
||||
const statusCls = classNames({
|
||||
[`${prefixCls}-status-dot`]: this.hasStatus(),
|
||||
[`${prefixCls}-status-dot`]: hasStatus(),
|
||||
[`${prefixCls}-status-${status}`]: !!status,
|
||||
[`${prefixCls}-status-${color}`]: isPresetColor(color),
|
||||
});
|
||||
@@ -203,13 +192,13 @@ export default class Badge extends React.Component<BadgeProps, any> {
|
||||
}
|
||||
|
||||
// <Badge status="success" />
|
||||
if (!children && this.hasStatus()) {
|
||||
const styleWithOffset = this.getStyleWithOffset();
|
||||
if (!children && hasStatus()) {
|
||||
const styleWithOffset = getStyleWithOffset();
|
||||
const statusTextColor = styleWithOffset && styleWithOffset.color;
|
||||
return (
|
||||
<span
|
||||
{...omit(restProps, omitArr)}
|
||||
className={this.getBadgeClassName(prefixCls, direction)}
|
||||
className={getBadgeClassName(prefixCls, direction)}
|
||||
style={styleWithOffset}
|
||||
>
|
||||
<span className={statusCls} style={statusStyle} />
|
||||
@@ -221,7 +210,7 @@ export default class Badge extends React.Component<BadgeProps, any> {
|
||||
}
|
||||
|
||||
return (
|
||||
<span {...omit(restProps, omitArr)} className={this.getBadgeClassName(prefixCls, direction)}>
|
||||
<span {...omit(restProps, omitArr)} className={getBadgeClassName(prefixCls, direction)}>
|
||||
{children}
|
||||
<Animate
|
||||
component=""
|
||||
@@ -236,7 +225,14 @@ export default class Badge extends React.Component<BadgeProps, any> {
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
return <ConfigConsumer>{this.renderBadge}</ConfigConsumer>;
|
||||
}
|
||||
}
|
||||
return <ConfigConsumer>{renderBadge}</ConfigConsumer>;
|
||||
};
|
||||
|
||||
Badge.defaultProps = {
|
||||
count: null,
|
||||
showZero: false,
|
||||
dot: false,
|
||||
overflowCount: 99,
|
||||
};
|
||||
|
||||
export default Badge;
|
||||
|
||||
@@ -531,8 +531,66 @@ exports[`renders ./components/button/demo/legacy-group.md correctly 1`] = `
|
||||
Button 2
|
||||
</span>
|
||||
</button>
|
||||
<span
|
||||
class="ant-tooltip-disabled-compatible-wrapper"
|
||||
style="display:inline-block;cursor:not-allowed"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-primary ant-btn-icon-only"
|
||||
disabled=""
|
||||
style="pointer-events:none"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-label="download"
|
||||
class="anticon anticon-download"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class=""
|
||||
data-icon="download"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M505.7 661a8 8 0 0012.6 0l112-141.7c4.1-5.2.4-12.9-6.3-12.9h-74.1V168c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v338.3H400c-6.7 0-10.4 7.7-6.3 12.9l112 141.8zM878 626h-60c-4.4 0-8 3.6-8 8v154H214V634c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v198c0 17.7 14.3 32 32 32h684c17.7 0 32-14.3 32-32V634c0-4.4-3.6-8-8-8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
</span>
|
||||
<button
|
||||
class="ant-btn ant-btn-primary ant-btn-icon-only"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-label="download"
|
||||
class="anticon anticon-download"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class=""
|
||||
data-icon="download"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M505.7 661a8 8 0 0012.6 0l112-141.7c4.1-5.2.4-12.9-6.3-12.9h-74.1V168c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v338.3H400c-6.7 0-10.4 7.7-6.3 12.9l112 141.8zM878 626h-60c-4.4 0-8 3.6-8 8v154H214V634c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v198c0 17.7 14.3 32 32 32h684c17.7 0 32-14.3 32-32V634c0-4.4-3.6-8-8-8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<div>
|
||||
<div
|
||||
class="ant-btn-group"
|
||||
@@ -553,8 +611,66 @@ exports[`renders ./components/button/demo/legacy-group.md correctly 1`] = `
|
||||
Button 2
|
||||
</span>
|
||||
</button>
|
||||
<span
|
||||
class="ant-tooltip-disabled-compatible-wrapper"
|
||||
style="display:inline-block;cursor:not-allowed"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-primary ant-btn-icon-only"
|
||||
disabled=""
|
||||
style="pointer-events:none"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-label="download"
|
||||
class="anticon anticon-download"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class=""
|
||||
data-icon="download"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M505.7 661a8 8 0 0012.6 0l112-141.7c4.1-5.2.4-12.9-6.3-12.9h-74.1V168c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v338.3H400c-6.7 0-10.4 7.7-6.3 12.9l112 141.8zM878 626h-60c-4.4 0-8 3.6-8 8v154H214V634c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v198c0 17.7 14.3 32 32 32h684c17.7 0 32-14.3 32-32V634c0-4.4-3.6-8-8-8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
</span>
|
||||
<button
|
||||
class="ant-btn ant-btn-primary ant-btn-icon-only"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-label="download"
|
||||
class="anticon anticon-download"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class=""
|
||||
data-icon="download"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M505.7 661a8 8 0 0012.6 0l112-141.7c4.1-5.2.4-12.9-6.3-12.9h-74.1V168c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v338.3H400c-6.7 0-10.4 7.7-6.3 12.9l112 141.8zM878 626h-60c-4.4 0-8 3.6-8 8v154H214V634c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v198c0 17.7 14.3 32 32 32h684c17.7 0 32-14.3 32-32V634c0-4.4-3.6-8-8-8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<div>
|
||||
<div
|
||||
class="ant-btn-group ant-btn-group-lg"
|
||||
@@ -575,13 +691,70 @@ exports[`renders ./components/button/demo/legacy-group.md correctly 1`] = `
|
||||
Button 2
|
||||
</span>
|
||||
</button>
|
||||
<span
|
||||
class="ant-tooltip-disabled-compatible-wrapper"
|
||||
style="display:inline-block;cursor:not-allowed"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-primary ant-btn-icon-only"
|
||||
disabled=""
|
||||
style="pointer-events:none"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-label="download"
|
||||
class="anticon anticon-download"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class=""
|
||||
data-icon="download"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M505.7 661a8 8 0 0012.6 0l112-141.7c4.1-5.2.4-12.9-6.3-12.9h-74.1V168c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v338.3H400c-6.7 0-10.4 7.7-6.3 12.9l112 141.8zM878 626h-60c-4.4 0-8 3.6-8 8v154H214V634c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v198c0 17.7 14.3 32 32 32h684c17.7 0 32-14.3 32-32V634c0-4.4-3.6-8-8-8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
</span>
|
||||
<button
|
||||
class="ant-btn ant-btn-primary ant-btn-icon-only"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-label="download"
|
||||
class="anticon anticon-download"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class=""
|
||||
data-icon="download"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M505.7 661a8 8 0 0012.6 0l112-141.7c4.1-5.2.4-12.9-6.3-12.9h-74.1V168c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v338.3H400c-6.7 0-10.4 7.7-6.3 12.9l112 141.8zM878 626h-60c-4.4 0-8 3.6-8 8v154H214V634c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v198c0 17.7 14.3 32 32 32h684c17.7 0 32-14.3 32-32V634c0-4.4-3.6-8-8-8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders ./components/button/demo/loading.md correctly 1`] = `
|
||||
<div>
|
||||
Array [
|
||||
<button
|
||||
class="ant-btn ant-btn-primary ant-btn-loading"
|
||||
type="button"
|
||||
@@ -613,7 +786,7 @@ exports[`renders ./components/button/demo/loading.md correctly 1`] = `
|
||||
<span>
|
||||
Loading
|
||||
</span>
|
||||
</button>
|
||||
</button>,
|
||||
<button
|
||||
class="ant-btn ant-btn-primary ant-btn-sm ant-btn-loading"
|
||||
type="button"
|
||||
@@ -645,8 +818,37 @@ exports[`renders ./components/button/demo/loading.md correctly 1`] = `
|
||||
<span>
|
||||
Loading
|
||||
</span>
|
||||
</button>
|
||||
<br />
|
||||
</button>,
|
||||
<button
|
||||
class="ant-btn ant-btn-primary ant-btn-icon-only ant-btn-loading"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="ant-btn-loading-icon"
|
||||
>
|
||||
<span
|
||||
aria-label="loading"
|
||||
class="anticon anticon-loading"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="anticon-spin"
|
||||
data-icon="loading"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="0 0 1024 1024"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M988 548c-19.9 0-36-16.1-36-36 0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 00-94.3-139.9 437.71 437.71 0 00-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3.1 19.9-16 36-35.9 36z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</button>,
|
||||
<br />,
|
||||
<button
|
||||
class="ant-btn ant-btn-primary"
|
||||
type="button"
|
||||
@@ -654,7 +856,7 @@ exports[`renders ./components/button/demo/loading.md correctly 1`] = `
|
||||
<span>
|
||||
Click me!
|
||||
</span>
|
||||
</button>
|
||||
</button>,
|
||||
<button
|
||||
class="ant-btn ant-btn-primary"
|
||||
type="button"
|
||||
@@ -682,8 +884,33 @@ exports[`renders ./components/button/demo/loading.md correctly 1`] = `
|
||||
<span>
|
||||
Click me!
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</button>,
|
||||
<button
|
||||
class="ant-btn ant-btn-primary ant-btn-icon-only"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-label="poweroff"
|
||||
class="anticon anticon-poweroff"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class=""
|
||||
data-icon="poweroff"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M705.6 124.9a8 8 0 00-11.6 7.2v64.2c0 5.5 2.9 10.6 7.5 13.6a352.2 352.2 0 0162.2 49.8c32.7 32.8 58.4 70.9 76.3 113.3a355 355 0 0127.9 138.7c0 48.1-9.4 94.8-27.9 138.7a355.92 355.92 0 01-76.3 113.3 353.06 353.06 0 01-113.2 76.4c-43.8 18.6-90.5 28-138.5 28s-94.7-9.4-138.5-28a353.06 353.06 0 01-113.2-76.4A355.92 355.92 0 01184 650.4a355 355 0 01-27.9-138.7c0-48.1 9.4-94.8 27.9-138.7 17.9-42.4 43.6-80.5 76.3-113.3 19-19 39.8-35.6 62.2-49.8 4.7-2.9 7.5-8.1 7.5-13.6V132c0-6-6.3-9.8-11.6-7.2C178.5 195.2 82 339.3 80 506.3 77.2 745.1 272.5 943.5 511.2 944c239 .5 432.8-193.3 432.8-432.4 0-169.2-97-315.7-238.4-386.7zM480 560h64c4.4 0 8-3.6 8-8V88c0-4.4-3.6-8-8-8h-64c-4.4 0-8 3.6-8 8v464c0 4.4 3.6 8 8 8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</button>,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`renders ./components/button/demo/multiple.md correctly 1`] = `
|
||||
@@ -1,12 +1,12 @@
|
||||
import React, { Component } from 'react';
|
||||
import { mount, render } from 'enzyme';
|
||||
import renderer from 'react-test-renderer';
|
||||
import { SearchOutlined } from '@ant-design/icons';
|
||||
import Button from '..';
|
||||
import ConfigProvider from '../../config-provider';
|
||||
import mountTest from '../../../tests/shared/mountTest';
|
||||
import rtlTest from '../../../tests/shared/rtlTest';
|
||||
import { sleep } from '../../../tests/utils';
|
||||
import { SizeType } from '../../config-provider/SizeContext';
|
||||
|
||||
describe('Button', () => {
|
||||
mountTest(Button);
|
||||
@@ -30,13 +30,14 @@ describe('Button', () => {
|
||||
});
|
||||
|
||||
it('mount correctly', () => {
|
||||
expect(() => renderer.create(<Button>Follow</Button>)).not.toThrow();
|
||||
expect(() => mount(<Button>Follow</Button>)).not.toThrow();
|
||||
});
|
||||
|
||||
it('warns if size is wrong', () => {
|
||||
const mockWarn = jest.fn();
|
||||
jest.spyOn(console, 'warn').mockImplementation(mockWarn);
|
||||
render(<Button.Group size="who am I" />);
|
||||
const size = ('who am I' as any) as SizeType;
|
||||
render(<Button.Group size={size} />);
|
||||
expect(mockWarn).toHaveBeenCalledTimes(1);
|
||||
expect(mockWarn.mock.calls[0][0]).toMatchObject({
|
||||
message: 'unreachable case: "who am I"',
|
||||
@@ -74,7 +75,7 @@ describe('Button', () => {
|
||||
});
|
||||
|
||||
it('renders Chinese characters correctly in HOC', () => {
|
||||
const Text = ({ children }) => <span>{children}</span>;
|
||||
const Text = ({ children }: { children: React.ReactNode }) => <span>{children}</span>;
|
||||
const wrapper = mount(
|
||||
<Button>
|
||||
<Text>按钮</Text>
|
||||
@@ -110,7 +111,7 @@ describe('Button', () => {
|
||||
|
||||
it('have static property for type detecting', () => {
|
||||
const wrapper = mount(<Button>Button Text</Button>);
|
||||
expect(wrapper.type().__ANT_BUTTON).toBe(true);
|
||||
expect((wrapper.type() as any).__ANT_BUTTON).toBe(true);
|
||||
});
|
||||
|
||||
it('should change loading state instantly by default', () => {
|
||||
@@ -189,7 +190,7 @@ describe('Button', () => {
|
||||
|
||||
it('should has click wave effect', async () => {
|
||||
const wrapper = mount(<Button type="primary">button</Button>);
|
||||
wrapper.find('.ant-btn').getDOMNode().click();
|
||||
wrapper.find('.ant-btn').getDOMNode<HTMLButtonElement>().click();
|
||||
await new Promise(resolve => setTimeout(resolve, 0));
|
||||
expect(wrapper.render()).toMatchSnapshot();
|
||||
});
|
||||
@@ -262,6 +263,5 @@ describe('Button', () => {
|
||||
throw new Error('Should not called!!!');
|
||||
},
|
||||
});
|
||||
wrapper.find('Button').instance().forceUpdate();
|
||||
});
|
||||
});
|
||||
@@ -4,7 +4,7 @@ import classNames from 'classnames';
|
||||
import omit from 'omit.js';
|
||||
|
||||
import Group from './button-group';
|
||||
import { ConfigContext, ConfigConsumerProps } from '../config-provider';
|
||||
import { ConfigContext } from '../config-provider';
|
||||
import Wave from '../_util/wave';
|
||||
import { Omit, tuple } from '../_util/type';
|
||||
import warning from '../_util/warning';
|
||||
@@ -65,13 +65,21 @@ function spaceChildren(children: React.ReactNode, needInserted: boolean) {
|
||||
);
|
||||
}
|
||||
|
||||
const ButtonTypes = tuple('default', 'primary', 'ghost', 'dashed', 'danger', 'link');
|
||||
const ButtonTypes = tuple('default', 'primary', 'ghost', 'dashed', 'link');
|
||||
export type ButtonType = typeof ButtonTypes[number];
|
||||
const ButtonShapes = tuple('circle', 'circle-outline', 'round');
|
||||
export type ButtonShape = typeof ButtonShapes[number];
|
||||
const ButtonHTMLTypes = tuple('submit', 'button', 'reset');
|
||||
export type ButtonHTMLType = typeof ButtonHTMLTypes[number];
|
||||
|
||||
export type LegacyButtonType = ButtonType | 'danger';
|
||||
export function convertLegacyProps(type?: LegacyButtonType): ButtonProps {
|
||||
if (type === 'danger') {
|
||||
return { danger: true };
|
||||
}
|
||||
return { type };
|
||||
}
|
||||
|
||||
export interface BaseButtonProps {
|
||||
type?: ButtonType;
|
||||
icon?: React.ReactNode;
|
||||
@@ -104,72 +112,59 @@ export type NativeButtonProps = {
|
||||
|
||||
export type ButtonProps = Partial<AnchorButtonProps & NativeButtonProps>;
|
||||
|
||||
interface ButtonState {
|
||||
loading?: boolean | { delay?: number };
|
||||
hasTwoCNChar: boolean;
|
||||
interface CompoundedComponent
|
||||
extends React.ForwardRefExoticComponent<ButtonProps & React.RefAttributes<HTMLElement>> {
|
||||
Group: typeof Group;
|
||||
__ANT_BUTTON: boolean;
|
||||
}
|
||||
|
||||
class Button extends React.Component<ButtonProps, ButtonState> {
|
||||
static Group: typeof Group;
|
||||
const InternalButton: React.ForwardRefRenderFunction<unknown, ButtonProps> = (props, ref) => {
|
||||
const size = React.useContext(SizeContext);
|
||||
const [loading, setLoading] = React.useState(props.loading);
|
||||
const [hasTwoCNChar, setHasTwoCNChar] = React.useState(false);
|
||||
const { getPrefixCls, autoInsertSpaceInButton, direction } = React.useContext(ConfigContext);
|
||||
const buttonRef = (ref as any) || React.createRef<HTMLElement>();
|
||||
let delayTimeout: number;
|
||||
|
||||
static __ANT_BUTTON = true;
|
||||
|
||||
static contextType = ConfigContext;
|
||||
|
||||
static defaultProps = {
|
||||
loading: false,
|
||||
ghost: false,
|
||||
block: false,
|
||||
htmlType: 'button' as ButtonProps['htmlType'],
|
||||
const isNeedInserted = () => {
|
||||
const { icon, children, type } = props;
|
||||
return React.Children.count(children) === 1 && !icon && type !== 'link';
|
||||
};
|
||||
|
||||
private delayTimeout: number;
|
||||
|
||||
private buttonNode: HTMLElement | null;
|
||||
|
||||
constructor(props: ButtonProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loading: props.loading,
|
||||
hasTwoCNChar: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.fixTwoCNChar();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: ButtonProps) {
|
||||
this.fixTwoCNChar();
|
||||
|
||||
if (prevProps.loading && typeof prevProps.loading !== 'boolean') {
|
||||
clearTimeout(this.delayTimeout);
|
||||
const fixTwoCNChar = () => {
|
||||
// Fix for HOC usage like <FormatMessage />
|
||||
if (!buttonRef || !buttonRef.current || autoInsertSpaceInButton === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { loading } = this.props;
|
||||
if (loading && typeof loading !== 'boolean' && loading.delay) {
|
||||
this.delayTimeout = window.setTimeout(() => {
|
||||
this.setState({ loading });
|
||||
}, loading.delay);
|
||||
} else if (prevProps.loading !== loading) {
|
||||
// eslint-disable-next-line react/no-did-update-set-state
|
||||
this.setState({ loading });
|
||||
const buttonText = buttonRef.current.textContent;
|
||||
if (isNeedInserted() && isTwoCNChar(buttonText)) {
|
||||
if (!hasTwoCNChar) {
|
||||
setHasTwoCNChar(true);
|
||||
}
|
||||
} else if (hasTwoCNChar) {
|
||||
setHasTwoCNChar(false);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.delayTimeout) {
|
||||
clearTimeout(this.delayTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
saveButtonRef = (node: HTMLElement | null) => {
|
||||
this.buttonNode = node;
|
||||
};
|
||||
|
||||
handleClick: React.MouseEventHandler<HTMLButtonElement | HTMLAnchorElement> = e => {
|
||||
const { loading } = this.state;
|
||||
const { onClick } = this.props;
|
||||
React.useEffect(() => {
|
||||
if (props.loading && typeof props.loading !== 'boolean') {
|
||||
clearTimeout(delayTimeout);
|
||||
}
|
||||
if (props.loading && typeof props.loading !== 'boolean' && props.loading.delay) {
|
||||
delayTimeout = window.setTimeout(() => {
|
||||
setLoading(props.loading);
|
||||
}, props.loading.delay);
|
||||
} else if (props.loading !== loading) {
|
||||
setLoading(props.loading);
|
||||
}
|
||||
}, [props.loading]);
|
||||
|
||||
React.useEffect(() => {
|
||||
fixTwoCNChar();
|
||||
}, [buttonRef]);
|
||||
|
||||
const handleClick = (e: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement, MouseEvent>) => {
|
||||
const { onClick } = props;
|
||||
if (loading) {
|
||||
return;
|
||||
}
|
||||
@@ -177,144 +172,115 @@ class Button extends React.Component<ButtonProps, ButtonState> {
|
||||
(onClick as React.MouseEventHandler<HTMLButtonElement | HTMLAnchorElement>)(e);
|
||||
}
|
||||
};
|
||||
const {
|
||||
prefixCls: customizePrefixCls,
|
||||
type,
|
||||
danger,
|
||||
shape,
|
||||
size: customizeSize,
|
||||
className,
|
||||
children,
|
||||
icon,
|
||||
ghost,
|
||||
block,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
fixTwoCNChar() {
|
||||
const { autoInsertSpaceInButton }: ConfigConsumerProps = this.context;
|
||||
warning(
|
||||
!(typeof icon === 'string' && icon.length > 2),
|
||||
'Button',
|
||||
`\`icon\` is using ReactNode instead of string naming in v4. Please check \`${icon}\` at https://ant.design/components/icon`,
|
||||
);
|
||||
|
||||
// Fix for HOC usage like <FormatMessage />
|
||||
if (!this.buttonNode || autoInsertSpaceInButton === false) {
|
||||
return;
|
||||
}
|
||||
const buttonText = this.buttonNode.textContent;
|
||||
if (this.isNeedInserted() && isTwoCNChar(buttonText)) {
|
||||
if (!this.state.hasTwoCNChar) {
|
||||
this.setState({
|
||||
hasTwoCNChar: true,
|
||||
});
|
||||
}
|
||||
} else if (this.state.hasTwoCNChar) {
|
||||
this.setState({
|
||||
hasTwoCNChar: false,
|
||||
});
|
||||
}
|
||||
const prefixCls = getPrefixCls('btn', customizePrefixCls);
|
||||
const autoInsertSpace = autoInsertSpaceInButton !== false;
|
||||
|
||||
// large => lg
|
||||
// small => sm
|
||||
let sizeCls = '';
|
||||
switch (customizeSize || size) {
|
||||
case 'large':
|
||||
sizeCls = 'lg';
|
||||
break;
|
||||
case 'small':
|
||||
sizeCls = 'sm';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
isNeedInserted() {
|
||||
const { icon, children, type } = this.props;
|
||||
return React.Children.count(children) === 1 && !icon && type !== 'link';
|
||||
}
|
||||
const iconType = loading ? 'loading' : icon;
|
||||
|
||||
render() {
|
||||
const { getPrefixCls, autoInsertSpaceInButton, direction }: ConfigConsumerProps = this.context;
|
||||
const classes = classNames(prefixCls, className, {
|
||||
[`${prefixCls}-${type}`]: type,
|
||||
[`${prefixCls}-${shape}`]: shape,
|
||||
[`${prefixCls}-${sizeCls}`]: sizeCls,
|
||||
[`${prefixCls}-icon-only`]: !children && children !== 0 && iconType,
|
||||
[`${prefixCls}-background-ghost`]: ghost,
|
||||
[`${prefixCls}-loading`]: loading,
|
||||
[`${prefixCls}-two-chinese-chars`]: hasTwoCNChar && autoInsertSpace,
|
||||
[`${prefixCls}-block`]: block,
|
||||
[`${prefixCls}-dangerous`]: !!danger,
|
||||
[`${prefixCls}-rtl`]: direction === 'rtl',
|
||||
});
|
||||
|
||||
const iconNode =
|
||||
icon && !loading ? (
|
||||
icon
|
||||
) : (
|
||||
<LoadingIcon existIcon={!!icon} prefixCls={prefixCls} loading={loading} />
|
||||
);
|
||||
|
||||
const kids =
|
||||
children || children === 0
|
||||
? spaceChildren(children, isNeedInserted() && autoInsertSpace)
|
||||
: null;
|
||||
|
||||
const linkButtonRestProps = omit(rest as AnchorButtonProps, ['htmlType', 'loading']);
|
||||
if (linkButtonRestProps.href !== undefined) {
|
||||
return (
|
||||
<SizeContext.Consumer>
|
||||
{size => {
|
||||
const {
|
||||
prefixCls: customizePrefixCls,
|
||||
type,
|
||||
danger,
|
||||
shape,
|
||||
size: customizeSize,
|
||||
className,
|
||||
children,
|
||||
icon,
|
||||
ghost,
|
||||
block,
|
||||
...rest
|
||||
} = this.props;
|
||||
const { loading, hasTwoCNChar } = this.state;
|
||||
|
||||
warning(
|
||||
!(typeof icon === 'string' && icon.length > 2),
|
||||
'Button',
|
||||
`\`icon\` is using ReactNode instead of string naming in v4. Please check \`${icon}\` at https://ant.design/components/icon`,
|
||||
);
|
||||
|
||||
const prefixCls = getPrefixCls('btn', customizePrefixCls);
|
||||
const autoInsertSpace = autoInsertSpaceInButton !== false;
|
||||
|
||||
// large => lg
|
||||
// small => sm
|
||||
let sizeCls = '';
|
||||
switch (customizeSize || size) {
|
||||
case 'large':
|
||||
sizeCls = 'lg';
|
||||
break;
|
||||
case 'small':
|
||||
sizeCls = 'sm';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
const iconType = loading ? 'loading' : icon;
|
||||
|
||||
const classes = classNames(prefixCls, className, {
|
||||
[`${prefixCls}-${type}`]: type,
|
||||
[`${prefixCls}-${shape}`]: shape,
|
||||
[`${prefixCls}-${sizeCls}`]: sizeCls,
|
||||
[`${prefixCls}-icon-only`]: !children && children !== 0 && iconType,
|
||||
[`${prefixCls}-background-ghost`]: ghost,
|
||||
[`${prefixCls}-loading`]: loading,
|
||||
[`${prefixCls}-two-chinese-chars`]: hasTwoCNChar && autoInsertSpace,
|
||||
[`${prefixCls}-block`]: block,
|
||||
[`${prefixCls}-dangerous`]: !!danger,
|
||||
[`${prefixCls}-rtl`]: direction === 'rtl',
|
||||
});
|
||||
|
||||
const iconNode =
|
||||
icon && !loading ? (
|
||||
icon
|
||||
) : (
|
||||
<LoadingIcon existIcon={!!icon} prefixCls={prefixCls} loading={loading} />
|
||||
);
|
||||
|
||||
const kids =
|
||||
children || children === 0
|
||||
? spaceChildren(children, this.isNeedInserted() && autoInsertSpace)
|
||||
: null;
|
||||
|
||||
const linkButtonRestProps = omit(rest as AnchorButtonProps, ['htmlType', 'loading']);
|
||||
if (linkButtonRestProps.href !== undefined) {
|
||||
return (
|
||||
<a
|
||||
{...linkButtonRestProps}
|
||||
className={classes}
|
||||
onClick={this.handleClick}
|
||||
ref={this.saveButtonRef}
|
||||
>
|
||||
{iconNode}
|
||||
{kids}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
// React does not recognize the `htmlType` prop on a DOM element. Here we pick it out of `rest`.
|
||||
const { htmlType, ...otherProps } = rest as NativeButtonProps;
|
||||
|
||||
const buttonNode = (
|
||||
<button
|
||||
{...(omit(otherProps, ['loading']) as NativeButtonProps)}
|
||||
type={htmlType}
|
||||
className={classes}
|
||||
onClick={this.handleClick}
|
||||
ref={this.saveButtonRef}
|
||||
>
|
||||
{iconNode}
|
||||
{kids}
|
||||
</button>
|
||||
);
|
||||
|
||||
if (type === 'link') {
|
||||
return buttonNode;
|
||||
}
|
||||
|
||||
return <Wave>{buttonNode}</Wave>;
|
||||
}}
|
||||
</SizeContext.Consumer>
|
||||
<a {...linkButtonRestProps} className={classes} onClick={handleClick} ref={buttonRef}>
|
||||
{iconNode}
|
||||
{kids}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// React does not recognize the `htmlType` prop on a DOM element. Here we pick it out of `rest`.
|
||||
const { htmlType, ...otherProps } = rest as NativeButtonProps;
|
||||
|
||||
const buttonNode = (
|
||||
<button
|
||||
{...(omit(otherProps, ['loading']) as NativeButtonProps)}
|
||||
type={htmlType}
|
||||
className={classes}
|
||||
onClick={handleClick}
|
||||
ref={buttonRef}
|
||||
>
|
||||
{iconNode}
|
||||
{kids}
|
||||
</button>
|
||||
);
|
||||
|
||||
if (type === 'link') {
|
||||
return buttonNode;
|
||||
}
|
||||
|
||||
return <Wave>{buttonNode}</Wave>;
|
||||
};
|
||||
|
||||
const Button = React.forwardRef<unknown, ButtonProps>(InternalButton) as CompoundedComponent;
|
||||
|
||||
Button.displayName = 'Button';
|
||||
|
||||
Button.defaultProps = {
|
||||
loading: false,
|
||||
ghost: false,
|
||||
block: false,
|
||||
htmlType: 'button' as ButtonProps['htmlType'],
|
||||
};
|
||||
|
||||
Button.Group = Group;
|
||||
Button.__ANT_BUTTON = true;
|
||||
|
||||
export default Button;
|
||||
|
||||
@@ -15,7 +15,8 @@ Debug usage
|
||||
Debug usage
|
||||
|
||||
```jsx
|
||||
import { Button } from 'antd';
|
||||
import { Button, Tooltip } from 'antd';
|
||||
import { DownloadOutlined } from '@ant-design/icons';
|
||||
|
||||
function getGroup(props) {
|
||||
return (
|
||||
@@ -23,6 +24,12 @@ function getGroup(props) {
|
||||
<Button.Group {...props}>
|
||||
<Button type="primary">Button 1</Button>
|
||||
<Button type="primary">Button 2</Button>
|
||||
<Tooltip title="Tooltip">
|
||||
<Button type="primary" icon={<DownloadOutlined />} disabled />
|
||||
</Tooltip>
|
||||
<Tooltip title="Tooltip">
|
||||
<Button type="primary" icon={<DownloadOutlined />} />
|
||||
</Tooltip>
|
||||
</Button.Group>
|
||||
</div>
|
||||
);
|
||||
@@ -31,9 +38,17 @@ function getGroup(props) {
|
||||
ReactDOM.render(
|
||||
<div>
|
||||
{getGroup({ size: 'small' })}
|
||||
<br />
|
||||
{getGroup()}
|
||||
<br />
|
||||
{getGroup({ size: 'large' })}
|
||||
</div>,
|
||||
mountNode,
|
||||
);
|
||||
```
|
||||
|
||||
```css
|
||||
#components-button-demo-legacy-group .ant-btn {
|
||||
margin: 0;
|
||||
}
|
||||
```
|
||||
|
||||
@@ -19,46 +19,51 @@ import { PoweroffOutlined } from '@ant-design/icons';
|
||||
|
||||
class App extends React.Component {
|
||||
state = {
|
||||
loading: false,
|
||||
iconLoading: false,
|
||||
loadings: [],
|
||||
};
|
||||
|
||||
enterLoading = () => {
|
||||
this.setState({ loading: true });
|
||||
enterLoading = index => {
|
||||
const newLoadings = [...this.state.loadings];
|
||||
newLoadings[index] = true;
|
||||
this.setState({
|
||||
loadings: newLoadings,
|
||||
});
|
||||
setTimeout(() => {
|
||||
this.setState({ loading: false });
|
||||
}, 8000);
|
||||
};
|
||||
|
||||
enterIconLoading = () => {
|
||||
this.setState({ iconLoading: true });
|
||||
setTimeout(() => {
|
||||
this.setState({ iconLoading: false });
|
||||
}, 8000);
|
||||
newLoadings[index] = false;
|
||||
this.setState({ loadings: newLoadings });
|
||||
}, 6000);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { loadings } = this.state;
|
||||
return (
|
||||
<div>
|
||||
<>
|
||||
<Button type="primary" loading>
|
||||
Loading
|
||||
</Button>
|
||||
<Button type="primary" size="small" loading>
|
||||
Loading
|
||||
</Button>
|
||||
<Button type="primary" icon={<PoweroffOutlined />} loading />
|
||||
<br />
|
||||
<Button type="primary" loading={this.state.loading} onClick={this.enterLoading}>
|
||||
<Button type="primary" loading={loadings[0]} onClick={() => this.enterLoading(0)}>
|
||||
Click me!
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PoweroffOutlined />}
|
||||
loading={this.state.iconLoading}
|
||||
onClick={this.enterIconLoading}
|
||||
loading={loadings[1]}
|
||||
onClick={() => this.enterLoading(1)}
|
||||
>
|
||||
Click me!
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PoweroffOutlined />}
|
||||
loading={loadings[2]}
|
||||
onClick={() => this.enterLoading(2)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,12 +54,15 @@ Following the Ant Design specification, we will add one space between if Button
|
||||
|
||||
<img src="https://gw.alipayobjects.com/zos/antfincdn/MY%26THAPZrW/38f06cb9-293a-4b42-b183-9f443e79ffea.png" style="box-shadow: none; margin: 0; width: 100px" alt="Button with two Chinese characters" />
|
||||
|
||||
|
||||
<style>
|
||||
[id^=components-button-demo-] .ant-btn {
|
||||
margin-right: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
[id^="components-button-demo-"] .ant-btn-rtl {
|
||||
margin-right: 0;
|
||||
margin-left: 8px;
|
||||
}
|
||||
[id^=components-button-demo-] .ant-btn-group > .ant-btn,
|
||||
[id^=components-button-demo-] .ant-btn-group > span > .ant-btn {
|
||||
margin-right: 0;
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import Button from './button';
|
||||
import ButtonGroup from './button-group';
|
||||
|
||||
export { ButtonProps, ButtonShape, ButtonType } from './button';
|
||||
export { ButtonGroupProps } from './button-group';
|
||||
export { SizeType as ButtonSize } from '../config-provider/SizeContext';
|
||||
|
||||
Button.Group = ButtonGroup;
|
||||
export default Button;
|
||||
|
||||
@@ -11,7 +11,7 @@ subtitle: 按钮
|
||||
|
||||
标记了一个(或封装一组)操作命令,响应用户点击行为,触发相应的业务逻辑。
|
||||
|
||||
在 Ant Design 中,我们有四种按钮。
|
||||
在 Ant Design 中我们提供了四种按钮。
|
||||
|
||||
- 主按钮:用于主行动点,一个操作区域只能有一个主按钮。
|
||||
- 默认按钮:用于没有主次之分的一组行动点。
|
||||
@@ -62,6 +62,10 @@ subtitle: 按钮
|
||||
margin-right: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
[id^="components-button-demo-"] .ant-btn-rtl {
|
||||
margin-right: 0;
|
||||
margin-left: 8px;
|
||||
}
|
||||
[id^="components-button-demo-"] .ant-btn-group > .ant-btn {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
@@ -94,10 +94,7 @@
|
||||
|
||||
&-icon-only {
|
||||
.btn-square(@btn-prefix-cls);
|
||||
|
||||
> i {
|
||||
vertical-align: middle;
|
||||
}
|
||||
vertical-align: -0.5px;
|
||||
}
|
||||
|
||||
&-round {
|
||||
@@ -158,6 +155,12 @@
|
||||
.@{iconfont-css-prefix} {
|
||||
padding-right: @margin-xs;
|
||||
}
|
||||
|
||||
&:only-child {
|
||||
.@{iconfont-css-prefix} {
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-group {
|
||||
|
||||
@@ -165,7 +165,7 @@
|
||||
}
|
||||
.button-group-base(@btnClassName) {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
display: inline-flex;
|
||||
> .@{btnClassName},
|
||||
> span > .@{btnClassName} {
|
||||
position: relative;
|
||||
@@ -179,7 +179,7 @@
|
||||
z-index: 0;
|
||||
}
|
||||
}
|
||||
> .@{btnClassName}-icon-only {
|
||||
.@{btnClassName}-icon-only {
|
||||
font-size: @font-size-base;
|
||||
}
|
||||
// size
|
||||
@@ -187,7 +187,7 @@
|
||||
&-lg > span > .@{btnClassName} {
|
||||
.button-size(@btn-height-lg; @btn-padding-horizontal-lg; @btn-font-size-lg; 0);
|
||||
}
|
||||
&-lg > .@{btnClassName}.@{btnClassName}-icon-only {
|
||||
&-lg .@{btnClassName}.@{btnClassName}-icon-only {
|
||||
.square(@btn-height-lg);
|
||||
padding-right: 0;
|
||||
padding-left: 0;
|
||||
@@ -199,7 +199,7 @@
|
||||
font-size: @font-size-base;
|
||||
}
|
||||
}
|
||||
&-sm > .@{btnClassName}.@{btnClassName}-icon-only {
|
||||
&-sm .@{btnClassName}.@{btnClassName}-icon-only {
|
||||
.square(@btn-height-sm);
|
||||
padding-right: 0;
|
||||
padding-left: 0;
|
||||
|
||||
@@ -24,30 +24,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
&&-loading:not(&-circle):not(&-circle-outline):not(&-icon-only) {
|
||||
.@{btn-prefix-cls}-rtl& {
|
||||
padding-right: 29px;
|
||||
padding-left: @padding-md - 1px;
|
||||
}
|
||||
|
||||
.@{iconfont-css-prefix}:not(:last-child) {
|
||||
.@{btn-prefix-cls}-rtl& {
|
||||
margin-right: -14px;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-sm&-loading:not(&-circle):not(&-circle-outline):not(&-icon-only) {
|
||||
.@{btn-prefix-cls}-rtl& {
|
||||
padding-right: 24px;
|
||||
padding-left: @padding-xs - 1px;
|
||||
}
|
||||
|
||||
& > &-loading-icon {
|
||||
.@{iconfont-css-prefix} {
|
||||
.@{btn-prefix-cls}-rtl& {
|
||||
margin-right: -17px;
|
||||
margin-left: 0;
|
||||
padding-right: 0;
|
||||
padding-left: @margin-xs;
|
||||
}
|
||||
}
|
||||
|
||||
&:only-child {
|
||||
.@{iconfont-css-prefix} {
|
||||
padding-right: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -942,7 +942,7 @@ exports[`Calendar Calendar should support locale 1`] = `
|
||||
|
||||
exports[`Calendar rtl render component should be rendered correctly in RTL direction 1`] = `
|
||||
<div
|
||||
class="ant-picker-calendar ant-picker-calendar-full"
|
||||
class="ant-picker-calendar ant-picker-calendar-full ant-picker-calendar-rtl"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-calendar-header"
|
||||
|
||||
@@ -98,7 +98,7 @@ function generateCalendar<DateType>(generateConfig: GenerateConfig<DateType>) {
|
||||
onPanelChange,
|
||||
onSelect,
|
||||
} = props;
|
||||
const { getPrefixCls } = React.useContext(ConfigContext);
|
||||
const { getPrefixCls, direction } = React.useContext(ConfigContext);
|
||||
const prefixCls = getPrefixCls('picker', customizePrefixCls);
|
||||
const calendarPrefixCls = `${prefixCls}-calendar`;
|
||||
const today = generateConfig.getNow();
|
||||
@@ -239,6 +239,7 @@ function generateCalendar<DateType>(generateConfig: GenerateConfig<DateType>) {
|
||||
className={classNames(calendarPrefixCls, className, {
|
||||
[`${calendarPrefixCls}-full`]: fullscreen,
|
||||
[`${calendarPrefixCls}-mini`]: !fullscreen,
|
||||
[`${calendarPrefixCls}-rtl`]: direction === 'rtl',
|
||||
})}
|
||||
style={style}
|
||||
>
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
justify-content: flex-end;
|
||||
padding: @padding-sm 0;
|
||||
|
||||
|
||||
.@{calendar-prefix-cls}-year-select {
|
||||
min-width: 80px;
|
||||
}
|
||||
@@ -190,3 +189,4 @@
|
||||
}
|
||||
}
|
||||
|
||||
@import './rtl';
|
||||
|
||||
44
components/calendar/style/rtl.less
Normal file
44
components/calendar/style/rtl.less
Normal file
@@ -0,0 +1,44 @@
|
||||
@import '../../style/themes/index';
|
||||
@import '../../style/mixins/index';
|
||||
|
||||
@calendar-prefix-cls: ~'@{ant-prefix}-picker-calendar';
|
||||
@calendar-picker-prefix-cls: ~'@{ant-prefix}-picker';
|
||||
|
||||
.@{calendar-prefix-cls} {
|
||||
&-rtl {
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
&-header {
|
||||
.@{calendar-prefix-cls}-month-select {
|
||||
.@{calendar-prefix-cls}-rtl & {
|
||||
margin-right: @padding-xs;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.@{calendar-prefix-cls}-mode-switch {
|
||||
.@{calendar-prefix-cls}-rtl & {
|
||||
margin-right: @padding-xs;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ========================== Full ==========================
|
||||
&-full {
|
||||
.@{calendar-picker-prefix-cls}-panel {
|
||||
.@{calendar-prefix-cls}-rtl& {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.@{calendar-picker-prefix-cls}-body {
|
||||
th {
|
||||
.@{calendar-prefix-cls}-rtl& {
|
||||
padding: 0 0 5px 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,8 +23,8 @@ A card can be used to display content related to a single subject. The content c
|
||||
| --- | --- | --- | --- | --- |
|
||||
| actions | The action list, shows at the bottom of the Card. | Array<ReactNode> | - | |
|
||||
| activeTabKey | Current TabPane's key | string | - | |
|
||||
| headStyle | Inline style to apply to the card head | object | - | |
|
||||
| bodyStyle | Inline style to apply to the card content | object | - | |
|
||||
| headStyle | Inline style to apply to the card head | CSSProperties | - | |
|
||||
| bodyStyle | Inline style to apply to the card content | CSSProperties | - | |
|
||||
| bordered | Toggles rendering of the border around the card | boolean | `true` | |
|
||||
| cover | Card cover | ReactNode | - | |
|
||||
| defaultActiveTabKey | Initial active TabPane's key, if `activeTabKey` is not set. | string | - | |
|
||||
@@ -37,22 +37,22 @@ A card can be used to display content related to a single subject. The content c
|
||||
| title | Card title | string\|ReactNode | - | |
|
||||
| type | Card style type, can be set to `inner` or not set | string | - | |
|
||||
| onTabChange | Callback when tab is switched | (key) => void | - | |
|
||||
| tabProps | [Tabs](https://ant.design/components/tabs/#Tabs) | - | - | |
|
||||
| tabProps | [Tabs](/components/tabs/#Tabs) | - | - | |
|
||||
|
||||
### Card.Grid
|
||||
|
||||
| Property | Description | Type | Default | Version |
|
||||
| --------- | ------------------------------- | ------- | ------- | ------- |
|
||||
| className | className of container | string | - | |
|
||||
| hoverable | Lift up when hovering card grid | boolean | true | |
|
||||
| style | style object of container | object | - | |
|
||||
| Property | Description | Type | Default | Version |
|
||||
| --------- | ------------------------------- | ------------- | ------- | ------- |
|
||||
| className | className of container | string | - | |
|
||||
| hoverable | Lift up when hovering card grid | boolean | true | |
|
||||
| style | style object of container | CSSProperties | - | |
|
||||
|
||||
### Card.Meta
|
||||
|
||||
| Property | Description | Type | Default | Version |
|
||||
| ----------- | ------------------------- | --------- | ------- | ------- |
|
||||
| avatar | avatar or icon | ReactNode | - | |
|
||||
| className | className of container | string | - | |
|
||||
| description | description content | ReactNode | - | |
|
||||
| style | style object of container | object | - | |
|
||||
| title | title content | ReactNode | - | |
|
||||
| Property | Description | Type | Default | Version |
|
||||
| ----------- | ------------------------- | ------------- | ------- | ------- |
|
||||
| avatar | avatar or icon | ReactNode | - | |
|
||||
| className | className of container | string | - | |
|
||||
| description | description content | ReactNode | - | |
|
||||
| style | style object of container | CSSProperties | - | |
|
||||
| title | title content | ReactNode | - | |
|
||||
|
||||
@@ -24,8 +24,8 @@ cols: 1
|
||||
| --- | --- | --- | --- | --- |
|
||||
| actions | 卡片操作组,位置在卡片底部 | Array<ReactNode> | - | |
|
||||
| activeTabKey | 当前激活页签的 key | string | - | |
|
||||
| headStyle | 自定义标题区域样式 | object | - | |
|
||||
| bodyStyle | 内容区域自定义样式 | object | - | |
|
||||
| headStyle | 自定义标题区域样式 | CSSProperties | - | |
|
||||
| bodyStyle | 内容区域自定义样式 | CSSProperties | - | |
|
||||
| bordered | 是否有边框 | boolean | true | |
|
||||
| cover | 卡片封面 | ReactNode | - | |
|
||||
| defaultActiveTabKey | 初始化选中页签的 key,如果没有设置 activeTabKey | string | 第一个页签 | |
|
||||
@@ -38,22 +38,22 @@ cols: 1
|
||||
| title | 卡片标题 | string\|ReactNode | - | |
|
||||
| type | 卡片类型,可设置为 `inner` 或 不设置 | string | - | |
|
||||
| onTabChange | 页签切换的回调 | (key) => void | - | |
|
||||
| tabProps | [Tabs](https://ant.design/components/tabs-cn/#Tabs) | - | - | |
|
||||
| tabProps | [Tabs](/components/tabs/#Tabs) | - | - | |
|
||||
|
||||
### Card.Grid
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
| --------- | ---------------------- | ------- | ------ | ---- |
|
||||
| className | 网格容器类名 | string | - | |
|
||||
| hoverable | 鼠标移过时可浮起 | boolean | true | |
|
||||
| style | 定义网格容器类名的样式 | object | - | |
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
| --------- | ---------------------- | ------------- | ------ | ---- |
|
||||
| className | 网格容器类名 | string | - | |
|
||||
| hoverable | 鼠标移过时可浮起 | boolean | true | |
|
||||
| style | 定义网格容器类名的样式 | CSSProperties | - | |
|
||||
|
||||
### Card.Meta
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
| ----------- | ------------------ | --------- | ------ | ---- |
|
||||
| avatar | 头像/图标 | ReactNode | - | |
|
||||
| className | 容器类名 | string | - | |
|
||||
| description | 描述内容 | ReactNode | - | |
|
||||
| style | 定义容器类名的样式 | object | - | |
|
||||
| title | 标题内容 | ReactNode | - | |
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
| ----------- | ------------------ | ------------- | ------ | ---- |
|
||||
| avatar | 头像/图标 | ReactNode | - | |
|
||||
| className | 容器类名 | string | - | |
|
||||
| description | 描述内容 | ReactNode | - | |
|
||||
| style | 定义容器类名的样式 | CSSProperties | - | |
|
||||
| title | 标题内容 | ReactNode | - | |
|
||||
|
||||
@@ -166,7 +166,7 @@
|
||||
display: block;
|
||||
min-width: 32px;
|
||||
font-size: @font-size-base;
|
||||
line-height: 22px;
|
||||
line-height: @line-height-base;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
|
||||
@@ -451,7 +451,7 @@ exports[`renders ./components/carousel/demo/fade.md correctly 1`] = `
|
||||
aria-hidden="false"
|
||||
class="slick-slide slick-active slick-current"
|
||||
data-index="0"
|
||||
style="outline:none;width:11.11111111111111%;position:relative;left:0;opacity:1;transition:opacity 500ms ease, visibility 500ms ease;-webkit-transition:opacity 500ms ease, visibility 500ms ease"
|
||||
style="outline:none;width:11.11111111111111%;position:relative;left:0;opacity:1;transition:opacity 500ms ease, visibility 500ms ease"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div>
|
||||
@@ -469,7 +469,7 @@ exports[`renders ./components/carousel/demo/fade.md correctly 1`] = `
|
||||
aria-hidden="true"
|
||||
class="slick-slide"
|
||||
data-index="1"
|
||||
style="outline:none;width:11.11111111111111%;position:relative;left:-11px;opacity:0;transition:opacity 500ms ease, visibility 500ms ease;-webkit-transition:opacity 500ms ease, visibility 500ms ease"
|
||||
style="outline:none;width:11.11111111111111%;position:relative;left:-11px;opacity:0;transition:opacity 500ms ease, visibility 500ms ease"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div>
|
||||
@@ -487,7 +487,7 @@ exports[`renders ./components/carousel/demo/fade.md correctly 1`] = `
|
||||
aria-hidden="true"
|
||||
class="slick-slide"
|
||||
data-index="2"
|
||||
style="outline:none;width:11.11111111111111%;position:relative;left:-22px;opacity:0;transition:opacity 500ms ease, visibility 500ms ease;-webkit-transition:opacity 500ms ease, visibility 500ms ease"
|
||||
style="outline:none;width:11.11111111111111%;position:relative;left:-22px;opacity:0;transition:opacity 500ms ease, visibility 500ms ease"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div>
|
||||
@@ -505,7 +505,7 @@ exports[`renders ./components/carousel/demo/fade.md correctly 1`] = `
|
||||
aria-hidden="true"
|
||||
class="slick-slide"
|
||||
data-index="3"
|
||||
style="outline:none;width:11.11111111111111%;position:relative;left:-33px;opacity:0;transition:opacity 500ms ease, visibility 500ms ease;-webkit-transition:opacity 500ms ease, visibility 500ms ease"
|
||||
style="outline:none;width:11.11111111111111%;position:relative;left:-33px;opacity:0;transition:opacity 500ms ease, visibility 500ms ease"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div>
|
||||
|
||||
@@ -32,4 +32,4 @@ A carousel component. Scales with its container.
|
||||
| next() | Change current slide to next slide |
|
||||
| prev() | Change current slide to previous slide |
|
||||
|
||||
For more info on the parameters, refer to the <https://github.com/akiran/react-slick>
|
||||
Find more APIs in react-slick [documentation](https://react-slick.neostack.com/docs/api).
|
||||
|
||||
@@ -33,4 +33,4 @@ subtitle: 走马灯
|
||||
| next() | 切换到下一面板 |
|
||||
| prev() | 切换到上一面板 |
|
||||
|
||||
更多参数可参考:<https://github.com/akiran/react-slick>
|
||||
更多 API 可参考:<https://react-slick.neostack.com/docs/api>
|
||||
|
||||
@@ -33,8 +33,19 @@
|
||||
.slick-slide {
|
||||
pointer-events: none;
|
||||
|
||||
// https://github.com/ant-design/ant-design/issues/23294
|
||||
input.@{ant-prefix}-radio-input,
|
||||
input.@{ant-prefix}-checkbox-input {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
&.slick-active {
|
||||
pointer-events: auto;
|
||||
|
||||
input.@{ant-prefix}-radio-input,
|
||||
input.@{ant-prefix}-checkbox-input {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ Cascade selection box.
|
||||
| popupVisible | set visible of cascader popup | boolean | - | |
|
||||
| showSearch | Whether show search input in single mode. | boolean\|object | false | |
|
||||
| size | input size | `large` \| `middle` \| `small` | | |
|
||||
| style | additional style | string | - | |
|
||||
| style | additional style | CSSProperties | - | |
|
||||
| suffixIcon | The custom suffix icon | ReactNode | - | |
|
||||
| value | selected value | string\[] | - | |
|
||||
| onChange | callback when finishing cascader select | `(value, selectedOptions) => void` | - | |
|
||||
|
||||
@@ -41,7 +41,7 @@ subtitle: 级联选择
|
||||
| popupVisible | 控制浮层显隐 | boolean | - | |
|
||||
| showSearch | 在选择框中显示搜索框 | boolean | false | |
|
||||
| size | 输入框大小 | `large` \| `middle` \| `small` | 无 | |
|
||||
| style | 自定义样式 | string | - | |
|
||||
| style | 自定义样式 | CSSProperties | - | |
|
||||
| suffixIcon | 自定义的选择框后缀图标 | ReactNode | - | |
|
||||
| value | 指定选中项 | string\[] | - | |
|
||||
| onChange | 选择完成后的回调 | `(value, selectedOptions) => void` | - | |
|
||||
|
||||
@@ -63,7 +63,7 @@ class Checkbox extends React.PureComponent<CheckboxProps, {}> {
|
||||
warning(
|
||||
'checked' in this.props || this.context || !('value' in this.props),
|
||||
'Checkbox',
|
||||
'`value` is not validate prop, do you mean `checked`?',
|
||||
'`value` is not a valid prop, do you mean `checked`?',
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import * as React from 'react';
|
||||
import * as PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import omit from 'omit.js';
|
||||
import Checkbox, { CheckboxChangeEvent } from './Checkbox';
|
||||
@@ -48,13 +47,6 @@ class CheckboxGroup extends React.PureComponent<CheckboxGroupProps, CheckboxGrou
|
||||
options: [],
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
defaultValue: PropTypes.array,
|
||||
value: PropTypes.array,
|
||||
options: PropTypes.array.isRequired,
|
||||
onChange: PropTypes.func,
|
||||
};
|
||||
|
||||
static getDerivedStateFromProps(nextProps: CheckboxGroupProps) {
|
||||
if ('value' in nextProps) {
|
||||
return {
|
||||
|
||||
@@ -30,7 +30,7 @@ describe('Checkbox', () => {
|
||||
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
mount(<Checkbox value />);
|
||||
expect(errorSpy).toHaveBeenCalledWith(
|
||||
'Warning: [antd: Checkbox] `value` is not validate prop, do you mean `checked`?',
|
||||
'Warning: [antd: Checkbox] `value` is not a valid prop, do you mean `checked`?',
|
||||
);
|
||||
errorSpy.mockRestore();
|
||||
});
|
||||
|
||||
@@ -171,7 +171,7 @@
|
||||
display: inline-block;
|
||||
&-item {
|
||||
display: inline-block;
|
||||
margin-right: 8px;
|
||||
margin-right: @checkbox-group-item-margin-right;
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import CollapsePanel from './CollapsePanel';
|
||||
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
|
||||
import animation from '../_util/openAnimation';
|
||||
|
||||
export type ExpandIconPosition = 'left' | 'right';
|
||||
export type ExpandIconPosition = 'left' | 'right' | undefined;
|
||||
|
||||
export interface CollapseProps {
|
||||
activeKey?: Array<string | number> | string | number;
|
||||
@@ -40,9 +40,16 @@ export default class Collapse extends React.Component<CollapseProps, any> {
|
||||
|
||||
static defaultProps = {
|
||||
bordered: true,
|
||||
expandIconPosition: 'left' as CollapseProps['expandIconPosition'],
|
||||
};
|
||||
|
||||
getIconPosition(direction: string = 'ltr') {
|
||||
const { expandIconPosition } = this.props;
|
||||
if (expandIconPosition !== undefined) {
|
||||
return expandIconPosition;
|
||||
}
|
||||
return direction === 'rtl' ? 'right' : 'left';
|
||||
}
|
||||
|
||||
renderExpandIcon = (panelProps: PanelProps = {}, prefixCls: string) => {
|
||||
const { expandIcon } = this.props;
|
||||
const icon = (expandIcon ? (
|
||||
@@ -59,17 +66,13 @@ export default class Collapse extends React.Component<CollapseProps, any> {
|
||||
};
|
||||
|
||||
renderCollapse = ({ getPrefixCls, direction }: ConfigConsumerProps) => {
|
||||
const {
|
||||
prefixCls: customizePrefixCls,
|
||||
className = '',
|
||||
bordered,
|
||||
expandIconPosition,
|
||||
} = this.props;
|
||||
const { prefixCls: customizePrefixCls, className = '', bordered } = this.props;
|
||||
const prefixCls = getPrefixCls('collapse', customizePrefixCls);
|
||||
const iconPosition = this.getIconPosition(direction);
|
||||
const collapseClassName = classNames(
|
||||
{
|
||||
[`${prefixCls}-borderless`]: !bordered,
|
||||
[`${prefixCls}-icon-position-${expandIconPosition}`]: true,
|
||||
[`${prefixCls}-icon-position-${iconPosition}`]: true,
|
||||
[`${prefixCls}-rtl`]: direction === 'rtl',
|
||||
},
|
||||
className,
|
||||
|
||||
@@ -658,6 +658,7 @@ exports[`renders ./components/collapse/demo/extra.md correctly 1`] = `
|
||||
</span>
|
||||
<div
|
||||
class="ant-select ant-select-single ant-select-show-arrow"
|
||||
style="margin:0 8px"
|
||||
>
|
||||
<div
|
||||
class="ant-select-selector"
|
||||
|
||||
@@ -51,7 +51,7 @@ exports[`Collapse could override default openAnimation 1`] = `
|
||||
|
||||
exports[`Collapse rtl render component should be rendered correctly in RTL direction 1`] = `
|
||||
<div
|
||||
class="ant-collapse ant-collapse-icon-position-left ant-collapse-rtl"
|
||||
class="ant-collapse ant-collapse-icon-position-right ant-collapse-rtl"
|
||||
/>
|
||||
`;
|
||||
|
||||
|
||||
@@ -69,7 +69,11 @@ class Demo extends React.Component {
|
||||
</Collapse>
|
||||
<br />
|
||||
<span>Expand Icon Position: </span>
|
||||
<Select value={expandIconPosition} onChange={this.onPositionChange}>
|
||||
<Select
|
||||
value={expandIconPosition}
|
||||
style={{ margin: '0 8px' }}
|
||||
onChange={this.onPositionChange}
|
||||
>
|
||||
<Option value="left">left</Option>
|
||||
<Option value="right">right</Option>
|
||||
</Select>
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
padding: @collapse-header-padding;
|
||||
padding-left: @collapse-header-padding-extra;
|
||||
color: @heading-color;
|
||||
line-height: 22px;
|
||||
line-height: @line-height-base;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
.clearfix;
|
||||
@@ -36,7 +36,7 @@
|
||||
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: @padding-md;
|
||||
left: @collapse-header-arrow-left;
|
||||
display: inline-block;
|
||||
font-size: @font-size-sm;
|
||||
transform: translateY(-50%);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user