Skip to content

Commit a6eb030

Browse files
author
shmuel hizmi
committed
master
1 parent 76d027a commit a6eb030

3 files changed

Lines changed: 52 additions & 5 deletions

File tree

packages/wmux-client/src/components/Sidebar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ function CategorySection({
217217
function SidebarFooter(): ReactElement {
218218
const shortcuts = [
219219
{ key: "⌘K", label: "Command palette" },
220-
{ key: "⌘1-9", label: "Switch category" },
220+
{ key: "↑↓", label: "Navigate sidebar" },
221221
{ key: "⌘[]", label: "Switch tab" },
222222
] as const;
223223

packages/wmux-client/src/components/WmuxApp.tsx

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,27 @@ export function WmuxApp(props: {
170170
onRestart: () => restartProcess(activeTabId),
171171
} : null;
172172

173+
// Build flat navigable sidebar item list
174+
const sidebarItems = useMemo(() => {
175+
const items: Array<{ readonly type: "category"; readonly name: string } | { readonly type: "tab"; readonly categoryName: string; readonly tabId: string }> = [];
176+
for (const cat of categories) {
177+
items.push({ type: "category", name: cat.name });
178+
if (!collapsedCategories.has(cat.name) && cat.type !== "files") {
179+
for (const tab of cat.tabs) {
180+
items.push({ type: "tab", categoryName: cat.name, tabId: tab.id });
181+
}
182+
}
183+
}
184+
return items;
185+
}, [categories, collapsedCategories]);
186+
173187
// Keyboard shortcuts
174188
useEffect(() => {
189+
const isTerminalFocused = (): boolean => {
190+
const el = document.activeElement;
191+
return el != null && el.closest(".xterm") != null;
192+
};
193+
175194
const handler = (e: KeyboardEvent): void => {
176195
const mod = e.metaKey || e.ctrlKey;
177196
if (mod && e.key === "k") { e.preventDefault(); setCmdkOpen((p) => !p); return; }
@@ -188,11 +207,33 @@ export function WmuxApp(props: {
188207
const idx = orderedTabs.findIndex((t) => t.id === activeTabId);
189208
const next = e.key === "]" ? (idx + 1) % orderedTabs.length : (idx - 1 + orderedTabs.length) % orderedTabs.length;
190209
selectTab(orderedTabs[next]!.id);
210+
return;
211+
}
212+
213+
// Arrow key sidebar navigation (only when terminal is not focused)
214+
if ((e.key === "ArrowUp" || e.key === "ArrowDown") && !isTerminalFocused() && !cmdkOpen && sidebarItems.length > 0) {
215+
e.preventDefault();
216+
const currentIdx = sidebarItems.findIndex((item) =>
217+
item.type === "tab"
218+
? item.tabId === activeTabId && item.categoryName === activeCategory
219+
: item.name === activeCategory && !sidebarItems.some((s) => s.type === "tab" && s.categoryName === activeCategory && s.tabId === activeTabId),
220+
);
221+
const delta = e.key === "ArrowDown" ? 1 : -1;
222+
const nextIdx = currentIdx === -1
223+
? 0
224+
: (currentIdx + delta + sidebarItems.length) % sidebarItems.length;
225+
const next = sidebarItems[nextIdx]!;
226+
if (next.type === "category") {
227+
selectCategory(next.name);
228+
} else {
229+
selectCategory(next.categoryName);
230+
selectTab(next.tabId);
231+
}
191232
}
192233
};
193234
window.addEventListener("keydown", handler);
194235
return () => window.removeEventListener("keydown", handler);
195-
}, [cmdkOpen, categories, selectCategory, orderedTabs, activeTabId, selectTab]);
236+
}, [cmdkOpen, categories, selectCategory, orderedTabs, activeTabId, selectTab, sidebarItems, activeCategory]);
196237

197238
const toggleCollapse = useCallback((name: string) => {
198239
setCollapsedCategories((prev) => { const next = new Set(prev); next.has(name) ? next.delete(name) : next.add(name); return next; });

packages/wmux/README.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ bun i @playfast/wmux
1616
import { wmux } from "@playfast/wmux";
1717

1818
await wmux({
19+
title: "my-app",
20+
description: "local dev",
1921
sidebarItems: [
2022
{
2123
category: "dev",
@@ -37,6 +39,8 @@ Open the printed URL in your browser.
3739

3840
```ts
3941
interface WmuxConfig {
42+
title?: string; // displayed in the top bar search button
43+
description?: string; // shown next to the title in the top bar
4044
sidebarItems: SidebarItem[];
4145
port?: number; // default: random
4246
hostname?: string; // default: "127.0.0.1"
@@ -67,7 +71,7 @@ Each sidebar item is either a **tab group** or a **file browser**:
6771
category: "services",
6872
icon: "Server", // optional, any Lucide icon name
6973
tabs: [
70-
{ name: "api", process: { command: "bun run dev" } },
74+
{ name: "api", description: "REST API", process: { command: "bun run dev" } },
7175
{ name: "docs", url: "http://localhost:3001" },
7276
],
7377
}
@@ -85,7 +89,7 @@ Each sidebar item is either a **tab group** or a **file browser**:
8589
```ts
8690
interface TabConfig {
8791
name: string;
88-
description?: string;
92+
description?: string; // shown below the tab name in the sidebar
8993
icon?: string; // Lucide icon name
9094
process?: ProcessConfig; // terminal process
9195
url?: string; // iframe URL
@@ -123,9 +127,11 @@ await wmux({
123127
- PTY terminals via `Bun.spawn`
124128
- Iframe tabs for web previews
125129
- File browser with Monaco editor viewer
126-
- Sidebar with collapsible categories and Lucide icons
130+
- VS Code-style top bar with search button
131+
- Sidebar with collapsible categories and color-coded left borders
127132
- Drag-to-reorder tabs
128133
- Command palette (`Cmd+K`)
134+
- Arrow key sidebar navigation (when terminal is not focused)
129135
- Keyboard shortcuts (`Cmd+1-9` switch category, `Cmd+[/]` switch tab)
130136
- Token-based WebSocket auth
131137
- Auto-restart on crash

0 commit comments

Comments
 (0)