ChatLauncher
A floating chat button in a corner of the page that opens a chat panel. The panel is not modal: the page stays interactive, Escape closes it while focus is inside, and focus returns to the button. The chat stays mounted while the panel is closed (keepMounted, true by default), so the conversation and a running stream survive closing. Place it with position (bottom-right or bottom-left) and offset (24px, a number or { x, y }); size the panel with panelWidth (380) and panelHeight (640), both clamped to the viewport. When the panel does not fit, or the available width is below fullScreenBreakpoint (520px), it opens full screen and page scroll is frozen (mobileFullScreen). unreadCount shows a badge on the closed button. Open state can be controlled with opened / onOpenedChange or left to defaultOpened. It renders in a portal by default; set withinPortal={false} to keep it inside a container. To add the chat to a page that is not a React app, or to isolate it from the page CSS, use mountChatLauncher(target, element, options): it renders into a Shadow DOM with its own MantineProvider, injects the stylesheets you pass in styles or styleUrls, and returns unmount. See Embedding the launcher. The previews render the launcher with withinPortal={false} inside a page frame, so it measures the frame instead of the window.
Pass actions to turn the first click into a fan of round buttons that leave the launcher one after another: chat, a call back, a demo, an email. The kit ships no brand icons and no links of its own — every action carries its own icon, label, color and either onClick or href, so the row stays yours. An action with opensChat opens the panel, the others only call back and hand the focus back to the button. While the fan is open the button turns into a close button and the unread badge steps aside; a click outside, Escape or opening the chat closes the fan, and Escape moves the focus only when it was already inside the launcher. A disabled action stays a button, never a link. actionsMotion sends them out one after another (sequence) or almost at once (together); actionSize, actionGap and buttonSize set the diameters and the spacing. pulse draws slow rings around the closed button, and altIcon with iconAnimation lets the button alternate between the icon and an avatar: swap fades the icon out and the avatar in, flip turns the button over, cover grows the avatar over the icon. Everything stays still under prefers-reduced-motion and while the panel is open, wherever the panel was opened from.
$pnpm add @sinups/ai-kit @mantine/core @mantine/hooks @tabler/icons-react