Skip to content

Commit f4bbe5f

Browse files
committed
πŸ€– refactor: Convert AIView to Tailwind CSS
- Remove @emotion/styled dependency from AIView.tsx - Replace 12 styled components with Tailwind utility classes - ViewContainer β†’ flex flex-1 flex-row with responsive media queries - ChatArea β†’ flex-1 min-w-[400px] flex flex-col - ViewHeader β†’ py-1 px-[15px] bg-[#252526] border-b - WorkspaceTitle β†’ font-semibold text-[#ccc] flex items-center gap-2 - WorkspacePath/WorkspaceName β†’ inline span with text truncation - TerminalIconButton β†’ bg-transparent hover:text-[#ccc] transition - OutputContainer/OutputContent β†’ flex-1 relative with overflow - EmptyState β†’ flex flex-col items-center justify-center - EditBarrier β†’ my-5 py-3 with repeating-linear-gradient border-image - JumpToBottomIndicator β†’ absolute bottom-2 with inline hover handlers - Use [@media(max-width:768px)]: arbitrary variants for responsive design - Maintain all interactive features (scrolling, barriers, resize, tooltips) - Code reduction: 649 β†’ 494 lines (24% reduction) All type checks passing, maintains feature parity
1 parent 6321016 commit f4bbe5f

File tree

1 file changed

+68
-223
lines changed

1 file changed

+68
-223
lines changed

β€Žsrc/components/AIView.tsxβ€Ž

Lines changed: 68 additions & 223 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, { useState, useCallback, useEffect, useRef } from "react";
2-
import styled from "@emotion/styled";
2+
import { cn } from "@/lib/utils";
33
import { MessageRenderer } from "./Messages/MessageRenderer";
44
import { InterruptedBarrier } from "./Messages/ChatBarrier/InterruptedBarrier";
55
import { StreamingBarrier } from "./Messages/ChatBarrier/StreamingBarrier";
@@ -30,189 +30,6 @@ import { TooltipWrapper, Tooltip } from "./Tooltip";
3030
import type { DisplayedMessage } from "@/types/message";
3131
import { useAIViewKeybinds } from "@/hooks/useAIViewKeybinds";
3232

33-
const ViewContainer = styled.div`
34-
flex: 1;
35-
display: flex;
36-
flex-direction: row;
37-
background: #1e1e1e;
38-
color: #d4d4d4;
39-
font-family: var(--font-monospace);
40-
font-size: 12px;
41-
overflow-x: auto;
42-
overflow-y: hidden;
43-
container-type: inline-size;
44-
45-
/* Mobile: Stack vertically */
46-
@media (max-width: 768px) {
47-
flex-direction: column;
48-
}
49-
`;
50-
51-
const ChatArea = styled.div`
52-
flex: 1;
53-
min-width: 400px; /* Reduced from 750px to allow narrower layout when Review panel is wide */
54-
display: flex;
55-
flex-direction: column;
56-
57-
/* Mobile: Remove min-width and take full width */
58-
@media (max-width: 768px) {
59-
min-width: 0;
60-
width: 100%;
61-
max-height: 100%;
62-
}
63-
`;
64-
65-
const ViewHeader = styled.div`
66-
padding: 4px 15px;
67-
background: #252526;
68-
border-bottom: 1px solid #3e3e42;
69-
display: flex;
70-
justify-content: space-between;
71-
align-items: center;
72-
73-
/* Mobile: Add padding for hamburger button and adjust spacing */
74-
@media (max-width: 768px) {
75-
padding: 8px 15px 8px 60px; /* Extra left padding for hamburger button */
76-
flex-wrap: wrap;
77-
gap: 8px;
78-
}
79-
`;
80-
81-
const WorkspaceTitle = styled.div`
82-
font-weight: 600;
83-
color: #cccccc;
84-
display: flex;
85-
align-items: center;
86-
gap: 8px;
87-
min-width: 0; /* Allow flex children to shrink */
88-
overflow: hidden;
89-
`;
90-
91-
const WorkspacePath = styled.span`
92-
font-family: var(--font-monospace);
93-
color: #888;
94-
font-weight: 400;
95-
font-size: 11px;
96-
white-space: nowrap;
97-
overflow: hidden;
98-
text-overflow: ellipsis;
99-
min-width: 0;
100-
`;
101-
102-
const WorkspaceName = styled.span`
103-
white-space: nowrap;
104-
overflow: hidden;
105-
text-overflow: ellipsis;
106-
min-width: 0;
107-
`;
108-
109-
const TerminalIconButton = styled.button`
110-
background: transparent;
111-
border: none;
112-
cursor: pointer;
113-
padding: 4px;
114-
display: flex;
115-
align-items: center;
116-
justify-content: center;
117-
color: #888;
118-
transition: color 0.2s;
119-
120-
&:hover {
121-
color: #ccc;
122-
}
123-
124-
svg {
125-
width: 16px;
126-
height: 16px;
127-
}
128-
`;
129-
130-
const OutputContainer = styled.div`
131-
flex: 1;
132-
position: relative;
133-
overflow: hidden;
134-
`;
135-
136-
const OutputContent = styled.div`
137-
height: 100%;
138-
overflow-y: auto;
139-
padding: 15px;
140-
white-space: pre-wrap;
141-
word-break: break-word;
142-
line-height: 1.5;
143-
`;
144-
145-
const EmptyState = styled.div`
146-
flex: 1;
147-
display: flex;
148-
flex-direction: column;
149-
align-items: center;
150-
justify-content: center;
151-
height: 100%;
152-
color: #6b6b6b;
153-
text-align: center;
154-
155-
h3 {
156-
margin: 0 0 10px 0;
157-
font-size: 16px;
158-
font-weight: 500;
159-
}
160-
161-
p {
162-
margin: 0;
163-
font-size: 13px;
164-
}
165-
`;
166-
167-
const EditBarrier = styled.div`
168-
margin: 20px 0;
169-
padding: 12px 15px;
170-
background: var(--color-editing-mode-alpha);
171-
border-bottom: 3px solid;
172-
border-image: repeating-linear-gradient(
173-
45deg,
174-
var(--color-editing-mode),
175-
var(--color-editing-mode) 10px,
176-
transparent 10px,
177-
transparent 20px
178-
)
179-
1;
180-
color: var(--color-editing-mode);
181-
font-size: 12px;
182-
font-weight: 500;
183-
text-align: center;
184-
`;
185-
186-
const JumpToBottomIndicator = styled.button`
187-
position: absolute;
188-
bottom: 8px;
189-
left: 50%;
190-
transform: translateX(-50%);
191-
padding: 4px 8px;
192-
background: hsl(from var(--color-assistant-border) h s l / 0.1);
193-
color: white;
194-
border: 1px solid hsl(from var(--color-assistant-border) h s l / 0.4);
195-
border-radius: 20px;
196-
font-size: 12px;
197-
font-weight: 500;
198-
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
199-
cursor: pointer;
200-
transition: all 0.2s ease;
201-
z-index: 100;
202-
font-family: var(--font-primary);
203-
backdrop-filter: blur(1px);
204-
205-
&:hover {
206-
background: hsl(from var(--color-assistant-border) h s l / 0.4);
207-
border-color: hsl(from var(--color-assistant-border) h s l / 0.6);
208-
transform: translateX(-50%) scale(1.05);
209-
}
210-
211-
&:active {
212-
transform: translateX(-50%) scale(0.95);
213-
}
214-
`;
215-
21633
interface AIViewProps {
21734
workspaceId: string;
21835
projectName: string;
@@ -426,11 +243,11 @@ const AIViewInner: React.FC<AIViewProps> = ({
426243
// Return early if workspace state not loaded yet
427244
if (!workspaceState) {
428245
return (
429-
<ViewContainer className={className}>
430-
<EmptyState>
431-
<h3>Loading workspace...</h3>
432-
</EmptyState>
433-
</ViewContainer>
246+
<div className={cn("flex flex-1 flex-row bg-[#1e1e1e] text-[#d4d4d4] font-mono text-xs overflow-x-auto overflow-y-hidden [@media(max-width:768px)]:flex-col", className)} style={{ containerType: "inline-size" }}>
247+
<div className="flex-1 flex flex-col items-center justify-center h-full text-[#6b6b6b] text-center">
248+
<h3 className="m-0 mb-2.5 text-base font-medium">Loading workspace...</h3>
249+
</div>
250+
</div>
434251
);
435252
}
436253

@@ -461,30 +278,30 @@ const AIViewInner: React.FC<AIViewProps> = ({
461278

462279
if (loading) {
463280
return (
464-
<ViewContainer className={className}>
465-
<EmptyState>
466-
<h3>Loading workspace...</h3>
467-
</EmptyState>
468-
</ViewContainer>
281+
<div className={cn("flex flex-1 flex-row bg-[#1e1e1e] text-[#d4d4d4] font-mono text-xs overflow-x-auto overflow-y-hidden [@media(max-width:768px)]:flex-col", className)} style={{ containerType: "inline-size" }}>
282+
<div className="flex-1 flex flex-col items-center justify-center h-full text-[#6b6b6b] text-center">
283+
<h3 className="m-0 mb-2.5 text-base font-medium">Loading workspace...</h3>
284+
</div>
285+
</div>
469286
);
470287
}
471288

472289
if (!projectName || !branch) {
473290
return (
474-
<ViewContainer className={className}>
475-
<EmptyState>
476-
<h3>No Workspace Selected</h3>
477-
<p>Select a workspace from the sidebar to view and interact with Claude</p>
478-
</EmptyState>
479-
</ViewContainer>
291+
<div className={cn("flex flex-1 flex-row bg-[#1e1e1e] text-[#d4d4d4] font-mono text-xs overflow-x-auto overflow-y-hidden [@media(max-width:768px)]:flex-col", className)} style={{ containerType: "inline-size" }}>
292+
<div className="flex-1 flex flex-col items-center justify-center h-full text-[#6b6b6b] text-center">
293+
<h3 className="m-0 mb-2.5 text-base font-medium">No Workspace Selected</h3>
294+
<p className="m-0 text-[13px]">Select a workspace from the sidebar to view and interact with Claude</p>
295+
</div>
296+
</div>
480297
);
481298
}
482299

483300
return (
484-
<ViewContainer className={className}>
485-
<ChatArea ref={chatAreaRef}>
486-
<ViewHeader>
487-
<WorkspaceTitle>
301+
<div className={cn("flex flex-1 flex-row bg-[#1e1e1e] text-[#d4d4d4] font-mono text-xs overflow-x-auto overflow-y-hidden [@media(max-width:768px)]:flex-col", className)} style={{ containerType: "inline-size" }}>
302+
<div ref={chatAreaRef} className="flex-1 min-w-[400px] flex flex-col [@media(max-width:768px)]:min-w-0 [@media(max-width:768px)]:w-full [@media(max-width:768px)]:max-h-full">
303+
<div className="py-1 px-[15px] bg-[#252526] border-b border-[#3e3e42] flex justify-between items-center [@media(max-width:768px)]:py-2 [@media(max-width:768px)]:pl-[60px] [@media(max-width:768px)]:flex-wrap [@media(max-width:768px)]:gap-2">
304+
<div className="font-semibold text-[#ccc] flex items-center gap-2 min-w-0 overflow-hidden">
488305
<StatusIndicator
489306
streaming={canInterrupt}
490307
title={
@@ -496,25 +313,28 @@ const AIViewInner: React.FC<AIViewProps> = ({
496313
workspaceId={workspaceId}
497314
tooltipPosition="bottom"
498315
/>
499-
<WorkspaceName>
316+
<span className="whitespace-nowrap overflow-hidden text-ellipsis min-w-0">
500317
{projectName} / {branch}
501-
</WorkspaceName>
502-
<WorkspacePath>{namedWorkspacePath}</WorkspacePath>
318+
</span>
319+
<span className="font-mono text-[#888] font-normal text-[11px] whitespace-nowrap overflow-hidden text-ellipsis min-w-0">{namedWorkspacePath}</span>
503320
<TooltipWrapper inline>
504-
<TerminalIconButton onClick={handleOpenTerminal}>
321+
<button
322+
onClick={handleOpenTerminal}
323+
className="bg-transparent border-none cursor-pointer p-1 flex items-center justify-center text-[#888] transition-colors hover:text-[#ccc] [&_svg]:w-4 [&_svg]:h-4"
324+
>
505325
<svg viewBox="0 0 16 16" fill="currentColor">
506326
<path d="M0 2.75C0 1.784.784 1 1.75 1h12.5c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0114.25 15H1.75A1.75 1.75 0 010 13.25V2.75zm1.75-.25a.25.25 0 00-.25.25v10.5c0 .138.112.25.25.25h12.5a.25.25 0 00.25-.25V2.75a.25.25 0 00-.25-.25H1.75zM7.25 8a.75.75 0 01-.22.53l-2.25 2.25a.75.75 0 01-1.06-1.06L5.44 8 3.72 6.28a.75.75 0 111.06-1.06l2.25 2.25c.141.14.22.331.22.53zm1.5 1.5a.75.75 0 000 1.5h3a.75.75 0 000-1.5h-3z" />
507327
</svg>
508-
</TerminalIconButton>
328+
</button>
509329
<Tooltip className="tooltip" position="bottom" align="center">
510330
Open in terminal ({formatKeybind(KEYBINDS.OPEN_TERMINAL)})
511331
</Tooltip>
512332
</TooltipWrapper>
513-
</WorkspaceTitle>
514-
</ViewHeader>
333+
</div>
334+
</div>
515335

516-
<OutputContainer>
517-
<OutputContent
336+
<div className="flex-1 relative overflow-hidden">
337+
<div
518338
ref={contentRef}
519339
onWheel={markUserInteraction}
520340
onTouchMove={markUserInteraction}
@@ -524,12 +344,13 @@ const AIViewInner: React.FC<AIViewProps> = ({
524344
aria-busy={canInterrupt}
525345
aria-label="Conversation transcript"
526346
tabIndex={0}
347+
className="h-full overflow-y-auto p-[15px] whitespace-pre-wrap break-words leading-[1.5]"
527348
>
528349
{mergedMessages.length === 0 ? (
529-
<EmptyState>
350+
<div className="flex-1 flex flex-col items-center justify-center h-full text-[#6b6b6b] text-center [&_h3]:m-0 [&_h3]:mb-2.5 [&_h3]:text-base [&_h3]:font-medium [&_p]:m-0 [&_p]:text-[13px]">
530351
<h3>No Messages Yet</h3>
531352
<p>Send a message below to begin</p>
532-
</EmptyState>
353+
</div>
533354
) : (
534355
<>
535356
{mergedMessages.map((msg) => {
@@ -551,9 +372,15 @@ const AIViewInner: React.FC<AIViewProps> = ({
551372
/>
552373
</div>
553374
{isAtCutoff && (
554-
<EditBarrier>
375+
<div
376+
className="my-5 py-3 px-[15px] text-xs font-medium text-center text-edit-mode bg-edit-mode/10"
377+
style={{
378+
borderBottom: "3px solid",
379+
borderImage: "repeating-linear-gradient(45deg, var(--color-editing-mode), var(--color-editing-mode) 10px, transparent 10px, transparent 20px) 1",
380+
}}
381+
>
555382
⚠️ Messages below this line will be removed when you submit the edit
556-
</EditBarrier>
383+
</div>
557384
)}
558385
{shouldShowInterruptedBarrier(msg) && <InterruptedBarrier />}
559386
</React.Fragment>
@@ -599,13 +426,31 @@ const AIViewInner: React.FC<AIViewProps> = ({
599426
}
600427
/>
601428
)}
602-
</OutputContent>
429+
</div>
603430
{!autoScroll && (
604-
<JumpToBottomIndicator onClick={jumpToBottom} type="button">
431+
<button
432+
onClick={jumpToBottom}
433+
type="button"
434+
className="absolute bottom-2 left-1/2 -translate-x-1/2 py-1 px-2 text-white border rounded-[20px] text-xs font-medium shadow-[0_4px_12px_rgba(0,0,0,0.3)] cursor-pointer transition-all duration-200 z-[100] font-primary backdrop-blur-[1px] hover:scale-105 active:scale-95"
435+
style={{
436+
background: "hsl(from var(--color-assistant-border) h s l / 0.1)",
437+
borderColor: "hsl(from var(--color-assistant-border) h s l / 0.4)",
438+
}}
439+
onMouseEnter={(e) => {
440+
const target = e.currentTarget;
441+
target.style.background = "hsl(from var(--color-assistant-border) h s l / 0.4)";
442+
target.style.borderColor = "hsl(from var(--color-assistant-border) h s l / 0.6)";
443+
}}
444+
onMouseLeave={(e) => {
445+
const target = e.currentTarget;
446+
target.style.background = "hsl(from var(--color-assistant-border) h s l / 0.1)";
447+
target.style.borderColor = "hsl(from var(--color-assistant-border) h s l / 0.4)";
448+
}}
449+
>
605450
Press {formatKeybind(KEYBINDS.JUMP_TO_BOTTOM)} to jump to bottom
606-
</JumpToBottomIndicator>
451+
</button>
607452
)}
608-
</OutputContainer>
453+
</div>
609454

610455
<ChatInput
611456
workspaceId={workspaceId}
@@ -620,7 +465,7 @@ const AIViewInner: React.FC<AIViewProps> = ({
620465
canInterrupt={canInterrupt}
621466
onReady={handleChatInputReady}
622467
/>
623-
</ChatArea>
468+
</div>
624469

625470
<RightSidebar
626471
key={workspaceId}
@@ -633,7 +478,7 @@ const AIViewInner: React.FC<AIViewProps> = ({
633478
isResizing={isResizing} // Pass resizing state
634479
onReviewNote={handleReviewNote} // Pass review note handler to append to chat
635480
/>
636-
</ViewContainer>
481+
</div>
637482
);
638483
};
639484

0 commit comments

Comments
Β (0)