diff --git a/src/App.tsx b/src/App.tsx index 18dc951..4755be2 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -14,11 +14,18 @@ function App() { const [showFavoritesOnly, setShowFavoritesOnly] = useState(false); const [fontSize] = useState(14); const [username, setUsername] = useState(''); + const [theme, setTheme] = useState<'light' | 'dark' | 'system'>('system'); + const [effectiveTheme, setEffectiveTheme] = useState<'light' | 'dark'>('light'); useEffect(() => { const savedServer = localStorage.getItem('serverURL'); const savedUsername = localStorage.getItem('username'); const savedPassword = localStorage.getItem('password'); + const savedTheme = localStorage.getItem('theme') as 'light' | 'dark' | 'system' | null; + + if (savedTheme) { + setTheme(savedTheme); + } if (savedServer && savedUsername && savedPassword) { const apiInstance = new NextcloudAPI({ @@ -32,6 +39,30 @@ function App() { } }, []); + useEffect(() => { + const updateEffectiveTheme = () => { + if (theme === 'system') { + const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches; + setEffectiveTheme(isDark ? 'dark' : 'light'); + } else { + setEffectiveTheme(theme); + } + }; + + updateEffectiveTheme(); + + if (theme === 'system') { + const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); + const handler = () => updateEffectiveTheme(); + mediaQuery.addEventListener('change', handler); + return () => mediaQuery.removeEventListener('change', handler); + } + }, [theme]); + + useEffect(() => { + document.documentElement.classList.toggle('dark', effectiveTheme === 'dark'); + }, [effectiveTheme]); + useEffect(() => { if (api && isLoggedIn) { syncNotes(); @@ -75,6 +106,11 @@ function App() { setIsLoggedIn(false); }; + const handleThemeChange = (newTheme: 'light' | 'dark' | 'system') => { + setTheme(newTheme); + localStorage.setItem('theme', newTheme); + }; + const handleCreateNote = async () => { if (!api) return; try { @@ -152,6 +188,8 @@ function App() { onSync={syncNotes} onLogout={handleLogout} username={username} + theme={theme} + onThemeChange={handleThemeChange} searchText={searchText} onSearchChange={setSearchText} showFavoritesOnly={showFavoritesOnly} diff --git a/src/components/NoteEditor.tsx b/src/components/NoteEditor.tsx index 0843576..c2a66bc 100644 --- a/src/components/NoteEditor.tsx +++ b/src/components/NoteEditor.tsx @@ -142,22 +142,22 @@ export function NoteEditor({ note, onUpdateNote, fontSize }: NoteEditorProps) { } return ( -
-
+
+
handleTitleChange(e.target.value)} placeholder="Note Title" - className="flex-1 text-2xl font-bold border-none outline-none focus:ring-0" + className="flex-1 text-2xl font-bold border-none outline-none focus:ring-0 bg-transparent text-gray-900 dark:text-gray-100" />
{hasUnsavedChanges && ( - Unsaved changes + Unsaved changes )} {isSaving && ( - Saving... + Saving... )}
{/* Formatting Toolbar */} -
+
- {notes.length} notes + {notes.length} notes
{notes.length === 0 ? ( -
+
@@ -125,8 +129,8 @@ export function NotesList({
onSelectNote(note.id)} - className={`p-4 border-b border-gray-200 cursor-pointer hover:bg-gray-100 transition-colors ${ - selectedNoteId === note.id ? 'bg-blue-50 hover:bg-blue-50' : '' + className={`p-3 border-b border-gray-200 dark:border-gray-700 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors group ${ + note.id === selectedNoteId ? 'bg-blue-50 dark:bg-gray-800 border-l-4 border-l-blue-500' : '' }`} >
@@ -136,7 +140,7 @@ export function NotesList({ )} -

+

{note.title || 'Untitled'}

@@ -145,7 +149,7 @@ export function NotesList({ e.stopPropagation(); onDeleteNote(note); }} - className="ml-2 p-1 hover:bg-red-100 rounded text-red-600 opacity-0 group-hover:opacity-100 transition-opacity" + className="ml-2 p-1 hover:bg-red-100 dark:hover:bg-red-900/30 rounded text-red-600 dark:text-red-400 opacity-0 group-hover:opacity-100 transition-opacity" title="Delete" > @@ -154,9 +158,9 @@ export function NotesList({
-
+
{note.category && ( - + {note.category} )} @@ -164,7 +168,7 @@ export function NotesList({
{getPreview(note.content) && ( -

+

{getPreview(note.content)}

)} @@ -174,24 +178,70 @@ export function NotesList({
{/* User Info and Logout */} -
-
+
+
{username.charAt(0).toUpperCase()}
- {username} + {username}
+ + {/* Theme Toggle */} +
+ Theme +
+ + + +
+
); diff --git a/src/index.css b/src/index.css index 0c76da7..e663abf 100644 --- a/src/index.css +++ b/src/index.css @@ -19,6 +19,11 @@ code { /* TipTap Editor Styles */ .ProseMirror { min-height: 100%; + color: #111827; +} + +.dark .ProseMirror { + color: #f3f4f6; } .ProseMirror:focus { @@ -72,6 +77,12 @@ code { border-radius: 0.25rem; font-family: 'Courier New', monospace; font-size: 0.9em; + color: #1f2937; +} + +.dark .ProseMirror code { + background-color: #374151; + color: #f3f4f6; } .ProseMirror pre { @@ -83,6 +94,10 @@ code { margin: 1em 0; } +.dark .ProseMirror pre { + background-color: #111827; +} + .ProseMirror pre code { background-color: transparent; padding: 0; diff --git a/tailwind.config.js b/tailwind.config.js index e9baf90..5fecf58 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,5 +1,6 @@ /** @type {import('tailwindcss').Config} */ export default { + darkMode: 'class', content: [ "./index.html", "./src/**/*.{js,ts,jsx,tsx}",