mirror of
https://gitee.com/ant-design/ant-design.git
synced 2024-12-03 04:30:06 +08:00
Merge pull request #2259 from RaoHai/MentionComponent
Mention Component
This commit is contained in:
commit
6158b39335
@ -48,3 +48,6 @@ export { Timeline }
|
||||
|
||||
import Tooltip from './tooltip';
|
||||
export { Tooltip }
|
||||
|
||||
import Mention from './mention';
|
||||
export { Mention };
|
||||
|
56
components/mention/demo/async.md
Normal file
56
components/mention/demo/async.md
Normal file
@ -0,0 +1,56 @@
|
||||
---
|
||||
order: 1
|
||||
title: 异步加载
|
||||
---
|
||||
|
||||
## zh-CN
|
||||
|
||||
匹配内容列表为异步返回时。
|
||||
|
||||
## en-US
|
||||
|
||||
asnyc
|
||||
|
||||
````jsx
|
||||
|
||||
import { Mention } from 'antd';
|
||||
|
||||
const users = ['afc163', 'benjycui', 'yiminghe', 'jljsj33', 'dqaria', 'RaoHai'];
|
||||
const AsyncMention = React.createClass({
|
||||
getInitialState() {
|
||||
return {
|
||||
suggestions: [],
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
fetchSuggestions(value, callback) {
|
||||
setTimeout(() => {
|
||||
callback(users.filter(item => item.indexOf(value) !== -1));
|
||||
}, 500);
|
||||
},
|
||||
onSearchChange(value) {
|
||||
this.fetchSuggestions(value, (suggestions) => {
|
||||
this.setState({
|
||||
suggestions,
|
||||
loading: false,
|
||||
});
|
||||
});
|
||||
this.setState({
|
||||
loading: true,
|
||||
});
|
||||
},
|
||||
render() {
|
||||
const { suggestions, loading } = this.state;
|
||||
return (<Mention
|
||||
loading={loading}
|
||||
suggestions={suggestions}
|
||||
onSearchChange={this.onSearchChange}
|
||||
/>);
|
||||
},
|
||||
});
|
||||
|
||||
ReactDOM.render(
|
||||
<AsyncMention />,
|
||||
mountNode
|
||||
);
|
||||
````
|
66
components/mention/demo/avatar.md
Normal file
66
components/mention/demo/avatar.md
Normal file
@ -0,0 +1,66 @@
|
||||
---
|
||||
order: 3
|
||||
title: 头像
|
||||
---
|
||||
|
||||
## zh-CN
|
||||
|
||||
自定义建议(含头像)
|
||||
|
||||
注意,自定义建议时,onSearchChange 必须不能为空。
|
||||
|
||||
## en-US
|
||||
|
||||
Customize suggestions
|
||||
|
||||
````jsx
|
||||
|
||||
import { Mention } from 'antd';
|
||||
const Nav = Mention.Nav;
|
||||
|
||||
const webFrameworks = [
|
||||
{ name: 'React', type: 'JavaScript', icon: 'https://zos.alipayobjects.com/rmsportal/LFIeMPzdLcLnEUe.svg' },
|
||||
{ name: 'Angular', type: 'JavaScript', icon: 'https://zos.alipayobjects.com/rmsportal/PJTbxSvzYWjDZnJ.png' },
|
||||
{ name: 'Laravel', type: 'PHP', icon: 'http://laravel-china.org/assets/img/laravel-logo.png' },
|
||||
{ name: 'Flask', type: 'Python', icon: 'https://zos.alipayobjects.com/rmsportal/xaypBUijfnpAlXE.png' },
|
||||
];
|
||||
|
||||
const CustomNavMention = React.createClass({
|
||||
getInitialState() {
|
||||
return {
|
||||
suggestions: [],
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
onSearchChange(value) {
|
||||
const searchValue = value.toLowerCase();
|
||||
const filtered = webFrameworks.filter(item =>
|
||||
item.name.toLowerCase().indexOf(searchValue) !== -1
|
||||
);
|
||||
|
||||
const suggestions = filtered.map(suggestion =>
|
||||
<Nav value={suggestion.name} data={suggestion} disabled={suggestion.disabled}>
|
||||
<span>
|
||||
<img alt={suggestion.name} style={{ height: 16, width: 16, marginRight: 5 }} src={suggestion.icon} />
|
||||
{suggestion.name} - {suggestion.type}
|
||||
</span>
|
||||
</Nav>);
|
||||
this.setState({
|
||||
suggestions,
|
||||
});
|
||||
},
|
||||
render() {
|
||||
const { suggestions, loading } = this.state;
|
||||
return (<Mention
|
||||
loading={loading}
|
||||
suggestions={suggestions}
|
||||
onSearchChange={this.onSearchChange}
|
||||
/>);
|
||||
},
|
||||
});
|
||||
|
||||
ReactDOM.render(
|
||||
<CustomNavMention />,
|
||||
mountNode
|
||||
);
|
||||
````
|
32
components/mention/demo/basic.md
Normal file
32
components/mention/demo/basic.md
Normal file
@ -0,0 +1,32 @@
|
||||
---
|
||||
order: 0
|
||||
title: 基本使用
|
||||
---
|
||||
|
||||
## zh-CN
|
||||
|
||||
基本使用
|
||||
|
||||
## en-US
|
||||
|
||||
Basic usage.
|
||||
|
||||
````jsx
|
||||
|
||||
import { Mention } from 'antd';
|
||||
const { toString, toEditorState } = Mention;
|
||||
|
||||
|
||||
function onChange(editorState) {
|
||||
console.log(toString(editorState));
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
<Mention
|
||||
onChange={onChange}
|
||||
defaultValue={toEditorState('@afc163')}
|
||||
suggestions={['afc163', 'benjycui', 'yiminghe', 'jljsj33', 'dqaria', 'RaoHai']}
|
||||
/>,
|
||||
mountNode
|
||||
);
|
||||
````
|
63
components/mention/demo/customTag.md
Normal file
63
components/mention/demo/customTag.md
Normal file
@ -0,0 +1,63 @@
|
||||
---
|
||||
order: 2
|
||||
title: 自定义建议
|
||||
---
|
||||
|
||||
## zh-CN
|
||||
|
||||
自定义建议
|
||||
|
||||
注意,自定义建议时,onSearchChange 必须不能为空。
|
||||
|
||||
## en-US
|
||||
|
||||
Customize suggestions
|
||||
|
||||
````jsx
|
||||
import { Mention } from 'antd';
|
||||
const Nav = Mention.Nav;
|
||||
|
||||
const webFrameworks = [
|
||||
{ name: 'React', type: 'JavaScript' },
|
||||
{ name: 'Angular', type: 'JavaScript' },
|
||||
{ name: 'Laravel', type: 'PHP', disabled: true },
|
||||
{ name: 'Flask', type: 'Python' },
|
||||
{ name: 'Django', type: 'Python' },
|
||||
];
|
||||
|
||||
const CustomNavMention = React.createClass({
|
||||
getInitialState() {
|
||||
return {
|
||||
suggestions: [],
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
onSearchChange(value) {
|
||||
const searchValue = value.toLowerCase();
|
||||
const filtered = webFrameworks.filter(item =>
|
||||
item.name.toLowerCase().indexOf(searchValue) !== -1
|
||||
);
|
||||
|
||||
const suggestions = filtered.map(suggestion =>
|
||||
<Nav value={suggestion.name} >
|
||||
<span>{suggestion.name} - {suggestion.type} </span>
|
||||
</Nav>);
|
||||
this.setState({
|
||||
suggestions,
|
||||
});
|
||||
},
|
||||
render() {
|
||||
const { suggestions, loading } = this.state;
|
||||
return (<Mention
|
||||
loading={loading}
|
||||
suggestions={suggestions}
|
||||
onSearchChange={this.onSearchChange}
|
||||
/>);
|
||||
},
|
||||
});
|
||||
|
||||
ReactDOM.render(
|
||||
<CustomNavMention />,
|
||||
mountNode
|
||||
);
|
||||
````
|
32
components/mention/demo/multilines.md
Normal file
32
components/mention/demo/multilines.md
Normal file
@ -0,0 +1,32 @@
|
||||
---
|
||||
order: 4
|
||||
title: 多行
|
||||
---
|
||||
|
||||
## zh-CN
|
||||
|
||||
多行模式,多行模式必须指定高度。
|
||||
|
||||
## en-US
|
||||
|
||||
Multi lines mode.
|
||||
|
||||
````jsx
|
||||
|
||||
import { Mention } from 'antd';
|
||||
const { toString } = Mention;
|
||||
|
||||
function onChange(editorState) {
|
||||
console.log(toString(editorState));
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
<Mention
|
||||
style={{ width: '100%', height: 200 }}
|
||||
onChange={onChange}
|
||||
suggestions={['afc163', 'benjycui', 'yiminghe', 'jljsj33', 'dqaria', 'RaoHai']}
|
||||
multiLines
|
||||
/>,
|
||||
mountNode
|
||||
);
|
||||
````
|
43
components/mention/index.en-US.md
Normal file
43
components/mention/index.en-US.md
Normal file
@ -0,0 +1,43 @@
|
||||
---
|
||||
category: Components
|
||||
chinese: 提及
|
||||
cols: 1
|
||||
type: Views
|
||||
english: Mention
|
||||
---
|
||||
|
||||
Mention component。
|
||||
|
||||
## When To Use
|
||||
|
||||
When need to mention someone or something.
|
||||
|
||||
```html
|
||||
<Mention
|
||||
onChange={onChange}
|
||||
suggestions={['afc163', 'benjycui', 'yiminghe', 'jljsj33', 'dqaria', 'RaoHai']}
|
||||
/>,
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Mention props
|
||||
|
||||
| Property | Description | Type | Default |
|
||||
|----------|---------------|----------|--------------|
|
||||
| suggestions | suggestion content | Array<string> or Array<Mention.Nav> | [] |
|
||||
| suggestionStyle | style of suggestion container | Objet | {} |
|
||||
| onSearchChange | Callback function called when search content changes | function(value:String) | [] |
|
||||
| onChange | Callback function called when content of input changes | function(editorState: EditorState) | null |
|
||||
| notFoundContent| suggestion when suggestions empty | string | '无匹配结果,轻敲空格完成输入' |
|
||||
| loading | loading mode | boolean | false |
|
||||
| multiLines | multilines mode | boolean | false |
|
||||
| defaultValue | default value | EditorState, you can use `Mention.toEditorState` to convert text to `EditorState` | null |
|
||||
| value | core state of mention | EditorState | null |
|
||||
|
||||
### Nav props
|
||||
|
||||
| Property | Description | Type | Default |
|
||||
|----------|---------------|----------|--------------|
|
||||
| value | value of suggestion,the value will insert into input filed while selected | string | "" |
|
||||
| children | suggestion content | Objet | {} |
|
101
components/mention/index.tsx
Normal file
101
components/mention/index.tsx
Normal file
@ -0,0 +1,101 @@
|
||||
import * as React from 'react';
|
||||
import RcMention, { Nav, toString, toEditorState } from 'rc-editor-mention';
|
||||
import classnames from 'classnames';
|
||||
|
||||
export interface MentionProps {
|
||||
prefixCls: string;
|
||||
suggestionStyle?: Object;
|
||||
suggestions?: Array<any>;
|
||||
onSearchChange?: Function;
|
||||
onChange?: Function;
|
||||
notFoundContent?: any;
|
||||
loading?: Boolean;
|
||||
style?: Object;
|
||||
defaultValue?: string;
|
||||
className?: string;
|
||||
multiLines?: Boolean;
|
||||
}
|
||||
|
||||
export interface MentionState {
|
||||
suggestions?: Array<any>;
|
||||
focus?: Boolean;
|
||||
}
|
||||
|
||||
export default class Mention extends React.Component<MentionProps, MentionState> {
|
||||
static Nav = Nav;
|
||||
static toString = toString;
|
||||
static toEditorState = toEditorState;
|
||||
static defaultProps = {
|
||||
prefixCls: 'ant-mention',
|
||||
suggestions: [],
|
||||
notFoundContent: '无匹配结果,轻敲空格完成输入',
|
||||
loading: false,
|
||||
multiLines: false,
|
||||
};
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
suggestions: props.suggestions,
|
||||
focus: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.setState({
|
||||
suggestions: nextProps.suggestions,
|
||||
});
|
||||
}
|
||||
|
||||
onSearchChange(value) {
|
||||
if (this.props.onSearchChange) {
|
||||
return this.props.onSearchChange(value);
|
||||
}
|
||||
return this.defaultSearchChange(value);
|
||||
}
|
||||
|
||||
onChange(editorState) {
|
||||
if (this.props.onChange) {
|
||||
this.props.onChange(editorState);
|
||||
}
|
||||
}
|
||||
|
||||
defaultSearchChange(value: String): void {
|
||||
const searchValue = value.toLowerCase();
|
||||
const filteredSuggestions = this.props.suggestions.filter(
|
||||
suggestion => suggestion.toLowerCase().indexOf(searchValue) !== -1
|
||||
);
|
||||
this.setState({
|
||||
suggestions: filteredSuggestions,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className, prefixCls, style, multiLines, defaultValue } = this.props;
|
||||
let { notFoundContent } = this.props;
|
||||
|
||||
const { suggestions, focus } = this.state;
|
||||
const cls = classnames({
|
||||
[className]: !!className,
|
||||
['active']: focus,
|
||||
});
|
||||
|
||||
if (this.props.loading) {
|
||||
notFoundContent = <i className="anticon anticon-loading"></i>;
|
||||
}
|
||||
|
||||
return <RcMention
|
||||
{...this.props}
|
||||
className={cls}
|
||||
prefixCls={prefixCls}
|
||||
style={style}
|
||||
defaultValue={defaultValue}
|
||||
multiLines={multiLines}
|
||||
onSearchChange={this.onSearchChange.bind(this)}
|
||||
onChange={this.onChange.bind(this)}
|
||||
onFocus={() => this.setState({focus: true})}
|
||||
onBlur={() => this.setState({focus: false})}
|
||||
suggestions={suggestions}
|
||||
notFoundContent={notFoundContent}
|
||||
/>;
|
||||
}
|
||||
}
|
44
components/mention/index.zh-CN.md
Normal file
44
components/mention/index.zh-CN.md
Normal file
@ -0,0 +1,44 @@
|
||||
---
|
||||
category: Components
|
||||
chinese: 提及
|
||||
cols: 1
|
||||
type: Views
|
||||
english: Mention
|
||||
---
|
||||
|
||||
提及组件。(圈人组件)
|
||||
|
||||
## 何时使用
|
||||
|
||||
用于在输入中提及某人或某事。
|
||||
|
||||
```html
|
||||
<Mention
|
||||
onChange={onChange}
|
||||
suggestions={['afc163', 'benjycui', 'yiminghe', 'jljsj33', 'dqaria', 'RaoHai']}
|
||||
/>,
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Mention props
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
|----------|---------------|----------|--------------|
|
||||
| suggestions | 建议内容 | Array<string> or Array<Mention.Nav> | [] |
|
||||
| suggestionStyle | 弹出下拉框样式 | Objet | {} |
|
||||
| onSearchChange | 输入框中 @ 变化时回调 | function(value:String) | [] |
|
||||
| onChange | 输入框内容变化时回调 | function(editorState: EditorState) | null |
|
||||
| notFoundContent| 未找到时的内容 | string | '无匹配结果,轻敲空格完成输入' |
|
||||
| loading | 加载中 | boolean | false |
|
||||
| multiLines | 多行模式 | boolean | false |
|
||||
| defaultValue | 默认值 | EditorState, 可以用 Mention.toEditorState(text) 把文字转换成 EditorState | null |
|
||||
| value | 值 | EditorState | null |
|
||||
|
||||
|
||||
### Nav props
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
|----------|---------------|----------|--------------|
|
||||
| value | 建议值,选择建议时,用此值插入到输入框中 | string | "" |
|
||||
| children | 建议内容 | Objet | {} |
|
98
components/mention/style/index.less
Normal file
98
components/mention/style/index.less
Normal file
@ -0,0 +1,98 @@
|
||||
@import "../../style/themes/default";
|
||||
@import "../../style/mixins/index";
|
||||
@import '../../input/style/mixin.less';
|
||||
|
||||
.ant-mention-wrapper {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
&.active .ant-mention-editor {
|
||||
.active;
|
||||
}
|
||||
.ant-mention-editor {
|
||||
.input;
|
||||
padding: 0;
|
||||
}
|
||||
.ant-mention-editor-wrapper {
|
||||
overflow-y: auto;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.DraftEditor-editorContainer .public-DraftEditor-content {
|
||||
height: auto;
|
||||
padding: 4px 7px;
|
||||
}
|
||||
|
||||
.ant-mention-dropdown {
|
||||
margin-top: 1.5em;
|
||||
max-height: 250px;
|
||||
min-width: 120px;
|
||||
background-color: white;
|
||||
border: 1px solid @border-color-base;
|
||||
box-shadow: @box-shadow-base;
|
||||
border-radius: @border-radius-base;
|
||||
box-sizing: border-box;
|
||||
z-index: @zindex-dropdown;
|
||||
left: -9999px;
|
||||
top: -9999px;
|
||||
position: absolute;
|
||||
outline: none;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
font-size: @font-size-base;
|
||||
&-notfound.ant-mention-dropdown-item {
|
||||
color: #ccc;
|
||||
|
||||
.anticon-loading {
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
&-item {
|
||||
position: relative;
|
||||
display: block;
|
||||
padding: 7px 15px;
|
||||
font-weight: normal;
|
||||
color: #666;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
transition: background 0.3s ease;
|
||||
|
||||
&:hover,
|
||||
&.focus,
|
||||
&-active {
|
||||
background-color: tint(@primary-color, 90%);
|
||||
}
|
||||
|
||||
&-disabled {
|
||||
color: #ccc;
|
||||
cursor: not-allowed;
|
||||
|
||||
&:hover {
|
||||
color: #ccc;
|
||||
background-color: #fff;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
&-selected {
|
||||
&,
|
||||
&:hover {
|
||||
background-color: #f7f7f7;
|
||||
font-weight: bold;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
&-divider {
|
||||
height: 1px;
|
||||
margin: 1px 0;
|
||||
overflow: hidden;
|
||||
background-color: #e5e5e5;
|
||||
line-height: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
1
components/mention/style/index.tsx
Normal file
1
components/mention/style/index.tsx
Normal file
@ -0,0 +1 @@
|
||||
import './index.less';
|
@ -47,6 +47,7 @@
|
||||
"rc-collapse": "~1.6.4",
|
||||
"rc-dialog": "~6.2.1",
|
||||
"rc-dropdown": "~1.4.8",
|
||||
"rc-editor-mention": "^0.1.0",
|
||||
"rc-form": "~0.17.1",
|
||||
"rc-input-number": "~2.5.14",
|
||||
"rc-menu": "~4.12.4",
|
||||
|
Loading…
Reference in New Issue
Block a user