MConline
Maya Chen
Acme reports that triage keeps tagging their billing questions as bugs. Anyone free to look before their 5pm sync?
Slack-style workspace with channels, unread badges, presence, reactions, and a side thread for replies.
First install and activate one skin. Core deliberately contains no visual token defaults.
npx shadcn@latest add https://control-ui.dev/r/team-chat-block.json"use client";import { useState } from "react";import { TeamChatBlock } from "@/components/control-ui/blocks/team-chat";import { ChatComposer, ChatComposerShell, ChatComposerSubmit, ChatComposerTextarea, ChatComposerToolbar,} from "@/components/control-ui/chat-composer";import { ChatLayout, ChatLayoutDescription, ChatLayoutHeader, ChatLayoutTitle, ChatThread } from "@/components/control-ui/chat-layout";import { ChatMessage, ChatMessageAuthor, ChatMessageAvatar, ChatMessageBody, ChatMessageContent, ChatMessageHeader, ChatMessageRow, ChatMessageTime,} from "@/components/control-ui/chat-message";import { Sidebar, SidebarContent, SidebarGroup, SidebarGroupLabel, SidebarMenu, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem,} from "@/components/control-ui/ui/sidebar";type Message = { id: string; author: string; sentAt: string; text: string };export default function TeamChatPage({ messages, onSend }: { messages: Message[]; onSend: (text: string) => Promise<void> }) { const [draft, setDraft] = useState(""); return ( <TeamChatBlock className="h-svh" sidebar={ <Sidebar> <SidebarContent> <SidebarGroup> <SidebarGroupLabel>Channels</SidebarGroupLabel> <SidebarMenu> <SidebarMenuItem> <SidebarMenuButton isActive>#support</SidebarMenuButton> <SidebarMenuBadge>3</SidebarMenuBadge> </SidebarMenuItem> </SidebarMenu> </SidebarGroup> </SidebarContent> </Sidebar> } > <ChatLayout chrome="embedded" className="h-full"> <ChatLayoutHeader> <ChatLayoutTitle render={(props) => <h1 {...props} />}>#support</ChatLayoutTitle> <ChatLayoutDescription>Customer conversations</ChatLayoutDescription> </ChatLayoutHeader> <ChatThread composer={ <ChatComposer value={draft} onValueChange={setDraft} onSubmit={async ({ value, clear }) => { await onSend(value); clear(); }} > <ChatComposerShell> <ChatComposerTextarea placeholder="Message #support" /> <ChatComposerToolbar> <ChatComposerSubmit>Send</ChatComposerSubmit> </ChatComposerToolbar> </ChatComposerShell> </ChatComposer> } > {messages.map((message) => ( <ChatMessage key={message.id} from="participant" layout="flat" authorLabel={message.author}> <ChatMessageRow> <ChatMessageAvatar>{message.author.slice(0, 1)}</ChatMessageAvatar> <ChatMessageBody> <ChatMessageHeader> <ChatMessageAuthor>{message.author}</ChatMessageAuthor> <ChatMessageTime dateTime={message.sentAt}>{new Date(message.sentAt).toLocaleTimeString()}</ChatMessageTime> </ChatMessageHeader> <ChatMessageContent>{message.text}</ChatMessageContent> </ChatMessageBody> </ChatMessageRow> </ChatMessage> ))} </ChatThread> </ChatLayout> </TeamChatBlock> );}This block’s recipe and private support files. Public dependencies stay linked above.
src/registry/blocks/control-ui/team-chat.tsx"use client";import type { ComponentProps, ReactNode } from "react";import { cn } from "@/components/control-ui/lib/cn";import { SidebarInset, SidebarProvider } from "@/components/control-ui/ui/sidebar";export type TeamChatBlockProps = Omit<ComponentProps<typeof SidebarProvider>, "children"> & { sidebar: ReactNode; children: ReactNode; thread?: ReactNode; threadLabel?: string;};export function TeamChatBlock({ sidebar, children, thread, threadLabel = "Thread", className, ...props }: TeamChatBlockProps) { const threadIsOpen = thread !== undefined && thread !== null; return ( <SidebarProvider className={cn("bg-background text-foreground", className)} {...props}> {sidebar} <SidebarInset className="@container/team-chat flex h-full min-h-0 min-w-0 flex-row"> <div className={cn("flex min-h-0 min-w-0 flex-1 flex-col", threadIsOpen && "@max-3xl/team-chat:hidden")}>{children}</div> {threadIsOpen ? ( <aside aria-label={threadLabel} className="flex min-h-0 w-full min-w-0 flex-col border-border @3xl/team-chat:w-[min(26rem,45%)] @3xl/team-chat:shrink-0 @3xl/team-chat:border-s" > {thread} </aside> ) : null} </SidebarInset> </SidebarProvider> );}Customer conversations that need a second pair of eyes