File size: 3,957 Bytes
f842a06
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5607582
f842a06
 
 
 
 
 
5607582
 
 
 
 
 
 
 
 
f842a06
 
5607582
 
f842a06
 
 
 
 
5607582
 
 
 
 
 
 
 
 
 
f842a06
5607582
 
 
f842a06
 
 
 
 
021b245
f842a06
 
 
 
5607582
 
 
 
 
 
f842a06
 
5607582
 
 
f842a06
 
5607582
f842a06
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
import { useCallback, useEffect, useRef, useState } from 'react'
import { searchPages, type SearchResult } from '../lib/api.ts'

interface Props {
  onSelectResult?: (result: SearchResult) => void
}

export default function SearchBar({ onSelectResult }: Props) {
  const [query, setQuery] = useState('')
  const [results, setResults] = useState<SearchResult[]>([])
  const [open, setOpen] = useState(false)
  const [loading, setLoading] = useState(false)
  const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null)
  const containerRef = useRef<HTMLDivElement>(null)

  const runSearch = useCallback(async (q: string) => {
    if (q.trim().length < 2) {
      setResults([])
      setOpen(false)
      return
    }
    setLoading(true)
    try {
      const res = await searchPages(q.trim())
      setResults(res)
      setOpen(true)
    } catch {
      setResults([])
    } finally {
      setLoading(false)
    }
  }, [])

  useEffect(() => {
    if (debounceRef.current) clearTimeout(debounceRef.current)
    debounceRef.current = setTimeout(() => {
      void runSearch(query)
    }, 300)
    return () => {
      if (debounceRef.current) clearTimeout(debounceRef.current)
    }
  }, [query, runSearch])

  useEffect(() => {
    const handler = (e: MouseEvent) => {
      if (containerRef.current && !containerRef.current.contains(e.target as Node)) {
        setOpen(false)
      }
    }
    document.addEventListener('mousedown', handler)
    return () => document.removeEventListener('mousedown', handler)
  }, [])

  return (
    <div ref={containerRef} className="relative w-64">
      <div className="relative">
        <input
          type="search"
          value={query}
          onChange={(e) => setQuery(e.target.value)}
          onFocus={() => results.length > 0 && setOpen(true)}
          placeholder="Rechercher..."
          className="
            w-full px-2 py-[2px]
            text-retro-sm font-retro
            bg-retro-white text-retro-black
            border border-retro-black
            shadow-retro-well
            placeholder:text-retro-darkgray
          "
        />
        {loading && (
          <span className="absolute right-2 top-1/2 -translate-y-1/2 text-retro-darkgray text-retro-xs">
            ...
          </span>
        )}
      </div>

      {open && (
        <div
          className="
            absolute top-full left-0 right-0 mt-[2px]
            bg-retro-white
            border-retro border-retro-black
            shadow-retro-lg
            z-50 max-h-60
            overflow-y-auto retro-scroll
          "
        >
          {results.length === 0 ? (
            <div className="px-2 py-2 text-retro-xs text-retro-darkgray">
              Aucun resultat.
            </div>
          ) : (
            <ul>
              {results.map((r) => (
                <li key={r.page_id}>
                  <button
                    type="button"
                    onClick={() => {
                      setOpen(false)
                      onSelectResult?.(r)
                    }}
                    className="
                      w-full text-left px-2 py-[3px]
                      text-retro-sm font-retro
                      hover:bg-retro-select hover:text-retro-select-text
                      border-b border-retro-gray last:border-0
                    "
                  >
                    <div className="flex items-center justify-between gap-2">
                      <span className="font-bold">{r.folio_label}</span>
                      <span className="text-retro-xs opacity-60">
                        {r.score}
                      </span>
                    </div>
                    <div className="text-retro-xs truncate opacity-70">
                      {r.excerpt}
                    </div>
                  </button>
                </li>
              ))}
            </ul>
          )}
        </div>
      )}
    </div>
  )
}