summaryrefslogtreecommitdiffhomepage
path: root/packages/desktop/src/components
diff options
context:
space:
mode:
authorAdam <[email protected]>2025-10-16 14:53:44 -0500
committerAdam <[email protected]>2025-10-16 14:53:44 -0500
commit47d9e017657c4d265dea53bd86d727097a7ba282 (patch)
treee278fb3983f13f6fa474228cf5031c3b4680f566 /packages/desktop/src/components
parentfc18fc8a08e703a54553e714344e638673b2d313 (diff)
downloadopencode-47d9e017657c4d265dea53bd86d727097a7ba282.tar.gz
opencode-47d9e017657c4d265dea53bd86d727097a7ba282.zip
wip: css/ui and desktop work
Diffstat (limited to 'packages/desktop/src/components')
-rw-r--r--packages/desktop/src/components/editor-pane.tsx237
-rw-r--r--packages/desktop/src/components/file-tree.tsx3
-rw-r--r--packages/desktop/src/components/prompt-form.tsx4
-rw-r--r--packages/desktop/src/components/resizeable-pane.tsx217
-rw-r--r--packages/desktop/src/components/select-dialog.tsx3
-rw-r--r--packages/desktop/src/components/select.tsx108
-rw-r--r--packages/desktop/src/components/session-list.tsx2
-rw-r--r--packages/desktop/src/components/session-timeline.tsx3
-rw-r--r--packages/desktop/src/components/sidebar-nav.tsx48
9 files changed, 124 insertions, 501 deletions
diff --git a/packages/desktop/src/components/editor-pane.tsx b/packages/desktop/src/components/editor-pane.tsx
index 2f4c87e25..2741a6208 100644
--- a/packages/desktop/src/components/editor-pane.tsx
+++ b/packages/desktop/src/components/editor-pane.tsx
@@ -1,6 +1,7 @@
import { For, Match, Show, Switch, createSignal, splitProps } from "solid-js"
-import { Tabs } from "@/ui/tabs"
-import { FileIcon, Icon, IconButton, Logo, Tooltip } from "@/ui"
+import { Tabs, Tooltip } from "@opencode-ai/ui"
+import { Icon } from "@opencode-ai/ui"
+import { FileIcon, IconButton } from "@/ui"
import {
DragDropProvider,
DragDropSensors,
@@ -64,127 +65,119 @@ export default function EditorPane(props: EditorPaneProps): JSX.Element {
}
return (
- <div class="relative flex h-full flex-col">
- <DragDropProvider
- onDragStart={handleDragStart}
- onDragEnd={handleDragEnd}
- onDragOver={handleDragOver}
- collisionDetector={closestCenter}
- >
- <DragDropSensors />
- <ConstrainDragYAxis />
- <Tabs
- class="relative grow w-full flex flex-col h-full"
- value={local.file.active()?.path}
- onChange={handleTabChange}
- >
- <div class="sticky top-0 shrink-0 flex">
- <Tabs.List class="grow">
- <SortableProvider ids={local.file.opened().map((file) => file.path)}>
- <For each={local.file.opened()}>
- {(file) => (
- <SortableTab file={file} onTabClick={localProps.onFileClick} onTabClose={handleTabClose} />
- )}
- </For>
- </SortableProvider>
- </Tabs.List>
- <div class="shrink-0 h-full flex items-center gap-1 px-2 border-b border-border-subtle/40">
- <Show when={local.file.active() && local.file.active()!.content?.diff}>
- {(() => {
- const activeFile = local.file.active()!
- const view = local.file.view(activeFile.path)
- return (
- <div class="flex items-center gap-1">
- <Show when={view !== "raw"}>
- <div class="mr-1 flex items-center gap-1">
- <Tooltip value="Previous change" placement="bottom">
- <IconButton size="xs" variant="ghost" onClick={() => navigateChange(-1)}>
- <Icon name="arrow-up" size={14} />
- </IconButton>
- </Tooltip>
- <Tooltip value="Next change" placement="bottom">
- <IconButton size="xs" variant="ghost" onClick={() => navigateChange(1)}>
- <Icon name="arrow-down" size={14} />
- </IconButton>
- </Tooltip>
- </div>
- </Show>
- <Tooltip value="Raw" placement="bottom">
- <IconButton
- size="xs"
- variant="ghost"
- classList={{
- "text-text": view === "raw",
- "text-text-muted/70": view !== "raw",
- "bg-background-element": view === "raw",
- }}
- onClick={() => local.file.setView(activeFile.path, "raw")}
- >
- <Icon name="file-text" size={14} />
- </IconButton>
- </Tooltip>
- <Tooltip value="Unified diff" placement="bottom">
- <IconButton
- size="xs"
- variant="ghost"
- classList={{
- "text-text": view === "diff-unified",
- "text-text-muted/70": view !== "diff-unified",
- "bg-background-element": view === "diff-unified",
- }}
- onClick={() => local.file.setView(activeFile.path, "diff-unified")}
- >
- <Icon name="checklist" size={14} />
- </IconButton>
- </Tooltip>
- <Tooltip value="Split diff" placement="bottom">
- <IconButton
- size="xs"
- variant="ghost"
- classList={{
- "text-text": view === "diff-split",
- "text-text-muted/70": view !== "diff-split",
- "bg-background-element": view === "diff-split",
- }}
- onClick={() => local.file.setView(activeFile.path, "diff-split")}
- >
- <Icon name="columns" size={14} />
- </IconButton>
- </Tooltip>
- </div>
- )
- })()}
- </Show>
- </div>
+ <DragDropProvider
+ onDragStart={handleDragStart}
+ onDragEnd={handleDragEnd}
+ onDragOver={handleDragOver}
+ collisionDetector={closestCenter}
+ >
+ <DragDropSensors />
+ <ConstrainDragYAxis />
+ <Tabs value={local.file.active()?.path} onChange={handleTabChange}>
+ <div class="sticky top-0 shrink-0 flex">
+ <Tabs.List>
+ <SortableProvider ids={local.file.opened().map((file) => file.path)}>
+ <For each={local.file.opened()}>
+ {(file) => <SortableTab file={file} onTabClick={localProps.onFileClick} onTabClose={handleTabClose} />}
+ </For>
+ </SortableProvider>
+ </Tabs.List>
+ <div class="hidden shrink-0 h-full _flex items-center gap-1 px-2 border-b border-border-subtle/40">
+ <Show when={local.file.active() && local.file.active()!.content?.diff}>
+ {(() => {
+ const activeFile = local.file.active()!
+ const view = local.file.view(activeFile.path)
+ return (
+ <div class="flex items-center gap-1">
+ <Show when={view !== "raw"}>
+ <div class="mr-1 flex items-center gap-1">
+ <Tooltip value="Previous change" placement="bottom">
+ <IconButton size="xs" variant="ghost" onClick={() => navigateChange(-1)}>
+ <Icon name="arrow-up" size={14} />
+ </IconButton>
+ </Tooltip>
+ <Tooltip value="Next change" placement="bottom">
+ <IconButton size="xs" variant="ghost" onClick={() => navigateChange(1)}>
+ <Icon name="arrow-down" size={14} />
+ </IconButton>
+ </Tooltip>
+ </div>
+ </Show>
+ <Tooltip value="Raw" placement="bottom">
+ <IconButton
+ size="xs"
+ variant="ghost"
+ classList={{
+ "text-text": view === "raw",
+ "text-text-muted/70": view !== "raw",
+ "bg-background-element": view === "raw",
+ }}
+ onClick={() => local.file.setView(activeFile.path, "raw")}
+ >
+ <Icon name="file-text" size={14} />
+ </IconButton>
+ </Tooltip>
+ <Tooltip value="Unified diff" placement="bottom">
+ <IconButton
+ size="xs"
+ variant="ghost"
+ classList={{
+ "text-text": view === "diff-unified",
+ "text-text-muted/70": view !== "diff-unified",
+ "bg-background-element": view === "diff-unified",
+ }}
+ onClick={() => local.file.setView(activeFile.path, "diff-unified")}
+ >
+ <Icon name="checklist" size={14} />
+ </IconButton>
+ </Tooltip>
+ <Tooltip value="Split diff" placement="bottom">
+ <IconButton
+ size="xs"
+ variant="ghost"
+ classList={{
+ "text-text": view === "diff-split",
+ "text-text-muted/70": view !== "diff-split",
+ "bg-background-element": view === "diff-split",
+ }}
+ onClick={() => local.file.setView(activeFile.path, "diff-split")}
+ >
+ <Icon name="columns" size={14} />
+ </IconButton>
+ </Tooltip>
+ </div>
+ )
+ })()}
+ </Show>
</div>
- <For each={local.file.opened()}>
- {(file) => (
- <Tabs.Content value={file.path} class="grow h-full pt-1 select-text">
- {(() => {
- const view = local.file.view(file.path)
- const showRaw = view === "raw" || !file.content?.diff
- const code = showRaw ? (file.content?.content ?? "") : (file.content?.diff ?? "")
- return <Code path={file.path} code={code} class="[&_code]:pb-60" />
- })()}
- </Tabs.Content>
- )}
- </For>
- </Tabs>
- <DragOverlay>
- {(() => {
- const id = activeItem()
- if (!id) return null
- const draggedFile = local.file.node(id)
- if (!draggedFile) return null
- return (
- <div class="relative px-3 h-8 flex items-center text-sm font-medium text-text whitespace-nowrap shrink-0 bg-background-panel border-x border-border-subtle/40 border-b border-b-transparent">
- <TabVisual file={draggedFile} />
- </div>
- )
- })()}
- </DragOverlay>
- </DragDropProvider>
- </div>
+ </div>
+ <For each={local.file.opened()}>
+ {(file) => (
+ <Tabs.Content value={file.path} class="select-text">
+ {(() => {
+ const view = local.file.view(file.path)
+ const showRaw = view === "raw" || !file.content?.diff
+ const code = showRaw ? (file.content?.content ?? "") : (file.content?.diff ?? "")
+ return <Code path={file.path} code={code} class="[&_code]:pb-60" />
+ })()}
+ </Tabs.Content>
+ )}
+ </For>
+ </Tabs>
+ <DragOverlay>
+ {(() => {
+ const id = activeItem()
+ if (!id) return null
+ const draggedFile = local.file.node(id)
+ if (!draggedFile) return null
+ return (
+ <div class="relative px-3 h-8 flex items-center text-sm font-medium text-text whitespace-nowrap shrink-0 bg-background-panel border-x border-border-subtle/40 border-b border-b-transparent">
+ <TabVisual file={draggedFile} />
+ </div>
+ )
+ })()}
+ </DragOverlay>
+ </DragDropProvider>
)
}
diff --git a/packages/desktop/src/components/file-tree.tsx b/packages/desktop/src/components/file-tree.tsx
index d31255ced..348e25ad7 100644
--- a/packages/desktop/src/components/file-tree.tsx
+++ b/packages/desktop/src/components/file-tree.tsx
@@ -1,6 +1,7 @@
import { useLocal } from "@/context"
import type { LocalFile } from "@/context/local"
-import { Collapsible, FileIcon, Tooltip } from "@/ui"
+import { Tooltip } from "@opencode-ai/ui"
+import { Collapsible, FileIcon } from "@/ui"
import { For, Match, Switch, Show, type ComponentProps, type ParentProps } from "solid-js"
import { Dynamic } from "solid-js/web"
diff --git a/packages/desktop/src/components/prompt-form.tsx b/packages/desktop/src/components/prompt-form.tsx
index 5e6441596..06fbfbb03 100644
--- a/packages/desktop/src/components/prompt-form.tsx
+++ b/packages/desktop/src/components/prompt-form.tsx
@@ -1,8 +1,8 @@
import { For, Show, createMemo, onCleanup, type JSX } from "solid-js"
import { createStore } from "solid-js/store"
import { Popover } from "@kobalte/core/popover"
-import { Button, FileIcon, Icon, IconButton, Tooltip } from "@/ui"
-import { Select } from "@/components/select"
+import { Tooltip, Button, Icon, Select } from "@opencode-ai/ui"
+import { FileIcon, IconButton } from "@/ui"
import { useLocal } from "@/context"
import type { FileContext, LocalFile } from "@/context/local"
import { getDirectory, getFilename } from "@/utils"
diff --git a/packages/desktop/src/components/resizeable-pane.tsx b/packages/desktop/src/components/resizeable-pane.tsx
deleted file mode 100644
index 49ccc4e70..000000000
--- a/packages/desktop/src/components/resizeable-pane.tsx
+++ /dev/null
@@ -1,217 +0,0 @@
-import { batch, createContext, createMemo, createSignal, onCleanup, Show, useContext } from "solid-js"
-import type { ComponentProps, JSX } from "solid-js"
-import { createStore } from "solid-js/store"
-import { useLocal } from "@/context"
-
-type PaneDefault = number | { size: number; visible?: boolean }
-
-type LayoutContextValue = {
- id: string
- register: (pane: string, options: { min?: number | string; max?: number | string }) => void
- size: (pane: string) => number
- visible: (pane: string) => boolean
- percent: (pane: string) => number
- next: (pane: string) => string | undefined
- startDrag: (left: string, right: string | undefined, event: MouseEvent) => void
- dragging: () => string | undefined
-}
-
-const LayoutContext = createContext<LayoutContextValue | undefined>(undefined)
-
-export interface ResizeableLayoutProps {
- id: string
- defaults: Record<string, PaneDefault>
- class?: ComponentProps<"div">["class"]
- classList?: ComponentProps<"div">["classList"]
- children: JSX.Element
-}
-
-export interface ResizeablePaneProps {
- id: string
- minSize?: number | string
- maxSize?: number | string
- class?: ComponentProps<"div">["class"]
- classList?: ComponentProps<"div">["classList"]
- children: JSX.Element
-}
-
-export function ResizeableLayout(props: ResizeableLayoutProps) {
- const local = useLocal()
- const [meta, setMeta] = createStore<Record<string, { min: number; max: number; minPx?: number; maxPx?: number }>>({})
- const [dragging, setDragging] = createSignal<string>()
- let container: HTMLDivElement | undefined
-
- local.layout.ensure(props.id, props.defaults)
-
- const order = createMemo(() => local.layout.order(props.id))
- const visibleOrder = createMemo(() => order().filter((pane) => local.layout.visible(props.id, pane)))
- const totalVisible = createMemo(() => {
- const panes = visibleOrder()
- if (!panes.length) return 0
- return panes.reduce((total, pane) => total + local.layout.size(props.id, pane), 0)
- })
-
- const percent = (pane: string) => {
- const panes = visibleOrder()
- if (!panes.length) return 0
- const total = totalVisible()
- if (!total) return 100 / panes.length
- return (local.layout.size(props.id, pane) / total) * 100
- }
-
- const nextPane = (pane: string) => {
- const panes = visibleOrder()
- const index = panes.indexOf(pane)
- if (index === -1) return undefined
- return panes[index + 1]
- }
-
- const minMax = (pane: string) => meta[pane] ?? { min: 5, max: 95 }
-
- const pxToPercent = (px: number, total: number) => (px / total) * 100
-
- const boundsForPair = (left: string, right: string, total: number) => {
- const leftMeta = minMax(left)
- const rightMeta = minMax(right)
- const containerWidth = container?.getBoundingClientRect().width ?? 0
-
- let minLeft = leftMeta.min
- let maxLeft = leftMeta.max
- let minRight = rightMeta.min
- let maxRight = rightMeta.max
-
- if (containerWidth && leftMeta.minPx !== undefined) minLeft = pxToPercent(leftMeta.minPx, containerWidth)
- if (containerWidth && leftMeta.maxPx !== undefined) maxLeft = pxToPercent(leftMeta.maxPx, containerWidth)
- if (containerWidth && rightMeta.minPx !== undefined) minRight = pxToPercent(rightMeta.minPx, containerWidth)
- if (containerWidth && rightMeta.maxPx !== undefined) maxRight = pxToPercent(rightMeta.maxPx, containerWidth)
-
- const finalMinLeft = Math.max(minLeft, total - maxRight)
- const finalMaxLeft = Math.min(maxLeft, total - minRight)
- return {
- min: Math.min(finalMinLeft, finalMaxLeft),
- max: Math.max(finalMinLeft, finalMaxLeft),
- }
- }
-
- const setPair = (left: string, right: string, leftSize: number, rightSize: number) => {
- batch(() => {
- local.layout.setSize(props.id, left, leftSize)
- local.layout.setSize(props.id, right, rightSize)
- })
- }
-
- const startDrag = (left: string, right: string | undefined, event: MouseEvent) => {
- if (!right) return
- if (!container) return
- const rect = container.getBoundingClientRect()
- if (!rect.width) return
- event.preventDefault()
- const startX = event.clientX
- const startLeft = local.layout.size(props.id, left)
- const startRight = local.layout.size(props.id, right)
- const total = startLeft + startRight
- const bounds = boundsForPair(left, right, total)
- const move = (moveEvent: MouseEvent) => {
- const delta = ((moveEvent.clientX - startX) / rect.width) * 100
- const nextLeft = Math.max(bounds.min, Math.min(bounds.max, startLeft + delta))
- const nextRight = total - nextLeft
- setPair(left, right, nextLeft, nextRight)
- }
- const stop = () => {
- setDragging()
- document.removeEventListener("mousemove", move)
- document.removeEventListener("mouseup", stop)
- }
- setDragging(left)
- document.addEventListener("mousemove", move)
- document.addEventListener("mouseup", stop)
- onCleanup(() => stop())
- }
-
- const register = (pane: string, options: { min?: number | string; max?: number | string }) => {
- let min = 5
- let max = 95
- let minPx: number | undefined
- let maxPx: number | undefined
-
- if (typeof options.min === "string" && options.min.endsWith("px")) {
- minPx = parseInt(options.min)
- min = 0
- } else if (typeof options.min === "number") {
- min = options.min
- }
-
- if (typeof options.max === "string" && options.max.endsWith("px")) {
- maxPx = parseInt(options.max)
- max = 100
- } else if (typeof options.max === "number") {
- max = options.max
- }
-
- setMeta(pane, () => ({ min, max, minPx, maxPx }))
- const fallback = props.defaults[pane]
- local.layout.ensurePane(props.id, pane, fallback ?? { size: min, visible: true })
- }
-
- const contextValue: LayoutContextValue = {
- id: props.id,
- register,
- size: (pane) => local.layout.size(props.id, pane),
- visible: (pane) => local.layout.visible(props.id, pane),
- percent,
- next: nextPane,
- startDrag,
- dragging,
- }
-
- return (
- <LayoutContext.Provider value={contextValue}>
- <div
- ref={(node) => {
- container = node ?? undefined
- }}
- class={props.class ? `relative flex h-full w-full ${props.class}` : "relative flex h-full w-full"}
- classList={props.classList}
- >
- {props.children}
- </div>
- </LayoutContext.Provider>
- )
-}
-
-export function ResizeablePane(props: ResizeablePaneProps) {
- const context = useContext(LayoutContext)!
- context.register(props.id, { min: props.minSize, max: props.maxSize })
- const visible = () => context.visible(props.id)
- const width = () => context.percent(props.id)
- const next = () => context.next(props.id)
- const dragging = () => context.dragging() === props.id
-
- return (
- <Show when={visible()}>
- <div
- class={props.class ? `relative flex h-full flex-col ${props.class}` : "relative flex h-full flex-col"}
- classList={props.classList}
- style={{
- width: `${width()}%`,
- flex: `0 0 ${width()}%`,
- }}
- >
- {props.children}
- <Show when={next()}>
- <div
- class="absolute top-0 -right-1 h-full w-1.5 cursor-col-resize z-50 group"
- onMouseDown={(event) => context.startDrag(props.id, next(), event)}
- >
- <div
- classList={{
- "w-0.5 h-full bg-transparent transition-colors group-hover:bg-border-active": true,
- "bg-border-active!": dragging(),
- }}
- />
- </div>
- </Show>
- </div>
- </Show>
- )
-}
diff --git a/packages/desktop/src/components/select-dialog.tsx b/packages/desktop/src/components/select-dialog.tsx
index 315fe14e5..bf9aa0dbd 100644
--- a/packages/desktop/src/components/select-dialog.tsx
+++ b/packages/desktop/src/components/select-dialog.tsx
@@ -1,6 +1,7 @@
import { createEffect, Show, For, createMemo, type JSX, createResource } from "solid-js"
import { Dialog } from "@kobalte/core/dialog"
-import { Icon, IconButton } from "@/ui"
+import { Icon } from "@opencode-ai/ui"
+import { IconButton } from "@/ui"
import { createStore } from "solid-js/store"
import { entries, flatMap, groupBy, map, pipe } from "remeda"
import { createList } from "solid-list"
diff --git a/packages/desktop/src/components/select.tsx b/packages/desktop/src/components/select.tsx
deleted file mode 100644
index 6ab3f4012..000000000
--- a/packages/desktop/src/components/select.tsx
+++ /dev/null
@@ -1,108 +0,0 @@
-import { Select as KobalteSelect } from "@kobalte/core/select"
-import { createMemo } from "solid-js"
-import type { ComponentProps } from "solid-js"
-import { Icon } from "@/ui/icon"
-import { pipe, groupBy, entries, map } from "remeda"
-import { Button, type ButtonProps } from "@/ui"
-
-export interface SelectProps<T> {
- placeholder?: string
- options: T[]
- current?: T
- value?: (x: T) => string
- label?: (x: T) => string
- groupBy?: (x: T) => string
- onSelect?: (value: T | undefined) => void
- class?: ComponentProps<"div">["class"]
- classList?: ComponentProps<"div">["classList"]
-}
-
-export function Select<T>(props: SelectProps<T> & ButtonProps) {
- const grouped = createMemo(() => {
- const result = pipe(
- props.options,
- groupBy((x) => (props.groupBy ? props.groupBy(x) : "")),
- // mapValues((x) => x.sort((a, b) => a.title.localeCompare(b.title))),
- entries(),
- map(([k, v]) => ({ category: k, options: v })),
- )
- return result
- })
-
- return (
- <KobalteSelect<T, { category: string; options: T[] }>
- value={props.current}
- options={grouped()}
- optionValue={(x) => (props.value ? props.value(x) : (x as string))}
- optionTextValue={(x) => (props.label ? props.label(x) : (x as string))}
- optionGroupChildren="options"
- placeholder={props.placeholder}
- sectionComponent={(props) => (
- <KobalteSelect.Section class="text-xs uppercase text-text-muted/60 font-light mt-3 first:mt-0 ml-2">
- {props.section.rawValue.category}
- </KobalteSelect.Section>
- )}
- itemComponent={(itemProps) => (
- <KobalteSelect.Item
- classList={{
- "relative flex cursor-pointer select-none items-center": true,
- "rounded-sm px-2 py-0.5 text-xs outline-none text-text": true,
- "transition-colors data-[disabled]:pointer-events-none": true,
- "data-[highlighted]:bg-background-element data-[disabled]:opacity-50": true,
- [props.class ?? ""]: !!props.class,
- }}
- {...itemProps}
- >
- <KobalteSelect.ItemLabel>
- {props.label ? props.label(itemProps.item.rawValue) : (itemProps.item.rawValue as string)}
- </KobalteSelect.ItemLabel>
- <KobalteSelect.ItemIndicator class="ml-auto">
- <Icon name="checkmark" size={16} />
- </KobalteSelect.ItemIndicator>
- </KobalteSelect.Item>
- )}
- onChange={(v) => {
- props.onSelect?.(v ?? undefined)
- }}
- >
- <KobalteSelect.Trigger
- as={Button}
- size={props.size || "sm"}
- variant={props.variant || "secondary"}
- classList={{
- ...(props.classList ?? {}),
- [props.class ?? ""]: !!props.class,
- }}
- >
- <KobalteSelect.Value<T> class="truncate">
- {(state) => {
- const selected = state.selectedOption() ?? props.current
- if (!selected) return props.placeholder || ""
- if (props.label) return props.label(selected)
- return selected as string
- }}
- </KobalteSelect.Value>
- <KobalteSelect.Icon
- classList={{
- "group size-fit shrink-0 text-text-muted transition-transform duration-100": true,
- }}
- >
- <Icon name="chevron-up" size={16} class="-my-2 group-data-[expanded]:rotate-180" />
- <Icon name="chevron-down" size={16} class="-my-2 group-data-[expanded]:rotate-180" />
- </KobalteSelect.Icon>
- </KobalteSelect.Trigger>
- <KobalteSelect.Portal>
- <KobalteSelect.Content
- classList={{
- "min-w-32 overflow-hidden rounded-md border border-border-subtle/40": true,
- "bg-background-panel p-1 shadow-md z-50": true,
- "data-[closed]:animate-out data-[closed]:fade-out-0 data-[closed]:zoom-out-95": true,
- "data-[expanded]:animate-in data-[expanded]:fade-in-0 data-[expanded]:zoom-in-95": true,
- }}
- >
- <KobalteSelect.Listbox class="overflow-y-auto max-h-48 whitespace-nowrap overflow-x-hidden" />
- </KobalteSelect.Content>
- </KobalteSelect.Portal>
- </KobalteSelect>
- )
-}
diff --git a/packages/desktop/src/components/session-list.tsx b/packages/desktop/src/components/session-list.tsx
index a339fc6b4..f26d39af8 100644
--- a/packages/desktop/src/components/session-list.tsx
+++ b/packages/desktop/src/components/session-list.tsx
@@ -1,5 +1,5 @@
import { useSync, useLocal } from "@/context"
-import { Tooltip } from "@/ui"
+import { Tooltip } from "@opencode-ai/ui"
import { DateTime } from "luxon"
import { VList } from "virtua/solid"
diff --git a/packages/desktop/src/components/session-timeline.tsx b/packages/desktop/src/components/session-timeline.tsx
index 07d93031e..d4adf2e4a 100644
--- a/packages/desktop/src/components/session-timeline.tsx
+++ b/packages/desktop/src/components/session-timeline.tsx
@@ -1,5 +1,6 @@
import { useLocal, useSync } from "@/context"
-import { Collapsible, Icon } from "@/ui"
+import { Icon } from "@opencode-ai/ui"
+import { Collapsible } from "@/ui"
import type { Part, ToolPart } from "@opencode-ai/sdk"
import { DateTime } from "luxon"
import {
diff --git a/packages/desktop/src/components/sidebar-nav.tsx b/packages/desktop/src/components/sidebar-nav.tsx
deleted file mode 100644
index 24750bdba..000000000
--- a/packages/desktop/src/components/sidebar-nav.tsx
+++ /dev/null
@@ -1,48 +0,0 @@
-import { For } from "solid-js"
-import { Icon, Link, Logo, Tooltip } from "@/ui"
-import { useLocation } from "@solidjs/router"
-
-const navigation = [
- { name: "Sessions", href: "/sessions", icon: "dashboard" as const },
- { name: "Commands", href: "/commands", icon: "slash" as const },
- { name: "Agents", href: "/agents", icon: "bolt" as const },
- { name: "Providers", href: "/providers", icon: "cloud" as const },
- { name: "Tools (MCP)", href: "/tools", icon: "hammer" as const },
- { name: "LSP", href: "/lsp", icon: "code" as const },
- { name: "Settings", href: "/settings", icon: "settings" as const },
-]
-
-export default function SidebarNav() {
- const location = useLocation()
- return (
- <div class="hidden md:fixed md:inset-y-0 md:left-0 md:z-50 md:block md:w-16 md:overflow-y-auto md:bg-background-panel md:pb-4">
- <div class="flex h-16 shrink-0 items-center justify-center">
- <Logo variant="mark" size={28} />
- </div>
- <nav class="mt-5">
- <ul role="list" class="flex flex-col items-center space-y-1">
- <For each={navigation}>
- {(item) => (
- <li>
- <Tooltip placement="right" value={item.name}>
- <Link
- href={item.href}
- classList={{
- "bg-background-element text-text": location.pathname.startsWith(item.href),
- "text-text-muted hover:bg-background-element hover:text-text": location.pathname !== item.href,
- "flex gap-x-3 rounded-md p-3 text-sm font-semibold": true,
- "focus-visible:outline-1 focus-visible:-outline-offset-1 focus-visible:outline-border-active": true,
- }}
- >
- <Icon name={item.icon} size={20} />
- <span class="sr-only">{item.name}</span>
- </Link>
- </Tooltip>
- </li>
- )}
- </For>
- </ul>
- </nav>
- </div>
- )
-}