crucıble

Steward

In-app support messenger block: a round launcher with a Tally unread badge that morphs (shared layout) into a 380×600 panel — a full-screen sheet on phones — with Home / Messages / Help tabs. Home has a team avatar stack, reply-time line, system-status strip, highlighted article search, recent conversation and a message CTA. Messages pushes from a thread list into a role="log" conversation with grouped bubbles, typing dots, delivered/read ticks, attachment chips, a hand-off card and a Sluice composer with optimistic send + retry. Help pushes categories → article reader with "Was this helpful?" that collapses into a thanks line. ref.receive() types then delivers, bouncing the launcher once when closed. Escape closes and restores focus; crossfades under reduced motion.

motionpro

Installation

CLI

npx shadcn@latest add @crucible/steward

Manual — install dependencies, then copy the source

npm install motion

Props

PropTypeDefaultDescription
teamStewardTeama seeded three-person teamSupport team: avatar stack, greeting and reply time.
statusStewardStatus | nullall systems operationalSystem-status strip on Home. Pass null to hide it.
articlesStewardArticle[]seeded articlesHelp-center articles: searchable on Home and Help, grouped by category.
conversationsStewardConversation[]seeded sampleInitial conversations (the widget owns them afterwards). Passing a new array resets them.
onSend(text: string, conversationId: string) => void | Promise<unknown>Fired when the visitor sends a message. The bubble appears optimistically; return a Promise — resolve marks it delivered, reject marks it failed with Retry.
onFeedback(articleId: string, helpful: boolean) => void | Promise<unknown>Fired from "Was this helpful?" on an article. A returned Promise holds the buttons pending; reject shows a retry hint.
inlinebooleanfalseRender the panel in place, always open, with no launcher.
defaultOpenbooleanfalseStart open (launcher mode).
defaultTabStewardTab"home"Tab shown first.
onOpenChange(open: boolean) => voidFired when the panel opens or closes.
position"fixed" | "absolute""fixed""fixed" pins the launcher to the viewport; "absolute" pins it to the nearest positioned ancestor (e.g. a preview frame).
launcherReact.ReactNodeCustom launcher icon (replaces the chat glyph).
labelstring"Support"Accessible name of the panel.
accentColorstring"#38bdf8"The single accent: launcher, your bubbles, links, focus rings, active tab. Hex gets auto black/white ink.
refReact.Ref<StewardHandle>Imperative handle — open(tab?), close(), receive(msg), replay().
classNamestringClasses for the root (the panel in inline mode).
panelClassNamestringClasses for the panel, e.g. a width or height.

Honors prefers-reduced-motion with a designed static fallback, and passes className through.