feat(rnotes): mobile stack navigation — Notion-style two-screen slide
Replace overlay sidebar with horizontal flex stack: full-width doc list slides to full-width editor with back bar on note tap. Resize-aware. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
3add66b5ef
commit
2eac542e19
|
|
@ -137,6 +137,8 @@ class FolkNotesApp extends HTMLElement {
|
|||
private expandedNotebooks = new Set<string>();
|
||||
private notebookNotes = new Map<string, Note[]>();
|
||||
private sidebarOpen = true;
|
||||
private mobileEditing = false;
|
||||
private _resizeHandler: (() => void) | null = null;
|
||||
|
||||
// Zone-based rendering
|
||||
private navZone!: HTMLDivElement;
|
||||
|
|
@ -219,6 +221,18 @@ class FolkNotesApp extends HTMLElement {
|
|||
if (!localStorage.getItem("rnotes_tour_done")) {
|
||||
setTimeout(() => this._tour.start(), 1200);
|
||||
}
|
||||
|
||||
// Mobile resize handler — sync mobile-editing state on viewport change
|
||||
this._resizeHandler = () => {
|
||||
if (window.innerWidth > 768) {
|
||||
// Switched to desktop — remove mobile-editing so both panels show
|
||||
this.setMobileEditing(false);
|
||||
} else if (this.selectedNote && this.editor) {
|
||||
// Went back to mobile with a note open — restore editor screen
|
||||
this.setMobileEditing(true);
|
||||
}
|
||||
};
|
||||
window.addEventListener('resize', this._resizeHandler);
|
||||
}
|
||||
|
||||
private async subscribeOfflineRuntime() {
|
||||
|
|
@ -285,27 +299,6 @@ class FolkNotesApp extends HTMLElement {
|
|||
|
||||
this.shadow.appendChild(style);
|
||||
this.shadow.appendChild(layout);
|
||||
|
||||
// Mobile sidebar toggle
|
||||
const mobileToggle = document.createElement('button');
|
||||
mobileToggle.className = 'mobile-sidebar-toggle';
|
||||
mobileToggle.innerHTML = '<span class="mobile-toggle-icon">\u{1F4C4}</span><span class="mobile-toggle-label">Docs</span>';
|
||||
mobileToggle.addEventListener('click', () => {
|
||||
this.sidebarOpen = !this.sidebarOpen;
|
||||
this.navZone.querySelector('.notes-sidebar')?.classList.toggle('open', this.sidebarOpen);
|
||||
this.shadow.querySelector('.sidebar-overlay')?.classList.toggle('open', this.sidebarOpen);
|
||||
});
|
||||
this.shadow.appendChild(mobileToggle);
|
||||
|
||||
// Mobile overlay
|
||||
const overlay = document.createElement('div');
|
||||
overlay.className = 'sidebar-overlay';
|
||||
overlay.addEventListener('click', () => {
|
||||
this.sidebarOpen = false;
|
||||
this.navZone.querySelector('.notes-sidebar')?.classList.remove('open');
|
||||
overlay.classList.remove('open');
|
||||
});
|
||||
this.shadow.appendChild(overlay);
|
||||
}
|
||||
|
||||
// ── Demo data ──
|
||||
|
|
@ -577,9 +570,33 @@ Gear: EUR 400 (10%)</code></pre><p><em>Maya is tracking expenses in rF
|
|||
this.mountEditor(newNote);
|
||||
}
|
||||
|
||||
// ── Mobile stack navigation ──
|
||||
|
||||
private setMobileEditing(editing: boolean) {
|
||||
this.mobileEditing = editing;
|
||||
this.shadow.getElementById('notes-layout')?.classList.toggle('mobile-editing', editing);
|
||||
}
|
||||
|
||||
private mobileGoBack() {
|
||||
this.setMobileEditing(false);
|
||||
}
|
||||
|
||||
private mobileBackBarHtml(): string {
|
||||
const title = this.selectedNotebook?.title || 'Notes';
|
||||
return `<div class="mobile-back-bar"><button class="mobile-back-btn">← ${this.esc(title)}</button></div>`;
|
||||
}
|
||||
|
||||
private isMobile(): boolean {
|
||||
return window.innerWidth <= 768;
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
this.destroyEditor();
|
||||
this.cleanupPresence();
|
||||
if (this._resizeHandler) {
|
||||
window.removeEventListener('resize', this._resizeHandler);
|
||||
this._resizeHandler = null;
|
||||
}
|
||||
this._offlineUnsub?.();
|
||||
this._offlineUnsub = null;
|
||||
for (const unsub of this._offlineNotebookUnsubs) unsub();
|
||||
|
|
@ -1051,11 +1068,9 @@ Gear: EUR 400 (10%)</code></pre><p><em>Maya is tracking expenses in rF
|
|||
private async openNote(noteId: string, notebookId: string) {
|
||||
const isDemo = this.space === "demo";
|
||||
|
||||
// Auto-close sidebar on mobile
|
||||
if (window.innerWidth <= 768) {
|
||||
this.sidebarOpen = false;
|
||||
this.navZone.querySelector('.notes-sidebar')?.classList.remove('open');
|
||||
this.shadow.querySelector('.sidebar-overlay')?.classList.remove('open');
|
||||
// Mobile: slide to editor screen
|
||||
if (this.isMobile()) {
|
||||
this.setMobileEditing(true);
|
||||
}
|
||||
|
||||
// Expand notebook if not expanded
|
||||
|
|
@ -1210,6 +1225,11 @@ Gear: EUR 400 (10%)</code></pre><p><em>Maya is tracking expenses in rF
|
|||
case 'AUDIO': this.mountAudioView(note, isEditable, isDemo); break;
|
||||
default: this.mountTiptapEditor(note, isEditable, isDemo); break;
|
||||
}
|
||||
|
||||
// Mobile: inject back bar and slide to editor
|
||||
this.contentZone.insertAdjacentHTML('afterbegin', this.mobileBackBarHtml());
|
||||
this.contentZone.querySelector('.mobile-back-btn')?.addEventListener('click', () => this.mobileGoBack());
|
||||
if (this.isMobile()) this.setMobileEditing(true);
|
||||
}
|
||||
|
||||
private mountTiptapEditor(note: Note, isEditable: boolean, isDemo: boolean) {
|
||||
|
|
@ -3229,22 +3249,9 @@ Gear: EUR 400 (10%)</code></pre><p><em>Maya is tracking expenses in rF
|
|||
.editor-empty-state svg { width: 48px; height: 48px; opacity: 0.4; }
|
||||
.editor-empty-state p { font-size: 14px; }
|
||||
|
||||
/* Mobile sidebar */
|
||||
.mobile-sidebar-toggle {
|
||||
display: none; position: fixed; bottom: 20px; left: 20px; z-index: 198;
|
||||
min-width: 44px; height: 44px; border-radius: 22px; border: none;
|
||||
padding: 0 14px; gap: 4px;
|
||||
background: var(--rs-primary); color: #fff; font-size: 13px;
|
||||
cursor: pointer; box-shadow: var(--rs-shadow-md);
|
||||
align-items: center; justify-content: center;
|
||||
}
|
||||
.mobile-toggle-icon { font-size: 18px; }
|
||||
.mobile-toggle-label { font-weight: 600; font-family: inherit; }
|
||||
.sidebar-overlay {
|
||||
display: none; position: fixed; inset: 0;
|
||||
background: rgba(0,0,0,0.4); z-index: 199;
|
||||
}
|
||||
.sidebar-overlay.open { display: block; }
|
||||
/* Mobile sidebar (legacy — hidden, replaced by stack nav) */
|
||||
.mobile-sidebar-toggle { display: none; }
|
||||
.sidebar-overlay { display: none; }
|
||||
|
||||
/* ── Navigation ── */
|
||||
.rapp-nav__btn { padding: 6px 14px; border-radius: 6px; border: none; background: var(--rs-primary); color: #fff; font-weight: 600; cursor: pointer; font-size: 13px; transition: background 0.15s; display: flex; align-items: center; gap: 4px; }
|
||||
|
|
@ -3521,15 +3528,42 @@ Gear: EUR 400 (10%)</code></pre><p><em>Maya is tracking expenses in rF
|
|||
.tiptap-container .tiptap s { color: var(--rs-text-muted); }
|
||||
.tiptap-container .tiptap u { text-underline-offset: 3px; }
|
||||
|
||||
/* ── Mobile back bar (hidden on desktop) ── */
|
||||
.mobile-back-bar { display: none; }
|
||||
@media (max-width: 768px) {
|
||||
#notes-layout { grid-template-columns: 1fr; }
|
||||
.notes-sidebar {
|
||||
position: fixed; left: 0; top: 0; bottom: 0; width: 280px;
|
||||
z-index: 200; transform: translateX(-100%);
|
||||
transition: transform 0.25s ease; box-shadow: var(--rs-shadow-lg);
|
||||
/* Two-screen horizontal stack: nav (100%) + editor (100%) side-by-side */
|
||||
#notes-layout {
|
||||
display: flex; overflow: hidden;
|
||||
grid-template-columns: unset;
|
||||
}
|
||||
.notes-sidebar.open { transform: translateX(0); }
|
||||
.mobile-sidebar-toggle { display: flex !important; }
|
||||
#nav-zone, .notes-right-col {
|
||||
flex: 0 0 100%; min-width: 0;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
/* Slide both panels left when editing */
|
||||
#notes-layout.mobile-editing > #nav-zone,
|
||||
#notes-layout.mobile-editing > .notes-right-col {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
/* Sidebar fills screen width */
|
||||
.notes-sidebar { width: 100%; position: static; transform: none; box-shadow: none; }
|
||||
/* Hide old overlay FAB (no longer needed) */
|
||||
.mobile-sidebar-toggle, .sidebar-overlay { display: none !important; }
|
||||
/* Hide empty state on mobile — user sees doc list */
|
||||
.editor-empty-state { display: none; }
|
||||
/* Show back bar */
|
||||
.mobile-back-bar {
|
||||
display: flex; align-items: center;
|
||||
padding: 8px 12px; border-bottom: 1px solid var(--rs-border-subtle);
|
||||
background: var(--rs-bg-surface);
|
||||
}
|
||||
.mobile-back-btn {
|
||||
background: none; border: none; color: var(--rs-primary);
|
||||
font-size: 15px; font-weight: 600; cursor: pointer;
|
||||
padding: 4px 8px; border-radius: 6px; font-family: inherit;
|
||||
}
|
||||
.mobile-back-btn:hover { background: var(--rs-bg-surface-raised); }
|
||||
/* Tighten editor padding */
|
||||
.editor-wrapper .editable-title { padding: 12px 14px 0; }
|
||||
.tiptap-container .tiptap { padding: 14px 16px; }
|
||||
.sidebar-footer-btn { min-height: 36px; padding: 7px 12px; }
|
||||
|
|
|
|||
Loading…
Reference in New Issue