feat: loading session header in a portal, show in toolbar

This commit is contained in:
Aaron Iker 2026-01-08 03:07:32 +01:00
commit f552eea391
6 changed files with 348 additions and 321 deletions

View file

@ -1,4 +1,3 @@
export { SessionHeader } from "./session-header"
export { SessionContextTab } from "./session-context-tab"
export { SortableTab, FileVisual } from "./session-sortable-tab"
export { SortableTerminalTab } from "./session-sortable-terminal-tab"

View file

@ -0,0 +1,66 @@
import { TooltipKeybind } from "@opencode-ai/ui/tooltip"
import { Button } from "@opencode-ai/ui/button"
import { Icon } from "@opencode-ai/ui/icon"
import type { Component, ComponentProps } from "solid-js"
import { useLayout } from "@/context/layout"
import { useCommand } from "@/context/command"
const IS_MAC = typeof navigator === "object" && /(Mac|iPod|iPhone|iPad)/.test(navigator.platform)
// ID for the portal mount target
export const TOOLBAR_PORTAL_ID = "toolbar-content-portal"
export const Toolbar: Component<ComponentProps<"div">> = ({ class: className, ...props }) => {
const command = useCommand()
const layout = useLayout()
return (
<div
classList={{
"pl-[80px]": IS_MAC,
"pl-2": !IS_MAC,
"py-2 mx-px bg-background-base border-b border-border-weak-base flex items-center justify-between w-full border-box relative": true,
...(className ? { [className]: true } : {}),
}}
data-tauri-drag-region
{...props}
>
<TooltipKeybind
class="shrink-0 relative z-10"
placement="bottom"
title="Toggle sidebar"
keybind={command.keybind("sidebar.toggle")}
>
<Button
variant="ghost"
size="normal"
class="group/sidebar-toggle shrink-0 text-left justify-center align-middle rounded-lg px-1.5"
onClick={layout.sidebar.toggle}
>
<div class="relative -ml-px flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
<Icon
name={layout.sidebar.opened() ? "layout-left" : "layout-right"}
size="small"
class="group-hover/sidebar-toggle:hidden"
/>
<Icon
name={layout.sidebar.opened() ? "layout-left-partial" : "layout-right-partial"}
size="small"
class="hidden group-hover/sidebar-toggle:inline-block"
/>
<Icon
name={layout.sidebar.opened() ? "layout-left-full" : "layout-right-full"}
size="small"
class="hidden group-active/sidebar-toggle:inline-block"
/>
</div>
</Button>
</TooltipKeybind>
{/* Portal mount target - content rendered here from DirectoryLayout */}
<div id={TOOLBAR_PORTAL_ID} class="contents" />
</div>
)
}
// Re-export for use in DirectoryLayout
export { ToolbarSession } from "./session"

View file

