Skip to content

Commit 6dad8e1

Browse files
cristipufuclaude
andcommitted
feat: add mobile responsive layout for dev console
Switch to vertical stacked layout on screens < 768px with drawer sidebar, unified tab navigation, and touch-enabled resize handles. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 4114979 commit 6dad8e1

15 files changed

Lines changed: 541 additions & 260 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "uipath-dev"
3-
version = "0.0.51"
3+
version = "0.0.52"
44
description = "UiPath Developer Console"
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.11"

src/uipath/dev/server/frontend/src/App.tsx

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import { useCallback, useEffect, useRef } from "react";
1+
import { useCallback, useEffect, useRef, useState } from "react";
22
import { useRunStore } from "./store/useRunStore";
33
import { useWebSocket } from "./store/useWebSocket";
44
import { listRuns, listEntrypoints, getRun } from "./api/client";
55
import type { RunDetail } from "./types/run";
66
import { useHashRoute } from "./hooks/useHashRoute";
7+
import { useIsMobile } from "./hooks/useIsMobile";
78
import Sidebar from "./components/layout/Sidebar";
89
import NewRunPanel from "./components/runs/NewRunPanel";
910
import SetupView from "./components/runs/SetupView";
@@ -12,6 +13,8 @@ import ReloadToast from "./components/shared/ReloadToast";
1213

