From 1737cab44fbb4e9d5a628076a8bca1e9fc6c872a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Flor=C3=AAncio?= Date: Fri, 23 Jan 2026 21:21:36 +0000 Subject: [PATCH] feat: improve chat ui paddings (#83) --- apps/packages/ui/src/index.ts | 1 + apps/packages/ui/src/pannel-session.tsx | 60 +++ apps/web/app/globals.css | 8 +- apps/web/components/session-tabs.tsx | 141 ++++++ .../task/chat/virtualized-message-list.tsx | 7 +- .../components/task/preview/preview-panel.tsx | 199 ++++---- .../task/process-output-terminal.tsx | 140 ------ apps/web/components/task/shell-terminal.tsx | 111 ++++- .../web/components/task/task-center-panel.tsx | 111 ++--- .../components/task/task-changes-panel.tsx | 5 +- apps/web/components/task/task-chat-input.tsx | 2 +- apps/web/components/task/task-chat-panel.tsx | 2 +- apps/web/components/task/task-files-panel.tsx | 46 +- apps/web/components/task/task-layout.tsx | 19 +- apps/web/components/task/task-right-panel.tsx | 440 ++++++++---------- .../components/task/task-session-sidebar.tsx | 5 +- apps/web/lib/editor/chat-editor-theme.ts | 22 +- 17 files changed, 712 insertions(+), 607 deletions(-) create mode 100644 apps/packages/ui/src/pannel-session.tsx create mode 100644 apps/web/components/session-tabs.tsx delete mode 100644 apps/web/components/task/process-output-terminal.tsx diff --git a/apps/packages/ui/src/index.ts b/apps/packages/ui/src/index.ts index c359bad6c4..53550740dc 100644 --- a/apps/packages/ui/src/index.ts +++ b/apps/packages/ui/src/index.ts @@ -30,6 +30,7 @@ export * from './label'; export * from './menubar'; export * from './navigation-menu'; export * from './pagination'; +export * from './pannel-session'; export * from './popover'; export * from './progress'; export * from './radio-group'; diff --git a/apps/packages/ui/src/pannel-session.tsx b/apps/packages/ui/src/pannel-session.tsx new file mode 100644 index 0000000000..35178bccc8 --- /dev/null +++ b/apps/packages/ui/src/pannel-session.tsx @@ -0,0 +1,60 @@ +import type { ReactNode } from 'react'; +import { forwardRef } from 'react'; +import { cn } from './lib/utils'; + +type BorderSide = 'none' | 'left' | 'right'; +type MarginSide = 'none' | 'right' | 'top'; + +interface SessionPanelProps { + children: ReactNode; + borderSide?: BorderSide; + margin?: MarginSide; + className?: string; +} + +interface SessionPanelContentProps { + children: ReactNode; + className?: string; +} + +export function SessionPanel({ + children, + borderSide = 'none', + margin = 'none', + className, +}: SessionPanelProps) { + return ( +
+ {children} +
+ ); +} + +export const SessionPanelContent = forwardRef( + function SessionPanelContent({ children, className }, ref) { + return ( +
+ {children} +
+ ); + } +); diff --git a/apps/web/app/globals.css b/apps/web/app/globals.css index 7a8ca2b54b..493f472f97 100644 --- a/apps/web/app/globals.css +++ b/apps/web/app/globals.css @@ -88,10 +88,10 @@ --sidebar-accent: oklch(0.97 0 0); --sidebar-accent-foreground: oklch(0.205 0 0); --sidebar-border: oklch(0.922 0 0); - --sidebar-ring: oklch(0.708 0 0); --scrollbar-thumb: oklch(0.78 0 0); --scrollbar-thumb-hover: oklch(0.66 0 0); - --scrollbar-track: oklch(0.96 0 0 / 0.6); + --scrollbar-track: var(--background); + --sidebar-ring: oklch(0.708 0 0); } .dark { @@ -125,10 +125,10 @@ --sidebar-accent: oklch(0.269 0 0); --sidebar-accent-foreground: oklch(0.985 0 0); --sidebar-border: oklch(1 0 0 / 10%); - --sidebar-ring: oklch(0.556 0 0); --scrollbar-thumb: oklch(0.48 0 0 / 0.7); --scrollbar-thumb-hover: oklch(0.62 0 0 / 0.8); - --scrollbar-track: oklch(0.2 0 0 / 0.6); + --scrollbar-track: var(--background); + --sidebar-ring: oklch(0.556 0 0); } @layer base { diff --git a/apps/web/components/session-tabs.tsx b/apps/web/components/session-tabs.tsx new file mode 100644 index 0000000000..9b1c0501ea --- /dev/null +++ b/apps/web/components/session-tabs.tsx @@ -0,0 +1,141 @@ +'use client'; + +import type { ReactNode, MouseEvent } from 'react'; +import { Fragment } from 'react'; +import { Tabs, TabsList, TabsTrigger } from '@kandev/ui/tabs'; + +export type SessionTab = { + id: string; + label: string; + icon?: ReactNode; + closable?: boolean; + onClose?: (event: MouseEvent) => void; + className?: string; +}; + +type SessionTabsProps = { + children?: ReactNode; // TabsContent elements (optional for cases where tabs are just visible) + tabs: SessionTab[]; + activeTab: string; + onTabChange: (tabId: string) => void; + showAddButton?: boolean; + onAddTab?: () => void; + addButtonLabel?: string; + separatorAfterIndex?: number; + className?: string; + // Collapse support + collapsible?: boolean; + isCollapsed?: boolean; + onToggleCollapse?: () => void; +}; + +export function SessionTabs({ + children, + tabs, + activeTab, + onTabChange, + showAddButton = false, + onAddTab, + addButtonLabel = '+', + separatorAfterIndex, + className, + collapsible = false, + isCollapsed = false, + onToggleCollapse, +}: SessionTabsProps) { + const tabsList = ( + + {tabs.map((tab, index) => ( + + {separatorAfterIndex !== undefined && index === separatorAfterIndex + 1 && ( +
+ )} + + {tab.icon} + {tab.label} + {tab.closable && tab.onClose && ( + + + + + + + )} + + + ))} + {showAddButton && onAddTab && ( + + {addButtonLabel} + + )} + + ); + + const collapseButton = collapsible && onToggleCollapse && ( + + ); + + return ( + + {collapsible ? ( +
+ {tabsList} + {collapseButton} +
+ ) : ( + tabsList + )} + {children} +
+ ); +} diff --git a/apps/web/components/task/chat/virtualized-message-list.tsx b/apps/web/components/task/chat/virtualized-message-list.tsx index 8d1518a94d..2fa221ed26 100644 --- a/apps/web/components/task/chat/virtualized-message-list.tsx +++ b/apps/web/components/task/chat/virtualized-message-list.tsx @@ -3,6 +3,7 @@ import { useCallback, useEffect, useMemo, useRef } from 'react'; import { useVirtualizer } from '@tanstack/react-virtual'; import { IconLoader2 } from '@tabler/icons-react'; +import { SessionPanelContent } from '@kandev/ui/pannel-session'; import type { Message } from '@/lib/types/http'; import { MessageRenderer } from '@/components/task/chat/message-renderer'; import { TurnSummary } from '@/components/task/chat/messages/turn-summary'; @@ -97,9 +98,9 @@ export function VirtualizedMessageList({ }, [virtualItems, hasMore, isLoadingMore, loadMore]); return ( -
{isLoadingMore && hasMore && (
@@ -147,6 +148,6 @@ export function VirtualizedMessageList({ )} {/* Turn summary - shows duration and model after last message */} -
+ ); } diff --git a/apps/web/components/task/preview/preview-panel.tsx b/apps/web/components/task/preview/preview-panel.tsx index 9558477771..23e118c5b4 100644 --- a/apps/web/components/task/preview/preview-panel.tsx +++ b/apps/web/components/task/preview/preview-panel.tsx @@ -4,10 +4,11 @@ import { useState, useEffect } from 'react'; import { IconRefresh, IconExternalLink, IconLoader2 } from '@tabler/icons-react'; import { Button } from '@kandev/ui/button'; import { Input } from '@kandev/ui/input'; +import { SessionPanel, SessionPanelContent } from '@kandev/ui/pannel-session'; import { usePreviewPanel } from '@/hooks/use-preview-panel'; import { useAppStore, useAppStoreApi } from '@/components/state-provider'; import { useLayoutStore } from '@/lib/state/layout-store'; -import { ProcessOutputTerminal } from '@/components/task/process-output-terminal'; +import { ShellTerminal } from '@/components/task/shell-terminal'; import { getLocalStorage } from '@/lib/local-storage'; type PreviewPanelProps = { @@ -182,107 +183,105 @@ export function PreviewPanel({ sessionId, hasDevScript }: PreviewPanelProps) { }; return ( -
-
- setPreviewUrlDraft(sessionId, event.target.value)} - onKeyDown={(event) => { - if (event.key === 'Enter') { - event.preventDefault(); - handleUrlSubmit(); + +
+
+ setPreviewUrlDraft(sessionId, event.target.value)} + onKeyDown={(event) => { + if (event.key === 'Enter') { + event.preventDefault(); + handleUrlSubmit(); + } + }} + placeholder={detectedUrl || "http://localhost:3000"} + className="h-6 flex-1 min-w-[240px]" + /> + + + + - - - -
- -
- {previewView === 'output' ? ( -
- -
- ) : showIframe && previewUrl ? ( -
-
-