@ -1,4 +1,4 @@
import { createMemo, createResource, Show } from "solid-js"
import { ComponentProps, createMemo, createResource, Show, Component } from "solid-js"
import { A, useNavigate, useParams } from "@solidjs/router"
import { useLayout } from "@/context/layout"
import { useCommand } from "@/context/command"
@ -22,7 +22,7 @@ import { SessionMcpIndicator } from "@/components/session-mcp-indicator"
import type { Session } from "@opencode-ai/sdk/v2/client"
import { same } from "@/utils/same"
export function SessionHeader() {
export const ToolbarSession: Component<ComponentProps<"header">> = ({ class: className, ...props }) => {
const globalSDK = useGlobalSDK()
const layout = useLayout()
const params = useParams()
@ -56,7 +56,7 @@ export function SessionHeader() {
}
return (
<header class="h-12 shrink-0 bg-background-base border-b border-border-weak-base flex">
<header class={`flex absolute inset-0 ${className}`} {...props}>
<button
type="button"
class="xl:hidden w-12 shrink-0 flex items-center justify-center border-r border-border-weak-base hover:bg-surface-raised-base-hover active:bg-surface-raised-base-active transition-colors"
@ -64,8 +64,8 @@ export function SessionHeader() {
>
<Icon name="menu" size="small" />
</button>
<div class="px-4 flex items-center justify-between gap-4 w-full">
<div class="flex items-center gap-3 min-w-0">
<div class="flex items-center justify-between gap-4 absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
<div class="flex items-center gap-2 min-w-0">
<div class="flex items-center gap-2 min-w-0">
<div class="hidden xl:flex items-center gap-2">
<Select
@ -137,124 +137,126 @@ export function SessionHeader() {
</div>
<Show when={currentSession() && !parentSession()}>
<TooltipKeybind class="hidden xl:block" title="New session" keybind={command.keybind("session.new")}>
<IconButton as={A} href={`/${params.dir}/session`} icon="edit-small-2" variant="ghost" />
<IconButton as={A} href={`/${params.dir}/session`} icon="plus-small" variant="ghost" />
</TooltipKeybind>
</Show>
</div>
<div class="flex items-center gap-3">
<div class="hidden md:flex items-center gap-1">
<Button
size="small"
variant="ghost"
onClick={() => {
dialog.show(() => <DialogSelectServer />)
</div>
<div class="flex items-center gap-3 absolute right-6 top-1/2 -translate-y-1/2">
<div class="hidden md:flex items-center gap-1">
<Button
size="small"
variant="ghost"
class="flex gap-2 items-center justify-center"
onClick={() => {
dialog.show(() => <DialogSelectServer />)
}}
>
<div
classList={{
"size-1.5 rounded-full": true,
"bg-icon-success-base": server.healthy() === true,
"bg-icon-critical-base": server.healthy() === false,
"bg-border-weak-base": server.healthy() === undefined,
}}
>
<div
classList={{
"size-1.5 rounded-full": true,
"bg-icon-success-base": server.healthy() === true,
"bg-icon-critical-base": server.healthy() === false,
"bg-border-weak-base": server.healthy() === undefined,
}}
/>
<Icon name="server" size="small" class="text-icon-weak" />
<span class="text-12-regular text-text-weak truncate max-w-[200px]">{server.name}</span>
</Button>
<SessionLspIndicator />
<SessionMcpIndicator />
</div>
<div class="flex items-center gap-1">
<Show when={currentSession()?.summary?.files}>
<TooltipKeybind
class="hidden md:block shrink-0"
title="Toggle review"
keybind={command.keybind("review.toggle")}
>
<Button variant="ghost" class="group/review-toggle size-6 p-0" onClick={layout.review.toggle}>
<div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
<Icon
name={layout.review.opened() ? "layout-right" : "layout-left"}
size="small"
class="group-hover/review-toggle:hidden"
/>
<Icon
name={layout.review.opened() ? "layout-right-partial" : "layout-left-partial"}
size="small"
class="hidden group-hover/review-toggle:inline-block"
/>
<Icon
name={layout.review.opened() ? "layout-right-full" : "layout-left-full"}
size="small"
class="hidden group-active/review-toggle:inline-block"
/>
</div>
</Button>
</TooltipKeybind>
</Show>
/>
<Icon name="server" size="small" class="text-icon-weak" />
<span class="text-12-regular text-text-weak truncate max-w-[200px]">{server.name}</span>
</Button>
<SessionLspIndicator />
<SessionMcpIndicator />
</div>
<div class="flex items-center gap-1">
<Show when={currentSession()?.summary?.files}>
<TooltipKeybind
class="hidden md:block shrink-0"
title="Toggle terminal"
keybind={command.keybind("terminal.toggle")}
title="Toggle review"
keybind={command.keybind("review.toggle")}
>
<Button variant="ghost" class="group/terminal-toggle size-6 p-0" onClick={layout.terminal.toggle}>
<Button variant="ghost" class="group/review-toggle size-6 p-0" onClick={layout.review.toggle}>
<div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
<Icon
name={layout.review.opened() ? "layout-right" : "layout-left"}
size="small"
name={layout.terminal.opened() ? "layout-bottom-full" : "layout-bottom"}
class="group-hover/terminal-toggle:hidden"
class="group-hover/review-toggle:hidden"
/>
<Icon
name={layout.review.opened() ? "layout-right-partial" : "layout-left-partial"}
size="small"
name="layout-bottom-partial"
class="hidden group-hover/terminal-toggle:inline-block"
class="hidden group-hover/review-toggle:inline-block"
/>
<Icon
name={layout.review.opened() ? "layout-right-full" : "layout-left-full"}
size="small"
name={layout.terminal.opened() ? "layout-bottom" : "layout-bottom-full"}
class="hidden group-active/terminal-toggle:inline-block"
class="hidden group-active/review-toggle:inline-block"
/>
</div>
</Button>
</TooltipKeybind>
</div>
<Show when={shareEnabled() && currentSession()}>
<Popover
title="Share session"
trigger={
<Tooltip class="shrink-0" value="Share session">
<IconButton icon="share" variant="ghost" class="" />
</Tooltip>
}
>
{iife(() => {
const [url] = createResource(
() => currentSession(),
async (session) => {
if (!session) return
let shareURL = session.share?.url
if (!shareURL) {
shareURL = await globalSDK.client.session
.share({ sessionID: session.id, directory: projectDirectory() })
.then((r) => r.data?.share?.url)
.catch((e) => {
console.error("Failed to share session", e)
return undefined
})
}
return shareURL
},
{ initialValue: "" },
)
return (
<Show when={url.latest}>
{(shareUrl) => <TextField value={shareUrl()} readOnly copyable class="w-72" />}
</Show>
)
})}
</Popover>
</Show>
<TooltipKeybind
class="hidden md:block shrink-0"
title="Toggle terminal"
keybind={command.keybind("terminal.toggle")}
>
<Button variant="ghost" class="group/terminal-toggle size-6 p-0" onClick={layout.terminal.toggle}>
<div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
<Icon
size="small"
name={layout.terminal.opened() ? "layout-bottom-full" : "layout-bottom"}
class="group-hover/terminal-toggle:hidden"
/>
<Icon
size="small"
name="layout-bottom-partial"
class="hidden group-hover/terminal-toggle:inline-block"
/>
<Icon
size="small"
name={layout.terminal.opened() ? "layout-bottom" : "layout-bottom-full"}
class="hidden group-active/terminal-toggle:inline-block"
/>
</div>
</Button>
</TooltipKeybind>
</div>
<Show when={shareEnabled() && currentSession()}>
<Popover
title="Share session"
trigger={
<Tooltip class="shrink-0" value="Share session">
<IconButton icon="share" variant="ghost" class="" />
</Tooltip>
}
>
{iife(() => {
const [url] = createResource(
() => currentSession(),
async (session) => {
if (!session) return
let shareURL = session.share?.url
if (!shareURL) {
shareURL = await globalSDK.client.session
.share({ sessionID: session.id, directory: projectDirectory() })
.then((r) => r.data?.share?.url)
.catch((e) => {
console.error("Failed to share session", e)
return undefined
})
}
return shareURL
},
{ initialValue: "" },
)
return (
<Show when={url.latest}>
{(shareUrl) => <TextField value={shareUrl()} readOnly copyable class="w-72" />}
</Show>
)
})}
</Popover>
</Show>
</div>
</header>
)