diff --git a/src/components/BreadcrumbBar.test.tsx b/src/components/BreadcrumbBar.test.tsx index 81d60225..26445de6 100644 --- a/src/components/BreadcrumbBar.test.tsx +++ b/src/components/BreadcrumbBar.test.tsx @@ -111,6 +111,38 @@ describe('BreadcrumbBar — archive/unarchive', () => { }) }) +describe('BreadcrumbBar — title in breadcrumb on scroll', () => { + it('does not show title when titleHidden is false', () => { + render() + expect(screen.queryByText('Test Note')).not.toBeInTheDocument() + }) + + it('shows type and title when titleHidden is true', () => { + render() + expect(screen.getByText('Note')).toBeInTheDocument() + expect(screen.getByText('›')).toBeInTheDocument() + expect(screen.getByText('Test Note')).toBeInTheDocument() + }) + + it('shows emoji icon when entry has an emoji icon', () => { + const entryWithEmoji = { ...baseEntry, icon: '🚀' } + render() + expect(screen.getByText('🚀')).toBeInTheDocument() + }) + + it('does not show icon when entry has a non-emoji icon', () => { + const entryWithPhosphor = { ...baseEntry, icon: 'cooking-pot' } + render() + expect(screen.queryByText('cooking-pot')).not.toBeInTheDocument() + }) + + it('falls back to "Note" when isA is null', () => { + const entryNoType = { ...baseEntry, isA: null } + render() + expect(screen.getByText('Note')).toBeInTheDocument() + }) +}) + describe('BreadcrumbBar — raw editor toggle', () => { it('shows Raw editor button with tooltip "Raw editor" when rawMode is off', () => { const onToggleRaw = vi.fn() diff --git a/src/components/BreadcrumbBar.tsx b/src/components/BreadcrumbBar.tsx index 55b3ca5b..93b76ae4 100644 --- a/src/components/BreadcrumbBar.tsx +++ b/src/components/BreadcrumbBar.tsx @@ -32,6 +32,8 @@ interface BreadcrumbBarProps { onRestore?: () => void onArchive?: () => void onUnarchive?: () => void + /** When true, the note title is scrolled out of view — show it inline. */ + titleHidden?: boolean } const DISABLED_ICON_STYLE = { opacity: 0.4, cursor: 'not-allowed' } as const @@ -161,19 +163,38 @@ function BreadcrumbActions({ entry, showDiffToggle, diffMode, diffLoading, onTog ) } +function BreadcrumbTitle({ entry }: { entry: VaultEntry }) { + const typeLabel = entry.isA ?? 'Note' + const icon = entry.icon + const emojiIcon = icon && /^\p{Emoji}/u.test(icon) ? icon : null + return ( +
+ {typeLabel} + + {emojiIcon && {emojiIcon}} + {entry.title} +
+ ) +} + export const BreadcrumbBar = memo(function BreadcrumbBar({ - entry, ...actionProps + entry, titleHidden, ...actionProps }: BreadcrumbBarProps) { return (
+
+ {titleHidden && } +
) diff --git a/src/components/EditorContent.tsx b/src/components/EditorContent.tsx index c82de615..8fa39b8a 100644 --- a/src/components/EditorContent.tsx +++ b/src/components/EditorContent.tsx @@ -1,5 +1,5 @@ import type React from 'react' -import { useCallback } from 'react' +import { useCallback, useRef, useState, useEffect } from 'react' import type { VaultEntry, NoteStatus } from '../types' import type { useCreateBlockNote } from '@blocknote/react' import { DiffView } from './DiffView' @@ -120,8 +120,9 @@ function bindPath(cb: ((path: string) => void) | undefined, path: string) { return cb ? () => cb(path) : undefined } -function ActiveTabBreadcrumb({ activeTab, props }: { +function ActiveTabBreadcrumb({ activeTab, titleHidden, props }: { activeTab: Tab + titleHidden: boolean props: Omit }) { const wordCount = countWords(activeTab.content) @@ -130,6 +131,7 @@ function ActiveTabBreadcrumb({ activeTab, props }: { (null) + const [titleScrolledAway, setTitleScrolledAway] = useState(false) + const titleHidden = showEditor && titleScrolledAway + + useEffect(() => { + const el = titleSectionRef.current + if (!el) return + const observer = new IntersectionObserver( + ([e]) => setTitleScrolledAway(!e.isIntersecting), + { threshold: 0 }, + ) + observer.observe(el) + return () => observer.disconnect() + }, [activeTab?.entry.path, showEditor]) + const handleSetIcon = useCallback((emoji: string) => { if (activeTab) onSetNoteIcon?.(activeTab.entry.path, emoji) }, [activeTab, onSetNoteIcon]) @@ -181,6 +198,7 @@ export function EditorContent({ {activeTab && ( )} @@ -203,7 +221,7 @@ export function EditorContent({ {showEditor && activeTab && (
-
+
{!emojiIcon && (