|
6 | 6 | * The store keeps per-path note content (`noteContents`) shared across |
7 | 7 | * all panes, so the same note open in two panes stays in sync on edit. |
8 | 8 | */ |
| 9 | +import { createPortal } from 'react-dom' |
9 | 10 | import { |
10 | 11 | Fragment, |
11 | 12 | useCallback, |
@@ -98,7 +99,7 @@ import { autocompletion } from '@codemirror/autocomplete' |
98 | 99 | import { useStore } from '../store' |
99 | 100 | import type { LineNumberMode } from '../store' |
100 | 101 | import type { PaneEdge, PaneLeaf } from '../lib/pane-layout' |
101 | | -import { findLeaf, inferPaneDropEdge } from '../lib/pane-layout' |
| 102 | +import { findLeaf, allLeaves, inferPaneDropEdge } from '../lib/pane-layout' |
102 | 103 | import { livePreviewPlugin } from '../lib/cm-live-preview' |
103 | 104 | import { codeBlockFlairPlugin } from '../lib/cm-code-block-flair' |
104 | 105 | import { tablePlugin, tableVimEntry } from '../lib/cm-table' |
@@ -892,6 +893,8 @@ export function EditorPane({ pane }: { pane: PaneLeaf }): JSX.Element { |
892 | 893 | const textFont = useStore((s) => s.textFont) |
893 | 894 | const tabsEnabled = useStore((s) => s.tabsEnabled) |
894 | 895 | const wrapTabs = useStore((s) => s.wrapTabs) |
| 896 | + const titlebarTabs = useStore((s) => s.titlebarTabs) |
| 897 | + const isSinglePane = useStore((s) => allLeaves(s.paneLayout).length === 1) |
895 | 898 | const jumpToPreviousNote = useStore((s) => s.jumpToPreviousNote) |
896 | 899 | const jumpToNextNote = useStore((s) => s.jumpToNextNote) |
897 | 900 | const canGoBack = useStore((s) => s.noteBackstack.length > 0) |
@@ -3438,11 +3441,13 @@ export function EditorPane({ pane }: { pane: PaneLeaf }): JSX.Element { |
3438 | 3441 | el?.scrollIntoView({ inline: 'nearest', block: 'nearest' }) |
3439 | 3442 | }, [activeTab, hasTabs, wrapTabs, tabStripMeasureKey]) |
3440 | 3443 |
|
3441 | | - // Outer header holds the back/forward nav buttons + the (flex-1) tab strip. |
3442 | | - const tabStripHeaderClass = [ |
3443 | | - 'glass-header flex shrink-0 items-stretch border-b border-paper-300/70 pl-1', |
3444 | | - wrapTabs ? 'min-h-[var(--z-tab-height)]' : 'h-[var(--z-tab-height)]' |
3445 | | - ].join(' ') |
| 3444 | + const titlebarTabsActive = titlebarTabs && isSinglePane && hasTabs |
| 3445 | + const tabStripHeaderClass = titlebarTabsActive |
| 3446 | + ? 'titlebar-tab-strip flex shrink-0 items-stretch' |
| 3447 | + : [ |
| 3448 | + 'glass-header flex shrink-0 items-stretch border-b border-paper-300/70 pl-1', |
| 3449 | + wrapTabs ? 'min-h-[var(--z-tab-height)]' : 'h-[var(--z-tab-height)]' |
| 3450 | + ].join(' ') |
3446 | 3451 | const tabStripClass = [ |
3447 | 3452 | 'workspace-tab-strip flex min-w-0 flex-1 items-stretch gap-0', |
3448 | 3453 | wrapTabs |
@@ -3663,6 +3668,72 @@ export function EditorPane({ pane }: { pane: PaneLeaf }): JSX.Element { |
3663 | 3668 | isActive ? '' : 'opacity-[0.98]' |
3664 | 3669 | ].join(' ') |
3665 | 3670 |
|
| 3671 | + const tabStripHost = |
| 3672 | + typeof document !== 'undefined' ? document.getElementById('titlebar-tabs-host') : null |
| 3673 | + const tabStrip = ( |
| 3674 | + <div className={tabStripHeaderClass}> |
| 3675 | + <div className="flex shrink-0 items-center gap-0.5 self-center"> |
| 3676 | + {!titlebarTabsActive && !sidebarOpen && ( |
| 3677 | + <IconBtn |
| 3678 | + title="Show sidebar (⌘1)" |
| 3679 | + onClick={toggleSidebar} |
| 3680 | + tooltipAlign="left" |
| 3681 | + > |
| 3682 | + <PanelLeftIcon width={16} height={16} /> |
| 3683 | + </IconBtn> |
| 3684 | + )} |
| 3685 | + <IconBtn |
| 3686 | + title={`Go back (${getKeymapDisplay( |
| 3687 | + tabNavOverrides, |
| 3688 | + vimMode ? 'vim.historyBack' : 'global.historyBack' |
| 3689 | + )})`} |
| 3690 | + onClick={() => void jumpToPreviousNote()} |
| 3691 | + disabled={!canGoBack} |
| 3692 | + tooltipAlign="left" |
| 3693 | + > |
| 3694 | + <ArrowLeftIcon width={16} height={16} /> |
| 3695 | + </IconBtn> |
| 3696 | + <IconBtn |
| 3697 | + title={`Go forward (${getKeymapDisplay( |
| 3698 | + tabNavOverrides, |
| 3699 | + vimMode ? 'vim.historyForward' : 'global.historyForward' |
| 3700 | + )})`} |
| 3701 | + onClick={() => void jumpToNextNote()} |
| 3702 | + disabled={!canGoForward} |
| 3703 | + tooltipAlign="left" |
| 3704 | + > |
| 3705 | + <ArrowRightIcon width={16} height={16} /> |
| 3706 | + </IconBtn> |
| 3707 | + </div> |
| 3708 | + <div |
| 3709 | + ref={tabStripRef} |
| 3710 | + className={tabStripClass} |
| 3711 | + onDragOver={handleTabStripDragOver} |
| 3712 | + onDrop={handleTabStripDrop} |
| 3713 | + > |
| 3714 | + {tabItems.map((tab, i) => { |
| 3715 | + // Draw a subtle vertical separator between the last pinned |
| 3716 | + // tab and the first unpinned one (VSCode convention). The |
| 3717 | + // separator is a flex sibling, not a wrapper, so drag hit- |
| 3718 | + // detection on the tab itself is unchanged. |
| 3719 | + const prevPinned = i > 0 ? tabItems[i - 1].pinned : false |
| 3720 | + const needsSeparator = prevPinned && !tab.pinned |
| 3721 | + return ( |
| 3722 | + <Fragment key={tab.path}> |
| 3723 | + {needsSeparator && ( |
| 3724 | + <div |
| 3725 | + aria-hidden |
| 3726 | + className="mx-0.5 h-5 shrink-0 self-center border-l border-paper-300/70" |
| 3727 | + /> |
| 3728 | + )} |
| 3729 | + {renderTab(tab)} |
| 3730 | + </Fragment> |
| 3731 | + ) |
| 3732 | + })} |
| 3733 | + </div> |
| 3734 | + </div> |
| 3735 | + ) |
| 3736 | + |
3666 | 3737 | return ( |
3667 | 3738 | <section |
3668 | 3739 | ref={paneRootRef} |
@@ -3690,69 +3761,8 @@ export function EditorPane({ pane }: { pane: PaneLeaf }): JSX.Element { |
3690 | 3761 | setFocusedPanel('editor') |
3691 | 3762 | }} |
3692 | 3763 | > |
3693 | | - {hasTabs && ( |
3694 | | - <div className={tabStripHeaderClass}> |
3695 | | - <div className="flex shrink-0 items-center gap-0.5 self-center"> |
3696 | | - {!sidebarOpen && ( |
3697 | | - <IconBtn |
3698 | | - title="Show sidebar (⌘1)" |
3699 | | - onClick={toggleSidebar} |
3700 | | - tooltipAlign="left" |
3701 | | - > |
3702 | | - <PanelLeftIcon width={16} height={16} /> |
3703 | | - </IconBtn> |
3704 | | - )} |
3705 | | - <IconBtn |
3706 | | - title={`Go back (${getKeymapDisplay( |
3707 | | - tabNavOverrides, |
3708 | | - vimMode ? 'vim.historyBack' : 'global.historyBack' |
3709 | | - )})`} |
3710 | | - onClick={() => void jumpToPreviousNote()} |
3711 | | - disabled={!canGoBack} |
3712 | | - tooltipAlign="left" |
3713 | | - > |
3714 | | - <ArrowLeftIcon width={16} height={16} /> |
3715 | | - </IconBtn> |
3716 | | - <IconBtn |
3717 | | - title={`Go forward (${getKeymapDisplay( |
3718 | | - tabNavOverrides, |
3719 | | - vimMode ? 'vim.historyForward' : 'global.historyForward' |
3720 | | - )})`} |
3721 | | - onClick={() => void jumpToNextNote()} |
3722 | | - disabled={!canGoForward} |
3723 | | - tooltipAlign="left" |
3724 | | - > |
3725 | | - <ArrowRightIcon width={16} height={16} /> |
3726 | | - </IconBtn> |
3727 | | - </div> |
3728 | | - <div |
3729 | | - ref={tabStripRef} |
3730 | | - className={tabStripClass} |
3731 | | - onDragOver={handleTabStripDragOver} |
3732 | | - onDrop={handleTabStripDrop} |
3733 | | - > |
3734 | | - {tabItems.map((tab, i) => { |
3735 | | - // Draw a subtle vertical separator between the last pinned |
3736 | | - // tab and the first unpinned one (VSCode convention). The |
3737 | | - // separator is a flex sibling, not a wrapper, so drag hit- |
3738 | | - // detection on the tab itself is unchanged. |
3739 | | - const prevPinned = i > 0 ? tabItems[i - 1].pinned : false |
3740 | | - const needsSeparator = prevPinned && !tab.pinned |
3741 | | - return ( |
3742 | | - <Fragment key={tab.path}> |
3743 | | - {needsSeparator && ( |
3744 | | - <div |
3745 | | - aria-hidden |
3746 | | - className="mx-0.5 h-5 shrink-0 self-center border-l border-paper-300/70" |
3747 | | - /> |
3748 | | - )} |
3749 | | - {renderTab(tab)} |
3750 | | - </Fragment> |
3751 | | - ) |
3752 | | - })} |
3753 | | - </div> |
3754 | | - </div> |
3755 | | - )} |
| 3764 | + {hasTabs && !titlebarTabsActive && tabStrip} |
| 3765 | + {hasTabs && titlebarTabsActive && tabStripHost && createPortal(tabStrip, tabStripHost)} |
3756 | 3766 | {content && !zenMode && ( |
3757 | 3767 | <header className="glass-header flex h-12 shrink-0 items-center justify-between gap-3 px-4"> |
3758 | 3768 | <div className="flex min-w-0 flex-1 items-center gap-1"> |
|
0 commit comments