summaryrefslogtreecommitdiffhomepage
path: root/packages/app/src/components
diff options
context:
space:
mode:
authorAdam <[email protected]>2026-01-12 10:11:29 -0600
committerAdam <[email protected]>2026-01-15 07:29:13 -0600
commit679270d9e0731c2b3e2c059d83907cb4086d90e2 (patch)
tree4e8d70a281b92f86a4b916ed45a5410ecbba0289 /packages/app/src/components
parent9f66a45970d1edf12ae9b3e7a22d77711b5e51c3 (diff)
downloadopencode-679270d9e0731c2b3e2c059d83907cb4086d90e2.tar.gz
opencode-679270d9e0731c2b3e2c059d83907cb4086d90e2.zip
feat(app): new layout
Diffstat (limited to 'packages/app/src/components')
-rw-r--r--packages/app/src/components/session/session-header.tsx101
-rw-r--r--packages/app/src/components/titlebar.tsx115
2 files changed, 142 insertions, 74 deletions
diff --git a/packages/app/src/components/session/session-header.tsx b/packages/app/src/components/session/session-header.tsx
index b2e7fafeb..5ed721740 100644
--- a/packages/app/src/components/session/session-header.tsx
+++ b/packages/app/src/components/session/session-header.tsx
@@ -34,6 +34,17 @@ export function SessionHeader() {
const sync = useSync()
const projectDirectory = createMemo(() => base64Decode(params.dir ?? ""))
+ const project = createMemo(() => {
+ const directory = projectDirectory()
+ if (!directory) return
+ return layout.projects.list().find((p) => p.worktree === directory || p.sandboxes?.includes(directory))
+ })
+ const name = createMemo(() => {
+ const current = project()
+ if (current) return current.name || getFilename(current.worktree)
+ return getFilename(projectDirectory())
+ })
+ const hotkey = createMemo(() => command.keybind("file.open"))
const sessions = createMemo(() => (sync.data.session ?? []).filter((s) => !s.parentID))
const currentSession = createMemo(() => sync.data.session.find((s) => s.id === params.id))
@@ -58,87 +69,29 @@ export function SessionHeader() {
navigate(`/${params.dir}/session/${session.id}`)
}
- const leftMount = createMemo(() => document.getElementById("opencode-titlebar-left"))
+ const centerMount = createMemo(() => document.getElementById("opencode-titlebar-center"))
const rightMount = createMemo(() => document.getElementById("opencode-titlebar-right"))
return (
<>
- <Show when={leftMount()}>
+ <Show when={centerMount()}>
{(mount) => (
<Portal mount={mount()}>
- <div class="flex items-center gap-3 min-w-0">
- <div class="flex items-center gap-2 min-w-0">
- <div class="hidden xl:flex items-center gap-2">
- <Select
- options={worktrees()}
- current={sync.project?.worktree ?? projectDirectory()}
- label={(x) => getFilename(x)}
- onSelect={(x) => (x ? navigateToProject(x) : undefined)}
- class="text-14-regular text-text-base"
- variant="ghost"
- >
- {/* @ts-ignore */}
- {(i) => (
- <div class="flex items-center gap-2">
- <Icon name="folder" size="small" />
- <div class="text-text-strong">{getFilename(i)}</div>
- </div>
- )}
- </Select>
- <div class="text-text-weaker">/</div>
- </div>
- <Show
- when={parentSession()}
- fallback={
- <>
- <Select
- options={sessions()}
- current={currentSession()}
- placeholder="New session"
- label={(x) => x.title}
- value={(x) => x.id}
- onSelect={navigateToSession}
- class="text-14-regular text-text-base max-w-[calc(100vw-180px)] md:max-w-md"
- variant="ghost"
- />
- </>
- }
- >
- <div class="flex items-center gap-2 min-w-0">
- <Select
- options={sessions()}
- current={parentSession()}
- placeholder="Back to parent session"
- label={(x) => x.title}
- value={(x) => x.id}
- onSelect={(session) => {
- const currentParent = parentSession()
- if (session && currentParent && session.id !== currentParent.id) {
- navigateToSession(session)
- }
- }}
- class="text-14-regular text-text-base max-w-[calc(100vw-180px)] md:max-w-md"
- variant="ghost"
- />
- <div class="text-text-weaker">/</div>
- <Tooltip value="Back to parent session">
- <button
- type="button"
- class="flex items-center justify-center gap-1 p-1 rounded hover:bg-surface-raised-base-hover active:bg-surface-raised-base-active transition-colors flex-shrink-0"
- onClick={() => navigateToSession(parentSession())}
- >
- <Icon name="arrow-left" size="small" class="text-icon-base" />
- </button>
- </Tooltip>
- </div>
- </Show>
- </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" />
- </TooltipKeybind>
+ <button
+ type="button"
+ class="hidden md:flex w-[320px] h-7 px-1.5 items-center gap-2 rounded-md border border-border-weak-base bg-surface-raised-base transition-colors cursor-default hover:bg-surface-raised-base-hover focus:bg-surface-raised-base-hover active:bg-surface-raised-base-active"
+ onClick={() => command.trigger("file.open")}
+ >
+ <Icon name="magnifying-glass" size="small" class="text-text-weak" />
+ <span class="flex-1 min-w-0 text-14-regular text-text-weak truncate">Search {name()}</span>
+ <Show when={hotkey()}>
+ {(keybind) => (
+ <span class="shrink-0 flex items-center justify-center h-5 px-2 rounded-md border border-border-weak-base bg-surface-base text-12-medium text-text-weak">
+ {keybind()}
+ </span>
+ )}
</Show>
- </div>
+ </button>
</Portal>
)}
</Show>
diff --git a/packages/app/src/components/titlebar.tsx b/packages/app/src/components/titlebar.tsx
new file mode 100644
index 000000000..5cf9f74bc
--- /dev/null
+++ b/packages/app/src/components/titlebar.tsx
@@ -0,0 +1,115 @@
+import { createEffect, createMemo, Show } from "solid-js"
+import { IconButton } from "@opencode-ai/ui/icon-button"
+import { TooltipKeybind } from "@opencode-ai/ui/tooltip"
+import { useTheme } from "@opencode-ai/ui/theme"
+
+import { useLayout } from "@/context/layout"
+import { usePlatform } from "@/context/platform"
+import { useCommand } from "@/context/command"
+
+export function Titlebar() {
+ const layout = useLayout()
+ const platform = usePlatform()
+ const command = useCommand()
+ const theme = useTheme()
+
+ const mac = createMemo(() => platform.platform === "desktop" && platform.os === "macos")
+ const reserve = createMemo(
+ () => platform.platform === "desktop" && (platform.os === "windows" || platform.os === "linux"),
+ )
+
+ const getWin = () => {
+ if (platform.platform !== "desktop") return
+
+ const tauri = (
+ window as unknown as {
+ __TAURI__?: { window?: { getCurrentWindow?: () => { startDragging?: () => Promise<void> } } }
+ }
+ ).__TAURI__
+ if (!tauri?.window?.getCurrentWindow) return
+
+ return tauri.window.getCurrentWindow()
+ }
+
+ createEffect(() => {
+ if (platform.platform !== "desktop") return
+
+ const scheme = theme.colorScheme()
+ const value = scheme === "system" ? null : scheme
+
+ const tauri = (window as unknown as { __TAURI__?: { webviewWindow?: { getCurrentWebviewWindow?: () => unknown } } })
+ .__TAURI__
+ const get = tauri?.webviewWindow?.getCurrentWebviewWindow
+ if (!get) return
+
+ const win = get() as { setTheme?: (theme?: "light" | "dark" | null) => Promise<void> }
+ if (!win.setTheme) return
+
+ void win.setTheme(value).catch(() => undefined)
+ })
+
+ const interactive = (target: EventTarget | null) => {
+ if (!(target instanceof Element)) return false
+
+ const selector =
+ "button, a, input, textarea, select, option, [role='button'], [role='menuitem'], [contenteditable='true'], [contenteditable='']"
+
+ return !!target.closest(selector)
+ }
+
+ const drag = (e: MouseEvent) => {
+ if (platform.platform !== "desktop") return
+ if (e.buttons !== 1) return
+ if (interactive(e.target)) return
+
+ const win = getWin()
+ if (!win?.startDragging) return
+
+ e.preventDefault()
+ void win.startDragging().catch(() => undefined)
+ }
+
+ return (
+ <header class="h-10 shrink-0 bg-background-base flex items-center relative">
+ <div
+ classList={{
+ "flex items-center w-full min-w-0 pr-2": true,
+ "pl-2": !mac(),
+ }}
+ onMouseDown={drag}
+ >
+ <Show when={mac()}>
+ <div class="w-[72px] h-full shrink-0" data-tauri-drag-region />
+ </Show>
+ <IconButton
+ icon="menu"
+ variant="ghost"
+ class="xl:hidden size-8 rounded-md"
+ onClick={layout.mobileSidebar.toggle}
+ />
+ <TooltipKeybind
+ class="hidden xl:flex shrink-0"
+ placement="bottom"
+ title="Toggle sidebar"
+ keybind={command.keybind("sidebar.toggle")}
+ >
+ <IconButton
+ icon={layout.sidebar.opened() ? "layout-left" : "layout-right"}
+ variant="ghost"
+ class="size-8 rounded-md"
+ onClick={layout.sidebar.toggle}
+ />
+ </TooltipKeybind>
+ <div id="opencode-titlebar-left" class="flex items-center gap-3 min-w-0 px-2" />
+ <div class="flex-1 h-full" data-tauri-drag-region />
+ <div id="opencode-titlebar-right" class="flex items-center gap-3 shrink-0" />
+ <Show when={reserve()}>
+ <div class="w-[120px] h-full shrink-0" data-tauri-drag-region />
+ </Show>
+ </div>
+ <div class="absolute inset-0 flex items-center justify-center pointer-events-none">
+ <div id="opencode-titlebar-center" class="pointer-events-auto" />
+ </div>
+ </header>
+ )
+}