mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-05-16 01:12:46 +00:00
refactor(main-content): migrate MainContent to TypeScript and modularize UI/state boundaries
Replace the previous monolithic MainContent.jsx with a typed one and extract focused subcomponents/hooks to improve readability, local state ownership, and maintainability while keeping runtime behavior unchanged. Key changes: - Replace `src/components/MainContent.jsx` with `src/components/MainContent.tsx`. - Add typed contracts for main-content domain in `src/components/main-content/types.ts`. - Extract header composition into: - `MainContentHeader.tsx` - `MainContentTitle.tsx` - `MainContentTabSwitcher.tsx` - `MobileMenuButton.tsx` - Extract loading/empty project views into `MainContentStateView.tsx`. - Extract editor presentation into `EditorSidebar.tsx`. - Move editor file-open + resize behavior into `useEditorSidebar.ts`. - Move mobile menu touch/click suppression logic into `useMobileMenuHandlers.ts`. - Extract TaskMaster-specific concerns into `TaskMasterPanel.tsx`: - task detail modal state - PRD editor modal state - PRD list loading/refresh - PRD save notification lifecycle Behavior/compatibility notes: - Preserve existing tab behavior, session passthrough props, and Chat/Git/File flows. - Keep interop with existing JS components via boundary `as any` casts where needed. - No intentional functional changes; this commit is structural/type-oriented refactor. Validation: - `npm run typecheck` passes. - `npm run build` passes (existing unrelated CSS minify warnings remain).
This commit is contained in:
110
src/hooks/main-content/useEditorSidebar.ts
Normal file
110
src/hooks/main-content/useEditorSidebar.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import type { MouseEvent as ReactMouseEvent } from 'react';
|
||||
import type { Project } from '../../types/app';
|
||||
import type { DiffInfo, EditingFile } from '../../components/main-content/types';
|
||||
|
||||
type UseEditorSidebarOptions = {
|
||||
selectedProject: Project | null;
|
||||
isMobile: boolean;
|
||||
initialWidth?: number;
|
||||
};
|
||||
|
||||
export function useEditorSidebar({
|
||||
selectedProject,
|
||||
isMobile,
|
||||
initialWidth = 600,
|
||||
}: UseEditorSidebarOptions) {
|
||||
const [editingFile, setEditingFile] = useState<EditingFile | null>(null);
|
||||
const [editorWidth, setEditorWidth] = useState(initialWidth);
|
||||
const [editorExpanded, setEditorExpanded] = useState(false);
|
||||
const [isResizing, setIsResizing] = useState(false);
|
||||
const resizeHandleRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const handleFileOpen = useCallback(
|
||||
(filePath: string, diffInfo: DiffInfo | null = null) => {
|
||||
const normalizedPath = filePath.replace(/\\/g, '/');
|
||||
const fileName = normalizedPath.split('/').pop() || filePath;
|
||||
|
||||
setEditingFile({
|
||||
name: fileName,
|
||||
path: filePath,
|
||||
projectName: selectedProject?.name,
|
||||
diffInfo,
|
||||
});
|
||||
},
|
||||
[selectedProject?.name],
|
||||
);
|
||||
|
||||
const handleCloseEditor = useCallback(() => {
|
||||
setEditingFile(null);
|
||||
setEditorExpanded(false);
|
||||
}, []);
|
||||
|
||||
const handleToggleEditorExpand = useCallback(() => {
|
||||
setEditorExpanded((prev) => !prev);
|
||||
}, []);
|
||||
|
||||
const handleResizeStart = useCallback(
|
||||
(event: ReactMouseEvent<HTMLDivElement>) => {
|
||||
if (isMobile) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsResizing(true);
|
||||
event.preventDefault();
|
||||
},
|
||||
[isMobile],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const handleMouseMove = (event: globalThis.MouseEvent) => {
|
||||
if (!isResizing) {
|
||||
return;
|
||||
}
|
||||
|
||||
const container = resizeHandleRef.current?.parentElement;
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
|
||||
const containerRect = container.getBoundingClientRect();
|
||||
const newWidth = containerRect.right - event.clientX;
|
||||
|
||||
const minWidth = 300;
|
||||
const maxWidth = containerRect.width * 0.8;
|
||||
|
||||
if (newWidth >= minWidth && newWidth <= maxWidth) {
|
||||
setEditorWidth(newWidth);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseUp = () => {
|
||||
setIsResizing(false);
|
||||
};
|
||||
|
||||
if (isResizing) {
|
||||
document.addEventListener('mousemove', handleMouseMove);
|
||||
document.addEventListener('mouseup', handleMouseUp);
|
||||
document.body.style.cursor = 'col-resize';
|
||||
document.body.style.userSelect = 'none';
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('mousemove', handleMouseMove);
|
||||
document.removeEventListener('mouseup', handleMouseUp);
|
||||
document.body.style.cursor = '';
|
||||
document.body.style.userSelect = '';
|
||||
};
|
||||
}, [isResizing]);
|
||||
|
||||
return {
|
||||
editingFile,
|
||||
editorWidth,
|
||||
editorExpanded,
|
||||
resizeHandleRef,
|
||||
handleFileOpen,
|
||||
handleCloseEditor,
|
||||
handleToggleEditorExpand,
|
||||
handleResizeStart,
|
||||
};
|
||||
}
|
||||
50
src/hooks/main-content/useMobileMenuHandlers.ts
Normal file
50
src/hooks/main-content/useMobileMenuHandlers.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { useCallback, useRef } from 'react';
|
||||
import type { MouseEvent, TouchEvent } from 'react';
|
||||
|
||||
type MenuEvent = MouseEvent<HTMLButtonElement> | TouchEvent<HTMLButtonElement>;
|
||||
|
||||
export function useMobileMenuHandlers(onMenuClick: () => void) {
|
||||
const suppressNextMenuClickRef = useRef(false);
|
||||
|
||||
const openMobileMenu = useCallback(
|
||||
(event?: MenuEvent) => {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
onMenuClick();
|
||||
},
|
||||
[onMenuClick],
|
||||
);
|
||||
|
||||
const handleMobileMenuTouchEnd = useCallback(
|
||||
(event: TouchEvent<HTMLButtonElement>) => {
|
||||
suppressNextMenuClickRef.current = true;
|
||||
openMobileMenu(event);
|
||||
|
||||
window.setTimeout(() => {
|
||||
suppressNextMenuClickRef.current = false;
|
||||
}, 350);
|
||||
},
|
||||
[openMobileMenu],
|
||||
);
|
||||
|
||||
const handleMobileMenuClick = useCallback(
|
||||
(event: MouseEvent<HTMLButtonElement>) => {
|
||||
if (suppressNextMenuClickRef.current) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
openMobileMenu(event);
|
||||
},
|
||||
[openMobileMenu],
|
||||
);
|
||||
|
||||
return {
|
||||
handleMobileMenuClick,
|
||||
handleMobileMenuTouchEnd,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user