11import { useNavigate } from "@tanstack/react-router" ;
22import type { ServerProvider } from "@t3tools/contracts" ;
33import { CircleCheckIcon , DownloadIcon , LoaderIcon , TriangleAlertIcon , XIcon } from "lucide-react" ;
4- import { useCallback , useEffect , useState , type CSSProperties } from "react" ;
4+ import {
5+ useCallback ,
6+ useEffect ,
7+ useRef ,
8+ useState ,
9+ type CSSProperties ,
10+ type Dispatch ,
11+ type SetStateAction ,
12+ type TransitionEvent ,
13+ } from "react" ;
514
615import { useServerProviders } from "../../rpc/serverState" ;
716import {
@@ -37,48 +46,36 @@ function latestProviderCheckedAt(
3746 ) ;
3847}
3948
40- export function SidebarProviderUpdatePill ( ) {
41- const navigate = useNavigate ( ) ;
42- const providers = useServerProviders ( ) ;
43- const [ dismissedKeys , setDismissedKeys ] = useState < ReadonlySet < string > > ( ( ) => new Set ( ) ) ;
44- const [ renderedView , setRenderedView ] = useState < ProviderUpdateSidebarPillView | null > ( null ) ;
45- const [ pendingView , setPendingView ] = useState < ProviderUpdateSidebarPillView | null > ( null ) ;
46- const [ exitingKey , setExitingKey ] = useState < string | null > ( null ) ;
47- const [ dismissAfterExitKey , setDismissAfterExitKey ] = useState < string | null > ( null ) ;
48- const [ visibleAfterIso , setVisibleAfterIso ] = useState < string | undefined > ( ) ;
49- const effectiveVisibleAfterIso = visibleAfterIso ?? latestProviderCheckedAt ( providers ) ;
50- const view = getProviderUpdateSidebarPillView ( providers , {
51- ...( effectiveVisibleAfterIso !== undefined
52- ? { visibleAfterIso : effectiveVisibleAfterIso }
53- : { } ) ,
54- dismissedKeys,
55- } ) ;
49+ function useProviderUpdateVisibleAfterIso (
50+ providers : ReadonlyArray < Pick < ServerProvider , "checkedAt" > > ,
51+ ) : string | undefined {
52+ const visibleAfterIsoRef = useRef < string | undefined > ( undefined ) ;
5653
57- useEffect ( ( ) => {
58- if ( visibleAfterIso === undefined && effectiveVisibleAfterIso !== undefined ) {
59- setVisibleAfterIso ( effectiveVisibleAfterIso ) ;
60- }
61- } , [ effectiveVisibleAfterIso , visibleAfterIso ] ) ;
54+ if ( visibleAfterIsoRef . current === undefined ) {
55+ visibleAfterIsoRef . current = latestProviderCheckedAt ( providers ) ;
56+ }
6257
63- const openProviderSettings = useCallback ( ( ) => {
64- void navigate ( { to : "/settings/providers" } ) ;
65- } , [ navigate ] ) ;
66- const displayedView = renderedView ?? view ;
67- const dismissAfterVisibleMs = displayedView ?. dismissAfterVisibleMs ;
68- const viewKey = displayedView ?. key ?? null ;
69- const showDismissProgress =
70- dismissAfterVisibleMs !== undefined &&
71- displayedView ?. tone !== "loading" &&
72- exitingKey !== viewKey ;
58+ return visibleAfterIsoRef . current ;
59+ }
60+
61+ function useDisplayedProviderUpdatePill ( input : {
62+ view : ProviderUpdateSidebarPillView | null ;
63+ setDismissedKeys : Dispatch < SetStateAction < ReadonlySet < string > > > ;
64+ } ) {
65+ const { view, setDismissedKeys } = input ;
66+ const [ renderedView , setRenderedView ] = useState < ProviderUpdateSidebarPillView | null > ( view ) ;
67+ const [ exitingKey , setExitingKey ] = useState < string | null > ( null ) ;
68+ const pendingViewRef = useRef < ProviderUpdateSidebarPillView | null > ( null ) ;
69+ const dismissAfterExitKeyRef = useRef < string | null > ( null ) ;
7370
7471 const startExit = useCallback (
7572 ( key : string , nextView : ProviderUpdateSidebarPillView | null , dismissKey ?: string ) => {
7673 if ( exitingKey === key ) {
7774 return ;
7875 }
79- setPendingView ( nextView ) ;
76+ pendingViewRef . current = nextView ;
77+ dismissAfterExitKeyRef . current = dismissKey ?? null ;
8078 setExitingKey ( key ) ;
81- setDismissAfterExitKey ( dismissKey ?? null ) ;
8279 } ,
8380 [ exitingKey ] ,
8481 ) ;
@@ -103,6 +100,46 @@ export function SidebarProviderUpdatePill() {
103100 }
104101 } , [ exitingKey , renderedView , startExit , view ] ) ;
105102
103+ const displayedView = renderedView ?? view ;
104+ const onTransitionEnd = useCallback (
105+ ( event : TransitionEvent < HTMLDivElement > ) => {
106+ if ( event . target !== event . currentTarget ) {
107+ return ;
108+ }
109+ if ( ! displayedView || exitingKey !== displayedView . key ) {
110+ return ;
111+ }
112+ if ( dismissAfterExitKeyRef . current === displayedView . key ) {
113+ setDismissedKeys ( ( previous ) => new Set ( previous ) . add ( displayedView . key ) ) ;
114+ }
115+ setRenderedView ( pendingViewRef . current ) ;
116+ pendingViewRef . current = null ;
117+ dismissAfterExitKeyRef . current = null ;
118+ setExitingKey ( null ) ;
119+ } ,
120+ [ displayedView , exitingKey , setDismissedKeys ] ,
121+ ) ;
122+
123+ return {
124+ displayedView,
125+ exitingKey,
126+ onTransitionEnd,
127+ startExit,
128+ } ;
129+ }
130+
131+ function useProviderUpdateAutoDismiss ( input : {
132+ dismissAfterVisibleMs : number | undefined ;
133+ exitingKey : string | null ;
134+ startExit : (
135+ key : string ,
136+ nextView : ProviderUpdateSidebarPillView | null ,
137+ dismissKey ?: string ,
138+ ) => void ;
139+ viewKey : string | null ;
140+ } ) {
141+ const { dismissAfterVisibleMs, exitingKey, startExit, viewKey } = input ;
142+
106143 useEffect ( ( ) => {
107144 if ( ! dismissAfterVisibleMs || ! viewKey ) {
108145 return ;
@@ -116,6 +153,40 @@ export function SidebarProviderUpdatePill() {
116153
117154 return ( ) => window . clearTimeout ( timeoutId ) ;
118155 } , [ dismissAfterVisibleMs , exitingKey , startExit , viewKey ] ) ;
156+ }
157+
158+ export function SidebarProviderUpdatePill ( ) {
159+ const navigate = useNavigate ( ) ;
160+ const providers = useServerProviders ( ) ;
161+ const [ dismissedKeys , setDismissedKeys ] = useState < ReadonlySet < string > > ( ( ) => new Set ( ) ) ;
162+ const effectiveVisibleAfterIso = useProviderUpdateVisibleAfterIso ( providers ) ;
163+ const view = getProviderUpdateSidebarPillView ( providers , {
164+ ...( effectiveVisibleAfterIso !== undefined
165+ ? { visibleAfterIso : effectiveVisibleAfterIso }
166+ : { } ) ,
167+ dismissedKeys,
168+ } ) ;
169+
170+ const openProviderSettings = useCallback ( ( ) => {
171+ void navigate ( { to : "/settings/providers" } ) ;
172+ } , [ navigate ] ) ;
173+ const { displayedView, exitingKey, onTransitionEnd, startExit } = useDisplayedProviderUpdatePill ( {
174+ view,
175+ setDismissedKeys,
176+ } ) ;
177+ const dismissAfterVisibleMs = displayedView ?. dismissAfterVisibleMs ;
178+ const viewKey = displayedView ?. key ?? null ;
179+ const showDismissProgress =
180+ dismissAfterVisibleMs !== undefined &&
181+ displayedView ?. tone !== "loading" &&
182+ exitingKey !== viewKey ;
183+
184+ useProviderUpdateAutoDismiss ( {
185+ dismissAfterVisibleMs,
186+ exitingKey,
187+ startExit,
188+ viewKey,
189+ } ) ;
119190
120191 if ( ! displayedView ) {
121192 return null ;
@@ -130,21 +201,7 @@ export function SidebarProviderUpdatePill() {
130201 ? "pointer-events-none translate-y-1.5 opacity-0"
131202 : "translate-y-0 opacity-100"
132203 } `}
133- onTransitionEnd = { ( event ) => {
134- if ( event . target !== event . currentTarget ) {
135- return ;
136- }
137- if ( ! displayedView || exitingKey !== displayedView . key ) {
138- return ;
139- }
140- if ( dismissAfterExitKey === displayedView . key ) {
141- setDismissedKeys ( ( previous ) => new Set ( previous ) . add ( displayedView . key ) ) ;
142- }
143- setRenderedView ( pendingView ) ;
144- setPendingView ( null ) ;
145- setExitingKey ( null ) ;
146- setDismissAfterExitKey ( null ) ;
147- } }
204+ onTransitionEnd = { onTransitionEnd }
148205 >
149206 { showDismissProgress ? (
150207 < div
0 commit comments