feat: add sidebar panel system with icon strip and panel toggle

This commit is contained in:
2026-03-04 15:45:34 +05:30
parent ed48f254f1
commit affe1a7e33
4 changed files with 247 additions and 2 deletions
+45 -2
View File
@@ -8,6 +8,7 @@
<link rel="stylesheet" href="styles.css"> <link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="styles-modern.css"> <link rel="stylesheet" href="styles-modern.css">
<link rel="stylesheet" href="styles-concreteinfo.css"> <link rel="stylesheet" href="styles-concreteinfo.css">
<link rel="stylesheet" href="styles-sidebar.css">
<!-- Load external resources asynchronously --> <!-- Load external resources asynchronously -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" media="print" onload="this.media='all'"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css" media="print" onload="this.media='all'">
<script defer src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script> <script defer src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
@@ -1293,7 +1294,48 @@
</div> </div>
</div> </div>
<div class="editor-container"> <div class="main-content" id="main-content">
<!-- Sidebar -->
<div class="sidebar collapsed" id="sidebar">
<div class="sidebar-icons">
<button class="sidebar-icon" data-panel="explorer" title="File Explorer (Ctrl+Shift+E)">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M22 19a2 2 0 01-2 2H4a2 2 0 01-2-2V5a2 2 0 012-2h5l2 3h9a2 2 0 012 2z"/>
</svg>
</button>
<button class="sidebar-icon" data-panel="git" title="Git (Ctrl+Shift+G)">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="6" y1="3" x2="6" y2="15"/>
<circle cx="18" cy="6" r="3"/>
<circle cx="6" cy="6" r="3"/>
<path d="M18 9a9 9 0 01-9 9"/>
</svg>
</button>
<button class="sidebar-icon" data-panel="snippets" title="Snippets (Ctrl+Shift+S)">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="16 18 22 12 16 6"/>
<polyline points="8 6 2 12 8 18"/>
</svg>
</button>
<button class="sidebar-icon" data-panel="templates" title="Templates">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
<line x1="3" y1="9" x2="21" y2="9"/>
<line x1="9" y1="21" x2="9" y2="9"/>
</svg>
</button>
</div>
<div class="sidebar-panel" id="sidebar-panel">
<div class="sidebar-panel-header">
<span class="sidebar-panel-title"></span>
<button class="sidebar-panel-close" title="Close panel">&times;</button>
</div>
<div class="sidebar-panel-content" id="sidebar-panel-content"></div>
</div>
</div>
<!-- Editor Container -->
<div class="editor-container">
<div class="tab-content active" id="tab-content-1" data-tab-id="1"> <div class="tab-content active" id="tab-content-1" data-tab-id="1">
<div id="editor-pane-1" class="pane"> <div id="editor-pane-1" class="pane">
<div class="editor-wrapper"> <div class="editor-wrapper">
@@ -1403,8 +1445,9 @@
<canvas id="pdf-canvas"></canvas> <canvas id="pdf-canvas"></canvas>
</div> </div>
</div> </div>
</div>
</div> </div>
<div class="status-bar"> <div class="status-bar">
<span id="status-text">Ready</span> <span id="status-text">Ready</span>
<span id="word-count">Words: 0 | Characters: 0</span> <span id="word-count">Words: 0 | Characters: 0</span>
+20
View File
@@ -10,6 +10,7 @@ const DOMPurify = require('dompurify');
const hljs = require('highlight.js'); const hljs = require('highlight.js');
const { createEditor } = require('./editor/codemirror-setup'); const { createEditor } = require('./editor/codemirror-setup');
const { undo, redo } = require('@codemirror/commands'); const { undo, redo } = require('@codemirror/commands');
const { SidebarManager } = require('./sidebar/sidebar-manager');
// Configure marked with highlight extension // Configure marked with highlight extension
marked.use(markedHighlight({ marked.use(markedHighlight({
@@ -1006,6 +1007,25 @@ let tabManager;
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
tabManager = new TabManager(); tabManager = new TabManager();
// Initialize sidebar
const sidebarManager = new SidebarManager();
sidebarManager.registerPanel('explorer', {
title: 'Explorer',
render: (container) => { container.innerHTML = '<p style="color:#888;padding:8px;">File explorer coming soon...</p>'; }
});
sidebarManager.registerPanel('git', {
title: 'Git',
render: (container) => { container.innerHTML = '<p style="color:#888;padding:8px;">Git panel coming soon...</p>'; }
});
sidebarManager.registerPanel('snippets', {
title: 'Snippets',
render: (container) => { container.innerHTML = '<p style="color:#888;padding:8px;">Snippets coming soon...</p>'; }
});
sidebarManager.registerPanel('templates', {
title: 'Templates',
render: (container) => { container.innerHTML = '<p style="color:#888;padding:8px;">Templates coming soon...</p>'; }
});
// Initialize CodeMirror for the initial tab (tab 1) // Initialize CodeMirror for the initial tab (tab 1)
const editorContainer = document.getElementById('editor-cm-1'); const editorContainer = document.getElementById('editor-cm-1');
if (editorContainer) { if (editorContainer) {
+50
View File
@@ -0,0 +1,50 @@
class SidebarManager {
constructor() {
this.sidebar = document.getElementById('sidebar');
this.panelContent = document.getElementById('sidebar-panel-content');
this.panelTitle = document.querySelector('.sidebar-panel-title');
this.activePanel = null;
this.panels = new Map();
this.setupEventListeners();
}
setupEventListeners() {
document.querySelectorAll('.sidebar-icon').forEach(btn => {
btn.addEventListener('click', () => this.togglePanel(btn.dataset.panel));
});
document.querySelector('.sidebar-panel-close')?.addEventListener('click', () => this.collapse());
}
registerPanel(name, { title, render }) {
this.panels.set(name, { title, render });
}
togglePanel(name) {
if (this.activePanel === name) {
this.collapse();
} else {
this.expand(name);
}
}
expand(name) {
const panel = this.panels.get(name);
if (!panel) return;
this.sidebar.classList.remove('collapsed');
this.panelTitle.textContent = panel.title;
this.panelContent.innerHTML = '';
panel.render(this.panelContent);
this.activePanel = name;
document.querySelectorAll('.sidebar-icon').forEach(btn => {
btn.classList.toggle('active', btn.dataset.panel === name);
});
}
collapse() {
this.sidebar.classList.add('collapsed');
this.activePanel = null;
document.querySelectorAll('.sidebar-icon').forEach(btn => btn.classList.remove('active'));
}
}
module.exports = { SidebarManager };
+132
View File
@@ -0,0 +1,132 @@
/* Sidebar */
.main-content {
display: flex;
flex: 1;
overflow: hidden;
min-height: 0;
}
.sidebar {
display: flex;
flex-shrink: 0;
height: 100%;
transition: width 0.2s ease;
}
.sidebar.collapsed {
width: 48px;
}
.sidebar:not(.collapsed) {
width: 328px; /* 48px icons + 280px panel */
}
.sidebar-icons {
display: flex;
flex-direction: column;
width: 48px;
background: var(--gray-100, #f3f4f6);
border-right: 1px solid var(--gray-200, #e5e7eb);
padding: 8px 0;
gap: 4px;
align-items: center;
}
.sidebar-icon {
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
border: none;
background: transparent;
border-radius: 8px;
cursor: pointer;
color: var(--gray-500, #6b7280);
transition: all 0.15s ease;
}
.sidebar-icon:hover {
background: var(--gray-200, #e5e7eb);
color: var(--gray-700, #374151);
}
.sidebar-icon.active {
background: var(--gray-200, #e5e7eb);
color: var(--primary-dark, #5661b3);
box-shadow: inset 3px 0 0 var(--primary-dark, #5661b3);
}
.sidebar-panel {
width: 280px;
background: var(--gray-50, #f9fafb);
border-right: 1px solid var(--gray-200, #e5e7eb);
display: flex;
flex-direction: column;
overflow: hidden;
}
.sidebar.collapsed .sidebar-panel {
display: none;
}
.sidebar-panel-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
border-bottom: 1px solid var(--gray-200, #e5e7eb);
font-weight: 600;
font-size: 13px;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--gray-600, #4b5563);
}
.sidebar-panel-close {
background: none;
border: none;
font-size: 18px;
cursor: pointer;
color: var(--gray-400, #9ca3af);
padding: 0 4px;
border-radius: 4px;
}
.sidebar-panel-close:hover {
background: var(--gray-200, #e5e7eb);
color: var(--gray-600, #4b5563);
}
.sidebar-panel-content {
flex: 1;
overflow-y: auto;
padding: 12px;
}
/* Dark theme support */
body[class*="dark"] .sidebar-icons,
body[class*="dark"] .sidebar-panel {
background: #1e1e1e;
border-color: #333;
}
body[class*="dark"] .sidebar-icon {
color: #888;
}
body[class*="dark"] .sidebar-icon:hover {
background: #333;
color: #ccc;
}
body[class*="dark"] .sidebar-icon.active {
background: #333;
color: #8b9aff;
box-shadow: inset 3px 0 0 #8b9aff;
}
body[class*="dark"] .sidebar-panel-header {
border-color: #333;
color: #ccc;
}