From 50c7bb68bb83f68c7c854d9106fcdef6e0eafbbf Mon Sep 17 00:00:00 2001 From: CC Worker Date: Sun, 31 May 2026 21:21:06 +0000 Subject: [PATCH] refactor: remove MUI and styled-components from BasePanel\n\n- Replace MUI Button/Menu/MenuItem with cc-btn + cc-menu + native div/button\n- Replace MUI icons with inline SVG icons\n- Replace MUI ThemeProvider/useMediaQuery with dataset/colorMode\n- Preserve TldrawUiButton for pin control in panel header\n- Minor: keep local panel state instead of MUI Menu open state --- .../components/shared/BasePanel.tsx | 412 +++++++++--------- 1 file changed, 197 insertions(+), 215 deletions(-) diff --git a/src/utils/tldraw/ui-overrides/components/shared/BasePanel.tsx b/src/utils/tldraw/ui-overrides/components/shared/BasePanel.tsx index 08a6b25..ab10b8e 100644 --- a/src/utils/tldraw/ui-overrides/components/shared/BasePanel.tsx +++ b/src/utils/tldraw/ui-overrides/components/shared/BasePanel.tsx @@ -1,31 +1,6 @@ -import React, { useEffect, useRef, useMemo } from 'react'; +import React, { useEffect, useRef, useMemo, useState } from 'react'; import { useLocation } from 'react-router-dom'; import { TldrawUiButton } from '@tldraw/tldraw'; -import { - Button, - Menu, - MenuItem, - ListItemIcon, - ListItemText, - styled, - ThemeProvider, - createTheme, - useMediaQuery -} from '@mui/material'; -import { - PushPin as PushPinIcon, - PushPinOutlined as PushPinOutlinedIcon, - ExpandMore as ExpandMoreIcon, - Category as ShapesIcon, - Slideshow as SlidesIcon, - YouTube as YouTubeIcon, - AccountTree as GraphIcon, - Search as SearchIcon, - Navigation as NavigationIcon, - Save as NodeIcon, - Assignment as ExamIcon, - Mic as MicIcon -} from '@mui/icons-material'; import { CCShapesPanel } from './CCShapesPanel'; import { CCSlidesPanel } from './CCSlidesPanel'; import { CCFilesPanel } from './CCFilesPanel'; @@ -33,9 +8,9 @@ import { CCCabinetsPanel } from './CCCabinetsPanel'; import { CCYoutubePanel } from './CCYoutubePanel'; import { CCGraphPanel } from './CCGraphPanel'; import { CCExamMarkerPanel } from './CCExamMarkerPanel'; -import { CCSearchPanel } from './CCSearchPanel' -import { CCGraphNavPanel } from './navigation/CCGraphNavPanel' -import { CCTranscriptionPanel } from './CCTranscriptionPanel' +import { CCSearchPanel } from './CCSearchPanel'; +import { CCGraphNavPanel } from './navigation/CCGraphNavPanel'; +import { CCTranscriptionPanel } from './CCTranscriptionPanel'; import { PANEL_DIMENSIONS, Z_INDICES } from './panel-styles'; import './panel.css'; // import { CCNavigationPanel } from './navigation/CCNavigationPanel'; @@ -78,45 +53,102 @@ interface BasePanelProps { onMenuOpenChange?: (open: boolean) => void; } -const PanelTypeButton = styled(Button)(() => ({ - textTransform: 'none', - padding: '6px 12px', - gap: '8px', - backgroundColor: 'var(--color-panel)', - color: 'var(--color-text)', - border: '1px solid transparent', - transition: 'border-color 200ms ease', - justifyContent: 'space-between', - minWidth: '200px', - '&:hover': { - backgroundColor: 'var(--color-panel)', - borderColor: 'var(--color-text)', - }, - '& .MuiSvgIcon-root': { - fontSize: '1.25rem', - color: 'inherit', +function getIconSvg(panelId: PanelType, size = '1.25rem') { + const common = { width: size, height: size, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: '2', strokeLinecap: 'round' as const, strokeLinejoin: 'round' as const }; + switch (panelId) { + case 'cabinets': + return ( + + + + + ); + case 'transcription': + return ( + + + + + + + + ); + case 'cc-shapes': + return ( + + + + + + + ); + case 'slides': + return ( + + + + + ); + case 'youtube': + return ( + + + + + ); + case 'graph': + return ( + + + + + + + + ); + case 'search': + return ( + + + + + ); + case 'navigation': + return ( + + + + ); + case 'node-snapshot': + return ( + + + + ); + case 'exam-marker': + return ( + + + + + + + + ); + default: + return ( + + + + ); } -})); +} -const StyledMenuItem = styled(MenuItem)(() => ({ - gap: '8px', - padding: '8px 16px', - transition: 'background-color 200ms ease', - '&:hover': { - backgroundColor: 'var(--color-hover)', - '& .MuiListItemIcon-root': { - color: 'var(--color-selected)', - } - }, - '& .MuiListItemIcon-root': { - color: 'var(--color-text)', - minWidth: '32px', - transition: 'color 200ms ease', - '& .MuiSvgIcon-root': { - fontSize: '1.25rem', - } - } -})); +function isDarkMode() { + if (typeof window === 'undefined') return false; + return document.documentElement.dataset.colorMode === 'dark' || + window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; +} export const BasePanel: React.FC = ({ initialPanelType = 'files', @@ -130,45 +162,39 @@ export const BasePanel: React.FC = ({ }) => { const location = useLocation(); const { tldrawPreferences } = useTLDraw(); - const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)'); - const [menuAnchorEl, setMenuAnchorEl] = React.useState(null); - - // Create a dynamic theme based on TLDraw preferences - const theme = useMemo(() => { - let mode: 'light' | 'dark'; - - if (tldrawPreferences?.colorScheme === 'system') { - mode = prefersDarkMode ? 'dark' : 'light'; - } else { - mode = tldrawPreferences?.colorScheme === 'dark' ? 'dark' : 'light'; - } + const [prefersDarkMode, setPrefersDarkMode] = useState(isDarkMode()); + const [menuOpen, setMenuOpen] = useState(false); + const menuRef = useRef(null); - return createTheme({ - palette: { - mode, - }, - }); - }, [tldrawPreferences?.colorScheme, prefersDarkMode]); + useEffect(() => { + const mq = window.matchMedia('(prefers-color-scheme: dark)'); + const handler = () => setPrefersDarkMode(isDarkMode()); + mq.addEventListener?.('change', handler); + return () => mq.removeEventListener?.('change', handler); + }, []); + + const colorMode = (tldrawPreferences?.colorScheme === 'system' ? prefersDarkMode : tldrawPreferences?.colorScheme === 'dark') + ? 'dark' + : 'light'; const isExamMarkerRoute = location.pathname === '/exam-marker'; const availablePanels = isExamMarkerRoute ? PANEL_TYPES.examMarker : PANEL_TYPES.default; - - const [currentPanelType, setCurrentPanelType] = React.useState( + + const [currentPanelType, setCurrentPanelType] = useState( isExamMarkerRoute ? 'exam-marker' : initialPanelType ); - - // Use controlled state if provided, otherwise use internal state - const [internalIsExpanded, setInternalIsExpanded] = React.useState(true); - const [internalIsPinned, setInternalIsPinned] = React.useState(true); - + + const [internalIsExpanded, setInternalIsExpanded] = useState(true); + const [internalIsPinned, setInternalIsPinned] = useState(true); + const isExpanded = controlledIsExpanded ?? internalIsExpanded; const isPinned = controlledIsPinned ?? internalIsPinned; - + const handleExpandedChange = (expanded: boolean) => { setInternalIsExpanded(expanded); onExpandedChange?.(expanded); }; - + const handlePinToggle = () => { const newPinned = !isPinned; setInternalIsPinned(newPinned); @@ -178,87 +204,24 @@ export const BasePanel: React.FC = ({ const panelRef = useRef(null); const dimensions = PANEL_DIMENSIONS[currentPanelType as keyof typeof PANEL_DIMENSIONS]; - // Handle click outside useEffect(() => { const handleClickOutside = (event: MouseEvent) => { - // Don't close if pinned if (isPinned) return; - - // Check if click is outside panel const isClickOutside = panelRef.current && !panelRef.current.contains(event.target as Node); - - // Check if click is not on a panel-related element const target = event.target as HTMLElement; const isPanelElement = target.closest('.panel-root, .panel-handle, .tlui-button'); - if (isClickOutside && !isPanelElement) { handleExpandedChange(false); } }; - if (isExpanded) { document.addEventListener('mousedown', handleClickOutside); } - return () => { document.removeEventListener('mousedown', handleClickOutside); }; }, [isExpanded, isPinned]); - const getIconForPanel = (panelId: PanelType) => { - switch (panelId) { - case 'cabinets': - return ; - case 'cc-shapes': - return ; - case 'transcription': - return ; - case 'slides': - return ; - case 'youtube': - return ; - case 'graph': - return ; - case 'search': - return ; - case 'navigation': - return ; - case 'node-snapshot': - return ; - case 'exam-marker': - return ; - default: - return ; - } - }; - - const getDescriptionForPanel = (panelId: PanelType) => { - switch (panelId) { - case 'cabinets': - return 'Manage file cabinets'; - case 'transcription': - return 'Record and transcribe lessons'; - case 'cc-shapes': - return 'Add shapes and elements to your canvas'; - case 'slides': - return 'Manage presentation slides'; - case 'youtube': - return 'Embed YouTube videos'; - case 'graph': - return 'View and manage graph connections'; - case 'search': - return 'Search through your content'; - case 'navigation': - return 'Navigate through different contexts'; - case 'node-snapshot': - return 'Manage node snapshots'; - case 'exam-marker': - return 'Mark and grade exams'; - default: - return ''; - } - }; - const renderCurrentPanel = () => { if (isExamMarkerRoute && currentPanelType === 'exam-marker') { return examMarkerProps ? : null; @@ -288,22 +251,15 @@ export const BasePanel: React.FC = ({ } }; - // Handle menu button click - const handleMenuClick = (event: React.MouseEvent) => { - setMenuAnchorEl(event.currentTarget); - onMenuOpenChange(true); - }; - - // Handle menu close - const handleMenuClose = () => { - setMenuAnchorEl(null); - onMenuOpenChange(false); + const toggleMenu = (open: boolean) => { + setMenuOpen(open); + onMenuOpenChange(open); }; return ( <> {!isExpanded && ( -
handleExpandedChange(true)} onTouchEnd={(e) => { @@ -316,7 +272,7 @@ export const BasePanel: React.FC = ({ )} {isExpanded && ( -
= ({ }} >
- - } - startIcon={getIconForPanel(currentPanelType)} - > + - + {menuOpen && ( +
{[...availablePanels] .sort((a, b) => a.order - b.order) .map(type => ( - { - setCurrentPanelType(type.id as PanelType); - handleMenuClose(); - }} - selected={currentPanelType === type.id} - > - - {getIconForPanel(type.id as PanelType)} - - { + setCurrentPanelType(type.id as PanelType); + toggleMenu(false); }} - secondaryTypographyProps={{ - sx: { color: 'var(--color-text-secondary)' } - }} - /> - - ))} -
-
+ > + + {getIconSvg(type.id as PanelType)} + + + {type.label} + + {type.id === 'cabinets' && 'Manage file cabinets'} + {type.id === 'transcription' && 'Record and transcribe lessons'} + {type.id === 'cc-shapes' && 'Add shapes and elements to your canvas'} + {type.id === 'slides' && 'Manage presentation slides'} + {type.id === 'youtube' && 'Embed YouTube videos'} + {type.id === 'graph' && 'View and manage graph connections'} + {type.id === 'search' && 'Search through your content'} + {type.id === 'navigation' && 'Navigate through different contexts'} + {type.id === 'node-snapshot' && 'Manage node snapshots'} + {type.id === 'exam-marker' && 'Mark and grade exams'} + + + + ))} +
+ )}
= ({ onClick={handlePinToggle} className="pin-button" > - {isPinned ? : } + + + {isPinned ? ( + <> + + + ) : ( + <> + + + + )} + +
-
{renderCurrentPanel()}
-
)} ); -}; \ No newline at end of file +};