From 2ff54674750451b801242465b5ab503330d64680 Mon Sep 17 00:00:00 2001 From: lucaronin Date: Fri, 27 Feb 2026 22:43:19 +0100 Subject: [PATCH] fix: add traffic light clearance to note list header when sidebar is collapsed When the sidebar is hidden (editor-list view mode), the NoteList becomes the leftmost panel and its header title overlaps with macOS traffic lights. Add 80px left padding to the NoteList header when sidebarCollapsed is true, matching the sidebar title bar's existing traffic light clearance. Co-Authored-By: Claude Opus 4.6 --- design/note-list-title-position.pen | 1 + src/App.tsx | 2 +- src/components/NoteList.test.tsx | 26 ++++++++++++++++++++++++++ src/components/NoteList.tsx | 5 +++-- 4 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 design/note-list-title-position.pen diff --git a/design/note-list-title-position.pen b/design/note-list-title-position.pen new file mode 100644 index 00000000..9cc46bf4 --- /dev/null +++ b/design/note-list-title-position.pen @@ -0,0 +1 @@ +{"children":[{"type":"frame","id":"AeVO5","name":"Sidebar Collapsed — NoteList Title Position","x":0,"y":0,"width":600,"fill":"$--background","layout":"vertical","gap":12,"padding":16,"children":[{"type":"text","id":"ryDyy","name":"label","content":"Sidebar Collapsed — NoteList with traffic light clearance","fill":"$--muted-foreground","fontFamily":"Inter","fontSize":11,"fontWeight":"600","letterSpacing":1},{"type":"frame","id":"xZMtO","name":"App Window (sidebar collapsed)","clip":true,"width":"fill_container","cornerRadius":8,"stroke":{"align":"inside","thickness":1,"fill":"$--border"},"layout":"vertical","children":[{"type":"frame","id":"ymeIo","name":"Content Row","width":"fill_container","height":320,"layout":"none","children":[{"type":"frame","id":"VOhi9","name":"Panel: NoteList","x":0,"y":0,"width":300,"height":320,"fill":"$--card","stroke":{"align":"inside","fill":"$--border","thickness":{"right":1}},"layout":"vertical","children":[{"type":"frame","id":"Q884J","name":"NoteList Header (padded for traffic lights)","width":"fill_container","height":52,"alignItems":"center","justifyContent":"space_between","padding":[0,16,0,80],"stroke":{"align":"inside","fill":"$--border","thickness":{"bottom":1}},"children":[{"type":"text","id":"Ra8AY","content":"All Notes","fill":"$--foreground","fontFamily":"Inter","fontSize":14,"fontWeight":"600"},{"type":"frame","id":"0TYpG","name":"actions","alignItems":"center","gap":12,"children":[{"type":"icon_font","id":"v4VUg","iconFontFamily":"phosphor","iconFontName":"magnifying-glass","width":16,"height":16,"fill":"$--muted-foreground"},{"type":"icon_font","id":"y4CYF","iconFontFamily":"phosphor","iconFontName":"plus","width":16,"height":16,"fill":"$--muted-foreground"}]}]},{"type":"frame","id":"OCJxT","name":"Note Item 1","layout":"vertical","width":"fill_container","gap":4,"padding":[14,16],"stroke":{"align":"inside","fill":"$--border","thickness":{"bottom":1}},"children":[{"type":"text","id":"OThF7","content":"Build Laputa App","fill":"$--foreground","fontFamily":"Inter","fontSize":13,"fontWeight":"600"},{"type":"text","id":"rwqIZ","content":"Personal knowledge management app built with Tauri.","fill":"$--muted-foreground","fontFamily":"Inter","fontSize":12,"lineHeight":1.5,"textGrowth":"fixed-width","width":"fill_container"},{"type":"text","id":"0phHO","content":"2m ago","fill":"$--muted-foreground","fontFamily":"Inter","fontSize":11}]},{"type":"frame","id":"YmjMb","name":"Note Item 2","layout":"vertical","width":"fill_container","gap":4,"padding":[14,16],"stroke":{"align":"inside","fill":"$--border","thickness":{"bottom":1}},"children":[{"type":"text","id":"gh2fT","content":"Facebook Ads Strategy","fill":"$--foreground","fontFamily":"Inter","fontSize":13,"fontWeight":"600"},{"type":"text","id":"Yn5dO","content":"Lookalike audiences convert 3x better than cold.","fill":"$--muted-foreground","fontFamily":"Inter","fontSize":12,"lineHeight":1.5,"textGrowth":"fixed-width","width":"fill_container"},{"type":"text","id":"5AKHZ","content":"1h ago","fill":"$--muted-foreground","fontFamily":"Inter","fontSize":11}]}]},{"type":"frame","id":"V4qF5","name":"Panel: Editor","x":300,"y":0,"width":300,"height":320,"fill":"$--background","layout":"vertical","children":[{"type":"frame","id":"ztw3q","name":"Tab Bar","width":"fill_container","height":45,"fill":"$--sidebar","alignItems":"center","stroke":{"align":"inside","fill":"$--border","thickness":{"bottom":1}},"children":[{"type":"text","id":"NGfeH","content":" Build Laputa App","fill":"$--foreground","fontFamily":"Inter","fontSize":13,"fontWeight":"500"}]},{"type":"frame","id":"DXXof","name":"Editor Body","width":"fill_container","height":"fill_container","fill":"$--background","padding":[24,32],"children":[{"type":"text","id":"Hgsg7","content":"Build Laputa App","fill":"$--foreground","fontFamily":"Inter","fontSize":28,"fontWeight":"700"}]}]},{"type":"frame","id":"aSWvV","name":"Traffic Lights","x":0,"y":0,"gap":8,"padding":[18,0,0,14],"children":[{"type":"ellipse","id":"wwhm9","width":12,"height":12,"fill":"$--traffic-red"},{"type":"ellipse","id":"vYx2N","width":12,"height":12,"fill":"$--traffic-yellow"},{"type":"ellipse","id":"eRAiC","width":12,"height":12,"fill":"$--traffic-green"}]}]}]},{"type":"frame","id":"u5rgz","name":"Annotation","layout":"horizontal","gap":8,"alignItems":"center","padding":[8,12],"fill":"#E8F4FE","cornerRadius":6,"children":[{"type":"text","id":"hDyxd","content":"→","fill":"$--primary","fontFamily":"Inter","fontSize":14,"fontWeight":"700"},{"type":"text","id":"EtBGq","content":"paddingLeft: 80px clears traffic lights when sidebar is hidden (matches sidebar title bar padding)","fill":"$--foreground","fontFamily":"Inter","fontSize":12,"lineHeight":1.4,"textGrowth":"fixed-width","width":520}]}]}],"variables":{"--background":{"type":"color","value":"#FFFFFF"},"--foreground":{"type":"color","value":"#37352F"},"--card":{"type":"color","value":"#FFFFFF"},"--border":{"type":"color","value":"#E9E9E7"},"--muted":{"type":"color","value":"#F0F0EF"},"--muted-foreground":{"type":"color","value":"#787774"},"--sidebar":{"type":"color","value":"#F7F6F3"},"--sidebar-border":{"type":"color","value":"#E9E9E7"},"--primary":{"type":"color","value":"#155DFF"},"--destructive":{"type":"color","value":"#E03E3E"},"--accent-red-light":{"type":"color","value":"#E03E3E18"},"--traffic-red":{"type":"color","value":"#FF5F57"},"--traffic-yellow":{"type":"color","value":"#FEBC2E"},"--traffic-green":{"type":"color","value":"#28C840"}}} \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index d1207d41..674a6527 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -282,7 +282,7 @@ function App() { {noteListVisible && ( <>
- +
diff --git a/src/components/NoteList.test.tsx b/src/components/NoteList.test.tsx index dcfa549d..34371a10 100644 --- a/src/components/NoteList.test.tsx +++ b/src/components/NoteList.test.tsx @@ -1077,3 +1077,29 @@ describe('NoteList — multi-select', () => { expect(screen.queryByTestId('bulk-action-bar')).not.toBeInTheDocument() }) }) + +describe('NoteList — traffic light padding when sidebar collapsed', () => { + it('adds left padding to header when sidebarCollapsed is true', () => { + const { container } = render( + + ) + const header = container.querySelector('.h-\\[52px\\]') as HTMLElement + expect(header.style.paddingLeft).toBe('80px') + }) + + it('does not add extra left padding when sidebarCollapsed is false', () => { + const { container } = render( + + ) + const header = container.querySelector('.h-\\[52px\\]') as HTMLElement + expect(header.style.paddingLeft).toBe('') + }) + + it('does not add extra left padding when sidebarCollapsed is not provided', () => { + const { container } = render( + + ) + const header = container.querySelector('.h-\\[52px\\]') as HTMLElement + expect(header.style.paddingLeft).toBe('') + }) +}) diff --git a/src/components/NoteList.tsx b/src/components/NoteList.tsx index b8edff0c..22f6fc14 100644 --- a/src/components/NoteList.tsx +++ b/src/components/NoteList.tsx @@ -26,6 +26,7 @@ interface NoteListProps { allContent: Record modifiedFiles?: ModifiedFile[] getNoteStatus?: (path: string) => NoteStatus + sidebarCollapsed?: boolean onSelectNote: (entry: VaultEntry) => void onReplaceActiveTab: (entry: VaultEntry) => void onCreateNote: () => void @@ -275,7 +276,7 @@ function useNoteListData({ entries, selection, allContent, query, listSort, list const defaultGetNoteStatus = (): NoteStatus => 'clean' -function NoteListInner({ entries, selection, selectedNote, allContent, modifiedFiles, getNoteStatus, onReplaceActiveTab, onCreateNote, onBulkArchive, onBulkTrash }: NoteListProps) { +function NoteListInner({ entries, selection, selectedNote, allContent, modifiedFiles, getNoteStatus, sidebarCollapsed, onReplaceActiveTab, onCreateNote, onBulkArchive, onBulkTrash }: NoteListProps) { const [search, setSearch] = useState('') const [searchVisible, setSearchVisible] = useState(false) const [collapsedGroups, setCollapsedGroups] = useState>(new Set()) @@ -354,7 +355,7 @@ function NoteListInner({ entries, selection, selectedNote, allContent, modifiedF return (
-
+

{resolveHeaderTitle(selection, typeDocument)}

{!isEntityView && }