From 107ec18d66db1a24f357ed9199b1bb1e6ffb95ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=91=E9=9B=A8?= Date: Mon, 21 Nov 2022 21:34:23 +0800 Subject: [PATCH] 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 --- components/mentions/__tests__/index.test.tsx | 19 +++- components/mentions/demo/async.tsx | 57 ++++++++++++ components/mentions/demo/autoSize.tsx | 25 +++++ components/mentions/demo/basic.tsx | 36 ++++++++ components/mentions/demo/form.tsx | 96 ++++++++++++++++++++ components/mentions/demo/placement.tsx | 25 +++++ components/mentions/demo/prefix.tsx | 33 +++++++ components/mentions/demo/readonly.tsx | 29 ++++++ components/mentions/demo/render-panel.tsx | 21 +++++ components/mentions/demo/status.tsx | 49 ++++++++++ components/mentions/index.en-US.md | 41 ++++++++- components/mentions/index.tsx | 62 +++++++++---- components/mentions/index.zh-CN.md | 42 ++++++++- package.json | 2 +- 14 files changed, 509 insertions(+), 28 deletions(-) create mode 100644 components/mentions/demo/async.tsx create mode 100644 components/mentions/demo/autoSize.tsx create mode 100644 components/mentions/demo/basic.tsx create mode 100644 components/mentions/demo/form.tsx create mode 100644 components/mentions/demo/placement.tsx create mode 100644 components/mentions/demo/prefix.tsx create mode 100644 components/mentions/demo/readonly.tsx create mode 100644 components/mentions/demo/render-panel.tsx create mode 100644 components/mentions/demo/status.tsx diff --git a/components/mentions/__tests__/index.test.tsx b/components/mentions/__tests__/index.test.tsx index 320336eafd09..725a4e5baee1 100644 --- a/components/mentions/__tests__/index.test.tsx +++ b/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,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( + + + + + , + ); + expect(errorSpy).toHaveBeenCalledWith( + 'Warning: [antd: Mentions] `Mentions.Option` is deprecated. Please use `options` instead.', + ); + }); }); diff --git a/components/mentions/demo/async.tsx b/components/mentions/demo/async.tsx new file mode 100644 index 000000000000..447c9189970e --- /dev/null +++ b/components/mentions/demo/async.tsx @@ -0,0 +1,57 @@ +import React, { useCallback, useRef, useState } from 'react'; +import { Mentions } from 'antd'; +import debounce from 'lodash/debounce'; + +const App: React.FC = () => { + const [loading, setLoading] = useState(false); + const [users, setUsers] = useState<{ login: string; avatar_url: string }[]>([]); + const ref = useRef(); + + const loadGithubUsers = (key: string) => { + if (!key) { + setUsers([]); + return; + } + + fetch(`https://api.github.com/search/users?q=${key}`) + .then((res) => res.json()) + .then(({ options = [] }) => { + if (ref.current !== key) return; + + setLoading(false); + setUsers(options.slice(0, 10)); + }); + }; + + const debounceLoadGithubUsers = useCallback(debounce(loadGithubUsers, 800), []); + + const onSearch = (search: string) => { + console.log('Search:', search); + ref.current = search; + setLoading(!!search); + setUsers([]); + + debounceLoadGithubUsers(search); + }; + + return ( + ({ + key: login, + value: login, + className: 'antd-demo-dynamic-option', + label: ( + <> + {login} + {login} + + ), + }))} + /> + ); +}; + +export default App; diff --git a/components/mentions/demo/autoSize.tsx b/components/mentions/demo/autoSize.tsx new file mode 100644 index 000000000000..3f6bc7847624 --- /dev/null +++ b/components/mentions/demo/autoSize.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { Mentions } from 'antd'; + +const App: React.FC = () => ( + +); + +export default App; diff --git a/components/mentions/demo/basic.tsx b/components/mentions/demo/basic.tsx new file mode 100644 index 000000000000..10f6f9150594 --- /dev/null +++ b/components/mentions/demo/basic.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { Mentions } from 'antd'; +import type { MentionsOptionProps } from 'antd/es/mentions'; + +const onChange = (value: string) => { + console.log('Change:', value); +}; + +const onSelect = (option: MentionsOptionProps) => { + console.log('select', option); +}; + +const App: React.FC = () => ( + +); + +export default App; diff --git a/components/mentions/demo/form.tsx b/components/mentions/demo/form.tsx new file mode 100644 index 000000000000..53879bbd0fea --- /dev/null +++ b/components/mentions/demo/form.tsx @@ -0,0 +1,96 @@ +import React from 'react'; +import { Button, Form, Mentions } from 'antd'; + +const { getMentions } = Mentions; + +const App: React.FC = () => { + const [form] = Form.useForm(); + + const onReset = () => { + form.resetFields(); + }; + + const onFinish = async () => { + try { + const values = await form.validateFields(); + console.log('Submit:', values); + } catch (errInfo) { + console.log('Error:', errInfo); + } + }; + + const checkMention = async (_: any, value: string) => { + const mentions = getMentions(value); + + if (mentions.length < 2) { + throw new Error('More than one must be selected!'); + } + }; + + return ( +
+ + + + + + + + +     + + +
+ ); +}; + +export default App; diff --git a/components/mentions/demo/placement.tsx b/components/mentions/demo/placement.tsx new file mode 100644 index 000000000000..1c38384e0487 --- /dev/null +++ b/components/mentions/demo/placement.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { Mentions } from 'antd'; + +const App: React.FC = () => ( + +); + +export default App; diff --git a/components/mentions/demo/prefix.tsx b/components/mentions/demo/prefix.tsx new file mode 100644 index 000000000000..8d66e81d6707 --- /dev/null +++ b/components/mentions/demo/prefix.tsx @@ -0,0 +1,33 @@ +import React, { useState } from 'react'; +import { Mentions } from 'antd'; + +const MOCK_DATA = { + '@': ['afc163', 'zombiej', 'yesmeck'], + '#': ['1.0', '2.0', '3.0'], +}; + +type PrefixType = keyof typeof MOCK_DATA; + +const App: React.FC = () => { + const [prefix, setPrefix] = useState('@'); + + const onSearch = (_: string, newPrefix: PrefixType) => { + setPrefix(newPrefix); + }; + + return ( + ({ + key: value, + value, + label: value, + }))} + /> + ); +}; + +export default App; diff --git a/components/mentions/demo/readonly.tsx b/components/mentions/demo/readonly.tsx new file mode 100644 index 000000000000..2c7fa1b159d6 --- /dev/null +++ b/components/mentions/demo/readonly.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { Mentions } from 'antd'; + +const options = ['afc163', 'zombiej', 'yesmeck'].map((value) => ({ + value, + key: value, + label: value, +})); + +const App: React.FC = () => ( + <> +
+ +
+ + +); + +export default App; diff --git a/components/mentions/demo/render-panel.tsx b/components/mentions/demo/render-panel.tsx new file mode 100644 index 000000000000..65199d789ee4 --- /dev/null +++ b/components/mentions/demo/render-panel.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { Mentions } from 'antd'; + +const { _InternalPanelDoNotUseOrYouWillBeFired: InternalMentions } = Mentions; + +const options = [ + { + value: 'afc163', + label: 'afc163', + }, + { + value: 'zombieJ', + label: 'zombieJ', + }, +] + +const App: React.FC = () => ( + +); + +export default App; diff --git a/components/mentions/demo/status.tsx b/components/mentions/demo/status.tsx new file mode 100644 index 000000000000..80c52d99db0b --- /dev/null +++ b/components/mentions/demo/status.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import { Mentions, Space } from 'antd'; +import type { MentionsOptionProps } from 'antd/es/mentions'; + +const onChange = (value: string) => { + console.log('Change:', value); +}; + +const onSelect = (option: MentionsOptionProps) => { + console.log('select', option); +}; + +const App: React.FC = () => { + const options = [ + { + value: 'afc163', + label: 'afc163', + }, + { + value: 'zombieJ', + label: 'zombieJ', + }, + { + value: 'yesmeck', + label: 'yesmeck', + }, + ]; + + return ( + + + + + ); +}; + +export default App; diff --git a/components/mentions/index.en-US.md b/components/mentions/index.en-US.md index ccdfc7ca6171..08cbf5b77348 100644 --- a/components/mentions/index.en-US.md +++ b/components/mentions/index.en-US.md @@ -1,8 +1,10 @@ --- category: Components -type: Data Entry +group: Data Entry title: Mentions cover: https://gw.alipayobjects.com/zos/alicdn/0pF5j477V/Mentions.svg +demo: + cols: 2 --- Mention component. @@ -11,14 +13,39 @@ Mention component. When you need to mention someone or something. -## API +## Examples + + +Basic +Asynchronous loading +With Form +Customize Trigger Token +disabled or readOnly +Placement +autoSize +Status +_InternalPanelDoNotUseOrYouWillBeFired + +### Usage upgrade after 5.1.0 + +```__react +import Alert from '../alert'; +ReactDOM.render(, mountNode); +``` ```jsx +// works when >=5.1.0, recommended ✅ +const options = [{ value: 'sample', label: 'sample' }]; +return ; + +// works when <5.1.0, deprecated when >=5.1.0 🙅🏻‍♀️ Sample - +; ``` +## API + ### Mention | Property | Description | Type | Default | Version | @@ -41,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 @@ -53,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 | - | diff --git a/components/mentions/index.tsx b/components/mentions/index.tsx index 9558e41af1ec..21976f8966e8 100644 --- a/components/mentions/index.tsx +++ b/components/mentions/index.tsx @@ -3,15 +3,22 @@ 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 import * as React from 'react'; import { ConfigContext } from '../config-provider'; import defaultRenderEmpty from '../config-provider/defaultRenderEmpty'; import { FormItemInputContext } from '../form/context'; +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; @@ -21,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; @@ -30,6 +41,8 @@ export interface OptionProps { export interface MentionProps extends RcMentionsProps { loading?: boolean; status?: InputStatus; + options?: MentionsOptionProps[]; + popupClassName?: string; } export interface MentionsRef extends RcMentionsRef {} @@ -51,6 +64,7 @@ interface MentionsEntity { interface CompoundedComponent extends React.ForwardRefExoticComponent> { Option: typeof Option; + _InternalPanelDoNotUseOrYouWillBeFired: typeof PurePanel; getMentions: (value: string, config?: MentionsConfig) => MentionsEntity[]; } @@ -63,7 +77,9 @@ const InternalMentions: React.ForwardRefRenderFunction(); 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, @@ -102,17 +128,11 @@ const InternalMentions: React.ForwardRefRenderFunction { - if (loading) { - return ( - - ); - } - - return children; - }; + const mergedOptions = loading ? [{ + value:'ANTD_SEARCHING', + disabled:true, + label:, + }] : options; const getFilterOption = (): any => { if (loading) { @@ -123,6 +143,9 @@ const InternalMentions: React.ForwardRefRenderFunction - {getOptions()} - + options={mergedOptions} + /> ); if (hasFeedback) { @@ -157,6 +181,7 @@ const InternalMentions: React.ForwardRefRenderFunction {mentions} @@ -165,7 +190,7 @@ const InternalMentions: React.ForwardRefRenderFunction( @@ -176,6 +201,11 @@ if (process.env.NODE_ENV !== 'production') { } Mentions.Option = Option; +// We don't care debug panel +/* istanbul ignore next */ +const PurePanel = genPurePanel(Mentions, 'mentions'); +Mentions._InternalPanelDoNotUseOrYouWillBeFired = PurePanel; + Mentions.getMentions = (value: string = '', config: MentionsConfig = {}): MentionsEntity[] => { const { prefix = '@', split = ' ' } = config; const prefixList: string[] = Array.isArray(prefix) ? prefix : [prefix]; @@ -185,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; diff --git a/components/mentions/index.zh-CN.md b/components/mentions/index.zh-CN.md index d303d5b9e39d..a7a51211a913 100644 --- a/components/mentions/index.zh-CN.md +++ b/components/mentions/index.zh-CN.md @@ -1,9 +1,11 @@ --- category: Components subtitle: 提及 -type: 数据录入 +group: 数据录入 title: Mentions cover: https://gw.alipayobjects.com/zos/alicdn/jPE-itMFM/Mentions.svg +demo: + cols: 2 --- 提及组件。 @@ -12,14 +14,39 @@ cover: https://gw.alipayobjects.com/zos/alicdn/jPE-itMFM/Mentions.svg 用于在输入中提及某人或某事,常用于发布、聊天或评论功能。 -## API +## 代码演示 + + +基本使用 +异步加载 +配合 Form 使用 +自定义触发字符 +无效或只读 +向上展开 +自动大小 +自定义状态 +_InternalPanelDoNotUseOrYouWillBeFired + +### 5.1.0 用法升级 + +```__react +import Alert from '../alert'; +ReactDOM.render(, mountNode); +``` ```jsx +// >=5.1.0 可用,推荐的写法 ✅ +const options = [{ value: 'sample', label: 'sample' }]; +return ; + +// <5.1.0 可用,>=5.1.0 时不推荐 🙅🏻‍♀️ Sample - +; ``` +## API + ### Mentions | 参数 | 说明 | 类型 | 默认值 | 版本 | @@ -42,6 +69,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) | [] | 5.1.0 | ### Mentions 方法 @@ -54,5 +82,9 @@ cover: https://gw.alipayobjects.com/zos/alicdn/jPE-itMFM/Mentions.svg | 参数 | 说明 | 类型 | 默认值 | | -------- | -------------- | --------- | ------ | -| children | 选项内容 | ReactNode | - | -| value | 选择时填充的值 | string | - | +| value | 选择时填充的值 | string | - | +| label | 选项的标题 | React.ReactNode | - | +| key | 选项的 key 值 | string | - | +| disabled | 是否可选 | boolean | - | +| className | css 类名 | string | - | +| style | 选项样式 | React.CSSProperties | - | diff --git a/package.json b/package.json index aae31ef004cb..2e827d925fe1 100644 --- a/package.json +++ b/package.json @@ -134,7 +134,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": "~4.6.0",