refactor: simplify breadcrumb and note-list search ui
This commit is contained in:
@@ -245,6 +245,12 @@ describe('BreadcrumbBar — action buttons always right-aligned', () => {
|
||||
expect(actions).toBeInTheDocument()
|
||||
expect(actions).toHaveClass('ml-auto')
|
||||
})
|
||||
|
||||
it('does not render the unused backlinks or more-actions placeholders', () => {
|
||||
render(<BreadcrumbBar entry={baseEntry} {...defaultProps} />)
|
||||
expect(screen.queryByRole('button', { name: 'Backlinks are coming soon' })).not.toBeInTheDocument()
|
||||
expect(screen.queryByRole('button', { name: 'More note actions are coming soon' })).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('BreadcrumbBar — raw editor toggle', () => {
|
||||
|
||||
@@ -8,10 +8,8 @@ import { TooltipProvider } from '@/components/ui/tooltip'
|
||||
import {
|
||||
GitBranch,
|
||||
Code,
|
||||
CursorText,
|
||||
Sparkle,
|
||||
SlidersHorizontal,
|
||||
DotsThree,
|
||||
Trash,
|
||||
Archive,
|
||||
ArrowUUpLeft,
|
||||
@@ -214,14 +212,6 @@ function DiffAction({
|
||||
)
|
||||
}
|
||||
|
||||
function PlaceholderAction({ copy, children }: { copy: ActionTooltipCopy; children: ReactNode }) {
|
||||
return (
|
||||
<IconActionButton copy={copy} style={DISABLED_ICON_STYLE}>
|
||||
{children}
|
||||
</IconActionButton>
|
||||
)
|
||||
}
|
||||
|
||||
function AIChatAction({ showAIChat, onToggleAIChat }: Pick<BreadcrumbBarProps, 'showAIChat' | 'onToggleAIChat'>) {
|
||||
const copy: ActionTooltipCopy = {
|
||||
label: showAIChat ? 'Close the AI panel' : 'Open the AI panel',
|
||||
@@ -481,16 +471,10 @@ function BreadcrumbActions({
|
||||
onToggleDiff={onToggleDiff}
|
||||
/>
|
||||
{!forceRawMode && <RawToggleButton rawMode={rawMode} onToggleRaw={onToggleRaw} />}
|
||||
<PlaceholderAction copy={{ label: 'Backlinks are coming soon' }}>
|
||||
<CursorText size={16} className={BREADCRUMB_ICON_CLASS} />
|
||||
</PlaceholderAction>
|
||||
<AIChatAction showAIChat={showAIChat} onToggleAIChat={onToggleAIChat} />
|
||||
<ArchiveAction archived={entry.archived} onArchive={onArchive} onUnarchive={onUnarchive} />
|
||||
<DeleteAction onDelete={onDelete} />
|
||||
<InspectorAction inspectorCollapsed={inspectorCollapsed} onToggleInspector={onToggleInspector} />
|
||||
<PlaceholderAction copy={{ label: 'More note actions are coming soon' }}>
|
||||
<DotsThree size={16} className={BREADCRUMB_ICON_CLASS} />
|
||||
</PlaceholderAction>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -295,6 +295,35 @@ describe('NoteList rendering', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('keeps the note-list search input full width and shows only an inline spinner while loading', () => {
|
||||
vi.useFakeTimers()
|
||||
try {
|
||||
renderNoteList({
|
||||
entries: [
|
||||
makeEntry({ path: '/vault/a.md', filename: 'a.md', title: 'Alpha Strategy' }),
|
||||
makeEntry({ path: '/vault/b.md', filename: 'b.md', title: 'Beta Note' }),
|
||||
],
|
||||
})
|
||||
|
||||
fireEvent.click(screen.getByTitle('Search notes'))
|
||||
fireEvent.change(screen.getByPlaceholderText('Search notes...'), { target: { value: 'strategy' } })
|
||||
|
||||
const searchInput = screen.getByPlaceholderText('Search notes...')
|
||||
expect(searchInput).toHaveClass('pr-8')
|
||||
expect(searchInput.parentElement).toHaveClass('relative', 'flex-1')
|
||||
expect(screen.getByTestId('note-list-search-loading')).toBeInTheDocument()
|
||||
expect(screen.queryByText('Searching...')).not.toBeInTheDocument()
|
||||
|
||||
act(() => {
|
||||
vi.advanceTimersByTime(180)
|
||||
})
|
||||
|
||||
expect(screen.queryByTestId('note-list-search-loading')).not.toBeInTheDocument()
|
||||
} finally {
|
||||
vi.useRealTimers()
|
||||
}
|
||||
})
|
||||
|
||||
it('shows backlinks from outgoing links in entity view', () => {
|
||||
const entriesWithBacklink = mockEntries.map((entry) =>
|
||||
entry.path === mockEntries[2].path ? { ...entry, outgoingLinks: ['Build Laputa App'] } : entry,
|
||||
|
||||
@@ -55,23 +55,23 @@ export function NoteListHeader({ title, typeDocument, isEntityView, listSort, li
|
||||
</div>
|
||||
{searchVisible && (
|
||||
<div className="border-b border-border px-3 py-2">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="relative flex-1" aria-live="polite">
|
||||
<Input
|
||||
ref={searchInputRef}
|
||||
placeholder="Search notes..."
|
||||
value={search}
|
||||
onChange={(e) => onSearchChange(e.target.value)}
|
||||
onKeyDown={onSearchKeyDown}
|
||||
className="h-8 text-[13px]"
|
||||
className="h-8 pr-8 text-[13px]"
|
||||
/>
|
||||
<div className="min-w-[92px] text-[12px] text-muted-foreground" aria-live="polite">
|
||||
{isSearching && (
|
||||
<span className="flex items-center gap-1" data-testid="note-list-search-loading">
|
||||
<Loader2 size={12} className="animate-spin" />
|
||||
Searching...
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{isSearching && (
|
||||
<span
|
||||
className="pointer-events-none absolute inset-y-0 right-3 flex items-center text-muted-foreground"
|
||||
data-testid="note-list-search-loading"
|
||||
>
|
||||
<Loader2 size={12} className="animate-spin" />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user