140 lines
4.5 KiB
TypeScript
140 lines
4.5 KiB
TypeScript
import { Collapsible } from "@kobalte/core/collapsible"
|
|
import { type ComponentProps, type JSX, For, Show, createMemo, splitProps } from "solid-js"
|
|
import { DiffChanges } from "@opencode-ai/ui/v2/diff-changes-v2"
|
|
import { TextShimmerV2 } from "@opencode-ai/ui/v2/text-shimmer-v2"
|
|
import "./basic-tool-v2.css"
|
|
|
|
function ChevronIcon() {
|
|
return (
|
|
<svg
|
|
data-slot="basic-tool-v2-chevron"
|
|
width="16"
|
|
height="16"
|
|
viewBox="0 0 16 16"
|
|
fill="none"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
aria-hidden="true"
|
|
>
|
|
<path
|
|
d="M6.75194 10.6243C6.41861 10.8187 6 10.5783 6 10.1924V5.80837C6 5.42247 6.41861 5.18204 6.75194 5.37648L10.5096 7.56846C10.8404 7.7614 10.8404 8.2393 10.5096 8.43224L6.75194 10.6243Z"
|
|
fill="currentColor"
|
|
/>
|
|
</svg>
|
|
)
|
|
}
|
|
|
|
export interface BasicToolV2TriggerTitle {
|
|
title: string
|
|
subtitle?: string
|
|
args?: string[]
|
|
changes?: { additions: number; deletions: number } | { additions: number; deletions: number }[]
|
|
action?: JSX.Element
|
|
}
|
|
|
|
const isTriggerTitle = (val: unknown): val is BasicToolV2TriggerTitle =>
|
|
typeof val === "object" && val !== null && "title" in val && (typeof Node === "undefined" || !(val instanceof Node))
|
|
|
|
export interface BasicToolV2Props extends Omit<ComponentProps<"div">, "children" | "title"> {
|
|
trigger: BasicToolV2TriggerTitle | JSX.Element
|
|
children?: JSX.Element
|
|
status?: string
|
|
open?: boolean
|
|
defaultOpen?: boolean
|
|
onOpenChange?: (open: boolean) => void
|
|
onSubtitleClick?: () => void
|
|
}
|
|
|
|
export function BasicToolV2(props: BasicToolV2Props) {
|
|
const [local, rest] = splitProps(props, [
|
|
"trigger",
|
|
"children",
|
|
"status",
|
|
"open",
|
|
"defaultOpen",
|
|
"onOpenChange",
|
|
"onSubtitleClick",
|
|
"class",
|
|
"classList",
|
|
])
|
|
|
|
const pending = createMemo(() => local.status === "pending" || local.status === "running")
|
|
|
|
const hasChildren = createMemo(() => {
|
|
const c = local.children
|
|
if (c == null) return false
|
|
return true
|
|
})
|
|
|
|
const canExpand = createMemo(() => hasChildren() && !pending())
|
|
|
|
const handleOpenChange = (value: boolean) => {
|
|
if (pending()) return
|
|
local.onOpenChange?.(value)
|
|
}
|
|
|
|
return (
|
|
<Collapsible
|
|
{...rest}
|
|
data-component="basic-tool-v2"
|
|
open={local.open}
|
|
defaultOpen={local.defaultOpen}
|
|
onOpenChange={handleOpenChange}
|
|
disabled={!canExpand()}
|
|
classList={{
|
|
...local.classList,
|
|
[local.class ?? ""]: !!local.class,
|
|
}}
|
|
>
|
|
<Collapsible.Trigger as="div" role="button" data-slot="basic-tool-v2-trigger">
|
|
<div data-slot="basic-tool-v2-labels">
|
|
<Show when={isTriggerTitle(local.trigger) && local.trigger} fallback={local.trigger as JSX.Element}>
|
|
{(title) => (
|
|
<>
|
|
<span data-slot="basic-tool-v2-title">
|
|
<TextShimmerV2 text={title().title} active={pending()} />
|
|
</span>
|
|
<Show when={!pending() && title().subtitle}>
|
|
<span data-slot="basic-tool-v2-sep" aria-hidden="true">
|
|
·
|
|
</span>
|
|
<span
|
|
data-slot="basic-tool-v2-subtitle"
|
|
style={local.onSubtitleClick ? { cursor: "pointer" } : undefined}
|
|
onClick={(e) => {
|
|
if (local.onSubtitleClick) {
|
|
e.stopPropagation()
|
|
local.onSubtitleClick()
|
|
}
|
|
}}
|
|
>
|
|
{title().subtitle}
|
|
</span>
|
|
</Show>
|
|
<Show when={!pending() && title().args?.length}>
|
|
<For each={title().args}>{(arg) => <span data-slot="basic-tool-v2-arg">{arg}</span>}</For>
|
|
</Show>
|
|
<Show when={!pending() && title().changes}>
|
|
<span data-slot="basic-tool-v2-diff">
|
|
<DiffChanges changes={title().changes!} />
|
|
</span>
|
|
</Show>
|
|
<Show when={!pending() && title().action}>{(action) => action()}</Show>
|
|
</>
|
|
)}
|
|
</Show>
|
|
<Show when={canExpand()}>
|
|
<span data-slot="basic-tool-v2-chevron-wrap">
|
|
<ChevronIcon />
|
|
</span>
|
|
</Show>
|
|
</div>
|
|
</Collapsible.Trigger>
|
|
<Show when={canExpand()}>
|
|
<Collapsible.Content data-slot="basic-tool-v2-content">
|
|
<div data-slot="basic-tool-v2-content-inner">{local.children}</div>
|
|
</Collapsible.Content>
|
|
</Show>
|
|
</Collapsible>
|
|
)
|
|
}
|