fix(insights): sidebar user-controlled + cluster chips organisées
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:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
})}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user