fix(app): restore tab layout consistency (#33804)
This commit is contained in:
@@ -56,6 +56,28 @@ describe("titlebar tab drag", () => {
|
|||||||
expect(captureTabDragLayout(list, ["a", "b"]).dividerWidth).toBe(13)
|
expect(captureTabDragLayout(list, ["a", "b"]).dividerWidth).toBe(13)
|
||||||
list.remove()
|
list.remove()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("uses the list gap as the divider width", () => {
|
||||||
|
const list = document.createElement("div")
|
||||||
|
const first = document.createElement("div")
|
||||||
|
const second = document.createElement("div")
|
||||||
|
const firstTab = document.createElement("div")
|
||||||
|
const secondTab = document.createElement("div")
|
||||||
|
first.dataset.titlebarTabSlot = ""
|
||||||
|
first.dataset.tabKey = "a"
|
||||||
|
second.dataset.titlebarTabSlot = ""
|
||||||
|
second.dataset.tabKey = "b"
|
||||||
|
firstTab.dataset.titlebarTab = ""
|
||||||
|
secondTab.dataset.titlebarTab = ""
|
||||||
|
first.append(firstTab)
|
||||||
|
second.append(secondTab)
|
||||||
|
list.append(first, second)
|
||||||
|
list.style.columnGap = "13.5px"
|
||||||
|
document.body.append(list)
|
||||||
|
|
||||||
|
expect(captureTabDragLayout(list, ["a", "b"]).dividerWidth).toBe(13.5)
|
||||||
|
list.remove()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("titlebar tab gestures", () => {
|
describe("titlebar tab gestures", () => {
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export function captureTabDragLayout(list: HTMLElement, order: string[]) {
|
|||||||
|
|
||||||
let dividerWidth = 0
|
let dividerWidth = 0
|
||||||
if (order.length >= 2) {
|
if (order.length >= 2) {
|
||||||
|
const gap = Number.parseFloat(getComputedStyle(list).columnGap) || 0
|
||||||
const secondId = order[1]
|
const secondId = order[1]
|
||||||
for (const slot of slots) {
|
for (const slot of slots) {
|
||||||
if (slot.dataset.tabKey !== secondId) continue
|
if (slot.dataset.tabKey !== secondId) continue
|
||||||
@@ -36,10 +37,11 @@ export function captureTabDragLayout(list: HTMLElement, order: string[]) {
|
|||||||
if (!tab) break
|
if (!tab) break
|
||||||
const style = getComputedStyle(slot)
|
const style = getComputedStyle(slot)
|
||||||
dividerWidth =
|
dividerWidth =
|
||||||
|
gap ||
|
||||||
slot.getBoundingClientRect().width -
|
slot.getBoundingClientRect().width -
|
||||||
tab.getBoundingClientRect().width +
|
tab.getBoundingClientRect().width +
|
||||||
(Number.parseFloat(style.marginLeft) || 0) +
|
(Number.parseFloat(style.marginLeft) || 0) +
|
||||||
(Number.parseFloat(style.marginRight) || 0)
|
(Number.parseFloat(style.marginRight) || 0)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,29 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-slot="titlebar-tabs"] {
|
||||||
|
container-type: inline-size;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-titlebar-tab-list] {
|
||||||
|
gap: calc(min(12px, 2cqw) + 1.5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-titlebar-tab-slot] {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-titlebar-tab-slot]:not(:first-child)::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
left: calc(0px - min(6px, 1cqw) - 0.75px);
|
||||||
|
width: 1.5px;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 9999px;
|
||||||
|
background: var(--v2-background-bg-layer-02);
|
||||||
|
}
|
||||||
|
|
||||||
[data-titlebar-tab] [data-slot="tab-close"]::before {
|
[data-titlebar-tab] [data-slot="tab-close"]::before {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -62,8 +85,14 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-titlebar-tab]:hover [data-slot="tab-close"],
|
@container (max-width: 64px) {
|
||||||
[data-titlebar-tab][data-active="true"] [data-slot="tab-close"],
|
[data-titlebar-tab-link] {
|
||||||
[data-titlebar-tab][data-editing="true"] [data-slot="tab-close"] {
|
justify-content: center;
|
||||||
background: var(--tab-bg);
|
gap: 0;
|
||||||
|
padding-inline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-titlebar-tab-title] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ export function TabNavItem(props: {
|
|||||||
data-slot="titlebar-tab-item"
|
data-slot="titlebar-tab-item"
|
||||||
data-title-overflow={titleOverflowing()}
|
data-title-overflow={titleOverflowing()}
|
||||||
data-editing={editing()}
|
data-editing={editing()}
|
||||||
class="group relative flex h-7 min-w-24 max-w-60 select-none flex-row items-center gap-1.5 overflow-hidden whitespace-nowrap rounded-[6px] bg-[var(--tab-bg)] px-1.5 [--tab-bg:var(--v2-background-bg-deep)] hover:[--tab-bg:var(--v2-background-bg-layer-02)] has-[>a:focus-visible]:[--tab-bg:var(--v2-background-bg-layer-02)] data-[active='true']:[--tab-bg:var(--v2-background-bg-layer-02)] data-[dragging='true']:[--tab-bg:var(--v2-background-bg-layer-02)] data-[pressed='true']:[--tab-bg:var(--v2-background-bg-layer-02)] data-[editing='true']:[--tab-bg:var(--v2-background-bg-layer-02)]"
|
class="group relative flex h-7 w-full min-w-0 max-w-56 select-none flex-row items-center gap-1.5 overflow-hidden whitespace-nowrap rounded-[6px] bg-[var(--tab-bg)] px-1.5 [container-type:inline-size] [--tab-bg:var(--v2-background-bg-deep)] hover:[--tab-bg:var(--v2-background-bg-layer-02)] has-[>a:focus-visible]:[--tab-bg:var(--v2-background-bg-layer-02)] data-[active='true']:[--tab-bg:var(--v2-background-bg-layer-02)] data-[dragging='true']:[--tab-bg:var(--v2-background-bg-layer-02)] data-[pressed='true']:[--tab-bg:var(--v2-background-bg-layer-02)] data-[editing='true']:[--tab-bg:var(--v2-background-bg-layer-02)]"
|
||||||
classList={{ invisible: props.hidden }}
|
classList={{ invisible: props.hidden }}
|
||||||
data-active={props.active}
|
data-active={props.active}
|
||||||
data-dragging={props.dragging}
|
data-dragging={props.dragging}
|
||||||
@@ -193,6 +193,7 @@ export function TabNavItem(props: {
|
|||||||
return (
|
return (
|
||||||
<a
|
<a
|
||||||
data-slot="tab-link"
|
data-slot="tab-link"
|
||||||
|
data-titlebar-tab-link
|
||||||
href={props.href}
|
href={props.href}
|
||||||
draggable={false}
|
draggable={false}
|
||||||
onDragStart={(event) => {
|
onDragStart={(event) => {
|
||||||
@@ -221,6 +222,7 @@ export function TabNavItem(props: {
|
|||||||
titleEl.textContent = session().title
|
titleEl.textContent = session().title
|
||||||
}}
|
}}
|
||||||
data-slot="tab-title"
|
data-slot="tab-title"
|
||||||
|
data-titlebar-tab-title
|
||||||
class="min-w-0 flex-1 outline-none leading-4"
|
class="min-w-0 flex-1 outline-none leading-4"
|
||||||
classList={{
|
classList={{
|
||||||
"overflow-hidden text-clip whitespace-nowrap": !editing(),
|
"overflow-hidden text-clip whitespace-nowrap": !editing(),
|
||||||
@@ -297,7 +299,7 @@ export function DraftTabItem(props: {
|
|||||||
data-active={props.active}
|
data-active={props.active}
|
||||||
data-dragging={props.dragging}
|
data-dragging={props.dragging}
|
||||||
data-pressed={props.pressed}
|
data-pressed={props.pressed}
|
||||||
class="group relative shrink-0 flex h-7 max-w-60 flex-row items-center gap-1.5 overflow-hidden rounded-[6px] bg-[var(--tab-bg)] pl-1.5 pr-8 whitespace-nowrap [--tab-bg:var(--v2-background-bg-deep)] hover:[--tab-bg:var(--v2-background-bg-layer-02)] has-[>a:focus-visible]:[--tab-bg:var(--v2-background-bg-layer-02)] data-[active='true']:has-[>a:focus-visible]:[--tab-bg:var(--v2-background-bg-layer-02)] data-[active='true']:[--tab-bg:var(--v2-overlay-simple-overlay-pressed)] data-[dragging='true']:[--tab-bg:var(--v2-background-bg-layer-02)] data-[pressed='true']:[--tab-bg:var(--v2-background-bg-layer-02)]"
|
class="group relative flex h-7 w-full min-w-0 max-w-56 flex-row items-center gap-1.5 overflow-hidden rounded-[6px] bg-[var(--tab-bg)] pl-1.5 pr-8 [container-type:inline-size] whitespace-nowrap [--tab-bg:var(--v2-background-bg-deep)] hover:[--tab-bg:var(--v2-background-bg-layer-02)] has-[>a:focus-visible]:[--tab-bg:var(--v2-background-bg-layer-02)] data-[active='true']:has-[>a:focus-visible]:[--tab-bg:var(--v2-background-bg-layer-02)] data-[active='true']:[--tab-bg:var(--v2-overlay-simple-overlay-pressed)] data-[dragging='true']:[--tab-bg:var(--v2-background-bg-layer-02)] data-[pressed='true']:[--tab-bg:var(--v2-background-bg-layer-02)] data-[active='true'][data-pressed='true']:[--tab-bg:var(--v2-overlay-simple-overlay-pressed)]"
|
||||||
classList={{ invisible: props.hidden }}
|
classList={{ invisible: props.hidden }}
|
||||||
onMouseDown={(event) => {
|
onMouseDown={(event) => {
|
||||||
if (event.button !== 1) return
|
if (event.button !== 1) return
|
||||||
@@ -306,6 +308,7 @@ export function DraftTabItem(props: {
|
|||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
data-slot="tab-link"
|
data-slot="tab-link"
|
||||||
|
data-titlebar-tab-link
|
||||||
href={props.href}
|
href={props.href}
|
||||||
draggable={false}
|
draggable={false}
|
||||||
onDragStart={(event) => {
|
onDragStart={(event) => {
|
||||||
@@ -322,7 +325,9 @@ export function DraftTabItem(props: {
|
|||||||
<span class="flex size-4 shrink-0 items-center justify-center">
|
<span class="flex size-4 shrink-0 items-center justify-center">
|
||||||
<IconV2 name="edit" />
|
<IconV2 name="edit" />
|
||||||
</span>
|
</span>
|
||||||
<span class="truncate leading-5">{props.title}</span>
|
<span data-titlebar-tab-title class="truncate leading-5">
|
||||||
|
{props.title}
|
||||||
|
</span>
|
||||||
</a>
|
</a>
|
||||||
<div data-slot="tab-close" class="absolute right-0 inset-y-0 flex w-7 items-center justify-center">
|
<div data-slot="tab-close" class="absolute right-0 inset-y-0 flex w-7 items-center justify-center">
|
||||||
<IconButtonV2
|
<IconButtonV2
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ import {
|
|||||||
function SessionTabSlot(props: {
|
function SessionTabSlot(props: {
|
||||||
tab: SessionTab
|
tab: SessionTab
|
||||||
id: string
|
id: string
|
||||||
first: () => boolean
|
|
||||||
active: () => boolean
|
active: () => boolean
|
||||||
activeServerKey: ServerConnection.Key
|
activeServerKey: ServerConnection.Key
|
||||||
forceTruncate: boolean
|
forceTruncate: boolean
|
||||||
@@ -104,10 +103,9 @@ function SessionTabSlot(props: {
|
|||||||
<div
|
<div
|
||||||
data-titlebar-tab-slot
|
data-titlebar-tab-slot
|
||||||
data-tab-key={props.id}
|
data-tab-key={props.id}
|
||||||
class="flex shrink-0"
|
class="flex min-w-0 max-w-56 flex-1 basis-0"
|
||||||
classList={{
|
classList={{
|
||||||
hidden: !session(),
|
hidden: !session(),
|
||||||
"ml-1.5 border-l border-[var(--v2-background-bg-layer-02)] pl-1.5": !props.first(),
|
|
||||||
"pointer-events-none": props.dragActive,
|
"pointer-events-none": props.dragActive,
|
||||||
}}
|
}}
|
||||||
onPointerDown={props.onPointerDown}
|
onPointerDown={props.onPointerDown}
|
||||||
@@ -461,17 +459,16 @@ export function TitlebarTabStrip(props: {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div data-slot="titlebar-tabs" class="relative min-w-0">
|
<div data-slot="titlebar-tabs" class="relative min-w-0 flex-1">
|
||||||
<div
|
<div
|
||||||
data-slot="titlebar-tabs-scroll"
|
data-slot="titlebar-tabs-scroll"
|
||||||
class="flex min-w-0 flex-row items-center gap-1.5 overflow-x-auto no-scrollbar [app-region:no-drag]"
|
class="flex min-w-0 flex-row items-center gap-1.5 overflow-x-auto no-scrollbar [app-region:no-drag]"
|
||||||
ref={scrollRef}
|
ref={scrollRef}
|
||||||
>
|
>
|
||||||
<div class="flex min-w-0 flex-row items-center" ref={listRef}>
|
<div data-titlebar-tab-list class="flex w-full min-w-0 flex-row items-center" ref={listRef}>
|
||||||
<For each={displayTabs()}>
|
<For each={displayTabs()}>
|
||||||
{(tab, index) => {
|
{(tab, index) => {
|
||||||
const id = tabKey(tab)
|
const id = tabKey(tab)
|
||||||
const first = () => index() === 0
|
|
||||||
let ref!: HTMLDivElement
|
let ref!: HTMLDivElement
|
||||||
useTabShortcut(index, () => props.onNavigate(tab, ref))
|
useTabShortcut(index, () => props.onNavigate(tab, ref))
|
||||||
|
|
||||||
@@ -487,7 +484,6 @@ export function TitlebarTabStrip(props: {
|
|||||||
<SessionTabSlot
|
<SessionTabSlot
|
||||||
tab={tab}
|
tab={tab}
|
||||||
id={id}
|
id={id}
|
||||||
first={first}
|
|
||||||
active={() => props.currentTab() === tab}
|
active={() => props.currentTab() === tab}
|
||||||
activeServerKey={props.activeServerKey}
|
activeServerKey={props.activeServerKey}
|
||||||
forceTruncate={props.forceTruncate}
|
forceTruncate={props.forceTruncate}
|
||||||
@@ -510,9 +506,8 @@ export function TitlebarTabStrip(props: {
|
|||||||
<div
|
<div
|
||||||
data-titlebar-tab-slot
|
data-titlebar-tab-slot
|
||||||
data-tab-key={id}
|
data-tab-key={id}
|
||||||
class="flex shrink-0"
|
class="flex min-w-0 max-w-56 flex-1 basis-0"
|
||||||
classList={{
|
classList={{
|
||||||
"ml-1.5 border-l border-[var(--v2-background-bg-layer-02)] pl-1.5": !first(),
|
|
||||||
"pointer-events-none": drag.active,
|
"pointer-events-none": drag.active,
|
||||||
}}
|
}}
|
||||||
onPointerDown={(event) => {
|
onPointerDown={(event) => {
|
||||||
|
|||||||
@@ -481,7 +481,6 @@ export function Titlebar(props: { update?: TitlebarUpdate }) {
|
|||||||
/>
|
/>
|
||||||
</TooltipV2>
|
</TooltipV2>
|
||||||
</Show>
|
</Show>
|
||||||
<div class="flex-1" />
|
|
||||||
<TitlebarV2Right state={v2RightState()} />
|
<TitlebarV2Right state={v2RightState()} />
|
||||||
<Show when={windows() && !electronWindows()}>
|
<Show when={windows() && !electronWindows()}>
|
||||||
<div data-tauri-decorum-tb class="flex flex-row" />
|
<div data-tauri-decorum-tb class="flex flex-row" />
|
||||||
|
|||||||
@@ -252,7 +252,7 @@ export function SessionComposerRegion(props: {
|
|||||||
</Show>
|
</Show>
|
||||||
<div
|
<div
|
||||||
classList={{
|
classList={{
|
||||||
"relative z-10": true,
|
"relative z-30": true,
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
"margin-top": `${-lift()}px`,
|
"margin-top": `${-lift()}px`,
|
||||||
|
|||||||
Reference in New Issue
Block a user