11"use client" ;
2- import { useEffect , useState , ReactNode , Suspense } from "react" ;
2+ import { useEffect , useRef , useState , ReactNode , Suspense } from "react" ;
33import { AnimatePresence } from "framer-motion" ;
44import { usePathname } from "@/i18n/navigation" ;
55import Preloader from "./preloader" ;
@@ -9,79 +9,96 @@ type PageWithLoaderProps = {
99 children : ReactNode ;
1010} ;
1111
12+ type NavigatorWithConnection = Navigator & {
13+ connection ?: {
14+ saveData ?: boolean ;
15+ } ;
16+ } ;
17+
18+ function prefersReducedMotion ( ) : boolean {
19+ if (
20+ typeof window === "undefined" ||
21+ typeof window . matchMedia !== "function"
22+ ) {
23+ return false ;
24+ }
25+ return window . matchMedia ( "(prefers-reduced-motion: reduce)" ) . matches ;
26+ }
27+
28+ function saveDataEnabled ( ) : boolean {
29+ if ( typeof navigator === "undefined" ) return false ;
30+ const nav = navigator as NavigatorWithConnection ;
31+ return Boolean ( nav . connection ?. saveData ) ;
32+ }
33+
1234export default function PageWithLoader ( {
1335 text,
1436 children,
1537} : PageWithLoaderProps ) {
1638 const pathname = usePathname ( ) ;
17- const [ isPreloaderVisible , setIsPreloaderVisible ] = useState ( true ) ;
18- const [ shouldShowGreetings , setShouldShowGreetings ] = useState ( false ) ;
19- const [ isContentReady , setIsContentReady ] = useState ( false ) ;
39+ // Inicialmente oculto para não bloquear o carregamento
40+ const [ isPreloaderVisible , setIsPreloaderVisible ] = useState < boolean > ( false ) ;
41+ const [ shouldShowGreetings , setShouldShowGreetings ] =
42+ useState < boolean > ( false ) ;
43+ const isInitialMount = useRef < boolean > ( true ) ;
2044
21- // Gerencia a lógica de saudações na página inicial
45+ // Exibe saudações ocasionais apenas na home em navegação "dura" e respeita preferências
2246 useEffect ( ( ) => {
2347 const isHome = pathname === "/" ;
24- const navEntries = performance . getEntriesByType (
25- "navigation"
26- ) as PerformanceNavigationTiming [ ] ;
48+ const navEntries = performance . getEntriesByType ( "navigation" ) ;
2749 const isHardNavigation =
28- navEntries . length && navEntries [ 0 ] . type === "navigate" ;
50+ navEntries . length > 0 &&
51+ ( navEntries [ 0 ] as PerformanceNavigationTiming ) . type === "navigate" ;
2952
3053 const GREETING_TIMEOUT_MINUTES = 30 ;
3154 const lastShown = sessionStorage . getItem ( "last-home-greeting" ) ;
3255 const now = Date . now ( ) ;
33-
3456 const hasExpired =
3557 ! lastShown ||
3658 now - parseInt ( lastShown , 10 ) > GREETING_TIMEOUT_MINUTES * 60 * 1000 ;
3759
60+ const prefersReduced = prefersReducedMotion ( ) ;
61+ const saveData = saveDataEnabled ( ) ;
62+
63+ if ( prefersReduced || saveData ) {
64+ setShouldShowGreetings ( false ) ;
65+ setIsPreloaderVisible ( false ) ;
66+ return ;
67+ }
68+
3869 if ( isHome && isHardNavigation && hasExpired ) {
3970 setShouldShowGreetings ( true ) ;
71+ setIsPreloaderVisible ( true ) ;
4072 sessionStorage . setItem ( "last-home-greeting" , now . toString ( ) ) ;
4173 }
4274 } , [ pathname ] ) ;
4375
44- // Gerencia a transição do preloader e conteúdo
76+ // Preloader curto em trocas internas de rota (não no primeiro render)
4577 useEffect ( ( ) => {
46- // Inicia o carregamento do conteúdo imediatamente
47- const contentLoadTimeout = setTimeout ( ( ) => {
48- setIsContentReady ( true ) ;
49- } , 100 ) ; // Pequeno delay para garantir que o React tenha tempo de montar o componente
50-
51- // Configura o timer para remover o preloader
52- const preloaderTimeout = setTimeout (
53- ( ) => {
54- requestAnimationFrame ( ( ) => {
55- document . body . style . cursor = "default" ;
56- window . scrollTo ( 0 , 0 ) ;
57- setIsPreloaderVisible ( false ) ;
58- } ) ;
59- } ,
60- shouldShowGreetings ? 2000 : 700
61- ) ;
62-
63- return ( ) => {
64- clearTimeout ( contentLoadTimeout ) ;
65- clearTimeout ( preloaderTimeout ) ;
66- } ;
78+ if ( isInitialMount . current ) {
79+ isInitialMount . current = false ;
80+ return ;
81+ }
82+ if ( shouldShowGreetings ) return ;
83+ setIsPreloaderVisible ( true ) ;
84+ const t = setTimeout ( ( ) => setIsPreloaderVisible ( false ) , 350 ) ;
85+ return ( ) => clearTimeout ( t ) ;
6786 } , [ pathname , shouldShowGreetings ] ) ;
6887
6988 return (
7089 < >
7190 < AnimatePresence mode = "wait" >
7291 { isPreloaderVisible && (
73- < Preloader text = { text } showGreetings = { shouldShowGreetings } />
92+ < Preloader
93+ text = { text }
94+ showGreetings = { shouldShowGreetings }
95+ onFinish = { ( ) => setIsPreloaderVisible ( false ) }
96+ />
7497 ) }
7598 </ AnimatePresence >
7699
77- < div
78- style = { {
79- opacity : isPreloaderVisible ? 0 : 1 ,
80- transition : "opacity 0.3s ease-in-out" ,
81- visibility : isPreloaderVisible ? "hidden" : "visible" ,
82- } }
83- >
84- < Suspense fallback = { null } > { isContentReady && children } </ Suspense >
100+ < div >
101+ < Suspense fallback = { null } > { children } </ Suspense >
85102 </ div >
86103 </ >
87104 ) ;
0 commit comments