@@ -16,97 +16,105 @@ export function AINextPageSuggestions() {
16
16
const currentPage = usePageContext ( ) ;
17
17
const visitedPages = useVisitedPages ( ( state ) => state . pages ) ;
18
18
19
- const [ pages , setPages ] = useState < SuggestedPage [ ] > (
20
- selectedJourney ?. pages ?? Array . from ( { length : 5 } )
21
- ) ;
19
+ const [ pages , setPages ] = useState < SuggestedPage [ ] > ( selectedJourney ?. pages ?? [ ] ) ;
20
+ const [ suggestedPages , setSuggestedPages ] = useState < SuggestedPage [ ] > ( [ ] ) ;
22
21
23
22
useEffect ( ( ) => {
24
23
let canceled = false ;
25
24
26
25
if ( selectedJourney ?. pages && selectedJourney . pages . length > 0 ) {
27
26
setPages ( selectedJourney . pages ) ;
27
+ } else {
28
+ setPages ( suggestedPages ) ;
28
29
}
29
30
30
- ( async ( ) => {
31
- const stream = await streamNextPageSuggestions ( {
32
- currentPage : {
33
- id : currentPage . pageId ,
34
- title : currentPage . title ,
35
- } ,
36
- currentSpace : {
37
- id : currentPage . spaceId ,
38
- } ,
39
- visitedPages : visitedPages ,
40
- } ) ;
31
+ if ( suggestedPages . length === 0 ) {
32
+ ( async ( ) => {
33
+ const stream = await streamNextPageSuggestions ( {
34
+ currentPage : {
35
+ id : currentPage . pageId ,
36
+ title : currentPage . title ,
37
+ } ,
38
+ currentSpace : {
39
+ id : currentPage . spaceId ,
40
+ } ,
41
+ visitedPages : visitedPages ,
42
+ } ) ;
41
43
42
- for await ( const page of stream ) {
43
- if ( canceled ) return ;
44
+ for await ( const page of stream ) {
45
+ if ( canceled ) return ;
44
46
45
- setPages ( ( prev ) => {
46
- const newPages = [ ...prev ] ;
47
- const emptyIndex = newPages . findIndex ( ( j ) => ! j ?. id ) ;
48
- if ( emptyIndex >= 0 ) {
49
- newPages [ emptyIndex ] = page ;
50
- }
51
- return newPages ;
52
- } ) ;
53
- }
54
- } ) ( ) ;
47
+ setPages ( ( prev ) => [ ...prev , page ] ) ;
48
+ setSuggestedPages ( ( prev ) => [ ...prev , page ] ) ;
49
+ }
50
+ } ) ( ) ;
51
+ }
55
52
56
53
return ( ) => {
57
54
canceled = true ;
58
55
} ;
59
- } , [ selectedJourney , currentPage . pageId , currentPage . spaceId , currentPage . title , visitedPages ] ) ;
56
+ } , [
57
+ selectedJourney ,
58
+ currentPage . pageId ,
59
+ currentPage . spaceId ,
60
+ currentPage . title ,
61
+ visitedPages ,
62
+ suggestedPages ,
63
+ ] ) ;
60
64
61
65
return (
62
- < AnimatePresence initial = { false } >
63
- { open && (
64
- < motion . div
65
- key = "next-page-suggestions"
66
- initial = { { opacity : 0 , height : 0 } }
67
- animate = { { opacity : 1 , height : 'auto' } }
68
- exit = { { opacity : 0 , height : 0 } }
69
- >
70
- < div className = "relative mb-2 flex flex-row items-center gap-3" >
66
+ open && (
67
+ < div className = "animate-fadeIn" >
68
+ < motion . div className = "mb-2 flex flex-row items-start gap-3" >
69
+ < AnimatePresence mode = "wait" >
71
70
{ selectedJourney ?. icon ? (
72
- < Icon
71
+ < motion . div
72
+ initial = { { opacity : 0 , scale : 0.8 } }
73
+ animate = { { opacity : 1 , scale : 1 } }
74
+ exit = { { opacity : 0 , scale : 0.8 } }
75
+ transition = { { delay : 0.2 } }
73
76
key = { selectedJourney . icon }
74
- icon = { selectedJourney . icon as IconName }
75
- className = "absolute left-0 size-6 animate-scaleIn text-tint-subtle [animation-delay:100ms]"
76
- />
77
+ >
78
+ < Icon
79
+ icon = { selectedJourney . icon as IconName }
80
+ className = "left-0 mt-2 size-6 shrink-0 text-tint-subtle"
81
+ />
82
+ </ motion . div >
77
83
) : null }
78
- < div
79
- className = { tcls (
80
- 'flex flex-col transition-all' ,
81
- selectedJourney ?. icon ? 'ml-9' : 'delay-0'
82
- ) }
83
- >
84
- < div className = "flex flex-row items-center gap-2 font-semibold text-tint text-xs uppercase tracking-wide" >
85
- Suggested pages
86
- </ div >
84
+ </ AnimatePresence >
85
+ < motion . div className = { tcls ( 'flex flex-col' ) } layout = "position" >
86
+ < div className = "font-semibold text-tint text-xs uppercase tracking-wide" >
87
+ Suggested pages
88
+ </ div >
89
+ < AnimatePresence mode = "wait" >
87
90
{ selectedJourney ?. label ? (
88
- < h5
91
+ < motion . h5
92
+ initial = { { opacity : 0 } }
93
+ animate = { { opacity : 1 } }
94
+ exit = { { opacity : 0 } }
89
95
key = { selectedJourney . label }
90
- className = "animate-fadeIn font-semibold text-base"
96
+ className = "font-semibold text-base"
91
97
>
92
98
{ selectedJourney . label }
93
- </ h5 >
99
+ </ motion . h5 >
94
100
) : null }
95
- </ div >
96
- </ div >
97
- < div className = "-mb-1.5 flex flex-col gap-1" >
98
- { pages . map ( ( page , index ) =>
99
- page ?. id ? (
101
+ </ AnimatePresence >
102
+ </ motion . div >
103
+ </ motion . div >
104
+ < div className = "-mb-1.5 flex flex-col gap-1" >
105
+ { Object . assign ( Array . from ( { length : 5 } ) , pages ) . map (
106
+ ( page : SuggestedPage | undefined , index ) =>
107
+ page ? (
100
108
< Link
101
- key = { selectedJourney ?. label + page . id }
109
+ key = { ` ${ selectedJourney ?. label } - ${ page . id } ` }
102
110
className = "-mx-2 flex animate-fadeIn gap-2 rounded px-2.5 py-1 transition-all hover:bg-tint-hover hover:text-tint-strong"
103
111
href = { page . href }
104
112
style = { { animationDelay : `${ 0.2 + index * 0.05 } s` } }
105
113
>
106
114
{ page . icon ? (
107
115
< Icon
108
116
icon = { page . icon as IconName }
109
- className = "mt-0.5 size-4 text-tint-subtle"
117
+ className = "mt-0.5 size-4 shrink-0 text-tint-subtle"
110
118
/>
111
119
) : null }
112
120
{ page . emoji ? < Emoji code = { page . emoji } /> : null }
@@ -116,13 +124,15 @@ export function AINextPageSuggestions() {
116
124
< div
117
125
key = { index }
118
126
className = "my-1 h-5 animate-pulse rounded bg-tint-hover"
119
- style = { { animationDelay : `${ index * 0.2 } s` , width : `${ ( ( ( index * 17 ) % 50 ) + 50 ) } %` } }
127
+ style = { {
128
+ animationDelay : `${ index * 0.2 } s` ,
129
+ width : `${ ( ( index * 17 ) % 50 ) + 50 } %` ,
130
+ } }
120
131
/>
121
132
)
122
- ) }
123
- </ div >
124
- </ motion . div >
125
- ) }
126
- </ AnimatePresence >
133
+ ) }
134
+ </ div >
135
+ </ div >
136
+ )
127
137
) ;
128
138
}
0 commit comments