11import { Carousel , CarouselContent , CarouselItem , CarouselNav , CarouselNext , CarouselPrevious } from "@/components/ui/carousel" ;
22import { type EmblaViewportRefType } from "embla-carousel-react" ;
33import React , { type PointerEventHandler } from "react" ;
4- import type { EmblaCarouselType } from "embla-carousel" ;
4+ import type { EmblaCarouselType , EmblaEventType } from "embla-carousel" ;
55import { Card , CardContent , CardHeader , CardTitle } from "@/components/ui/card" ;
66import { DialogClose } from "./TransparentDialog" ;
77import { XIcon } from "lucide-react" ;
@@ -18,7 +18,7 @@ type ProjectCarouselProps = Omit<React.ComponentProps<typeof Carousel>, 'externa
1818 slides : CarouselContentItemWithTitle [ ] ,
1919 prevRef ?: React . RefObject < HTMLButtonElement | null > ,
2020 nextRef ?: React . RefObject < HTMLButtonElement | null > ,
21- onCarouselSelect : ( emblaApi ? : EmblaCarouselType ) => void ,
21+ onCarouselSelect : ( emblaApi : EmblaCarouselType | undefined , evtType ?: EmblaEventType ) => void ,
2222 externalApi ?: EmblaCarouselType ,
2323 showToast : ShowToastFn ,
2424 getHovercardContentForIndex : ( index : number ) => React . ReactNode ,
@@ -57,19 +57,43 @@ const CarouselSlideContentSkeleton = React.memo(()=>{
5757
5858
5959const ProjectCarouselItem = React . memo ( ( { project, handleRef, startTransition, index : i , slide, onPointerDown : clickCallback , showToast, ...props } : ProjectCarouselItemProps ) => {
60- // const isCurrent = React.useDeferredValue<boolean>(isCurrentItem);
6160 const skeleton = React . useMemo ( ( ) => < CarouselSlideContentSkeleton /> , [ ] ) ;
6261 const [ isCurrentItem , setIsCurrentItem ] = React . useState < boolean > ( false ) ;
63-
64- // const setIsCurrent = React.useCallback((isCurrent: boolean) => {
65- // startTransition(()=>{
66- // setIsCurrentItem(isCurrent);
67- // });
68- // }, [startTransition]);
62+ const isCurrent = React . useDeferredValue < boolean > ( isCurrentItem ) ;
63+
64+ const deactivationTimeout = React . useRef < ReturnType < typeof setTimeout > | undefined > ( undefined ) ;
65+ const activationTimeout = React . useRef < ReturnType < typeof setTimeout > | undefined > ( undefined ) ;
66+
67+ const setIsCurrent = React . useCallback ( ( isCurrent : boolean ) => {
68+ if ( isCurrent ) {
69+ clearTimeout ( deactivationTimeout . current ) ;
70+ deactivationTimeout . current = undefined ;
71+ if ( activationTimeout . current === undefined ) {
72+ activationTimeout . current = setTimeout ( ( ) => {
73+ React . startTransition ( ( ) =>
74+ setIsCurrentItem ( isCurrent )
75+ ) ;
76+ activationTimeout . current = undefined ;
77+ } , 200 ) ;
78+ }
79+ return ;
80+ }
81+ clearTimeout ( activationTimeout . current ) ;
82+ activationTimeout . current = undefined ;
83+ if ( deactivationTimeout . current === undefined ) {
84+ deactivationTimeout . current = setTimeout ( ( ) => {
85+ React . startTransition ( ( ) =>
86+ setIsCurrent ( false )
87+ ) ;
88+ deactivationTimeout . current = undefined ;
89+ } , 2500 ) ;
90+ }
91+ } , [ ] ) ;
6992
7093 React . useImperativeHandle ( handleRef , ( ) => ( {
71- setIsCurrent : setIsCurrentItem
72- } ) , [ ] ) ;
94+ // setIsCurrent: setIsCurrentItem
95+ setIsCurrent
96+ } ) , [ setIsCurrent ] ) ;
7397
7498 return < CarouselItem key = { i } id = { `slide-${ i } ` } className = "pointer-events-auto h-min" { ...props } >
7599 < Card className = "relative w-full flex pointer-events-auto max-h-[calc(100vh-(--spacing(25)))] overflow-y-scroll" >
@@ -89,12 +113,13 @@ const ProjectCarouselItem = React.memo(({ project, handleRef, startTransition, i
89113 { /* <DialogClose aria-label="Close carousel" data-slot="dialog-close" className="sr-only" /> */ }
90114 { /* above sr-only DialogClose is a compact additional accessible control -- main visual close still has icon */ }
91115 { /* <ShareButton className="absolute top-4 right-14 text-sm" showToast={showToast} openProjectId={slide.props["data-project-id"]} /> */ }
92- { /* <React.Suspense>
93- {slide}
94- </React.Suspense> */ }
95- { /* {isCurrentItem ? slide : skeleton} */ }
96116 < ProjectProvider project = { project } >
97- { slide }
117+ { isCurrent ?
118+ < React . Suspense fallback = { skeleton } >
119+ { slide }
120+ </ React . Suspense >
121+ : skeleton
122+ }
98123 </ ProjectProvider >
99124 </ CardContent >
100125 </ Card >
@@ -115,7 +140,8 @@ const ProjectCarousel = React.memo(({
115140 getHovercardContentForIndex,
116141} : ProjectCarouselProps ) => {
117142 const slideHandles = React . useRef < Record < string , React . RefObject < ProjectCarouselItemHandle > > > ( { } ) ;
118- slideHandles . current = Object . fromEntries ( slides . map ( ( slide ) => [ slide . props [ "data-project-id" ] , slideHandles . current [ slide . props [ 'data-project-id' ] ?? React . createRef ( ) ] ] ) ) ;
143+ slideHandles . current = Object . fromEntries ( slides . map ( ( slide ) => [ slide . props [ "data-project-id" ] , slideHandles . current [ slide . props [ 'data-project-id' ] ] ?? React . createRef ( ) ] ) ) ;
144+
119145 const [ _isPending , startTransition ] = React . useTransition ( ) ;
120146
121147 const slideElems = React . useMemo ( ( ) => {
@@ -124,41 +150,37 @@ const ProjectCarousel = React.memo(({
124150 ) )
125151 } , [ slides , showToast ] ) ;
126152
127- // const slideHovercards = React.useMemo(()=>{
128-
129- // }, [])
130-
131- // const onSelect0: typeof onSelect = React.useCallback((api) => {
132- // // if(api) {
133- // // const index = api.selectedScrollSnap();
134- // // const prevIndex = api.previousScrollSnap();
135-
136- // // const currId = slides[index]?.props['data-project-id'];
137- // // const currHandle = currId ? slideHandles.current[currId] : undefined;
138-
139- // // if(currHandle) {
140- // // const prevId = slides[prevIndex]?.props['data-project-id'];
141- // // const prevHandle = (prevIndex === index || !prevId) ? undefined : slideHandles.current[prevId];
142- // // console.log(currHandle, prevHandle);
143- // // React.startTransition(()=>{
144- // // try {
145- // // prevHandle?.current?.setIsCurrent(false);
146- // // } finally {
147- // // currHandle?.current?.setIsCurrent(true);
148- // // }
149- // // });
150- // // }
151- // // }
152- // }, [slides]);
153-
154- // const onSelect_: typeof onSelect = React.useCallback(api=>{
155- // try {
156- // onSelect0(api);
157- // } finally {
158- // // startTransition(()=>onSelect(api));
159- // onSelect(api);
160- // }
161- // }, [onSelect, onSelect0]);
153+ const onSelect0 : typeof onSelect = React . useCallback ( ( api , _evtType ) => {
154+ if ( ! api ) return ;
155+ const index = api . selectedScrollSnap ( ) ;
156+ const prevIndex = api . previousScrollSnap ( ) ;
157+
158+ const currId = slides [ index ] ?. props [ 'data-project-id' ] ;
159+ const currHandle = currId ? slideHandles . current [ currId ] : undefined ;
160+
161+ // console.log('api/currHandle:', api, currHandle);
162+
163+ // if(!currHandle) return;
164+ const prevId = slides [ prevIndex ] ?. props [ 'data-project-id' ] ;
165+ const prevHandle = ( prevIndex === index || ! prevId ) ? undefined : slideHandles . current [ prevId ] ;
166+ // console.log(currHandle, prevHandle);
167+ startTransition ( ( ) => {
168+ try {
169+ prevHandle ?. current ?. setIsCurrent ( false ) ;
170+ } finally {
171+ currHandle ?. current ?. setIsCurrent ( true ) ;
172+ }
173+ } ) ;
174+ } , [ slides ] ) ;
175+
176+ const onSelect_ : typeof onSelect = React . useCallback ( ( api , evtType ) => {
177+ try {
178+ onSelect0 ( api , evtType ) ;
179+ } finally {
180+ // startTransition(()=>onSelect(api));
181+ onSelect ( api , evtType ) ;
182+ }
183+ } , [ onSelect , onSelect0 ] ) ;
162184
163185 return < Carousel
164186 ref = { emblaRef }
@@ -167,7 +189,7 @@ const ProjectCarousel = React.memo(({
167189 opts = { opts }
168190 className = "overflow-visible z-60 w-full max-w-[calc(min(100vw,var(--container-2xl)))] pointer-events-none
169191 "
170- onCarouselSelect = { onSelect }
192+ onCarouselSelect = { onSelect_ }
171193 >
172194 < CarouselContent
173195 id = "embla-container"
0 commit comments