balmet.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

SearchPredict.js (3285B)


      1 import {
      2     useEffect, useState, useRef,
      3 } from '@wordpress/element'
      4 import Fuse from 'fuse.js'
      5 
      6 export default function SearchPredict({ value, setValue, list, label, touched }) {
      7     const searchRef = useRef()
      8     const wasFocused = useRef(false)
      9     const [componentId] = useState('extendify-label' + Date.now() + Math.floor(Math.random() * 1000000))
     10     const [fuzzy, setFuzzy] = useState({})
     11     const [tempValue, setTempValue] = useState('')
     12     const [visibleChoices, setVisibleChoices] = useState([])
     13 
     14     const updateSearch = (term) => {
     15         wasFocused.current = true
     16         setTempValue(term)
     17         filter(term)
     18     }
     19 
     20     const filter = (term = '') => {
     21         if (!term) {
     22             setVisibleChoices(list)
     23         }
     24         const results = fuzzy.search(term)
     25         if (!results || !results.length) {
     26             return
     27         }
     28         setVisibleChoices(results.length ? results.map(t => t.item) : list)
     29     }
     30 
     31     useEffect(() => {
     32         setTempValue(value)
     33     }, [value])
     34 
     35     useEffect(() => {
     36         setFuzzy(new Fuse(list, {}))
     37     }, [list])
     38 
     39     useEffect(() => {
     40         if (!list.length) {
     41             return
     42         }
     43         const handle = (event) => {
     44             if (searchRef.current.isSameNode(event.target)) {
     45                 return
     46             }
     47             if (event.target.classList.contains(`extendify-predict-${componentId}`)) {
     48                 return setVisibleChoices([])
     49             }
     50             setVisibleChoices([])
     51             if (!list.includes(searchRef.current.value)) {
     52                 setValue('')
     53             }
     54 
     55             // Consider the component touched when clicked away
     56             wasFocused.current && touched()
     57         }
     58         document.addEventListener('click', handle)
     59         return () => document.removeEventListener('click', handle)
     60     }, [componentId, touched, setValue, list])
     61 
     62     return <div className="relative max-w-md">
     63         <input
     64             ref={searchRef}
     65             id={componentId}
     66             value={tempValue || ''}
     67             onChange={(event) => updateSearch(event.target.value)}
     68             onFocus={() => updateSearch('')}
     69             type="text"
     70             disabled={!Object.keys(fuzzy).length}
     71             className="extendify-special-input button-focus text-sm h-8 min-h-0 border border-gray-900 special-input placeholder-transparent rounded-none w-full px-2 button-focus-big-green"
     72             placeholder={label} />
     73         <label htmlFor={componentId} className="-top-3 bg-white absolute left-1 px-1 transition-all delay-300">
     74             {label}
     75         </label>
     76         {/* TODO: this could use some accessability updates like keyboard nav, etc */}
     77         {visibleChoices && <div className="absolute top-100 flex flex-col w-full shadow-md bg-white overflow-x-hidden left-px divide-y max-h-64 overflow-scroll">
     78             {visibleChoices.map((cat =>
     79                 <button
     80                     key={cat}
     81                     type="button"
     82                     className={`outline-none focus:bg-gray-100 bg-white text-left p-4 text-sm border-gray-300 hover:bg-gray-100 extendify-predict-${componentId}`}
     83                     onClick={() => { setValue(cat); setTempValue(cat)}}>
     84                     {cat}
     85                 </button>
     86             ))}
     87         </div>}
     88     </div>
     89 }