Files
claudecodeui/src/components/sidebar/SidebarHeader.tsx
Haileyesus 62f8c8b64e refactor(sidebar): extract typed app/sidebar architecture and split Sidebar into modular components
- Replace `src/App.jsx` with `src/App.tsx` and move route-level UI orchestration into `src/components/app/AppContent.tsx`.
  This separates provider/bootstrap concerns from runtime app layout logic, keeps route definitions minimal, and improves readability of the root app entry.

- Introduce `src/hooks/useProjectsState.ts` to centralize project/session/sidebar state management previously embedded in `App.jsx`.
  This keeps the existing behavior for:
  project loading,
  Cursor session hydration,
  WebSocket `loading_progress` handling,
  additive-update protection for active sessions,
  URL-based session selection,
  sidebar refresh/delete/new-session flows.
  The hook now exposes a typed `sidebarSharedProps` contract and typed handlers used by `AppContent`.

- Introduce `src/hooks/useSessionProtection.ts` for active/processing session lifecycle logic.
  This preserves session-protection behavior while isolating `activeSessions`, `processingSessions`, and temporary-session replacement into a dedicated reusable hook.

- Replace monolithic `src/components/Sidebar.jsx` with typed `src/components/Sidebar.tsx` as a thin orchestrator.
  `Sidebar.tsx` now focuses on wiring controller state/actions, modal visibility, collapsed mode, and version modal behavior instead of rendering every UI branch inline.

- Add `src/hooks/useSidebarController.ts` to encapsulate sidebar interaction/state logic.
  This includes expand/collapse state, inline project/session editing state, project starring/sorting/filtering, lazy session pagination, delete confirmations, rename/delete actions, refresh state, and mobile touch click handling.

- Add strongly typed sidebar domain models in `src/components/sidebar/types.ts` and move sidebar-derived helpers into `src/components/sidebar/utils.ts`.
  Utility coverage now includes:
  session provider normalization,
  session view-model creation (name/time/activity/message count),
  project sorting/filtering,
  task indicator status derivation,
  starred-project persistence and readbacks.

- Split sidebar rendering into focused components under `src/components/sidebar/`:
  `SidebarContent.tsx` for top-level sidebar layout composition.
  `SidebarProjectList.tsx` for project-state branching and project iteration.
  `SidebarProjectsState.tsx` for loading/empty/no-search-result placeholders.
  `SidebarProjectItem.tsx` for per-project desktop/mobile header rendering and actions.
  `SidebarProjectSessions.tsx` for expanded session area, skeletons, pagination, and new-session controls.
  `SidebarSessionItem.tsx` for per-session desktop/mobile item rendering and session actions.
  `SessionProviderIcon.tsx` for provider icon normalization.
  `SidebarHeader.tsx`, `SidebarFooter.tsx`, `SidebarCollapsed.tsx`, and `SidebarModals.tsx` as dedicated typed UI surfaces.
  This keeps rendering responsibilities local and significantly improves traceability.

- Convert shared UI primitives from JSX to TSX:
  `src/components/ui/button.tsx`,
  `src/components/ui/input.tsx`,
  `src/components/ui/badge.tsx`,
  `src/components/ui/scroll-area.tsx`.
  These now provide typed props/variants (`forwardRef` where appropriate) while preserving existing class/behavior.

- Add shared app typings in `src/types/app.ts` for projects/sessions/websocket/loading contracts used by new hooks/components.

- Add global window declarations in `src/types/global.d.ts` for `__ROUTER_BASENAME__`, `refreshProjects`, and `openSettings`, removing implicit `any` usage for global integration points.

- Update `src/main.jsx` to import `App.tsx` and keep app bootstrap consistent with the TS migration.

- Update `src/components/QuickSettingsPanel.jsx` to self-resolve mobile state via `useDeviceSettings` (remove `isMobile` prop dependency), and update `src/components/ChatInterface.jsx` to render `QuickSettingsPanel` directly.
  This reduces prop drilling and keeps quick settings colocated with chat UI concerns.
2026-02-11 15:28:37 +03:00

188 lines
7.1 KiB
TypeScript

