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>
This commit is contained in:
Luca Rossi
2026-02-26 18:43:29 +01:00
committed by GitHub
parent 943f6e3bc7
commit f351ed9a1d
10 changed files with 27 additions and 12 deletions

View File

@@ -0,0 +1 @@
{"children":[],"variables":{}}

View File

@@ -17,7 +17,7 @@ import { splitFrontmatter, preProcessWikilinks, injectWikilinks, restoreWikilink
import { preFilterWikilinks, deduplicateByPath, disambiguateTitles, MAX_RESULTS, MIN_QUERY_LENGTH } from '../utils/wikilinkSuggestions'
import { filterPersonMentions, PERSON_MENTION_MIN_QUERY } from '../utils/personMentionSuggestions'
import { resolveWikilinkColor as resolveColor } from '../utils/wikilinkColors'
import { getTypeColor, buildTypeEntryMap } from '../utils/typeColors'
import { getTypeColor, getTypeLightColor, buildTypeEntryMap } from '../utils/typeColors'
import { getTypeIcon } from './NoteItem'
import { WikilinkSuggestionMenu, type WikilinkSuggestionItem } from './WikilinkSuggestionMenu'
import './Editor.css'
@@ -177,6 +177,7 @@ function SingleEditorView({ editor, entries, onNavigateWikilink, onChange }: { e
...rest,
noteType,
typeColor: noteType ? getTypeColor(group, te?.color) : undefined,
typeLightColor: noteType ? getTypeLightColor(group, te?.color) : undefined,
TypeIcon: noteType ? getTypeIcon(group, te?.icon) : undefined,
}
})
@@ -207,6 +208,7 @@ function SingleEditorView({ editor, entries, onNavigateWikilink, onChange }: { e
...rest,
noteType,
typeColor: noteType ? getTypeColor(group, te?.color) : undefined,
typeLightColor: noteType ? getTypeLightColor(group, te?.color) : undefined,
TypeIcon: noteType ? getTypeIcon(group, te?.icon) : undefined,
}
})

View File

