From 47d9e017657c4d265dea53bd86d727097a7ba282 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Thu, 16 Oct 2025 14:53:44 -0500 Subject: wip: css/ui and desktop work --- packages/desktop/src/components/editor-pane.tsx | 237 ++++++++++----------- packages/desktop/src/components/file-tree.tsx | 3 +- packages/desktop/src/components/prompt-form.tsx | 4 +- .../desktop/src/components/resizeable-pane.tsx | 217 ------------------- packages/desktop/src/components/select-dialog.tsx | 3 +- packages/desktop/src/components/select.tsx | 108 ---------- packages/desktop/src/components/session-list.tsx | 2 +- .../desktop/src/components/session-timeline.tsx | 3 +- packages/desktop/src/components/sidebar-nav.tsx | 48 ----- 9 files changed, 124 insertions(+), 501 deletions(-) delete mode 100644 packages/desktop/src/components/resizeable-pane.tsx delete mode 100644 packages/desktop/src/components/select.tsx delete mode 100644 packages/desktop/src/components/sidebar-nav.tsx (limited to 'packages/desktop/src/components') 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 ( -
- - - - -
- - file.path)}> - - {(file) => ( - - )} - - - -
- - {(() => { - const activeFile = local.file.active()! - const view = local.file.view(activeFile.path) - return ( -
- -
- - navigateChange(-1)}> - - - - - navigateChange(1)}> - - - -
-
- - local.file.setView(activeFile.path, "raw")} - > - - - - - local.file.setView(activeFile.path, "diff-unified")} - > - - - - - local.file.setView(activeFile.path, "diff-split")} - > - - - -
- ) - })()} -
-
+ + + + +
+ + file.path)}> + + {(file) => } + + + + - - {(file) => ( - - {(() => { - const view = local.file.view(file.path) - const showRaw = view === "raw" || !file.content?.diff - const code = showRaw ? (file.content?.content ?? "") : (file.content?.diff ?? "") - return - })()} - - )} - - - - {(() => { - const id = activeItem() - if (!id) return null - const draggedFile = local.file.node(id) - if (!draggedFile) return null - return ( -
- -
- ) - })()} -
- -
+
+ + {(file) => ( + + {(() => { + const view = local.file.view(file.path) + const showRaw = view === "raw" || !file.content?.diff + const code = showRaw ? (file.content?.content ?? "") : (file.content?.diff ?? "") + return + })()} + + )} + +
+ + {(() => { + const id = activeItem() + if (!id) return null + const draggedFile = local.file.node(id) + if (!draggedFile) return null + return ( +
+ +
+ ) + })()} +
+
) } 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(undefined) - -export interface ResizeableLayoutProps { - id: string - defaults: Record - 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>({}) - const [dragging, setDragging] = createSignal() - 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 ( - -
{ - 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} -
-
- ) -} - -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 ( - -
- {props.children} - -
context.startDrag(props.id, next(), event)} - > -
-
- -
-
- ) -} 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 { - 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(props: SelectProps & 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 ( - - 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) => ( - - {props.section.rawValue.category} - - )} - itemComponent={(itemProps) => ( - - - {props.label ? props.label(itemProps.item.rawValue) : (itemProps.item.rawValue as string)} - - - - - - )} - onChange={(v) => { - props.onSelect?.(v ?? undefined) - }} - > - - 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 - }} - - - - - - - - - - - - - ) -} 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 ( - - ) -} -- cgit v1.2.3