1314
export default function App() {
1415
const ws = useWebSocket();
16+
const isMobile = useIsMobile();
17+
const [sidebarOpen, setSidebarOpen] = useState(false);
1518
const {
1619
runs,
1720
selectedRunId,
@@ -149,24 +152,44 @@ export default function App() {
149152
const handleRunCreated = (runId: string) => {
150153
navigate(`#/runs/${runId}/traces`);
151154
selectRun(runId);
155+
setSidebarOpen(false);
152156
};
153157

154158
const handleSelectRun = (runId: string) => {
155159
navigate(`#/runs/${runId}/traces`);
156160
selectRun(runId);
161+
setSidebarOpen(false);
157162
};
158163

159164
const handleNewRun = () => {
160165
navigate("#/new");
166+
setSidebarOpen(false);
161167
};
162168

163169
return (
164-
<div className="flex h-screen w-screen">
170+
<div className="flex h-screen w-screen relative">
171+
{/* Mobile hamburger button */}
172+
{isMobile && !sidebarOpen && (
173+
<button
174+
onClick={() => setSidebarOpen(true)}
175+
className="fixed top-2 left-2 z-40 w-9 h-9 flex items-center justify-center rounded-lg cursor-pointer"
176+
style={{ background: "var(--bg-secondary)", border: "1px solid var(--border)" }}
177+
>
178+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
179+
<line x1="3" y1="6" x2="21" y2="6" />
180+
<line x1="3" y1="12" x2="21" y2="12" />
181+
<line x1="3" y1="18" x2="21" y2="18" />
182+
</svg>
183+
</button>
184+
)}
165185
<Sidebar
166186
runs={Object.values(runs)}
167187
selectedRunId={selectedRunId}
168188
onSelectRun={handleSelectRun}
169189
onNewRun={handleNewRun}
190+
isMobile={isMobile}
191+
isOpen={sidebarOpen}
192+
onClose={() => setSidebarOpen(false)}
170193
/>
171194
<main className="flex-1 overflow-hidden bg-[var(--bg-primary)]">
172195
{view === "new" ? (
@@ -177,9 +200,10 @@ export default function App() {
177200
mode={setupMode}
178201
ws={ws}
179202
onRunCreated={handleRunCreated}
203+
isMobile={isMobile}
180204
/>
181205
) : selectedRun ? (
182-
<RunDetailsPanel run={selectedRun} ws={ws} />
206+
<RunDetailsPanel run={selectedRun} ws={ws} isMobile={isMobile} />
183207
) : (
184208
<div className="flex items-center justify-center h-full text-[var(--text-muted)]">
185209
Select a run or create a new one

src/uipath/dev/server/frontend/src/components/layout/Sidebar.tsx

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,12 @@ interface Props {
77
selectedRunId: string | null;
88
onSelectRun: (id: string) => void;
99
onNewRun: () => void;
10+
isMobile?: boolean;
11+
isOpen?: boolean;
12+
onClose?: () => void;
1013
}
1114

12-
export default function Sidebar({ runs, selectedRunId, onSelectRun, onNewRun }: Props) {
15+
export default function Sidebar({ runs, selectedRunId, onSelectRun, onNewRun, isMobile, isOpen, onClose }: Props) {
1316
const { theme, toggleTheme } = useTheme();
1417

1518
const sorted = [...runs].sort(
@@ -18,6 +21,104 @@ export default function Sidebar({ runs, selectedRunId, onSelectRun, onNewRun }:
1821
new Date(a.start_time ?? 0).getTime(),
1922
);
2023

24+
// On mobile: hidden unless open, renders as overlay drawer
25+
if (isMobile) {
26+
if (!isOpen) return null;
27+
return (
28+
<>
29+
{/* Backdrop */}
30+
<div
31+
className="fixed inset-0 z-50"
32+
style={{ background: "rgba(0,0,0,0.5)" }}
33+
onClick={onClose}
34+
/>
35+
{/* Drawer */}
36+
<aside className="fixed inset-y-0 left-0 z-50 w-64 bg-[var(--sidebar-bg)] border-r border-[var(--border)] flex flex-col">
37+
{/* Header */}
38+
<div className="px-3 h-10 border-b border-[var(--border)] flex items-center justify-between">
39+
<button
40+
onClick={onNewRun}
41+
className="flex items-center gap-1.5 cursor-pointer transition-opacity hover:opacity-80"
42+
>
43+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
44+
<rect width="24" height="24" rx="4" fill="var(--accent)" />
45+
<text x="12" y="17" textAnchor="middle" fill="white" fontSize="14" fontWeight="700" fontFamily="Arial, sans-serif">U</text>
46+
</svg>
47+
<span
48+
className="text-[11px] uppercase tracking-widest font-semibold"
49+
style={{ color: "var(--text-muted)" }}
50+
>
51+
Dev Console
52+
</span>
53+
</button>
54+
<div className="flex items-center gap-1">
55+
<button
56+
onClick={toggleTheme}
57+
className="w-6 h-6 flex items-center justify-center rounded cursor-pointer transition-colors"
58+
style={{ color: "var(--text-muted)" }}
59+
title={`Switch to ${theme === "dark" ? "light" : "dark"} theme`}
60+
>
61+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
62+
{theme === "dark" ? (
63+
<><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></>
64+
) : (
65+
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
66+
)}
67+
</svg>
68+
</button>
69+
{/* Close button */}
70+
<button
71+
onClick={onClose}
72+
className="w-6 h-6 flex items-center justify-center rounded cursor-pointer transition-colors"
73+
style={{ color: "var(--text-muted)" }}
74+
>
75+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
76+
<line x1="18" y1="6" x2="6" y2="18" />
77+
<line x1="6" y1="6" x2="18" y2="18" />
78+
</svg>
79+
</button>
80+
</div>
81+
</div>
82+
83+
{/* New Run */}
84+
<button
85+
onClick={onNewRun}
86+
className="mx-3 mt-2.5 mb-1 px-2 py-1.5 text-[10px] uppercase tracking-wider font-semibold rounded border border-[var(--border)] bg-transparent transition-colors cursor-pointer"
87+
style={{ color: "var(--text-muted)" }}
88+
>
89+
+ New Run
90+
</button>
91+
92+
{/* Runs label */}
93+
<div
94+
className="px-3 pt-3 pb-1 text-[10px] uppercase tracking-widest font-semibold"
95+
style={{ color: "var(--text-muted)" }}
96+
>
97+
History
98+
</div>
99+
100+
{/* Run list */}
101+
<div className="flex-1 overflow-y-auto">
102+
{sorted.map((run) => (
103+
<RunHistoryItem
104+
key={run.id}
105+
run={run}
106+
isSelected={run.id === selectedRunId}
107+
onClick={() => onSelectRun(run.id)}
108+
/>
109+
))}
110+
{sorted.length === 0 && (
111+
<p className="text-xs px-3 py-4 text-center" style={{ color: "var(--text-muted)" }}>
112+
No runs yet
113+
</p>
114+
)}
115+
</div>
116+
</aside>
117+
</>
118+
);
119+
}
120+
121+
// Desktop: unchanged
21122
return (
22123
<aside className="w-44 bg-[var(--sidebar-bg)] border-r border-[var(--border)] flex flex-col">
23124
{/* Header */}

0 commit comments

Comments
 (0)