* fix: resolve custom type color and icon in all autocomplete contexts Custom types (e.g., Evergreen, Recipe) appeared grey in wikilink [[, @-mention, Cmd+P, and search autocompletes because getTypeColor() was called without the custom color key from the Type document. Root causes: - Editor.tsx: getTypeColor(group) missing typeEntryMap[group]?.color - useNoteSearch.ts: getTypeColor(e.isA) missing custom color lookup - SearchPanel.tsx: type badge had no color styling at all Fixes: - Extract buildTypeEntryMap to shared utility in typeColors.ts - Pass custom color key in all autocomplete code paths - Add type icon (Phosphor) to the left of each result in NoteSearchList - Add TypeIcon to WikilinkSuggestionItem and NoteAutocomplete Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: add design file for autocomplete type color fix Shows the fixed state: type icon on the left, colored type badge on the right, untyped notes neutral grey with no icon. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
86 lines
2.5 KiB
TypeScript
86 lines
2.5 KiB
TypeScript
import { useRef, useEffect, type ComponentType, type SVGAttributes } from 'react'
|
|
import { cn } from '@/lib/utils'
|
|
import { Badge } from '@/components/ui/badge'
|
|
|
|
export interface NoteSearchResultItem {
|
|
title: string
|
|
noteType?: string
|
|
typeColor?: string
|
|
TypeIcon?: ComponentType<SVGAttributes<SVGSVGElement>>
|
|
}
|
|
|
|
interface NoteSearchListProps<T extends NoteSearchResultItem> {
|
|
items: T[]
|
|
selectedIndex: number
|
|
getItemKey: (item: T, index: number) => string
|
|
onItemClick: (item: T, index: number) => void
|
|
onItemHover?: (index: number) => void
|
|
emptyMessage?: string
|
|
className?: string
|
|
}
|
|
|
|
export function NoteSearchList<T extends NoteSearchResultItem>({
|
|
items,
|
|
selectedIndex,
|
|
getItemKey,
|
|
onItemClick,
|
|
onItemHover,
|
|
emptyMessage = 'No results',
|
|
className,
|
|
}: NoteSearchListProps<T>) {
|
|
const listRef = useRef<HTMLDivElement>(null)
|
|
|
|
useEffect(() => {
|
|
if (!listRef.current) return
|
|
const el = listRef.current.children[selectedIndex] as HTMLElement | undefined
|
|
el?.scrollIntoView({ block: 'nearest' })
|
|
}, [selectedIndex])
|
|
|
|
if (items.length === 0) {
|
|
return (
|
|
<div ref={listRef} className={cn('py-1', className)}>
|
|
<div className="px-4 py-3 text-center text-[13px] text-muted-foreground">
|
|
{emptyMessage}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div ref={listRef} className={cn('py-1', className)}>
|
|
{items.map((item, i) => (
|
|
<div
|
|
key={getItemKey(item, i)}
|
|
className={cn(
|
|
'flex cursor-pointer items-center justify-between gap-2 px-3 py-1.5 transition-colors',
|
|
i === selectedIndex ? 'bg-accent' : 'hover:bg-secondary',
|
|
)}
|
|
onClick={() => onItemClick(item, i)}
|
|
onMouseEnter={() => onItemHover?.(i)}
|
|
>
|
|
<span className="flex min-w-0 flex-1 items-center gap-1.5 truncate text-sm text-foreground">
|
|
{item.TypeIcon && (
|
|
<item.TypeIcon
|
|
width={14}
|
|
height={14}
|
|
className="shrink-0"
|
|
style={item.typeColor ? { color: item.typeColor } : undefined}
|
|
/>
|
|
)}
|
|
<span className="truncate">{item.title}</span>
|
|
</span>
|
|
{item.noteType && (
|
|
<Badge
|
|
variant="secondary"
|
|
className="shrink-0 text-[11px]"
|
|
style={item.typeColor ? { color: item.typeColor } : undefined}
|
|
>
|
|
{item.noteType}
|
|
</Badge>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
)
|
|
}
|