feat: add customizable input layout with slot props and reusable sub-components#88
feat: add customizable input layout with slot props and reusable sub-components#88
Conversation
…components Add prepend, append, and actions slot props to ChatInput for flexible layout customization. Each slot accepts a ReactNode or a render function that receives ChatInputRenderContext (isLoading, disabled, message, sendMessage, stopMessage). Extract SendButton and StopButton as standalone exported components so users can compose custom action layouts while reusing the default themed buttons. Add new Storybook demos: model selector, toolbar, custom actions, and full custom layout combining all slots. https://claude.ai/code/session_01ATC3T4KBTV5pXpzDTL5Zz7
Add 'before' and 'after' options to actionsPlacement prop for placing action buttons to the left or right of the editor on the same row. This complements the existing 'inline', 'top', and 'bottom' placements. Add corresponding theme keys (actions.before, actions.after) and Storybook demos (ActionsAfter, ActionsBefore). https://claude.ai/code/session_01ATC3T4KBTV5pXpzDTL5Zz7
There was a problem hiding this comment.
Pull request overview
This PR enhances ChatInput customization by adding slot-style props (prepend, append, actions) and flexible actionsPlacement options, while extracting reusable themed SendButton/StopButton components and adding Storybook demos for various layouts.
Changes:
- Add
prepend/append/actionsslot props (ReactNode or render function viaChatInputRenderContext) andactionsPlacementtoChatInput. - Extract and export reusable
SendButtonandStopButtoncomponents. - Extend the theme shape to support prepend/append wrappers and non-inline action placements; add Storybook demos for custom layouts.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| stories/CustomInputLayout.stories.tsx | Adds new Storybook demos showcasing custom input layouts and action placements. |
| src/theme.ts | Extends ChatTheme.input for prepend/append and action placement variants. |
| src/ChatInput/ChatInput.tsx | Implements slot rendering, action placement logic, and exports SendButton/StopButton + render context types. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
| @@ -0,0 +1,611 @@ | |||
| import { Meta, StoryFn } from '@storybook/react'; | |||
| import { useState, useCallback } from 'react'; | |||
There was a problem hiding this comment.
useCallback is imported but never used in this story file. Please remove the unused import to keep lint/TS clean.
| import { useState, useCallback } from 'react'; | |
| import { useState } from 'react'; |
| return ( | ||
| <Button | ||
| title="Send" | ||
| className={cn(theme.input.actions.send, className)} |
There was a problem hiding this comment.
SendButton reads theme.input.actions.send directly from ChatContext, but ChatContext's default value has no theme. Since SendButton is now exported for reuse, using it outside a <Chat> provider will throw at runtime. Consider falling back to chatTheme when theme is undefined (as other components do), or require a theme prop and/or throw a clearer error when missing context.
| return ( | |
| <Button | |
| title="Send" | |
| className={cn(theme.input.actions.send, className)} | |
| const sendClassName = theme?.input?.actions?.send; | |
| return ( | |
| <Button | |
| title="Send" | |
| className={cn(sendClassName, className)} |
| {resolvedPrepend && ( | ||
| <div className={cn(theme.input.prepend)}>{resolvedPrepend}</div> | ||
| )} |
There was a problem hiding this comment.
{resolvedPrepend && ...} will skip rendering valid ReactNode values like 0 or an empty string. Prefer an explicit null/undefined check (e.g. resolvedPrepend != null) so all valid slot content renders correctly.
| {resolvedAppend && ( | ||
| <div className={cn(theme.input.append)}>{resolvedAppend}</div> | ||
| )} |
There was a problem hiding this comment.
{resolvedAppend && ...} has the same truthiness issue as prepend and will not render valid ReactNode values like 0/empty string. Use an explicit null/undefined check instead of a truthy check.
| input: [ | ||
| 'w-full border rounded-3xl px-3 py-2 pr-16 text-gray-500 border-gray-200 hover:bg-blue-100 hover:border-blue-500 after:hidden after:mx-10! bg-white [&>textarea]:w-full [&>textarea]:flex-none', | ||
| 'dark:border-gray-700/50 dark:text-gray-200 dark:bg-gray-950 dark:hover:bg-blue-950/40' | ||
| ].join(' '), |
There was a problem hiding this comment.
theme.input.input includes a hard-coded pr-16 to reserve space for inline actions, but this class is applied for all actionsPlacement values. When actions are placed top/bottom/before/after, this leaves unnecessary right padding on the editor. Consider making the reserved padding conditional (only for actionsPlacement === 'inline') or splitting the theme into separate classes for inline vs non-inline layouts.
The Cloudflare build uses pnpm with frozen-lockfile by default. The lockfile was out of sync (stale zod version range and removed devDeps). https://claude.ai/code/session_01ATC3T4KBTV5pXpzDTL5Zz7
Deploying reachat-storybook with
|
| Latest commit: |
ed00156
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://6c6d67da.reachat-storybook.pages.dev |
| Branch Preview URL: | https://claude-customizable-input-la.reachat-storybook.pages.dev |
- Remove unused useCallback import from stories - Add theme fallback for SendButton/StopButton so they work safely outside a <Chat> provider (falls back to default theme) - Fix truthy slot checks: use != null instead of && to correctly render falsy ReactNode values like 0 or empty strings - Remove hardcoded pr-16 from input theme; apply it conditionally only when actionsPlacement is 'inline' to avoid wasted padding for top/bottom/before/after placements https://claude.ai/code/session_01ATC3T4KBTV5pXpzDTL5Zz7
Add prepend, append, and actions slot props to ChatInput for flexible layout
customization. Each slot accepts a ReactNode or a render function that receives
ChatInputRenderContext (isLoading, disabled, message, sendMessage, stopMessage).
Extract SendButton and StopButton as standalone exported components so users can
compose custom action layouts while reusing the default themed buttons.
Add new Storybook demos: model selector, toolbar, custom actions, and full
custom layout combining all slots.
https://claude.ai/code/session_01ATC3T4KBTV5pXpzDTL5Zz7