diff --git a/.changeset/animate-tool-card-reveals.md b/.changeset/animate-tool-card-reveals.md new file mode 100644 index 00000000000..a273d7ae45e --- /dev/null +++ b/.changeset/animate-tool-card-reveals.md @@ -0,0 +1,5 @@ +--- +"kilo-code": patch +--- + +Animate tool-call and reasoning details when expanding and collapsing. diff --git a/packages/kilo-ui/src/components/basic-tool.css b/packages/kilo-ui/src/components/basic-tool.css index 0111b6381a9..49ed771af57 100644 --- a/packages/kilo-ui/src/components/basic-tool.css +++ b/packages/kilo-ui/src/components/basic-tool.css @@ -129,19 +129,62 @@ html[data-theme="kilo-vscode"] [data-component="tool-trigger"] { } } +@keyframes tool-card-down { + from { + grid-template-rows: 0fr; + opacity: 0; + } + + to { + grid-template-rows: 1fr; + opacity: 1; + } +} + +@keyframes tool-card-up { + from { + grid-template-rows: 1fr; + opacity: 1; + } + + to { + grid-template-rows: 0fr; + opacity: 0; + } +} + /* Card styling for tool calls in the VS Code sidebar theme */ html[data-theme="kilo-vscode"] [data-component="tool-part-wrapper"][data-part-type="tool"] { border-radius: 0; border: 0; overflow: visible; - /* Header trigger — add bottom border when the collapsible is open */ [data-component="collapsible"].tool-collapsible { gap: 0; border-radius: 0; - &:has([data-slot="collapsible-trigger"][aria-expanded="true"]) { - gap: 6px; + > [data-slot="collapsible-content"] { + display: grid; + overflow: visible; + clip-path: inset(0 -6px); + + > * { + min-height: 0; + } + } + + > [data-slot="collapsible-content"][data-expanded] { + animation: tool-card-down 220ms cubic-bezier(0.23, 1, 0.32, 1); + } + + > [data-slot="collapsible-content"][data-closed] { + animation: tool-card-up 160ms cubic-bezier(0.77, 0, 0.175, 1); + } + + [data-slot="basic-tool-details"]::before { + display: block; + height: 6px; + content: ""; } [data-slot="collapsible-trigger"] { @@ -409,3 +452,12 @@ html[data-theme="kilo-vscode"] [data-component="tool-part-wrapper"][data-part-ty } } } + +@media (prefers-reduced-motion: reduce) { + html[data-theme="kilo-vscode"] [data-component="tool-part-wrapper"][data-part-type="tool"] { + [data-component="collapsible"].tool-collapsible > [data-slot="collapsible-content"][data-expanded], + [data-component="collapsible"].tool-collapsible > [data-slot="collapsible-content"][data-closed] { + animation-duration: 0.01ms; + } + } +} diff --git a/packages/kilo-ui/src/components/basic-tool.tsx b/packages/kilo-ui/src/components/basic-tool.tsx index 0f4234ad1a4..7ba072c7c44 100644 --- a/packages/kilo-ui/src/components/basic-tool.tsx +++ b/packages/kilo-ui/src/components/basic-tool.tsx @@ -20,14 +20,16 @@ export function initialOpen(props: OpenProps) { export function BasicTool(props: BasicToolProps) { const key = () => toolOpenKey(props) const initial = () => initialOpen(props) + const change = (open: boolean) => { + writeToolOpen(key(), open) + props.onOpenChange?.(open) + } + if (!("children" in props)) { + return + } return ( - { - writeToolOpen(key(), open) - props.onOpenChange?.(open) - }} - /> + +
{props.children}
+ ) } diff --git a/packages/kilo-ui/src/components/message-part.css b/packages/kilo-ui/src/components/message-part.css index ffb4ae84ec4..5a3fd732da0 100644 --- a/packages/kilo-ui/src/components/message-part.css +++ b/packages/kilo-ui/src/components/message-part.css @@ -477,6 +477,30 @@ html[data-theme="kilo-vscode"] [data-component="todos"] { padding: 8px; } +@keyframes reasoning-card-down { + from { + grid-template-rows: 0fr; + opacity: 0; + } + + to { + grid-template-rows: 1fr; + opacity: 1; + } +} + +@keyframes reasoning-card-up { + from { + grid-template-rows: 1fr; + opacity: 1; + } + + to { + grid-template-rows: 0fr; + opacity: 0; + } +} + /* Reasoning block: visually distinct from normal agent output. Reasoning stays open unless the user explicitly collapses it. */ [data-component="reasoning-part"] { @@ -550,22 +574,21 @@ html[data-theme="kilo-vscode"] [data-component="todos"] { } } - /* Collapse/expand animation via Kobalte's CSS variable. - Uses grid trick: the outer wrapper transitions grid-template-rows - between 0fr (collapsed) and 1fr (expanded) so the content smoothly - collapses without needing to know the pixel height up front. */ [data-slot="collapsible-content"] { display: grid; - grid-template-rows: 0fr; - transition: grid-template-rows 250ms ease-out; overflow: hidden; > * { + min-height: 0; overflow: hidden; } &[data-expanded] { - grid-template-rows: 1fr; + animation: reasoning-card-down 220ms cubic-bezier(0.23, 1, 0.32, 1); + } + + &[data-closed] { + animation: reasoning-card-up 160ms cubic-bezier(0.77, 0, 0.175, 1); } } @@ -598,10 +621,6 @@ html[data-theme="kilo-vscode"] [data-component="reasoning-part"] { [data-component="collapsible"].tool-collapsible { gap: 0; border-radius: 0; - - &:has([data-slot="collapsible-trigger"][aria-expanded="true"]) { - gap: 6px; - } } [data-slot="collapsible-trigger"] { @@ -667,6 +686,12 @@ html[data-theme="kilo-vscode"] [data-component="reasoning-part"] { width: calc(100% + 12px); } + [data-slot="reasoning-details"]::before { + display: block; + height: 6px; + content: ""; + } + [data-slot="reasoning-content"] { padding: 8px 12px 10px; border: 0; @@ -712,3 +737,10 @@ html[data-theme="kilo-vscode"] [data-component="reasoning-part"] { line-height: var(--line-height-large); } } + +@media (prefers-reduced-motion: reduce) { + [data-component="reasoning-part"] [data-slot="collapsible-content"][data-expanded], + [data-component="reasoning-part"] [data-slot="collapsible-content"][data-closed] { + animation-duration: 0.01ms; + } +} diff --git a/packages/kilo-ui/src/components/message-part.tsx b/packages/kilo-ui/src/components/message-part.tsx index d8a448ed0ed..4fb2aab53bc 100644 --- a/packages/kilo-ui/src/components/message-part.tsx +++ b/packages/kilo-ui/src/components/message-part.tsx @@ -1591,8 +1591,10 @@ PART_MAPPING["reasoning"] = function ReasoningPartDisplay(props: MessagePartProp -
- +
+
+ +
diff --git a/packages/ui/src/components/basic-tool.tsx b/packages/ui/src/components/basic-tool.tsx index 3dd1f37836f..9e713a12d64 100644 --- a/packages/ui/src/components/basic-tool.tsx +++ b/packages/ui/src/components/basic-tool.tsx @@ -31,6 +31,7 @@ export interface BasicToolProps { defaultOpen?: boolean forceOpen?: boolean defer?: boolean + retainDetails?: boolean // kilocode_change hasDetails?: boolean // kilocode_change locked?: boolean animated?: boolean @@ -125,7 +126,7 @@ export function BasicTool(props: BasicToolProps) { if (!props.defer) return if (!value) { cancel() - setState("ready", false) + if (!props.retainDetails) setState("ready", false) // kilocode_change return } @@ -175,6 +176,14 @@ export function BasicTool(props: BasicToolProps) { props.onOpenChange?.(value) // kilocode_change } + // kilocode_change start + const end = (event: AnimationEvent) => { + if (event.target !== event.currentTarget) return + if (!props.retainDetails || open()) return + setState("ready", false) + } + // kilocode_change end + const trigger = () => (
{/* kilocode_change start */} - + {props.children}