mirror of
https://github.com/anomalyco/opencode.git
synced 2026-07-21 10:16:03 +00:00
feat(desktop): prevent overlapping composer borders (#37490)
This commit is contained in:
@@ -36,6 +36,7 @@ import {
|
|||||||
export type PromptInputV2ComposerProps = {
|
export type PromptInputV2ComposerProps = {
|
||||||
class?: string
|
class?: string
|
||||||
controller: PromptInputV2ComposerController
|
controller: PromptInputV2ComposerController
|
||||||
|
borderUnderlay?: boolean
|
||||||
edit?: PromptInputProps["edit"]
|
edit?: PromptInputProps["edit"]
|
||||||
onEditLoaded?: PromptInputProps["onEditLoaded"]
|
onEditLoaded?: PromptInputProps["onEditLoaded"]
|
||||||
}
|
}
|
||||||
@@ -57,6 +58,7 @@ export function PromptInputV2Composer(props: PromptInputV2ComposerProps) {
|
|||||||
<div class="flex flex-col gap-3">
|
<div class="flex flex-col gap-3">
|
||||||
<PromptInputV2
|
<PromptInputV2
|
||||||
controller={props.controller}
|
controller={props.controller}
|
||||||
|
borderUnderlay={props.borderUnderlay}
|
||||||
class={props.class}
|
class={props.class}
|
||||||
modelControl={
|
modelControl={
|
||||||
<PromptInputV2ModelControl
|
<PromptInputV2ModelControl
|
||||||
|
|||||||
@@ -1460,10 +1460,10 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
t={(key) => language.t(key as Parameters<typeof language.t>[0])}
|
t={(key) => language.t(key as Parameters<typeof language.t>[0])}
|
||||||
/>
|
/>
|
||||||
<DockShellForm
|
<DockShellForm
|
||||||
|
data-dock-border-underlay="legacy"
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
classList={{
|
classList={{
|
||||||
"group/prompt-input": true,
|
"group/prompt-input": true,
|
||||||
"focus-within:shadow-xs-border": true,
|
|
||||||
"border-icon-info-active border-dashed": store.draggingType !== null,
|
"border-icon-info-active border-dashed": store.draggingType !== null,
|
||||||
[props.class ?? ""]: !!props.class,
|
[props.class ?? ""]: !!props.class,
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -2265,6 +2265,7 @@ export default function Page() {
|
|||||||
return (
|
return (
|
||||||
<PromptInputV2Composer
|
<PromptInputV2Composer
|
||||||
controller={controller}
|
controller={controller}
|
||||||
|
borderUnderlay
|
||||||
edit={editingFollowup()}
|
edit={editingFollowup()}
|
||||||
onEditLoaded={clearFollowupEdit}
|
onEditLoaded={clearFollowupEdit}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { For } from "solid-js"
|
import { For } from "solid-js"
|
||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
|
import { DockShell } from "@opencode-ai/ui/dock-surface"
|
||||||
import { SessionRevertDock } from "@/pages/session/composer/session-revert-dock"
|
import { SessionRevertDock } from "@/pages/session/composer/session-revert-dock"
|
||||||
import { SettingsProvider, useSettings } from "@/context/settings"
|
import { SettingsProvider, useSettings } from "@/context/settings"
|
||||||
|
|
||||||
@@ -78,12 +79,17 @@ function Stage(props: { count: number }) {
|
|||||||
{/* Reproduce the real composer stack: dock + card overlapping the dock's bottom by lift() = 18px */}
|
{/* Reproduce the real composer stack: dock + card overlapping the dock's bottom by lift() = 18px */}
|
||||||
<div style={{ display: "flex", "flex-direction": "column" }}>
|
<div style={{ display: "flex", "flex-direction": "column" }}>
|
||||||
<SessionRevertDock items={store.items} onRestore={restore} />
|
<SessionRevertDock items={store.items} onRestore={restore} />
|
||||||
<div
|
<DockShell
|
||||||
|
data-dock-border-underlay={v2() ? "v2" : "legacy"}
|
||||||
style={{ position: "relative", "z-index": 70, "margin-top": "-18px" }}
|
style={{ position: "relative", "z-index": 70, "margin-top": "-18px" }}
|
||||||
class="min-h-24 w-full rounded-[12px] border border-v2-border-border-base bg-v2-background-bg-base px-4 py-3 text-[13px] text-v2-text-text-faint"
|
classList={{
|
||||||
|
"min-h-24 w-full rounded-[12px] px-4 py-3 text-[13px]": true,
|
||||||
|
"bg-v2-background-bg-base text-v2-text-text-faint": v2(),
|
||||||
|
"text-text-weak": !v2(),
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Ask anything...
|
Ask anything...
|
||||||
</div>
|
</DockShell>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-[12px] text-v2-text-text-faint">
|
<div class="text-[12px] text-v2-text-text-faint">
|
||||||
|
|||||||
@@ -8,8 +8,10 @@ import { TextReveal } from "@opencode-ai/ui/text-reveal"
|
|||||||
import { TextStrikethrough } from "@opencode-ai/ui/text-strikethrough"
|
import { TextStrikethrough } from "@opencode-ai/ui/text-strikethrough"
|
||||||
import { createResizeObserver } from "@solid-primitives/resize-observer"
|
import { createResizeObserver } from "@solid-primitives/resize-observer"
|
||||||
import { Index, createEffect, createMemo } from "solid-js"
|
import { Index, createEffect, createMemo } from "solid-js"
|
||||||
|
import { Dynamic } from "solid-js/web"
|
||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
|
import { useSettings } from "@/context/settings"
|
||||||
|
|
||||||
const doneToken = "\u0000done\u0000"
|
const doneToken = "\u0000done\u0000"
|
||||||
const totalToken = "\u0000total\u0000"
|
const totalToken = "\u0000total\u0000"
|
||||||
@@ -48,6 +50,7 @@ export function SessionTodoDock(props: {
|
|||||||
dockProgress: number
|
dockProgress: number
|
||||||
}) {
|
}) {
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
|
const settings = useSettings()
|
||||||
const [store, setStore] = createStore({
|
const [store, setStore] = createStore({
|
||||||
height: 78,
|
height: 78,
|
||||||
})
|
})
|
||||||
@@ -91,8 +94,13 @@ export function SessionTodoDock(props: {
|
|||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DockTray
|
<Dynamic
|
||||||
|
component={settings.general.newLayoutDesigns() ? "div" : DockTray}
|
||||||
data-component="session-todo-dock"
|
data-component="session-todo-dock"
|
||||||
|
classList={{
|
||||||
|
"w-full overflow-hidden rounded-xl border-[0.5px] border-v2-border-border-base bg-v2-background-bg-layer-01":
|
||||||
|
settings.general.newLayoutDesigns(),
|
||||||
|
}}
|
||||||
style={{
|
style={{
|
||||||
"overflow-x": "visible",
|
"overflow-x": "visible",
|
||||||
"overflow-y": "hidden",
|
"overflow-y": "hidden",
|
||||||
@@ -102,7 +110,11 @@ export function SessionTodoDock(props: {
|
|||||||
<div ref={contentRef}>
|
<div ref={contentRef}>
|
||||||
<div
|
<div
|
||||||
data-action="session-todo-toggle"
|
data-action="session-todo-toggle"
|
||||||
class="pl-3 pr-2 py-2 flex items-center gap-2 overflow-visible"
|
classList={{
|
||||||
|
"flex items-center gap-2 overflow-visible": true,
|
||||||
|
"h-[42px] pl-4 pr-2": settings.general.newLayoutDesigns(),
|
||||||
|
"pl-3 pr-2 py-2": !settings.general.newLayoutDesigns(),
|
||||||
|
}}
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onClick={props.onToggle}
|
onClick={props.onToggle}
|
||||||
@@ -113,7 +125,12 @@ export function SessionTodoDock(props: {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="text-14-regular text-text-strong cursor-default inline-flex items-baseline shrink-0 overflow-visible"
|
classList={{
|
||||||
|
"cursor-default inline-flex items-baseline shrink-0 overflow-visible": true,
|
||||||
|
"font-[440] text-[13px] leading-5 tracking-[-0.04px] text-v2-text-text-muted":
|
||||||
|
settings.general.newLayoutDesigns(),
|
||||||
|
"text-14-regular text-text-strong": !settings.general.newLayoutDesigns(),
|
||||||
|
}}
|
||||||
aria-label={label()}
|
aria-label={label()}
|
||||||
style={{
|
style={{
|
||||||
"--tool-motion-odometer-ms": "600ms",
|
"--tool-motion-odometer-ms": "600ms",
|
||||||
@@ -145,7 +162,11 @@ export function SessionTodoDock(props: {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TextReveal
|
<TextReveal
|
||||||
class="text-14-regular text-text-base cursor-default"
|
class={
|
||||||
|
settings.general.newLayoutDesigns()
|
||||||
|
? "cursor-default text-[13px] font-[440] leading-5 tracking-[-0.04px] text-v2-text-text-faint"
|
||||||
|
: "text-14-regular text-text-base cursor-default"
|
||||||
|
}
|
||||||
text={props.collapsed ? preview() : undefined}
|
text={props.collapsed ? preview() : undefined}
|
||||||
duration={600}
|
duration={600}
|
||||||
travel={25}
|
travel={25}
|
||||||
@@ -191,7 +212,7 @@ export function SessionTodoDock(props: {
|
|||||||
<TodoList todos={props.todos} />
|
<TodoList todos={props.todos} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DockTray>
|
</Dynamic>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ export type PromptInputV2Props = {
|
|||||||
controller: PromptInputV2Interaction
|
controller: PromptInputV2Interaction
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
readOnly?: boolean
|
readOnly?: boolean
|
||||||
|
borderUnderlay?: boolean
|
||||||
class?: string
|
class?: string
|
||||||
modelControl?: JSX.Element
|
modelControl?: JSX.Element
|
||||||
}
|
}
|
||||||
@@ -102,8 +103,12 @@ export function PromptInputV2(props: PromptInputV2Props) {
|
|||||||
</Show>
|
</Show>
|
||||||
<form
|
<form
|
||||||
data-component="prompt-input-v2"
|
data-component="prompt-input-v2"
|
||||||
class="group/prompt-input relative min-h-[96px] w-full rounded-xl bg-v2-background-bg-base shadow-[var(--v2-elevation-raised)]"
|
data-dock-border-underlay={props.borderUnderlay ? "v2" : undefined}
|
||||||
classList={{ "border border-v2-icon-icon-info border-dashed": state.drag === "active" }}
|
class="group/prompt-input relative min-h-[96px] w-full rounded-xl bg-v2-background-bg-base"
|
||||||
|
classList={{
|
||||||
|
"shadow-[var(--v2-elevation-raised)]": !props.borderUnderlay,
|
||||||
|
"border border-v2-icon-icon-info border-dashed": state.drag === "active",
|
||||||
|
}}
|
||||||
onSubmit={(event) => {
|
onSubmit={(event) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
if (!props.disabled) props.controller.submit()
|
if (!props.disabled) props.controller.submit()
|
||||||
|
|||||||
@@ -7,6 +7,20 @@
|
|||||||
overflow: clip;
|
overflow: clip;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-dock-border-underlay] {
|
||||||
|
/* Later shadows paint underneath, so this solid ring masks overlapping surfaces without changing border geometry. */
|
||||||
|
box-shadow:
|
||||||
|
var(--dock-shell-visual-shadow, var(--shadow-xs-border)),
|
||||||
|
0 0 0 var(--dock-shell-border-underlay-width, 1px)
|
||||||
|
var(--dock-shell-border-underlay-background, var(--surface-raised-stronger-non-alpha));
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-dock-border-underlay="v2"] {
|
||||||
|
--dock-shell-visual-shadow: var(--v2-elevation-raised);
|
||||||
|
--dock-shell-border-underlay-width: 0.5px;
|
||||||
|
--dock-shell-border-underlay-background: var(--v2-background-bg-base);
|
||||||
|
}
|
||||||
|
|
||||||
[data-dock-surface="tray"] {
|
[data-dock-surface="tray"] {
|
||||||
background-color: var(--background-base);
|
background-color: var(--background-base);
|
||||||
border: 1px solid var(--border-weak-base);
|
border: 1px solid var(--border-weak-base);
|
||||||
|
|||||||
Reference in New Issue
Block a user