1- import React , { useState , useContext , useEffect , useCallback } from "react" ;
1+ import React , {
2+ useState ,
3+ useContext ,
4+ useEffect ,
5+ useCallback ,
6+ useRef ,
7+ } from "react" ;
28import { ExpandMoreRight } from "@courselit/icons" ;
3- import { ColorSelector , Skeleton } from "@courselit/components-library" ;
9+ import { ColorSelector } from "@courselit/components-library" ;
410import { capitalize , FetchBuilder , truncate } from "@courselit/utils" ;
511import {
612 Columns3 ,
@@ -22,6 +28,8 @@ import StructureSelector from "./structure-selector";
2228import { ThemeCard } from "./theme-card" ;
2329import { Theme } from "@courselit/page-models" ;
2430import { ThemeWithDraftState } from "./theme-with-draft-state" ;
31+ import useThemes from "../use-themes" ;
32+ import { ThemeCardSkeleton } from "./theme-card-skeleton" ;
2533
2634type Section = {
2735 id : string ;
@@ -120,12 +128,7 @@ function ThemeEditor({
120128} : {
121129 onThemeChange : ( theme : Theme ) => void ;
122130} ) {
123- const [ themes , setThemes ] = useState < {
124- system : Theme [ ] ;
125- custom : Theme [ ] ;
126- } > ( { system : [ ] , custom : [ ] } ) ;
127- const [ firstLoad , setFirstLoad ] = useState ( true ) ;
128- const [ theme , setTheme ] = useState < Theme | null > ( null ) ;
131+ const { themes, theme, setTheme, loadThemes, loaded } = useThemes ( ) ;
129132 const [ navigationStack , setNavigationStack ] = useState < NavigationItem [ ] > (
130133 [ ] ,
131134 ) ;
@@ -136,97 +139,28 @@ function ThemeEditor({
136139 const address = useContext ( AddressContext ) ;
137140 const { theme : currentTheme , setTheme : setCurrentTheme } =
138141 useContext ( ThemeContext ) ;
142+ const selectedThemeRef = useRef < HTMLDivElement > ( null ) ;
139143
140144 useEffect ( ( ) => {
141145 if ( theme ) {
142146 onThemeChange ( theme ) ;
143- replaceThemeInThemesArray ( theme ) ;
144147 }
145148 } , [ theme ] ) ;
146149
147- const replaceThemeInThemesArray = useCallback ( ( theme : Theme ) => {
148- setThemes ( ( prev ) => ( {
149- ...prev ,
150- system : prev . system . map ( ( t ) => ( t . id === theme . id ? theme : t ) ) ,
151- custom : prev . custom . map ( ( t ) => ( t . id === theme . id ? theme : t ) ) ,
152- } ) ) ;
153- } , [ ] ) ;
154-
155- const loadThemes = useCallback ( async ( ) => {
156- setIsLoading ( true ) ;
157- const query = `
158- query {
159- themes: getThemes {
160- system {
161- themeId
162- name
163- theme {
164- colors
165- typography
166- interactives
167- structure
168- }
169- draftTheme {
170- colors
171- typography
172- interactives
173- structure
174- }
175- }
176- custom {
177- themeId
178- name
179- theme {
180- colors
181- typography
182- interactives
183- structure
184- }
185- draftTheme {
186- colors
187- typography
188- interactives
189- structure
190- }
191- }
192- }
193- }
194- ` ;
195- const fetch = new FetchBuilder ( )
196- . setUrl ( `${ address . backend } /api/graph` )
197- . setPayload ( { query } )
198- . setIsGraphQLEndpoint ( true )
199- . build ( ) ;
200-
201- try {
202- const { themes } = await fetch . exec ( ) ;
203- if ( themes ) {
204- setThemes ( {
205- system : themes . system . map ( transformServerTheme ) ,
206- custom : themes . custom . map ( transformServerTheme ) ,
207- } ) ;
208- }
209- } catch ( error ) {
210- console . error ( error ) ;
211- } finally {
212- setIsLoading ( false ) ;
213- }
214- } , [ address . backend ] ) ;
215-
216- useEffect ( ( ) => {
217- loadThemes ( ) ;
218- } , [ loadThemes ] ) ;
219-
220150 useEffect ( ( ) => {
221- if ( currentTheme ?. id ) {
222- let theme = themes . system . find ( ( t ) => t . id === currentTheme . id ) ;
223- theme = themes . custom . find ( ( t ) => t . id === currentTheme . id ) ;
224- if ( theme && firstLoad ) {
225- setTheme ( theme ) ;
226- setFirstLoad ( false ) ;
227- }
151+ if ( loaded ) {
152+ setIsLoading ( false ) ;
153+ // Scroll to selected theme after a short delay to ensure DOM is ready
154+ setTimeout ( ( ) => {
155+ if ( selectedThemeRef . current ) {
156+ selectedThemeRef . current . scrollIntoView ( {
157+ behavior : "smooth" ,
158+ block : "center" ,
159+ } ) ;
160+ }
161+ } , 100 ) ;
228162 }
229- } , [ themes , currentTheme ] ) ;
163+ } , [ loaded , theme ?. id ] ) ;
230164
231165 const navigateTo = useCallback ( ( item : NavigationItem ) => {
232166 setNavigationStack ( ( prev ) => [ ...prev , item ] ) ;
@@ -290,7 +224,6 @@ function ThemeEditor({
290224 const updatedThemeNew =
291225 transformServerTheme ( updatedTheme ) ;
292226 setTheme ( updatedThemeNew ) ;
293- setCurrentTheme ( updatedThemeNew ) ;
294227 loadThemes ( ) ;
295228 }
296229 }
@@ -373,34 +306,15 @@ function ThemeEditor({
373306 </ div >
374307 < div className = "space-y-2" >
375308 { isLoading
376- ? // Skeleton for system themes
377- Array ( 3 )
309+ ? Array ( 3 )
378310 . fill ( 0 )
379311 . map ( ( _ , index ) => (
380- < div
381- key = { index }
382- className = "w-full flex items-center justify-between px-3 py-3 rounded-md"
383- >
384- < div className = "flex items-center gap-3" >
385- < div className = "flex gap-1" >
386- { Array ( 5 )
387- . fill ( 0 )
388- . map ( ( _ , i ) => (
389- < Skeleton
390- key = { i }
391- className = "w-4 h-4 rounded-full"
392- />
393- ) ) }
394- </ div >
395- < Skeleton className = "h-4 w-32" />
396- </ div >
397- < Skeleton className = "h-4 w-4" />
398- </ div >
312+ < ThemeCardSkeleton key = { index } />
399313 ) )
400314 : themes . system . map ( ( themeItem ) => (
401315 < ThemeCard
402316 key = { themeItem . id }
403- name = { truncate ( themeItem . name , 20 ) }
317+ name = { truncate ( themeItem . name , 30 ) }
404318 palette = { colorOrder
405319 . map (
406320 ( key ) =>
@@ -416,6 +330,11 @@ function ThemeEditor({
416330 } }
417331 showUseButton = { true }
418332 className = "cursor-pointer"
333+ ref = {
334+ themeItem . id === theme ?. id
335+ ? selectedThemeRef
336+ : null
337+ }
419338 onClick = { ( ) => {
420339 setTheme ( themeItem ) ;
421340 setNavigationStack ( [
@@ -434,29 +353,10 @@ function ThemeEditor({
434353 </ div >
435354 < div className = "space-y-2" >
436355 { isLoading ? (
437- // Skeleton for custom themes
438356 Array ( 2 )
439357 . fill ( 0 )
440358 . map ( ( _ , index ) => (
441- < div
442- key = { index }
443- className = "w-full flex items-center justify-between px-3 py-3 rounded-md"
444- >
445- < div className = "flex items-center gap-3" >
446- < div className = "flex gap-1" >
447- { Array ( 5 )
448- . fill ( 0 )
449- . map ( ( _ , i ) => (
450- < Skeleton
451- key = { i }
452- className = "w-4 h-4 rounded-full"
453- />
454- ) ) }
455- </ div >
456- < Skeleton className = "h-4 w-32" />
457- </ div >
458- < Skeleton className = "h-4 w-4" />
459- </ div >
359+ < ThemeCardSkeleton key = { index } />
460360 ) )
461361 ) : themes . custom . length === 0 ? (
462362 < div className = "text-muted-foreground text-sm" >
@@ -466,7 +366,7 @@ function ThemeEditor({
466366 themes . custom . map ( ( themeItem ) => (
467367 < ThemeCard
468368 key = { themeItem . id }
469- name = { truncate ( themeItem . name , 20 ) }
369+ name = { truncate ( themeItem . name , 30 ) }
470370 palette = { colorOrder
471371 . map (
472372 ( key ) =>
@@ -483,6 +383,11 @@ function ThemeEditor({
483383 } }
484384 showUseButton = { true }
485385 className = "cursor-pointer"
386+ ref = {
387+ themeItem . id === theme ?. id
388+ ? selectedThemeRef
389+ : null
390+ }
486391 onClick = { ( ) => {
487392 setTheme ( themeItem ) ;
488393 setNavigationStack ( [
0 commit comments