import React, { useState } from "react"; import type { Meta, StoryObj } from "@storybook/react"; import { CopilotChatInput, CopilotChatConfigurationProvider, type ToolsMenuItem, } from "@copilotkit/react-core/v2"; const meta = { title: "UI/CopilotChatInput", component: CopilotChatInput, tags: ["autodocs"], decorators: [ (Story) => (
), ], parameters: { layout: "fullscreen", docs: { description: { component: ` The CopilotChatInput component provides a streamlined chat entry experience with a persistent add menu trigger that sits beside the text area. When the message grows beyond a single row, the textarea automatically moves above the controls to preserve layout. ## Key Features - 📝 Auto-resizing textarea with configurable \`maxRows\` - ➕ Dedicated add menu button that opens attachments or custom actions - 🎤 Voice transcription mode with audio recorder - 🎨 Fully themable through class overrides or slot replacements - ♿ Keyboard accessible and screen-reader friendly ## Basic Usage \`\`\`tsx import { CopilotChatInput, CopilotChatConfigurationProvider } from '@copilotkit/react-core/v2'; function ChatComponent() { return ( console.log('Message:', value)} onAddFile={() => console.log('Add file')} /> ); } \`\`\` ## Customization The component supports deep customization via: - **Slots** for the textarea, send button, add menu button, and audio recorder - **Render props** to compose your own layout while reusing internal primitives - **Props** such as \`toolsMenu\` for declarative menu configuration - **Styling overrides** through Tailwind-compatible class names `, }, }, }, argTypes: { mode: { control: { type: "radio" }, options: ["input", "transcribe"], description: "Select between text entry and transcription modes", table: { type: { summary: "'input' | 'transcribe'" }, defaultValue: { summary: "input" }, category: "Behavior", }, }, toolsMenu: { description: "Menu configuration rendered inside the add button dropdown", table: { type: { summary: "(ToolsMenuItem | '-')[]" }, category: "Features", }, }, addMenuButton: { description: "Slot override or class override for the add menu trigger", table: { type: { summary: "SlotValue" }, category: "Customization", }, }, sendButton: { description: "Slot override for the send button", table: { type: { summary: "SlotValue" }, category: "Customization", }, }, textArea: { description: "Props or overrides for the textarea slot", table: { type: { summary: "SlotValue" }, category: "Configuration", }, }, value: { control: { type: "text" }, description: "Controlled input value", table: { type: { summary: "string" }, category: "Data", }, }, onStartTranscribe: { action: "startTranscribe", description: "Invoked when transcription mode starts", table: { type: { summary: "() => void" }, category: "Events", }, }, onCancelTranscribe: { action: "cancelTranscribe", description: "Invoked when transcription mode is cancelled", table: { type: { summary: "() => void" }, category: "Events", }, }, onFinishTranscribe: { action: "finishTranscribe", description: "Invoked when transcription mode completes", table: { type: { summary: "() => void" }, category: "Events", }, }, onAddFile: { action: "addFile", description: "Called when the default add menu item is selected", table: { type: { summary: "() => void" }, category: "Events", }, }, onSubmitMessage: { action: "submit", description: "Called when the send button or Enter submits a message", table: { type: { summary: "(value: string) => void" }, category: "Events", }, }, }, args: { onStartTranscribe: () => console.log("Transcribe started"), onCancelTranscribe: () => console.log("Transcribe cancelled"), onFinishTranscribe: () => console.log("Transcribe completed"), onAddFile: () => console.log("Add file clicked"), onSubmitMessage: (value: string) => console.log(`Message sent: ${value}`), }, } satisfies Meta; export default meta; type Story = StoryObj; export const Default: Story = { parameters: { docs: { description: { story: "Default configuration with the add menu enabled and empty input.", }, source: { code: ``, }, }, }, }; export const WithMenuItems: Story = { args: { toolsMenu: [ { label: "Insert template", action: () => alert("Template inserted"), }, "-", { label: "Advanced", items: [ { label: "Summarize selection", action: () => alert("Summarize action"), }, { label: "Tag teammate", action: () => alert("Tagging teammate"), }, ], }, ] as (ToolsMenuItem | "-")[], }, parameters: { docs: { description: { story: "Demonstrates configuring nested items inside the add menu dropdown.", }, source: { code: ` alert("Template inserted") }, "-", { label: "Advanced", items: [ { label: "Summarize selection", action: () => alert("Summarize action") }, { label: "Tag teammate", action: () => alert("Tagging teammate") } ] } ]} />`, }, }, }, }; export const TranscribeMode: Story = { args: { mode: "transcribe", }, parameters: { docs: { description: { story: "Shows the audio recorder interface with cancel/finish controls in transcription mode.", }, source: { code: ``, }, }, }, }; export const CustomButtons: Story = { args: { sendButton: (props: React.ButtonHTMLAttributes) => ( ), addMenuButton: { className: "border border-indigo-200 bg-white text-indigo-500 hover:bg-indigo-50", }, }, parameters: { docs: { description: { story: "Overrides the send button with a custom component and tweaks the add menu button styling via slot props.", }, source: { code: ` ( )} addMenuButton={{ className: "border border-indigo-200 bg-white text-indigo-500 hover:bg-indigo-50" }} />`, }, }, }, }; export const PrefilledText: Story = { args: { value: "Hello, this is a prefilled message!", }, parameters: { docs: { description: { story: "Illustrates controlled usage by supplying a preset value to the textarea.", }, source: { code: ``, }, }, }, }; export const ExpandedTextarea: Story = { args: { value: "This is a longer message that will cause the textarea to expand to multiple rows.\n\nThe textarea remains beside the add button until a wrap occurs, then moves above the controls.", textArea: { maxRows: 10, }, }, parameters: { docs: { description: { story: "Demonstrates automatic multiline layout when the message spans multiple rows.", }, source: { code: ``, }, }, }, }; export const CustomStyling: Story = { decorators: [ (Story) => ( <> ), ], args: { className: "custom-chat-input", addMenuButton: { className: "border border-indigo-300 bg-white text-indigo-600", }, sendButton: { className: "bg-indigo-500 text-white hover:bg-indigo-600", }, }, parameters: { docs: { description: { story: "Applies custom classes to the container and key slots to achieve a distinct visual style.", }, source: { code: ``, }, }, }, }; export const CustomLayout: Story = { render: (args) => ( {({ textArea, sendButton, addMenuButton, isMultiline }) => (
{isMultiline ? "Multiline message" : "Single line message"} {addMenuButton}
{textArea}
{sendButton}
)}
), parameters: { docs: { description: { story: "Uses the render prop API to compose a custom layout while still leveraging the provided slots.", }, source: { code: ` {({ textArea, sendButton, addMenuButton, isMultiline }) => (
{isMultiline ? "Multiline message" : "Single line message"} {addMenuButton}
{textArea}
{sendButton}
)}
`, }, }, }, }; export const ControlledInputExample: Story = { render: (args) => { const [value, setValue] = useState("Draft message ready to send."); return ( { alert(`Submitted: ${submitted}`); setValue(""); }} /> ); }, parameters: { docs: { description: { story: "Showcases a controlled input pattern with external state management.", }, source: { code: `const [value, setValue] = useState("Draft message ready to send."); { alert(\`Submitted: \${submitted}\`); setValue(""); }} />`, }, }, }, };