Browse Source

feat: support options (#38876)

* feat: support options

* feat: update package

* feat: update package

* feat: update doc

* feat: update doc

* feat: update doc

* feat:  add test case
pull/38913/head
黑雨 2 years ago
committed by GitHub
parent
commit
066be35de3
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 37
      components/mentions/__tests__/index.test.tsx
  2. 27
      components/mentions/demo/async.md
  3. 27
      components/mentions/demo/autoSize.md
  4. 28
      components/mentions/demo/basic.md
  5. 49
      components/mentions/demo/form.md
  6. 27
      components/mentions/demo/placement.md
  7. 17
      components/mentions/demo/prefix.md
  8. 33
      components/mentions/demo/readonly.md
  9. 23
      components/mentions/index.en-US.md
  10. 31
      components/mentions/index.tsx
  11. 29
      components/mentions/index.zh-CN.md
  12. 6
      package.json

37
components/mentions/__tests__/index.test.tsx

@ -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,39 @@ describe('Mentions', () => {
expect(wrapper.container.querySelectorAll('li.ant-mentions-dropdown-menu-item').length).toBe(1);
expect(wrapper.container.querySelectorAll('.bamboo-light').length).toBeTruthy();
});
it('do not lose label when use children Option', () => {
const wrapper = render( <Mentions
style={{ width: '100%' }}
>
<Mentions.Option value="afc163">Afc163</Mentions.Option>
<Mentions.Option value="zombieJ">ZombieJ</Mentions.Option>
<Mentions.Option value="yesmeck">Yesmeck</Mentions.Option>
</Mentions>);
simulateInput(wrapper, '@');
const {container} = wrapper
fireEvent.mouseEnter(
container.querySelector('li.ant-mentions-dropdown-menu-item:last-child')!,
);
fireEvent.focus(container.querySelector('textarea')!);
act(() => {
jest.runAllTimers();
});
expect(
wrapper.container.querySelector('.ant-mentions-dropdown-menu-item-active')?.textContent,
).toBe('Yesmeck');
});
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.',
);
});
});

27
components/mentions/demo/async.md

@ -14,11 +14,10 @@ title:
async
```tsx
import React, { useCallback, useRef, useState } from 'react';
import { Mentions } from 'antd';
import debounce from 'lodash/debounce';
import React, { useCallback, useRef, useState } from 'react';
const { Option } = Mentions;
const App: React.FC = () => {
const [loading, setLoading] = useState(false);
const [users, setUsers] = useState<{ login: string; avatar_url: string }[]>([]);
@ -52,14 +51,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">
<img src={avatar} alt={login} />
<span>{login}</span>
</Option>
))}
</Mentions>
<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>
</>
),
}))}
/>
);
};

27
components/mentions/demo/autoSize.md

@ -14,17 +14,28 @@ title:
Height autoSize.
```tsx
import { Mentions } from 'antd';
import React from 'react';
const { Option } = Mentions;
import { Mentions } from 'antd';
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;

28
components/mentions/demo/basic.md

@ -14,17 +14,15 @@ title:
Basic usage.
```tsx
import { Mentions } from 'antd';
import type { OptionProps } from 'antd/es/mentions';
import React from 'react';
const { Option } = Mentions;
import { Mentions } from 'antd';
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);
};
@ -34,11 +32,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;

49
components/mentions/demo/form.md

@ -14,10 +14,10 @@ title:
Controlled mode, for example, to work with `Form`.
```tsx
import { Button, Form, Mentions } from 'antd';
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();
@ -52,11 +52,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"
@ -65,11 +77,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">

27
components/mentions/demo/placement.md

@ -14,17 +14,28 @@ title:
Change the suggestions placement.
```tsx
import { Mentions } from 'antd';
import React from 'react';
const { Option } = Mentions;
import { Mentions } from 'antd';
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;

17
components/mentions/demo/prefix.md

@ -14,10 +14,8 @@ title:
Customize Trigger Token by `prefix` props. Default to `@`, `Array<string>` also supported.
```tsx
import { Mentions } from 'antd';
import React, { useState } from 'react';
const { Option } = Mentions;
import { Mentions } from 'antd';
const MOCK_DATA = {
'@': ['afc163', 'zombiej', 'yesmeck'],
@ -39,13 +37,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,
}))}
/>
);
};

33
components/mentions/demo/readonly.md

