@@ -6,7 +6,7 @@ import classNames from 'classnames';
66import Mousetrap from 'mousetrap' ;
77
88import VisuallyHidden from '@reach/visually-hidden' ;
9- import ReactAutocomplete from 'react-autocomplete ' ;
9+ import Autosuggest from 'react-autosuggest ' ;
1010
1111import { urlHandler } from '../../utils' ;
1212import { BaseComponent } from '../base-component' ;
@@ -20,26 +20,32 @@ class Search extends BaseComponent {
2020 this . useShadow = false ;
2121 this . defaultMaxResults = 10 ;
2222
23+ // Autosuggest is a controlled component.
24+ // This means that you need to provide an input value
25+ // and an onChange handler that updates this value (see below).
26+ // Suggestions also need to be provided to the Autosuggest,
27+ // and they are initially empty because the Autosuggest is closed.
2328 this . state = {
24- isOpen : false ,
2529 value : '' ,
30+ suggestions : [ ] ,
2631 } ;
2732
2833 this . receiveIframeMessage = this . receiveIframeMessage . bind ( this ) ;
34+ this . onChange = this . onChange . bind ( this ) ;
2935 this . toggleSearch = this . toggleSearch . bind ( this ) ;
30- this . clearSearch = this . clearSearch . bind ( this ) ;
36+ // this.clearSearch = this.clearSearch.bind(this);
3137 this . closeSearch = this . closeSearch . bind ( this ) ;
3238 this . openSearch = this . openSearch . bind ( this ) ;
3339
34- this . data = [ ] ;
40+ this . items = [ ] ;
3541 for ( const patternType in window . patternPaths ) {
3642 if ( window . patternPaths . hasOwnProperty ( patternType ) ) {
3743 for ( const pattern in window . patternPaths [ patternType ] ) {
3844 if ( window . patternPaths [ patternType ] . hasOwnProperty ( pattern ) ) {
3945 const obj = { } ;
4046 obj . label = patternType + '-' + pattern ;
4147 obj . id = window . patternPaths [ patternType ] [ pattern ] ;
42- this . data . push ( obj ) ;
48+ this . items . push ( obj ) ;
4349 }
4450 }
4551 }
@@ -58,7 +64,7 @@ class Search extends BaseComponent {
5864 }
5965
6066 rendered ( ) {
61- this . inputElement = this . input ;
67+ this . inputElement = this . querySelector ( '.js-c-typeahead__input' ) ;
6268 }
6369
6470 static props = {
@@ -71,19 +77,6 @@ class Search extends BaseComponent {
7177 // External Redux store not yet in use
7278 _stateChanged ( state ) { }
7379
74- // update the iframe via the history api handler
75- passPath ( item ) {
76- this . setState ( { value : item } ) ;
77- this . closeSearch ( ) ;
78- const obj = JSON . stringify ( {
79- event : 'patternLab.updatePath' ,
80- path : urlHandler . getFileName ( item ) ,
81- } ) ;
82- document
83- . querySelector ( '.pl-js-iframe' )
84- . contentWindow . postMessage ( obj , urlHandler . targetOrigin ) ;
85- }
86-
8780 toggleSearch ( ) {
8881 if ( ! this . state . isOpen ) {
8982 this . openSearch ( ) ;
@@ -131,10 +124,14 @@ class Search extends BaseComponent {
131124 }
132125 }
133126
134- // highlights keywords in the search results in a react-friendly way + limits total number / max displayed
135- filterAndLimitResults ( ) {
136- const data = this . data ;
127+ getSuggestionValue = suggestion => suggestion . label ;
137128
129+ renderSuggestion ( item , { query, isHighlighted } ) {
130+ return < span > { item . highlightedLabel } </ span > ;
131+ }
132+
133+ // highlights keywords in the search results in a react-friendly way + limits total number / max displayed
134+ getSuggestions ( value ) {
138135 const maxResults = this . props . maxResults
139136 ? this . props . maxResults
140137 : this . defaultMaxResults ;
@@ -150,8 +147,8 @@ class Search extends BaseComponent {
150147 minMatchCharLength : 1 ,
151148 keys : [ 'label' ] ,
152149 } ;
153- const fuse = new Fuse ( data , fuseOptions ) ;
154- const results = fuse . search ( this . state . value ? this . state . value : '' ) ;
150+ const fuse = new Fuse ( this . items , fuseOptions ) ;
151+ const results = fuse . search ( value ) ;
155152
156153 const highlighter = function ( item ) {
157154 const resultItem = item ;
@@ -202,9 +199,51 @@ class Search extends BaseComponent {
202199 }
203200 }
204201
202+ // Autosuggest calls this when a search result is selected
203+ onChange = ( event , { newValue } ) => {
204+ const patternName = urlHandler . getFileName ( newValue ) ;
205+
206+ if ( patternName ) {
207+ const obj = JSON . stringify ( {
208+ event : 'patternLab.updatePath' ,
209+ path : patternName ,
210+ } ) ;
211+
212+ document
213+ . querySelector ( '.pl-js-iframe' )
214+ . contentWindow . postMessage ( obj , urlHandler . targetOrigin ) ;
215+ }
216+
217+ this . setState ( {
218+ value : newValue ,
219+ } ) ;
220+ } ;
221+
222+ // Autosuggest calls this every time you need to update suggestions.
223+ onSuggestionsFetchRequested = ( { value } ) => {
224+ this . setState ( { isOpen : true } ) ;
225+
226+ this . setState ( {
227+ suggestions : this . getSuggestions ( value ) ,
228+ } ) ;
229+ } ;
230+
231+ // Autosuggest calls this every time you need to clear suggestions.
232+ onSuggestionsClearRequested = ( ) => {
233+ this . setState ( {
234+ suggestions : [ ] ,
235+ } ) ;
236+
237+ this . setState ( { isOpen : false } ) ;
238+ } ;
239+
240+ onSuggestionSelected ( ) {
241+ this . setState ( { isOpen : false } ) ;
242+ }
243+
205244 render ( ) {
206- const open = this . state . isOpen ;
207- const currentValue = this . state . value ;
245+ const { value , suggestions } = this . state ;
246+
208247 const shouldShowClearButton = this . props . showClearButton
209248 ? this . props . showClearButton
210249 : true ;
@@ -213,61 +252,54 @@ class Search extends BaseComponent {
213252 ? this . props . clearButtonText
214253 : 'Clear Search Results' ;
215254
255+ // no CSS for these Autosuggest selectors yet -- not yet needed
256+ const theme = {
257+ container : classNames ( 'pl-c-typeahead' ) ,
258+ containerOpen : classNames ( 'pl-c-typeahead--open' ) ,
259+ input : classNames ( 'pl-c-typeahead__input' , 'js-c-typeahead__input' , {
260+ [ `pl-c-typeahead__input--with-clear-button` ] : shouldShowClearButton ,
261+ } ) ,
262+ inputOpen : classNames ( 'pl-c-typeahead__input--open' ) ,
263+ inputFocused : classNames ( 'pl-c-typeahead__input--focused' ) ,
264+ suggestionsContainer : classNames ( 'pl-c-typeahead__menu' ) ,
265+ suggestionsContainerOpen : classNames ( 'pl-is-open' ) ,
266+ suggestionsList : classNames ( 'pl-c-typeahead__results' ) ,
267+ suggestion : classNames ( 'pl-c-typeahead__result' ) ,
268+ suggestionFirst : classNames ( 'pl-c-typeahead__result--first' ) ,
269+ suggestionHighlighted : classNames ( 'pl-has-cursor' ) ,
270+ sectionContainer : classNames (
271+ 'pl-c-typeahead__section-container'
272+ ) /* [1] */ ,
273+ sectionContainerFirst : classNames (
274+ 'pl-c-typeahead__section-container--first'
275+ ) /* [1] */ ,
276+ sectionTitle : classNames ( 'pl-c-typeahead__section-title' ) /* [1] */ ,
277+ } ;
278+
279+ // Autosuggest will pass through all these props to the input.
280+ const inputProps = {
281+ placeholder : this . props . placeholder
282+ ? this . props . placeholder
283+ : 'Find a Pattern' ,
284+ value,
285+ onChange : this . onChange ,
286+ } ;
287+
216288 return (
217- < div className = { 'pl-c-typeahead-wrapper' } >
218- < ReactAutocomplete
219- items = { this . filterAndLimitResults ( ) }
220- ref = { el => ( this . input = el ) }
221- wrapperProps = { {
222- className : classNames ( 'pl-c-typeahead' ) ,
223- } }
224- //setting autoHighlight to false seems to help prevent an occasional JS error from firing (relating to the dom-scroll-into-view library)
225- autoHighlight = { false }
226- onMenuVisibilityChange = { isOpen => this . setState ( { isOpen } ) }
227- getItemValue = { item => item . label }
228- renderItem = { ( item , highlighted ) => (
229- < div
230- className = { classNames ( 'pl-c-typeahead__result' , {
231- [ `pl-has-cursor` ] : highlighted ,
232- } ) }
233- key = { item . id }
234- >
235- { item . highlightedLabel }
236- </ div >
237- ) }
238- inputProps = { {
239- className : classNames ( 'pl-c-typeahead__input' , {
240- [ `pl-c-typeahead__input--with-clear-button` ] : shouldShowClearButton ,
241- } ) ,
242- placeholder : this . props . placeholder
243- ? this . props . placeholder
244- : 'Find a Pattern' ,
245- } }
246- renderMenu = { ( items , value , style ) => (
247- < div
248- className = { classNames ( 'pl-c-typeahead__menu' , {
249- [ `pl-is-open` ] : open ,
250- } ) }
251- >
252- < div
253- className = { 'pl-c-typeahead__results' }
254- style = { { ...style , ...this . menuStyle } }
255- children = { items }
256- />
257- </ div >
258- ) }
259- value = { this . state . value }
260- onChange = { e => {
261- e . target . value !== '' && e . target . value !== undefined
262- ? this . setState ( { value : e . target . value } )
263- : this . setState ( { value : '' } ) ;
264- } }
265- onSelect = { value => this . passPath ( value ) }
289+ < div className = { classNames ( 'pl-c-typeahead-wrapper' ) } >
290+ < Autosuggest
291+ theme = { theme }
292+ suggestions = { suggestions }
293+ onSuggestionsFetchRequested = { this . onSuggestionsFetchRequested }
294+ onSuggestionsClearRequested = { this . onSuggestionsClearRequested }
295+ getSuggestionValue = { this . getSuggestionValue }
296+ renderSuggestion = { this . renderSuggestion }
297+ inputProps = { inputProps }
266298 />
267299 { shouldShowClearButton && (
268300 < button
269301 className = { classNames ( 'pl-c-typeahead__clear-button' , {
270- [ `pl-is-visible` ] : currentValue !== '' ,
302+ [ `pl-is-visible` ] : value !== '' ,
271303 } ) }
272304 onClick = { ( ) => {
273305 this . clearSearch ( ) ;
@@ -280,7 +312,7 @@ class Search extends BaseComponent {
280312 width = "16"
281313 className = { 'pl-c-typeahead__clear-button-icon' }
282314 >
283- < title > Clear Search Results </ title >
315+ < title > { clearButtonText } </ title >
284316 < path d = "M12.207 10.793l-1.414 1.414-2.793-2.793-2.793 2.793-1.414-1.414 2.793-2.793-2.793-2.793 1.414-1.414 2.793 2.793 2.793-2.793 1.414 1.414-2.793 2.793 2.793 2.793z" />
285317 </ svg >
286318 </ button >
0 commit comments