mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-13 19:46:16 +03:00
feat(app): add a sidebar toggle for vertical tabs (#55646)
This commit is contained in:
committed by
GitHub
Unverified
parent
b6598c9351
commit
7a636bb737
@@ -4,6 +4,7 @@ import {
|
||||
NO_PROVIDER,
|
||||
REMOTE_SERVER,
|
||||
SERVER,
|
||||
draftHref,
|
||||
expectPath,
|
||||
holdRoute,
|
||||
project,
|
||||
@@ -362,6 +363,34 @@ test("vertical tabs resize, scroll, show shortcut hints, and navigate", async ({
|
||||
await expectPath(page, sessionHref(b.id))
|
||||
})
|
||||
|
||||
for (const [screen, href] of [
|
||||
["home", "/"],
|
||||
["new session", draftHref("draft_collapsed_sidebar")],
|
||||
["settings", "/settings"],
|
||||
["session", sessionHref(a.id)],
|
||||
] as const) {
|
||||
test(`collapsed vertical tabs sidebar reopens from the ${screen} screen`, async ({ page }) => {
|
||||
await mockWorkspace(page, {
|
||||
name: "Tabs",
|
||||
sessions: [a],
|
||||
seed: {
|
||||
settings: { appearance: { tabLayout: "vertical", sidebarOpen: false } },
|
||||
tabs: [a.id, { draft: "draft_collapsed_sidebar", directory: "C:/OpenCode/Tabs" }],
|
||||
},
|
||||
})
|
||||
await page.goto(href)
|
||||
|
||||
const sidebar = page.locator('[data-slot="vertical-tabs-sidebar"]')
|
||||
await expect(sidebar).toHaveAttribute("data-state", "closed")
|
||||
await page.locator("main").getByRole("button", { name: "Toggle sidebar", exact: true }).click()
|
||||
await expect(sidebar).toHaveAttribute("data-state", "open")
|
||||
await expect(sidebar.getByRole("button", { name: "Toggle sidebar", exact: true })).toHaveAttribute(
|
||||
"aria-expanded",
|
||||
"true",
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
test("vertical tab gaps hover and select the nearest row", async ({ page }) => {
|
||||
await mockWorkspace(page, {
|
||||
name: "Tabs",
|
||||
|
||||
@@ -27,6 +27,7 @@ import { ServerRowMenuView, serverMenuLabels } from "@/servers/registry/row-menu
|
||||
import { ServerHealthIndicator } from "@/servers/registry/row"
|
||||
import { type ServerHealth } from "@/runtime/server/health"
|
||||
import { fileManagerApp } from "@/home/projects/file-manager"
|
||||
import { CollapsedSidebarToggleSpace } from "@/shell/sidebar-toggle"
|
||||
import "./view.css"
|
||||
|
||||
const HOME_PROJECT_NAV_LABEL = "min-w-0 flex-1 overflow-hidden text-ellipsis whitespace-nowrap"
|
||||
@@ -173,7 +174,7 @@ function HomeProjectsPanel(props: HomeProjectsViewProps) {
|
||||
props.dropdown
|
||||
? "flex max-h-[min(60dvh,calc(var(--kb-popper-content-available-height)-12px))] min-h-0 min-w-0 flex-col overflow-hidden"
|
||||
: `
|
||||
mt-6 flex min-h-0 min-w-0 flex-col gap-4 overflow-hidden
|
||||
mt-2.5 flex min-h-0 min-w-0 flex-col gap-4 overflow-hidden
|
||||
lg:sticky lg:top-14 lg:mt-14 lg:h-[calc(100cqh-56px)] lg:self-start lg:pt-[52px]
|
||||
`
|
||||
}
|
||||
@@ -185,7 +186,13 @@ function HomeProjectsPanel(props: HomeProjectsViewProps) {
|
||||
>
|
||||
<Show when={!props.dropdown}>
|
||||
<div class="flex h-7 min-w-0 shrink-0 items-center justify-between pl-1.5 pr-3">
|
||||
<div class="text-v2-text-text-muted [font-weight:530]">{props.language.t("home.projects")}</div>
|
||||
{/* Below lg, Projects heads the page on the collapsed sidebar toggle's line. Starting the space at the row's
|
||||
edge (the shell's 8px inset plus the grid's 12px padding) keeps the row's 6px padding after the toggle,
|
||||
which lines the label up with the icons that follow it in the session and settings headers. */}
|
||||
<div class="flex min-w-0 items-center">
|
||||
<CollapsedSidebarToggleSpace start={20} class="lg:hidden" />
|
||||
<div class="text-v2-text-text-muted [font-weight:530]">{props.language.t("home.projects")}</div>
|
||||
</div>
|
||||
<Show when={props.servers.length === 1 && !(props.projects.length === 0 && props.recentlyClosed.length > 0)}>
|
||||
<Tooltip placement="bottom" value={props.language.t("home.project.add")}>
|
||||
<IconButton
|
||||
|
||||
@@ -54,6 +54,7 @@ export const dict = {
|
||||
"command.input.focus": "Focus input",
|
||||
"command.fileTree.toggle": "Toggle file tree",
|
||||
"command.review.toggle": "Toggle review",
|
||||
"command.sidebar.toggle": "Toggle sidebar",
|
||||
"command.message.previous": "Previous message",
|
||||
"command.message.previous.description": "Go to the previous user message",
|
||||
"command.message.next": "Next message",
|
||||
|
||||
@@ -27,6 +27,7 @@ import { sessionHref } from "@/shell/routes/session"
|
||||
import { showToast } from "@/shell/notifications/toast"
|
||||
import { sessionTitle } from "./title"
|
||||
import { SessionWorkingIndicator } from "./header/session-working-indicator"
|
||||
import { CollapsedSidebarToggleSpace } from "@/shell/sidebar-toggle"
|
||||
import "./session-identity-header.css"
|
||||
|
||||
export function SessionTitleHeader(props: ParentProps) {
|
||||
@@ -34,7 +35,10 @@ export function SessionTitleHeader(props: ParentProps) {
|
||||
<>
|
||||
{/* The bottom inset reserves room for the fade without covering rows stuck below the bar. */}
|
||||
<div data-session-title class="pointer-events-none sticky top-0 z-30 w-full pb-4">
|
||||
<div class="pointer-events-auto bg-v2-background-bg-base pe-3 ps-2.5">{props.children}</div>
|
||||
<div class="pointer-events-auto flex items-center bg-v2-background-bg-base pe-3 ps-2.5">
|
||||
<CollapsedSidebarToggleSpace />
|
||||
<div class="min-w-0 flex-1">{props.children}</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Stuck timeline headers stack above this fade, so it only fades content scrolling under the bar. */}
|
||||
<div
|
||||
|
||||
@@ -133,6 +133,7 @@ const appearanceSchema = Persistence.struct({
|
||||
sans: Schema.String,
|
||||
terminal: Schema.String,
|
||||
tabLayout: Schema.Literals(["horizontal", "vertical"]),
|
||||
sidebarOpen: Persistence.optional(Schema.Boolean),
|
||||
})
|
||||
|
||||
const permissionsSchema = Persistence.struct({
|
||||
@@ -409,6 +410,10 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont
|
||||
setTabLayout(value: TabLayout) {
|
||||
setStore("appearance", "tabLayout", value)
|
||||
},
|
||||
sidebarOpen: withFallback(() => store.appearance?.sidebarOpen, true),
|
||||
setSidebarOpen(value: boolean) {
|
||||
setStore("appearance", "sidebarOpen", value)
|
||||
},
|
||||
},
|
||||
keybinds: {
|
||||
get: (action: string) => store.keybinds?.[action],
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Tabs } from "@opencode/ui/tabs"
|
||||
import { createEffect, ErrorBoundary, For, on, Show, type ComponentProps, type JSX } from "solid-js"
|
||||
import { useLanguage } from "@/runtime/i18n/language"
|
||||
import { ErrorPage } from "@/shell/errors/error"
|
||||
import { CollapsedSidebarToggleSpace } from "@/shell/sidebar-toggle"
|
||||
import { useSettingsSurface } from "./surface"
|
||||
import { SettingsSearch } from "./search"
|
||||
import "./search.css"
|
||||
@@ -54,10 +55,15 @@ export function SettingsNavigation(props: {
|
||||
return (
|
||||
<Tabs orientation="vertical" variant="settings" value={props.value} onChange={change} class="settings">
|
||||
<div class="settings-mobile-nav">
|
||||
<button type="button" class="settings-back" onClick={back}>
|
||||
<Icon name="arrow-left" size="small" class="settings-back-icon" />
|
||||
<span>{backLabel()}</span>
|
||||
</button>
|
||||
<div class="flex min-w-0 items-center">
|
||||
{/* Starts after the shell's 8px inset and this row's 16px padding. Its 32px height makes the row 48px tall,
|
||||
like the session header, so the back button centers on the collapsed sidebar toggle's line. */}
|
||||
<CollapsedSidebarToggleSpace start={24} class="h-8" />
|
||||
<button type="button" class="settings-back" onClick={back}>
|
||||
<Icon name="arrow-left" size="small" class="settings-back-icon" />
|
||||
<span>{backLabel()}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="settings-mobile-actions">
|
||||
{props.mobileAction}
|
||||
<Menu placement="bottom-end" gutter={8}>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Show, Suspense, type ParentProps } from "solid-js"
|
||||
import { createEffect, on, Show, Suspense, type ParentProps } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { createMediaQuery } from "@solid-primitives/media"
|
||||
import { ResizeHandle } from "@opencode/ui/resize-handle"
|
||||
@@ -11,19 +11,54 @@ import { useSettingsSurface } from "@/settings/surface"
|
||||
import { useSettings } from "@/settings/model"
|
||||
import { ExtensionServerCover } from "@/runtime/extension/server-shell"
|
||||
import { ExtensionSlot } from "@/runtime/extension/render"
|
||||
import { useCommand } from "@/shell/commands/command"
|
||||
import { useLanguage } from "@/runtime/i18n/language"
|
||||
import { CollapsedSidebarToggle } from "@/shell/sidebar-toggle"
|
||||
|
||||
export default function Layout(props: ParentProps) {
|
||||
const platform = usePlatform()
|
||||
const settings = useSettingsSurface()
|
||||
const preferences = useSettings()
|
||||
const command = useCommand()
|
||||
const language = useLanguage()
|
||||
const mobile = createMediaQuery("(max-width: 767px)")
|
||||
|
||||
const [state, setState] = createStore<{ tabsWidth: number; tabsMount: HTMLElement | undefined }>({
|
||||
const [state, setState] = createStore<{
|
||||
tabsWidth: number
|
||||
tabsMount: HTMLElement | undefined
|
||||
tabsAnimating: boolean
|
||||
}>({
|
||||
tabsWidth: 260,
|
||||
tabsMount: undefined,
|
||||
tabsAnimating: false,
|
||||
})
|
||||
|
||||
const verticalTabs = () => preferences.appearance.tabLayout() === "vertical" && !mobile()
|
||||
|
||||
// Animate only open/close; resize drags must track the pointer.
|
||||
createEffect(
|
||||
on(
|
||||
preferences.appearance.sidebarOpen,
|
||||
() => {
|
||||
if (verticalTabs()) setState("tabsAnimating", true)
|
||||
},
|
||||
{ defer: true },
|
||||
),
|
||||
)
|
||||
|
||||
command.register("sidebar", () =>
|
||||
verticalTabs()
|
||||
? [
|
||||
{
|
||||
id: "sidebar.toggle",
|
||||
title: language.t("command.sidebar.toggle"),
|
||||
category: language.t("command.category.view"),
|
||||
keybind: "mod+shift+b",
|
||||
onSelect: () => preferences.appearance.setSidebarOpen(!preferences.appearance.sidebarOpen()),
|
||||
},
|
||||
]
|
||||
: [],
|
||||
)
|
||||
const bottomTitlebar = () => mobile() && preferences.general.mobileTitlebarPosition() === "bottom"
|
||||
|
||||
return (
|
||||
@@ -49,22 +84,43 @@ export default function Layout(props: ParentProps) {
|
||||
<div class="flex flex-1 min-h-0 min-w-0 flex-row">
|
||||
<Show when={verticalTabs()}>
|
||||
<aside
|
||||
ref={(element) => setState("tabsMount", element)}
|
||||
id="vertical-tabs-sidebar"
|
||||
data-slot="vertical-tabs-sidebar"
|
||||
class="relative flex h-full min-h-0 shrink-0 flex-col bg-v2-background-bg-deep pe-0.5 ps-2.5 pb-[var(--shell-bottom-inset,8px)] pt-[var(--shell-top-inset,8px)]"
|
||||
style={{
|
||||
width: `${state.tabsWidth}px`,
|
||||
"padding-bottom": "max(10px, var(--safe-area-inset-bottom, env(safe-area-inset-bottom, 0px)))",
|
||||
data-state={preferences.appearance.sidebarOpen() ? "open" : "closed"}
|
||||
classList={{
|
||||
"relative h-full min-h-0 shrink-0": true,
|
||||
"transition-[width] duration-[240ms] ease-[cubic-bezier(0.4,0,0.2,1)] motion-reduce:transition-none":
|
||||
state.tabsAnimating,
|
||||
}}
|
||||
style={{ width: preferences.appearance.sidebarOpen() ? `${state.tabsWidth}px` : "0px" }}
|
||||
inert={!preferences.appearance.sidebarOpen()}
|
||||
onTransitionEnd={(event) => {
|
||||
if (event.target === event.currentTarget) setState("tabsAnimating", false)
|
||||
}}
|
||||
onTransitionCancel={(event) => {
|
||||
if (event.target === event.currentTarget) setState("tabsAnimating", false)
|
||||
}}
|
||||
>
|
||||
<ResizeHandle
|
||||
class="-end-2"
|
||||
direction="horizontal"
|
||||
size={state.tabsWidth}
|
||||
min={140}
|
||||
max={520}
|
||||
onResize={(width) => setState("tabsWidth", width)}
|
||||
/>
|
||||
<div class="absolute inset-0 overflow-hidden">
|
||||
<div
|
||||
ref={(element) => setState("tabsMount", element)}
|
||||
class="flex h-full min-h-0 flex-col bg-v2-background-bg-deep pe-0.5 ps-2.5 pb-[var(--shell-bottom-inset,8px)] pt-[var(--shell-top-inset,8px)]"
|
||||
style={{
|
||||
width: `${state.tabsWidth}px`,
|
||||
"padding-bottom": "max(10px, var(--safe-area-inset-bottom, env(safe-area-inset-bottom, 0px)))",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Show when={preferences.appearance.sidebarOpen()}>
|
||||
<ResizeHandle
|
||||
class="-end-2"
|
||||
direction="horizontal"
|
||||
size={state.tabsWidth}
|
||||
min={140}
|
||||
max={520}
|
||||
onResize={(width) => setState("tabsWidth", width)}
|
||||
/>
|
||||
</Show>
|
||||
</aside>
|
||||
</Show>
|
||||
{/* Size containment collapses percentage-height descendants in WebKit. */}
|
||||
@@ -84,6 +140,7 @@ export default function Layout(props: ParentProps) {
|
||||
<ExtensionServerCover>
|
||||
<Suspense>{props.children}</Suspense>
|
||||
</ExtensionServerCover>
|
||||
<CollapsedSidebarToggle />
|
||||
</main>
|
||||
</div>
|
||||
<ExtensionSlot at="window.bottom" input={{}} />
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
import { Show } from "solid-js"
|
||||
import { createMediaQuery } from "@solid-primitives/media"
|
||||
import { Icon } from "@opencode/ui/icon"
|
||||
import { IconButton } from "@opencode/ui/icon-button"
|
||||
import { Keybind } from "@opencode/ui/keybind"
|
||||
import { Tooltip } from "@opencode/ui/tooltip"
|
||||
import { useCommand } from "@/shell/commands/command"
|
||||
import { useLanguage } from "@/runtime/i18n/language"
|
||||
import { usePlatform } from "@/runtime/platform/platform"
|
||||
import { useSettings } from "@/settings/model"
|
||||
|
||||
// Native controls: 14px left inset, two 20px button pitches, and a 14px button.
|
||||
export const macTrafficLightsBaseWidth = 68
|
||||
|
||||
// The toggle's 28px plus a 4px gap before the header contents.
|
||||
const slotWidth = 32
|
||||
|
||||
// The shell's 8px inline inset plus the session header's 10px start padding.
|
||||
const contentStart = 18
|
||||
|
||||
export function SidebarToggle() {
|
||||
const command = useCommand()
|
||||
const language = useLanguage()
|
||||
const settings = useSettings()
|
||||
const keybind = () => command.keybindParts("sidebar.toggle")
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
class="shrink-0 [app-region:no-drag]"
|
||||
placement="bottom"
|
||||
value={
|
||||
<>
|
||||
{language.t("command.sidebar.toggle")}
|
||||
<Show when={keybind().length > 0}>
|
||||
<Keybind keys={keybind()} variant="neutral" />
|
||||
</Show>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<IconButton
|
||||
type="button"
|
||||
variant="ghost-muted"
|
||||
size="large"
|
||||
class="shrink-0"
|
||||
state={settings.appearance.sidebarOpen() ? "pressed" : undefined}
|
||||
onClick={() => settings.appearance.setSidebarOpen(!settings.appearance.sidebarOpen())}
|
||||
aria-label={language.t("command.sidebar.toggle")}
|
||||
aria-expanded={settings.appearance.sidebarOpen()}
|
||||
aria-controls="vertical-tabs-sidebar"
|
||||
icon={<Icon name="layout-left" />}
|
||||
/>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
// Where the collapsed sidebar's toggle sits at the start of the content area. Windows keeps its titlebar in vertical
|
||||
// tabs mode, so the toggle lives there instead.
|
||||
function useCollapsedSlot() {
|
||||
const platform = usePlatform()
|
||||
const settings = useSettings()
|
||||
const desktop = createMediaQuery("(min-width: 768px)")
|
||||
const windows = () => platform.platform === "desktop" && platform.os === "windows"
|
||||
const mac = () => platform.platform === "desktop" && platform.os === "macos" && !platform.windowFullscreen?.()
|
||||
|
||||
return {
|
||||
visible: () => desktop() && !windows() && settings.appearance.tabLayout() === "vertical",
|
||||
open: settings.appearance.sidebarOpen,
|
||||
// Keep the toggle clear of the traffic lights, which end 68px from the window edge.
|
||||
width: () =>
|
||||
mac() ? macTrafficLightsBaseWidth / (platform.webviewZoom?.() ?? 1) - contentStart + slotWidth : slotWidth,
|
||||
}
|
||||
}
|
||||
|
||||
const slotClass =
|
||||
"flex shrink-0 justify-end overflow-hidden transition-[width] duration-[240ms] ease-[cubic-bezier(0.4,0,0.2,1)] motion-reduce:transition-none"
|
||||
|
||||
// Floats over the top of every screen, centered on the session header's 48px row. Slides in from the content's start
|
||||
// edge as the sidebar closes.
|
||||
export function CollapsedSidebarToggle() {
|
||||
const slot = useCollapsedSlot()
|
||||
|
||||
return (
|
||||
<Show when={slot.visible()}>
|
||||
<div
|
||||
class={`absolute top-[calc(var(--shell-top-inset,8px)+10px)] start-[calc(var(--shell-inline-inset,8px)+10px)] z-40 ${slotClass}`}
|
||||
style={{ width: slot.open() ? "0px" : `${slot.width()}px` }}
|
||||
inert={slot.open()}
|
||||
>
|
||||
<div class="flex shrink-0 pe-1">
|
||||
<SidebarToggle />
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
|
||||
// Reserves the floating toggle's room in a header, pushing its contents along as the toggle slides in. `start` is where
|
||||
// the space begins, in pixels from the content area's start edge, so every header's contents line up after the toggle.
|
||||
export function CollapsedSidebarToggleSpace(props: { start?: number; class?: string }) {
|
||||
const slot = useCollapsedSlot()
|
||||
|
||||
return (
|
||||
<Show when={slot.visible()}>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class={`${slotClass} ${props.class ?? ""}`}
|
||||
style={{ width: slot.open() ? "0px" : `${slot.width() + contentStart - (props.start ?? contentStart)}px` }}
|
||||
/>
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import { useSettings } from "@/settings/model"
|
||||
import { WindowsAppMenu } from "./windows-menu"
|
||||
import { applyPath, backPath, forwardPath, type HistoryLocation } from "./history"
|
||||
import { TitlebarTabStrip } from "@/shell/titlebar/tab-strip"
|
||||
import { macTrafficLightsBaseWidth, SidebarToggle } from "@/shell/sidebar-toggle"
|
||||
import { makeEventListener } from "@solid-primitives/event-listener"
|
||||
import { createMediaQuery } from "@solid-primitives/media"
|
||||
import { readSessionTabsRemovedDetail, SESSION_TABS_REMOVED_EVENT } from "@/shell/titlebar/session-events"
|
||||
@@ -45,15 +46,15 @@ const minTitlebarZoom = 0.25
|
||||
|
||||
const windowsControlsBaseWidth = 138 // 3 native Windows caption buttons at 46px each.
|
||||
|
||||
// Native controls: 14px left inset, two 20px button pitches, and a 14px button.
|
||||
const macTrafficLightsBaseWidth = 68
|
||||
|
||||
const macTrafficLightsTopClearance = 28
|
||||
|
||||
// iOS blurs page content just below the status bar in Home Screen web apps, so with a top safe area the phone
|
||||
// titlebar row stays 16px clear of it, as ChatGPT's phone header does. Without one, 8px matches the content gap below.
|
||||
const mobileTopClearance = "max(8px, min(16px, env(safe-area-inset-top, 0px) * 1000))"
|
||||
|
||||
const channelIndicatorVisible =
|
||||
!!import.meta.env.VITE_OPENCODE_CHANNEL && import.meta.env.VITE_OPENCODE_CHANNEL !== "prod"
|
||||
|
||||
export function Titlebar(props: { verticalTabs?: { mount?: HTMLElement } }) {
|
||||
const platform = usePlatform()
|
||||
const command = useCommand()
|
||||
@@ -109,6 +110,7 @@ export function Titlebar(props: { verticalTabs?: { mount?: HTMLElement } }) {
|
||||
|
||||
const titlebarItems = useTitlebarItems()
|
||||
const hideVerticalTitlebar = createMemo(() => !!props.verticalTabs && !windows())
|
||||
const homeInSidebarHeader = () => !windows() && !macTrafficLights() && !channelIndicatorVisible
|
||||
|
||||
const back = () => {
|
||||
const next = backPath(history)
|
||||
@@ -540,6 +542,9 @@ export function Titlebar(props: { verticalTabs?: { mount?: HTMLElement } }) {
|
||||
<Show when={windows() || linux()}>
|
||||
<WindowsAppMenu command={command} platform={platform} />
|
||||
</Show>
|
||||
<Show when={windows() && props.verticalTabs}>
|
||||
<SidebarToggle />
|
||||
</Show>
|
||||
<Show when={!mobile() && !props.verticalTabs}>{homeButton()}</Show>
|
||||
|
||||
<Show
|
||||
@@ -715,17 +720,45 @@ export function Titlebar(props: { verticalTabs?: { mount?: HTMLElement } }) {
|
||||
mount={mount}
|
||||
ref={(element) => (element.className = "flex size-full min-h-0 flex-col")}
|
||||
>
|
||||
<Show when={macVerticalTabs()}>
|
||||
<div
|
||||
class="mb-4 min-h-7 w-full shrink-0"
|
||||
style={{ height: `${macTrafficLightsTopClearance / zoom()}px` }}
|
||||
data-tauri-drag-region
|
||||
/>
|
||||
</Show>
|
||||
<Show when={!windows()}>
|
||||
<ChannelIndicator sidebar />
|
||||
{/* Shares the session header's 48px center line. Without traffic lights or a channel
|
||||
icon, Home fills the start so the row doesn't leave an empty band. */}
|
||||
<div
|
||||
class="flex w-full shrink-0 gap-2"
|
||||
classList={{
|
||||
"items-start pt-2.5": homeInSidebarHeader(),
|
||||
"mb-2 h-12 items-center": !homeInSidebarHeader(),
|
||||
}}
|
||||
style={{
|
||||
"min-height": macVerticalTabs()
|
||||
? `${macTrafficLightsTopClearance / zoom()}px`
|
||||
: undefined,
|
||||
}}
|
||||
data-tauri-drag-region
|
||||
>
|
||||
<Show when={macTrafficLights()}>
|
||||
{/* The sidebar's 10px start padding already covers part of the traffic lights. */}
|
||||
<div
|
||||
class="shrink-0"
|
||||
style={{ width: `calc(${macTrafficLightsBaseWidth / zoom()}px - 10px)` }}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</Show>
|
||||
<Show
|
||||
when={homeInSidebarHeader()}
|
||||
fallback={
|
||||
<>
|
||||
<ChannelIndicator sidebar />
|
||||
<div class="flex-1" />
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div class="min-w-0 flex-1">{homeButton(true)}</div>
|
||||
</Show>
|
||||
<SidebarToggle />
|
||||
</div>
|
||||
</Show>
|
||||
{homeButton(true)}
|
||||
<Show when={!homeInSidebarHeader()}>{homeButton(true)}</Show>
|
||||
<RecentlyClosedTabsMenu
|
||||
vertical
|
||||
onNewTab={openNewTab}
|
||||
@@ -798,7 +831,7 @@ function ChannelIndicator(props: { horizontal?: boolean; sidebar?: boolean }) {
|
||||
<Tooltip
|
||||
placement={props.sidebar ? "right" : "bottom"}
|
||||
value={label()}
|
||||
class={`shrink-0 [app-region:no-drag] ${props.sidebar ? "mb-4 ms-0.5 self-start" : ""} ${props.horizontal ? "me-1.5" : ""} ${props.horizontal && platform.platform === "web" ? "ps-2.5" : ""}`}
|
||||
class={`shrink-0 [app-region:no-drag] ${props.sidebar ? "ms-0.5" : ""} ${props.horizontal ? "me-1.5" : ""} ${props.horizontal && platform.platform === "web" ? "ps-2.5" : ""}`}
|
||||
>
|
||||
<Dynamic
|
||||
component={debug() ? "button" : "div"}
|
||||
|
||||
Reference in New Issue
Block a user