diff --git a/src/components/AIChat.tsx b/src/components/AIChat.tsx index d6c4718..5c59965 100644 --- a/src/components/AIChat.tsx +++ b/src/components/AIChat.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useState, useCallback, memo } from 'react'; import { Button } from '@/components/ui/button'; import { ScrollArea } from '@/components/ui/scroll-area'; import { Textarea } from '@/components/ui/textarea'; @@ -25,7 +25,7 @@ export function AIChat({ currentCode }: AIChatProps) { const [input, setInput] = useState(''); const [isLoading, setIsLoading] = useState(false); - const handleSend = async () => { + const handleSend = useCallback(async () => { if (!input.trim() || isLoading) return; const userMessage = input.trim(); @@ -61,14 +61,14 @@ export function AIChat({ currentCode }: AIChatProps) { } finally { setIsLoading(false); } - }; + }, [input, isLoading, currentCode]); - const handleKeyDown = (e: React.KeyboardEvent) => { + const handleKeyDown = useCallback((e: React.KeyboardEvent) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSend(); } - }; + }, [handleSend]); return (
diff --git a/src/components/FileTree.tsx b/src/components/FileTree.tsx index 1475c40..3627a6f 100644 --- a/src/components/FileTree.tsx +++ b/src/components/FileTree.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useState, useCallback, memo } from 'react'; import { Button } from '@/components/ui/button'; import { ScrollArea } from '@/components/ui/scroll-area'; import { Input } from '@/components/ui/input'; @@ -52,7 +52,7 @@ export function FileTree({ const [draggedId, setDraggedId] = useState(null); const [dropTargetId, setDropTargetId] = useState(null); - const toggleFolder = (id: string) => { + const toggleFolder = useCallback((id: string) => { setExpandedFolders((prev) => { const next = new Set(prev); if (next.has(id)) { @@ -62,37 +62,37 @@ export function FileTree({ } return next; }); - }; + }, []); - const startRename = (file: FileNode) => { + const startRename = useCallback((file: FileNode) => { setEditingId(file.id); setEditingName(file.name); - }; + }, []); - const finishRename = (id: string) => { + const finishRename = useCallback((id: string) => { if (editingName.trim() && editingName !== '') { onFileRename(id, editingName.trim()); toast.success('File renamed'); } setEditingId(null); setEditingName(''); - }; + }, [editingName, onFileRename]); - const handleDelete = (file: FileNode) => { + const handleDelete = useCallback((file: FileNode) => { if (confirm(`Delete ${file.name}?`)) { onFileDelete(file.id); toast.success('File deleted'); } - }; + }, [onFileDelete]); - const handleDragStart = (e: React.DragEvent, node: FileNode) => { + const handleDragStart = useCallback((e: React.DragEvent, node: FileNode) => { e.stopPropagation(); setDraggedId(node.id); e.dataTransfer.effectAllowed = 'move'; e.dataTransfer.setData('text/plain', node.id); - }; + }, []); - const handleDragOver = (e: React.DragEvent, node: FileNode) => { + const handleDragOver = useCallback((e: React.DragEvent, node: FileNode) => { e.preventDefault(); e.stopPropagation(); @@ -101,15 +101,15 @@ export function FileTree({ e.dataTransfer.dropEffect = 'move'; setDropTargetId(node.id); } - }; + }, [draggedId]); - const handleDragLeave = (e: React.DragEvent) => { + const handleDragLeave = useCallback((e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); setDropTargetId(null); - }; + }, []); - const handleDrop = (e: React.DragEvent, targetNode: FileNode) => { + const handleDrop = useCallback((e: React.DragEvent, targetNode: FileNode) => { e.preventDefault(); e.stopPropagation(); @@ -130,12 +130,12 @@ export function FileTree({ toast.success('File moved'); setDraggedId(null); setDropTargetId(null); - }; + }, [draggedId, onFileMove]); - const handleDragEnd = () => { + const handleDragEnd = useCallback(() => { setDraggedId(null); setDropTargetId(null); - }; + }, []); const renderNode = (node: FileNode, depth: number = 0) => { const isExpanded = expandedFolders.has(node.id); diff --git a/src/components/SearchInFilesPanel.tsx b/src/components/SearchInFilesPanel.tsx index d0f4b3f..6558aac 100644 --- a/src/components/SearchInFilesPanel.tsx +++ b/src/components/SearchInFilesPanel.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useState, useCallback, useMemo } from 'react'; import { ScrollArea } from '@/components/ui/scroll-area'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; @@ -33,7 +33,7 @@ export function SearchInFilesPanel({ const [isSearching, setIsSearching] = useState(false); const [caseSensitive, setCaseSensitive] = useState(false); - const searchInFiles = (query: string) => { + const searchInFiles = useCallback((query: string) => { if (!query.trim()) { setResults([]); return; @@ -77,13 +77,13 @@ export function SearchInFilesPanel({ files.forEach(searchNode); setResults(foundResults); setIsSearching(false); - }; + }, [files, caseSensitive]); - const handleSearch = () => { + const handleSearch = useCallback(() => { searchInFiles(searchQuery); - }; + }, [searchInFiles, searchQuery]); - const handleResultClick = (result: SearchResult) => { + const handleResultClick = useCallback((result: SearchResult) => { const findFile = (nodes: FileNode[]): FileNode | null => { for (const node of nodes) { if (node.id === result.fileId) return node; @@ -99,7 +99,7 @@ export function SearchInFilesPanel({ if (file) { onFileSelect(file, result.line); } - }; + }, [files, onFileSelect]); const highlightMatch = (content: string, start: number, end: number) => { return ( diff --git a/src/components/Toolbar.tsx b/src/components/Toolbar.tsx index adc73b8..a2f77dd 100644 --- a/src/components/Toolbar.tsx +++ b/src/components/Toolbar.tsx @@ -10,7 +10,7 @@ import { } from '@/components/ui/dropdown-menu'; import { Copy, FileCode, Download, Info, Play, FolderPlus, User, SignOut, List } from '@phosphor-icons/react'; import { toast } from 'sonner'; -import { useState, useEffect } from 'react'; +import { useState, useEffect, useCallback, memo } from 'react'; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { ThemeSwitcher } from './ThemeSwitcher'; @@ -33,16 +33,16 @@ export function Toolbar({ code, onTemplatesClick, onPreviewClick, onNewProjectCl } }, []); - const handleCopy = async () => { + const handleCopy = useCallback(async () => { try { await navigator.clipboard.writeText(code); toast.success('Code copied to clipboard!'); } catch (error) { toast.error('Failed to copy code'); } - }; + }, [code]); - const handleExport = () => { + const handleExport = useCallback(() => { const blob = new Blob([code], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); @@ -53,7 +53,7 @@ export function Toolbar({ code, onTemplatesClick, onPreviewClick, onNewProjectCl document.body.removeChild(a); URL.revokeObjectURL(url); toast.success('Script exported!'); - }; + }, [code]); return ( <>