Sidebar redesign, dropdown fixes, and soft-delete UI #57
@@ -75,7 +75,10 @@ function Label({ children, collapsed }: { children: React.ReactNode; collapsed:
|
|||||||
|
|
||||||
// ─── Site Picker ────────────────────────────────────────────
|
// ─── Site Picker ────────────────────────────────────────────
|
||||||
|
|
||||||
function SitePicker({ sites, siteId, collapsed }: { sites: Site[]; siteId: string; collapsed: boolean }) {
|
function SitePicker({ sites, siteId, collapsed, onExpand, onCollapse, wasCollapsed }: {
|
||||||
|
sites: Site[]; siteId: string; collapsed: boolean
|
||||||
|
onExpand: () => void; onCollapse: () => void; wasCollapsed: React.MutableRefObject<boolean>
|
||||||
|
}) {
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
const [search, setSearch] = useState('')
|
const [search, setSearch] = useState('')
|
||||||
const [faviconFailed, setFaviconFailed] = useState(false)
|
const [faviconFailed, setFaviconFailed] = useState(false)
|
||||||
@@ -88,15 +91,23 @@ function SitePicker({ sites, siteId, collapsed }: { sites: Site[]; siteId: strin
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handler = (e: MouseEvent) => {
|
const handler = (e: MouseEvent) => {
|
||||||
if (ref.current && !ref.current.contains(e.target as Node)) { setOpen(false); setSearch('') }
|
if (ref.current && !ref.current.contains(e.target as Node)) {
|
||||||
|
if (open) {
|
||||||
|
setOpen(false); setSearch('')
|
||||||
|
// Re-collapse if we auto-expanded
|
||||||
|
if (wasCollapsed.current) { onCollapse(); wasCollapsed.current = false }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
document.addEventListener('mousedown', handler)
|
document.addEventListener('mousedown', handler)
|
||||||
return () => document.removeEventListener('mousedown', handler)
|
return () => document.removeEventListener('mousedown', handler)
|
||||||
}, [])
|
}, [open, onCollapse, wasCollapsed])
|
||||||
|
|
||||||
const switchSite = (id: string) => {
|
const switchSite = (id: string) => {
|
||||||
router.push(`/sites/${id}${pathname.replace(/^\/sites\/[^/]+/, '')}`)
|
router.push(`/sites/${id}${pathname.replace(/^\/sites\/[^/]+/, '')}`)
|
||||||
setOpen(false); setSearch('')
|
setOpen(false); setSearch('')
|
||||||
|
// Re-collapse if we auto-expanded
|
||||||
|
if (wasCollapsed.current) { onCollapse(); wasCollapsed.current = false }
|
||||||
}
|
}
|
||||||
|
|
||||||
const filtered = sites.filter(
|
const filtered = sites.filter(
|
||||||
@@ -106,7 +117,16 @@ function SitePicker({ sites, siteId, collapsed }: { sites: Site[]; siteId: strin
|
|||||||
return (
|
return (
|
||||||
<div className="relative mb-4 px-2" ref={ref}>
|
<div className="relative mb-4 px-2" ref={ref}>
|
||||||
<button
|
<button
|
||||||
onClick={() => { if (!collapsed) setOpen(!open) }}
|
onClick={() => {
|
||||||
|
if (collapsed) {
|
||||||
|
wasCollapsed.current = true
|
||||||
|
onExpand()
|
||||||
|
// Open picker after sidebar expands
|
||||||
|
setTimeout(() => setOpen(true), 220)
|
||||||
|
} else {
|
||||||
|
setOpen(!open)
|
||||||
|
}
|
||||||
|
}}
|
||||||
className="w-full flex items-center gap-2.5 rounded-lg px-2.5 py-2 text-sm font-medium text-neutral-700 dark:text-neutral-200 hover:bg-neutral-100 dark:hover:bg-neutral-800 overflow-hidden"
|
className="w-full flex items-center gap-2.5 rounded-lg px-2.5 py-2 text-sm font-medium text-neutral-700 dark:text-neutral-200 hover:bg-neutral-100 dark:hover:bg-neutral-800 overflow-hidden"
|
||||||
>
|
>
|
||||||
<span className="w-7 h-7 rounded-md bg-brand-orange/10 text-brand-orange flex items-center justify-center text-xs font-bold shrink-0 overflow-hidden">
|
<span className="w-7 h-7 rounded-md bg-brand-orange/10 text-brand-orange flex items-center justify-center text-xs font-bold shrink-0 overflow-hidden">
|
||||||
@@ -224,9 +244,10 @@ export default function Sidebar({
|
|||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
const [sites, setSites] = useState<Site[]>([])
|
const [sites, setSites] = useState<Site[]>([])
|
||||||
const [pendingHref, setPendingHref] = useState<string | null>(null)
|
const [pendingHref, setPendingHref] = useState<string | null>(null)
|
||||||
|
const wasCollapsedRef = useRef(false)
|
||||||
const [collapsed, setCollapsed] = useState(() => {
|
const [collapsed, setCollapsed] = useState(() => {
|
||||||
if (typeof window === 'undefined') return false
|
if (typeof window === 'undefined') return true // SSR: default collapsed to avoid hydration flash
|
||||||
return localStorage.getItem(SIDEBAR_KEY) === 'true'
|
return localStorage.getItem(SIDEBAR_KEY) !== 'false' // default collapsed unless explicitly set to false
|
||||||
})
|
})
|
||||||
|
|
||||||
useEffect(() => { listSites().then(setSites).catch(() => {}) }, [])
|
useEffect(() => { listSites().then(setSites).catch(() => {}) }, [])
|
||||||
@@ -248,6 +269,14 @@ export default function Sidebar({
|
|||||||
setCollapsed((prev) => { const next = !prev; localStorage.setItem(SIDEBAR_KEY, String(next)); return next })
|
setCollapsed((prev) => { const next = !prev; localStorage.setItem(SIDEBAR_KEY, String(next)); return next })
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const expand = useCallback(() => {
|
||||||
|
setCollapsed(false); localStorage.setItem(SIDEBAR_KEY, 'false')
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const collapse = useCallback(() => {
|
||||||
|
setCollapsed(true); localStorage.setItem(SIDEBAR_KEY, 'true')
|
||||||
|
}, [])
|
||||||
|
|
||||||
const handleNavigate = useCallback((href: string) => { setPendingHref(href) }, [])
|
const handleNavigate = useCallback((href: string) => { setPendingHref(href) }, [])
|
||||||
|
|
||||||
const sidebarContent = (isMobile: boolean) => {
|
const sidebarContent = (isMobile: boolean) => {
|
||||||
@@ -266,7 +295,7 @@ export default function Sidebar({
|
|||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
{/* Site Picker */}
|
{/* Site Picker */}
|
||||||
<SitePicker sites={sites} siteId={siteId} collapsed={c} />
|
<SitePicker sites={sites} siteId={siteId} collapsed={c} onExpand={expand} onCollapse={collapse} wasCollapsed={wasCollapsedRef} />
|
||||||
|
|
||||||
{/* Nav Groups */}
|
{/* Nav Groups */}
|
||||||
<nav className="flex-1 overflow-y-auto overflow-x-hidden px-2 space-y-4">
|
<nav className="flex-1 overflow-y-auto overflow-x-hidden px-2 space-y-4">
|
||||||
|
|||||||
Reference in New Issue
Block a user