fix(insights): sidebar user-controlled + cluster chips organisées
All checks were successful
CI / Lint, Unit Tests & Build (push) Successful in 5m25s
CI / Deploy production (on server) (push) Successful in 23s

Sidebar (UX: User Freedom):
- Visible par défaut sur /insights (pas caché par le système)
- Toggle via bouton hamburger: toggle-insights-sidebar event
- Préférence persistée en localStorage (insights-sidebar-collapsed)
- User décide: voir ou cacher le sidebar

Cluster chips:
- Triées par taille (plus grand cluster en premier)
- Compteur de notes par cluster (badge arrondi)
- max-w-[120px] sur le nom pour éviter overflow
- padding/gap plus compacts (px-2.5 py-1 gap-1.5)
This commit is contained in:
Antigravity
2026-07-04 20:37:42 +00:00
parent e48152e294
commit 72f9798e97
3 changed files with 38 additions and 14 deletions

View File

@@ -298,8 +298,8 @@ export default function InsightsPage() {
<div className="flex items-center gap-4">
<button
className="p-2 -ms-1 text-foreground hover:bg-foreground/5 rounded-lg transition-colors shrink-0 cursor-pointer focus-visible:ring-2 focus-visible:ring-ochre/50 focus-visible:outline-none"
onClick={() => window.dispatchEvent(new CustomEvent('open-mobile-sidebar'))}
aria-label={t('sidebar.openNavigation') || 'Open navigation'}
onClick={() => window.dispatchEvent(new CustomEvent('toggle-insights-sidebar'))}
aria-label="Toggle sidebar"
>
<Menu size={22} />
</button>

View File

@@ -362,22 +362,27 @@ export function NetworkGraph({
return (
<div ref={containerRef} className="w-full h-full bg-paper dark:bg-[#121212] rounded-3xl overflow-hidden border border-border/40 relative">
{/* Pastilles de cluster — cliquables pour activer le focus */}
<div className="absolute top-6 left-6 z-10 flex flex-wrap gap-2 max-w-[90%]">
{clusters.map(c => {
{/* Pastilles de cluster — triées par taille, avec compteurs */}
<div className="absolute top-6 left-6 z-10 flex flex-wrap gap-1.5 max-w-[85%]">
{[...clusters]
.sort((a, b) => b.noteIds.length - a.noteIds.length)
.map(c => {
const isSelected = String(c.id) === selectedClusterId
return (
<button
key={c.id}
onClick={() => onClusterSelect?.(isSelected ? null : String(c.id))}
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-full border shadow-sm transition-all text-[9px] font-bold uppercase tracking-wider ${
className={`flex items-center gap-1.5 px-2.5 py-1 rounded-full border transition-all text-[9px] font-bold uppercase tracking-wider cursor-pointer focus-visible:ring-2 focus-visible:ring-ochre/50 focus-visible:outline-none ${
isSelected
? 'bg-ink text-white dark:bg-white dark:text-black border-ink dark:border-white scale-105 shadow-md'
: 'bg-white/90 dark:bg-black/80 text-concrete hover:text-ink hover:border-concrete/40 border-border'
: 'bg-white/90 dark:bg-black/80 text-concrete hover:text-ink hover:border-concrete/40 border-border shadow-sm'
}`}
>
<div className="w-1.5 h-1.5 rounded-full" style={{ backgroundColor: c.color }} />
<span>{c.name ?? String(c.id)}</span>
<div className="w-2 h-2 rounded-full shrink-0" style={{ backgroundColor: c.color }} />
<span className="truncate max-w-[120px]">{c.name ?? String(c.id)}</span>
<span className={`text-[8px] px-1 rounded-full shrink-0 ${isSelected ? 'bg-white/20' : 'bg-black/5 dark:bg-white/10'}`}>
{c.noteIds.length}
</span>
</button>
)
})}

View File

@@ -703,6 +703,27 @@ 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)
useEffect(() => {
if (isImmersiveRoute) {
const stored = localStorage.getItem('insights-sidebar-collapsed')
setUserCollapsed(stored === 'true')
}
}, [isImmersiveRoute])
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') &&
@@ -1158,15 +1179,13 @@ export function Sidebar({ className, user }: { className?: string; user?: any })
<aside
className={cn(
// Sur /insights : sidebar en overlay (toutes tailles d'écran)
// Ailleurs : overlay sur mobile, relative sur desktop
isImmersiveRoute
isImmersiveRoute && userCollapsed
? 'fixed inset-y-0 start-0 z-[70]'
: 'fixed inset-y-0 start-0 z-[70] md:relative md:z-auto',
'h-full min-h-0 w-80 xl:w-[22rem] 2xl:w-[26rem] shrink-0 flex flex-row overflow-hidden',
'transition-transform duration-300 ease-in-out',
isImmersiveRoute
? (isMobileOpen ? 'translate-x-0 shadow-2xl' : '-translate-x-full')
isImmersiveRoute && userCollapsed
? '-translate-x-full'
: (isMobileOpen ? 'translate-x-0 shadow-2xl' : '-translate-x-full md:translate-x-0'),
'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