diff --git a/src/App.tsx b/src/App.tsx index 5c4f0d9..6af2744 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -81,6 +81,7 @@ const isValidWorkspaceBlocks = (blocks: any): blocks is Block[] => { }; export default function App() { + const { isDarkMode, toggleDarkMode } = useDarkMode(); const [blocks, setBlocks] = useState(initialDemoBlocks); const [selectedBlockId, setSelectedBlockId] = useState('demo-1'); const [activeParentId, setActiveParentId] = useState(null); @@ -122,40 +123,6 @@ export default function App() { } catch {} }, []); - // Dark mode state - const [isDarkMode, setIsDarkMode] = useState(() => { - let initialDark = window.matchMedia('(prefers-color-scheme: dark)').matches; - try { - const saved = localStorage.getItem('flowforge_dark_mode'); - if (saved) initialDark = saved === 'true'; - } catch { - // Ignore storage errors - } - - // Apply class pre-paint - if (initialDark) { - document.documentElement.classList.add('dark'); - } else { - document.documentElement.classList.remove('dark'); - } - return initialDark; - }); - - useEffect(() => { - // Preserve ongoing theme synchronization after mount - if (isDarkMode) { - document.documentElement.classList.add('dark'); - } else { - document.documentElement.classList.remove('dark'); - } - try { - localStorage.setItem('flowforge_dark_mode', String(isDarkMode)); - } catch { - // Ignore storage errors - } - }, [isDarkMode]); - - const toggleDarkMode = () => setIsDarkMode((prev) => !prev); // Function to push a toast const showToast = (message: string, type: 'success' | 'info' | 'error' = 'success') => { diff --git a/src/components/CenterCanvas.tsx b/src/components/CenterCanvas.tsx index 7652c80..d706713 100644 --- a/src/components/CenterCanvas.tsx +++ b/src/components/CenterCanvas.tsx @@ -420,6 +420,7 @@ export default function CenterCanvas({ - + {Math.round(scale * 100)}% @@ -849,16 +852,16 @@ export default function CenterCanvas({ {/* Confirmation Dialog Modal */} {showConfirmModal && (
-
-

New Flowchart Confirmation

-

+

+

New Flowchart Confirmation

+

Start a new flowchart? Current work will be lost.

diff --git a/src/components/LeftSidebar.tsx b/src/components/LeftSidebar.tsx index 46986fd..7335976 100644 --- a/src/components/LeftSidebar.tsx +++ b/src/components/LeftSidebar.tsx @@ -129,6 +129,7 @@ export default function LeftSidebar({ type="button" onClick={onCancelActiveParent} title="Cancel chain connection" + aria-label="Cancel chain connection" className="p-1 hover:bg-indigo-100 rounded-md text-indigo-500 hover:text-indigo-700 transition-colors cursor-pointer" > @@ -138,7 +139,7 @@ export default function LeftSidebar({
- +
{shapeCards.map((card) => { const isSelected = selectedType === card.type; @@ -251,21 +252,23 @@ export default function LeftSidebar({
diff --git a/src/components/Toast.tsx b/src/components/Toast.tsx index e226c9b..e4b3819 100644 --- a/src/components/Toast.tsx +++ b/src/components/Toast.tsx @@ -65,7 +65,8 @@ function ToastItem({ toast, onClose }: { toast: ToastConfig; onClose: (id: strin
diff --git a/src/index.css b/src/index.css index 54f8224..862c0d5 100644 --- a/src/index.css +++ b/src/index.css @@ -19,6 +19,11 @@ body { overflow: hidden; } +.dark body { + background-color: #0f172a; /* slate-900 */ + color: #f1f5f9; /* slate-100 */ +} + /* Custom scrollbars */ .custom-scrollbar::-webkit-scrollbar { width: 5px; diff --git a/src/utils/useDarkMode.ts b/src/utils/useDarkMode.ts new file mode 100644 index 0000000..6773abc --- /dev/null +++ b/src/utils/useDarkMode.ts @@ -0,0 +1,56 @@ +import { useState, useEffect } from 'react'; + +export function useDarkMode() { + const [isDarkMode, setIsDarkMode] = useState(() => { + try { + const stored = localStorage.getItem('flowforge_theme'); + if (stored === 'dark') return true; + if (stored === 'light') return false; + + // Fallback to system preference + return window.matchMedia('(prefers-color-scheme: dark)').matches; + } catch { + return false; + } + }); + + useEffect(() => { + const root = window.document.documentElement; + if (isDarkMode) { + root.classList.add('dark'); + } else { + root.classList.remove('dark'); + } + }, [isDarkMode]); + + useEffect(() => { + const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); + const handleChange = (e: MediaQueryListEvent) => { + try { + const stored = localStorage.getItem('flowforge_theme'); + if (!stored) { + setIsDarkMode(e.matches); + } + } catch { + // ignore + } + }; + + mediaQuery.addEventListener('change', handleChange); + return () => mediaQuery.removeEventListener('change', handleChange); + }, []); + + const toggleDarkMode = () => { + setIsDarkMode((prev) => { + const next = !prev; + try { + localStorage.setItem('flowforge_theme', next ? 'dark' : 'light'); + } catch { + // ignore + } + return next; + }); + }; + + return { isDarkMode, toggleDarkMode }; +}