@@ -9,11 +9,16 @@ import {
99 SquareMousePointer ,
1010 Type ,
1111 ChevronLeft ,
12+ ChevronDown ,
1213} from "lucide-react" ;
1314import TypographySelector from "./typography-selector" ;
1415import { toast } from "@/hooks/use-toast" ;
1516import { AddressContext } from "@components/contexts" ;
1617import { TOAST_TITLE_ERROR } from "@ui-config/strings" ;
18+ import {
19+ InteractiveSelector ,
20+ interactiveDisplayNames ,
21+ } from "./interactive-selector" ;
1722
1823interface ThemeEditorProps {
1924 draftTheme : Theme ;
@@ -58,26 +63,62 @@ const sections: Section[] = [
5863] ;
5964
6065const typographyDisplayNames : Record < string , string > = {
61- preheader : "Preheader" ,
66+ // Headers
6267 header1 : "Header 1" ,
6368 header2 : "Header 2" ,
6469 header3 : "Header 3" ,
6570 header4 : "Header 4" ,
71+ preheader : "Preheader" ,
72+
73+ // Subheaders
6674 subheader1 : "Subheader 1" ,
6775 subheader2 : "Subheader 2" ,
76+
77+ // Body Text
6878 text1 : "Text 1" ,
6979 text2 : "Text 2" ,
80+ caption : "Caption" ,
81+
82+ // Interactive Elements
7083 link : "Link" ,
7184 button : "Button Text" ,
7285 input : "Input Text" ,
73- caption : "Caption" ,
86+ } as const ;
87+
88+ // Add a type for the categories
89+ type TypographyCategory = {
90+ name : string ;
91+ items : string [ ] ;
7492} ;
7593
94+ // Define the categories
95+ const typographyCategories : TypographyCategory [ ] = [
96+ {
97+ name : "Headers" ,
98+ items : [ "header1" , "header2" , "header3" , "header4" , "preheader" ] ,
99+ } ,
100+ {
101+ name : "Subheaders" ,
102+ items : [ "subheader1" , "subheader2" ] ,
103+ } ,
104+ {
105+ name : "Body Text" ,
106+ items : [ "text1" , "text2" , "caption" ] ,
107+ } ,
108+ {
109+ name : "Interactive Elements" ,
110+ items : [ "link" , "button" , "input" ] ,
111+ } ,
112+ ] ;
113+
76114function ThemeEditor ( { draftTheme, onClose, onSave } : ThemeEditorProps ) {
77115 const [ theme , setTheme ] = useState < Theme > ( draftTheme ) ;
78116 const [ navigationStack , setNavigationStack ] = useState < NavigationItem [ ] > (
79117 [ ] ,
80118 ) ;
119+ const [ collapsedCategories , setCollapsedCategories ] = useState <
120+ Record < string , boolean >
121+ > ( { } ) ;
81122 const address = useContext ( AddressContext ) ;
82123
83124 React . useEffect ( ( ) => {
@@ -92,6 +133,13 @@ function ThemeEditor({ draftTheme, onClose, onSave }: ThemeEditorProps) {
92133 setNavigationStack ( ( prev ) => prev . slice ( 0 , - 1 ) ) ;
93134 } ;
94135
136+ const toggleCategory = ( categoryName : string ) => {
137+ setCollapsedCategories ( ( prev ) => ( {
138+ ...prev ,
139+ [ categoryName ] : ! prev [ categoryName ] ,
140+ } ) ) ;
141+ } ;
142+
95143 const updateThemeCategory = async (
96144 category : "colors" | "typography" | "interactives" | "structure" ,
97145 categoryData : Record < string , string > ,
@@ -129,6 +177,7 @@ function ThemeEditor({ draftTheme, onClose, onSave }: ThemeEditorProps) {
129177
130178 const getCurrentView = ( ) => {
131179 const currentItem = navigationStack [ navigationStack . length - 1 ] ;
180+ const parentItem = navigationStack [ navigationStack . length - 2 ] ;
132181
133182 if ( ! currentItem ) {
134183 // Root view - Main sections
@@ -161,7 +210,10 @@ function ThemeEditor({ draftTheme, onClose, onSave }: ThemeEditorProps) {
161210 }
162211
163212 // Check if current item is a typography item
164- if ( currentItem . id in theme . typography ) {
213+ if (
214+ parentItem ?. id === "typography" &&
215+ currentItem . id in theme . typography
216+ ) {
165217 return (
166218 < TypographySelector
167219 title = {
@@ -190,63 +242,173 @@ function ThemeEditor({ draftTheme, onClose, onSave }: ThemeEditorProps) {
190242 ) ;
191243 }
192244
245+ // Check if current item is an interactive item
246+ if (
247+ parentItem ?. id === "interactives" &&
248+ currentItem . id in theme . interactives
249+ ) {
250+ return (
251+ < InteractiveSelector
252+ title = {
253+ interactiveDisplayNames [ currentItem . id ] ||
254+ capitalize ( currentItem . id )
255+ }
256+ type = {
257+ currentItem . id as "button" | "link" | "card" | "input"
258+ }
259+ value = { theme . interactives [ currentItem . id ] }
260+ onChange = { async ( value ) => {
261+ const updatedInteractives = {
262+ ...theme . interactives ,
263+ [ currentItem . id ] : value ,
264+ } ;
265+ setTheme ( {
266+ ...theme ,
267+ interactives : updatedInteractives ,
268+ } ) ;
269+ await updateThemeCategory (
270+ "interactives" ,
271+ updatedInteractives as unknown as Record <
272+ string ,
273+ string
274+ > ,
275+ ) ;
276+ } }
277+ />
278+ ) ;
279+ }
280+
193281 switch ( currentItem . id ) {
194282 case "colors" :
195283 return (
196284 < div className = "space-y-2 p-2" >
197- { Object . keys ( theme . colors ) . map ( ( color ) => (
198- < ColorSelector
199- key = { color }
200- title = { capitalize ( color ) }
201- value = { theme . colors [ color ] }
202- onChange = { async ( value ) => {
203- const updatedColors = {
204- ...theme . colors ,
205- [ color ] : value ,
206- } ;
207- setTheme ( {
208- ...theme ,
209- colors : updatedColors ,
210- } ) ;
211- await updateThemeCategory (
212- "colors" ,
213- updatedColors as unknown as Record <
214- string ,
215- string
216- > ,
217- ) ;
218- } }
219- allowReset = { false }
220- />
221- ) ) }
285+ { Object . keys ( theme . colors )
286+ . filter (
287+ ( color ) =>
288+ ! [
289+ "success" ,
290+ "warning" ,
291+ "error" ,
292+ "info" ,
293+ ] . includes ( color ) ,
294+ )
295+ . map ( ( color ) => (
296+ < ColorSelector
297+ key = { color }
298+ title = { capitalize ( color ) }
299+ value = { theme . colors [ color ] }
300+ onChange = { async ( value ) => {
301+ const updatedColors = {
302+ ...theme . colors ,
303+ [ color ] : value ,
304+ } ;
305+ setTheme ( {
306+ ...theme ,
307+ colors : updatedColors ,
308+ } ) ;
309+ await updateThemeCategory (
310+ "colors" ,
311+ updatedColors as unknown as Record <
312+ string ,
313+ string
314+ > ,
315+ ) ;
316+ } }
317+ allowReset = { false }
318+ />
319+ ) ) }
222320 </ div >
223321 ) ;
224322 case "typography" :
225323 return (
226324 < div className = "space-y-1 p-2" >
227- { Object . keys ( theme . typography ) . map ( ( typography ) => (
228- < button
229- key = { typography }
230- onClick = { ( ) =>
231- navigateTo ( {
232- id : typography ,
233- label :
234- typographyDisplayNames [
235- typography
236- ] || capitalize ( typography ) ,
237- } )
238- }
239- className = "w-full flex items-center justify-between px-3 py-2 text-sm rounded-md hover:bg-muted transition-colors group"
240- >
241- < span className = "group-hover:text-foreground transition-colors" >
242- { typographyDisplayNames [ typography ] ||
243- capitalize ( typography ) }
244- </ span >
245- < ExpandMoreRight className = "h-4 w-4 text-muted-foreground group-hover:text-foreground transition-colors" />
246- </ button >
325+ { typographyCategories . map ( ( category ) => (
326+ < div key = { category . name } className = "mb-2" >
327+ < button
328+ type = "button"
329+ className = "flex items-center w-full px-2 py-1.5 text-sm hover:bg-muted rounded transition-colors"
330+ onClick = { ( ) =>
331+ toggleCategory ( category . name )
332+ }
333+ >
334+ < ChevronDown
335+ className = { `h-4 w-4 mr-2 transition-transform ${ collapsedCategories [ category . name ] ? "rotate-[-90deg]" : "rotate-0" } ` }
336+ />
337+ { category . name }
338+ </ button >
339+ { ! collapsedCategories [ category . name ] && (
340+ < div >
341+ { category . items . map (
342+ ( typography , idx ) => (
343+ < button
344+ key = { typography }
345+ onClick = { ( ) =>
346+ navigateTo ( {
347+ id : typography ,
348+ label :
349+ typographyDisplayNames [
350+ typography
351+ ] ||
352+ capitalize (
353+ typography ,
354+ ) ,
355+ } )
356+ }
357+ className = {
358+ `w-full flex items-center justify-between px-3 py-2 text-xs rounded-md hover:bg-muted transition-colors group` +
359+ ( idx ===
360+ category . items . length -
361+ 1
362+ ? " mb-4"
363+ : "" )
364+ }
365+ >
366+ < span className = "group-hover:text-foreground transition-colors" >
367+ { typographyDisplayNames [
368+ typography
369+ ] ||
370+ capitalize (
371+ typography ,
372+ ) }
373+ </ span >
374+ < ExpandMoreRight className = "h-4 w-4 text-muted-foreground group-hover:text-foreground transition-colors" />
375+ </ button >
376+ ) ,
377+ ) }
378+ </ div >
379+ ) }
380+ </ div >
247381 ) ) }
248382 </ div >
249383 ) ;
384+ case "interactives" :
385+ return (
386+ < div className = "space-y-1 p-2" >
387+ { Object . keys ( interactiveDisplayNames ) . map (
388+ ( interactive ) => (
389+ < button
390+ key = { interactive }
391+ onClick = { ( ) =>
392+ navigateTo ( {
393+ id : interactive ,
394+ label :
395+ interactiveDisplayNames [
396+ interactive
397+ ] || capitalize ( interactive ) ,
398+ } )
399+ }
400+ className = "w-full flex items-center justify-between px-3 py-2 text-xs rounded-md hover:bg-muted transition-colors group"
401+ >
402+ < span className = "group-hover:text-foreground transition-colors" >
403+ { interactiveDisplayNames [ interactive ] ||
404+ capitalize ( interactive ) }
405+ </ span >
406+ < ExpandMoreRight className = "h-4 w-4 text-muted-foreground group-hover:text-foreground transition-colors" />
407+ </ button >
408+ ) ,
409+ ) }
410+ </ div >
411+ ) ;
250412 default :
251413 return (
252414 < p className = "text-xs text-muted-foreground p-2" >
0 commit comments