style(header): session tabs follow the titlebar tab design language
Uniform compact tabs (h-7, 6px radius, 13px medium) that share the strip width and shrink before scrolling, thin separators between inactive neighbours that disappear around the active or hovered tab, a hover-revealed menu button anchored to the tab's end, a soft selection pill for the active tab, and hidden scrollbars with fade edges. The active tab is single-line: the project/branch meta row stays only in the VS Code header, which keeps the plain title.
This commit is contained in:
@@ -1739,6 +1739,7 @@ export const Header: React.FC = () => {
|
||||
<form
|
||||
ref={headerRenameFormRef}
|
||||
className="flex w-full min-w-0 items-center gap-2 leading-tight"
|
||||
onPointerDown={(event) => event.stopPropagation()}
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault();
|
||||
void saveHeaderSessionRename();
|
||||
@@ -1755,7 +1756,7 @@ export const Header: React.FC = () => {
|
||||
}
|
||||
}}
|
||||
placeholder={t('sessions.sidebar.session.menu.rename')}
|
||||
className="min-w-0 flex-1 bg-transparent typography-ui-label text-[14px] font-normal leading-tight outline-none placeholder:text-muted-foreground"
|
||||
className="min-w-0 flex-1 bg-transparent text-[13px] font-medium leading-4 outline-none placeholder:text-muted-foreground"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
@@ -1776,36 +1777,16 @@ export const Header: React.FC = () => {
|
||||
</button>
|
||||
</form>
|
||||
) : (
|
||||
<span className="truncate typography-ui-label text-[14px] font-normal leading-tight text-foreground max-w-full">
|
||||
<span className="truncate text-[13px] font-medium leading-4 text-foreground max-w-full">
|
||||
{isNewSessionDraftOpen ? t('sessions.switcher.draftTitle') : currentSessionTitle}
|
||||
</span>
|
||||
)}
|
||||
{showHeaderMetaRow ? (
|
||||
<span className="flex min-w-0 max-w-full items-center gap-1.5 truncate typography-micro text-[10.5px] font-normal leading-tight text-muted-foreground/75">
|
||||
{activeProjectLabel ? <span className="truncate">{activeProjectLabel}</span> : null}
|
||||
{currentBranchLabel ? (
|
||||
<span className="inline-flex min-w-0 items-center gap-0.5">
|
||||
<Icon name="git-branch" className="h-3 w-3 flex-shrink-0 text-muted-foreground/70" />
|
||||
<span className="truncate">{currentBranchLabel}</span>
|
||||
</span>
|
||||
) : null}
|
||||
{!isNewSessionDraftOpen && worktreeBadgeKind ? (
|
||||
<span className={cn(
|
||||
"inline-flex min-w-0 items-center gap-0.5",
|
||||
worktreeBadgeKind === 'attention' || worktreeBadgeKind === 'invalid' || worktreeBadgeKind === 'missing' ? 'text-status-warning' : 'text-muted-foreground/60'
|
||||
)}>
|
||||
<Icon name="alert" className="h-3 w-3 flex-shrink-0" />
|
||||
<span className="truncate">{worktreeBadge}</span>
|
||||
</span>
|
||||
) : null}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
<div className={cn(
|
||||
'flex h-[18px] shrink-0 items-center justify-center',
|
||||
// Top-aligned only when the title has a metadata line under it;
|
||||
// alone, the title is centred and the button must follow.
|
||||
showHeaderMetaRow ? 'self-start' : 'self-center',
|
||||
'self-center',
|
||||
)}>
|
||||
{currentSessionId && !isNewSessionDraftOpen && !isRenamingHeaderSession ? (
|
||||
<DropdownMenu
|
||||
|
||||
@@ -45,15 +45,16 @@ const ActiveTabShell: React.FC<{ id: string; children: React.ReactNode }> = ({ i
|
||||
<div
|
||||
ref={setNodeRef}
|
||||
style={{ transform: DndCSS.Translate.toString(transform), transition }}
|
||||
className={cn('h-9 shrink-0 touch-none', isDragging && 'z-10 opacity-60')}
|
||||
className={cn('session-tab-slot flex h-7 w-56 min-w-16 max-w-56 flex-shrink touch-none', isDragging && 'z-10 opacity-60')}
|
||||
data-active-session-tab
|
||||
data-active="true"
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
>
|
||||
<div
|
||||
role="tab"
|
||||
aria-selected
|
||||
className="flex h-9 min-w-0 max-w-[340px] items-center rounded-[10px] bg-interactive-selection px-3"
|
||||
className="flex h-7 w-full min-w-0 items-center rounded-md bg-interactive-selection px-2"
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
@@ -85,7 +86,8 @@ const InactiveSessionTab: React.FC<{
|
||||
<div
|
||||
ref={setNodeRef}
|
||||
style={{ transform: DndCSS.Translate.toString(transform), transition }}
|
||||
className={cn('h-8 shrink-0', isDragging && 'z-10 opacity-60')}
|
||||
className={cn('session-tab-slot flex h-7 w-56 min-w-10 max-w-56 flex-shrink', isDragging && 'z-10 opacity-60')}
|
||||
data-active="false"
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
>
|
||||
@@ -107,13 +109,19 @@ const InactiveSessionTab: React.FC<{
|
||||
}
|
||||
}}
|
||||
className={cn(
|
||||
'group/session-tab flex h-8 max-w-[200px] cursor-pointer touch-none select-none items-center rounded-[10px] px-3',
|
||||
'text-muted-foreground transition-colors duration-150 hover:bg-interactive-hover/40 hover:text-foreground',
|
||||
menuOpen && 'bg-interactive-hover/40 text-foreground',
|
||||
'group/session-tab relative flex h-7 w-full min-w-0 cursor-pointer touch-none select-none items-center rounded-md px-2',
|
||||
'text-muted-foreground transition-colors duration-150 hover:bg-interactive-hover hover:text-foreground',
|
||||
menuOpen && 'bg-interactive-hover text-foreground',
|
||||
)}
|
||||
title={title}
|
||||
>
|
||||
<span className="min-w-0 truncate typography-ui-label text-[13px] font-normal leading-tight">
|
||||
<span
|
||||
className={cn(
|
||||
'min-w-0 flex-1 truncate text-[13px] font-medium leading-4',
|
||||
'group-hover/session-tab:pr-5',
|
||||
menuOpen && 'pr-5',
|
||||
)}
|
||||
>
|
||||
{title}
|
||||
</span>
|
||||
<DropdownMenu open={menuOpen} onOpenChange={setMenuOpen}>
|
||||
@@ -124,10 +132,10 @@ const InactiveSessionTab: React.FC<{
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
onPointerDown={(event) => event.stopPropagation()}
|
||||
className={cn(
|
||||
'ml-0 hidden w-0 shrink-0 items-center justify-center overflow-hidden rounded-md text-muted-foreground',
|
||||
'absolute right-1 top-1/2 hidden size-5 -translate-y-1/2 items-center justify-center rounded text-muted-foreground',
|
||||
'opacity-0 transition-opacity duration-150 hover:text-foreground',
|
||||
'group-hover/session-tab:ml-1.5 group-hover/session-tab:flex group-hover/session-tab:h-5 group-hover/session-tab:w-5 group-hover/session-tab:opacity-100',
|
||||
menuOpen && 'ml-1.5 flex h-5 w-5 opacity-100',
|
||||
'group-hover/session-tab:flex group-hover/session-tab:opacity-100',
|
||||
menuOpen && 'flex opacity-100',
|
||||
)}
|
||||
>
|
||||
<Icon name="more" className="size-4" />
|
||||
@@ -298,7 +306,7 @@ export const SessionTabsStrip: React.FC<{ children: React.ReactNode }> = ({ chil
|
||||
<div
|
||||
ref={scrollRef}
|
||||
onScroll={updateEdges}
|
||||
className="flex min-w-0 items-center gap-1 overflow-x-auto py-1 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
|
||||
className="flex min-w-0 flex-1 items-center gap-1.5 overflow-x-auto overscroll-x-contain [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden"
|
||||
style={maskImage ? { maskImage, WebkitMaskImage: maskImage } : undefined}
|
||||
>
|
||||
<DndContext
|
||||
@@ -315,7 +323,8 @@ export const SessionTabsStrip: React.FC<{ children: React.ReactNode }> = ({ chil
|
||||
<div
|
||||
role="tab"
|
||||
aria-selected
|
||||
className="flex h-9 min-w-0 max-w-[340px] shrink-0 items-center rounded-[10px] bg-interactive-selection px-3"
|
||||
className="session-tab-slot flex h-7 w-56 min-w-16 max-w-56 flex-shrink items-center rounded-md bg-interactive-selection px-2"
|
||||
data-active="true"
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@@ -1781,3 +1781,25 @@ html.desktop-runtime [class*="cursor-pointer"] {
|
||||
html.desktop-runtime .markdown-content [data-openchamber-file-link="true"] {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* Header session tabs: thin separators between inactive neighbours, hidden
|
||||
around the active or hovered tab (mirrors the titlebar tab language). */
|
||||
.session-tab-slot {
|
||||
position: relative;
|
||||
}
|
||||
.session-tab-slot:not(:first-child):not([data-active='true'])::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
inset-inline-start: -2.75px;
|
||||
width: 1.5px;
|
||||
height: 12px;
|
||||
border-radius: 9999px;
|
||||
background: var(--border);
|
||||
opacity: 0.6;
|
||||
}
|
||||
.session-tab-slot[data-active='true'] + .session-tab-slot::before,
|
||||
.session-tab-slot:not([data-active='true']):hover::before,
|
||||
.session-tab-slot:not([data-active='true']):hover + .session-tab-slot::before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user