feat: animate accordion, user message

This commit is contained in:
Aaron Iker
2026-01-29 17:50:00 +01:00
parent e3312f1c3f
commit d6beccf938
5 changed files with 69 additions and 44 deletions

View File

@@ -36,7 +36,9 @@
border-radius: var(--radius-md);
overflow: clip;
color: var(--text-strong);
transition: background-color 0.15s ease;
transition-property: background-color, border-color;
transition-duration: var(--transition-duration);
transition-timing-function: var(--transition-easing);
/* text-12-regular */
font-family: var(--font-family-sans);
@@ -58,6 +60,16 @@
}
}
[data-slot="accordion-arrow"] {
flex-shrink: 0;
width: 16px;
height: 16px;
display: flex;
align-items: center;
justify-content: center;
color: var(--text-weak);
}
&[data-expanded] {
[data-slot="accordion-trigger"] {
border-bottom-left-radius: 0;
@@ -69,30 +81,29 @@
border-top: none;
border-bottom-left-radius: var(--radius-md);
border-bottom-right-radius: var(--radius-md);
height: auto;
}
}
[data-slot="accordion-content"] {
overflow: hidden;
display: grid;
grid-template-rows: 0fr;
transition-property: grid-template-rows, opacity;
transition-duration: var(--transition-duration);
transition-timing-function: var(--transition-easing);
width: 100%;
> * {
overflow: hidden;
}
&[data-expanded] {
grid-template-rows: 1fr;
}
&[data-closed] {
grid-template-rows: 0fr;
}
}
}
}
@keyframes slideDown {
from {
height: 0;
}
to {
height: var(--kb-accordion-content-height);
}
}
@keyframes slideUp {
from {
height: var(--kb-accordion-content-height);
}
to {
height: 0;
}
}

View File

@@ -1,6 +1,7 @@
import { Accordion as Kobalte } from "@kobalte/core/accordion"
import { splitProps } from "solid-js"
import { Accessor, createContext, splitProps, useContext } from "solid-js"
import type { ComponentProps, ParentProps } from "solid-js"
import { MorphChevron } from "./morph-chevron"
export interface AccordionProps extends ComponentProps<typeof Kobalte> {}
export interface AccordionItemProps extends ComponentProps<typeof Kobalte.Item> {}
@@ -8,6 +9,8 @@ export interface AccordionHeaderProps extends ComponentProps<typeof Kobalte.Head
export interface AccordionTriggerProps extends ComponentProps<typeof Kobalte.Trigger> {}
export interface AccordionContentProps extends ComponentProps<typeof Kobalte.Content> {}
const AccordionItemContext = createContext<Accessor<boolean>>()
function AccordionRoot(props: AccordionProps) {
const [split, rest] = splitProps(props, ["class", "classList"])
return (
@@ -22,17 +25,19 @@ function AccordionRoot(props: AccordionProps) {
)
}
function AccordionItem(props: AccordionItemProps) {
const [split, rest] = splitProps(props, ["class", "classList"])
function AccordionItem(props: AccordionItemProps & { expanded?: boolean }) {
const [split, rest] = splitProps(props, ["class", "classList", "expanded"])
return (
<Kobalte.Item
{...rest}
data-slot="accordion-item"
classList={{
...(split.classList ?? {}),
[split.class ?? ""]: !!split.class,
}}
/>
<AccordionItemContext.Provider value={() => split.expanded ?? false}>
<Kobalte.Item
{...rest}
data-slot="accordion-item"
classList={{
...(split.classList ?? {}),
[split.class ?? ""]: !!split.class,
}}
/>
</AccordionItemContext.Provider>
)
}
@@ -84,9 +89,25 @@ function AccordionContent(props: ParentProps<AccordionContentProps>) {
)
}
export interface AccordionArrowProps extends ComponentProps<"div"> {
expanded?: boolean
}
function AccordionArrow(props: AccordionArrowProps = {}) {
const [local, rest] = splitProps(props, ["expanded"])
const contextExpanded = useContext(AccordionItemContext)
const isExpanded = () => local.expanded ?? contextExpanded?.() ?? false
return (
<div data-slot="accordion-arrow" {...rest}>
<MorphChevron expanded={isExpanded()} />
</div>
)
}
export const Accordion = Object.assign(AccordionRoot, {
Item: AccordionItem,
Header: AccordionHeader,
Trigger: AccordionTrigger,
Content: AccordionContent,
Arrow: AccordionArrow,
})

View File

@@ -106,10 +106,11 @@
[data-component="user-message"] [data-slot="user-message-text"] {
max-height: var(--user-message-collapsed-height, 64px);
transition: max-height 200ms cubic-bezier(0.25, 0, 0.5, 1);
}
[data-component="user-message"][data-expanded="true"] [data-slot="user-message-text"] {
max-height: none;
max-height: 2000px;
}
[data-component="user-message"][data-can-expand="true"] [data-slot="user-message-text"] {
@@ -141,17 +142,6 @@
background: transparent;
cursor: pointer;
color: var(--text-weak);
[data-slot="icon-svg"] {
transition: transform 0.15s ease;
}
}
[data-component="user-message"][data-expanded="true"]
[data-slot="user-message-text"]
[data-slot="user-message-expand"]
[data-slot="icon-svg"] {
transform: rotate(180deg);
}
[data-component="user-message"] [data-slot="user-message-text"] [data-slot="user-message-expand"]:hover {
@@ -457,6 +447,7 @@
gap: 16px;
align-items: center;
justify-content: flex-end;
color: var(--icon-base);
}
[data-slot="session-turn-accordion-content"] {

View File

@@ -548,7 +548,7 @@ export function SessionTurn(
data-slot="session-turn-collapsible-trigger-content"
variant="ghost"
size="small"
onClick={props.onStepsExpandedToggle ?? (() => {})}
onClick={props.onStepsExpandedToggle ?? (() => { })}
aria-expanded={props.stepsExpanded}
>
<Switch>

View File

@@ -33,8 +33,10 @@
@import "../components/markdown.css" layer(components);
@import "../components/message-part.css" layer(components);
@import "../components/message-nav.css" layer(components);
@import "../components/morph-chevron.css" layer(components);
@import "../components/popover.css" layer(components);
@import "../components/progress-circle.css" layer(components);
@import "../components/reasoning-icon.css" layer(components);
@import "../components/radio-group.css" layer(components);
@import "../components/resize-handle.css" layer(components);
@import "../components/select.css" layer(components);