@ -14,28 +14,31 @@ title:
Configurate `disabled` and `readOnly`.
```tsx
import { Mentions } from 'antd';
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}
/>
</>
);

23
components/mentions/index.en-US.md

@ -11,14 +11,26 @@ Mention component.
When you need to mention someone or something.
## API
### Usage upgrade after 4.24.4
```__react
import Alert from '../alert';
ReactDOM.render(<Alert message="After version 4.24.4, 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 5.0." />, mountNode);
```
```jsx
// works when >=4.24.4, recommended ✅
const options = [{ value: 'sample', label: 'sample' }];
return <Mentions options={options} />;
// works when <4.24.4, deprecated when >=4.24.4 🙅🏻‍♀️
<Mentions onChange={onChange}>
<Mentions.Option value="sample">Sample</Mentions.Option>
</Mentions>
</Mentions>;
```
## API
### Mention
| Property | Description | Type | Default | Version |
@ -41,6 +53,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) | \[] | 4.24.4 |
### Mention methods
@ -53,5 +66,9 @@ 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 | - |

31
components/mentions/index.tsx

@ -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';
import * as React from 'react';
@ -12,6 +13,7 @@ import { FormItemInputContext } from '../form/context';
import Spin from '../spin';
import type { InputStatus } from '../_util/statusUtils';
import { getMergedStatus, getStatusClassNames } from '../_util/statusUtils';
import warning from '../_util/warning';
export const { Option } = RcMentions;
@ -21,6 +23,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;
@ -30,6 +36,7 @@ export interface OptionProps {
export interface MentionProps extends RcMentionsProps {
loading?: boolean;
status?: InputStatus;
options?: MentionsOptionProps[];
}
export interface MentionsRef extends RcMentionsRef {}
@ -63,6 +70,7 @@ const InternalMentions: React.ForwardRefRenderFunction<MentionsRef, MentionProps
filterOption,
children,
notFoundContent,
options,
status: customStatus,
...restProps
},
@ -71,6 +79,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,
@ -114,6 +132,16 @@ const InternalMentions: React.ForwardRefRenderFunction<MentionsRef, MentionProps
return children;
};
const mergedOptions = loading
? [
{
value: 'ANTD_SEARCHING',
disabled: true,
label: <Spin size="small" />,
},
]
: options;
const getFilterOption = (): any => {
if (loading) {
return loadingFilterOption;
@ -145,6 +173,7 @@ const InternalMentions: React.ForwardRefRenderFunction<MentionsRef, MentionProps
onFocus={onFocus}
onBlur={onBlur}
ref={mergedRef as any}
options={mergedOptions}
>
{getOptions()}
</RcMentions>
@ -185,7 +214,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;

29
components/mentions/index.zh-CN.md

@ -12,14 +12,26 @@ cover: https://gw.alipayobjects.com/zos/alicdn/jPE-itMFM/Mentions.svg
用于在输入中提及某人或某事,常用于发布、聊天或评论功能。
## API
### 4.24.4 用法升级
```__react
import Alert from '../alert';
ReactDOM.render(<Alert message="在 4.24.4 版本后,我们提供了 <Mentions options={[...]} /> 的简写方式,有更好的性能和更方便的数据组织方式,开发者不再需要自行拼接 JSX。同时我们废弃了原先的写法,你还是可以在 4.x 继续使用,但会在控制台看到警告,并会在 5.0 后移除。" />, mountNode);
```
```jsx
// >=4.24.4 可用,推荐的写法 ✅
const options = [{ value: 'sample', label: 'sample' }];
return <Mentions options={options} />;
// <4.24.4 可用>=4.24.4 时不推荐 🙅🏻‍♀️
<Mentions onChange={onChange}>
<Mentions.Option value="sample">Sample</Mentions.Option>
</Mentions>
</Mentions>;
```
## API
### Mentions
| 参数 | 说明 | 类型 | 默认值 | 版本 |
@ -42,6 +54,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/jPE-itMFM/Mentions.svg
| onResize | resize 回调 | function({ width, height }) | - | |
| onSearch | 搜索时触发 | (text: string, prefix: string) => void | - | |
| onSelect | 选择选项时触发 | (option: OptionProps, prefix: string) => void | - | |
| options | 选项配置 | [Options](#Option) | [] | 4.24.4 |
### Mentions 方法
@ -52,7 +65,11 @@ cover: https://gw.alipayobjects.com/zos/alicdn/jPE-itMFM/Mentions.svg
### Option
| 参数 | 说明 | 类型 | 默认值 |
| -------- | -------------- | --------- | ------ |
| children | 选项内容 | ReactNode | - |
| value | 选择时填充的值 | string | - |
| 参数 | 说明 | 类型 | 默认值 |
| --------- | -------------- | ------------------- | ------ |
| value | 选择时填充的值 | string | - |
| label | 选项的标题 | React.ReactNode | - |
| key | 选项的 key 值 | string | - |
| disabled | 是否可选 | boolean | - |
| className | css 类名 | string | - |
| style | 选项样式 | React.CSSProperties | - |

6
package.json

@ -134,8 +134,8 @@
"rc-image": "~5.12.0",
"rc-input": "~0.1.4",
"rc-input-number": "~7.3.9",
"rc-mentions": "~1.11.0",
"rc-menu": "~9.7.2",
"rc-mentions": "~1.13.1",
"rc-menu": "~9.8.0",
"rc-motion": "^2.6.1",
"rc-notification": "~4.6.0",
"rc-pagination": "~3.2.0",
@ -149,7 +149,7 @@
"rc-steps": "~5.0.0-alpha.2",
"rc-switch": "~3.2.0",
"rc-table": "~7.26.0",
"rc-tabs": "~12.3.0",
"rc-tabs": "~12.4.1",
"rc-textarea": "~0.4.5",
"rc-tooltip": "~5.2.0",
"rc-tree": "~5.7.0",

Loading…
Cancel
Save