Mentions data driven (#38630)

* feat: reset and force update

* feat: update package

* feat: reset

* feat: update for viewer

* feat: update for viewer

* feat: update for viewer

* feat: update for viewer

* feat: update for viewer

* update doc

* feat: add waring

* feat: update doc

* feat: add test case
This commit is contained in:
黑雨 2022-11-21 21:34:23 +08:00 committed by GitHub
parent 6a8f5b5765
commit 67ee019478
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 260 additions and 117 deletions

View File

@ -1,5 +1,5 @@
import React from 'react';
import Mentions from '..';
import Mentions,{Option} from '..';
import focusTest from '../../../tests/shared/focusTest';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
@ -84,4 +84,21 @@ describe('Mentions', () => {
expect(wrapper.container.querySelectorAll('li.ant-mentions-dropdown-menu-item').length).toBe(1);
expect(wrapper.container.querySelectorAll('.bamboo-light').length).toBeTruthy();
});
it('warning if use Mentions.Option', () => {
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
render(
<Mentions
style={{ width: '100%' }}
defaultValue="@afc163"
>
<Option value="afc163">afc163</Option>
<Option value="zombieJ">zombieJ</Option>
<Option value="yesmeck">yesmeck</Option>
</Mentions>,
);
expect(errorSpy).toHaveBeenCalledWith(
'Warning: [antd: Mentions] `Mentions.Option` is deprecated. Please use `options` instead.',
);
});
});

View File