@@ -96,7 +96,7 @@ describe('NoteAutocomplete', () => {
expect(typeLabels.length).toBe(0)
})
it('applies type color from typeEntryMap', () => {
it('applies type color and light background from typeEntryMap', () => {
const { container } = render(
<NoteAutocomplete entries={entries} typeEntryMap={typeEntryMap} value="Luca" onChange={onChange} onSelect={onSelect} />,
)
@@ -104,6 +104,7 @@ describe('NoteAutocomplete', () => {
const typeLabel = container.querySelector('.wikilink-menu__type')
expect(typeLabel).toBeTruthy()
expect((typeLabel as HTMLElement).style.color).toBe('var(--accent-yellow)')
expect((typeLabel as HTMLElement).style.backgroundColor).toBe('var(--accent-yellow-light)')
})
it('calls onSelect when clicking a dropdown item', () => {

View File

@@ -1,6 +1,6 @@
import { useState, useRef, useCallback, useMemo, useEffect, type ComponentType, type SVGAttributes } from 'react'
import type { VaultEntry } from '../types'
import { getTypeColor } from '../utils/typeColors'
import { getTypeColor, getTypeLightColor } from '../utils/typeColors'
import { getTypeIcon } from './NoteItem'
import './WikilinkSuggestionMenu.css'
@@ -23,6 +23,7 @@ interface MatchedEntry {
title: string
noteType?: string
typeColor?: string
typeLightColor?: string
TypeIcon?: ComponentType<SVGAttributes<SVGSVGElement>>
}
@@ -41,6 +42,7 @@ function matchEntries(entries: VaultEntry[], typeEntryMap: Record<string, VaultE
title: e.title,
noteType,
typeColor: noteType ? getTypeColor(isA, te?.color) : undefined,
typeLightColor: noteType ? getTypeLightColor(isA, te?.color) : undefined,
TypeIcon: noteType ? getTypeIcon(isA, te?.icon) : undefined,
}
})
@@ -143,7 +145,7 @@ export function NoteAutocomplete({ entries, typeEntryMap, value, onChange, onSel
{item.title}
</span>
{item.noteType && (
<span className="wikilink-menu__type" style={{ color: item.typeColor }}>
<span className="wikilink-menu__type" style={{ color: item.typeColor, backgroundColor: item.typeLightColor, borderRadius: 9999, padding: '1px 8px' }}>
{item.noteType}
</span>
)}

View File

@@ -11,7 +11,7 @@ interface TestItem extends NoteSearchResultItem {
}
const items: TestItem[] = [
{ id: '1', title: 'Alpha Project', noteType: 'Project', typeColor: 'var(--accent-blue)' },
{ id: '1', title: 'Alpha Project', noteType: 'Project', typeColor: 'var(--accent-blue)', typeLightColor: 'var(--accent-blue-light)' },
{ id: '2', title: 'Beta Notes' },
{ id: '3', title: 'Gamma Experiment', noteType: 'Experiment' },
]
@@ -65,7 +65,7 @@ describe('NoteSearchList', () => {
expect(screen.queryByText('Note')).not.toBeInTheDocument()
})
it('applies typeColor to badge when provided', () => {
it('applies typeColor and typeLightColor to badge when provided', () => {
render(
<NoteSearchList
items={[items[0]]}
@@ -76,6 +76,7 @@ describe('NoteSearchList', () => {
)
const badge = screen.getByText('Project')
expect(badge.style.color).toBe('var(--accent-blue)')
expect(badge.style.backgroundColor).toBe('var(--accent-blue-light)')
})
it('shows empty message when no items', () => {

View File

@@ -6,6 +6,7 @@ export interface NoteSearchResultItem {
title: string
noteType?: string
typeColor?: string
typeLightColor?: string
TypeIcon?: ComponentType<SVGAttributes<SVGSVGElement>>
}
@@ -73,7 +74,7 @@ export function NoteSearchList<T extends NoteSearchResultItem>({
<Badge
variant="secondary"
className="shrink-0 text-[11px]"
style={item.typeColor ? { color: item.typeColor } : undefined}
style={item.typeColor ? { color: item.typeColor, backgroundColor: item.typeLightColor } : undefined}
>
{item.noteType}
</Badge>

View File

@@ -4,7 +4,7 @@ import { cn } from '@/lib/utils'
import { Badge } from '@/components/ui/badge'
import type { SearchResult, VaultEntry } from '../types'
import { useUnifiedSearch } from '../hooks/useUnifiedSearch'
import { getTypeColor, buildTypeEntryMap } from '../utils/typeColors'
import { getTypeColor, getTypeLightColor, buildTypeEntryMap } from '../utils/typeColors'
interface SearchPanelProps {
open: boolean
@@ -193,6 +193,7 @@ function SearchContent({
const isA = entryLookup.get(result.path)?.isA ?? result.noteType
const noteType = isA && isA !== 'Note' ? isA : null
const typeColor = noteType ? getTypeColor(isA, typeEntryMap[isA ?? '']?.color) : undefined
const typeLightColor = noteType ? getTypeLightColor(isA, typeEntryMap[isA ?? '']?.color) : undefined
return (
<div
key={result.path}
@@ -206,7 +207,7 @@ function SearchContent({
<div className="flex items-center gap-2">
<span className="text-[13px] font-medium text-foreground">{result.title}</span>
{noteType && (
<Badge variant="secondary" className="text-[10px]" style={typeColor ? { color: typeColor } : undefined}>
<Badge variant="secondary" className="text-[10px]" style={typeColor ? { color: typeColor, backgroundColor: typeLightColor } : undefined}>
{noteType}
</Badge>
)}

View File

@@ -7,6 +7,7 @@ export interface WikilinkSuggestionItem {
onItemClick: () => void
noteType?: string
typeColor?: string
typeLightColor?: string
TypeIcon?: ComponentType<SVGAttributes<SVGSVGElement>>
aliases?: string[]
entryTitle?: string

View File

@@ -60,18 +60,20 @@ describe('useNoteSearch', () => {
expect(result.current.results).toHaveLength(2)
})
it('includes noteType for non-Note entries', () => {
it('includes noteType and light color for non-Note entries', () => {
const { result } = renderHook(() => useNoteSearch(entries, ''))
const project = result.current.results.find((r) => r.title === 'Alpha Project')
expect(project?.noteType).toBe('Project')
expect(project?.typeColor).toBeTruthy()
expect(project?.typeLightColor).toBeTruthy()
})
it('excludes noteType for Note entries', () => {
it('excludes noteType and light color for Note entries', () => {
const { result } = renderHook(() => useNoteSearch(entries, ''))
const note = result.current.results.find((r) => r.title === 'Beta Notes')
expect(note?.noteType).toBeUndefined()
expect(note?.typeColor).toBeUndefined()
expect(note?.typeLightColor).toBeUndefined()
})
it('includes original VaultEntry in results', () => {
@@ -182,10 +184,12 @@ describe('useNoteSearch', () => {
const pasta = result.current.results.find(r => r.title === 'Pasta')
expect(pasta?.noteType).toBe('Recipe')
expect(pasta?.typeColor).toBe('var(--accent-orange)')
expect(pasta?.typeLightColor).toBe('var(--accent-orange-light)')
expect(pasta?.TypeIcon).toBeDefined()
// Built-in type still works
const project = result.current.results.find(r => r.title === 'My Project')
expect(project?.typeColor).toBe('var(--accent-red)')
expect(project?.typeLightColor).toBe('var(--accent-red-light)')
expect(project?.TypeIcon).toBeDefined()
})
})

View File

@@ -1,7 +1,7 @@
import { useState, useMemo, useCallback, useEffect } from 'react'
import type { VaultEntry } from '../types'
import { fuzzyMatch } from '../utils/fuzzyMatch'
import { getTypeColor, buildTypeEntryMap } from '../utils/typeColors'
import { getTypeColor, getTypeLightColor, buildTypeEntryMap } from '../utils/typeColors'
import { getTypeIcon } from '../components/NoteItem'
import type { NoteSearchResultItem } from '../components/NoteSearchList'
@@ -19,6 +19,7 @@ function toResult(e: VaultEntry, typeEntryMap: Record<string, VaultEntry>): Note
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,
}
}