Files
tolaria/src/hooks/useNoteSearch.ts
Luca Rossi f351ed9a1d feat: use light type color as background for note type labels (#93)
Type labels in autocomplete dropdowns (wiki-link [[, relation add,
Cmd+P quick open) and search panel now use the light/muted variant
of the type color as background instead of grey. This matches the
existing color convention used in wiki-link chips and inspector panels.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 17:43:29 +00:00

68 lines
2.4 KiB
TypeScript

import { useState, useMemo, useCallback, useEffect } from 'react'
import type { VaultEntry } from '../types'
import { fuzzyMatch } from '../utils/fuzzyMatch'
import { getTypeColor, getTypeLightColor, buildTypeEntryMap } from '../utils/typeColors'
import { getTypeIcon } from '../components/NoteItem'
import type { NoteSearchResultItem } from '../components/NoteSearchList'
const DEFAULT_MAX_RESULTS = 20
export interface NoteSearchResult extends NoteSearchResultItem {
entry: VaultEntry
}
function toResult(e: VaultEntry, typeEntryMap: Record<string, VaultEntry>): NoteSearchResult {
const noteType = e.isA && e.isA !== 'Note' ? e.isA : undefined
const te = typeEntryMap[e.isA ?? '']
return {
entry: e,
title: e.title,
noteType,
typeColor: noteType ? getTypeColor(e.isA, te?.color) : undefined,
typeLightColor: noteType ? getTypeLightColor(e.isA, te?.color) : undefined,
TypeIcon: noteType ? getTypeIcon(e.isA, te?.icon) : undefined,
}
}
export function useNoteSearch(entries: VaultEntry[], query: string, maxResults = DEFAULT_MAX_RESULTS) {
const [selectedIndex, setSelectedIndex] = useState(0)
const typeEntryMap = useMemo(() => buildTypeEntryMap(entries), [entries])
const results: NoteSearchResult[] = useMemo(() => {
const mapResult = (e: VaultEntry) => toResult(e, typeEntryMap)
if (!query.trim()) {
return [...entries]
.sort((a, b) => (b.modifiedAt ?? 0) - (a.modifiedAt ?? 0))
.slice(0, maxResults)
.map(mapResult)
}
return entries
.map((e) => ({ entry: e, ...fuzzyMatch(query, e.title) }))
.filter((r) => r.match)
.sort((a, b) => b.score - a.score)
.slice(0, maxResults)
.map((r) => mapResult(r.entry))
}, [entries, query, maxResults, typeEntryMap])
useEffect(() => {
setSelectedIndex(0) // eslint-disable-line react-hooks/set-state-in-effect -- reset on query change
}, [query])
const selectedEntry = results[selectedIndex]?.entry ?? null
const handleKeyDown = useCallback(
(e: React.KeyboardEvent | KeyboardEvent) => {
if (e.key === 'ArrowDown') {
e.preventDefault()
setSelectedIndex((i) => Math.min(i + 1, results.length - 1))
} else if (e.key === 'ArrowUp') {
e.preventDefault()
setSelectedIndex((i) => Math.max(i - 1, 0))
}
},
[results.length],
)
return { results, selectedIndex, setSelectedIndex, selectedEntry, handleKeyDown }
}