summaryrefslogtreecommitdiffhomepage
path: root/packages/app/src/components/prompt-input
diff options
context:
space:
mode:
authorAdam <[email protected]>2026-02-12 09:49:14 -0600
committerGitHub <[email protected]>2026-02-12 09:49:14 -0600
commitff4414bb152acfddb5c0eb073c38bedc1df4ae14 (patch)
tree78381c67d21ef6f089647f6b19e7aa2976840dbc /packages/app/src/components/prompt-input
parent56ad2db02055955f926fda0e4a89055b22ead6f9 (diff)
downloadopencode-ff4414bb152acfddb5c0eb073c38bedc1df4ae14.tar.gz
opencode-ff4414bb152acfddb5c0eb073c38bedc1df4ae14.zip
chore: refactor packages/app files (#13236)
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com> Co-authored-by: Frank <[email protected]>
Diffstat (limited to 'packages/app/src/components/prompt-input')
-rw-r--r--packages/app/src/components/prompt-input/context-items.tsx109
-rw-r--r--packages/app/src/components/prompt-input/drag-overlay.tsx7
-rw-r--r--packages/app/src/components/prompt-input/image-attachments.tsx15
-rw-r--r--packages/app/src/components/prompt-input/slash-popover.tsx79
4 files changed, 114 insertions, 96 deletions
diff --git a/packages/app/src/components/prompt-input/context-items.tsx b/packages/app/src/components/prompt-input/context-items.tsx
index a843e109d..b575c3961 100644
--- a/packages/app/src/components/prompt-input/context-items.tsx
+++ b/packages/app/src/components/prompt-input/context-items.tsx
@@ -20,61 +20,68 @@ export const PromptContextItems: Component<ContextItemsProps> = (props) => {
<Show when={props.items.length > 0}>
<div class="flex flex-nowrap items-start gap-2 p-2 overflow-x-auto no-scrollbar">
<For each={props.items}>
- {(item) => (
- <Tooltip
- value={
- <span class="flex max-w-[300px]">
- <span class="text-text-invert-base truncate-start [unicode-bidi:plaintext] min-w-0">
- {getDirectory(item.path)}
+ {(item) => {
+ const directory = getDirectory(item.path)
+ const filename = getFilename(item.path)
+ const label = getFilenameTruncated(item.path, 14)
+ const selected = props.active(item)
+
+ return (
+ <Tooltip
+ value={
+ <span class="flex max-w-[300px]">
+ <span class="text-text-invert-base truncate-start [unicode-bidi:plaintext] min-w-0">
+ {directory}
+ </span>
+ <span class="shrink-0">{filename}</span>
</span>
- <span class="shrink-0">{getFilename(item.path)}</span>
- </span>
- }
- placement="top"
- openDelay={2000}
- >
- <div
- classList={{
- "group shrink-0 flex flex-col rounded-[6px] pl-2 pr-1 py-1 max-w-[200px] h-12 transition-all transition-transform shadow-xs-border hover:shadow-xs-border-hover": true,
- "cursor-pointer hover:bg-surface-interactive-weak": !!item.commentID && !props.active(item),
- "cursor-pointer bg-surface-interactive-hover hover:bg-surface-interactive-hover shadow-xs-border-hover":
- props.active(item),
- "bg-background-stronger": !props.active(item),
- }}
- onClick={() => props.openComment(item)}
+ }
+ placement="top"
+ openDelay={2000}
>
- <div class="flex items-center gap-1.5">
- <FileIcon node={{ path: item.path, type: "file" }} class="shrink-0 size-3.5" />
- <div class="flex items-center text-11-regular min-w-0 font-medium">
- <span class="text-text-strong whitespace-nowrap">{getFilenameTruncated(item.path, 14)}</span>
- <Show when={item.selection}>
- {(sel) => (
- <span class="text-text-weak whitespace-nowrap shrink-0">
- {sel().startLine === sel().endLine
- ? `:${sel().startLine}`
- : `:${sel().startLine}-${sel().endLine}`}
- </span>
- )}
- </Show>
+ <div
+ classList={{
+ "group shrink-0 flex flex-col rounded-[6px] pl-2 pr-1 py-1 max-w-[200px] h-12 transition-all transition-transform shadow-xs-border hover:shadow-xs-border-hover": true,
+ "cursor-pointer hover:bg-surface-interactive-weak": !!item.commentID && !selected,
+ "cursor-pointer bg-surface-interactive-hover hover:bg-surface-interactive-hover shadow-xs-border-hover":
+ selected,
+ "bg-background-stronger": !selected,
+ }}
+ onClick={() => props.openComment(item)}
+ >
+ <div class="flex items-center gap-1.5">
+ <FileIcon node={{ path: item.path, type: "file" }} class="shrink-0 size-3.5" />
+ <div class="flex items-center text-11-regular min-w-0 font-medium">
+ <span class="text-text-strong whitespace-nowrap">{label}</span>
+ <Show when={item.selection}>
+ {(sel) => (
+ <span class="text-text-weak whitespace-nowrap shrink-0">
+ {sel().startLine === sel().endLine
+ ? `:${sel().startLine}`
+ : `:${sel().startLine}-${sel().endLine}`}
+ </span>
+ )}
+ </Show>
+ </div>
+ <IconButton
+ type="button"
+ icon="close-small"
+ variant="ghost"
+ class="ml-auto size-3.5 text-text-weak hover:text-text-strong transition-all"
+ onClick={(e) => {
+ e.stopPropagation()
+ props.remove(item)
+ }}
+ aria-label={props.t("prompt.context.removeFile")}
+ />
</div>
- <IconButton
- type="button"
- icon="close-small"
- variant="ghost"
- class="ml-auto size-3.5 text-text-weak hover:text-text-strong transition-all"
- onClick={(e) => {
- e.stopPropagation()
- props.remove(item)
- }}
- aria-label={props.t("prompt.context.removeFile")}
- />
+ <Show when={item.comment}>
+ {(comment) => <div class="text-12-regular text-text-strong ml-5 pr-1 truncate">{comment()}</div>}
+ </Show>
</div>
- <Show when={item.comment}>
- {(comment) => <div class="text-12-regular text-text-strong ml-5 pr-1 truncate">{comment()}</div>}
- </Show>
- </div>
- </Tooltip>
- )}
+ </Tooltip>
+ )
+ }}
</For>
</div>
</Show>
diff --git a/packages/app/src/components/prompt-input/drag-overlay.tsx b/packages/app/src/components/prompt-input/drag-overlay.tsx
index e05b47d7c..41962ce53 100644
--- a/packages/app/src/components/prompt-input/drag-overlay.tsx
+++ b/packages/app/src/components/prompt-input/drag-overlay.tsx
@@ -6,12 +6,17 @@ type PromptDragOverlayProps = {
label: string
}
+const kindToIcon = {
+ image: "photo",
+ "@mention": "link",
+} as const
+
export const PromptDragOverlay: Component<PromptDragOverlayProps> = (props) => {
return (
<Show when={props.type !== null}>
<div class="absolute inset-0 z-10 flex items-center justify-center bg-surface-raised-stronger-non-alpha/90 pointer-events-none">
<div class="flex flex-col items-center gap-2 text-text-weak">
- <Icon name={props.type === "@mention" ? "link" : "photo"} class="size-8" />
+ <Icon name={props.type ? kindToIcon[props.type] : kindToIcon.image} class="size-8" />
<span class="text-14-regular">{props.label}</span>
</div>
</div>
diff --git a/packages/app/src/components/prompt-input/image-attachments.tsx b/packages/app/src/components/prompt-input/image-attachments.tsx
index ba3addf0a..835fddc30 100644
--- a/packages/app/src/components/prompt-input/image-attachments.tsx
+++ b/packages/app/src/components/prompt-input/image-attachments.tsx
@@ -9,6 +9,13 @@ type PromptImageAttachmentsProps = {
removeLabel: string
}
+const fallbackClass = "size-16 rounded-md bg-surface-base flex items-center justify-center border border-border-base"
+const imageClass =
+ "size-16 rounded-md object-cover border border-border-base hover:border-border-strong-base transition-colors"
+const removeClass =
+ "absolute -top-1.5 -right-1.5 size-5 rounded-full bg-surface-raised-stronger-non-alpha border border-border-base flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity hover:bg-surface-raised-base-hover"
+const nameClass = "absolute bottom-0 left-0 right-0 px-1 py-0.5 bg-black/50 rounded-b-md"
+
export const PromptImageAttachments: Component<PromptImageAttachmentsProps> = (props) => {
return (
<Show when={props.attachments.length > 0}>
@@ -19,7 +26,7 @@ export const PromptImageAttachments: Component<PromptImageAttachmentsProps> = (p
<Show
when={attachment.mime.startsWith("image/")}
fallback={
- <div class="size-16 rounded-md bg-surface-base flex items-center justify-center border border-border-base">
+ <div class={fallbackClass}>
<Icon name="folder" class="size-6 text-text-weak" />
</div>
}
@@ -27,19 +34,19 @@ export const PromptImageAttachments: Component<PromptImageAttachmentsProps> = (p
<img
src={attachment.dataUrl}
alt={attachment.filename}
- class="size-16 rounded-md object-cover border border-border-base hover:border-border-strong-base transition-colors"
+ class={imageClass}
onClick={() => props.onOpen(attachment)}
/>
</Show>
<button
type="button"
onClick={() => props.onRemove(attachment.id)}
- class="absolute -top-1.5 -right-1.5 size-5 rounded-full bg-surface-raised-stronger-non-alpha border border-border-base flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity hover:bg-surface-raised-base-hover"
+ class={removeClass}
aria-label={props.removeLabel}
>
<Icon name="close" class="size-3 text-text-weak" />
</button>
- <div class="absolute bottom-0 left-0 right-0 px-1 py-0.5 bg-black/50 rounded-b-md">
+ <div class={nameClass}>
<span class="text-10-regular text-white truncate block">{attachment.filename}</span>
</div>
</div>
diff --git a/packages/app/src/components/prompt-input/slash-popover.tsx b/packages/app/src/components/prompt-input/slash-popover.tsx
index b97bb6752..554a15bb7 100644
--- a/packages/app/src/components/prompt-input/slash-popover.tsx
+++ b/packages/app/src/components/prompt-input/slash-popover.tsx
@@ -52,47 +52,46 @@ export const PromptPopover: Component<PromptPopoverProps> = (props) => {
fallback={<div class="text-text-weak px-2 py-1">{props.t("prompt.popover.emptyResults")}</div>}
>
<For each={props.atFlat.slice(0, 10)}>
- {(item) => (
- <button
- classList={{
- "w-full flex items-center gap-x-2 rounded-md px-2 py-0.5": true,
- "bg-surface-raised-base-hover": props.atActive === props.atKey(item),
- }}
- onClick={() => props.onAtSelect(item)}
- onMouseEnter={() => props.setAtActive(props.atKey(item))}
- >
- <Show
- when={item.type === "agent"}
- fallback={
- <>
- <FileIcon
- node={{ path: item.type === "file" ? item.path : "", type: "file" }}
- class="shrink-0 size-4"
- />
- <div class="flex items-center text-14-regular min-w-0">
- <span class="text-text-weak whitespace-nowrap truncate min-w-0">
- {item.type === "file"
- ? item.path.endsWith("/")
- ? item.path
- : getDirectory(item.path)
- : ""}
- </span>
- <Show when={item.type === "file" && !item.path.endsWith("/")}>
- <span class="text-text-strong whitespace-nowrap">
- {item.type === "file" ? getFilename(item.path) : ""}
- </span>
- </Show>
- </div>
- </>
- }
+ {(item) => {
+ const active = props.atActive === props.atKey(item)
+ const shared = {
+ "w-full flex items-center gap-x-2 rounded-md px-2 py-0.5": true,
+ "bg-surface-raised-base-hover": active,
+ }
+
+ if (item.type === "agent") {
+ return (
+ <button
+ classList={shared}
+ onClick={() => props.onAtSelect(item)}
+ onMouseEnter={() => props.setAtActive(props.atKey(item))}
+ >
+ <Icon name="brain" size="small" class="text-icon-info-active shrink-0" />
+ <span class="text-14-regular text-text-strong whitespace-nowrap">@{item.name}</span>
+ </button>
+ )
+ }
+
+ const isDirectory = item.path.endsWith("/")
+ const directory = isDirectory ? item.path : getDirectory(item.path)
+ const filename = isDirectory ? "" : getFilename(item.path)
+
+ return (
+ <button
+ classList={shared}
+ onClick={() => props.onAtSelect(item)}
+ onMouseEnter={() => props.setAtActive(props.atKey(item))}
>
- <Icon name="brain" size="small" class="text-icon-info-active shrink-0" />
- <span class="text-14-regular text-text-strong whitespace-nowrap">
- @{item.type === "agent" ? item.name : ""}
- </span>
- </Show>
- </button>
- )}
+ <FileIcon node={{ path: item.path, type: "file" }} class="shrink-0 size-4" />
+ <div class="flex items-center text-14-regular min-w-0">
+ <span class="text-text-weak whitespace-nowrap truncate min-w-0">{directory}</span>
+ <Show when={!isDirectory}>
+ <span class="text-text-strong whitespace-nowrap">{filename}</span>
+ </Show>
+ </div>
+ </button>
+ )
+ }}
</For>
</Show>
</Match>