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:
1
design/note-type-labels.pen
Normal file
1
design/note-type-labels.pen
Normal file
@@ -0,0 +1 @@
|
||||
{"children":[],"variables":{}}
|
||||
@@ -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,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -7,6 +7,7 @@ export interface WikilinkSuggestionItem {
|
||||
onItemClick: () => void
|
||||
noteType?: string
|
||||
typeColor?: string
|
||||
typeLightColor?: string
|
||||
TypeIcon?: ComponentType<SVGAttributes<SVGSVGElement>>
|
||||
aliases?: string[]
|
||||
entryTitle?: string
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user