import { FolderPlus, MessageSquare, RefreshCw, Search, X } from 'lucide-react';
import type { TFunction } from 'i18next';
import { Button } from '../ui/button';
import { Input } from '../ui/input';
import { IS_PLATFORM } from '../../constants/config';
type SidebarHeaderProps = {
isPWA: boolean;
isMobile: boolean;
isLoading: boolean;
projectsCount: number;
searchFilter: string;
onSearchFilterChange: (value: string) => void;
onClearSearchFilter: () => void;
onRefresh: () => void;
isRefreshing: boolean;
onCreateProject: () => void;
onCollapseSidebar: () => void;
t: TFunction;
};
export default function SidebarHeader({
isPWA,
isMobile,
isLoading,
projectsCount,
searchFilter,
onSearchFilterChange,
onClearSearchFilter,
onRefresh,
isRefreshing,
onCreateProject,
onCollapseSidebar,
t,
}: SidebarHeaderProps) {
return (
<>
<div
className="md:p-4 md:border-b md:border-border"
style={isPWA && isMobile ? { paddingTop: '44px' } : {}}
>
<div className="hidden md:flex items-center justify-between">
{IS_PLATFORM ? (
<a
href="https://cloudcli.ai/dashboard"
className="flex items-center gap-3 hover:opacity-80 transition-opacity group"
title={t('tooltips.viewEnvironments')}
>
<div className="w-8 h-8 bg-primary rounded-lg flex items-center justify-center shadow-sm group-hover:shadow-md transition-shadow">
<MessageSquare className="w-4 h-4 text-primary-foreground" />
</div>
<div>
<h1 className="text-lg font-bold text-foreground">{t('app.title')}</h1>
<p className="text-sm text-muted-foreground">{t('app.subtitle')}</p>
</div>
</a>
) : (
<div className="flex items-center gap-3">
<div className="w-8 h-8 bg-primary rounded-lg flex items-center justify-center shadow-sm">
<MessageSquare className="w-4 h-4 text-primary-foreground" />
</div>
<div>
<h1 className="text-lg font-bold text-foreground">{t('app.title')}</h1>
<p className="text-sm text-muted-foreground">{t('app.subtitle')}</p>
</div>
</div>
)}
<Button
variant="ghost"
size="sm"
className="h-8 w-8 px-0 hover:bg-accent transition-colors duration-200"
onClick={onCollapseSidebar}
title={t('tooltips.hideSidebar')}
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
</svg>
</Button>
</div>
<div
className="md:hidden p-3 border-b border-border"
style={isPWA && isMobile ? { paddingTop: '16px' } : {}}
>
<div className="flex items-center justify-between">
{IS_PLATFORM ? (
<a
href="https://cloudcli.ai/dashboard"
className="flex items-center gap-3 active:opacity-70 transition-opacity"
title={t('tooltips.viewEnvironments')}
>
<div className="w-8 h-8 bg-primary rounded-lg flex items-center justify-center">
<MessageSquare className="w-4 h-4 text-primary-foreground" />
</div>
<div>
<h1 className="text-lg font-semibold text-foreground">{t('app.title')}</h1>
<p className="text-sm text-muted-foreground">{t('projects.title')}</p>
</div>
</a>
) : (
<div className="flex items-center gap-3">
<div className="w-8 h-8 bg-primary rounded-lg flex items-center justify-center">
<MessageSquare className="w-4 h-4 text-primary-foreground" />
</div>
<div>
<h1 className="text-lg font-semibold text-foreground">{t('app.title')}</h1>
<p className="text-sm text-muted-foreground">{t('projects.title')}</p>
</div>
</div>
)}
<div className="flex gap-2">
<button
className="w-8 h-8 rounded-md bg-background border border-border flex items-center justify-center active:scale-95 transition-all duration-150"
onClick={onRefresh}
disabled={isRefreshing}
>
<RefreshCw className={`w-4 h-4 text-foreground ${isRefreshing ? 'animate-spin' : ''}`} />
</button>
<button
className="w-8 h-8 rounded-md bg-primary text-primary-foreground flex items-center justify-center active:scale-95 transition-all duration-150"
onClick={onCreateProject}
>
<FolderPlus className="w-4 h-4" />
</button>
</div>
</div>
</div>
</div>
{!isLoading && !isMobile && (
<div className="px-3 md:px-4 py-2 border-b border-border">
<div className="flex gap-2">
<Button
variant="default"
size="sm"
className="flex-1 h-8 text-xs bg-primary hover:bg-primary/90 transition-all duration-200"
onClick={onCreateProject}
title={t('tooltips.createProject')}
>
<FolderPlus className="w-3.5 h-3.5 mr-1.5" />
{t('projects.newProject')}
</Button>
<Button
variant="outline"
size="sm"
className="h-8 w-8 px-0 hover:bg-accent transition-colors duration-200 group"
onClick={onRefresh}
disabled={isRefreshing}
title={t('tooltips.refresh')}
>
<RefreshCw
className={`w-3.5 h-3.5 ${
isRefreshing ? 'animate-spin' : 'group-hover:rotate-180 transition-transform duration-300'
}`}
/>
</Button>
</div>
</div>
)}
{projectsCount > 0 && !isLoading && (
<div className="px-3 md:px-4 py-2 border-b border-border">
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-muted-foreground" />
<Input
type="text"
placeholder={t('projects.searchPlaceholder')}
value={searchFilter}
onChange={(event) => onSearchFilterChange(event.target.value)}
className="pl-9 h-9 text-sm bg-muted/50 border-0 focus:bg-background focus:ring-1 focus:ring-primary/20"
/>
{searchFilter && (
<button
onClick={onClearSearchFilter}
className="absolute right-2 top-1/2 transform -translate-y-1/2 p-1 hover:bg-accent rounded"
>
<X className="w-3 h-3 text-muted-foreground" />
</button>
)}
</div>
</div>
)}
</>
);
}