Top Bar petits écrans
This commit is contained in:
@@ -217,10 +217,22 @@ function IconPlusCircle() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const IconBurger = () => (
|
||||||
|
<svg width="28" height="28" viewBox="0 0 28 28" fill="none"
|
||||||
|
stroke="currentColor" strokeWidth="2" strokeLinecap="round"
|
||||||
|
aria-hidden="true">
|
||||||
|
<line x1="4" y1="8" x2="24" y2="8"/>
|
||||||
|
<line x1="4" y1="14" x2="24" y2="14"/>
|
||||||
|
<line x1="4" y1="20" x2="24" y2="20"/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
export default function Layout() {
|
export default function Layout() {
|
||||||
const { sidebarCollapsed, setSidebarCollapsed, toggleSidebar, displayMode, setDisplayMode } = useUi();
|
const { sidebarCollapsed, setSidebarCollapsed, toggleSidebar, displayMode, setDisplayMode } = useUi();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [navIcons, setNavIcons] = useState({});
|
const [navIcons, setNavIcons] = useState({});
|
||||||
|
const [burgerOpen, setBurgerOpen] = useState(false);
|
||||||
|
const burgerRef = useRef(null);
|
||||||
|
|
||||||
// Auto-collapse en mode portrait sur tablette (768–1200px)
|
// Auto-collapse en mode portrait sur tablette (768–1200px)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -231,6 +243,16 @@ export default function Layout() {
|
|||||||
return () => mq.removeEventListener('change', handler);
|
return () => mq.removeEventListener('change', handler);
|
||||||
}, [setSidebarCollapsed]);
|
}, [setSidebarCollapsed]);
|
||||||
|
|
||||||
|
// Fermer le burger au clic extérieur
|
||||||
|
useEffect(() => {
|
||||||
|
if (!burgerOpen) return;
|
||||||
|
const handler = (e) => {
|
||||||
|
if (burgerRef.current && !burgerRef.current.contains(e.target)) setBurgerOpen(false);
|
||||||
|
};
|
||||||
|
document.addEventListener('mousedown', handler);
|
||||||
|
return () => document.removeEventListener('mousedown', handler);
|
||||||
|
}, [burgerOpen]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
api.get('/icons').then(rows => {
|
api.get('/icons').then(rows => {
|
||||||
const m = {};
|
const m = {};
|
||||||
@@ -302,28 +324,55 @@ export default function Layout() {
|
|||||||
<div className="topbar topbar-global">
|
<div className="topbar topbar-global">
|
||||||
<ProjectSearch />
|
<ProjectSearch />
|
||||||
<div className="topbar-right">
|
<div className="topbar-right">
|
||||||
<button
|
{/* Boutons visibles sur grand écran */}
|
||||||
className="btn-add-invest"
|
<button className="btn-add-invest topbar-action-btn" onClick={() => navigate('/investissements?new=1')}>
|
||||||
onClick={() => navigate('/investissements?new=1')}
|
<IconPlusCircle />Ajout Investissement
|
||||||
>
|
|
||||||
<IconPlusCircle />
|
|
||||||
Ajout Investissement
|
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button className="btn-add-invest topbar-action-btn" onClick={() => navigate('/remboursements?new=1')}>
|
||||||
className="btn-add-invest"
|
<IconPlusCircle />Nouveau remboursement
|
||||||
onClick={() => navigate('/remboursements?new=1')}
|
|
||||||
>
|
|
||||||
<IconPlusCircle />
|
|
||||||
Nouveau remboursement
|
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button className="btn-add-invest topbar-action-btn" onClick={() => navigate('/depots-retraits?new=1')}>
|
||||||
className="btn-add-invest"
|
<IconPlusCircle />Nouveau dépôt/retrait
|
||||||
onClick={() => navigate('/depots-retraits?new=1')}
|
|
||||||
>
|
|
||||||
<IconPlusCircle />
|
|
||||||
Nouveau dépôt/retrait
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
{/* Bouton burger — visible sur tablette/mobile */}
|
||||||
|
<div className="topbar-burger-wrap" ref={burgerRef}>
|
||||||
|
<button
|
||||||
|
className="topbar-burger-btn"
|
||||||
|
onClick={() => setBurgerOpen(v => !v)}
|
||||||
|
aria-label="Menu actions"
|
||||||
|
aria-expanded={burgerOpen}
|
||||||
|
>
|
||||||
|
<IconBurger />
|
||||||
|
</button>
|
||||||
|
{burgerOpen && (
|
||||||
|
<div className="topbar-burger-menu">
|
||||||
|
<button className="topbar-burger-search-item" onClick={() => { setBurgerOpen(false); document.querySelector('.project-search-input')?.focus(); }}>
|
||||||
|
<svg width="15" height="15" viewBox="0 0 18 18" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" aria-hidden="true"><circle cx="7.5" cy="7.5" r="5.5"/><line x1="11.5" y1="11.5" x2="16" y2="16"/></svg>
|
||||||
|
Rechercher un projet
|
||||||
|
</button>
|
||||||
|
<div className="topbar-burger-sep" />
|
||||||
|
<button className="topbar-burger-item" onClick={() => { setBurgerOpen(false); navigate('/investissements?new=1'); }}>
|
||||||
|
<IconPlusCircle />Ajout Investissement
|
||||||
|
</button>
|
||||||
|
<button className="topbar-burger-item" onClick={() => { setBurgerOpen(false); navigate('/remboursements?new=1'); }}>
|
||||||
|
<IconPlusCircle />Nouveau remboursement
|
||||||
|
</button>
|
||||||
|
<button className="topbar-burger-item" onClick={() => { setBurgerOpen(false); navigate('/depots-retraits?new=1'); }}>
|
||||||
|
<IconPlusCircle />Nouveau dépôt/retrait
|
||||||
|
</button>
|
||||||
|
<div className="topbar-burger-sep" />
|
||||||
|
<div className="topbar-burger-toggle">
|
||||||
|
<span className="topbar-burger-toggle-label">Affichage</span>
|
||||||
|
<div className="display-toggle" role="group" aria-label="Mode d'affichage">
|
||||||
|
<button className={`display-toggle-btn${displayMode === 'brut' ? ' active' : ''}`} onClick={() => setDisplayMode('brut')} aria-pressed={displayMode === 'brut'}>Brut</button>
|
||||||
|
<button className={`display-toggle-btn${displayMode === 'net' ? ' active' : ''}`} onClick={() => setDisplayMode('net')} aria-pressed={displayMode === 'net'}>Net</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="display-toggle" role="group" aria-label="Mode d'affichage">
|
<div className="display-toggle" role="group" aria-label="Mode d'affichage">
|
||||||
<button
|
<button
|
||||||
className={`display-toggle-btn${displayMode === 'brut' ? ' active' : ''}`}
|
className={`display-toggle-btn${displayMode === 'brut' ? ' active' : ''}`}
|
||||||
|
|||||||
@@ -240,6 +240,69 @@ a { color: var(--primary); }
|
|||||||
}
|
}
|
||||||
.btn-add-invest:hover { background: var(--primary-hover); box-shadow: 0 3px 12px rgba(59,130,246,.5); }
|
.btn-add-invest:hover { background: var(--primary-hover); box-shadow: 0 3px 12px rgba(59,130,246,.5); }
|
||||||
|
|
||||||
|
/* ── Burger menu topbar ──────────────────────────────────────── */
|
||||||
|
.topbar-burger-wrap { position: relative; display: none; }
|
||||||
|
.topbar-burger-btn {
|
||||||
|
display: flex; align-items: center; justify-content: center;
|
||||||
|
padding: 0; background: none; border: none;
|
||||||
|
cursor: pointer; color: var(--text);
|
||||||
|
transition: color .15s;
|
||||||
|
}
|
||||||
|
.topbar-burger-btn:hover { color: var(--primary); }
|
||||||
|
.topbar-burger-menu {
|
||||||
|
position: absolute; top: calc(100% + 8px); right: 0;
|
||||||
|
background: var(--surface); border: 1px solid var(--border);
|
||||||
|
border-radius: 10px; box-shadow: 0 8px 24px rgba(0,0,0,.10);
|
||||||
|
min-width: 220px; z-index: 200; padding: 6px;
|
||||||
|
display: flex; flex-direction: column; gap: 2px;
|
||||||
|
}
|
||||||
|
.topbar-burger-item {
|
||||||
|
display: flex; align-items: center; gap: 8px;
|
||||||
|
padding: 9px 12px; border: none; border-radius: 6px;
|
||||||
|
background: none; color: var(--text); font-size: var(--fs-base);
|
||||||
|
font-weight: 600; cursor: pointer; text-align: left; width: 100%;
|
||||||
|
transition: background .12s;
|
||||||
|
}
|
||||||
|
.topbar-burger-item:hover { background: var(--surface-2); }
|
||||||
|
.topbar-burger-item svg { color: var(--primary); flex-shrink: 0; }
|
||||||
|
.topbar-burger-search-item {
|
||||||
|
display: flex; align-items: center; gap: 8px;
|
||||||
|
padding: 9px 12px; border: none; border-radius: 6px;
|
||||||
|
background: none; color: var(--text-muted); font-size: var(--fs-sm);
|
||||||
|
cursor: pointer; text-align: left; width: 100%;
|
||||||
|
transition: background .12s;
|
||||||
|
}
|
||||||
|
.topbar-burger-search-item:hover { background: var(--surface-2); }
|
||||||
|
.topbar-burger-sep { height: 1px; background: var(--border); margin: 2px 0; }
|
||||||
|
|
||||||
|
/* Bloc toggle dans le burger */
|
||||||
|
.topbar-burger-toggle {
|
||||||
|
display: flex; align-items: center; justify-content: space-between;
|
||||||
|
padding: 6px 12px 4px;
|
||||||
|
}
|
||||||
|
.topbar-burger-toggle-label {
|
||||||
|
font-size: var(--fs-sm); color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tablette : masquer les boutons texte et le toggle, afficher le burger */
|
||||||
|
@media (max-width: 1100px) {
|
||||||
|
.topbar-action-btn { display: none !important; }
|
||||||
|
.topbar-burger-wrap { display: block; }
|
||||||
|
.topbar-global .topbar-right > .display-toggle { display: none; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile : masquer aussi la barre de recherche dans la topbar */
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.topbar-global .project-search-wrap { display: none; }
|
||||||
|
.topbar-burger-search-item { display: flex; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sur tablette, masquer l'item recherche dans le burger (search reste visible) */
|
||||||
|
@media (min-width: 641px) and (max-width: 1100px) {
|
||||||
|
.topbar-burger-search-item,
|
||||||
|
.topbar-burger-search-item + .topbar-burger-sep { display: none; }
|
||||||
|
}
|
||||||
|
|
||||||
/* btn-icon — bouton icône discret (cercle hover) */
|
/* btn-icon — bouton icône discret (cercle hover) */
|
||||||
.btn-icon {
|
.btn-icon {
|
||||||
display: flex; align-items: center; justify-content: center;
|
display: flex; align-items: center; justify-content: center;
|
||||||
|
|||||||
Reference in New Issue
Block a user