diff --git a/package-lock.json b/package-lock.json
index 8ccedf3..43a7fe3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2016,7 +2016,6 @@
"version": "19.2.9",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.9.tgz",
"integrity": "sha512-Lpo8kgb/igvMIPeNV2rsYKTgaORYdO1XGVZ4Qz3akwOj0ySGYMPlQWa8BaLn0G63D1aSaAQ5ldR06wCpChQCjA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"csstype": "^3.2.2"
@@ -2679,7 +2678,6 @@
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
- "dev": true,
"license": "MIT"
},
"node_modules/debug": {
@@ -5253,7 +5251,6 @@
"version": "4.1.18",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",
"integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==",
- "dev": true,
"license": "MIT"
},
"node_modules/tapable": {
diff --git a/src/components/ThemeSwitcher.tsx b/src/components/ThemeSwitcher.tsx
new file mode 100644
index 0000000..f9de3ef
--- /dev/null
+++ b/src/components/ThemeSwitcher.tsx
@@ -0,0 +1,98 @@
+import { useState } from 'react';
+import { Palette, ChevronUp } from 'lucide-react';
+
+const THEMES = [
+ // Dark Themes
+ { name: 'Monokai', value: '' },
+ { name: 'Dracula', value: 'theme-dracula' },
+ { name: 'Nord', value: 'theme-nord' },
+ { name: 'Solarized Dark', value: 'theme-solarized-dark' },
+ { name: 'One Dark', value: 'theme-one-dark' },
+ { name: 'Gruvbox Dark', value: 'theme-gruvbox-dark' },
+ { name: 'Tokyo Night', value: 'theme-tokyo-night' },
+ { name: 'Material Dark', value: 'theme-material-dark' },
+ // Light Themes
+ { name: 'Light', value: 'theme-light' },
+ { name: 'GitHub Light', value: 'theme-github-light' },
+ { name: 'Solarized Light', value: 'theme-solarized-light' },
+ { name: 'One Light', value: 'theme-one-light' },
+];
+
+export function ThemeSwitcher() {
+ const [isOpen, setIsOpen] = useState(false);
+ const [currentTheme, setCurrentTheme] = useState('');
+
+ const handleThemeChange = (themeValue: string) => {
+ setCurrentTheme(themeValue);
+
+ // Remove all theme classes
+ THEMES.forEach(theme => {
+ if (theme.value) {
+ document.documentElement.classList.remove(theme.value);
+ }
+ });
+
+ // Add selected theme class
+ if (themeValue) {
+ document.documentElement.classList.add(themeValue);
+ }
+
+ setIsOpen(false);
+ };
+
+ // const currentThemeName = THEMES.find(t => t.value === currentTheme)?.name || 'Monokai';
+
+ return (
+
+
+
+ {isOpen && (
+
+ {THEMES.map((theme) => (
+
+ ))}
+
+ )}
+
+ );
+}
diff --git a/src/components/layout/ActivityBar.tsx b/src/components/layout/ActivityBar.tsx
index b88b1f6..fec44d4 100644
--- a/src/components/layout/ActivityBar.tsx
+++ b/src/components/layout/ActivityBar.tsx
@@ -1,17 +1,23 @@
-import { Files, Search, GitBranch, Settings, CircleUser } from 'lucide-react';
+import { Files, Search, GitBranch, CircleUser, Lock } from 'lucide-react';
import { ActivityBarItem } from './ActivityBarItem';
export function ActivityBar() {
return (
-
+
);
diff --git a/src/components/layout/ActivityBarItem.tsx b/src/components/layout/ActivityBarItem.tsx
index 25ca5f3..55346ec 100644
--- a/src/components/layout/ActivityBarItem.tsx
+++ b/src/components/layout/ActivityBarItem.tsx
@@ -7,7 +7,24 @@ interface ActivityBarItemProps {
export function ActivityBarItem({ icon: Icon, active = false }: ActivityBarItemProps) {
return (
-
+
{
+ if (!active) {
+ e.currentTarget.style.color = 'var(--activity-item-text-hover)';
+ }
+ }}
+ onMouseLeave={(e) => {
+ if (!active) {
+ e.currentTarget.style.color = 'var(--activity-item-text-default)';
+ }
+ }}
+ >
);
diff --git a/src/components/layout/MainLayout.tsx b/src/components/layout/MainLayout.tsx
index 16b4b97..c39e4b2 100644
--- a/src/components/layout/MainLayout.tsx
+++ b/src/components/layout/MainLayout.tsx
@@ -2,6 +2,8 @@ import type { ReactNode } from 'react';
import { Panel, Group, Separator } from 'react-resizable-panels';
import { ActivityBar } from './ActivityBar';
import { TitleBar } from './TitleBar';
+import { useAppStore } from '../../store/useAppStore';
+import { ChevronRight } from 'lucide-react';
interface MainLayoutProps {
sidebarContent: ReactNode;
@@ -9,8 +11,16 @@ interface MainLayoutProps {
}
export function MainLayout({ sidebarContent, editorContent }: MainLayoutProps) {
+ const { isExplorerCollapsed, toggleExplorerCollapsed } = useAppStore();
+
return (
-
+
@@ -21,17 +31,57 @@ export function MainLayout({ sidebarContent, editorContent }: MainLayoutProps) {
{/* Sidebar Panel */}
-
+ {!isExplorerCollapsed && (
+ <>
+
-
+
+ >
+ )}
+
+ {/* Collapsed Sidebar - VS Code Style */}
+ {isExplorerCollapsed && (
+
+
+
+ )}
{/* Editor Panel */}
-
-
+
+
{editorContent}
diff --git a/src/components/layout/TitleBar.tsx b/src/components/layout/TitleBar.tsx
index 0e2bf01..8858ff8 100644
--- a/src/components/layout/TitleBar.tsx
+++ b/src/components/layout/TitleBar.tsx
@@ -1,7 +1,18 @@
export function TitleBar() {
return (
-
-
Cinder Notes
+
+
+ Cinder Notes
+
);
}
diff --git a/src/components/layout/editor/Editor.tsx b/src/components/layout/editor/Editor.tsx
index 8440ceb..27e227d 100644
--- a/src/components/layout/editor/Editor.tsx
+++ b/src/components/layout/editor/Editor.tsx
@@ -1,59 +1,97 @@
-import { useState } from 'react';
import { useAppStore } from '../../../store/useAppStore';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
-import { Eye, Edit2 } from 'lucide-react';
+import { Eye, ChevronLeft, FileText, Save, Sparkles } from 'lucide-react';
-export function Editor() {
+interface EditorProps {
+ isPreview: boolean;
+ onPreviewChange?: (isPreview: boolean) => void;
+}
+
+export function Editor({ isPreview }: EditorProps) {
const { activeFileId, activeFileContent, updateFileContent } = useAppStore();
- const [isPreview, setIsPreview] = useState(false);
+
+ // Check if this is a blank tab
+ const isBlankTab = activeFileId?.startsWith('new-tab-');
// If no active file, just show empty state in main area
// Tabs are now handled by EditorPane
return (
-
+
- {!activeFileId ? (
-
+ {!activeFileId || isBlankTab ? (
+
-
No file selected
+
+
+
+
Cinder Notes
+
+
+
+
Select a file from the explorer to start editing
+
+
+
+
Create new notes in Markdown format
+
+
+
+
Toggle preview mode to see rendered content
+
+
+
+
Auto-saves as you type
+
+
) : (
- {/* Toolbar/Toggle */}
-
-
{isPreview ? (
-
+
{activeFileContent}
) : (
-
)}
diff --git a/src/components/layout/editor/EditorHeader.tsx b/src/components/layout/editor/EditorHeader.tsx
new file mode 100644
index 0000000..203ce5e
--- /dev/null
+++ b/src/components/layout/editor/EditorHeader.tsx
@@ -0,0 +1,92 @@
+import { Undo2, Redo2, Eye, Edit2 } from 'lucide-react';
+import { useAppStore } from '../../../store/useAppStore';
+
+interface EditorHeaderProps {
+ isPreview: boolean;
+ onPreviewToggle: () => void;
+}
+
+export function EditorHeader({ isPreview, onPreviewToggle }: EditorHeaderProps) {
+ const { activeFileId, findFile } = useAppStore();
+
+ // Check if this is a blank tab
+ const isBlankTab = activeFileId?.startsWith('new-tab-');
+ const file = activeFileId && !isBlankTab ? findFile(activeFileId) : null;
+ const tabName = isBlankTab ? 'New Tab' : file?.name || '';
+
+ // Only show header if there's an active file or blank tab
+ if (!activeFileId) return null;
+
+ return (
+
+ {/* Left side: Undo/Redo buttons */}
+
+
+
+
+
+ {/* Center: Tab name */}
+
+ {tabName}
+
+
+ {/* Right side: Preview toggle button */}
+
+
+ );
+}
diff --git a/src/components/layout/editor/EditorPane.tsx b/src/components/layout/editor/EditorPane.tsx
index e34042c..ce3b081 100644
--- a/src/components/layout/editor/EditorPane.tsx
+++ b/src/components/layout/editor/EditorPane.tsx
@@ -1,13 +1,21 @@
+import { useState } from 'react';
import { Editor } from './Editor';
import { EditorTabs } from './EditorTabs';
+import { EditorHeader } from './EditorHeader';
import { EditorStatusBar } from './EditorStatusBar';
export function EditorPane() {
+ const [isPreview, setIsPreview] = useState(false);
+
return (
-
+
+
setIsPreview(!isPreview)} />
-
+
diff --git a/src/components/layout/editor/EditorStatusBar.tsx b/src/components/layout/editor/EditorStatusBar.tsx
index 2f2bbc1..3948237 100644
--- a/src/components/layout/editor/EditorStatusBar.tsx
+++ b/src/components/layout/editor/EditorStatusBar.tsx
@@ -3,7 +3,14 @@ import { EditorStatusBarItem } from './EditorStatusBarItem';
export function EditorStatusBar() {
return (
-
+
diff --git a/src/components/layout/editor/EditorStatusBarItem.tsx b/src/components/layout/editor/EditorStatusBarItem.tsx
index c793ef5..d4ff5f7 100644
--- a/src/components/layout/editor/EditorStatusBarItem.tsx
+++ b/src/components/layout/editor/EditorStatusBarItem.tsx
@@ -2,7 +2,14 @@ import type { ReactNode } from 'react';
export function EditorStatusBarItem({ children, className = '' }: { children: ReactNode, className?: string }) {
return (
-
+
e.currentTarget.style.backgroundColor = 'var(--bg-tertiary)'}
+ onMouseLeave={(e) => e.currentTarget.style.backgroundColor = 'transparent'}
+ >
{children}
)
diff --git a/src/components/layout/editor/EditorTabs.tsx b/src/components/layout/editor/EditorTabs.tsx
index cda03c1..6f3f2e9 100644
--- a/src/components/layout/editor/EditorTabs.tsx
+++ b/src/components/layout/editor/EditorTabs.tsx
@@ -1,38 +1,76 @@
-
-import { X } from 'lucide-react';
+import { X, Plus } from 'lucide-react';
import { useAppStore } from '../../../store/useAppStore';
export function EditorTabs() {
- const { openFiles, activeFileId, selectFile, closeFile, findFile } = useAppStore();
-
- if (openFiles.length === 0) return null;
+ const { openFiles, activeFileId, selectFile, closeFile, findFile, createNewTab } = useAppStore();
return (
-
+
{openFiles.map(fileId => {
const file = findFile(fileId);
const isActive = activeFileId === fileId;
- if (!file) return null;
+
+ // Check if this is a blank tab (new-tab-X format)
+ const isBlankTab = fileId.startsWith('new-tab-');
+ const tabName = isBlankTab ? 'New Tab' : file?.name;
return (
selectFile(fileId)}
- className={`
- group flex items-center min-w-[120px] max-w-[200px] h-[35px] px-3 border-r border-[#171717]/50 cursor-pointer text-[13px] select-none
- ${isActive ? 'bg-[#272822] text-[#f8f8f2]' : 'bg-[#1e1f1c] text-[#75715e] hover:bg-[#272822]/50'}
- `}
+ className={`group flex items-center min-w-[120px] max-w-[200px] h-[35px] px-3 border-r cursor-pointer text-[13px] select-none transition-colors`}
+ style={{
+ borderColor: 'var(--border-primary)',
+ backgroundColor: isActive ? 'var(--bg-secondary)' : 'transparent',
+ color: isActive ? 'var(--text-white)' : 'var(--text-tertiary)',
+ fontStyle: isBlankTab && !isActive ? 'italic' : 'normal',
+ opacity: isBlankTab && !isActive ? 0.8 : 1
+ }}
+ onMouseEnter={(e) => {
+ if (!isActive) {
+ e.currentTarget.style.backgroundColor = 'var(--bg-hover)';
+ }
+ }}
+ onMouseLeave={(e) => {
+ if (!isActive) {
+ e.currentTarget.style.backgroundColor = 'transparent';
+ }
+ }}
>
- {file.name}
+ {tabName}
{ e.stopPropagation(); closeFile(fileId); }}
- className={`opacity-0 group-hover:opacity-100 p-0.5 rounded hover:bg-[#3e3d32] ${isActive ? 'text-[#f8f8f2]' : ''}`}
+ className={`opacity-0 group-hover:opacity-100 p-0.5 rounded transition-colors`}
+ style={{
+ color: isActive ? 'var(--text-white)' : 'var(--text-tertiary)'
+ }}
+ onMouseEnter={(e) => e.currentTarget.style.backgroundColor = 'var(--bg-tertiary)'}
+ onMouseLeave={(e) => e.currentTarget.style.backgroundColor = 'transparent'}
>
)
})}
+
)
}
diff --git a/src/components/layout/explorer/ExplorerActions.tsx b/src/components/layout/explorer/ExplorerActions.tsx
index ead0b8d..5463109 100644
--- a/src/components/layout/explorer/ExplorerActions.tsx
+++ b/src/components/layout/explorer/ExplorerActions.tsx
@@ -1,20 +1,51 @@
-import { FilePlus, FolderPlus, RefreshCw, MinusSquare } from 'lucide-react';
+import { FilePlus, FolderPlus, RefreshCw, ChevronLeft } from 'lucide-react';
+import { useAppStore } from '../../../store/useAppStore';
export function ExplorerActions() {
+ const { toggleExplorerCollapsed } = useAppStore();
+
return (
-
-