Skip to content

Commit 8a40663

Browse files
Improving chat input aesthetics (dyad-sh#2561)
<!-- devin-review-badge-begin --> --- <a href="https://app.devin.ai/review/dyad-sh/dyad/pull/2561" target="_blank"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1"> <img src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1" alt="Open with Devin"> </picture> </a> <!-- devin-review-badge-end --> <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Refreshes the chat input UI for a cleaner look and consistent controls. Adds per‑mode icons and clearer visual states across inputs, pickers, and actions. - **UI Improvements** - Unified control sizing (h-7, text-xs, rounded-lg); vertical align center; input row aligns to end; removed spacer divs. - Borderless triggers for Mode/Model/Tools/Pro with subtle hover; smaller icons; muted “Model:”; mode chips show icons for all modes; Ask/Plan get color accents. - Input boxes rounded-2xl with focus-within ring; home input gets hover border; context banner rounded-t-2xl. - Send/Cancel use text-color hovers with clearer disabled; Cancel turns destructive on hover; Auxiliary Actions is circular with gentle hover scale. - Increased input padding and 15px text with repositioned placeholder; trimmed outer and MessagesList padding for tighter alignment; home send icon switched to SendHorizontal. <sup>Written for commit 301875e. Summary will update on new commits.</sup> <!-- End of auto-generated description by cubic. --> --------- Co-authored-by: Will Chen <willchen90@gmail.com>
1 parent 62fb900 commit 8a40663

13 files changed

Lines changed: 93 additions & 56 deletions

rules/git-workflow.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ The stashed changes will be automatically merged back after the rebase completes
8282
- If both sides of a conflict have valid imports/hooks, keep both and remove any duplicate constant redefinitions
8383
- When rebasing documentation/table conflicts (e.g., workflow README tables), prefer keeping **both** additions from HEAD and upstream - merge new rows/content from both branches rather than choosing one side
8484
- **Complementary additions**: When both sides added new sections at the end of a file (e.g., both added different documentation tips), keep both sections rather than choosing one — they're not truly conflicting, just different additions
85+
- **React component wrapper conflicts**: When rebasing UI changes that conflict on wrapper div classes (e.g., `flex items-start space-x-2` vs `flex items-end gap-1`), keep the newer styling from the incoming commit but preserve any functional components (like dialogs or modals) that exist in HEAD but not in the incoming change
8586

8687
## Rebasing with uncommitted changes
8788

src/components/ChatInputControls.tsx

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export function ChatInputControls({
2323
(settings?.selectedChatMode === "build" && enabledMcpServersCount > 0);
2424

2525
return (
26-
<div className="flex">
26+
<div className="flex items-center">
2727
<ChatModeSelector />
2828
{showMcpToolsPicker && (
2929
<>
@@ -33,15 +33,8 @@ export function ChatInputControls({
3333
)}
3434
<div className="w-1.5"></div>
3535
<ModelPicker />
36-
<div className="w-1.5"></div>
3736
<ProModeSelector />
38-
<div className="w-1"></div>
39-
{showContextFilesPicker && (
40-
<>
41-
<ContextFilesPicker />
42-
<div className="w-0.5"></div>
43-
</>
44-
)}
37+
{showContextFilesPicker && <ContextFilesPicker />}
4538
</div>
4639
);
4740
}

src/components/ChatModeSelector.tsx

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { toast } from "sonner";
2222
import { LocalAgentNewChatToast } from "./LocalAgentNewChatToast";
2323
import { useAtomValue } from "jotai";
2424
import { chatMessagesByIdAtom } from "@/atoms/chatAtoms";
25+
import { Hammer, Bot, MessageCircle, Lightbulb } from "lucide-react";
2526

2627
function NewBadge() {
2728
return (
@@ -95,6 +96,22 @@ export function ChatModeSelector() {
9596
return "Build";
9697
}
9798
};
99+
100+
const getModeIcon = (mode: ChatMode) => {
101+
switch (mode) {
102+
case "build":
103+
case "agent":
104+
return <Hammer size={14} />;
105+
case "ask":
106+
return <MessageCircle size={14} />;
107+
case "local-agent":
108+
return <Bot size={14} />;
109+
case "plan":
110+
return <Lightbulb size={14} />;
111+
default:
112+
return <Hammer size={14} />;
113+
}
114+
};
98115
const isMac = detectIsMac();
99116

100117
return (
@@ -109,18 +126,25 @@ export function ChatModeSelector() {
109126
<MiniSelectTrigger
110127
data-testid="chat-mode-selector"
111128
className={cn(
112-
"h-6 w-fit px-1.5 py-0 text-xs-sm font-medium shadow-none gap-0.5",
113-
selectedMode === "build" ||
114-
selectedMode === "local-agent" ||
115-
selectedMode === "plan"
116-
? "bg-background hover:bg-muted/50 focus:bg-muted/50"
117-
: "bg-primary/10 hover:bg-primary/20 focus:bg-primary/20 text-primary border-primary/20 dark:bg-primary/20 dark:hover:bg-primary/30 dark:focus:bg-primary/30",
129+
"cursor-pointer w-fit px-2 py-0 text-xs font-medium border-none shadow-none gap-1 rounded-lg transition-colors",
130+
selectedMode === "build" || selectedMode === "local-agent"
131+
? "text-foreground/80 hover:text-foreground hover:bg-muted/60"
132+
: selectedMode === "ask"
133+
? "bg-purple-500/10 text-purple-600 hover:bg-purple-500/15 dark:bg-purple-500/15 dark:text-purple-400 dark:hover:bg-purple-500/20"
134+
: selectedMode === "plan"
135+
? "bg-blue-500/10 text-blue-600 hover:bg-blue-500/15 dark:bg-blue-500/15 dark:text-blue-400 dark:hover:bg-blue-500/20"
136+
: "text-foreground/80 hover:text-foreground hover:bg-muted/60",
118137
)}
119138
size="sm"
120139
/>
121140
}
122141
>
123-
<SelectValue>{getModeDisplayName(selectedMode)}</SelectValue>
142+
<SelectValue>
143+
<span className="flex items-center gap-1.5">
144+
{getModeIcon(selectedMode)}
145+
{getModeDisplayName(selectedMode)}
146+
</span>
147+
</SelectValue>
124148
</TooltipTrigger>
125149
<TooltipContent>
126150
{`Open mode menu (${isMac ? "\u2318 + ." : "Ctrl + ."} to toggle)`}
@@ -131,10 +155,11 @@ export function ChatModeSelector() {
131155
<SelectItem value="local-agent">
132156
<div className="flex flex-col items-start">
133157
<div className="flex items-center gap-1.5">
158+
<Bot size={14} className="text-muted-foreground" />
134159
<span className="font-medium">Agent v2</span>
135160
<NewBadge />
136161
</div>
137-
<span className="text-xs text-muted-foreground">
162+
<span className="text-xs text-muted-foreground ml-[22px]">
138163
Better at bigger tasks and debugging
139164
</span>
140165
</div>
@@ -143,10 +168,11 @@ export function ChatModeSelector() {
143168
<SelectItem value="plan">
144169
<div className="flex flex-col items-start">
145170
<div className="flex items-center gap-1.5">
171+
<Lightbulb size={14} className="text-blue-500" />
146172
<span className="font-medium">Plan</span>
147173
<NewBadge />
148174
</div>
149-
<span className="text-xs text-muted-foreground">
175+
<span className="text-xs text-muted-foreground ml-[22px]">
150176
Design before you build
151177
</span>
152178
</div>
@@ -155,13 +181,14 @@ export function ChatModeSelector() {
155181
<SelectItem value="local-agent" disabled={isQuotaExceeded}>
156182
<div className="flex flex-col items-start">
157183
<div className="flex items-center gap-1.5">
184+
<Bot size={14} className="text-muted-foreground" />
158185
<span className="font-medium">Basic Agent</span>
159186
<span className="text-xs text-muted-foreground">
160187
({isQuotaExceeded ? "0" : messagesRemaining}/5 remaining for
161188
today)
162189
</span>
163190
</div>
164-
<span className="text-xs text-muted-foreground">
191+
<span className="text-xs text-muted-foreground ml-[22px]">
165192
{isQuotaExceeded
166193
? "Daily limit reached"
167194
: "Try our AI agent for free"}
@@ -171,16 +198,22 @@ export function ChatModeSelector() {
171198
)}
172199
<SelectItem value="build">
173200
<div className="flex flex-col items-start">
174-
<span className="font-medium">Build</span>
175-
<span className="text-xs text-muted-foreground">
201+
<div className="flex items-center gap-1.5">
202+
<Hammer size={14} className="text-muted-foreground" />
203+
<span className="font-medium">Build</span>
204+
</div>
205+
<span className="text-xs text-muted-foreground ml-[22px]">
176206
Generate and edit code
177207
</span>
178208
</div>
179209
</SelectItem>
180210
<SelectItem value="ask">
181211
<div className="flex flex-col items-start">
182-
<span className="font-medium">Ask</span>
183-
<span className="text-xs text-muted-foreground">
212+
<div className="flex items-center gap-1.5">
213+
<MessageCircle size={14} className="text-purple-500" />
214+
<span className="font-medium">Ask</span>
215+
</div>
216+
<span className="text-xs text-muted-foreground ml-[22px]">
184217
Ask questions about the app
185218
</span>
186219
</div>

src/components/McpToolsPicker.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ export function McpToolsPicker() {
2525
return (
2626
<Popover open={isOpen} onOpenChange={setIsOpen}>
2727
<PopoverTrigger
28-
className="inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground h-8 px-2"
28+
className="inline-flex items-center justify-center whitespace-nowrap rounded-lg text-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 border-none bg-transparent shadow-none text-muted-foreground hover:text-foreground hover:bg-muted/60 h-7 px-1.5 cursor-pointer"
2929
data-testid="mcp-tools-button"
3030
title="Tools"
3131
>
32-
<Wrench className="size-4" />
32+
<Wrench className="size-3.5" />
3333
</PopoverTrigger>
3434
<PopoverContent
3535
className="w-120 max-h-[80vh] overflow-y-auto"

src/components/ModelPicker.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,14 +166,16 @@ export function ModelPicker() {
166166
return (
167167
<DropdownMenu open={open} onOpenChange={setOpen}>
168168
<DropdownMenuTrigger
169-
className="inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground h-8 max-w-[130px] px-1.5 text-xs-sm gap-2"
169+
className="inline-flex items-center justify-center whitespace-nowrap rounded-lg text-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 border-none bg-transparent shadow-none text-foreground/80 hover:text-foreground hover:bg-muted/60 h-7 max-w-[130px] px-2 gap-1.5 cursor-pointer"
170170
data-testid="model-picker"
171171
title={modelDisplayName}
172172
>
173173
<span className="truncate">
174174
{modelDisplayName === "Auto" && (
175175
<>
176-
<span className="text-xs text-muted-foreground">Model:</span>{" "}
176+
<span className="text-xs text-muted-foreground/70">
177+
Model:
178+
</span>{" "}
177179
</>
178180
)}
179181
{modelDisplayName}

src/components/ProModeSelector.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,11 @@ export function ProModeSelector() {
7171
<Tooltip>
7272
<TooltipTrigger
7373
render={
74-
<PopoverTrigger className="inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 border border-primary/50 bg-background shadow-sm hover:bg-primary/10 h-8 px-1.5 gap-1.5 shadow-primary/10 hover:shadow-md hover:shadow-primary/15" />
74+
<PopoverTrigger className="inline-flex items-center justify-center whitespace-nowrap rounded-lg text-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 border-none bg-transparent shadow-none text-primary/95 hover:text-primary hover:bg-primary/10 h-7 px-2 gap-1 cursor-pointer" />
7575
}
7676
>
77-
<Sparkles className="h-4 w-4 text-primary" />
78-
<span className="text-primary font-medium text-xs-sm">Pro</span>
77+
<Sparkles className="h-3.5 w-3.5" />
78+
<span className="font-medium">Pro</span>
7979
</TooltipTrigger>
8080
<TooltipContent>Configure Dyad Pro settings</TooltipContent>
8181
</Tooltip>

src/components/chat/AuxiliaryActionsMenu.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ export function AuxiliaryActionsMenu({
132132
<>
133133
<DropdownMenu open={isOpen} onOpenChange={setIsOpen}>
134134
<DropdownMenuTrigger
135-
className="inline-flex items-center justify-center whitespace-nowrap rounded-xl text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 hover:bg-muted bg-primary/10 text-primary cursor-pointer h-8 px-2"
135+
className="inline-flex items-center justify-center whitespace-nowrap rounded-full text-sm font-medium transition-all duration-200 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 hover:bg-primary/20 hover:scale-105 bg-primary/10 text-primary cursor-pointer h-8 w-8 mb-1"
136136
data-testid="auxiliary-actions-menu"
137137
>
138138
<Plus

src/components/chat/ChatInput.tsx

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ import { useCountTokens } from "@/hooks/useCountTokens";
9393
import { useChats } from "@/hooks/useChats";
9494
import { useRouter } from "@tanstack/react-router";
9595
import { showError as showErrorToast } from "@/lib/toast";
96+
import { cn } from "@/lib/utils";
9697

9798
const showTokenBarAtom = atom(false);
9899

@@ -526,7 +527,7 @@ export function ChatInput({ chatId }: { chatId?: number }) {
526527
{t("errorLoadingProposal", { message: proposalError.message })}
527528
</div>
528529
)}
529-
<div className="p-4" data-testid="chat-input-container">
530+
<div className="p-2 pt-0" data-testid="chat-input-container">
530531
{/* Show context limit banner above chat input for visibility */}
531532
{showBanner && tokenCountResult && (
532533
<ContextLimitBanner
@@ -535,9 +536,12 @@ export function ChatInput({ chatId }: { chatId?: number }) {
535536
/>
536537
)}
537538
<div
538-
className={`relative flex flex-col border border-border rounded-lg bg-(--background-lighter) shadow-sm ${
539-
isDraggingOver ? "ring-2 ring-blue-500 border-blue-500" : ""
540-
} ${showBanner ? "rounded-t-none border-t-0" : ""}`}
539+
className={cn(
540+
"relative flex flex-col border border-border rounded-2xl bg-(--background-lighter) transition-colors duration-200",
541+
"focus-within:border-primary/30 focus-within:ring-1 focus-within:ring-primary/20",
542+
isDraggingOver && "ring-2 ring-blue-500 border-blue-500",
543+
showBanner && "rounded-t-none border-t-0",
544+
)}
541545
onDragOver={handleDragOver}
542546
onDragLeave={handleDragLeave}
543547
onDrop={handleDrop}
@@ -698,7 +702,7 @@ export function ChatInput({ chatId }: { chatId?: number }) {
698702
onCancel={cancelPendingFiles}
699703
/>
700704

701-
<div className="flex items-start space-x-2 ">
705+
<div className="flex items-end gap-1">
702706
<LexicalChatInput
703707
value={inputValue}
704708
onChange={setInputValue}
@@ -717,7 +721,7 @@ export function ChatInput({ chatId }: { chatId?: number }) {
717721
<button
718722
onClick={handleCancel}
719723
aria-label={t("cancelGeneration")}
720-
className="px-2 py-2 mt-1 mr-1 hover:bg-(--background-darkest) text-(--sidebar-accent-fg) rounded-lg"
724+
className="px-2 py-2 mb-0.5 mr-1 text-muted-foreground hover:text-destructive rounded-lg transition-colors duration-150 cursor-pointer"
721725
/>
722726
}
723727
>
@@ -736,7 +740,7 @@ export function ChatInput({ chatId }: { chatId?: number }) {
736740
disableSendButton
737741
}
738742
aria-label={t("sendMessage")}
739-
className="px-2 py-2 mt-1 mr-1 hover:bg-(--background-darkest) text-(--sidebar-accent-fg) rounded-lg disabled:opacity-50"
743+
className="px-2 py-2 mb-0.5 mr-1 text-muted-foreground hover:text-primary rounded-lg transition-colors duration-150 disabled:opacity-30 disabled:hover:text-muted-foreground cursor-pointer disabled:cursor-default"
740744
/>
741745
}
742746
>
@@ -746,7 +750,7 @@ export function ChatInput({ chatId }: { chatId?: number }) {
746750
</Tooltip>
747751
)}
748752
</div>
749-
<div className="pl-2 pr-1 flex items-center justify-between pb-2">
753+
<div className="px-2 flex items-center justify-between pb-0.5 pt-0.5">
750754
<div className="flex items-center">
751755
<ChatInputControls showContextFilesPicker={false} />
752756
</div>

src/components/chat/ContextLimitBanner.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export function ContextLimitBanner({
5050

5151
return (
5252
<div
53-
className="mx-auto max-w-3xl px-3 py-1.5 rounded-t-md border-t border-l border-r border-amber-500/30 bg-amber-500/10 flex items-center justify-between gap-3 text-xs text-amber-600 dark:text-amber-500"
53+
className="mx-auto max-w-3xl px-3 py-1.5 rounded-t-2xl border-t border-l border-r border-amber-500/30 bg-amber-500/10 flex items-center justify-between gap-3 text-xs text-amber-600 dark:text-amber-500"
5454
data-testid="context-limit-banner"
5555
>
5656
<span className="flex items-center gap-1.5">

src/components/chat/HomeChatInput.tsx

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { SendIcon, StopCircleIcon } from "lucide-react";
1+
import { SendHorizontalIcon, StopCircleIcon } from "lucide-react";
22
import {
33
Tooltip,
44
TooltipTrigger,
@@ -20,6 +20,7 @@ import { LexicalChatInput } from "./LexicalChatInput";
2020
import { useChatModeToggle } from "@/hooks/useChatModeToggle";
2121
import { useTypingPlaceholder } from "@/hooks/useTypingPlaceholder";
2222
import { AuxiliaryActionsMenu } from "./AuxiliaryActionsMenu";
23+
import { cn } from "@/lib/utils";
2324

2425
export function HomeChatInput({
2526
onSubmit,
@@ -85,9 +86,12 @@ export function HomeChatInput({
8586
<>
8687
<div className="p-4" data-testid="home-chat-input-container">
8788
<div
88-
className={`relative flex flex-col space-y-2 border border-border rounded-lg bg-(--background-lighter) shadow-sm ${
89-
isDraggingOver ? "ring-2 ring-blue-500 border-blue-500" : ""
90-
}`}
89+
className={cn(
90+
"relative flex flex-col border border-border rounded-2xl bg-(--background-lighter) transition-colors duration-200",
91+
"hover:border-primary/30",
92+
"focus-within:border-primary/30 focus-within:ring-1 focus-within:ring-primary/20",
93+
isDraggingOver && "ring-2 ring-blue-500 border-blue-500",
94+
)}
9195
onDragOver={handleDragOver}
9296
onDragLeave={handleDragLeave}
9397
onDrop={handleDrop}
@@ -108,7 +112,7 @@ export function HomeChatInput({
108112
onCancel={cancelPendingFiles}
109113
/>
110114

111-
<div className="flex items-start space-x-2 ">
115+
<div className="flex items-end gap-1">
112116
<LexicalChatInput
113117
value={inputValue}
114118
onChange={setInputValue}
@@ -127,7 +131,7 @@ export function HomeChatInput({
127131
render={
128132
<button
129133
aria-label="Cancel generation (unavailable here)"
130-
className="px-2 py-2 mt-1 mr-1 text-(--sidebar-accent-fg) rounded-lg opacity-50 cursor-not-allowed"
134+
className="px-2 py-2 mb-0.5 mr-1 text-muted-foreground rounded-lg opacity-50 cursor-not-allowed transition-colors duration-150"
131135
/>
132136
}
133137
>
@@ -145,17 +149,17 @@ export function HomeChatInput({
145149
onClick={handleCustomSubmit}
146150
disabled={!inputValue.trim() && attachments.length === 0}
147151
aria-label="Send message"
148-
className="px-2 py-2 mt-1 mr-1 hover:bg-(--background-darkest) text-(--sidebar-accent-fg) rounded-lg disabled:opacity-50"
152+
className="px-2 py-2 mb-0.5 mr-1 text-muted-foreground hover:text-primary rounded-lg transition-colors duration-150 disabled:opacity-30 disabled:hover:text-muted-foreground cursor-pointer disabled:cursor-default"
149153
/>
150154
}
151155
>
152-
<SendIcon size={20} />
156+
<SendHorizontalIcon size={20} />
153157
</TooltipTrigger>
154158
<TooltipContent>Send message</TooltipContent>
155159
</Tooltip>
156160
)}
157161
</div>
158-
<div className="pl-2 pr-1 flex items-center justify-between pb-2">
162+
<div className="px-2 flex items-center justify-between pb-0.5 pt-0.5">
159163
<div className="flex items-center">
160164
<ChatInputControls showContextFilesPicker={false} />
161165
</div>

0 commit comments

Comments
 (0)