@@ -3,23 +3,54 @@ import ReactDOM from 'react-dom'
33import _ from 'lodash'
44import linkState from 'boost/linkState'
55
6+ function isNotEmptyString ( str ) {
7+ return _ . isString ( str ) && str . length > 0
8+ }
9+
610export default class TagSelect extends React . Component {
711 constructor ( props ) {
812 super ( props )
913
1014 this . state = {
11- input : ''
15+ input : '' ,
16+ isInputFocused : false
1217 }
1318 }
1419
15- handleKeyDown ( e ) {
16- if ( e . keyCode !== 13 ) return false
17- e . preventDefault ( )
20+ componentDidMount ( ) {
21+ this . blurInputBlurHandler = e => {
22+ if ( ReactDOM . findDOMNode ( this . refs . tagInput ) !== document . activeElement ) {
23+ this . setState ( { isInputFocused : false } )
24+ }
25+ }
26+ window . addEventListener ( 'click' , this . blurInputBlurHandler )
27+ }
28+
29+ componentWillUnmount ( e ) {
30+ window . removeEventListener ( 'click' , this . blurInputBlurHandler )
31+ }
1832
33+ // Suggestは必ずInputの下に位置するようにする
34+ componentDidUpdate ( ) {
35+ if ( this . shouldShowSuggest ( ) ) {
36+ let inputRect = ReactDOM . findDOMNode ( this . refs . tagInput ) . getBoundingClientRect ( )
37+ let suggestElement = ReactDOM . findDOMNode ( this . refs . suggestTags )
38+ if ( suggestElement != null ) {
39+ suggestElement . style . top = inputRect . top + 20 + 'px'
40+ suggestElement . style . left = inputRect . left + 'px'
41+ }
42+ }
43+ }
44+
45+ shouldShowSuggest ( ) {
46+ return this . state . isInputFocused && isNotEmptyString ( this . state . input )
47+ }
48+
49+ addTag ( tag , clearInput = true ) {
1950 let tags = this . props . tags . slice ( 0 )
20- let newTag = this . state . input . trim ( )
51+ let newTag = tag . trim ( )
2152
22- if ( newTag . length === 0 ) {
53+ if ( newTag . length === 0 && clearInput ) {
2354 this . setState ( { input : '' } )
2455 return
2556 }
@@ -30,13 +61,38 @@ export default class TagSelect extends React.Component {
3061 if ( _ . isFunction ( this . props . onChange ) ) {
3162 this . props . onChange ( newTag , tags )
3263 }
33- this . setState ( { input : '' } )
64+ if ( clearInput ) this . setState ( { input : '' } )
65+ }
66+
67+ handleKeyDown ( e ) {
68+ switch ( e . keyCode ) {
69+ case 8 :
70+ {
71+ if ( this . state . input . length > 0 ) break
72+ e . preventDefault ( )
73+
74+ let tags = this . props . tags . slice ( 0 )
75+ tags . pop ( )
76+
77+ this . props . onChange ( null , tags )
78+ }
79+ break
80+ case 13 :
81+ {
82+ e . preventDefault ( )
83+ this . addTag ( this . state . input )
84+ }
85+ }
3486 }
3587
3688 handleThisClick ( e ) {
3789 ReactDOM . findDOMNode ( this . refs . tagInput ) . focus ( )
3890 }
3991
92+ handleInputFocus ( e ) {
93+ this . setState ( { isInputFocused : true } )
94+ }
95+
4096 handleItemRemoveButton ( tag ) {
4197 return e => {
4298 e . stopPropagation ( )
@@ -50,33 +106,63 @@ export default class TagSelect extends React.Component {
50106 }
51107 }
52108
109+ handleSuggestClick ( tag ) {
110+ return e => {
111+ this . addTag ( tag )
112+ }
113+ }
114+
53115 render ( ) {
54- var tagElements = _ . isArray ( this . props . tags )
116+ let { tags, suggestTags } = this . props
117+
118+ let tagElements = _ . isArray ( tags )
55119 ? this . props . tags . map ( tag => (
56120 < span key = { tag } className = 'tagItem' >
57121 < button onClick = { e => this . handleItemRemoveButton ( tag ) ( e ) } className = 'tagRemoveBtn' > < i className = 'fa fa-fw fa-times' /> </ button >
58122 < span className = 'tagLabel' > { tag } </ span >
59123 </ span > ) )
60124 : null
61125
126+ let suggestElements = this . shouldShowSuggest ( ) ? suggestTags
127+ . filter ( tag => {
128+ return tag . match ( this . state . input )
129+ } )
130+ . map ( tag => {
131+ return < button onClick = { e => this . handleSuggestClick ( tag ) ( e ) } key = { tag } > { tag } </ button >
132+ } )
133+ : null
134+
62135 return (
63136 < div className = 'TagSelect' onClick = { e => this . handleThisClick ( e ) } >
64- { tagElements }
65- < input
66- type = 'text'
67- onKeyDown = { e => this . handleKeyDown ( e ) }
68- ref = 'tagInput'
69- valueLink = { this . linkState ( 'input' ) }
70- placeholder = 'Click here to add tags'
71- className = 'tagInput' />
137+ < div className = 'tags' >
138+ { tagElements }
139+ < input
140+ type = 'text'
141+ onKeyDown = { e => this . handleKeyDown ( e ) }
142+ ref = 'tagInput'
143+ valueLink = { this . linkState ( 'input' ) }
144+ placeholder = 'Click here to add tags'
145+ className = 'tagInput'
146+ onFocus = { e => this . handleInputFocus ( e ) }
147+ />
148+ </ div >
149+ { suggestElements != null && suggestElements . length > 0
150+ ? (
151+ < div ref = 'suggestTags' className = 'suggestTags' >
152+ { suggestElements }
153+ </ div >
154+ )
155+ : null
156+ }
72157 </ div >
73158 )
74159 }
75160}
76161
77162TagSelect . propTypes = {
78163 tags : PropTypes . arrayOf ( PropTypes . string ) ,
79- onChange : PropTypes . func
164+ onChange : PropTypes . func ,
165+ suggestTags : PropTypes . array
80166}
81167
82168TagSelect . prototype . linkState = linkState
0 commit comments