Skip to content

Commit 9ea4c1c

Browse files
author
Test
committed
🤖 feat: add mobile toggle for Review panel
Add a Floating Action Button (FAB) to access the right sidebar (Costs/Review panels) on mobile devices where it was previously inaccessible. Changes: - Add FAB button in bottom-right corner (mobile only) - Implement slide-in animation from right (mirrors left sidebar) - Add backdrop overlay with click-to-close - Add close button inside sidebar for explicit dismissal - Update mobile styles to use fixed positioning with transform The right sidebar now slides in from the right on mobile when the FAB is clicked, providing access to the Review and Costs panels that were previously hidden on narrow screens. _Generated with `cmux`_ Change-Id: I16419aef58f515366f5dec90f3e9d27cc368069c Signed-off-by: Test <test@example.com>
1 parent 4a5ff11 commit 9ea4c1c

File tree

1 file changed

+151
-100
lines changed

1 file changed

+151
-100
lines changed

src/components/RightSidebar.tsx

Lines changed: 151 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,11 @@ const SidebarContainer: React.FC<SidebarContainerProps> = ({
5252
"bg-separator border-l border-border-light flex flex-col overflow-hidden flex-shrink-0",
5353
customWidth ? "" : "transition-[width] duration-200",
5454
collapsed && "sticky right-0 z-10 shadow-[-2px_0_4px_rgba(0,0,0,0.2)]",
55-
"max-md:border-l-0 max-md:border-t max-md:border-border-light",
56-
collapsed && "max-md:w-0 max-md:absolute max-md:bottom-0",
57-
!collapsed && "max-md:w-full max-md:relative max-md:max-h-[50vh]"
55+
// Mobile: slide in from right (similar to left sidebar pattern)
56+
"max-md:fixed max-md:right-0 max-md:top-0 max-md:h-screen max-md:transition-transform max-md:duration-300",
57+
collapsed && "max-md:translate-x-full max-md:shadow-none",
58+
!collapsed &&
59+
"max-md:translate-x-0 max-md:w-full max-md:max-w-md max-md:z-[999] max-md:shadow-[-2px_0_8px_rgba(0,0,0,0.5)] max-md:border-l max-md:border-border-light"
5860
)}
5961
style={{ width }}
6062
role={role}
@@ -186,112 +188,161 @@ const RightSidebarComponent: React.FC<RightSidebarProps> = ({
186188
const verticalMeter = showMeter ? <VerticalTokenMeter data={verticalMeterData} /> : null;
187189

188190
return (
189-
<SidebarContainer
190-
collapsed={showCollapsed}
191-
wide={selectedTab === "review" && !width} // Auto-wide only if not drag-resizing
192-
customWidth={width} // Drag-resized width from AIView (Review tab only)
193-
role="complementary"
194-
aria-label="Workspace insights"
195-
>
196-
{/* Full view when not collapsed */}
197-
<div className={cn("flex-row h-full", !showCollapsed ? "flex" : "hidden")}>
198-
{/* Render meter when Review tab is active */}
199-
{selectedTab === "review" && (
200-
<div className="bg-separator border-border-light flex w-5 shrink-0 flex-col border-r">
201-
{verticalMeter}
202-
</div>
203-
)}
191+
<>
192+
{/* FAB - Floating Action Button for mobile, only visible when collapsed */}
193+
{showCollapsed && (
194+
<button
195+
onClick={() => setShowCollapsed(false)}
196+
title={`Open ${selectedTab} panel`}
197+
aria-label={`Open ${selectedTab} panel`}
198+
className={cn(
199+
"hidden max-md:flex fixed bottom-20 right-4 z-[998]",
200+
"w-12 h-12 bg-accent border border-accent rounded-full cursor-pointer",
201+
"items-center justify-center text-white text-lg transition-all duration-200",
202+
"shadow-[0_4px_12px_rgba(0,0,0,0.4)]",
203+
"hover:bg-accent-hover hover:scale-105",
204+
"active:scale-95"
205+
)}
206+
>
207+
{selectedTab === "costs" ? "💰" : "📋"}
208+
</button>
209+
)}
204210

205-
{/* Render resize handle to right of meter when Review tab is active */}
206-
{selectedTab === "review" && onStartResize && (
207-
<div
208-
className={cn(
209-
"w-1 flex-shrink-0 z-10 transition-[background] duration-150",
210-
"bg-border-light cursor-col-resize hover:bg-accent",
211-
isResizing && "bg-accent"
212-
)}
213-
onMouseDown={(e) => onStartResize(e as unknown as React.MouseEvent)}
214-
/>
215-
)}
211+
{/* Backdrop overlay - only on mobile when sidebar is expanded */}
212+
{!showCollapsed && (
213+
<div
214+
className="hidden max-md:block fixed inset-0 bg-black/50 z-[998] backdrop-blur-sm"
215+
onClick={() => setShowCollapsed(true)}
216+
aria-hidden="true"
217+
/>
218+
)}
216219

217-
<div className="flex min-w-0 flex-1 flex-col">
218-
<div
219-
className="bg-background-secondary border-border flex border-b [&>*]:flex-1"
220-
role="tablist"
221-
aria-label="Metadata views"
222-
>
223-
<TooltipWrapper inline>
224-
<button
225-
className={cn(
226-
"w-full py-2.5 px-[15px] border-none border-solid cursor-pointer font-primary text-[13px] font-medium transition-all duration-200",
227-
selectedTab === "costs"
228-
? "text-white bg-separator border-b-2 border-b-plan-mode"
229-
: "bg-transparent text-secondary border-b-2 border-b-transparent hover:bg-background-secondary hover:text-foreground"
230-
)}
231-
onClick={() => setSelectedTab("costs")}
232-
id={costsTabId}
233-
role="tab"
234-
type="button"
235-
aria-selected={selectedTab === "costs"}
236-
aria-controls={costsPanelId}
237-
>
238-
Costs
239-
</button>
240-
<Tooltip className="tooltip" position="bottom" align="center">
241-
{formatKeybind(KEYBINDS.COSTS_TAB)}
242-
</Tooltip>
243-
</TooltipWrapper>
244-
<TooltipWrapper inline>
220+
<SidebarContainer
221+
collapsed={showCollapsed}
222+
wide={selectedTab === "review" && !width} // Auto-wide only if not drag-resizing
223+
customWidth={width} // Drag-resized width from AIView (Review tab only)
224+
role="complementary"
225+
aria-label="Workspace insights"
226+
>
227+
{/* Full view when not collapsed */}
228+
<div className={cn("flex-row h-full", !showCollapsed ? "flex" : "hidden")}>
229+
{/* Render meter when Review tab is active */}
230+
{selectedTab === "review" && (
231+
<div className="bg-separator border-border-light flex w-5 shrink-0 flex-col border-r">
232+
{verticalMeter}
233+
</div>
234+
)}
235+
236+
{/* Render resize handle to right of meter when Review tab is active */}
237+
{selectedTab === "review" && onStartResize && (
238+
<div
239+
className={cn(
240+
"w-1 flex-shrink-0 z-10 transition-[background] duration-150",
241+
"bg-border-light cursor-col-resize hover:bg-accent",
242+
isResizing && "bg-accent"
243+
)}
244+
onMouseDown={(e) => onStartResize(e as unknown as React.MouseEvent)}
245+
/>
246+
)}
247+
248+
<div className="flex min-w-0 flex-1 flex-col">
249+
<div
250+
className="bg-background-secondary border-border flex border-b [&>*]:flex-1 relative"
251+
role="tablist"
252+
aria-label="Metadata views"
253+
>
254+
{/* Close button - only visible on mobile */}
245255
<button
256+
onClick={() => setShowCollapsed(true)}
257+
title="Close panel"
258+
aria-label="Close panel"
246259
className={cn(
247-
"w-full py-2.5 px-[15px] border-none border-solid cursor-pointer font-primary text-[13px] font-medium transition-all duration-200",
248-
selectedTab === "review"
249-
? "text-white bg-separator border-b-2 border-b-plan-mode"
250-
: "bg-transparent text-secondary border-b-2 border-b-transparent hover:bg-background-secondary hover:text-foreground"
260+
"hidden max-md:flex absolute top-2 right-2 z-10",
261+
"w-8 h-8 bg-separator border border-border-light rounded cursor-pointer",
262+
"items-center justify-center text-foreground transition-all duration-200",
263+
"hover:bg-hover hover:border-bg-light",
264+
"active:scale-95"
251265
)}
252-
onClick={() => setSelectedTab("review")}
253-
id={reviewTabId}
254-
role="tab"
255-
type="button"
256-
aria-selected={selectedTab === "review"}
257-
aria-controls={reviewPanelId}
258266
>
259-
Review
267+
260268
</button>
261-
<Tooltip className="tooltip" position="bottom" align="center">
262-
{formatKeybind(KEYBINDS.REVIEW_TAB)}
263-
</Tooltip>
264-
</TooltipWrapper>
265-
</div>
266-
<div
267-
className={cn("flex-1 overflow-y-auto", selectedTab === "review" ? "p-0" : "p-[15px]")}
268-
>
269-
{selectedTab === "costs" && (
270-
<div role="tabpanel" id={costsPanelId} aria-labelledby={costsTabId}>
271-
<CostsTab workspaceId={workspaceId} />
272-
</div>
273-
)}
274-
{selectedTab === "review" && (
275-
<div
276-
role="tabpanel"
277-
id={reviewPanelId}
278-
aria-labelledby={reviewTabId}
279-
className="h-full"
280-
>
281-
<ReviewPanel
282-
workspaceId={workspaceId}
283-
workspacePath={workspacePath}
284-
onReviewNote={onReviewNote}
285-
focusTrigger={focusTrigger}
286-
/>
287-
</div>
288-
)}
269+
270+
<TooltipWrapper inline>
271+
<button
272+
className={cn(
273+
"w-full py-2.5 px-[15px] border-none border-solid cursor-pointer font-primary text-[13px] font-medium transition-all duration-200",
274+
selectedTab === "costs"
275+
? "text-white bg-separator border-b-2 border-b-plan-mode"
276+
: "bg-transparent text-secondary border-b-2 border-b-transparent hover:bg-background-secondary hover:text-foreground"
277+
)}
278+
onClick={() => setSelectedTab("costs")}
279+
id={costsTabId}
280+
role="tab"
281+
type="button"
282+
aria-selected={selectedTab === "costs"}
283+
aria-controls={costsPanelId}
284+
>
285+
Costs
286+
</button>
287+
<Tooltip className="tooltip" position="bottom" align="center">
288+
{formatKeybind(KEYBINDS.COSTS_TAB)}
289+
</Tooltip>
290+
</TooltipWrapper>
291+
<TooltipWrapper inline>
292+
<button
293+
className={cn(
294+
"w-full py-2.5 px-[15px] border-none border-solid cursor-pointer font-primary text-[13px] font-medium transition-all duration-200",
295+
selectedTab === "review"
296+
? "text-white bg-separator border-b-2 border-b-plan-mode"
297+
: "bg-transparent text-secondary border-b-2 border-b-transparent hover:bg-background-secondary hover:text-foreground"
298+
)}
299+
onClick={() => setSelectedTab("review")}
300+
id={reviewTabId}
301+
role="tab"
302+
type="button"
303+
aria-selected={selectedTab === "review"}
304+
aria-controls={reviewPanelId}
305+
>
306+
Review
307+
</button>
308+
<Tooltip className="tooltip" position="bottom" align="center">
309+
{formatKeybind(KEYBINDS.REVIEW_TAB)}
310+
</Tooltip>
311+
</TooltipWrapper>
312+
</div>
313+
<div
314+
className={cn(
315+
"flex-1 overflow-y-auto",
316+
selectedTab === "review" ? "p-0" : "p-[15px]"
317+
)}
318+
>
319+
{selectedTab === "costs" && (
320+
<div role="tabpanel" id={costsPanelId} aria-labelledby={costsTabId}>
321+
<CostsTab workspaceId={workspaceId} />
322+
</div>
323+
)}
324+
{selectedTab === "review" && (
325+
<div
326+
role="tabpanel"
327+
id={reviewPanelId}
328+
aria-labelledby={reviewTabId}
329+
className="h-full"
330+
>
331+
<ReviewPanel
332+
workspaceId={workspaceId}
333+
workspacePath={workspacePath}
334+
onReviewNote={onReviewNote}
335+
focusTrigger={focusTrigger}
336+
/>
337+
</div>
338+
)}
339+
</div>
289340
</div>
290341
</div>
291-
</div>
292-
{/* Render meter in collapsed view when sidebar is collapsed */}
293-
<div className={cn("h-full", showCollapsed ? "flex" : "hidden")}>{verticalMeter}</div>
294-
</SidebarContainer>
342+
{/* Render meter in collapsed view when sidebar is collapsed */}
343+
<div className={cn("h-full", showCollapsed ? "flex" : "hidden")}>{verticalMeter}</div>
344+
</SidebarContainer>
345+
</>
295346
);
296347
};
297348

0 commit comments

Comments
 (0)