mirror of
https://gitee.com/ant-design/ant-design.git
synced 2024-12-04 21:18:01 +08:00
Merge pull request #29428 from ant-design/master-to-merge-feature
chore: merge master into feature
This commit is contained in:
commit
1c90bf8010
29
.eslintrc.js
29
.eslintrc.js
@ -5,7 +5,7 @@ module.exports = {
|
|||||||
'plugin:jest/recommended',
|
'plugin:jest/recommended',
|
||||||
'plugin:react/recommended',
|
'plugin:react/recommended',
|
||||||
'plugin:import/typescript',
|
'plugin:import/typescript',
|
||||||
'prettier/react',
|
'plugin:markdown/recommended',
|
||||||
],
|
],
|
||||||
env: {
|
env: {
|
||||||
browser: true,
|
browser: true,
|
||||||
@ -20,7 +20,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
parser: '@typescript-eslint/parser',
|
parser: '@typescript-eslint/parser',
|
||||||
plugins: ['markdown', 'react', 'babel', 'jest', '@typescript-eslint', 'react-hooks', 'unicorn'],
|
plugins: ['react', 'babel', 'jest', '@typescript-eslint', 'react-hooks', 'unicorn', 'markdown'],
|
||||||
// https://github.com/typescript-eslint/typescript-eslint/issues/46#issuecomment-470486034
|
// https://github.com/typescript-eslint/typescript-eslint/issues/46#issuecomment-470486034
|
||||||
overrides: [
|
overrides: [
|
||||||
{
|
{
|
||||||
@ -32,7 +32,28 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
files: ['*.md'],
|
// In v2, explicitly apply eslint-plugin-markdown's `markdown`
|
||||||
|
// processor on any Markdown files you want to lint.
|
||||||
|
files: ['components/*/demo/*.md'],
|
||||||
|
processor: 'markdown/markdown',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// In v2, configuration for fenced code blocks is separate from the
|
||||||
|
// containing Markdown file. Each code block has a virtual filename
|
||||||
|
// appended to the Markdown file's path.
|
||||||
|
files: [
|
||||||
|
'components/*/demo/*.md/*.ts',
|
||||||
|
'components/*/demo/*.md/*.tsx',
|
||||||
|
'components/*/demo/*.md/*.js',
|
||||||
|
'components/*/demo/*.md/*.jsx',
|
||||||
|
],
|
||||||
|
// Configuration for fenced code blocks goes with the override for
|
||||||
|
// the code block's virtual filename, for example:
|
||||||
|
parserOptions: {
|
||||||
|
ecmaFeatures: {
|
||||||
|
impliedStrict: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
globals: {
|
globals: {
|
||||||
React: true,
|
React: true,
|
||||||
ReactDOM: true,
|
ReactDOM: true,
|
||||||
@ -44,10 +65,12 @@ module.exports = {
|
|||||||
'no-plusplus': 0,
|
'no-plusplus': 0,
|
||||||
'eol-last': 0,
|
'eol-last': 0,
|
||||||
'no-script-url': 0,
|
'no-script-url': 0,
|
||||||
|
'default-case': 0,
|
||||||
'prefer-rest-params': 0,
|
'prefer-rest-params': 0,
|
||||||
'react/no-access-state-in-setstate': 0,
|
'react/no-access-state-in-setstate': 0,
|
||||||
'react/destructuring-assignment': 0,
|
'react/destructuring-assignment': 0,
|
||||||
'react/no-multi-comp': 0,
|
'react/no-multi-comp': 0,
|
||||||
|
'react/no-array-index-key': 0,
|
||||||
'jsx-a11y/href-no-hash': 0,
|
'jsx-a11y/href-no-hash': 0,
|
||||||
'import/no-extraneous-dependencies': 0,
|
'import/no-extraneous-dependencies': 0,
|
||||||
'jsx-a11y/control-has-associated-label': 0,
|
'jsx-a11y/control-has-associated-label': 0,
|
||||||
|
26
.github/workflows/sync-gitee.yml
vendored
Normal file
26
.github/workflows/sync-gitee.yml
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
name: 🔀 Sync mirror to Gitee
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- gh-pages
|
||||||
|
- feature
|
||||||
|
- 2.x-stable
|
||||||
|
- 3.x-stable
|
||||||
|
create:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
mirror:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.repository == 'ant-design/ant-design'
|
||||||
|
steps:
|
||||||
|
- name: mirror
|
||||||
|
continue-on-error: true
|
||||||
|
if: github.event_name == 'push' || (github.event_name == 'create' && github.event.ref_type == 'tag')
|
||||||
|
uses: wearerequired/git-mirror-action@v1
|
||||||
|
env:
|
||||||
|
SSH_PRIVATE_KEY: ${{ secrets.GITEE_SSH_PRIVATE_KEY }}
|
||||||
|
with:
|
||||||
|
source-repo: 'git@github.com:ant-design/ant-design.git'
|
||||||
|
destination-repo: 'git@gitee.com:ant-design/ant-design.git'
|
2
.jest.js
2
.jest.js
@ -30,7 +30,7 @@ module.exports = {
|
|||||||
testPathIgnorePatterns: ['/node_modules/', 'dekko', 'node', 'image.test.js', 'image.test.ts'],
|
testPathIgnorePatterns: ['/node_modules/', 'dekko', 'node', 'image.test.js', 'image.test.ts'],
|
||||||
transform: {
|
transform: {
|
||||||
'\\.tsx?$': './node_modules/@ant-design/tools/lib/jest/codePreprocessor',
|
'\\.tsx?$': './node_modules/@ant-design/tools/lib/jest/codePreprocessor',
|
||||||
'\\.js$': './node_modules/@ant-design/tools/lib/jest/codePreprocessor',
|
'\\.(m?)js$': './node_modules/@ant-design/tools/lib/jest/codePreprocessor',
|
||||||
'\\.md$': './node_modules/@ant-design/tools/lib/jest/demoPreprocessor',
|
'\\.md$': './node_modules/@ant-design/tools/lib/jest/demoPreprocessor',
|
||||||
'\\.(jpg|png|gif|svg)$': './node_modules/@ant-design/tools/lib/jest/imagePreprocessor',
|
'\\.(jpg|png|gif|svg)$': './node_modules/@ant-design/tools/lib/jest/imagePreprocessor',
|
||||||
},
|
},
|
||||||
|
@ -18,9 +18,7 @@ import { Anchor } from 'antd';
|
|||||||
|
|
||||||
const { Link } = Anchor;
|
const { Link } = Anchor;
|
||||||
|
|
||||||
const getCurrentAnchor = () => {
|
const getCurrentAnchor = () => '#components-anchor-demo-static';
|
||||||
return '#components-anchor-demo-static';
|
|
||||||
};
|
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<Anchor affix={false} getCurrentAnchor={getCurrentAnchor}>
|
<Anchor affix={false} getCurrentAnchor={getCurrentAnchor}>
|
||||||
|
@ -17,11 +17,9 @@ Basic Usage, set data source of autocomplete with `options` property.
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { AutoComplete } from 'antd';
|
import { AutoComplete } from 'antd';
|
||||||
|
|
||||||
const mockVal = (str: string, repeat: number = 1) => {
|
const mockVal = (str: string, repeat: number = 1) => ({
|
||||||
return {
|
value: str.repeat(repeat),
|
||||||
value: str.repeat(repeat),
|
});
|
||||||
};
|
|
||||||
};
|
|
||||||
const Complete: React.FC = () => {
|
const Complete: React.FC = () => {
|
||||||
const [value, setValue] = useState('');
|
const [value, setValue] = useState('');
|
||||||
const [options, setOptions] = useState<{ value: string }[]>([]);
|
const [options, setOptions] = useState<{ value: string }[]>([]);
|
||||||
|
@ -17,40 +17,36 @@ Demonstration of [Lookup Patterns: Certain Category](https://ant.design/docs/spe
|
|||||||
import { Input, AutoComplete } from 'antd';
|
import { Input, AutoComplete } from 'antd';
|
||||||
import { UserOutlined } from '@ant-design/icons';
|
import { UserOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
const renderTitle = (title: string) => {
|
const renderTitle = (title: string) => (
|
||||||
return (
|
<span>
|
||||||
<span>
|
{title}
|
||||||
{title}
|
<a
|
||||||
<a
|
style={{ float: 'right' }}
|
||||||
style={{ float: 'right' }}
|
href="https://www.google.com/search?q=antd"
|
||||||
href="https://www.google.com/search?q=antd"
|
target="_blank"
|
||||||
target="_blank"
|
rel="noopener noreferrer"
|
||||||
rel="noopener noreferrer"
|
>
|
||||||
>
|
more
|
||||||
more
|
</a>
|
||||||
</a>
|
</span>
|
||||||
</span>
|
);
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderItem = (title: string, count: number) => {
|
const renderItem = (title: string, count: number) => ({
|
||||||
return {
|
value: title,
|
||||||
value: title,
|
label: (
|
||||||
label: (
|
<div
|
||||||
<div
|
style={{
|
||||||
style={{
|
display: 'flex',
|
||||||
display: 'flex',
|
justifyContent: 'space-between',
|
||||||
justifyContent: 'space-between',
|
}}
|
||||||
}}
|
>
|
||||||
>
|
{title}
|
||||||
{title}
|
<span>
|
||||||
<span>
|
<UserOutlined /> {count}
|
||||||
<UserOutlined /> {count}
|
</span>
|
||||||
</span>
|
</div>
|
||||||
</div>
|
),
|
||||||
),
|
});
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const options = [
|
const options = [
|
||||||
{
|
{
|
||||||
|
@ -22,8 +22,8 @@ function getRandomInt(max: number, min: number = 0) {
|
|||||||
return Math.floor(Math.random() * (max - min + 1)) + min; // eslint-disable-line no-mixed-operators
|
return Math.floor(Math.random() * (max - min + 1)) + min; // eslint-disable-line no-mixed-operators
|
||||||
}
|
}
|
||||||
|
|
||||||
const searchResult = (query: string) => {
|
const searchResult = (query: string) =>
|
||||||
return new Array(getRandomInt(5))
|
new Array(getRandomInt(5))
|
||||||
.join('.')
|
.join('.')
|
||||||
.split('.')
|
.split('.')
|
||||||
.map((_, idx) => {
|
.map((_, idx) => {
|
||||||
@ -52,7 +52,6 @@ const searchResult = (query: string) => {
|
|||||||
),
|
),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
};
|
|
||||||
|
|
||||||
const Complete: React.FC = () => {
|
const Complete: React.FC = () => {
|
||||||
const [options, setOptions] = useState<SelectProps<object>['options']>([]);
|
const [options, setOptions] = useState<SelectProps<object>['options']>([]);
|
||||||
|
@ -17,44 +17,40 @@ Avatar group display.
|
|||||||
import { Avatar, Divider, Tooltip } from 'antd';
|
import { Avatar, Divider, Tooltip } from 'antd';
|
||||||
import { UserOutlined, AntDesignOutlined } from '@ant-design/icons';
|
import { UserOutlined, AntDesignOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
class Demo extends React.Component {
|
const Demo = () => (
|
||||||
render() {
|
<>
|
||||||
return (
|
<Avatar.Group>
|
||||||
<>
|
<Avatar src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" />
|
||||||
<Avatar.Group>
|
<Avatar style={{ backgroundColor: '#f56a00' }}>K</Avatar>
|
||||||
<Avatar src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" />
|
<Tooltip title="Ant User" placement="top">
|
||||||
<Avatar style={{ backgroundColor: '#f56a00' }}>K</Avatar>
|
<Avatar style={{ backgroundColor: '#87d068' }} icon={<UserOutlined />} />
|
||||||
<Tooltip title="Ant User" placement="top">
|
</Tooltip>
|
||||||
<Avatar style={{ backgroundColor: '#87d068' }} icon={<UserOutlined />} />
|
<Avatar style={{ backgroundColor: '#1890ff' }} icon={<AntDesignOutlined />} />
|
||||||
</Tooltip>
|
</Avatar.Group>
|
||||||
<Avatar style={{ backgroundColor: '#1890ff' }} icon={<AntDesignOutlined />} />
|
<Divider />
|
||||||
</Avatar.Group>
|
<Avatar.Group maxCount={2} maxStyle={{ color: '#f56a00', backgroundColor: '#fde3cf' }}>
|
||||||
<Divider />
|
<Avatar src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" />
|
||||||
<Avatar.Group maxCount={2} maxStyle={{ color: '#f56a00', backgroundColor: '#fde3cf' }}>
|
<Avatar style={{ backgroundColor: '#f56a00' }}>K</Avatar>
|
||||||
<Avatar src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" />
|
<Tooltip title="Ant User" placement="top">
|
||||||
<Avatar style={{ backgroundColor: '#f56a00' }}>K</Avatar>
|
<Avatar style={{ backgroundColor: '#87d068' }} icon={<UserOutlined />} />
|
||||||
<Tooltip title="Ant User" placement="top">
|
</Tooltip>
|
||||||
<Avatar style={{ backgroundColor: '#87d068' }} icon={<UserOutlined />} />
|
<Avatar style={{ backgroundColor: '#1890ff' }} icon={<AntDesignOutlined />} />
|
||||||
</Tooltip>
|
</Avatar.Group>
|
||||||
<Avatar style={{ backgroundColor: '#1890ff' }} icon={<AntDesignOutlined />} />
|
<Divider />
|
||||||
</Avatar.Group>
|
<Avatar.Group
|
||||||
<Divider />
|
maxCount={2}
|
||||||
<Avatar.Group
|
size="large"
|
||||||
maxCount={2}
|
maxStyle={{ color: '#f56a00', backgroundColor: '#fde3cf' }}
|
||||||
size="large"
|
>
|
||||||
maxStyle={{ color: '#f56a00', backgroundColor: '#fde3cf' }}
|
<Avatar src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" />
|
||||||
>
|
<Avatar style={{ backgroundColor: '#f56a00' }}>K</Avatar>
|
||||||
<Avatar src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" />
|
<Tooltip title="Ant User" placement="top">
|
||||||
<Avatar style={{ backgroundColor: '#f56a00' }}>K</Avatar>
|
<Avatar style={{ backgroundColor: '#87d068' }} icon={<UserOutlined />} />
|
||||||
<Tooltip title="Ant User" placement="top">
|
</Tooltip>
|
||||||
<Avatar style={{ backgroundColor: '#87d068' }} icon={<UserOutlined />} />
|
<Avatar style={{ backgroundColor: '#1890ff' }} icon={<AntDesignOutlined />} />
|
||||||
</Tooltip>
|
</Avatar.Group>
|
||||||
<Avatar style={{ backgroundColor: '#1890ff' }} icon={<AntDesignOutlined />} />
|
</>
|
||||||
</Avatar.Group>
|
);
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ReactDOM.render(<Demo />, mountNode);
|
ReactDOM.render(<Demo />, mountNode);
|
||||||
```
|
```
|
||||||
|
@ -235,7 +235,7 @@ const ConfigProvider: React.FC<ConfigProviderProps> & {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @private internal usage. do not use in your production */
|
/** @private internal Usage. do not use in your production */
|
||||||
ConfigProvider.ConfigContext = ConfigContext;
|
ConfigProvider.ConfigContext = ConfigContext;
|
||||||
ConfigProvider.SizeContext = SizeContext;
|
ConfigProvider.SizeContext = SizeContext;
|
||||||
ConfigProvider.config = setGlobalConfig;
|
ConfigProvider.config = setGlobalConfig;
|
||||||
|
@ -28,11 +28,7 @@ const App: React.FC = () => {
|
|||||||
setVisible(false);
|
setVisible(false);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<ConfigProvider
|
<ConfigProvider getPopupContainer={() => domRef.current!}>
|
||||||
getPopupContainer={() => {
|
|
||||||
return domRef.current!;
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div ref={domRef} className="site-drawer-render-in-current-wrapper">
|
<div ref={domRef} className="site-drawer-render-in-current-wrapper">
|
||||||
<Button type="primary" onClick={showDrawer}>
|
<Button type="primary" onClick={showDrawer}>
|
||||||
Open
|
Open
|
||||||
|
@ -11,9 +11,9 @@ const EMPTY_LIST: React.ReactNode[] = [];
|
|||||||
|
|
||||||
export interface ErrorListProps {
|
export interface ErrorListProps {
|
||||||
errors?: React.ReactNode[];
|
errors?: React.ReactNode[];
|
||||||
/** @private Internal usage. Do not use in your production */
|
/** @private Internal Usage. Do not use in your production */
|
||||||
help?: React.ReactNode;
|
help?: React.ReactNode;
|
||||||
/** @private Internal usage. Do not use in your production */
|
/** @private Internal Usage. Do not use in your production */
|
||||||
onDomErrorVisibleChange?: (visible: boolean) => void;
|
onDomErrorVisibleChange?: (visible: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ interface FormItemInputMiscProps {
|
|||||||
hasFeedback?: boolean;
|
hasFeedback?: boolean;
|
||||||
validateStatus?: ValidateStatus;
|
validateStatus?: ValidateStatus;
|
||||||
onDomErrorVisibleChange: (visible: boolean) => void;
|
onDomErrorVisibleChange: (visible: boolean) => void;
|
||||||
/** @private Internal usage, do not use in any of your production. */
|
/** @private Internal Usage, do not use in any of your production. */
|
||||||
_internalItemRender?: {
|
_internalItemRender?: {
|
||||||
mark: string;
|
mark: string;
|
||||||
render: (
|
render: (
|
||||||
|
@ -43,7 +43,6 @@ const Demo = () => {
|
|||||||
return;
|
return;
|
||||||
case 'other':
|
case 'other':
|
||||||
form.setFieldsValue({ note: 'Hi there!' });
|
form.setFieldsValue({ note: 'Hi there!' });
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -82,13 +81,13 @@ const Demo = () => {
|
|||||||
noStyle
|
noStyle
|
||||||
shouldUpdate={(prevValues, currentValues) => prevValues.gender !== currentValues.gender}
|
shouldUpdate={(prevValues, currentValues) => prevValues.gender !== currentValues.gender}
|
||||||
>
|
>
|
||||||
{({ getFieldValue }) => {
|
{({ getFieldValue }) =>
|
||||||
return getFieldValue('gender') === 'other' ? (
|
getFieldValue('gender') === 'other' ? (
|
||||||
<Form.Item name="customizeGender" label="Customize Gender" rules={[{ required: true }]}>
|
<Form.Item name="customizeGender" label="Customize Gender" rules={[{ required: true }]}>
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
) : null;
|
) : null
|
||||||
}}
|
}
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item {...tailLayout}>
|
<Form.Item {...tailLayout}>
|
||||||
<Button type="primary" htmlType="submit">
|
<Button type="primary" htmlType="submit">
|
||||||
|
@ -40,7 +40,6 @@ class Demo extends React.Component {
|
|||||||
return;
|
return;
|
||||||
case 'other':
|
case 'other':
|
||||||
this.formRef.current!.setFieldsValue({ note: 'Hi there!' });
|
this.formRef.current!.setFieldsValue({ note: 'Hi there!' });
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -80,8 +79,8 @@ class Demo extends React.Component {
|
|||||||
noStyle
|
noStyle
|
||||||
shouldUpdate={(prevValues, currentValues) => prevValues.gender !== currentValues.gender}
|
shouldUpdate={(prevValues, currentValues) => prevValues.gender !== currentValues.gender}
|
||||||
>
|
>
|
||||||
{({ getFieldValue }) => {
|
{({ getFieldValue }) =>
|
||||||
return getFieldValue('gender') === 'other' ? (
|
getFieldValue('gender') === 'other' ? (
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="customizeGender"
|
name="customizeGender"
|
||||||
label="Customize Gender"
|
label="Customize Gender"
|
||||||
@ -89,8 +88,8 @@ class Demo extends React.Component {
|
|||||||
>
|
>
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
) : null;
|
) : null
|
||||||
}}
|
}
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item {...tailLayout}>
|
<Form.Item {...tailLayout}>
|
||||||
<Button type="primary" htmlType="submit">
|
<Button type="primary" htmlType="submit">
|
||||||
|
@ -92,7 +92,7 @@ const Demo = () => {
|
|||||||
if (value.number > 0) {
|
if (value.number > 0) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
return Promise.reject('Price must be greater than zero!');
|
return Promise.reject(new Error('Price must be greater than zero!'));
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -31,10 +31,10 @@ const Demo = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Form.Item noStyle dependencies={['debug1']}>
|
<Form.Item noStyle dependencies={['debug1']}>
|
||||||
{() => {
|
{
|
||||||
return acc++;
|
() => acc++
|
||||||
// return <pre>{JSON.stringify(form.getFieldsValue(), null, 2)}</pre>;
|
// return <pre>{JSON.stringify(form.getFieldsValue(), null, 2)}</pre>;
|
||||||
}}
|
}
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="debug1" name="debug1">
|
<Form.Item label="debug1" name="debug1">
|
||||||
<Input />
|
<Input />
|
||||||
|
@ -34,26 +34,24 @@ interface CustomizedFormProps {
|
|||||||
fields: FieldData[];
|
fields: FieldData[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const CustomizedForm: React.FC<CustomizedFormProps> = ({ onChange, fields }) => {
|
const CustomizedForm: React.FC<CustomizedFormProps> = ({ onChange, fields }) => (
|
||||||
return (
|
<Form
|
||||||
<Form
|
name="global_state"
|
||||||
name="global_state"
|
layout="inline"
|
||||||
layout="inline"
|
fields={fields}
|
||||||
fields={fields}
|
onFieldsChange={(_, allFields) => {
|
||||||
onFieldsChange={(_, allFields) => {
|
onChange(allFields);
|
||||||
onChange(allFields);
|
}}
|
||||||
}}
|
>
|
||||||
|
<Form.Item
|
||||||
|
name="username"
|
||||||
|
label="Username"
|
||||||
|
rules={[{ required: true, message: 'Username is required!' }]}
|
||||||
>
|
>
|
||||||
<Form.Item
|
<Input />
|
||||||
name="username"
|
</Form.Item>
|
||||||
label="Username"
|
</Form>
|
||||||
rules={[{ required: true, message: 'Username is required!' }]}
|
);
|
||||||
>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const Demo = () => {
|
const Demo = () => {
|
||||||
const [fields, setFields] = useState<FieldData[]>([{ name: ['username'], value: 'Ant Design' }]);
|
const [fields, setFields] = useState<FieldData[]>([{ name: ['username'], value: 'Ant Design' }]);
|
||||||
|
@ -49,7 +49,7 @@ const HorizontalLoginForm = () => {
|
|||||||
placeholder="Password"
|
placeholder="Password"
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item shouldUpdate={true}>
|
<Form.Item shouldUpdate>
|
||||||
{() => (
|
{() => (
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
|
@ -21,6 +21,7 @@ const layout = {
|
|||||||
wrapperCol: { span: 16 },
|
wrapperCol: { span: 16 },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* eslint-disable no-template-curly-in-string */
|
||||||
const validateMessages = {
|
const validateMessages = {
|
||||||
required: '${label} is required!',
|
required: '${label} is required!',
|
||||||
types: {
|
types: {
|
||||||
@ -31,6 +32,7 @@ const validateMessages = {
|
|||||||
range: '${label} must be between ${min} and ${max}',
|
range: '${label} must be between ${min} and ${max}',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
/* eslint-enable no-template-curly-in-string */
|
||||||
|
|
||||||
const Demo = () => {
|
const Demo = () => {
|
||||||
const onFinish = (values: any) => {
|
const onFinish = (values: any) => {
|
||||||
|
@ -178,7 +178,7 @@ const RegistrationForm = () => {
|
|||||||
if (!value || getFieldValue('password') === value) {
|
if (!value || getFieldValue('password') === value) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
return Promise.reject('The two passwords that you entered do not match!');
|
return Promise.reject(new Error('The two passwords that you entered do not match!'));
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
]}
|
]}
|
||||||
@ -252,7 +252,7 @@ const RegistrationForm = () => {
|
|||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
validator: (_, value) =>
|
validator: (_, value) =>
|
||||||
value ? Promise.resolve() : Promise.reject('Should accept agreement'),
|
value ? Promise.resolve() : Promise.reject(new Error('Should accept agreement')),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
{...tailFormItemLayout}
|
{...tailFormItemLayout}
|
||||||
|
@ -24,8 +24,8 @@ const FormLayoutDemo = () => {
|
|||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const [requiredMark, setRequiredMarkType] = useState<RequiredMark>('optional');
|
const [requiredMark, setRequiredMarkType] = useState<RequiredMark>('optional');
|
||||||
|
|
||||||
const onRequiredTypeChange = ({ requiredMark }: { requiredMark: RequiredMark }) => {
|
const onRequiredTypeChange = ({ requiredMarkValue }: { requiredMarkValue: RequiredMark }) => {
|
||||||
setRequiredMarkType(requiredMark);
|
setRequiredMarkType(requiredMarkValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -39,7 +39,7 @@ const FormLayoutDemo = () => {
|
|||||||
<Form.Item label="Required Mark" name="requiredMark">
|
<Form.Item label="Required Mark" name="requiredMark">
|
||||||
<Radio.Group>
|
<Radio.Group>
|
||||||
<Radio.Button value="optional">Optional</Radio.Button>
|
<Radio.Button value="optional">Optional</Radio.Button>
|
||||||
<Radio.Button value={true}>Required</Radio.Button>
|
<Radio.Button value>Required</Radio.Button>
|
||||||
<Radio.Button value={false}>Hidden</Radio.Button>
|
<Radio.Button value={false}>Hidden</Radio.Button>
|
||||||
</Radio.Group>
|
</Radio.Group>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
@ -56,8 +56,8 @@ const Demo = () => {
|
|||||||
{...formItemLayout}
|
{...formItemLayout}
|
||||||
onFinish={onFinish}
|
onFinish={onFinish}
|
||||||
initialValues={{
|
initialValues={{
|
||||||
['input-number']: 3,
|
'input-number': 3,
|
||||||
['checkbox-group']: ['A', 'B'],
|
'checkbox-group': ['A', 'B'],
|
||||||
rate: 3.5,
|
rate: 3.5,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -9,6 +9,7 @@ import LeftOutlined from '@ant-design/icons/LeftOutlined';
|
|||||||
import RightOutlined from '@ant-design/icons/RightOutlined';
|
import RightOutlined from '@ant-design/icons/RightOutlined';
|
||||||
import { GroupConsumerProps } from 'rc-image/lib/PreviewGroup';
|
import { GroupConsumerProps } from 'rc-image/lib/PreviewGroup';
|
||||||
import { ConfigContext } from '../config-provider';
|
import { ConfigContext } from '../config-provider';
|
||||||
|
import { getTransitionName } from '../_util/motion';
|
||||||
|
|
||||||
export const icons = {
|
export const icons = {
|
||||||
rotateLeft: <RotateLeftOutlined />,
|
rotateLeft: <RotateLeftOutlined />,
|
||||||
@ -22,11 +23,34 @@ export const icons = {
|
|||||||
|
|
||||||
const InternalPreviewGroup: React.FC<GroupConsumerProps> = ({
|
const InternalPreviewGroup: React.FC<GroupConsumerProps> = ({
|
||||||
previewPrefixCls: customizePrefixCls,
|
previewPrefixCls: customizePrefixCls,
|
||||||
|
preview,
|
||||||
...props
|
...props
|
||||||
}) => {
|
}) => {
|
||||||
const { getPrefixCls } = React.useContext(ConfigContext);
|
const { getPrefixCls } = React.useContext(ConfigContext);
|
||||||
const prefixCls = getPrefixCls('image-preview', customizePrefixCls);
|
const prefixCls = getPrefixCls('image-preview', customizePrefixCls);
|
||||||
return <RcImage.PreviewGroup previewPrefixCls={prefixCls} icons={icons} {...props} />;
|
const rootPrefixCls = getPrefixCls();
|
||||||
|
|
||||||
|
const mergedPreview = React.useMemo(() => {
|
||||||
|
if (preview === false) {
|
||||||
|
return preview;
|
||||||
|
}
|
||||||
|
const _preview = typeof preview === 'object' ? preview : {};
|
||||||
|
|
||||||
|
return {
|
||||||
|
..._preview,
|
||||||
|
transitionName: getTransitionName(rootPrefixCls, 'zoom', _preview.transitionName),
|
||||||
|
maskTransitionName: getTransitionName(rootPrefixCls, 'fade', _preview.maskTransitionName),
|
||||||
|
};
|
||||||
|
}, [preview]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RcImage.PreviewGroup
|
||||||
|
preview={mergedPreview}
|
||||||
|
previewPrefixCls={prefixCls}
|
||||||
|
icons={icons}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default InternalPreviewGroup;
|
export default InternalPreviewGroup;
|
||||||
|
@ -1,8 +1,66 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { mount } from 'enzyme';
|
||||||
import Image from '..';
|
import Image from '..';
|
||||||
import mountTest from '../../../tests/shared/mountTest';
|
import mountTest from '../../../tests/shared/mountTest';
|
||||||
import rtlTest from '../../../tests/shared/rtlTest';
|
import rtlTest from '../../../tests/shared/rtlTest';
|
||||||
|
|
||||||
|
const src = 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png';
|
||||||
|
|
||||||
describe('Image', () => {
|
describe('Image', () => {
|
||||||
mountTest(Image);
|
mountTest(Image);
|
||||||
rtlTest(Image);
|
rtlTest(Image);
|
||||||
|
it('Image preview props set false', () => {
|
||||||
|
const wrapper = mount(<Image src={src} preview={false} />);
|
||||||
|
|
||||||
|
expect(wrapper.find('Image').at(0).prop('preview')).toBe(false);
|
||||||
|
expect(wrapper.find('Image').at(1).prop('preview')).toBe(false);
|
||||||
|
});
|
||||||
|
it('Group preview props set false', () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<Image.PreviewGroup preview={false}>
|
||||||
|
<Image src={src} />
|
||||||
|
</Image.PreviewGroup>,
|
||||||
|
);
|
||||||
|
expect(wrapper.find('Group').prop('preview')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Default preview props', () => {
|
||||||
|
const wrapper = mount(<Image src={src} preview={{ visible: true }} />);
|
||||||
|
|
||||||
|
expect(wrapper.find('Preview').prop('transitionName')).toBe('ant-zoom');
|
||||||
|
expect(wrapper.find('Preview').prop('maskTransitionName')).toBe('ant-fade');
|
||||||
|
});
|
||||||
|
it('Default Group preview props', () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<Image.PreviewGroup preview={{ visible: true }}>
|
||||||
|
<Image src={src} />
|
||||||
|
</Image.PreviewGroup>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper.find('Preview').prop('transitionName')).toBe('ant-zoom');
|
||||||
|
expect(wrapper.find('Preview').prop('maskTransitionName')).toBe('ant-fade');
|
||||||
|
});
|
||||||
|
it('Customize preview props', () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<Image
|
||||||
|
src={src}
|
||||||
|
preview={{ visible: true, transitionName: 'abc', maskTransitionName: 'def' }}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper.find('Preview').prop('transitionName')).toBe('abc');
|
||||||
|
expect(wrapper.find('Preview').prop('maskTransitionName')).toBe('def');
|
||||||
|
});
|
||||||
|
it('Customize Group preview props', () => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<Image.PreviewGroup
|
||||||
|
preview={{ visible: true, transitionName: 'abc', maskTransitionName: 'def' }}
|
||||||
|
>
|
||||||
|
<Image src={src} />
|
||||||
|
</Image.PreviewGroup>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper.find('Preview').prop('transitionName')).toBe('abc');
|
||||||
|
expect(wrapper.find('Preview').prop('maskTransitionName')).toBe('def');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -5,6 +5,7 @@ import RcImage, { ImageProps } from 'rc-image';
|
|||||||
import defaultLocale from '../locale/en_US';
|
import defaultLocale from '../locale/en_US';
|
||||||
import PreviewGroup, { icons } from './PreviewGroup';
|
import PreviewGroup, { icons } from './PreviewGroup';
|
||||||
import { ConfigContext } from '../config-provider';
|
import { ConfigContext } from '../config-provider';
|
||||||
|
import { getTransitionName } from '../_util/motion';
|
||||||
|
|
||||||
export interface CompositionImage<P> extends React.FC<P> {
|
export interface CompositionImage<P> extends React.FC<P> {
|
||||||
PreviewGroup: typeof PreviewGroup;
|
PreviewGroup: typeof PreviewGroup;
|
||||||
@ -17,6 +18,7 @@ const Image: CompositionImage<ImageProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { getPrefixCls } = useContext(ConfigContext);
|
const { getPrefixCls } = useContext(ConfigContext);
|
||||||
const prefixCls = getPrefixCls('image', customizePrefixCls);
|
const prefixCls = getPrefixCls('image', customizePrefixCls);
|
||||||
|
const rootPrefixCls = getPrefixCls();
|
||||||
|
|
||||||
const { locale: contextLocale = defaultLocale } = useContext(ConfigContext);
|
const { locale: contextLocale = defaultLocale } = useContext(ConfigContext);
|
||||||
const imageLocale = contextLocale.Image || defaultLocale.Image;
|
const imageLocale = contextLocale.Image || defaultLocale.Image;
|
||||||
@ -25,6 +27,7 @@ const Image: CompositionImage<ImageProps> = ({
|
|||||||
if (preview === false) {
|
if (preview === false) {
|
||||||
return preview;
|
return preview;
|
||||||
}
|
}
|
||||||
|
const _preview = typeof preview === 'object' ? preview : {};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
mask: (
|
mask: (
|
||||||
@ -34,7 +37,9 @@ const Image: CompositionImage<ImageProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
icons,
|
icons,
|
||||||
...(typeof preview === 'object' ? preview : null),
|
..._preview,
|
||||||
|
transitionName: getTransitionName(rootPrefixCls, 'zoom', _preview.transitionName),
|
||||||
|
maskTransitionName: getTransitionName(rootPrefixCls, 'fade', _preview.maskTransitionName),
|
||||||
};
|
};
|
||||||
}, [preview, imageLocale]);
|
}, [preview, imageLocale]);
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ interface ClearableInputProps extends BasicProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ClearableLabeledInput extends React.Component<ClearableInputProps> {
|
class ClearableLabeledInput extends React.Component<ClearableInputProps> {
|
||||||
/** @private Do not use out of this class. We do not promise this is always keep. */
|
/** @private Do Not use out of this class. We do not promise this is always keep. */
|
||||||
private containerRef = React.createRef<HTMLSpanElement>();
|
private containerRef = React.createRef<HTMLSpanElement>();
|
||||||
|
|
||||||
onInputMouseUp: React.MouseEventHandler = e => {
|
onInputMouseUp: React.MouseEventHandler = e => {
|
||||||
|
@ -32,7 +32,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/5FrZKStG_/List.svg
|
|||||||
| locale | 默认文案设置,目前包括空数据文案 | object | {emptyText: `暂无数据`} | |
|
| locale | 默认文案设置,目前包括空数据文案 | object | {emptyText: `暂无数据`} | |
|
||||||
| pagination | 对应的 `pagination` 配置, 设置 false 不显示 | boolean \| object | false | |
|
| pagination | 对应的 `pagination` 配置, 设置 false 不显示 | boolean \| object | false | |
|
||||||
| renderItem | 当使用 dataSource 时,可以用 `renderItem` 自定义渲染列表项 | (item) => ReactNode | - | |
|
| renderItem | 当使用 dataSource 时,可以用 `renderItem` 自定义渲染列表项 | (item) => ReactNode | - | |
|
||||||
| rowKey | 当 `renderItem` 自定义渲染列表项有效时,自定义每一行的 `key` 的获取方式 | ((item: T) => string) | `list-item-${index}` | |
|
| rowKey | 当 `renderItem` 自定义渲染列表项有效时,自定义每一行的 `key` 的获取方式 | ((item: T) => string) | `list-item-${index}` | |
|
||||||
| size | list 的尺寸 | `default` \| `large` \| `small` | `default` | |
|
| size | list 的尺寸 | `default` \| `large` \| `small` | `default` | |
|
||||||
| split | 是否展示分割线 | boolean | true | |
|
| split | 是否展示分割线 | boolean | true | |
|
||||||
|
|
||||||
|
@ -252,7 +252,7 @@ export interface MessageApi extends MessageInstance {
|
|||||||
useMessage(): [MessageInstance, React.ReactElement];
|
useMessage(): [MessageInstance, React.ReactElement];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @private test only function. Not work on production */
|
/** @private test Only function. Not work on production */
|
||||||
export const getInstance = () => (process.env.NODE_ENV === 'test' ? messageInstance : null);
|
export const getInstance = () => (process.env.NODE_ENV === 'test' ? messageInstance : null);
|
||||||
|
|
||||||
export default api as MessageApi;
|
export default api as MessageApi;
|
||||||
|
@ -282,7 +282,7 @@ export interface NotificationApi extends NotificationInstance {
|
|||||||
useNotification: () => [NotificationInstance, React.ReactElement];
|
useNotification: () => [NotificationInstance, React.ReactElement];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @private test only function. Not work on production */
|
/** @private test Only function. Not work on production */
|
||||||
export const getInstance = async (cacheKey: string) =>
|
export const getInstance = async (cacheKey: string) =>
|
||||||
process.env.NODE_ENV === 'test' ? notificationInstance[cacheKey] : null;
|
process.env.NODE_ENV === 'test' ? notificationInstance[cacheKey] : null;
|
||||||
|
|
||||||
|
@ -155,9 +155,9 @@
|
|||||||
|
|
||||||
&-fade-enter,
|
&-fade-enter,
|
||||||
&-fade-appear {
|
&-fade-appear {
|
||||||
opacity: 0;
|
|
||||||
.notification-fade-effect();
|
.notification-fade-effect();
|
||||||
|
|
||||||
|
opacity: 0;
|
||||||
animation-play-state: paused;
|
animation-play-state: paused;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,74 +13,94 @@ title:
|
|||||||
|
|
||||||
A complete multiple select sample with remote search, debounce fetch, ajax callback order flow, and loading state.
|
A complete multiple select sample with remote search, debounce fetch, ajax callback order flow, and loading state.
|
||||||
|
|
||||||
```jsx
|
```tsx
|
||||||
import { Select, Spin } from 'antd';
|
import { Select, Spin } from 'antd';
|
||||||
|
import { SelectProps } from 'antd/es/select';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
|
|
||||||
const { Option } = Select;
|
export interface DebounceSelectProps<ValueType = any>
|
||||||
|
extends Omit<SelectProps<ValueType>, 'options' | 'children'> {
|
||||||
|
fetchOptions: (search: string) => Promise<ValueType[]>;
|
||||||
|
debounceTimeout?: number;
|
||||||
|
}
|
||||||
|
|
||||||
class UserRemoteSelect extends React.Component {
|
function DebounceSelect<
|
||||||
constructor(props) {
|
ValueType extends { key?: string; label: React.ReactNode; value: string | number } = any
|
||||||
super(props);
|
>({ fetchOptions, debounceTimeout = 800, ...props }: DebounceSelectProps) {
|
||||||
this.lastFetchId = 0;
|
const [fetching, setFetching] = React.useState(false);
|
||||||
this.fetchUser = debounce(this.fetchUser, 800);
|
const [options, setOptions] = React.useState<ValueType[]>([]);
|
||||||
}
|
const fetchRef = React.useRef(0);
|
||||||
|
|
||||||
state = {
|
const debounceFetcher = React.useMemo(() => {
|
||||||
data: [],
|
const loadOptions = (value: string) => {
|
||||||
value: [],
|
fetchRef.current += 1;
|
||||||
fetching: false,
|
const fetchId = fetchRef.current;
|
||||||
};
|
setOptions([]);
|
||||||
|
setFetching(true);
|
||||||
|
|
||||||
fetchUser = value => {
|
fetchOptions(value).then(newOptions => {
|
||||||
console.log('fetching user', value);
|
if (fetchId !== fetchRef.current) {
|
||||||
this.lastFetchId += 1;
|
|
||||||
const fetchId = this.lastFetchId;
|
|
||||||
this.setState({ data: [], fetching: true });
|
|
||||||
fetch('https://randomuser.me/api/?results=5')
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(body => {
|
|
||||||
if (fetchId !== this.lastFetchId) {
|
|
||||||
// for fetch callback order
|
// for fetch callback order
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const data = body.results.map(user => ({
|
|
||||||
text: `${user.name.first} ${user.name.last}`,
|
setOptions(newOptions);
|
||||||
value: user.login.username,
|
setFetching(false);
|
||||||
}));
|
|
||||||
this.setState({ data, fetching: false });
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
handleChange = value => {
|
return debounce(loadOptions, debounceTimeout);
|
||||||
this.setState({
|
}, [fetchOptions, debounceTimeout]);
|
||||||
value,
|
|
||||||
data: [],
|
|
||||||
fetching: false,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
return (
|
||||||
const { fetching, data, value } = this.state;
|
<Select<ValueType>
|
||||||
return (
|
labelInValue
|
||||||
<Select
|
filterOption={false}
|
||||||
mode="multiple"
|
onSearch={debounceFetcher}
|
||||||
labelInValue
|
notFoundContent={fetching ? <Spin size="small" /> : null}
|
||||||
value={value}
|
{...props}
|
||||||
placeholder="Select users"
|
options={options}
|
||||||
notFoundContent={fetching ? <Spin size="small" /> : null}
|
/>
|
||||||
filterOption={false}
|
);
|
||||||
onSearch={this.fetchUser}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
>
|
|
||||||
{data.map(d => (
|
|
||||||
<Option key={d.value}>{d.text}</Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactDOM.render(<UserRemoteSelect />, mountNode);
|
// Usage of DebounceSelect
|
||||||
|
interface UserValue {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchUserList(username: string): Promise<UserValue[]> {
|
||||||
|
console.log('fetching user', username);
|
||||||
|
|
||||||
|
return fetch('https://randomuser.me/api/?results=5')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(body =>
|
||||||
|
body.results.map(
|
||||||
|
(user: { name: { first: string; last: string }; login: { username: string } }) => ({
|
||||||
|
label: `${user.name.first} ${user.name.last}`,
|
||||||
|
value: user.login.username,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Demo = () => {
|
||||||
|
const [value, setValue] = React.useState<UserValue[]>([]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DebounceSelect
|
||||||
|
mode="multiple"
|
||||||
|
value={value}
|
||||||
|
placeholder="Select users"
|
||||||
|
fetchOptions={fetchUserList}
|
||||||
|
onChange={newValue => {
|
||||||
|
setValue(newValue);
|
||||||
|
}}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ReactDOM.render(<Demo />, mountNode);
|
||||||
```
|
```
|
||||||
|
@ -18,7 +18,6 @@ import {
|
|||||||
ColumnsType,
|
ColumnsType,
|
||||||
TableCurrentDataSource,
|
TableCurrentDataSource,
|
||||||
SorterResult,
|
SorterResult,
|
||||||
Key,
|
|
||||||
GetPopupContainer,
|
GetPopupContainer,
|
||||||
ExpandableConfig,
|
ExpandableConfig,
|
||||||
ExpandType,
|
ExpandType,
|
||||||
@ -26,6 +25,7 @@ import {
|
|||||||
SortOrder,
|
SortOrder,
|
||||||
TableLocale,
|
TableLocale,
|
||||||
TableAction,
|
TableAction,
|
||||||
|
FilterValue,
|
||||||
} from './interface';
|
} from './interface';
|
||||||
import useSelection, {
|
import useSelection, {
|
||||||
SELECTION_ALL,
|
SELECTION_ALL,
|
||||||
@ -54,7 +54,7 @@ interface ChangeEventInfo<RecordType> {
|
|||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
total?: number;
|
total?: number;
|
||||||
};
|
};
|
||||||
filters: Record<string, (Key | boolean)[] | null>;
|
filters: Record<string, FilterValue | null>;
|
||||||
sorter: SorterResult<RecordType> | SorterResult<RecordType>[];
|
sorter: SorterResult<RecordType> | SorterResult<RecordType>[];
|
||||||
|
|
||||||
filterStates: FilterState<RecordType>[];
|
filterStates: FilterState<RecordType>[];
|
||||||
@ -85,7 +85,7 @@ export interface TableProps<RecordType>
|
|||||||
|
|
||||||
onChange?: (
|
onChange?: (
|
||||||
pagination: TablePaginationConfig,
|
pagination: TablePaginationConfig,
|
||||||
filters: Record<string, (Key | boolean)[] | null>,
|
filters: Record<string, FilterValue | null>,
|
||||||
sorter: SorterResult<RecordType> | SorterResult<RecordType>[],
|
sorter: SorterResult<RecordType> | SorterResult<RecordType>[],
|
||||||
extra: TableCurrentDataSource<RecordType>,
|
extra: TableCurrentDataSource<RecordType>,
|
||||||
) => void;
|
) => void;
|
||||||
@ -273,7 +273,7 @@ function Table<RecordType extends object = any>(props: TableProps<RecordType>) {
|
|||||||
|
|
||||||
// ============================ Filter ============================
|
// ============================ Filter ============================
|
||||||
const onFilterChange = (
|
const onFilterChange = (
|
||||||
filters: Record<string, (Key | boolean)[]>,
|
filters: Record<string, FilterValue>,
|
||||||
filterStates: FilterState<RecordType>[],
|
filterStates: FilterState<RecordType>[],
|
||||||
) => {
|
) => {
|
||||||
triggerOnChange(
|
triggerOnChange(
|
||||||
|
@ -333,8 +333,8 @@ describe('Table.pagination', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `pagination` is not designed to accept `true` value, but in practice, many people assign
|
* `pagination` is not designed to accept `true` value, but in practice, many people assign `true`
|
||||||
* `true` to `pagination`, since they misunderstand that `pagination` can accept a boolean value.
|
* to `pagination`, since they misunderstand that `pagination` can accept a boolean value.
|
||||||
*/
|
*/
|
||||||
it('Accepts pagination as true', () => {
|
it('Accepts pagination as true', () => {
|
||||||
const wrapper = mount(createTable({ pagination: true }));
|
const wrapper = mount(createTable({ pagination: true }));
|
||||||
|
@ -16,38 +16,42 @@ By using custom components, we can integrate table with react-dnd to implement d
|
|||||||
```jsx
|
```jsx
|
||||||
import React, { useState, useCallback, useRef } from 'react';
|
import React, { useState, useCallback, useRef } from 'react';
|
||||||
import { Table } from 'antd';
|
import { Table } from 'antd';
|
||||||
import { DndProvider, useDrag, useDrop, createDndContext } from 'react-dnd';
|
import { DndProvider, useDrag, useDrop } from 'react-dnd';
|
||||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||||
import update from 'immutability-helper';
|
import update from 'immutability-helper';
|
||||||
|
|
||||||
const RNDContext = createDndContext(HTML5Backend);
|
|
||||||
|
|
||||||
const type = 'DragableBodyRow';
|
const type = 'DragableBodyRow';
|
||||||
|
|
||||||
const DragableBodyRow = ({ index, moveRow, className, style, ...restProps }) => {
|
const DragableBodyRow = ({ index, moveRow, className, style, ...restProps }) => {
|
||||||
const ref = React.useRef();
|
const ref = React.useRef();
|
||||||
const [{ isOver, dropClassName }, drop] = useDrop({
|
const [{ isOver, dropClassName }, drop] = useDrop(
|
||||||
accept: type,
|
() => ({
|
||||||
collect: monitor => {
|
accept: type,
|
||||||
const { index: dragIndex } = monitor.getItem() || {};
|
collect: monitor => {
|
||||||
if (dragIndex === index) {
|
const { index: dragIndex } = monitor.getItem() || {};
|
||||||
return {};
|
if (dragIndex === index) {
|
||||||
}
|
return {};
|
||||||
return {
|
}
|
||||||
isOver: monitor.isOver(),
|
return {
|
||||||
dropClassName: dragIndex < index ? ' drop-over-downward' : ' drop-over-upward',
|
isOver: monitor.isOver(),
|
||||||
};
|
dropClassName: dragIndex < index ? ' drop-over-downward' : ' drop-over-upward',
|
||||||
},
|
};
|
||||||
drop: item => {
|
},
|
||||||
moveRow(item.index, index);
|
drop: item => {
|
||||||
},
|
moveRow(item.index, index);
|
||||||
});
|
},
|
||||||
const [, drag] = useDrag({
|
|
||||||
item: { type, index },
|
|
||||||
collect: monitor => ({
|
|
||||||
isDragging: monitor.isDragging(),
|
|
||||||
}),
|
}),
|
||||||
});
|
[index],
|
||||||
|
);
|
||||||
|
const [, drag] = useDrag(
|
||||||
|
() => ({
|
||||||
|
item: { type, index },
|
||||||
|
collect: monitor => ({
|
||||||
|
isDragging: monitor.isDragging(),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
[],
|
||||||
|
);
|
||||||
drop(drag(ref));
|
drop(drag(ref));
|
||||||
return (
|
return (
|
||||||
<tr
|
<tr
|
||||||
@ -120,10 +124,8 @@ const DragSortingTable: React.FC = () => {
|
|||||||
[data],
|
[data],
|
||||||
);
|
);
|
||||||
|
|
||||||
const manager = useRef(RNDContext);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DndProvider manager={manager.current.dragDropManager}>
|
<DndProvider backend={HTML5Backend}>
|
||||||
<Table
|
<Table
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={data}
|
dataSource={data}
|
||||||
|
@ -134,7 +134,7 @@ function FilterDropdown<RecordType>(props: FilterDropdownProps<RecordType>) {
|
|||||||
typeof filterDropdownVisible === 'boolean' ? filterDropdownVisible : visible;
|
typeof filterDropdownVisible === 'boolean' ? filterDropdownVisible : visible;
|
||||||
|
|
||||||
// ===================== Select Keys =====================
|
// ===================== Select Keys =====================
|
||||||
const propFilteredKeys = filterState && filterState.filteredKeys;
|
const propFilteredKeys = filterState?.filteredKeys;
|
||||||
const [getFilteredKeysSync, setFilteredKeysSync] = useSyncState(propFilteredKeys || []);
|
const [getFilteredKeysSync, setFilteredKeysSync] = useSyncState(propFilteredKeys || []);
|
||||||
|
|
||||||
const onSelectKeys = ({ selectedKeys }: { selectedKeys?: Key[] }) => {
|
const onSelectKeys = ({ selectedKeys }: { selectedKeys?: Key[] }) => {
|
||||||
|
@ -6,6 +6,8 @@ import {
|
|||||||
ColumnTitleProps,
|
ColumnTitleProps,
|
||||||
Key,
|
Key,
|
||||||
TableLocale,
|
TableLocale,
|
||||||
|
FilterValue,
|
||||||
|
FilterKey,
|
||||||
GetPopupContainer,
|
GetPopupContainer,
|
||||||
ColumnFilterItem,
|
ColumnFilterItem,
|
||||||
} from '../../interface';
|
} from '../../interface';
|
||||||
@ -15,7 +17,7 @@ import FilterDropdown from './FilterDropdown';
|
|||||||
export interface FilterState<RecordType> {
|
export interface FilterState<RecordType> {
|
||||||
column: ColumnType<RecordType>;
|
column: ColumnType<RecordType>;
|
||||||
key: Key;
|
key: Key;
|
||||||
filteredKeys?: Key[] | null;
|
filteredKeys?: FilterKey;
|
||||||
forceFiltered?: boolean;
|
forceFiltered?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,7 +43,7 @@ function collectFilterStates<RecordType>(
|
|||||||
filterStates.push({
|
filterStates.push({
|
||||||
column,
|
column,
|
||||||
key: getColumnKey(column, columnPos),
|
key: getColumnKey(column, columnPos),
|
||||||
filteredKeys: filteredValues,
|
filteredKeys: filteredValues as FilterKey,
|
||||||
forceFiltered: column.filtered,
|
forceFiltered: column.filtered,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -49,8 +51,9 @@ function collectFilterStates<RecordType>(
|
|||||||
filterStates.push({
|
filterStates.push({
|
||||||
column,
|
column,
|
||||||
key: getColumnKey(column, columnPos),
|
key: getColumnKey(column, columnPos),
|
||||||
filteredKeys:
|
filteredKeys: (init && column.defaultFilteredValue
|
||||||
init && column.defaultFilteredValue ? column.defaultFilteredValue! : undefined,
|
? column.defaultFilteredValue!
|
||||||
|
: undefined) as FilterKey,
|
||||||
forceFiltered: column.filtered,
|
forceFiltered: column.filtered,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -121,7 +124,7 @@ function injectFilter<RecordType>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function flattenKeys(filters?: ColumnFilterItem[]) {
|
function flattenKeys(filters?: ColumnFilterItem[]) {
|
||||||
let keys: (string | number | boolean)[] = [];
|
let keys: FilterValue = [];
|
||||||
(filters || []).forEach(({ value, children }) => {
|
(filters || []).forEach(({ value, children }) => {
|
||||||
keys.push(value);
|
keys.push(value);
|
||||||
if (children) {
|
if (children) {
|
||||||
@ -132,7 +135,7 @@ function flattenKeys(filters?: ColumnFilterItem[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function generateFilterInfo<RecordType>(filterStates: FilterState<RecordType>[]) {
|
function generateFilterInfo<RecordType>(filterStates: FilterState<RecordType>[]) {
|
||||||
const currentFilters: Record<string, (Key | boolean)[] | null> = {};
|
const currentFilters: Record<string, FilterValue | null> = {};
|
||||||
|
|
||||||
filterStates.forEach(({ key, filteredKeys, column }) => {
|
filterStates.forEach(({ key, filteredKeys, column }) => {
|
||||||
const { filters, filterDropdown } = column;
|
const { filters, filterDropdown } = column;
|
||||||
@ -178,7 +181,7 @@ interface FilterConfig<RecordType> {
|
|||||||
mergedColumns: ColumnsType<RecordType>;
|
mergedColumns: ColumnsType<RecordType>;
|
||||||
locale: TableLocale;
|
locale: TableLocale;
|
||||||
onFilterChange: (
|
onFilterChange: (
|
||||||
filters: Record<string, (Key | boolean)[] | null>,
|
filters: Record<string, FilterValue | null>,
|
||||||
filterStates: FilterState<RecordType>[],
|
filterStates: FilterState<RecordType>[],
|
||||||
) => void;
|
) => void;
|
||||||
getPopupContainer?: GetPopupContainer;
|
getPopupContainer?: GetPopupContainer;
|
||||||
@ -194,7 +197,7 @@ function useFilter<RecordType>({
|
|||||||
}: FilterConfig<RecordType>): [
|
}: FilterConfig<RecordType>): [
|
||||||
TransformColumns<RecordType>,
|
TransformColumns<RecordType>,
|
||||||
FilterState<RecordType>[],
|
FilterState<RecordType>[],
|
||||||
() => Record<string, (Key | boolean)[] | null>,
|
() => Record<string, FilterValue | null>,
|
||||||
] {
|
] {
|
||||||
const [filterStates, setFilterStates] = React.useState<FilterState<RecordType>[]>(
|
const [filterStates, setFilterStates] = React.useState<FilterState<RecordType>[]>(
|
||||||
collectFilterStates(mergedColumns, true),
|
collectFilterStates(mergedColumns, true),
|
||||||
|
@ -67,7 +67,7 @@ const columns = [
|
|||||||
| expandable | Config expandable content | [expandable](#expandable) | - | |
|
| expandable | Config expandable content | [expandable](#expandable) | - | |
|
||||||
| footer | Table footer renderer | function(currentPageData) | - | |
|
| footer | Table footer renderer | function(currentPageData) | - | |
|
||||||
| getPopupContainer | The render container of dropdowns in table | (triggerNode) => HTMLElement | () => TableHtmlElement | |
|
| getPopupContainer | The render container of dropdowns in table | (triggerNode) => HTMLElement | () => TableHtmlElement | |
|
||||||
| loading | Loading status of table | boolean \| [object](/components/spin/#API) ([more](https://github.com/ant-design/ant-design/issues/4544#issuecomment-271533135)) | false | |
|
| loading | Loading status of table | boolean \| [Spin Props](/components/spin/#API) | false | |
|
||||||
| locale | The i18n text including filter, sort, empty text, etc | object | filterConfirm: `Ok` <br> filterReset: `Reset` <br> emptyText: `No Data` <br> [Default](https://github.com/ant-design/ant-design/blob/4ad1ccac277782d7ed14f7e5d02d6346aae0db67/components/locale/default.tsx#L19) | |
|
| locale | The i18n text including filter, sort, empty text, etc | object | filterConfirm: `Ok` <br> filterReset: `Reset` <br> emptyText: `No Data` <br> [Default](https://github.com/ant-design/ant-design/blob/4ad1ccac277782d7ed14f7e5d02d6346aae0db67/components/locale/default.tsx#L19) | |
|
||||||
| pagination | Config of pagination. You can ref table pagination [config](#pagination) or full [`pagination`](/components/pagination/) document, hide it by setting it to `false` | object | - | |
|
| pagination | Config of pagination. You can ref table pagination [config](#pagination) or full [`pagination`](/components/pagination/) document, hide it by setting it to `false` | object | - | |
|
||||||
| rowClassName | Row's className | function(record, index): string | - | |
|
| rowClassName | Row's className | function(record, index): string | - | |
|
||||||
|
@ -74,7 +74,7 @@ const columns = [
|
|||||||
| expandable | 配置展开属性 | [expandable](#expandable) | - | |
|
| expandable | 配置展开属性 | [expandable](#expandable) | - | |
|
||||||
| footer | 表格尾部 | function(currentPageData) | - | |
|
| footer | 表格尾部 | function(currentPageData) | - | |
|
||||||
| getPopupContainer | 设置表格内各类浮层的渲染节点,如筛选菜单 | (triggerNode) => HTMLElement | () => TableHtmlElement | |
|
| getPopupContainer | 设置表格内各类浮层的渲染节点,如筛选菜单 | (triggerNode) => HTMLElement | () => TableHtmlElement | |
|
||||||
| loading | 页面是否加载中 | boolean \| [object](/components/spin/#API) ([更多](https://github.com/ant-design/ant-design/issues/4544#issuecomment-271533135)) | false | |
|
| loading | 页面是否加载中 | boolean \| [Spin Props](/components/spin/#API) | false | |
|
||||||
| locale | 默认文案设置,目前包括排序、过滤、空数据文案 | object | filterConfirm: `确定` <br> filterReset: `重置` <br> emptyText: `暂无数据` <br> [默认值](https://github.com/ant-design/ant-design/blob/4ad1ccac277782d7ed14f7e5d02d6346aae0db67/components/locale/default.tsx#L19) | |
|
| locale | 默认文案设置,目前包括排序、过滤、空数据文案 | object | filterConfirm: `确定` <br> filterReset: `重置` <br> emptyText: `暂无数据` <br> [默认值](https://github.com/ant-design/ant-design/blob/4ad1ccac277782d7ed14f7e5d02d6346aae0db67/components/locale/default.tsx#L19) | |
|
||||||
| pagination | 分页器,参考[配置项](#pagination)或 [pagination](/components/pagination/) 文档,设为 false 时不展示和进行分页 | object | - | |
|
| pagination | 分页器,参考[配置项](#pagination)或 [pagination](/components/pagination/) 文档,设为 false 时不展示和进行分页 | object | - | |
|
||||||
| rowClassName | 表格行的类名 | function(record, index): string | - | |
|
| rowClassName | 表格行的类名 | function(record, index): string | - | |
|
||||||
@ -205,7 +205,7 @@ const columns = [
|
|||||||
| renderCell | 渲染勾选框,用法与 Column 的 `render` 相同 | function(checked, record, index, originNode) {} | - | 4.1.0 |
|
| renderCell | 渲染勾选框,用法与 Column 的 `render` 相同 | function(checked, record, index, originNode) {} | - | 4.1.0 |
|
||||||
| selectedRowKeys | 指定选中项的 key 数组,需要和 onChange 进行配合 | string\[] \| number\[] | \[] | |
|
| selectedRowKeys | 指定选中项的 key 数组,需要和 onChange 进行配合 | string\[] \| number\[] | \[] | |
|
||||||
| selections | 自定义选择项 [配置项](#selection), 设为 `true` 时使用默认选择项 | object\[] \| boolean | true | |
|
| selections | 自定义选择项 [配置项](#selection), 设为 `true` 时使用默认选择项 | object\[] \| boolean | true | |
|
||||||
| type | 多选/单选,`checkbox` or `radio` | string | `checkbox` | |
|
| type | 多选/单选 | `checkbox` \| `radio` | `checkbox` | |
|
||||||
| onChange | 选中项发生变化时的回调 | function(selectedRowKeys, selectedRows) | - | |
|
| onChange | 选中项发生变化时的回调 | function(selectedRowKeys, selectedRows) | - | |
|
||||||
| onSelect | 用户手动选择/取消选择某行的回调 | function(record, selected, selectedRows, nativeEvent) | - | |
|
| onSelect | 用户手动选择/取消选择某行的回调 | function(record, selected, selectedRows, nativeEvent) | - | |
|
||||||
| onSelectAll | 用户手动选择/取消选择所有行的回调 | function(selected, selectedRows, changeRows) | - | |
|
| onSelectAll | 用户手动选择/取消选择所有行的回调 | function(selected, selectedRows, changeRows) | - | |
|
||||||
|
@ -68,6 +68,8 @@ export type ColumnTitle<RecordType> =
|
|||||||
| React.ReactNode
|
| React.ReactNode
|
||||||
| ((props: ColumnTitleProps<RecordType>) => React.ReactNode);
|
| ((props: ColumnTitleProps<RecordType>) => React.ReactNode);
|
||||||
|
|
||||||
|
export type FilterValue = (Key | boolean)[];
|
||||||
|
export type FilterKey = Key[] | null;
|
||||||
export interface FilterConfirmProps {
|
export interface FilterConfirmProps {
|
||||||
closeDropdown: boolean;
|
closeDropdown: boolean;
|
||||||
}
|
}
|
||||||
@ -103,8 +105,8 @@ export interface ColumnType<RecordType> extends RcColumnType<RecordType> {
|
|||||||
filters?: ColumnFilterItem[];
|
filters?: ColumnFilterItem[];
|
||||||
filterDropdown?: React.ReactNode | ((props: FilterDropdownProps) => React.ReactNode);
|
filterDropdown?: React.ReactNode | ((props: FilterDropdownProps) => React.ReactNode);
|
||||||
filterMultiple?: boolean;
|
filterMultiple?: boolean;
|
||||||
filteredValue?: Key[] | null;
|
filteredValue?: FilterValue | null;
|
||||||
defaultFilteredValue?: Key[] | null;
|
defaultFilteredValue?: FilterValue | null;
|
||||||
filterIcon?: React.ReactNode | ((filtered: boolean) => React.ReactNode);
|
filterIcon?: React.ReactNode | ((filtered: boolean) => React.ReactNode);
|
||||||
onFilter?: (value: string | number | boolean, record: RecordType) => boolean;
|
onFilter?: (value: string | number | boolean, record: RecordType) => boolean;
|
||||||
filterDropdownVisible?: boolean;
|
filterDropdownVisible?: boolean;
|
||||||
|
@ -67,22 +67,22 @@ const Demo = () => {
|
|||||||
const [selectedKeys, setSelectedKeys] = useState<React.Key[]>([]);
|
const [selectedKeys, setSelectedKeys] = useState<React.Key[]>([]);
|
||||||
const [autoExpandParent, setAutoExpandParent] = useState<boolean>(true);
|
const [autoExpandParent, setAutoExpandParent] = useState<boolean>(true);
|
||||||
|
|
||||||
const onExpand = (expandedKeys: React.Key[]) => {
|
const onExpand = (expandedKeysValue: React.Key[]) => {
|
||||||
console.log('onExpand', expandedKeys);
|
console.log('onExpand', expandedKeysValue);
|
||||||
// if not set autoExpandParent to false, if children expanded, parent can not collapse.
|
// if not set autoExpandParent to false, if children expanded, parent can not collapse.
|
||||||
// or, you can remove all expanded children keys.
|
// or, you can remove all expanded children keys.
|
||||||
setExpandedKeys(expandedKeys);
|
setExpandedKeys(expandedKeysValue);
|
||||||
setAutoExpandParent(false);
|
setAutoExpandParent(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onCheck = (checkedKeys: React.Key[]) => {
|
const onCheck = (checkedKeysValue: React.Key[]) => {
|
||||||
console.log('onCheck', checkedKeys);
|
console.log('onCheck', checkedKeysValue);
|
||||||
setCheckedKeys(checkedKeys);
|
setCheckedKeys(checkedKeysValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSelect = (selectedKeys: React.Key[], info: any) => {
|
const onSelect = (selectedKeysValue: React.Key[], info: any) => {
|
||||||
console.log('onSelect', info);
|
console.log('onSelect', info);
|
||||||
setSelectedKeys(selectedKeys);
|
setSelectedKeys(selectedKeysValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -38,7 +38,8 @@ function updateTreeData(list: DataNode[], key: React.Key, children: DataNode[]):
|
|||||||
...node,
|
...node,
|
||||||
children,
|
children,
|
||||||
};
|
};
|
||||||
} else if (node.children) {
|
}
|
||||||
|
if (node.children) {
|
||||||
return {
|
return {
|
||||||
...node,
|
...node,
|
||||||
children: updateTreeData(node.children, key, children),
|
children: updateTreeData(node.children, key, children),
|
||||||
|
@ -98,11 +98,7 @@ const Demo: React.FC<{}> = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onSetShowLine = (checked: boolean) => {
|
const onSetShowLine = (checked: boolean) => {
|
||||||
if (checked) {
|
setShowLine(checked ? { showLeafIcon } : false);
|
||||||
showLeafIcon ? setShowLine(checked) : setShowLine({ showLeafIcon });
|
|
||||||
} else {
|
|
||||||
setShowLine(checked);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -719,7 +719,7 @@ exports[`renders ./components/upload/demo/drag-sorting.md correctly 1`] = `
|
|||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
Click to Upload
|
Click to Upload
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
|
@ -16,40 +16,44 @@ By using `itemRender`, we can integrate upload with react-dnd to implement drag
|
|||||||
```jsx
|
```jsx
|
||||||
import React, { useState, useCallback, useRef } from 'react';
|
import React, { useState, useCallback, useRef } from 'react';
|
||||||
import { Upload, Button, Tooltip } from 'antd';
|
import { Upload, Button, Tooltip } from 'antd';
|
||||||
import { DndProvider, useDrag, useDrop, createDndContext } from 'react-dnd';
|
import { DndProvider, useDrag, useDrop } from 'react-dnd';
|
||||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||||
import update from 'immutability-helper';
|
import update from 'immutability-helper';
|
||||||
import { UploadOutlined } from '@ant-design/icons';
|
import { UploadOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
const RNDContext = createDndContext(HTML5Backend);
|
|
||||||
|
|
||||||
const type = 'DragableUploadList';
|
const type = 'DragableUploadList';
|
||||||
|
|
||||||
const DragableUploadListItem = ({ originNode, moveRow, file, fileList }) => {
|
const DragableUploadListItem = ({ originNode, moveRow, file, fileList }) => {
|
||||||
const ref = React.useRef();
|
const ref = React.useRef();
|
||||||
const index = fileList.indexOf(file);
|
const index = fileList.indexOf(file);
|
||||||
const [{ isOver, dropClassName }, drop] = useDrop({
|
const [{ isOver, dropClassName }, drop] = useDrop(
|
||||||
accept: type,
|
() => ({
|
||||||
collect: monitor => {
|
accept: type,
|
||||||
const { index: dragIndex } = monitor.getItem() || {};
|
collect: monitor => {
|
||||||
if (dragIndex === index) {
|
const { index: dragIndex } = monitor.getItem() || {};
|
||||||
return {};
|
if (dragIndex === index) {
|
||||||
}
|
return {};
|
||||||
return {
|
}
|
||||||
isOver: monitor.isOver(),
|
return {
|
||||||
dropClassName: dragIndex < index ? ' drop-over-downward' : ' drop-over-upward',
|
isOver: monitor.isOver(),
|
||||||
};
|
dropClassName: dragIndex < index ? ' drop-over-downward' : ' drop-over-upward',
|
||||||
},
|
};
|
||||||
drop: item => {
|
},
|
||||||
moveRow(item.index, index);
|
drop: item => {
|
||||||
},
|
moveRow(item.index, index);
|
||||||
});
|
},
|
||||||
const [, drag] = useDrag({
|
|
||||||
item: { type, index },
|
|
||||||
collect: monitor => ({
|
|
||||||
isDragging: monitor.isDragging(),
|
|
||||||
}),
|
}),
|
||||||
});
|
[index],
|
||||||
|
);
|
||||||
|
const [, drag] = useDrag(
|
||||||
|
() => ({
|
||||||
|
item: { type, index },
|
||||||
|
collect: monitor => ({
|
||||||
|
isDragging: monitor.isDragging(),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
[],
|
||||||
|
);
|
||||||
drop(drag(ref));
|
drop(drag(ref));
|
||||||
const errorNode = (
|
const errorNode = (
|
||||||
<Tooltip title="Upload Error" getPopupContainer={() => document.body}>
|
<Tooltip title="Upload Error" getPopupContainer={() => document.body}>
|
||||||
@ -115,14 +119,12 @@ const DragSortingUpload: React.FC = () => {
|
|||||||
[fileList],
|
[fileList],
|
||||||
);
|
);
|
||||||
|
|
||||||
const manager = useRef(RNDContext);
|
|
||||||
|
|
||||||
const onChange = ({ fileList: newFileList }) => {
|
const onChange = ({ fileList: newFileList }) => {
|
||||||
setFileList(newFileList);
|
setFileList(newFileList);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DndProvider manager={manager.current.dragDropManager}>
|
<DndProvider backend={HTML5Backend}>
|
||||||
<Upload
|
<Upload
|
||||||
action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
|
action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
|
||||||
fileList={fileList}
|
fileList={fileList}
|
||||||
@ -136,9 +138,7 @@ const DragSortingUpload: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Button>
|
<Button icon={<UploadOutlined />}>Click to Upload</Button>
|
||||||
<UploadOutlined /> Click to Upload
|
|
||||||
</Button>
|
|
||||||
</Upload>
|
</Upload>
|
||||||
</DndProvider>
|
</DndProvider>
|
||||||
);
|
);
|
||||||
|
@ -75,7 +75,7 @@ Note:
|
|||||||
|
|
||||||
### Customize in Umi
|
### Customize in Umi
|
||||||
|
|
||||||
You can easily use [theme](https://umijs.org/config/#theme) field in [config/config.js](https://github.com/ant-design/ant-design-pro/blob/56e648ec14bdb9f6724169fd64830447e224ccb1/config/config.js#L45) (Umi) file of your project root directory if you are using [Umi](http://umijs.org/), which could be an object or a javascript file path.
|
You can easily use [theme](https://umijs.org/config/#theme) field in `.umirc.ts` or [config/config.ts](https://github.com/ant-design/ant-design-pro/blob/v5/config/config.ts) file of your project root directory if you are using [Umi](http://umijs.org/), which could be an object or a javascript file path.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
"theme": {
|
"theme": {
|
||||||
@ -148,25 +148,17 @@ We have some official themes, try them out and give us some feedback!
|
|||||||
|
|
||||||
Method 1: using Umi 3
|
Method 1: using Umi 3
|
||||||
|
|
||||||
If you're using [Umi 3](http://umijs.org/zh/), which only need two steps:
|
If you're using [Umi 3](http://umijs.org):
|
||||||
|
|
||||||
1. Install `@umijs/plugin-antd` plugin;
|
```js
|
||||||
|
// .umirc.ts or config/config.ts
|
||||||
```bash
|
export default {
|
||||||
$ npm i @umijs/plugin-antd -D
|
antd: {
|
||||||
```
|
dark: true, // active dark theme
|
||||||
|
compact: true, // active compact theme
|
||||||
2. set `dark` or `compact` to `true`.
|
},
|
||||||
|
},
|
||||||
```js
|
```
|
||||||
// .umirc.ts or config/config.ts
|
|
||||||
export default {
|
|
||||||
antd: {
|
|
||||||
dark: true, // active dark theme
|
|
||||||
compact: true, // active compact theme
|
|
||||||
},
|
|
||||||
},
|
|
||||||
```
|
|
||||||
|
|
||||||
Method 2: Import [antd/dist/antd.dark.less](https://unpkg.com/browse/antd@4.x/dist/antd.dark.less) or [antd/dist/antd.compact.less](https://unpkg.com/browse/antd@4.x/dist/antd.compact.less) in the style file:
|
Method 2: Import [antd/dist/antd.dark.less](https://unpkg.com/browse/antd@4.x/dist/antd.dark.less) or [antd/dist/antd.compact.less](https://unpkg.com/browse/antd@4.x/dist/antd.compact.less) in the style file:
|
||||||
|
|
||||||
|
@ -75,7 +75,7 @@ module.exports = {
|
|||||||
|
|
||||||
### 在 Umi 里配置主题
|
### 在 Umi 里配置主题
|
||||||
|
|
||||||
如果你在使用 [Umi](http://umijs.org/zh/),那么可以很方便地在项目根目录的 [config/config.js](https://github.com/ant-design/ant-design-pro/blob/56e648ec14bdb9f6724169fd64830447e224ccb1/config/config.js#L45)(Umi)文件中 [theme](https://umijs.org/zh/config/#theme) 字段进行主题配置。`theme` 可以配置为一个对象或文件路径。
|
如果你在使用 [Umi](http://umijs.org/zh/),那么可以很方便地在项目根目录的 `.umirc.ts` 或 [config/config.ts](https://github.com/ant-design/ant-design-pro/blob/v5/config/config.ts) 文件中 [theme](https://umijs.org/zh-CN/config#theme) 字段进行主题配置。`theme` 可以配置为一个对象或文件路径。
|
||||||
|
|
||||||
```js
|
```js
|
||||||
"theme": {
|
"theme": {
|
||||||
@ -126,25 +126,17 @@ module.exports = {
|
|||||||
|
|
||||||
方式一:使用 Umi 3
|
方式一:使用 Umi 3
|
||||||
|
|
||||||
如果你在使用 [Umi 3](http://umijs.org/zh/),仅需两步:
|
如果你在使用 [Umi 3](http://umijs.org/zh-CN):
|
||||||
|
|
||||||
1. 安装 `@umijs/plugin-antd` 插件;
|
```js
|
||||||
|
// .umirc.ts or config/config.ts
|
||||||
```bash
|
export default {
|
||||||
$ npm i @umijs/plugin-antd -D
|
antd: {
|
||||||
```
|
dark: true, // 开启暗色主题
|
||||||
|
compact: true, // 开启紧凑主题
|
||||||
2. 配置 `dark` 和 `compact`。
|
},
|
||||||
|
},
|
||||||
```js
|
```
|
||||||
// .umirc.ts or config/config.ts
|
|
||||||
export default {
|
|
||||||
antd: {
|
|
||||||
dark: true, // 开启暗色主题
|
|
||||||
compact: true, // 开启紧凑主题
|
|
||||||
},
|
|
||||||
},
|
|
||||||
```
|
|
||||||
|
|
||||||
方式二:是在样式文件全量引入 [antd.dark.less](https://unpkg.com/browse/antd@4.x/dist/antd.dark.less) 或 [antd.compact.less](https://unpkg.com/browse/antd@4.x/dist/antd.compact.less)。
|
方式二:是在样式文件全量引入 [antd.dark.less](https://unpkg.com/browse/antd@4.x/dist/antd.dark.less) 或 [antd.compact.less](https://unpkg.com/browse/antd@4.x/dist/antd.compact.less)。
|
||||||
|
|
||||||
|
17
package.json
17
package.json
@ -69,7 +69,7 @@
|
|||||||
"dist:esbuild": "ESBUILD=true npm run dist",
|
"dist:esbuild": "ESBUILD=true npm run dist",
|
||||||
"lint": "npm run tsc && npm run lint:script && npm run lint:demo && npm run lint:style && npm run lint:deps && npm run lint:md",
|
"lint": "npm run tsc && npm run lint:script && npm run lint:demo && npm run lint:style && npm run lint:deps && npm run lint:md",
|
||||||
"lint-fix": "npm run lint-fix:script && npm run lint-fix:demo && npm run lint-fix:style",
|
"lint-fix": "npm run lint-fix:script && npm run lint-fix:demo && npm run lint-fix:style",
|
||||||
"lint-fix:demo": "eslint-tinker ./components/*/demo/*.md",
|
"lint-fix:demo": "npm run lint:demo -- --fix",
|
||||||
"lint-fix:script": "npm run lint:script -- --fix",
|
"lint-fix:script": "npm run lint:script -- --fix",
|
||||||
"lint-fix:style": "npm run lint:style -- --fix",
|
"lint-fix:style": "npm run lint:style -- --fix",
|
||||||
"lint:demo": "eslint components/*/demo/*.md",
|
"lint:demo": "eslint components/*/demo/*.md",
|
||||||
@ -125,7 +125,7 @@
|
|||||||
"rc-drawer": "~4.3.0",
|
"rc-drawer": "~4.3.0",
|
||||||
"rc-dropdown": "~3.2.0",
|
"rc-dropdown": "~3.2.0",
|
||||||
"rc-field-form": "~1.19.0",
|
"rc-field-form": "~1.19.0",
|
||||||
"rc-image": "~5.2.0",
|
"rc-image": "~5.2.3",
|
||||||
"rc-input-number": "~7.0.0-alpha.4",
|
"rc-input-number": "~7.0.0-alpha.4",
|
||||||
"rc-mentions": "~1.5.0",
|
"rc-mentions": "~1.5.0",
|
||||||
"rc-menu": "~8.10.0",
|
"rc-menu": "~8.10.0",
|
||||||
@ -188,7 +188,7 @@
|
|||||||
"bundlesize": "^0.18.0",
|
"bundlesize": "^0.18.0",
|
||||||
"chalk": "^4.0.0",
|
"chalk": "^4.0.0",
|
||||||
"cheerio": "^1.0.0-rc.3",
|
"cheerio": "^1.0.0-rc.3",
|
||||||
"concurrently": "^5.0.2",
|
"concurrently": "^6.0.0",
|
||||||
"cross-env": "^7.0.0",
|
"cross-env": "^7.0.0",
|
||||||
"css-minimizer-webpack-plugin": "^1.1.1",
|
"css-minimizer-webpack-plugin": "^1.1.1",
|
||||||
"css-split-webpack-plugin": "^0.2.6",
|
"css-split-webpack-plugin": "^0.2.6",
|
||||||
@ -200,16 +200,15 @@
|
|||||||
"esbuild-loader": "^2.7.0",
|
"esbuild-loader": "^2.7.0",
|
||||||
"eslint": "^7.9.0",
|
"eslint": "^7.9.0",
|
||||||
"eslint-config-airbnb": "^18.0.0",
|
"eslint-config-airbnb": "^18.0.0",
|
||||||
"eslint-config-prettier": "^7.0.0",
|
"eslint-config-prettier": "^8.0.0",
|
||||||
"eslint-plugin-babel": "^5.3.0",
|
"eslint-plugin-babel": "^5.3.0",
|
||||||
"eslint-plugin-import": "^2.21.1",
|
"eslint-plugin-import": "^2.21.1",
|
||||||
"eslint-plugin-jest": "^24.0.1",
|
"eslint-plugin-jest": "^24.0.1",
|
||||||
"eslint-plugin-jsx-a11y": "^6.2.1",
|
"eslint-plugin-jsx-a11y": "^6.2.1",
|
||||||
"eslint-plugin-markdown": "^1.0.0",
|
"eslint-plugin-markdown": "^2.0.0",
|
||||||
"eslint-plugin-react": "^7.20.6",
|
"eslint-plugin-react": "^7.20.6",
|
||||||
"eslint-plugin-react-hooks": "^4.1.2",
|
"eslint-plugin-react-hooks": "^4.1.2",
|
||||||
"eslint-plugin-unicorn": "^27.0.0",
|
"eslint-plugin-unicorn": "^28.0.2",
|
||||||
"eslint-tinker": "^0.5.0",
|
|
||||||
"fetch-jsonp": "^1.1.3",
|
"fetch-jsonp": "^1.1.3",
|
||||||
"fs-extra": "^9.0.0",
|
"fs-extra": "^9.0.0",
|
||||||
"full-icu": "^1.3.0",
|
"full-icu": "^1.3.0",
|
||||||
@ -247,8 +246,8 @@
|
|||||||
"react": "^17.0.1",
|
"react": "^17.0.1",
|
||||||
"react-color": "^2.17.3",
|
"react-color": "^2.17.3",
|
||||||
"react-copy-to-clipboard": "^5.0.1",
|
"react-copy-to-clipboard": "^5.0.1",
|
||||||
"react-dnd": "^11.1.1",
|
"react-dnd": "^13.0.0",
|
||||||
"react-dnd-html5-backend": "^11.1.1",
|
"react-dnd-html5-backend": "^12.1.0",
|
||||||
"react-dom": "^17.0.1",
|
"react-dom": "^17.0.1",
|
||||||
"react-draggable": "^4.4.3",
|
"react-draggable": "^4.4.3",
|
||||||
"react-github-button": "^0.1.11",
|
"react-github-button": "^0.1.11",
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
/* eslint no-console: 0 */
|
||||||
const chalk = require('chalk');
|
const chalk = require('chalk');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const { join } = require('path');
|
const { join } = require('path');
|
||||||
|
@ -76,7 +76,11 @@ module.exports = {
|
|||||||
|
|
||||||
// Resolve use react hook fail when yarn link or npm link
|
// Resolve use react hook fail when yarn link or npm link
|
||||||
// https://github.com/webpack/webpack/issues/8607#issuecomment-453068938
|
// https://github.com/webpack/webpack/issues/8607#issuecomment-453068938
|
||||||
config.resolve.alias = { ...config.resolve.alias, react: require.resolve('react') };
|
config.resolve.alias = {
|
||||||
|
...config.resolve.alias,
|
||||||
|
'react/jsx-runtime': require.resolve('react/jsx-runtime'),
|
||||||
|
react: require.resolve('react'),
|
||||||
|
};
|
||||||
} else if (process.env.ESBUILD) {
|
} else if (process.env.ESBUILD) {
|
||||||
// use esbuild
|
// use esbuild
|
||||||
config.plugins.push(new ESBuildPlugin());
|
config.plugins.push(new ESBuildPlugin());
|
||||||
|
Loading…
Reference in New Issue
Block a user