feat(ui): panneau repliable, carnets plus lisibles et accueil plus clair

Harmonise la liste des notes et l’éditeur (titres, dates, retour),
rend la saisie rapide et les raccourcis de l’accueil utilisables
sur téléphone, et conserve le panneau latéral repliable.
This commit is contained in:
Antigravity
2026-09-05 20:53:48 +00:00
parent a7b16870a4
commit 3bdbec575a
37 changed files with 1138 additions and 482 deletions

View File

@@ -43,6 +43,7 @@ import {
Plug,
Key,
Info,
ChevronLeft,
} from 'lucide-react'
import { useSearchModal } from '@/context/search-modal-context'
import { useLanguage } from '@/lib/i18n'
@@ -504,8 +505,8 @@ function SidebarCarnetItem({
<div className="flex-1 text-start flex items-center gap-2 min-w-0">
<span className={cn(
'text-[12px] font-medium transition-colors truncate',
isActive ? 'text-ink' : 'text-muted-ink group-hover/item:text-ink'
'text-[13px] font-medium transition-colors truncate',
isActive ? 'text-foreground' : 'text-foreground/80 group-hover/item:text-foreground'
)}>
{carnet.name}
</span>
@@ -523,13 +524,13 @@ function SidebarCarnetItem({
{/* Compteur de notes (toujours visible, à droite du nom) */}
{notes.length > 0 && (
<span className="text-[9px] font-bold text-concrete/40 px-1.5 border border-border/40 rounded-full group-hover/item:text-concrete transition-colors shrink-0 ms-auto">
<span className="text-[11px] font-medium tabular-nums text-foreground/75 px-1.5 bg-brand-accent/5 border border-border/40 rounded-full transition-colors shrink-0 ms-auto">
{notes.length}
</span>
)}
{/* Boutons d'action en absolu, toujours à droite */}
<div className="absolute end-0 top-0 bottom-0 flex items-center gap-1 pe-1 ps-6 opacity-0 group-hover/item:opacity-100 transition-opacity bg-gradient-to-l from-[var(--color-paper,white)] dark:from-[var(--color-paper,#1a1a1a)] from-60% to-transparent">
<div className="absolute end-0 top-0 bottom-0 flex items-center gap-1 pe-1 ps-6 opacity-0 group-hover/item:opacity-100 focus-within:opacity-100 transition-opacity bg-gradient-to-l from-[var(--color-paper,white)] dark:from-[var(--color-paper,#1a1a1a)] from-60% to-transparent">
{isPinned && (
<span className="text-brand-accent" title={t('notebook.pinnedFrozenTooltip')}>
<Pin size={9} className="opacity-70" />
@@ -890,27 +891,39 @@ export function Sidebar({ className, user }: { className?: string; user?: any })
const isRemindersRoute = pathname === '/home' && searchParams.get('reminders') === '1'
const isSharedRoute = pathname === '/home' && searchParams.get('shared') === '1'
const isImmersiveRoute = pathname === '/insights'
// Sur /insights : sidebar visible par défaut, user peut le cacher via toggle
const [userCollapsed, setUserCollapsed] = useState(false)
const [dashboardPanelCollapsed, setDashboardPanelCollapsed] = useState(false)
useEffect(() => {
try {
setDashboardPanelCollapsed(localStorage.getItem('memento-dashboard-panel-collapsed') === 'true')
} catch {}
}, [])
const [insightsPanelCollapsed, setInsightsPanelCollapsed] = useState(false)
useEffect(() => {
try {
setInsightsPanelCollapsed(localStorage.getItem('insights-sidebar-collapsed') === 'true')
} catch {}
}, [])
const hasCollapsiblePanel = isDashboardRoute || isImmersiveRoute
const collapseDashboardPanel = (isDashboardRoute && dashboardPanelCollapsed) || (isImmersiveRoute && insightsPanelCollapsed)
const dashboardPanelLabel = isImmersiveRoute
? t('insightsView.toggleMenu')
: t(collapseDashboardPanel ? 'sidebar.showDashboardPanel' : 'sidebar.hideDashboardPanel')
const toggleDashboardPanel = () => {
if (isImmersiveRoute) {
const stored = localStorage.getItem('insights-sidebar-collapsed')
setUserCollapsed(stored === 'true')
const next = !insightsPanelCollapsed
setInsightsPanelCollapsed(next)
try {
localStorage.setItem('insights-sidebar-collapsed', String(next))
} catch {}
return
}
}, [isImmersiveRoute])
const next = !dashboardPanelCollapsed
setDashboardPanelCollapsed(next)
try {
localStorage.setItem('memento-dashboard-panel-collapsed', String(next))
} catch {}
}
useEffect(() => {
const onToggle = () => {
setUserCollapsed(prev => {
const next = !prev
if (isImmersiveRoute) localStorage.setItem('insights-sidebar-collapsed', String(next))
return next
})
}
window.addEventListener('toggle-insights-sidebar', onToggle)
return () => window.removeEventListener('toggle-insights-sidebar', onToggle)
}, [isImmersiveRoute])
const isNotebooksRoute =
(pathname === '/home' || pathname.startsWith('/notes')) &&
!pathname.startsWith('/settings') &&
@@ -1369,19 +1382,9 @@ export function Sidebar({ className, user }: { className?: string; user?: any })
<aside
ref={asideRef}
style={sidebarWidth !== null ? { width: sidebarWidth } : undefined}
style={sidebarWidth !== null ? { width: sidebarWidth, '--sidebar-expanded-width': `${sidebarWidth}px` } as React.CSSProperties : undefined}
className={cn(
// Sur /insights replié : fixed hors flux pour que <main flex-1> s'agrandisse.
// Ne jamais ajouter un `relative` nu ensuite — twMerge le ferait gagner sur `fixed`
// et réserverait toujours l'espace (bande beige vide).
isImmersiveRoute && userCollapsed
? cn(
'fixed inset-y-0 start-0 z-[70]',
isRtl ? 'translate-x-full' : '-translate-x-full',
'pointer-events-none',
sidebarWidth === null && 'w-80 md:w-[26rem] 2xl:w-[30rem]',
)
: cn(
cn(
'fixed inset-y-0 start-0 z-[70] md:relative md:z-auto',
sidebarWidth === null && 'w-80 md:w-[26rem] 2xl:w-[30rem]',
isMobileOpen
@@ -1389,11 +1392,12 @@ export function Sidebar({ className, user }: { className?: string; user?: any })
: cn(isRtl ? 'translate-x-full md:translate-x-0' : '-translate-x-full md:translate-x-0'),
),
'h-full min-h-0 shrink-0 flex flex-row self-stretch overflow-visible',
collapseDashboardPanel && 'md:!w-[72px]',
hasCollapsiblePanel && 'dashboard-sidebar md:z-40',
'transition-transform duration-300 ease-in-out',
'border-e border-border/40 bg-white/95 md:bg-white/30 backdrop-blur-md sidebar-shadow dark:border-white/6 dark:bg-[#151515] dark:backdrop-blur-none',
className
)}
aria-hidden={isImmersiveRoute && userCollapsed ? true : undefined}
>
{/* ── Column 1 : Rail icône + libellé — destinations lisibles sans survol ── */}
<div className="w-[72px] border-e border-border/40 bg-[#FAF9F5] dark:bg-[#0E0E0E] flex flex-col items-center justify-between py-3 shrink-0 select-none overflow-y-auto overflow-x-hidden custom-scrollbar">
@@ -1631,7 +1635,7 @@ export function Sidebar({ className, user }: { className?: string; user?: any })
</div>
{/* ── Column 2 : Panneau de contenu dynamique ── */}
<div className="flex-1 h-full flex flex-col overflow-hidden bg-[#FCFCFA] dark:bg-[#111111]">
<div id="sidebar-context-panel" data-collapsed={collapseDashboardPanel} className="flex-1 h-full flex flex-col overflow-hidden bg-[#FCFCFA] dark:bg-[#111111]">
{/* ── Scrollable content ── */}
<div
@@ -2076,13 +2080,29 @@ export function Sidebar({ className, user }: { className?: string; user?: any })
</div>{/* fin colonne 2 */}
{hasCollapsiblePanel && (
<button
type="button"
onClick={toggleDashboardPanel}
aria-label={dashboardPanelLabel}
title={dashboardPanelLabel}
aria-expanded={!collapseDashboardPanel}
aria-controls="sidebar-context-panel"
className="group absolute top-5 -end-[18px] z-40 hidden h-9 w-9 items-center justify-center rounded-full md:flex focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-accent"
>
<span className="flex h-7 w-7 items-center justify-center rounded-full border border-border bg-background text-foreground transition-colors duration-150 group-hover:border-brand-accent group-hover:text-brand-accent">
<ChevronLeft size={16} strokeWidth={1.75} className={cn('transition-transform duration-300 motion-reduce:transition-none', collapseDashboardPanel ? 'rotate-180 rtl:rotate-0' : 'rtl:rotate-180')} />
</span>
</button>
)}
{/* Poignée redimensionnement largeur sidebar (bord vers le contenu) */}
<div
role="separator"
aria-orientation="vertical"
aria-label={t('sidebar.resizeSidebar')}
onPointerDown={handleSidebarWidthResizeStart}
className="hidden md:flex absolute inset-y-0 end-0 translate-x-1/2 w-4 cursor-col-resize touch-none z-30 items-center justify-center group"
className={cn('hidden md:flex absolute inset-y-0 end-0 translate-x-1/2 w-4 cursor-col-resize touch-none z-30 items-center justify-center group', collapseDashboardPanel && 'md:hidden')}
>
<div className="h-10 w-1 rounded-full bg-border/80 group-hover:bg-brand-accent/70 group-active:bg-brand-accent transition-colors shadow-sm" />
</div>