feat(app): add inline file browser tabs (#35829)

This commit is contained in:
Luke Parker 2026-07-09 09:09:21 +10:00 committed by GitHub
commit 62de2b3695
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 968 additions and 179 deletions

View file

@ -201,6 +201,7 @@ export default function FileTree(props: {
kinds?: ReadonlyMap<string, Kind>
draggable?: boolean
onFileClick?: (file: FileNode) => void
onFileDoubleClick?: (file: FileNode) => void
_filter?: Filter
_marks?: Set<string>
@ -440,6 +441,7 @@ export default function FileTree(props: {
active={props.active}
draggable={props.draggable}
onFileClick={props.onFileClick}
onFileDoubleClick={props.onFileDoubleClick}
_filter={filter()}
_marks={marks()}
_deeps={deeps()}
@ -462,6 +464,7 @@ export default function FileTree(props: {
as="button"
type="button"
onClick={() => props.onFileClick?.(node)}
onDblClick={() => props.onFileDoubleClick?.(node)}
>
<div class="w-4 shrink-0" />
<Switch>

View file

@ -4,6 +4,7 @@ import { ProgressCircleV2 } from "@opencode-ai/ui/v2/progress-circle-v2"
import { Button } from "@opencode-ai/ui/button"
import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2"
import { createMediaQuery } from "@solid-primitives/media"
import { useFile } from "@/context/file"
import { useLayout } from "@/context/layout"
@ -14,6 +15,7 @@ import { useSDK } from "@/context/sdk"
import { getSessionContext, getSessionTokenTotal } from "@/components/session/session-context-metrics"
import { useSessionLayout } from "@/pages/session/session-layout"
import { createSessionTabs } from "@/pages/session/helpers"
import { useSettings } from "@/context/settings"
interface SessionContextUsageProps {
variant?: "button" | "indicator"
@ -47,8 +49,10 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
const layout = useLayout()
const language = useLanguage()
const sdk = useSDK()
const settings = useSettings()
const providers = useProviders(() => sdk().directory)
const { params, tabs, view } = useSessionLayout()
const isDesktop = createMediaQuery("(min-width: 768px)")
const variant = createMemo(() => props.variant ?? "button")
const buttonAppearance = createMemo(() => props.buttonAppearance ?? "default")
@ -56,6 +60,7 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
tabs,
pathFromTab: file.pathFromTab,
normalizeTab: (tab) => (tab.startsWith("file://") ? file.tab(tab) : tab),
fileBrowser: () => settings.general.newLayoutDesigns() && isDesktop() && !!params.id,
})
const messages = createMemo(() => (params.id ? (sync().data.message[params.id] ?? []) : []))
const info = createMemo(() => (params.id ? sync().session.get(params.id) : undefined))

View file

@ -10,7 +10,7 @@ import { useFile } from "@/context/file"
import { useLanguage } from "@/context/language"
import { useCommand } from "@/context/command"
export function FileVisual(props: { path: string; active?: boolean }): JSX.Element {
export function FileVisual(props: { path: string; active?: boolean; temporary?: boolean }): JSX.Element {
return (
<div class="flex items-center gap-x-1.5 min-w-0">
<Show
@ -22,12 +22,19 @@ export function FileVisual(props: { path: string; active?: boolean }): JSX.Eleme
<FileIcon node={{ path: props.path, type: "file" }} mono class="absolute inset-0 size-4 tab-fileicon-mono" />
</span>
</Show>
<span class="text-14-medium truncate">{getFilename(props.path)}</span>
<span class="text-14-medium truncate" classList={{ italic: props.temporary }}>
{getFilename(props.path)}
</span>
</div>
)
}
export function SortableTab(props: { tab: string; onTabClose: (tab: string) => void }): JSX.Element {
export function SortableTab(props: {
tab: string
temporary?: boolean
onTabClose: (tab: string) => void
onTabDoubleClick?: (tab: string) => void
}): JSX.Element {
const file = useFile()
const language = useLanguage()
const command = useCommand()
@ -36,7 +43,7 @@ export function SortableTab(props: { tab: string; onTabClose: (tab: string) => v
const content = createMemo(() => {
const value = path()
if (!value) return
return <FileVisual path={value} />
return <FileVisual path={value} temporary={props.temporary} />
})
return (
<div use:sortable class="h-full flex items-center" classList={{ "opacity-0": sortable.isActiveDraggable }}>
@ -61,6 +68,7 @@ export function SortableTab(props: { tab: string; onTabClose: (tab: string) => v
}
hideCloseButton
onMiddleClick={() => props.onTabClose(props.tab)}
onDblClick={() => props.onTabDoubleClick?.(props.tab)}
>
<Show when={content()}>{(value) => value()}</Show>
</Tabs.Trigger>