From f351ed9a1d68279f13e4968001598c31b0f53387 Mon Sep 17 00:00:00 2001 From: Luca Rossi Date: Thu, 26 Feb 2026 18:43:29 +0100 Subject: [PATCH] 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 --- design/note-type-labels.pen | 1 + src/components/Editor.tsx | 4 +++- src/components/NoteAutocomplete.test.tsx | 3 ++- src/components/NoteAutocomplete.tsx | 6 ++++-- src/components/NoteSearchList.test.tsx | 5 +++-- src/components/NoteSearchList.tsx | 3 ++- src/components/SearchPanel.tsx | 5 +++-- src/components/WikilinkSuggestionMenu.tsx | 1 + src/hooks/useNoteSearch.test.ts | 8 ++++++-- src/hooks/useNoteSearch.ts | 3 ++- 10 files changed, 27 insertions(+), 12 deletions(-) create mode 100644 design/note-type-labels.pen diff --git a/design/note-type-labels.pen b/design/note-type-labels.pen new file mode 100644 index 00000000..747b5ab3 --- /dev/null +++ b/design/note-type-labels.pen @@ -0,0 +1 @@ +{"children":[],"variables":{}} diff --git a/src/components/Editor.tsx b/src/components/Editor.tsx index 4438f8c3..ed8b7e80 100644 --- a/src/components/Editor.tsx +++ b/src/components/Editor.tsx @@ -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, } }) diff --git a/src/components/NoteAutocomplete.test.tsx b/src/components/NoteAutocomplete.test.tsx index 7684a9df..184e4662 100644 --- a/src/components/NoteAutocomplete.test.tsx +++ b/src/components/NoteAutocomplete.test.tsx @@ -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( , ) @@ -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', () => { diff --git a/src/components/NoteAutocomplete.tsx b/src/components/NoteAutocomplete.tsx index f0987993..45ecd7fd 100644 --- a/src/components/NoteAutocomplete.tsx +++ b/src/components/NoteAutocomplete.tsx @@ -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> } @@ -41,6 +42,7 @@ function matchEntries(entries: VaultEntry[], typeEntryMap: Record {item.noteType && ( - + {item.noteType} )} diff --git a/src/components/NoteSearchList.test.tsx b/src/components/NoteSearchList.test.tsx index 921581d6..66cbb2fc 100644 --- a/src/components/NoteSearchList.test.tsx +++ b/src/components/NoteSearchList.test.tsx @@ -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( { ) 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', () => { diff --git a/src/components/NoteSearchList.tsx b/src/components/NoteSearchList.tsx index 98cb26e8..741f8fb1 100644 --- a/src/components/NoteSearchList.tsx +++ b/src/components/NoteSearchList.tsx @@ -6,6 +6,7 @@ export interface NoteSearchResultItem { title: string noteType?: string typeColor?: string + typeLightColor?: string TypeIcon?: ComponentType> } @@ -73,7 +74,7 @@ export function NoteSearchList({ {item.noteType} diff --git a/src/components/SearchPanel.tsx b/src/components/SearchPanel.tsx index ffc7a9eb..28e05b6d 100644 --- a/src/components/SearchPanel.tsx +++ b/src/components/SearchPanel.tsx @@ -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 (
{result.title} {noteType && ( - + {noteType} )} diff --git a/src/components/WikilinkSuggestionMenu.tsx b/src/components/WikilinkSuggestionMenu.tsx index e6198330..8402dd37 100644 --- a/src/components/WikilinkSuggestionMenu.tsx +++ b/src/components/WikilinkSuggestionMenu.tsx @@ -7,6 +7,7 @@ export interface WikilinkSuggestionItem { onItemClick: () => void noteType?: string typeColor?: string + typeLightColor?: string TypeIcon?: ComponentType> aliases?: string[] entryTitle?: string diff --git a/src/hooks/useNoteSearch.test.ts b/src/hooks/useNoteSearch.test.ts index 44da8007..d1b1bb06 100644 --- a/src/hooks/useNoteSearch.test.ts +++ b/src/hooks/useNoteSearch.test.ts @@ -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() }) }) diff --git a/src/hooks/useNoteSearch.ts b/src/hooks/useNoteSearch.ts index 60b4350c..5152a077 100644 --- a/src/hooks/useNoteSearch.ts +++ b/src/hooks/useNoteSearch.ts @@ -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): 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, } }