summaryrefslogtreecommitdiffhomepage
path: root/packages/desktop/src/components
diff options
context:
space:
mode:
authorAdam <[email protected]>2025-11-13 14:47:25 -0600
committerAdam <[email protected]>2025-11-13 14:47:29 -0600
commita8836c5615f620321cc7aa2670767e0c9586d27b (patch)
tree4af1b0f7397d2dceac6e0c9ba734049f41d7c326 /packages/desktop/src/components
parent779a27693a9d37ae212c27af353414ff4de07ea4 (diff)
downloadopencode-a8836c5615f620321cc7aa2670767e0c9586d27b.tar.gz
opencode-a8836c5615f620321cc7aa2670767e0c9586d27b.zip
wip(desktop): layout improvements
Diffstat (limited to 'packages/desktop/src/components')
-rw-r--r--packages/desktop/src/components/prompt-input.tsx12
-rw-r--r--packages/desktop/src/components/session-review.tsx104
-rw-r--r--packages/desktop/src/components/sticky-accordion-header.tsx17
3 files changed, 127 insertions, 6 deletions
diff --git a/packages/desktop/src/components/prompt-input.tsx b/packages/desktop/src/components/prompt-input.tsx
index b2022cf30..2ae5f87fc 100644
--- a/packages/desktop/src/components/prompt-input.tsx
+++ b/packages/desktop/src/components/prompt-input.tsx
@@ -347,7 +347,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
<Show when={store.popoverIsOpen}>
<div
class="absolute inset-x-0 -top-3 -translate-y-full origin-bottom-left max-h-[252px] min-h-10
- overflow-auto no-scrollbar flex flex-col p-2 pb-0 rounded-2xl
+ overflow-auto no-scrollbar flex flex-col p-2 pb-0 rounded-md
border border-border-base bg-surface-raised-stronger-non-alpha shadow-md"
>
<Show when={flat().length > 0} fallback={<div class="text-text-weak px-2">No matching files</div>}>
@@ -382,7 +382,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
onSubmit={handleSubmit}
classList={{
"bg-surface-raised-stronger-non-alpha border border-border-strong-base": true,
- "rounded-2xl overflow-clip focus-within:border-transparent focus-within:shadow-xs-border-select": true,
+ "rounded-md overflow-clip focus-within:border-transparent focus-within:shadow-xs-border-select": true,
[props.class ?? ""]: !!props.class,
}}
>
@@ -396,17 +396,17 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
onInput={handleInput}
onKeyDown={handleKeyDown}
classList={{
- "w-full p-3 text-14-regular text-text-strong focus:outline-none whitespace-pre-wrap": true,
+ "w-full px-5 py-3 text-14-regular text-text-strong focus:outline-none whitespace-pre-wrap": true,
"[&>[data-type=file]]:text-icon-info-active": true,
}}
/>
<Show when={!session.prompt.dirty()}>
- <div class="absolute top-0 left-0 p-3 text-14-regular text-text-weak pointer-events-none">
+ <div class="absolute top-0 left-0 px-5 py-3 text-14-regular text-text-weak pointer-events-none">
Plan and build anything
</div>
</Show>
</div>
- <div class="p-3 flex items-center justify-between">
+ <div class="relative p-3 flex items-center justify-between">
<div class="flex items-center justify-start gap-1">
<Select
options={local.agent.list().map((agent) => agent.name)}
@@ -489,7 +489,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
disabled={!session.prompt.dirty() && !session.working()}
icon={session.working() ? "stop" : "arrow-up"}
variant="primary"
- class="rounded-full"
+ class="h-10 w-8 absolute right-2 bottom-2"
/>
</Tooltip>
</div>
diff --git a/packages/desktop/src/components/session-review.tsx b/packages/desktop/src/components/session-review.tsx
new file mode 100644
index 000000000..8aefbbd46
--- /dev/null
+++ b/packages/desktop/src/components/session-review.tsx
@@ -0,0 +1,104 @@
+import { useLocal } from "@/context/local"
+import { useSession } from "@/context/session"
+import { FileIcon } from "@/ui"
+import { getDirectory, getFilename } from "@/utils"
+import { Accordion, Button, Diff, DiffChanges, Icon, IconButton, Tooltip } from "@opencode-ai/ui"
+import { For, Match, Show, Switch } from "solid-js"
+import { StickyAccordionHeader } from "./sticky-accordion-header"
+import { createStore } from "solid-js/store"
+
+export const SessionReview = (props: { split?: boolean; class?: string; hideExpand?: boolean }) => {
+ const local = useLocal()
+ const session = useSession()
+ const [store, setStore] = createStore({
+ open: session.diffs().map((d) => d.file),
+ })
+
+ const handleChange = (open: string[]) => {
+ setStore("open", open)
+ }
+
+ const handleExpandOrCollapseAll = () => {
+ if (store.open.length > 0) {
+ setStore("open", [])
+ } else {
+ setStore(
+ "open",
+ session.diffs().map((d) => d.file),
+ )
+ }
+ }
+
+ return (
+ <div
+ classList={{
+ "flex flex-col gap-3 h-full overflow-y-auto no-scrollbar": true,
+ [props.class ?? ""]: !!props.class,
+ }}
+ >
+ <div class="sticky top-0 z-20 bg-background-stronger h-8 shrink-0 flex justify-between items-center self-stretch">
+ <div class="text-14-medium text-text-strong">Session changes</div>
+ <div class="flex items-center gap-x-4 pr-px">
+ <Button size="normal" icon="chevron-grabber-vertical" onClick={handleExpandOrCollapseAll}>
+ <Switch>
+ <Match when={store.open.length > 0}>Collapse all</Match>
+ <Match when={true}>Expand all</Match>
+ </Switch>
+ </Button>
+ <Show when={!props.hideExpand}>
+ <Tooltip value="Open in tab">
+ <IconButton
+ icon="expand"
+ variant="ghost"
+ onClick={() => {
+ local.layout.review.tab()
+ session.layout.setActiveTab("review")
+ }}
+ />
+ </Tooltip>
+ </Show>
+ </div>
+ </div>
+ <Accordion multiple value={store.open} onChange={handleChange}>
+ <For each={session.diffs()}>
+ {(diff) => (
+ <Accordion.Item value={diff.file}>
+ <StickyAccordionHeader class="top-11 data-expanded:before:-top-11">
+ <Accordion.Trigger class="bg-background-stronger">
+ <div class="flex items-center justify-between w-full gap-5">
+ <div class="grow flex items-center gap-5 min-w-0">
+ <FileIcon node={{ path: diff.file, type: "file" }} class="shrink-0 size-4" />
+ <div class="flex grow min-w-0">
+ <Show when={diff.file.includes("/")}>
+ <span class="text-text-base truncate-start">{getDirectory(diff.file)}&lrm;</span>
+ </Show>
+ <span class="text-text-strong shrink-0">{getFilename(diff.file)}</span>
+ </div>
+ </div>
+ <div class="shrink-0 flex gap-4 items-center justify-end">
+ <DiffChanges changes={diff} />
+ <Icon name="chevron-grabber-vertical" size="small" />
+ </div>
+ </div>
+ </Accordion.Trigger>
+ </StickyAccordionHeader>
+ <Accordion.Content>
+ <Diff
+ diffStyle={props.split ? "split" : "unified"}
+ before={{
+ name: diff.file!,
+ contents: diff.before!,
+ }}
+ after={{
+ name: diff.file!,
+ contents: diff.after!,
+ }}
+ />
+ </Accordion.Content>
+ </Accordion.Item>
+ )}
+ </For>
+ </Accordion>
+ </div>
+ )
+}
diff --git a/packages/desktop/src/components/sticky-accordion-header.tsx b/packages/desktop/src/components/sticky-accordion-header.tsx
new file mode 100644
index 000000000..cb3f0d5eb
--- /dev/null
+++ b/packages/desktop/src/components/sticky-accordion-header.tsx
@@ -0,0 +1,17 @@
+import { Accordion } from "@opencode-ai/ui"
+import { ParentProps } from "solid-js"
+
+export function StickyAccordionHeader(props: ParentProps<{ class?: string }>) {
+ return (
+ <Accordion.Header
+ classList={{
+ "sticky top-0 data-expanded:z-10": true,
+ "data-expanded:before:content-[''] data-expanded:before:z-[-10]": true,
+ "data-expanded:before:absolute data-expanded:before:inset-0 data-expanded:before:bg-background-stronger": true,
+ [props.class ?? ""]: !!props.class,
+ }}
+ >
+ {props.children}
+ </Accordion.Header>
+ )
+}