npx shadcn@latest add https://sdk-components.thesqd.com/r/ai-thinking-card.jsonimport { AiThinkingCard } from "@/components/blocks/ai-thinking-card";import { AiThinkingCard } from "@/components/blocks/ai-thinking-card";
export function AssistantReply({ answer }: { answer?: string }) {
return (
<AiThinkingCard
title="Squad AI"
states={["Thinking", "Reading the brief", "Drafting a reply"]}
answer={answer}
/>
);
}
AiThinkingCard (card-surface + t-resize — it tweens its own height)
├── .t-stagger × 3 (mark, title, toggle — each reveals on its own mount)
├── .t-icon-swap (one grid cell, two children cross-fading)
│ ├── LoadingState (the syncing pill's loader — pattern="drive", sm)
│ └── icon (Phosphor, duotone — what the dots become)
├── title | .t-think (the state while it thinks, the title once it answers)
│ └── .t-think (rotating status line)
│ ├── .t-think-sizer (hidden, holds the longest state — fixes the width)
│ └── .t-think-text (the visible line; shimmer on ::before)
├── aside (right of the heading line)
├── collapse toggle (Button + caret — `collapsible`, once there is content)
├── chip (`[label](chip:type/id)` → hover card, via `resolveChip`)
├── .t-stream-w × n (the answer — one span per word, or per block with `markdown`)
└── footer (anything under the answer)Your fall series still needs:
The stage graphic sets the type treatment everything else inherits.
Set the kit with npm run kit fall.
moved the stage graphic to , which puts it after locks its type.
Everything else on that project still lands in the same week.
Your fall series has three pieces left: the stage graphic, the sermon slide set, and the social cutdowns. The stage graphic is the long pole, so I would start there and let the slides follow its type treatment.
AiThinkingCard
| Prop | Type | Default | Description |
|---|---|---|---|
states | readonly string[] | a generic set | Status lines to rotate through while thinking. Each one trails off with an ellipsis unless it already ends in punctuation, so pass them clean. The longest sets the width of the box, so the line never resizes as it swaps. |
stateDuration | number | 2000 | How long each state holds before the next swaps in, ms. The swap itself rides `--think-swap` / `--think-gap` on top of it. |
icon | ReactNode | a duotone Sparkle | The icon the dots become when the thinking ends — a Phosphor icon at `weight="duotone"`, the SDK's icon voice. The two share one grid cell and cross-fade, so the loader does not vanish and leave the icon to appear. |
title | ReactNode | — | Heading for the card. It waits for the answer — while the card is thinking the status line IS the line, and the title takes its place when the state goes. |
aside | ReactNode | — | Right of the heading — a model name, a stop button, an elapsed timer. The leading slot is icon-sized, so anything that is not an icon belongs here. |
answer | string | — | The answer. Passing it ends the thinking: the card grows to fit and the words resolve in. Changing it streams the new one. |
thinking | boolean | `answer` is empty | Force the thinking state on or off, for work you are tracking yourself. |
wordDelay | number | --stream-gap (60) | Gap between streamed words, ms. |
markdown | boolean | false | Render `answer` as markdown. The reveal then cascades a block at a time instead of a word at a time, on the same `wordDelay` clock. |
resolveChip | (ref: ChipRef) => ReactNode | — | Resolve a `[label](chip:type/id)` reference into what its popover shows. `ChipRef` is `{ type, id, label }`. Return nothing and the reference renders as plain text. Needs `markdown`. |
chipIcon | (ref: ChipRef) => ReactNode | — | Optional glyph inside the chip, ahead of its label. |
tone | LoadingState tone | "muted" | Brand tint for the dots — brand, violet, coral, amber, magenta, plum, muted. |
size | "sm" | "default" | "lg" | "sm" | Dot scale. `sm` is the syncing pill's, which this loader borrows so the two read as one product. |
collapsible | boolean | true | Give the card a collapse toggle. It only appears once there is something to collapse — an `answer` or a `footer` — so a card that is still thinking has no control on it. Collapsing rides the same height tween as the answer arriving. |
defaultCollapsed | boolean | false | Where an uncontrolled card starts. |
collapsed | boolean | — | Controlled collapse state. Pair with `onCollapsedChange`. |
onCollapsedChange | (collapsed: boolean) => void | — | Fires whenever the card collapses or expands. |
collapseLabel | string | "Toggle details" | Accessible name for the toggle. |
footer | ReactNode | — | Anything below the answer — sources, actions, a follow-up field. |