feat: responsive mobile UI/UX — hamburger menu, touch support, adaptive layout
FlowDeck CI / test (push) Failing after 5s
FlowDeck CI / docker (push) Has been skipped

- CSS: 3 breakpoints (480px / 768px / 1024px) avec mobile-first design
- Hamburger button dans la topbar (visible ≤768px)
- Sidebar slide-in avec overlay + bouton close
- Kanban scroll-snap columns full-width sur mobile
- Tables scrollables horizontalement
- Touch targets 44px minimum
- Swipe-left to close sidebar
- Modal full-width sur ≤480px
- Topbar simplifiée sur mobile (cacher breadcrumbs center)
- Filter/toolbar wrap + scroll
- Donut/team-load redimensionnés
- app.js: touch events, htmx sidebar close, modal body scroll lock
This commit is contained in:
2026-07-11 00:15:24 -04:00
parent b01c5bbfe3
commit e2a739c4c6
3 changed files with 363 additions and 31 deletions
+38 -8
View File
@@ -12,6 +12,12 @@
<body hx-headers='{"X-CSRF-Token":"__CSRF_PLACEHOLDER__"}'>
<div class="app-layout" x-data="appState()">
<!-- ═══════════ MOBILE SIDEBAR OVERLAY ═══════════ -->
<div class="sidebar-overlay"
:class="{ visible: mobileSidebarOpen }"
@click="mobileSidebarOpen = false; sidebarCollapsed = true;">
</div>
{# ─── Recursive tree macro ─── #}
{% macro render_tree_item(page, depth=0) %}
{% set indent_px = depth * 16 %}
@@ -49,15 +55,22 @@
{% endmacro %}
<!-- ═══════════ SIDEBAR Notion ═══════════ -->
<aside class="sidebar" :class="{ collapsed: sidebarCollapsed }" id="sidebar"
<aside class="sidebar"
:class="{ collapsed: sidebarCollapsed, 'mobile-open': mobileSidebarOpen }"
id="sidebar"
x-data x-init="$nextTick(() => { if(typeof initTreeSortable==='function') initTreeSortable(); })">
<!-- Workspace header -->
<div class="sidebar-workspace" @click="toggleWorkspaceMenu">
<div class="workspace-info">
<div class="workspace-avatar">{{ workspaceInitial }}</div>
<span class="workspace-name">{{ workspaceName }}</span>
<!-- Workspace header + mobile back -->
<div style="display:flex;align-items:center;gap:4px;">
<button class="sidebar-back-btn" @click="mobileSidebarOpen = false; sidebarCollapsed = true;" title="Fermer le menu">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
</button>
<div class="sidebar-workspace" @click="toggleWorkspaceMenu" style="flex:1;">
<div class="workspace-info">
<div class="workspace-avatar">{{ workspaceInitial }}</div>
<span class="workspace-name">{{ workspaceName }}</span>
</div>
<span class="workspace-chevron">▾</span>
</div>
<span class="workspace-chevron">▾</span>
</div>
<!-- Navigation icons row -->
@@ -305,6 +318,10 @@
<!-- Topbar -->
{% block topbar %}
<header class="topbar">
<!-- Mobile hamburger -->
<button class="hamburger-btn" @click="mobileSidebarOpen = true; sidebarCollapsed = false;" title="Menu">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>
</button>
<div class="topbar-left">
<div class="topbar-breadcrumb">
<span x-text="workspaceName"></span>
@@ -442,6 +459,7 @@
function appState() {
return {
sidebarCollapsed: false,
mobileSidebarOpen: false,
showInvite: true,
workspaceName: '{{ workspace_name }}',
workspaceInitial: '{{ workspace_initial }}',
@@ -461,7 +479,19 @@
// ── Sections collapsible ──
sectionsOpen: { recents: true, private: true, meetings: true, favorites: true, agents: true, shared: true, apps: true },
toggleSection(key) { this.sectionsOpen[key] = !this.sectionsOpen[key]; this.sectionsOpen = Object.assign({}, this.sectionsOpen); },
toggleWorkspaceMenu() { this.sidebarCollapsed = !this.sidebarCollapsed; },
toggleWorkspaceMenu() {
if (window.innerWidth <= 768) {
this.mobileSidebarOpen = !this.mobileSidebarOpen;
this.sidebarCollapsed = !this.mobileSidebarOpen;
} else {
this.sidebarCollapsed = !this.sidebarCollapsed;
}
},
navigateTo(url) {
this.mobileSidebarOpen = false;
this.sidebarCollapsed = true;
window.location.href = url;
},
openQuickFind() { alert('Quick Find — Ctrl+K'); },
// ── Tree state ──