@ -2,7 +2,6 @@ import React, { useCallback, useRef, useState } from 'react';
import { Mentions } from 'antd';
import debounce from 'lodash/debounce';
const { Option } = Mentions;
const App: React.FC = () => {
const [loading, setLoading] = useState(false);
const [users, setUsers] = useState<{ login: string; avatar_url: string }[]>([]);
@ -15,12 +14,12 @@ const App: React.FC = () => {
}
fetch(`https://api.github.com/search/users?q=${key}`)
.then(res => res.json())
.then(({ items = [] }) => {
.then((res) => res.json())
.then(({ options = [] }) => {
if (ref.current !== key) return;
setLoading(false);
setUsers(items.slice(0, 10));
setUsers(options.slice(0, 10));
});
};
@ -36,14 +35,22 @@ const App: React.FC = () => {
};
return (
<Mentions style={{ width: '100%' }} loading={loading} onSearch={onSearch}>
{users.map(({ login, avatar_url: avatar }) => (
<Option key={login} value={login} className="antd-demo-dynamic-option">
<Mentions
style={{ width: '100%' }}
loading={loading}
onSearch={onSearch}
options={users.map(({ login, avatar_url: avatar }) => ({
key: login,
value: login,
className: 'antd-demo-dynamic-option',
label: (
<>
<img src={avatar} alt={login} />
<span>{login}</span>
</Option>
))}
</Mentions>
</>
),
}))}
/>
);
};

View File

@ -1,14 +1,25 @@
import React from 'react';
import { Mentions } from 'antd';
const { Option } = Mentions;
const App: React.FC = () => (
<Mentions autoSize style={{ width: '100%' }}>
<Option value="afc163">afc163</Option>
<Option value="zombieJ">zombieJ</Option>
<Option value="yesmeck">yesmeck</Option>
</Mentions>
<Mentions
autoSize
style={{ width: '100%' }}
options={[
{
value: 'afc163',
label: 'afc163',
},
{
value: 'zombieJ',
label: 'zombieJ',
},
{
value: 'yesmeck',
label: 'yesmeck',
},
]}
/>
);
export default App;

View File

@ -1,14 +1,12 @@
import React from 'react';
import { Mentions } from 'antd';
import type { OptionProps } from 'antd/es/mentions';
const { Option } = Mentions;
import type { MentionsOptionProps } from 'antd/es/mentions';
const onChange = (value: string) => {
console.log('Change:', value);
};
const onSelect = (option: OptionProps) => {
const onSelect = (option: MentionsOptionProps) => {
console.log('select', option);
};
@ -18,11 +16,21 @@ const App: React.FC = () => (
onChange={onChange}
onSelect={onSelect}
defaultValue="@afc163"
>
<Option value="afc163">afc163</Option>
<Option value="zombieJ">zombieJ</Option>
<Option value="yesmeck">yesmeck</Option>
</Mentions>
options={[
{
value: 'afc163',
label: 'afc163',
},
{
value: 'zombieJ',
label: 'zombieJ',
},
{
value: 'yesmeck',
label: 'yesmeck',
},
]}
/>
);
export default App;

View File

@ -1,7 +1,7 @@
import React from 'react';
import { Button, Form, Mentions } from 'antd';
const { Option, getMentions } = Mentions;
const { getMentions } = Mentions;
const App: React.FC = () => {
const [form] = Form.useForm();
@ -36,11 +36,23 @@ const App: React.FC = () => {
wrapperCol={{ span: 16 }}
rules={[{ validator: checkMention }]}
>
<Mentions rows={1}>
<Option value="afc163">afc163</Option>
<Option value="zombieJ">zombieJ</Option>
<Option value="yesmeck">yesmeck</Option>
</Mentions>
<Mentions
rows={1}
options={[
{
value: 'afc163',
label: 'afc163',
},
{
value: 'zombieJ',
label: 'zombieJ',
},
{
value: 'yesmeck',
label: 'yesmeck',
},
]}
/>
</Form.Item>
<Form.Item
name="bio"
@ -49,11 +61,24 @@ const App: React.FC = () => {
wrapperCol={{ span: 16 }}
rules={[{ required: true }]}
>
<Mentions rows={3} placeholder="You can use @ to ref user here">
<Option value="afc163">afc163</Option>
<Option value="zombieJ">zombieJ</Option>
<Option value="yesmeck">yesmeck</Option>
</Mentions>
<Mentions
rows={3}
placeholder="You can use @ to ref user here"
options={[
{
value: 'afc163',
label: 'afc163',
},
{
value: 'zombieJ',
label: 'zombieJ',
},
{
value: 'yesmeck',
label: 'yesmeck',
},
]}
/>
</Form.Item>
<Form.Item wrapperCol={{ span: 14, offset: 6 }}>
<Button htmlType="submit" type="primary">

View File

@ -1,14 +1,25 @@
import React from 'react';
import { Mentions } from 'antd';
const { Option } = Mentions;
const App: React.FC = () => (
<Mentions style={{ width: '100%' }} placement="top">
<Option value="afc163">afc163</Option>
<Option value="zombieJ">zombieJ</Option>
<Option value="yesmeck">yesmeck</Option>
</Mentions>
<Mentions
style={{ width: '100%' }}
placement="top"
options={[
{
value: 'afc163',
label: 'afc163',
},
{
value: 'zombieJ',
label: 'zombieJ',
},
{
value: 'yesmeck',
label: 'yesmeck',
},
]}
/>
);
export default App;

View File

@ -1,8 +1,6 @@
import React, { useState } from 'react';
import { Mentions } from 'antd';
const { Option } = Mentions;
const MOCK_DATA = {
'@': ['afc163', 'zombiej', 'yesmeck'],
'#': ['1.0', '2.0', '3.0'],
@ -23,13 +21,12 @@ const App: React.FC = () => {
placeholder="input @ to mention people, # to mention tag"
prefix={['@', '#']}
onSearch={onSearch}
>
{(MOCK_DATA[prefix] || []).map(value => (
<Option key={value} value={value}>
{value}
</Option>
))}
</Mentions>
options={(MOCK_DATA[prefix] || []).map((value) => ({
key: value,
value,
label: value,
}))}
/>
);
};

View File

@ -1,25 +1,28 @@
import React from 'react';
import { Mentions } from 'antd';
const { Option } = Mentions;
const getOptions = () =>
['afc163', 'zombiej', 'yesmeck'].map(value => (
<Option key={value} value={value}>
{value}
</Option>
));
const options = ['afc163', 'zombiej', 'yesmeck'].map((value) => ({
value,
key: value,
label: value,
}));
const App: React.FC = () => (
<>
<div style={{ marginBottom: 10 }}>
<Mentions style={{ width: '100%' }} placeholder="this is disabled Mentions" disabled>
{getOptions()}
</Mentions>
<Mentions
style={{ width: '100%' }}
placeholder="this is disabled Mentions"
disabled
options={options}
/>
</div>
<Mentions style={{ width: '100%' }} placeholder="this is readOnly Mentions" readOnly>
{getOptions()}
</Mentions>
<Mentions
style={{ width: '100%' }}
placeholder="this is readOnly Mentions"
readOnly
options={options}
/>
</>
);

View File

@ -1,15 +1,21 @@
import React from 'react';
import { Mentions } from 'antd';
const { Option } = Mentions;
const { _InternalPanelDoNotUseOrYouWillBeFired: InternalMentions } = Mentions;
const options = [
{
value: 'afc163',
label: 'afc163',
},
{
value: 'zombieJ',
label: 'zombieJ',
},
]
const App: React.FC = () => (
<InternalMentions style={{ width: '100%' }} value="@">
<Option value="afc163">afc163</Option>
<Option value="zombieJ">zombieJ</Option>
</InternalMentions>
<InternalMentions style={{ width: '100%' }} value="@" options={options}/>
);
export default App;

View File

@ -1,34 +1,47 @@
import React from 'react';
import { Mentions, Space } from 'antd';
import type { OptionProps } from 'antd/es/mentions';
const { Option } = Mentions;
import type { MentionsOptionProps } from 'antd/es/mentions';
const onChange = (value: string) => {
console.log('Change:', value);
};
const onSelect = (option: OptionProps) => {
const onSelect = (option: MentionsOptionProps) => {
console.log('select', option);
};
const App: React.FC = () => {
const options = (
<>
<Option value="afc163">afc163</Option>
<Option value="zombieJ">zombieJ</Option>
<Option value="yesmeck">yesmeck</Option>
</>
);
const options = [
{
value: 'afc163',
label: 'afc163',
},
{
value: 'zombieJ',
label: 'zombieJ',
},
{
value: 'yesmeck',
label: 'yesmeck',
},
];
return (
<Space direction="vertical">
<Mentions onChange={onChange} onSelect={onSelect} defaultValue="@afc163" status="error">
{options}
</Mentions>
<Mentions onChange={onChange} onSelect={onSelect} defaultValue="@afc163" status="warning">
{options}
</Mentions>
<Mentions
onChange={onChange}
onSelect={onSelect}
defaultValue="@afc163"
status="error"
options={options}
/>
<Mentions
onChange={onChange}
onSelect={onSelect}
defaultValue="@afc163"
status="warning"
options={options}
/>
</Space>
);
};

View File

@ -26,14 +26,26 @@ When you need to mention someone or something.
<code src="./demo/status.tsx">Status</code>
<code src="./demo/render-panel.tsx" debug>_InternalPanelDoNotUseOrYouWillBeFired</code>
## API
### Usage upgrade after 5.1.0
```__react
import Alert from '../alert';
ReactDOM.render(<Alert message="After version 5.1.0, we provide a simpler usage <Mentions options={[...]} /> with better performance and potential of writing simpler code style in your applications. Meanwhile, we deprecated the old usage in browser console, we will remove it in antd 6.0." />, mountNode);
```
```jsx
// works when >=5.1.0, recommended ✅
const options = [{ value: 'sample', label: 'sample' }];
return <Mentions options={options} />;
// works when <5.1.0, deprecated when >=5.1.0 🙅🏻‍♀️
<Mentions onChange={onChange}>
<Mentions.Option value="sample">Sample</Mentions.Option>
</Mentions>
</Mentions>;
```
## API
### Mention
| Property | Description | Type | Default | Version |
@ -56,6 +68,7 @@ When you need to mention someone or something.
| onResize | The callback function that is triggered when textarea resize | function({ width, height }) | - | |
| onSearch | Trigger when prefix hit | (text: string, prefix: string) => void | - | |
| onSelect | Trigger when user select the option | (option: OptionProps, prefix: string) => void | - | |
| options | Option Configuration | [Options](#Option) | \[] | 5.1.0 |
### Mention methods
@ -68,5 +81,8 @@ When you need to mention someone or something.
| Property | Description | Type | Default |
| --- | --- | --- | --- |
| children | Suggestion content | ReactNode | - |
| value | The value of suggestion, the value will insert into input filed while selected | string | - |
| label | Title of the option | React.ReactNode | - |
| key | The key value of the option | string | - |
| disabled | Optional | boolean | - |
| className | className | string | - |
| style | The style of the option | React.CSSProperties | - |

View File

@ -3,6 +3,7 @@ import RcMentions from 'rc-mentions';
import type {
MentionsProps as RcMentionsProps,
MentionsRef as RcMentionsRef,
DataDrivenOptionProps as MentionsOptionProps,
} from 'rc-mentions/lib/Mentions';
import { composeRef } from 'rc-util/lib/ref';
// eslint-disable-next-line import/no-named-as-default
@ -14,9 +15,11 @@ import genPurePanel from '../_util/PurePanel';
import Spin from '../spin';
import type { InputStatus } from '../_util/statusUtils';
import { getMergedStatus, getStatusClassNames } from '../_util/statusUtils';
import warning from "../_util/warning";
import useStyle from './style';
export const { Option } = RcMentions;
function loadingFilterOption() {
@ -25,6 +28,10 @@ function loadingFilterOption() {
export type MentionPlacement = 'top' | 'bottom';
export type {
DataDrivenOptionProps as MentionsOptionProps,
} from 'rc-mentions/lib/Mentions';
export interface OptionProps {
value: string;
children: React.ReactNode;
@ -34,6 +41,7 @@ export interface OptionProps {
export interface MentionProps extends RcMentionsProps {
loading?: boolean;
status?: InputStatus;
options?: MentionsOptionProps[];
popupClassName?: string;
}
@ -69,6 +77,7 @@ const InternalMentions: React.ForwardRefRenderFunction<MentionsRef, MentionProps
filterOption,
children,
notFoundContent,
options,
status: customStatus,
popupClassName,
...restProps
@ -78,6 +87,16 @@ const InternalMentions: React.ForwardRefRenderFunction<MentionsRef, MentionProps
const [focused, setFocused] = React.useState(false);
const innerRef = React.useRef<MentionsRef>();
const mergedRef = composeRef(ref, innerRef);
// =================== Warning =====================
if (process.env.NODE_ENV !== 'production') {
warning(
!(children),
'Mentions',
'`Mentions.Option` is deprecated. Please use `options` instead.',
);
}
const { getPrefixCls, renderEmpty, direction } = React.useContext(ConfigContext);
const {
status: contextStatus,
@ -109,17 +128,11 @@ const InternalMentions: React.ForwardRefRenderFunction<MentionsRef, MentionProps
return (renderEmpty || defaultRenderEmpty)('Select');
};
const getOptions = () => {
if (loading) {
return (
<Option value="ANTD_SEARCHING" disabled>
<Spin size="small" />
</Option>
);
}
return children;
};
const mergedOptions = loading ? [{
value:'ANTD_SEARCHING',
disabled:true,
label:<Spin size="small" />,
}] : options;
const getFilterOption = (): any => {
if (loading) {
@ -157,9 +170,8 @@ const InternalMentions: React.ForwardRefRenderFunction<MentionsRef, MentionProps
onBlur={onBlur}
dropdownClassName={classNames(popupClassName, hashId)}
ref={mergedRef as any}
>
{getOptions()}
</RcMentions>
options={mergedOptions}
/>
);
if (hasFeedback) {
@ -203,7 +215,7 @@ Mentions.getMentions = (value: string = '', config: MentionsConfig = {}): Mentio
.map((str = ''): MentionsEntity | null => {
let hitPrefix: string | null = null;
prefixList.some(prefixStr => {
prefixList.some((prefixStr) => {
const startStr = str.slice(0, prefixStr.length);
if (startStr === prefixStr) {
hitPrefix = prefixStr;

View File

@ -27,14 +27,26 @@ demo:
<code src="./demo/status.tsx">自定义状态</code>
<code src="./demo/render-panel.tsx" debug>_InternalPanelDoNotUseOrYouWillBeFired</code>
## API
### 5.1.0 用法升级
```__react
import Alert from '../alert';
ReactDOM.render(<Alert message="在 5.1.0 版本后,我们提供了 <Mentions options={[...]} /> 的简写方式,有更好的性能和更方便的数据组织方式,开发者不再需要自行拼接 JSX。同时我们废弃了原先的写法你还是可以在 5.x 继续使用,但会在控制台看到警告,并会在 6.0 后移除。" />, mountNode);
```
```jsx
// >=5.1.0 可用,推荐的写法 ✅
const options = [{ value: 'sample', label: 'sample' }];
return <Mentions options={options} />;
// <5.1.0 可用>=5.1.0 时不推荐 🙅🏻‍♀️
<Mentions onChange={onChange}>
<Mentions.Option value="sample">Sample</Mentions.Option>
</Mentions>
</Mentions>;
```
## API
### Mentions
| 参数 | 说明 | 类型 | 默认值 | 版本 |
@ -57,6 +69,7 @@ demo:
| onResize | resize 回调 | function({ width, height }) | - | |
| onSearch | 搜索时触发 | (text: string, prefix: string) => void | - | |
| onSelect | 选择选项时触发 | (option: OptionProps, prefix: string) => void | - | |
| options | 选项配置 | [Options](#Option) | [] | 5.1.0 |
### Mentions 方法
@ -69,5 +82,9 @@ demo:
| 参数 | 说明 | 类型 | 默认值 |
| -------- | -------------- | --------- | ------ |
| children | 选项内容 | ReactNode | - |
| value | 选择时填充的值 | string | - |
| label | 选项的标题 | React.ReactNode | - |
| key | 选项的 key 值 | string | - |
| disabled | 是否可选 | boolean | - |
| className | css 类名 | string | - |
| style | 选项样式 | React.CSSProperties | - |

View File

@ -128,7 +128,7 @@
"rc-image": "~5.12.0",
"rc-input": "~0.1.4",
"rc-input-number": "~7.3.9",
"rc-mentions": "~1.11.0",
"rc-mentions": "~1.12.0",
"rc-menu": "~9.7.2",
"rc-motion": "^2.6.1",
"rc-notification": "~5.0.0-alpha.9",