DocsSuggestions.tsx 2.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. import { useDocsSearch, type DocsSearchResult } from 'common'
  2. import { Book, Github, Loader2 } from 'lucide-react'
  3. import { cn } from 'ui'
  4. import { useChangedSync } from '@/hooks/misc/useChanged'
  5. import { DOCS_URL } from '@/lib/constants'
  6. function useDocsSuggestions(subject: string) {
  7. const { handleDocsSearchDebounced, resetSearch, searchState } = useDocsSearch()
  8. const trimmedSubject = subject.trim()
  9. const subjectChanged = useChangedSync(trimmedSubject)
  10. if (subjectChanged && trimmedSubject) {
  11. handleDocsSearchDebounced(trimmedSubject)
  12. } else if (subjectChanged && !trimmedSubject) {
  13. resetSearch()
  14. }
  15. return searchState
  16. }
  17. interface DocsSuggestionsProps {
  18. searchString: string
  19. }
  20. export function DocsSuggestions({ searchString }: DocsSuggestionsProps) {
  21. const searchState = useDocsSuggestions(searchString)
  22. const results =
  23. 'results' in searchState
  24. ? searchState.results
  25. : 'staleResults' in searchState
  26. ? searchState.staleResults
  27. : []
  28. const resultsStale = searchState.status === 'loading'
  29. return (
  30. <>
  31. {searchState.status === 'loading' && <DocsSuggestions_Loading />}
  32. {results.length > 0 && <DocsSuggestions_Results results={results} isStale={resultsStale} />}
  33. </>
  34. )
  35. }
  36. function DocsSuggestions_Loading() {
  37. return (
  38. <div className="flex items-center gap-2 text-sm text-foreground-light">
  39. <Loader2 className="animate-spin" size={14} />
  40. <span>Searching for relevant resources...</span>
  41. </div>
  42. )
  43. }
  44. interface DocsSuggestions_ResultsProps {
  45. results: DocsSearchResult[]
  46. isStale: boolean
  47. }
  48. function DocsSuggestions_Results({ results, isStale }: DocsSuggestions_ResultsProps) {
  49. return (
  50. <ul
  51. className={cn(
  52. 'flex flex-col gap-y-0.5 transition-opacity duration-200',
  53. isStale ? 'opacity-50' : 'opacity-100'
  54. )}
  55. >
  56. {results.slice(0, 5).map((page) => {
  57. return (
  58. <li key={page.id} className="flex items-center gap-x-1">
  59. {page.type === 'github-discussions' ? (
  60. <Github size={16} className="text-foreground-muted" />
  61. ) : (
  62. <Book size={16} className="text-foreground-muted" />
  63. )}
  64. <a
  65. href={page.type === 'github-discussions' ? page.path : `${DOCS_URL}${page.path}`}
  66. target="_blank"
  67. rel="noreferrer"
  68. className="text-sm text-foreground-light hover:text-foreground transition"
  69. >
  70. {page.title}
  71. </a>
  72. </li>
  73. )
  74. })}
  75. </ul>
  76. )
  77. }