From 78338f33c12c73852cb67ae0eef9521bb9847d9a Mon Sep 17 00:00:00 2001 From: Shantur Rathore Date: Tue, 2 Dec 2025 23:52:45 +0000 Subject: [PATCH] add responsive session sidebar --- .../components/instance/instance-shell.tsx | 86 ++++++++++++++++++- .../ui/src/components/message-list-header.tsx | 40 ++++++--- .../ui/src/components/message-section.tsx | 4 + .../src/components/session/session-view.tsx | 4 + .../src/styles/messaging/message-section.css | 42 +++++++++ .../ui/src/styles/panels/session-layout.css | 61 +++++++++++++ 6 files changed, 222 insertions(+), 15 deletions(-) diff --git a/packages/ui/src/components/instance/instance-shell.tsx b/packages/ui/src/components/instance/instance-shell.tsx index 5c429425..5088f15c 100644 --- a/packages/ui/src/components/instance/instance-shell.tsx +++ b/packages/ui/src/components/instance/instance-shell.tsx @@ -1,4 +1,4 @@ -import { Show, createMemo, createSignal, type Component } from "solid-js" +import { Show, createMemo, createSignal, onCleanup, onMount, type Component } from "solid-js" import type { Accessor } from "solid-js" import type { Instance } from "../../types/instance" import type { Command } from "../../lib/commands" @@ -30,9 +30,38 @@ interface InstanceShellProps { } const DEFAULT_SESSION_SIDEBAR_WIDTH = 350 +const MOBILE_SIDEBAR_BREAKPOINT = 1024 const InstanceShell: Component = (props) => { const [sessionSidebarWidth, setSessionSidebarWidth] = createSignal(DEFAULT_SESSION_SIDEBAR_WIDTH) + const [isCompactLayout, setIsCompactLayout] = createSignal(false) + const [isSidebarOpen, setIsSidebarOpen] = createSignal(true) + const sidebarId = `session-sidebar-${props.instance.id}` + let previousIsCompact = false + + const shouldShowSidebarToggle = () => isCompactLayout() && !isSidebarOpen() + + onMount(() => { + if (typeof window === "undefined") return + + const handleResize = () => { + const compact = window.innerWidth < MOBILE_SIDEBAR_BREAKPOINT + setIsCompactLayout(compact) + if (!compact) { + setIsSidebarOpen(true) + } else if (!previousIsCompact && compact) { + setIsSidebarOpen(false) + } + previousIsCompact = compact + } + + handleResize() + window.addEventListener("resize", handleResize) + + onCleanup(() => { + window.removeEventListener("resize", handleResize) + }) + }) const activeSessions = createMemo(() => { const parentId = activeParentSessionId().get(props.instance.id) @@ -68,8 +97,20 @@ const InstanceShell: Component = (props) => { return ( <> 0} fallback={}> -
-
+
+
= (props) => { showFooter={false} headerContent={
- Sessions +
+ Sessions + + + +
{keyboardShortcuts().length ? ( @@ -138,6 +191,20 @@ const InstanceShell: Component = (props) => {
+ + + = (props) => { instanceId={props.instance.id} instanceFolder={props.instance.folder} escapeInDebounce={props.escapeInDebounce} + showSidebarToggle={shouldShowSidebarToggle()} + onSidebarToggle={() => setIsSidebarOpen(true)} /> )} @@ -168,6 +237,15 @@ const InstanceShell: Component = (props) => {
+ + +
diff --git a/packages/ui/src/components/message-list-header.tsx b/packages/ui/src/components/message-list-header.tsx index 3c2d52ee..b5532c55 100644 --- a/packages/ui/src/components/message-list-header.tsx +++ b/packages/ui/src/components/message-list-header.tsx @@ -1,12 +1,18 @@ import { Show } from "solid-js" import Kbd from "./kbd" +const METRIC_CHIP_CLASS = "inline-flex items-center gap-1 rounded-full border border-base px-2 py-0.5 text-xs text-primary" +const METRIC_LABEL_CLASS = "uppercase text-[10px] tracking-wide text-primary/70" + interface MessageListHeaderProps { usedTokens: number + availableTokens?: number | null connectionStatus: "connected" | "connecting" | "error" | "disconnected" | "unknown" | null onCommandPalette: () => void formatTokens: (value: number) => string + showSidebarToggle?: boolean + onSidebarToggle?: () => void } export default function MessageListHeader(props: MessageListHeaderProps) { @@ -15,14 +21,26 @@ export default function MessageListHeader(props: MessageListHeaderProps) { return (
-
-
- Used - {props.formatTokens(props.usedTokens)} -
-
- Avail - {hasAvailableTokens() ? availableDisplay() : "--"} +
+ + + +
+
+ Used + {props.formatTokens(props.usedTokens)} +
+
+ Avail + {hasAvailableTokens() ? availableDisplay() : "--"} +
@@ -41,19 +59,19 @@ export default function MessageListHeader(props: MessageListHeaderProps) { - Connected + Connected - Connecting... + Connecting... - Disconnected + Disconnected
diff --git a/packages/ui/src/components/message-section.tsx b/packages/ui/src/components/message-section.tsx index a450f778..eedca971 100644 --- a/packages/ui/src/components/message-section.tsx +++ b/packages/ui/src/components/message-section.tsx @@ -27,6 +27,8 @@ export interface MessageSectionProps { onRevert?: (messageId: string) => void onFork?: (messageId?: string) => void registerScrollToBottom?: (fn: () => void) => void + showSidebarToggle?: boolean + onSidebarToggle?: () => void } export default function MessageSection(props: MessageSectionProps) { @@ -336,6 +338,8 @@ export default function MessageSection(props: MessageSectionProps) { connectionStatus={connectionStatus()} onCommandPalette={handleCommandPaletteClick} formatTokens={formatTokens} + showSidebarToggle={props.showSidebarToggle} + onSidebarToggle={props.onSidebarToggle} />
diff --git a/packages/ui/src/components/session/session-view.tsx b/packages/ui/src/components/session/session-view.tsx index 2e3a4e31..511ca727 100644 --- a/packages/ui/src/components/session/session-view.tsx +++ b/packages/ui/src/components/session/session-view.tsx @@ -19,6 +19,8 @@ interface SessionViewProps { instanceId: string instanceFolder: string escapeInDebounce: boolean + showSidebarToggle?: boolean + onSidebarToggle?: () => void } export const SessionView: Component = (props) => { @@ -150,6 +152,8 @@ export const SessionView: Component = (props) => { registerScrollToBottom={(fn) => { scrollToBottomHandle = fn }} + showSidebarToggle={props.showSidebarToggle} + onSidebarToggle={props.onSidebarToggle} /> diff --git a/packages/ui/src/styles/messaging/message-section.css b/packages/ui/src/styles/messaging/message-section.css index 660cdb4a..a4f8e6b8 100644 --- a/packages/ui/src/styles/messaging/message-section.css +++ b/packages/ui/src/styles/messaging/message-section.css @@ -9,6 +9,48 @@ border-bottom: 1px solid var(--border-base); } +.session-sidebar-menu-button { + @apply inline-flex items-center justify-center border rounded-md px-2 py-1 text-sm font-medium; + border-color: var(--border-base); + background-color: transparent; + color: var(--text-primary); + transition: color 0.2s ease, background-color 0.2s ease; +} + +.session-sidebar-menu-button:hover { + background-color: var(--surface-hover); +} + +.session-sidebar-menu-button:focus-visible { + @apply ring-2 ring-offset-1; + ring-color: var(--accent-primary); + ring-offset-color: var(--surface-secondary); +} + +.session-sidebar-menu-icon { + font-size: var(--font-size-base); + line-height: 1; +} + +.status-indicator { + @apply flex items-center gap-1.5 text-xs; + color: var(--text-muted); +} + +.status-indicator .status-dot { + @apply w-2 h-2 rounded-full; +} + +.status-indicator .status-text { + display: inline-block; +} + +@media (max-width: 1024px) { + .status-indicator .status-text { + display: none; + } +} + .connection-status-info { justify-self: start; } diff --git a/packages/ui/src/styles/panels/session-layout.css b/packages/ui/src/styles/panels/session-layout.css index af0452ea..8b6bf5e3 100644 --- a/packages/ui/src/styles/panels/session-layout.css +++ b/packages/ui/src/styles/panels/session-layout.css @@ -17,10 +17,71 @@ background-color: var(--surface-secondary); } +.session-layout-compact { + position: relative; +} + +.session-sidebar-overlay { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: min(90vw, 360px); + max-width: 360px; + border-right: 1px solid var(--border-base); + box-shadow: var(--folder-card-shadow); + transform: translateX(0); + transition: transform 0.25s ease, opacity 0.2s ease; + z-index: 40; +} + +.session-sidebar-collapsed { + transform: translateX(-100%); + opacity: 0; + pointer-events: none; +} + +.session-sidebar-backdrop { + @apply absolute inset-0; + border: none; + padding: 0; + background-color: var(--overlay-scrim); + cursor: pointer; + z-index: 30; +} + +.session-sidebar-menu-button--floating { + position: absolute; + top: 1rem; + left: 1rem; + z-index: 20; +} + .session-sidebar-header { @apply flex flex-col gap-2 w-full; } +.session-sidebar-header-row { + @apply flex items-center justify-between gap-2; +} + +.session-sidebar-close { + @apply inline-flex items-center gap-1 text-xs font-medium px-2 py-1 rounded-md border transition-colors; + border-color: var(--border-base); + background-color: var(--surface-base); + color: var(--text-primary); +} + +.session-sidebar-close:hover { + background-color: var(--surface-hover); +} + +.session-sidebar-close:focus-visible { + @apply ring-2 ring-offset-1; + ring-color: var(--accent-primary); + ring-offset-color: var(--surface-secondary); +} + .session-sidebar-title { color: var(--text-primary); }