11<script setup lang="ts">
22const modelValue = defineModel <Colors >({ required: true })
33
4+ const toast = useToast ()
5+
46const colorFields = [
57 { key: ' accent' , label: ' Accent' },
68 { key: ' accentContrast' , label: ' Accent Contrast' },
@@ -18,6 +20,98 @@ const colorFields = [
1820
1921type ColorKey = (typeof colorFields )[number ][' key' ]
2022
23+ // Convert camelCase to kebab-case
24+ function toKebabCase(str : string ): string {
25+ return str .replace (/ ([a-z ] )([A-Z ] )/ g , ' $1-$2' ).toLowerCase ()
26+ }
27+
28+ // Convert kebab-case to camelCase
29+ function toCamelCase(str : string ): string {
30+ return str .replace (/ -([a-z ] )/ g , (_ , letter ) => letter .toUpperCase ())
31+ }
32+
33+ // Export theme as JSON file
34+ function exportTheme() {
35+ const themeJson = {
36+ light: Object .fromEntries (
37+ Object .entries (modelValue .value .light )
38+ .filter (([key ]) => key !== ' __typename' )
39+ .map (([key , value ]) => [toKebabCase (key ), value ]),
40+ ),
41+ dark: Object .fromEntries (
42+ Object .entries (modelValue .value .dark )
43+ .filter (([key ]) => key !== ' __typename' )
44+ .map (([key , value ]) => [toKebabCase (key ), value ]),
45+ ),
46+ }
47+ console .log (themeJson )
48+
49+ const blob = new Blob ([JSON .stringify (themeJson , null , 2 )], {
50+ type: ' application/json' ,
51+ })
52+ const url = URL .createObjectURL (blob )
53+ const a = document .createElement (' a' )
54+ a .href = url
55+ a .download = ` theme.json `
56+ a .click ()
57+ URL .revokeObjectURL (url )
58+ }
59+
60+ // Import theme from JSON file
61+ function importTheme() {
62+ const input = document .createElement (' input' )
63+ input .type = ' file'
64+ input .accept = ' .json'
65+ input .onchange = async (e ) => {
66+ const file = (e .target as HTMLInputElement ).files ?.[0 ]
67+ if (! file ) return
68+
69+ try {
70+ const text = await file .text ()
71+ const json = JSON .parse (text )
72+
73+ if (! json .light || ! json .dark ) {
74+ throw new Error (' Invalid theme format: missing light or dark keys' )
75+ }
76+
77+ const convertColorSet = (
78+ colorSet : Record <string , string >,
79+ ): Record <string , string > => {
80+ return Object .fromEntries (
81+ Object .entries (colorSet ).map (([key , value ]) => [
82+ toCamelCase (key ),
83+ value ,
84+ ]),
85+ )
86+ }
87+
88+ modelValue .value = {
89+ ... modelValue .value ,
90+ light: {
91+ ... modelValue .value .light ,
92+ ... convertColorSet (json .light ),
93+ },
94+ dark: {
95+ ... modelValue .value .dark ,
96+ ... convertColorSet (json .dark ),
97+ },
98+ }
99+
100+ toast .add ({
101+ title: ' Theme imported' ,
102+ color: ' success' ,
103+ })
104+ } catch (err ) {
105+ toast .add ({
106+ title: ' Failed to import theme' ,
107+ description: err instanceof Error ? err .message : ' Invalid JSON file' ,
108+ color: ' error' ,
109+ })
110+ }
111+ }
112+ input .click ()
113+ }
114+
21115const lightStyles = computed (() => {
22116 return {
23117 ' --color-accent' : modelValue .value .light .accent ,
@@ -78,10 +172,9 @@ function updateDarkColor(key: ColorKey, value: string) {
78172 <UButton variant =" soft" block >Open theme editor</UButton >
79173
80174 <template #body >
81- <div class =" flex gap-8 " >
82- <div class =" grid flex-1 grid-cols-2 gap-6 " >
175+ <div class =" flex flex-col gap-6 " >
176+ <div class =" mx-auto flex gap-8 " >
83177 <div >
84- <h3 class =" mb-4 text-lg font-semibold" >Light Mode</h3 >
85178 <div class =" space-y-3" >
86179 <UFormField
87180 v-for =" field in colorFields"
@@ -95,8 +188,17 @@ function updateDarkColor(key: ColorKey, value: string) {
95188 </UFormField >
96189 </div >
97190 </div >
191+ <div class =" flex shrink-0 gap-4" >
192+ <div class =" text-center" >
193+ <p class =" text-muted mb-2 text-sm" >Light</p >
194+ <AdminProjectThemePreview :style =" lightStyles" />
195+ </div >
196+ <div class =" text-center" >
197+ <p class =" text-muted mb-2 text-sm" >Dark</p >
198+ <AdminProjectThemePreview :style =" darkStyles" />
199+ </div >
200+ </div >
98201 <div >
99- <h3 class =" mb-4 text-lg font-semibold" >Dark Mode</h3 >
100202 <div class =" space-y-3" >
101203 <UFormField
102204 v-for =" field in colorFields"
@@ -111,15 +213,13 @@ function updateDarkColor(key: ColorKey, value: string) {
111213 </div >
112214 </div >
113215 </div >
114- <div class =" flex shrink-0 gap-4" >
115- <div class =" text-center" >
116- <p class =" text-muted mb-2 text-sm" >Light</p >
117- <AdminProjectThemePreview :style =" lightStyles" />
118- </div >
119- <div class =" text-center" >
120- <p class =" text-muted mb-2 text-sm" >Dark</p >
121- <AdminProjectThemePreview :style =" darkStyles" />
122- </div >
216+ <div class =" flex justify-center gap-2" >
217+ <UButton variant =" soft" icon =" i-lucide-upload" @click =" importTheme" >
218+ Import
219+ </UButton >
220+ <UButton variant =" soft" icon =" i-lucide-download" @click =" exportTheme" >
221+ Export
222+ </UButton >
123223 </div >
124224 </div >
125225 </template >
0 commit comments