feat: refactor TopLoader to use a hook for active state management and improve CSS for fixed positioning
This commit is contained in:
@@ -1,26 +1,44 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useEffect, useState, useSyncExternalStore } from 'react'
|
||||
|
||||
/**
|
||||
* Thin top strip shown while `active` is true. Stays visible for at least
|
||||
* `minDurationMs` so quick fetches don't produce an invisible flicker.
|
||||
*/
|
||||
export function TopLoader({ active, minDurationMs = 350 }: { active: boolean; minDurationMs?: number }) {
|
||||
const [visible, setVisible] = useState(active)
|
||||
// Global ref-count store. Any page can call useTopLoader(active) and the
|
||||
// shared <TopLoader /> (mounted once in the dashboard layout) will reflect it.
|
||||
|
||||
let activeCount = 0
|
||||
const listeners = new Set<() => void>()
|
||||
function emit() { listeners.forEach(l => l()) }
|
||||
function subscribe(cb: () => void) { listeners.add(cb); return () => { listeners.delete(cb) } }
|
||||
function getSnapshot() { return activeCount }
|
||||
|
||||
export function useTopLoader(active: boolean) {
|
||||
useEffect(() => {
|
||||
if (!active) return
|
||||
activeCount += 1
|
||||
emit()
|
||||
return () => {
|
||||
activeCount = Math.max(0, activeCount - 1)
|
||||
emit()
|
||||
}
|
||||
}, [active])
|
||||
}
|
||||
|
||||
// Short tail so a sub-100ms fetch still shows the strip briefly.
|
||||
const TAIL_MS = 350
|
||||
|
||||
export function TopLoader() {
|
||||
const count = useSyncExternalStore(subscribe, getSnapshot, () => 0)
|
||||
const [tailing, setTailing] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (active) {
|
||||
setVisible(true)
|
||||
return
|
||||
}
|
||||
const t = setTimeout(() => setVisible(false), minDurationMs)
|
||||
if (count > 0) { setTailing(true); return }
|
||||
const t = setTimeout(() => setTailing(false), TAIL_MS)
|
||||
return () => clearTimeout(t)
|
||||
}, [active, minDurationMs])
|
||||
}, [count])
|
||||
|
||||
if (!visible) return null
|
||||
if (count === 0 && !tailing) return null
|
||||
return (
|
||||
<div className="top-loader">
|
||||
<div className="top-loader" aria-hidden="true">
|
||||
<div className="top-loader__bar">
|
||||
<span className="top-loader__head" />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user