feat/resizable-panels #20
+79
@@ -1451,6 +1451,14 @@
|
||||
return;
|
||||
}
|
||||
|
||||
if (ev.key === 'Escape' && $('main').classList.contains('focus-peek')) {
|
||||
exitFocusPeek();
|
||||
state.selectedIndex = -1;
|
||||
renderEntityList();
|
||||
renderDetailPane();
|
||||
return;
|
||||
}
|
||||
|
||||
const sel = state.entities[state.selectedIndex];
|
||||
|
||||
switch (ev.key) {
|
||||
@@ -1534,6 +1542,17 @@
|
||||
|
||||
function toggleZen() {
|
||||
const m = $('main');
|
||||
|
||||
if (m.classList.contains('focus-peek')) {
|
||||
exitFocusPeek();
|
||||
return;
|
||||
}
|
||||
|
||||
if (state.selectedIndex >= 0) {
|
||||
m.classList.add('focus-peek');
|
||||
return;
|
||||
}
|
||||
|
||||
const isZen = m.classList.contains('hide-rail') && m.classList.contains('hide-peek');
|
||||
if (isZen) {
|
||||
m.classList.remove('hide-rail', 'hide-peek');
|
||||
@@ -1546,12 +1565,72 @@
|
||||
}
|
||||
}
|
||||
|
||||
function exitFocusPeek() {
|
||||
$('main').classList.remove('focus-peek');
|
||||
}
|
||||
|
||||
(function restorePanels() {
|
||||
const m = $('main');
|
||||
if (localStorage.getItem('nib:hide-rail')) m.classList.add('hide-rail');
|
||||
if (localStorage.getItem('nib:hide-peek')) m.classList.add('hide-peek');
|
||||
const railW = localStorage.getItem('nib:rail-w');
|
||||
const peekW = localStorage.getItem('nib:peek-w');
|
||||
if (railW) m.style.setProperty('--rail-w', railW + 'px');
|
||||
if (peekW) m.style.setProperty('--peek-w', peekW + 'px');
|
||||
})();
|
||||
|
||||
// ========== Resize handles ==========
|
||||
|
||||
$$('.resize-handle').forEach(handle => {
|
||||
let startX, startW, panel;
|
||||
|
||||
handle.addEventListener('mousedown', (ev) => {
|
||||
ev.preventDefault();
|
||||
panel = handle.dataset.panel;
|
||||
startX = ev.clientX;
|
||||
const m = $('main');
|
||||
m.classList.add('resizing');
|
||||
handle.classList.add('active');
|
||||
|
||||
if (panel === 'rail') {
|
||||
startW = $('#tag-rail').offsetWidth;
|
||||
} else {
|
||||
startW = $('#detail-pane').offsetWidth;
|
||||
}
|
||||
|
||||
document.addEventListener('mousemove', onMove);
|
||||
document.addEventListener('mouseup', onUp);
|
||||
});
|
||||
|
||||
function onMove(ev) {
|
||||
const m = $('main');
|
||||
const dx = ev.clientX - startX;
|
||||
let newW;
|
||||
|
||||
if (panel === 'rail') {
|
||||
newW = Math.max(120, Math.min(360, startW + dx));
|
||||
m.style.setProperty('--rail-w', newW + 'px');
|
||||
} else {
|
||||
newW = Math.max(250, Math.min(700, startW - dx));
|
||||
m.style.setProperty('--peek-w', newW + 'px');
|
||||
}
|
||||
}
|
||||
|
||||
function onUp() {
|
||||
const m = $('main');
|
||||
m.classList.remove('resizing');
|
||||
handle.classList.remove('active');
|
||||
document.removeEventListener('mousemove', onMove);
|
||||
document.removeEventListener('mouseup', onUp);
|
||||
|
||||
if (panel === 'rail') {
|
||||
localStorage.setItem('nib:rail-w', $('#tag-rail').offsetWidth);
|
||||
} else {
|
||||
localStorage.setItem('nib:peek-w', $('#detail-pane').offsetWidth);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function scrollSelectedIntoView() {
|
||||
const el = $(`.entity-item[data-index="${state.selectedIndex}"], .card-row[data-index="${state.selectedIndex}"]`);
|
||||
if (el) el.scrollIntoView({ block: 'nearest' });
|
||||
|
||||
@@ -24,11 +24,13 @@
|
||||
</header>
|
||||
<main>
|
||||
<aside id="tag-rail"></aside>
|
||||
<div class="resize-handle" data-panel="rail"></div>
|
||||
<section id="entity-panel">
|
||||
<div id="month-nav"></div>
|
||||
<div id="entity-list"></div>
|
||||
<div id="capture-bar"></div>
|
||||
</section>
|
||||
<div class="resize-handle" data-panel="peek"></div>
|
||||
<aside id="detail-pane">
|
||||
<div class="detail-empty">select an entity</div>
|
||||
</aside>
|
||||
|
||||
+29
-7
@@ -177,17 +177,39 @@ nav { display: flex; gap: 2px; }
|
||||
/* ── MAIN LAYOUT ────────────────────────────────────── */
|
||||
main {
|
||||
display: grid;
|
||||
grid-template-columns: 192px 1fr 400px;
|
||||
grid-template-columns: var(--rail-w, 192px) 4px 1fr 4px var(--peek-w, 400px);
|
||||
overflow: hidden;
|
||||
transition: grid-template-columns var(--t-base);
|
||||
}
|
||||
|
||||
main.hide-rail { grid-template-columns: 0px 1fr 400px; }
|
||||
main.hide-peek { grid-template-columns: 192px 1fr 0px; }
|
||||
main.hide-rail.hide-peek { grid-template-columns: 0px 1fr 0px; }
|
||||
main.resizing { transition: none; }
|
||||
main:not(.resizing) { transition: grid-template-columns var(--t-base); }
|
||||
|
||||
main.hide-rail #tag-rail { overflow: hidden; border-right: none; }
|
||||
main.hide-peek #detail-pane { overflow: hidden; border-left: none; }
|
||||
main.hide-rail { grid-template-columns: 0px 0px 1fr 4px var(--peek-w, 400px); }
|
||||
main.hide-peek { grid-template-columns: var(--rail-w, 192px) 4px 1fr 0px 0px; }
|
||||
main.hide-rail.hide-peek { grid-template-columns: 0px 0px 1fr 0px 0px; }
|
||||
|
||||
main.hide-rail #tag-rail { overflow: hidden; border-right: none; min-width: 0; }
|
||||
main.hide-peek #detail-pane { overflow: hidden; border-left: none; min-width: 0; }
|
||||
main.hide-rail .resize-handle[data-panel="rail"] { visibility: hidden; }
|
||||
main.hide-peek .resize-handle[data-panel="peek"] { visibility: hidden; }
|
||||
|
||||
main.focus-peek { grid-template-columns: 0px 0px 0px 0px 1fr; }
|
||||
main.focus-peek #tag-rail { overflow: hidden; border-right: none; min-width: 0; }
|
||||
main.focus-peek #entity-panel { overflow: hidden; min-width: 0; }
|
||||
main.focus-peek .resize-handle { visibility: hidden; }
|
||||
|
||||
.resize-handle {
|
||||
cursor: col-resize;
|
||||
background: transparent;
|
||||
transition: background var(--t-fast);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.resize-handle:hover,
|
||||
.resize-handle.active {
|
||||
background: var(--accent);
|
||||
opacity: .4;
|
||||
}
|
||||
|
||||
/* ── TAG RAIL ───────────────────────────────────────── */
|
||||
#tag-rail {
|
||||
|
||||
Reference in New Issue
Block a user