funuicss 3.7.15 → 3.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/css/fun.css +560 -203
  2. package/demo/theme.tsx +1311 -0
  3. package/index.d.ts +3 -0
  4. package/index.js +7 -1
  5. package/package.json +1 -1
  6. package/ui/button/Button.d.ts +2 -1
  7. package/ui/button/Button.js +3 -3
  8. package/ui/components/ImageScaler.d.ts +6 -0
  9. package/ui/components/ImageScaler.js +17 -0
  10. package/ui/div/Div.d.ts +3 -1
  11. package/ui/div/Div.js +2 -2
  12. package/ui/empty/Empty.d.ts +17 -0
  13. package/ui/empty/Empty.js +66 -0
  14. package/ui/feature/Feature.d.ts +130 -0
  15. package/ui/feature/Feature.js +380 -0
  16. package/ui/flex/Flex.d.ts +2 -1
  17. package/ui/flex/Flex.js +3 -3
  18. package/ui/footer/Footer.d.ts +1 -0
  19. package/ui/footer/Footer.js +6 -1
  20. package/ui/icons/Dynamic.d.ts +12 -0
  21. package/ui/icons/Dynamic.js +163 -0
  22. package/ui/modal/Modal.d.ts +1 -1
  23. package/ui/products/CartModal.d.ts +20 -0
  24. package/ui/products/CartModal.js +85 -0
  25. package/ui/products/ProductCard.d.ts +13 -0
  26. package/ui/products/ProductCard.js +56 -0
  27. package/ui/products/ProductDetail.d.ts +14 -0
  28. package/ui/products/ProductDetail.js +249 -0
  29. package/ui/products/ProductDetailModal.d.ts +17 -0
  30. package/ui/products/ProductDetailModal.js +99 -0
  31. package/ui/products/Products.d.ts +60 -0
  32. package/ui/products/Products.js +312 -0
  33. package/ui/products/Store.d.ts +99 -0
  34. package/ui/products/Store.js +515 -0
  35. package/ui/sidebar/SideBar.d.ts +3 -1
  36. package/ui/sidebar/SideBar.js +50 -11
  37. package/ui/specials/Circle.d.ts +2 -1
  38. package/ui/specials/Circle.js +2 -2
  39. package/ui/table/Table.d.ts +15 -1
  40. package/ui/table/Table.js +143 -15
  41. package/ui/theme/theme.d.ts +91 -0
  42. package/ui/theme/theme.js +468 -25
  43. package/ui/vista/Vista.js +8 -12
  44. package/utils/Buckets.d.ts +0 -0
  45. package/utils/Buckets.js +1 -0
package/demo/theme.tsx ADDED
@@ -0,0 +1,1311 @@
1
+
2
+ // 'use client'
3
+ // import React, {
4
+ // useEffect,
5
+ // createContext,
6
+ // useContext,
7
+ // useState,
8
+ // ReactNode,
9
+ // useMemo,
10
+ // } from 'react'
11
+ // import { colorVarsToDarken, themes } from './themes'
12
+ // import { getDarkenAmount, darkenToRgba } from './darkenUtils'
13
+
14
+ // /* -------------------------------------------------------------------------- */
15
+ // /* TYPES */
16
+ // /* -------------------------------------------------------------------------- */
17
+ // export type ThemeVariant = 'standard' | 'minimal'
18
+
19
+ // export type ThemeName =
20
+ // | 'light'
21
+ // | 'dark'
22
+ // | 'dark-blue'
23
+ // | 'light-gray'
24
+ // | 'pastel-green'
25
+ // | 'warm-orange'
26
+ // | 'frosted-glass'
27
+ // | 'midnight-purple'
28
+ // | 'cyber-metal'
29
+
30
+ // interface ThemeConfig {
31
+ // [key: string]: any
32
+ // }
33
+
34
+ // interface Variable {
35
+ // name: string
36
+ // value: any
37
+ // }
38
+
39
+ // interface ProjectData {
40
+ // theme_config?: {
41
+ // colors?: Record<string, string>
42
+ // typography?: Record<string, string>
43
+ // [key: string]: any
44
+ // }
45
+ // components?: Record<string, any>
46
+ // default_variation?: ThemeVariant
47
+ // variables?: Variable[]
48
+ // assets?: Asset[]
49
+ // name?: string
50
+ // project_id?: string
51
+ // version?: number
52
+ // updated_at?: string
53
+ // trustedDomains?: Array<{
54
+ // domain: string
55
+ // status: string
56
+ // isDefault?: boolean
57
+ // }>
58
+ // }
59
+
60
+ // interface ThemeProviderProps {
61
+ // theme: ThemeName
62
+ // projectId?: string
63
+ // funcss?: string
64
+ // minHeight?: string
65
+ // children: ReactNode
66
+ // project?: ProjectData | null // New prop: directly provide project data
67
+ // }
68
+
69
+ // /* -------------------------------------------------------------------------- */
70
+ // /* THEME CONTEXT */
71
+ // /* -------------------------------------------------------------------------- */
72
+ // interface ThemeContextType {
73
+ // variant: ThemeVariant
74
+ // setVariant: React.Dispatch<React.SetStateAction<ThemeVariant>>
75
+ // themeConfig: ThemeConfig
76
+ // projectData: ProjectData | null
77
+ // isLoading: boolean
78
+ // isInitialLoad: boolean
79
+ // error: string | null
80
+ // }
81
+
82
+ // const ThemeContext = createContext<ThemeContextType>({
83
+ // variant: 'standard',
84
+ // setVariant: () => {},
85
+ // themeConfig: {},
86
+ // projectData: null,
87
+ // isLoading: true,
88
+ // isInitialLoad: true,
89
+ // error: null,
90
+ // })
91
+
92
+ // export const useTheme = (): ThemeContextType => {
93
+ // const context = useContext(ThemeContext)
94
+ // if (!context) {
95
+ // throw new Error('useTheme must be used within ThemeProvider')
96
+ // }
97
+ // return context
98
+ // }
99
+
100
+ // export const useVariant = () => {
101
+ // const { variant, setVariant } = useTheme()
102
+ // return { variant, setVariant }
103
+ // }
104
+
105
+ // /* -------------------------------------------------------------------------- */
106
+ // /* ORIGIN VALIDATION */
107
+ // /* -------------------------------------------------------------------------- */
108
+
109
+ // const getCurrentOrigin = (): string => {
110
+ // if (typeof window === 'undefined') return ''
111
+
112
+ // // For local development, return localhost
113
+ // if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
114
+ // return 'localhost'
115
+ // }
116
+
117
+ // // For production, return the domain without protocol and www
118
+ // let domain = window.location.hostname
119
+ // domain = domain.replace(/^www\./, '')
120
+ // return domain
121
+ // }
122
+
123
+ // const validateOriginAccess = async (projectId: string): Promise<boolean> => {
124
+ // if (!projectId) {
125
+ // console.error('❌ No project ID provided for origin validation')
126
+ // return false
127
+ // }
128
+
129
+ // const currentOrigin = getCurrentOrigin()
130
+ // console.log(`🔍 Validating origin access for: ${currentOrigin}`)
131
+
132
+ // try {
133
+ // // Load project data from CDN to check trusted domains
134
+ // const projectData = await loadThemeFromCDN(projectId)
135
+
136
+ // if (!projectData) {
137
+ // console.error('❌ Project not found or inaccessible')
138
+ // return false
139
+ // }
140
+
141
+ // const trustedDomains = projectData.trustedDomains || []
142
+
143
+ // // Check if current origin is in trusted domains
144
+ // const hasAccess = trustedDomains.some(domain => {
145
+ // const trustedDomain = domain.domain.toLowerCase()
146
+ // const currentDomain = currentOrigin.toLowerCase()
147
+
148
+ // // Exact match or subdomain match
149
+ // return currentDomain === trustedDomain ||
150
+ // currentDomain.endsWith('.' + trustedDomain) ||
151
+ // (trustedDomain === 'localhost' && currentOrigin === 'localhost')
152
+ // })
153
+
154
+ // if (!hasAccess) {
155
+ // console.error(`❌ Access denied: Origin "${currentOrigin}" is not in trusted domains`)
156
+ // console.log('📋 Trusted domains:', trustedDomains.map(d => d.domain))
157
+ // return false
158
+ // }
159
+
160
+ // return true
161
+
162
+ // } catch (error) {
163
+ // console.error('❌ Error during origin validation:', error)
164
+ // return false
165
+ // }
166
+ // }
167
+
168
+ // /* -------------------------------------------------------------------------- */
169
+ // /* LOCAL FILE MANAGEMENT */
170
+ // /* -------------------------------------------------------------------------- */
171
+
172
+ // const loadLocalTheme = async (): Promise<ProjectData | null> => {
173
+ // try {
174
+ // const response = await fetch('/funui.json', {
175
+ // cache: 'no-cache',
176
+ // })
177
+
178
+ // if (response.ok) {
179
+ // const data = await response.json()
180
+ // return data
181
+ // }
182
+ // } catch (error) {
183
+ // console.log('ℹ️ No local theme file found')
184
+ // }
185
+ // return null
186
+ // }
187
+
188
+ // /* -------------------------------------------------------------------------- */
189
+ // /* CDN THEME LOADER */
190
+ // /* -------------------------------------------------------------------------- */
191
+
192
+ // const loadThemeFromCDN = async (projectId: string): Promise<ProjectData | null> => {
193
+ // if (!projectId) {
194
+ // console.error('❌ No project ID provided for CDN loading')
195
+ // return null
196
+ // }
197
+
198
+ // // Try Firebase Storage public URL
199
+ // try {
200
+ // const publicUrl = `https://firebasestorage.googleapis.com/v0/b/funui-4bcd1.firebasestorage.app/o/themes%2F${projectId}.json?alt=media`
201
+
202
+ // const response = await fetch(publicUrl, {
203
+ // cache: 'no-cache',
204
+ // })
205
+
206
+ // if (response.ok) {
207
+ // const data = await response.json()
208
+ // return data
209
+ // } else {
210
+ // console.error('❌ Firebase Storage fetch failed:', response.status, response.statusText)
211
+ // }
212
+ // } catch (error) {
213
+ // console.error('❌ Error loading from Firebase Storage:', error)
214
+ // }
215
+
216
+ // return null
217
+ // }
218
+
219
+ // /* -------------------------------------------------------------------------- */
220
+ // /* CSS VARIABLE APPLIER */
221
+ // /* -------------------------------------------------------------------------- */
222
+
223
+ // const applyTypographyVariables = (typography: Record<string, string>, root: HTMLElement) => {
224
+ // if (!typography) return
225
+
226
+ // Object.entries(typography).forEach(([key, value]) => {
227
+ // const cssVarName = `--${key.replace(/_/g, '-')}`
228
+ // root.style.setProperty(cssVarName, value)
229
+ // })
230
+ // }
231
+
232
+ // const applyColorVariables = (colors: Record<string, string>, root: HTMLElement) => {
233
+ // if (!colors) return
234
+
235
+ // Object.entries(colors).forEach(([key, value]) => {
236
+ // const cssVarName = `--${key.replace(/_/g, '-')}`
237
+ // root.style.setProperty(cssVarName, value)
238
+ // })
239
+ // }
240
+
241
+ // const applyThemeConfig = (themeConfig: Record<string, any>, root: HTMLElement) => {
242
+ // if (!themeConfig) return
243
+
244
+ // if (themeConfig.colors) {
245
+ // applyColorVariables(themeConfig.colors, root)
246
+ // }
247
+
248
+ // if (themeConfig.typography) {
249
+ // applyTypographyVariables(themeConfig.typography, root)
250
+ // }
251
+
252
+ // Object.entries(themeConfig).forEach(([key, value]) => {
253
+ // if (key !== 'colors' && key !== 'typography' && typeof value === 'string') {
254
+ // const cssVarName = `--${key.replace(/_/g, '-')}`
255
+ // root.style.setProperty(cssVarName, value)
256
+ // }
257
+ // })
258
+ // }
259
+
260
+ // /* -------------------------------------------------------------------------- */
261
+ // /* VARIABLES HELPER */
262
+ // /* -------------------------------------------------------------------------- */
263
+
264
+ // let cachedProjectData: ProjectData | null = null
265
+
266
+ // export const getVariable = (name: string): { name: string; value: any } | undefined => {
267
+ // if (!cachedProjectData?.variables) {
268
+ // console.warn('No variables available. Make sure ThemeProvider is mounted.')
269
+ // return undefined
270
+ // }
271
+
272
+ // const variable = cachedProjectData.variables.find(v => v.name === name)
273
+ // return variable
274
+ // }
275
+
276
+ // export const getAllVariables = (): Variable[] => {
277
+ // return cachedProjectData?.variables || []
278
+ // }
279
+
280
+ // /* -------------------------------------------------------------------------- */
281
+ // /* COMPONENT */
282
+ // /* -------------------------------------------------------------------------- */
283
+ // const ThemeProvider: React.FC<ThemeProviderProps> = ({
284
+ // theme,
285
+ // children,
286
+ // funcss = '',
287
+ // minHeight = '100vh',
288
+ // projectId,
289
+ // project: providedProject, // New prop
290
+ // }) => {
291
+ // const [variant, setVariant] = useState<ThemeVariant>('standard')
292
+ // const [themeConfig, setThemeConfig] = useState<ThemeConfig>({})
293
+ // const [projectData, setProjectData] = useState<ProjectData | null>(null)
294
+ // const [isLoading, setIsLoading] = useState(true)
295
+ // const [isInitialLoad, setIsInitialLoad] = useState(true)
296
+ // const [error, setError] = useState<string | null>(null)
297
+ // const [currentVersion, setCurrentVersion] = useState<number | null>(null)
298
+
299
+ // /* -------------------------- Apply base theme --------------------------- */
300
+ // useEffect(() => {
301
+ // const root = document.documentElement
302
+ // const selectedTheme = themes[theme] || themes.light
303
+
304
+ // Object.entries(selectedTheme).forEach(([key, value]) => {
305
+ // root.style.setProperty(key, value)
306
+ // })
307
+
308
+ // if (
309
+ // ['dark', 'dark-blue', 'midnight-purple', 'cyber-metal'].includes(theme)
310
+ // ) {
311
+ // colorVarsToDarken.forEach((varName) => {
312
+ // const original = getComputedStyle(root)
313
+ // .getPropertyValue(varName)
314
+ // .trim()
315
+ // if (original) {
316
+ // const darkAmount = getDarkenAmount(varName)
317
+ // const rgba = darkenToRgba(original, darkAmount, 0.9)
318
+ // root.style.setProperty(varName, rgba)
319
+ // }
320
+ // })
321
+ // }
322
+ // }, [theme])
323
+
324
+ // /* ---------------------- Theme Loading Logic ----------------------- */
325
+ // useEffect(() => {
326
+ // if (typeof window === 'undefined') {
327
+ // setIsLoading(false)
328
+ // setIsInitialLoad(false)
329
+ // return
330
+ // }
331
+
332
+ // const root = document.documentElement
333
+ // let pollTimer: NodeJS.Timeout
334
+
335
+ // const loadTheme = async () => {
336
+ // try {
337
+ // let finalTheme: ProjectData | null = null
338
+ // let finalVersion: number | null = null
339
+
340
+ // // If project data is provided directly, use it and skip all loading
341
+ // if (providedProject) {
342
+ // console.log('✅ Using provided project data directly')
343
+ // finalTheme = providedProject
344
+ // finalVersion = providedProject.version || 0
345
+ // } else {
346
+ // // Otherwise, follow the normal loading flow
347
+ // // First, try to load local theme
348
+ // const localTheme = await loadLocalTheme()
349
+ // const localVersion = localTheme?.version || 0
350
+
351
+ // if (projectId) {
352
+ // // Validate origin access for CDN
353
+ // const hasAccess = await validateOriginAccess(projectId)
354
+
355
+ // if (hasAccess) {
356
+ // // Try to load from CDN
357
+ // const cdnTheme = await loadThemeFromCDN(projectId)
358
+ // const cdnVersion = cdnTheme?.version || 0
359
+
360
+ // if (cdnTheme) {
361
+ // // CDN theme available - use it
362
+ // finalTheme = cdnTheme
363
+ // finalVersion = cdnVersion
364
+
365
+ // if (cdnVersion !== localVersion) {
366
+ // console.log(`🔄 Version mismatch: Local(${localVersion}) vs CDN(${cdnVersion})`)
367
+ // console.log('ℹ️ Using CDN version. Please update your local funui.json file manually.')
368
+ // }
369
+
370
+ // } else if (localTheme) {
371
+ // // CDN not available but we have local theme
372
+ // console.log('⚠️ CDN unavailable, using local theme')
373
+ // finalTheme = localTheme
374
+ // finalVersion = localVersion
375
+ // } else {
376
+ // // No theme available anywhere
377
+ // console.warn('⚠️ No theme found (CDN unavailable and no local theme)')
378
+ // setError('Theme not found')
379
+ // }
380
+ // } else {
381
+ // // Origin validation failed
382
+ // if (localTheme) {
383
+ // console.log('⚠️ Origin validation failed, using local theme')
384
+ // finalTheme = localTheme
385
+ // finalVersion = localVersion
386
+ // } else {
387
+ // console.error('❌ Origin validation failed and no local theme available')
388
+ // setError('Access denied and no local theme available')
389
+ // }
390
+ // }
391
+ // } else {
392
+ // // No project ID provided - only use local theme
393
+ // console.log('ℹ️ No project ID provided, using local theme only')
394
+ // if (localTheme) {
395
+ // finalTheme = localTheme
396
+ // finalVersion = localVersion
397
+ // console.log('✅ Theme loaded from local file')
398
+ // } else {
399
+ // console.log('ℹ️ No local theme file found - using base theme only')
400
+ // // No error here - it's valid to use only base theme
401
+ // }
402
+ // }
403
+ // }
404
+
405
+ // // Apply the theme if we have one
406
+ // if (finalTheme && (!currentVersion || finalVersion !== currentVersion)) {
407
+ // applyThemeData(finalTheme, root)
408
+ // setCurrentVersion(finalVersion)
409
+ // setError(null)
410
+ // } else if (finalTheme) {
411
+ // console.log('✓ Theme up to date')
412
+ // }
413
+
414
+ // } catch (err) {
415
+ // console.error('❌ Error loading theme:', err)
416
+ // setError('Failed to load theme')
417
+ // } finally {
418
+ // setIsLoading(false)
419
+ // setIsInitialLoad(false)
420
+ // }
421
+ // }
422
+
423
+ // // Initial load
424
+ // loadTheme()
425
+
426
+ // // Only poll for updates if we have a project ID AND no provided project
427
+ // if (projectId && !providedProject) {
428
+ // pollTimer = setInterval(() => {
429
+ // loadTheme()
430
+ // }, 5 * 60 * 1000)
431
+ // }
432
+
433
+ // return () => {
434
+ // if (pollTimer) {
435
+ // clearInterval(pollTimer)
436
+ // }
437
+ // }
438
+ // }, [projectId, currentVersion, theme, providedProject]) // Added providedProject to dependencies
439
+
440
+ // const applyThemeData = (data: ProjectData, root: HTMLElement) => {
441
+ // const themeConfig = data.theme_config ?? {}
442
+ // const newVariant = data.default_variation || 'standard'
443
+
444
+ // setVariant(newVariant)
445
+ // setThemeConfig(themeConfig)
446
+ // setProjectData(data)
447
+
448
+ // // Cache for variable access
449
+ // cachedProjectData = data
450
+
451
+ // // Cache for asset access
452
+ // cachedAssets = data.assets || []
453
+
454
+ // // Apply all theme config to CSS variables
455
+ // applyThemeConfig(themeConfig, root)
456
+ // }
457
+
458
+ // const contextValue = useMemo(
459
+ // () => ({
460
+ // variant,
461
+ // setVariant,
462
+ // themeConfig,
463
+ // projectData,
464
+ // isLoading,
465
+ // isInitialLoad,
466
+ // error,
467
+ // }),
468
+ // [variant, themeConfig, projectData, isLoading, isInitialLoad, error]
469
+ // )
470
+
471
+ // return (
472
+ // <ThemeContext.Provider value={contextValue}>
473
+ // <div
474
+ // className={`theme-${theme} ${funcss}`}
475
+ // style={{
476
+ // backgroundColor: 'var(--page-bg)',
477
+ // color: 'var(--text-color)',
478
+ // minHeight: minHeight,
479
+ // transition: isInitialLoad ? 'none' : 'background-color 0.3s ease, color 0.3s ease',
480
+ // }}
481
+ // >
482
+ // {children}
483
+ // </div>
484
+ // </ThemeContext.Provider>
485
+ // )
486
+ // }
487
+
488
+ // export default ThemeProvider
489
+
490
+ // /* -------------------------------------------------------------------------- */
491
+ // /* HELPER HOOKS */
492
+ // /* -------------------------------------------------------------------------- */
493
+
494
+ // export const useThemeValue = (key: string): string | undefined => {
495
+ // const { themeConfig } = useTheme()
496
+ // return themeConfig[key]
497
+ // }
498
+
499
+ // export const useComponentConfig = (componentName: string): any => {
500
+ // const { projectData } = useTheme()
501
+ // return projectData?.components?.[componentName] || {}
502
+ // }
503
+
504
+ // export const useColors = (): Record<string, string> => {
505
+ // const { projectData } = useTheme()
506
+ // return projectData?.theme_config?.colors || {}
507
+ // }
508
+
509
+ // export const useTypography = (): Record<string, string> => {
510
+ // const { projectData } = useTheme()
511
+ // return projectData?.theme_config?.typography || {}
512
+ // }
513
+
514
+ // export const useThemeConfig = (): Record<string, any> => {
515
+ // const { projectData } = useTheme()
516
+ // return projectData?.theme_config || {}
517
+ // }
518
+
519
+ // export const useProjectData = (): ProjectData | null => {
520
+ // const { projectData } = useTheme()
521
+ // return projectData
522
+ // }
523
+
524
+ // export const useColor = (colorName: string): string | undefined => {
525
+ // const colors = useColors()
526
+ // return colors[colorName]
527
+ // }
528
+
529
+ // export const useTypographyValue = (property: string): string | undefined => {
530
+ // const typography = useTypography()
531
+ // return typography[property]
532
+ // }
533
+
534
+ // export const useComponentVariant = (componentName: string, variantName: string = 'default'): any => {
535
+ // const componentConfig = useComponentConfig(componentName)
536
+ // return componentConfig[variantName] || {}
537
+ // }
538
+
539
+ // // Hook to access variables
540
+ // export const useVariables = (): Variable[] => {
541
+ // const { projectData } = useTheme()
542
+ // return projectData?.variables || []
543
+ // }
544
+
545
+ // // Hook to get a specific variable
546
+ // export const useVariable = (name: string): any => {
547
+ // const variables = useVariables()
548
+ // const variable = variables.find(v => v.name === name)
549
+ // return variable?.value
550
+ // }
551
+
552
+ // /* -------------------------------------------------------------------------- */
553
+ // /* ASSETS HELPER */
554
+ // /* -------------------------------------------------------------------------- */
555
+
556
+ // interface Asset {
557
+ // name: string
558
+ // url: string
559
+ // file_type: string
560
+ // }
561
+
562
+ // let cachedAssets: Asset[] = []
563
+
564
+ // export const getAsset = (name: string): Asset | undefined => {
565
+ // if (!cachedAssets.length) {
566
+ // console.warn('No assets available. Make sure ThemeProvider is mounted and assets are loaded.')
567
+ // return undefined
568
+ // }
569
+
570
+ // const asset = cachedAssets.find(a => a.name === name)
571
+ // return asset
572
+ // }
573
+
574
+ // export const getAllAssets = (): Asset[] => {
575
+ // return cachedAssets
576
+ // }
577
+
578
+ // export const getAssetValue = (name: string): string | undefined => {
579
+ // const asset = getAsset(name)
580
+ // return asset?.url
581
+ // }
582
+
583
+ // export const getAssetType = (name: string): string | undefined => {
584
+ // const asset = getAsset(name)
585
+ // return asset?.file_type
586
+ // }
587
+
588
+ // export const getAssetInfo = (name: string): { value: string; type: string; name: string } | undefined => {
589
+ // const asset = getAsset(name)
590
+ // if (!asset) return undefined
591
+
592
+ // return {
593
+ // value: asset.url,
594
+ // type: asset.file_type,
595
+ // name: asset.name
596
+ // }
597
+ // }
598
+
599
+ // // Hook to access all assets
600
+ // export const useAssets = (): Asset[] => {
601
+ // const { projectData } = useTheme()
602
+ // return projectData?.assets || []
603
+ // }
604
+
605
+ // // Hook to get a specific asset
606
+ // export const useAsset = (name: string): Asset | undefined => {
607
+ // const assets = useAssets()
608
+ // const asset = assets.find(a => a.name === name)
609
+ // return asset
610
+ // }
611
+
612
+ // // Hook to get asset URL (most common use case)
613
+ // export const useAssetValue = (name: string): string | undefined => {
614
+ // const asset = useAsset(name)
615
+ // return asset?.url
616
+ // }
617
+
618
+ // // Hook to get asset type
619
+ // export const useAssetType = (name: string): string | undefined => {
620
+ // const asset = useAsset(name)
621
+ // return asset?.file_type
622
+ // }
623
+
624
+ // // Hook to get complete asset info
625
+ // export const useAssetInfo = (name: string): { value: string; type: string; name: string } | undefined => {
626
+ // const asset = useAsset(name)
627
+ // if (!asset) return undefined
628
+
629
+ // return {
630
+ // value: asset.url,
631
+ // type: asset.file_type,
632
+ // name: asset.name
633
+ // }
634
+ // }
635
+
636
+ // // Hook to filter assets by type
637
+ // export const useAssetsByType = (type: string): Asset[] => {
638
+ // const assets = useAssets()
639
+ // return assets.filter(asset => asset.file_type === type)
640
+ // }
641
+
642
+ // // Hook to get image assets
643
+ // export const useImageAssets = (): Asset[] => {
644
+ // return useAssetsByType('image')
645
+ // }
646
+
647
+ // // Hook to get video assets
648
+ // export const useVideoAssets = (): Asset[] => {
649
+ // return useAssetsByType('video')
650
+ // }
651
+
652
+ // // Hook to get audio assets
653
+ // export const useAudioAssets = (): Asset[] => {
654
+ // return useAssetsByType('audio')
655
+ // }
656
+
657
+ // // Hook to get document assets
658
+ // export const useDocumentAssets = (): Asset[] => {
659
+ // return useAssetsByType('document')
660
+ // }
661
+
662
+ // // 'use client'
663
+ // // import React, {
664
+ // // useEffect,
665
+ // // createContext,
666
+ // // useContext,
667
+ // // useState,
668
+ // // ReactNode,
669
+ // // useMemo,
670
+ // // } from 'react'
671
+ // // import { colorVarsToDarken, themes } from './themes'
672
+ // // import { getDarkenAmount, darkenToRgba } from './darkenUtils'
673
+
674
+ // // /* -------------------------------------------------------------------------- */
675
+ // // /* TYPES */
676
+ // // /* -------------------------------------------------------------------------- */
677
+ // // export type ThemeVariant = 'standard' | 'minimal'
678
+
679
+ // // export type ThemeName =
680
+ // // | 'light'
681
+ // // | 'dark'
682
+ // // | 'dark-blue'
683
+ // // | 'light-gray'
684
+ // // | 'pastel-green'
685
+ // // | 'warm-orange'
686
+ // // | 'frosted-glass'
687
+ // // | 'midnight-purple'
688
+ // // | 'cyber-metal'
689
+
690
+ // // interface ThemeConfig {
691
+ // // [key: string]: any
692
+ // // }
693
+
694
+ // // interface Variable {
695
+ // // name: string
696
+ // // value: any
697
+ // // }
698
+
699
+ // // interface ProjectData {
700
+ // // theme_config?: {
701
+ // // colors?: Record<string, string>
702
+ // // typography?: Record<string, string>
703
+ // // [key: string]: any
704
+ // // }
705
+ // // components?: Record<string, any>
706
+ // // default_variation?: ThemeVariant
707
+ // // variables?: Variable[]
708
+ // // assets?: Asset[]
709
+ // // name?: string
710
+ // // project_id?: string
711
+ // // version?: number
712
+ // // updated_at?: string
713
+ // // trustedDomains?: Array<{
714
+ // // domain: string
715
+ // // status: string
716
+ // // isDefault?: boolean
717
+ // // }>
718
+ // // }
719
+
720
+ // // interface ThemeProviderProps {
721
+ // // theme: ThemeName
722
+ // // projectId?: string
723
+ // // funcss?: string
724
+ // // minHeight?: string
725
+ // // children: ReactNode
726
+ // // }
727
+
728
+ // // /* -------------------------------------------------------------------------- */
729
+ // // /* THEME CONTEXT */
730
+ // // /* -------------------------------------------------------------------------- */
731
+ // // interface ThemeContextType {
732
+ // // variant: ThemeVariant
733
+ // // setVariant: React.Dispatch<React.SetStateAction<ThemeVariant>>
734
+ // // themeConfig: ThemeConfig
735
+ // // projectData: ProjectData | null
736
+ // // isLoading: boolean
737
+ // // isInitialLoad: boolean
738
+ // // error: string | null
739
+ // // }
740
+
741
+ // // const ThemeContext = createContext<ThemeContextType>({
742
+ // // variant: 'standard',
743
+ // // setVariant: () => {},
744
+ // // themeConfig: {},
745
+ // // projectData: null,
746
+ // // isLoading: true,
747
+ // // isInitialLoad: true,
748
+ // // error: null,
749
+ // // })
750
+
751
+ // // export const useTheme = (): ThemeContextType => {
752
+ // // const context = useContext(ThemeContext)
753
+ // // if (!context) {
754
+ // // throw new Error('useTheme must be used within ThemeProvider')
755
+ // // }
756
+ // // return context
757
+ // // }
758
+
759
+ // // export const useVariant = () => {
760
+ // // const { variant, setVariant } = useTheme()
761
+ // // return { variant, setVariant }
762
+ // // }
763
+
764
+ // // /* -------------------------------------------------------------------------- */
765
+ // // /* ORIGIN VALIDATION */
766
+ // // /* -------------------------------------------------------------------------- */
767
+
768
+ // // const getCurrentOrigin = (): string => {
769
+ // // if (typeof window === 'undefined') return ''
770
+
771
+ // // // For local development, return localhost
772
+ // // if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
773
+ // // return 'localhost'
774
+ // // }
775
+
776
+ // // // For production, return the domain without protocol and www
777
+ // // let domain = window.location.hostname
778
+ // // domain = domain.replace(/^www\./, '')
779
+ // // return domain
780
+ // // }
781
+
782
+ // // const validateOriginAccess = async (projectId: string): Promise<boolean> => {
783
+ // // if (!projectId) {
784
+ // // console.error('❌ No project ID provided for origin validation')
785
+ // // return false
786
+ // // }
787
+
788
+ // // const currentOrigin = getCurrentOrigin()
789
+ // // console.log(`🔍 Validating origin access for: ${currentOrigin}`)
790
+
791
+ // // try {
792
+ // // // Load project data from CDN to check trusted domains
793
+ // // const projectData = await loadThemeFromCDN(projectId)
794
+
795
+ // // if (!projectData) {
796
+ // // console.error('❌ Project not found or inaccessible')
797
+ // // return false
798
+ // // }
799
+
800
+ // // const trustedDomains = projectData.trustedDomains || []
801
+
802
+ // // // Check if current origin is in trusted domains
803
+ // // const hasAccess = trustedDomains.some(domain => {
804
+ // // const trustedDomain = domain.domain.toLowerCase()
805
+ // // const currentDomain = currentOrigin.toLowerCase()
806
+
807
+ // // // Exact match or subdomain match
808
+ // // return currentDomain === trustedDomain ||
809
+ // // currentDomain.endsWith('.' + trustedDomain) ||
810
+ // // (trustedDomain === 'localhost' && currentOrigin === 'localhost')
811
+ // // })
812
+
813
+ // // if (!hasAccess) {
814
+ // // console.error(`❌ Access denied: Origin "${currentOrigin}" is not in trusted domains`)
815
+ // // console.log('📋 Trusted domains:', trustedDomains.map(d => d.domain))
816
+ // // return false
817
+ // // }
818
+
819
+ // // return true
820
+
821
+ // // } catch (error) {
822
+ // // console.error('❌ Error during origin validation:', error)
823
+ // // return false
824
+ // // }
825
+ // // }
826
+
827
+ // // /* -------------------------------------------------------------------------- */
828
+ // // /* LOCAL FILE MANAGEMENT */
829
+ // // /* -------------------------------------------------------------------------- */
830
+
831
+ // // const loadLocalTheme = async (): Promise<ProjectData | null> => {
832
+ // // try {
833
+ // // const response = await fetch('/funui.json', {
834
+ // // cache: 'no-cache',
835
+ // // })
836
+
837
+ // // if (response.ok) {
838
+ // // const data = await response.json()
839
+ // // return data
840
+ // // }
841
+ // // } catch (error) {
842
+ // // console.log('ℹ️ No local theme file found')
843
+ // // }
844
+ // // return null
845
+ // // }
846
+
847
+ // // /* -------------------------------------------------------------------------- */
848
+ // // /* CDN THEME LOADER */
849
+ // // /* -------------------------------------------------------------------------- */
850
+
851
+ // // const loadThemeFromCDN = async (projectId: string): Promise<ProjectData | null> => {
852
+ // // if (!projectId) {
853
+ // // console.error('❌ No project ID provided for CDN loading')
854
+ // // return null
855
+ // // }
856
+
857
+ // // // Try Firebase Storage public URL
858
+ // // try {
859
+ // // const publicUrl = `https://firebasestorage.googleapis.com/v0/b/funui-4bcd1.firebasestorage.app/o/themes%2F${projectId}.json?alt=media`
860
+
861
+ // // const response = await fetch(publicUrl, {
862
+ // // cache: 'no-cache',
863
+ // // })
864
+
865
+ // // if (response.ok) {
866
+ // // const data = await response.json()
867
+ // // return data
868
+ // // } else {
869
+ // // console.error('❌ Firebase Storage fetch failed:', response.status, response.statusText)
870
+ // // }
871
+ // // } catch (error) {
872
+ // // console.error('❌ Error loading from Firebase Storage:', error)
873
+ // // }
874
+
875
+ // // return null
876
+ // // }
877
+
878
+ // // /* -------------------------------------------------------------------------- */
879
+ // // /* CSS VARIABLE APPLIER */
880
+ // // /* -------------------------------------------------------------------------- */
881
+
882
+ // // const applyTypographyVariables = (typography: Record<string, string>, root: HTMLElement) => {
883
+ // // if (!typography) return
884
+
885
+ // // Object.entries(typography).forEach(([key, value]) => {
886
+ // // const cssVarName = `--${key.replace(/_/g, '-')}`
887
+ // // root.style.setProperty(cssVarName, value)
888
+ // // })
889
+ // // }
890
+
891
+ // // const applyColorVariables = (colors: Record<string, string>, root: HTMLElement) => {
892
+ // // if (!colors) return
893
+
894
+ // // Object.entries(colors).forEach(([key, value]) => {
895
+ // // const cssVarName = `--${key.replace(/_/g, '-')}`
896
+ // // root.style.setProperty(cssVarName, value)
897
+ // // })
898
+ // // }
899
+
900
+ // // const applyThemeConfig = (themeConfig: Record<string, any>, root: HTMLElement) => {
901
+ // // if (!themeConfig) return
902
+
903
+ // // if (themeConfig.colors) {
904
+ // // applyColorVariables(themeConfig.colors, root)
905
+ // // }
906
+
907
+ // // if (themeConfig.typography) {
908
+ // // applyTypographyVariables(themeConfig.typography, root)
909
+ // // }
910
+
911
+ // // Object.entries(themeConfig).forEach(([key, value]) => {
912
+ // // if (key !== 'colors' && key !== 'typography' && typeof value === 'string') {
913
+ // // const cssVarName = `--${key.replace(/_/g, '-')}`
914
+ // // root.style.setProperty(cssVarName, value)
915
+ // // }
916
+ // // })
917
+ // // }
918
+
919
+ // // /* -------------------------------------------------------------------------- */
920
+ // // /* VARIABLES HELPER */
921
+ // // /* -------------------------------------------------------------------------- */
922
+
923
+ // // let cachedProjectData: ProjectData | null = null
924
+
925
+ // // export const getVariable = (name: string): { name: string; value: any } | undefined => {
926
+ // // if (!cachedProjectData?.variables) {
927
+ // // console.warn('No variables available. Make sure ThemeProvider is mounted.')
928
+ // // return undefined
929
+ // // }
930
+
931
+ // // const variable = cachedProjectData.variables.find(v => v.name === name)
932
+ // // return variable
933
+ // // }
934
+
935
+ // // export const getAllVariables = (): Variable[] => {
936
+ // // return cachedProjectData?.variables || []
937
+ // // }
938
+
939
+ // // /* -------------------------------------------------------------------------- */
940
+ // // /* COMPONENT */
941
+ // // /* -------------------------------------------------------------------------- */
942
+ // // const ThemeProvider: React.FC<ThemeProviderProps> = ({
943
+ // // theme,
944
+ // // children,
945
+ // // funcss = '',
946
+ // // minHeight = '100vh',
947
+ // // projectId,
948
+ // // }) => {
949
+ // // const [variant, setVariant] = useState<ThemeVariant>('standard')
950
+ // // const [themeConfig, setThemeConfig] = useState<ThemeConfig>({})
951
+ // // const [projectData, setProjectData] = useState<ProjectData | null>(null)
952
+ // // const [isLoading, setIsLoading] = useState(true)
953
+ // // const [isInitialLoad, setIsInitialLoad] = useState(true)
954
+ // // const [error, setError] = useState<string | null>(null)
955
+ // // const [currentVersion, setCurrentVersion] = useState<number | null>(null)
956
+
957
+ // // /* -------------------------- Apply base theme --------------------------- */
958
+ // // useEffect(() => {
959
+ // // const root = document.documentElement
960
+ // // const selectedTheme = themes[theme] || themes.light
961
+
962
+ // // Object.entries(selectedTheme).forEach(([key, value]) => {
963
+ // // root.style.setProperty(key, value)
964
+ // // })
965
+
966
+ // // if (
967
+ // // ['dark', 'dark-blue', 'midnight-purple', 'cyber-metal'].includes(theme)
968
+ // // ) {
969
+ // // colorVarsToDarken.forEach((varName) => {
970
+ // // const original = getComputedStyle(root)
971
+ // // .getPropertyValue(varName)
972
+ // // .trim()
973
+ // // if (original) {
974
+ // // const darkAmount = getDarkenAmount(varName)
975
+ // // const rgba = darkenToRgba(original, darkAmount, 0.9)
976
+ // // root.style.setProperty(varName, rgba)
977
+ // // }
978
+ // // })
979
+ // // }
980
+ // // }, [theme])
981
+
982
+ // // /* ---------------------- Theme Loading Logic ----------------------- */
983
+ // // useEffect(() => {
984
+ // // if (typeof window === 'undefined') {
985
+ // // setIsLoading(false)
986
+ // // setIsInitialLoad(false)
987
+ // // return
988
+ // // }
989
+
990
+ // // const root = document.documentElement
991
+ // // let pollTimer: NodeJS.Timeout
992
+
993
+ // // const loadTheme = async () => {
994
+ // // try {
995
+
996
+ // // let finalTheme: ProjectData | null = null
997
+ // // let finalVersion: number | null = null
998
+
999
+ // // // First, try to load local theme
1000
+ // // const localTheme = await loadLocalTheme()
1001
+ // // const localVersion = localTheme?.version || 0
1002
+
1003
+ // // if (projectId) {
1004
+ // // // Validate origin access for CDN
1005
+ // // const hasAccess = await validateOriginAccess(projectId)
1006
+
1007
+ // // if (hasAccess) {
1008
+ // // // Try to load from CDN
1009
+ // // const cdnTheme = await loadThemeFromCDN(projectId)
1010
+ // // const cdnVersion = cdnTheme?.version || 0
1011
+
1012
+ // // if (cdnTheme) {
1013
+ // // // CDN theme available - use it
1014
+ // // finalTheme = cdnTheme
1015
+ // // finalVersion = cdnVersion
1016
+
1017
+ // // if (cdnVersion !== localVersion) {
1018
+ // // console.log(`🔄 Version mismatch: Local(${localVersion}) vs CDN(${cdnVersion})`)
1019
+ // // console.log('ℹ️ Using CDN version. Please update your local funui.json file manually.')
1020
+ // // }
1021
+
1022
+ // // } else if (localTheme) {
1023
+ // // // CDN not available but we have local theme
1024
+ // // console.log('⚠️ CDN unavailable, using local theme')
1025
+ // // finalTheme = localTheme
1026
+ // // finalVersion = localVersion
1027
+ // // } else {
1028
+ // // // No theme available anywhere
1029
+ // // console.warn('⚠️ No theme found (CDN unavailable and no local theme)')
1030
+ // // setError('Theme not found')
1031
+ // // }
1032
+ // // } else {
1033
+ // // // Origin validation failed
1034
+ // // if (localTheme) {
1035
+ // // console.log('⚠️ Origin validation failed, using local theme')
1036
+ // // finalTheme = localTheme
1037
+ // // finalVersion = localVersion
1038
+ // // } else {
1039
+ // // console.error('❌ Origin validation failed and no local theme available')
1040
+ // // setError('Access denied and no local theme available')
1041
+ // // }
1042
+ // // }
1043
+ // // } else {
1044
+ // // // No project ID provided - only use local theme
1045
+ // // console.log('ℹ️ No project ID provided, using local theme only')
1046
+ // // if (localTheme) {
1047
+ // // finalTheme = localTheme
1048
+ // // finalVersion = localVersion
1049
+ // // console.log('✅ Theme loaded from local file')
1050
+ // // } else {
1051
+ // // console.log('ℹ️ No local theme file found - using base theme only')
1052
+ // // // No error here - it's valid to use only base theme
1053
+ // // }
1054
+ // // }
1055
+
1056
+ // // // Apply the theme if we have one
1057
+ // // if (finalTheme && (!currentVersion || finalVersion !== currentVersion)) {
1058
+ // // applyThemeData(finalTheme, root)
1059
+ // // setCurrentVersion(finalVersion)
1060
+ // // setError(null)
1061
+ // // } else if (finalTheme) {
1062
+ // // console.log('✓ Theme up to date')
1063
+ // // }
1064
+
1065
+ // // } catch (err) {
1066
+ // // console.error('❌ Error loading theme:', err)
1067
+ // // setError('Failed to load theme')
1068
+ // // } finally {
1069
+ // // setIsLoading(false)
1070
+ // // setIsInitialLoad(false)
1071
+ // // }
1072
+ // // }
1073
+
1074
+ // // // Initial load
1075
+ // // loadTheme()
1076
+
1077
+ // // // Only poll for updates if we have a project ID
1078
+ // // if (projectId) {
1079
+ // // pollTimer = setInterval(() => {
1080
+ // // loadTheme()
1081
+ // // }, 5 * 60 * 1000)
1082
+ // // }
1083
+
1084
+ // // return () => {
1085
+ // // if (pollTimer) {
1086
+ // // clearInterval(pollTimer)
1087
+ // // }
1088
+ // // }
1089
+ // // }, [projectId, currentVersion, theme])
1090
+
1091
+ // // const applyThemeData = (data: ProjectData, root: HTMLElement) => {
1092
+ // // const themeConfig = data.theme_config ?? {}
1093
+ // // const newVariant = data.default_variation || 'standard'
1094
+
1095
+ // // setVariant(newVariant)
1096
+ // // setThemeConfig(themeConfig)
1097
+ // // setProjectData(data)
1098
+
1099
+ // // // Cache for variable access
1100
+ // // cachedProjectData = data
1101
+
1102
+ // // // Cache for asset access
1103
+ // // cachedAssets = data.assets || []
1104
+
1105
+ // // // Apply all theme config to CSS variables
1106
+ // // applyThemeConfig(themeConfig, root)
1107
+ // // }
1108
+
1109
+ // // const contextValue = useMemo(
1110
+ // // () => ({
1111
+ // // variant,
1112
+ // // setVariant,
1113
+ // // themeConfig,
1114
+ // // projectData,
1115
+ // // isLoading,
1116
+ // // isInitialLoad,
1117
+ // // error,
1118
+ // // }),
1119
+ // // [variant, themeConfig, projectData, isLoading, isInitialLoad, error]
1120
+ // // )
1121
+
1122
+ // // return (
1123
+ // // <ThemeContext.Provider value={contextValue}>
1124
+ // // <div
1125
+ // // className={`theme-${theme} ${funcss}`}
1126
+ // // style={{
1127
+ // // backgroundColor: 'var(--page-bg)',
1128
+ // // color: 'var(--text-color)',
1129
+ // // minHeight: minHeight,
1130
+ // // transition: isInitialLoad ? 'none' : 'background-color 0.3s ease, color 0.3s ease',
1131
+ // // }}
1132
+ // // >
1133
+ // // {children}
1134
+ // // </div>
1135
+ // // </ThemeContext.Provider>
1136
+ // // )
1137
+ // // }
1138
+
1139
+ // // export default ThemeProvider
1140
+
1141
+ // // /* -------------------------------------------------------------------------- */
1142
+ // // /* HELPER HOOKS */
1143
+ // // /* -------------------------------------------------------------------------- */
1144
+
1145
+ // // export const useThemeValue = (key: string): string | undefined => {
1146
+ // // const { themeConfig } = useTheme()
1147
+ // // return themeConfig[key]
1148
+ // // }
1149
+
1150
+ // // export const useComponentConfig = (componentName: string): any => {
1151
+ // // const { projectData } = useTheme()
1152
+ // // return projectData?.components?.[componentName] || {}
1153
+ // // }
1154
+
1155
+ // // export const useColors = (): Record<string, string> => {
1156
+ // // const { projectData } = useTheme()
1157
+ // // return projectData?.theme_config?.colors || {}
1158
+ // // }
1159
+
1160
+ // // export const useTypography = (): Record<string, string> => {
1161
+ // // const { projectData } = useTheme()
1162
+ // // return projectData?.theme_config?.typography || {}
1163
+ // // }
1164
+
1165
+ // // export const useThemeConfig = (): Record<string, any> => {
1166
+ // // const { projectData } = useTheme()
1167
+ // // return projectData?.theme_config || {}
1168
+ // // }
1169
+
1170
+ // // export const useProjectData = (): ProjectData | null => {
1171
+ // // const { projectData } = useTheme()
1172
+ // // return projectData
1173
+ // // }
1174
+
1175
+ // // export const useColor = (colorName: string): string | undefined => {
1176
+ // // const colors = useColors()
1177
+ // // return colors[colorName]
1178
+ // // }
1179
+
1180
+ // // export const useTypographyValue = (property: string): string | undefined => {
1181
+ // // const typography = useTypography()
1182
+ // // return typography[property]
1183
+ // // }
1184
+
1185
+ // // export const useComponentVariant = (componentName: string, variantName: string = 'default'): any => {
1186
+ // // const componentConfig = useComponentConfig(componentName)
1187
+ // // return componentConfig[variantName] || {}
1188
+ // // }
1189
+
1190
+ // // // Hook to access variables
1191
+ // // export const useVariables = (): Variable[] => {
1192
+ // // const { projectData } = useTheme()
1193
+ // // return projectData?.variables || []
1194
+ // // }
1195
+
1196
+ // // // Hook to get a specific variable
1197
+ // // export const useVariable = (name: string): any => {
1198
+ // // const variables = useVariables()
1199
+ // // const variable = variables.find(v => v.name === name)
1200
+ // // return variable?.value
1201
+ // // }
1202
+
1203
+ // // /* -------------------------------------------------------------------------- */
1204
+ // // /* ASSETS HELPER */
1205
+ // // /* -------------------------------------------------------------------------- */
1206
+
1207
+ // // interface Asset {
1208
+ // // name: string
1209
+ // // url: string
1210
+ // // file_type: string
1211
+ // // }
1212
+
1213
+ // // let cachedAssets: Asset[] = []
1214
+
1215
+ // // export const getAsset = (name: string): Asset | undefined => {
1216
+ // // if (!cachedAssets.length) {
1217
+ // // console.warn('No assets available. Make sure ThemeProvider is mounted and assets are loaded.')
1218
+ // // return undefined
1219
+ // // }
1220
+
1221
+ // // const asset = cachedAssets.find(a => a.name === name)
1222
+ // // return asset
1223
+ // // }
1224
+
1225
+ // // export const getAllAssets = (): Asset[] => {
1226
+ // // return cachedAssets
1227
+ // // }
1228
+
1229
+ // // export const getAssetValue = (name: string): string | undefined => {
1230
+ // // const asset = getAsset(name)
1231
+ // // return asset?.url
1232
+ // // }
1233
+
1234
+ // // export const getAssetType = (name: string): string | undefined => {
1235
+ // // const asset = getAsset(name)
1236
+ // // return asset?.file_type
1237
+ // // }
1238
+
1239
+ // // export const getAssetInfo = (name: string): { value: string; type: string; name: string } | undefined => {
1240
+ // // const asset = getAsset(name)
1241
+ // // if (!asset) return undefined
1242
+
1243
+ // // return {
1244
+ // // value: asset.url,
1245
+ // // type: asset.file_type,
1246
+ // // name: asset.name
1247
+ // // }
1248
+ // // }
1249
+
1250
+ // // // Hook to access all assets
1251
+ // // export const useAssets = (): Asset[] => {
1252
+ // // const { projectData } = useTheme()
1253
+ // // return projectData?.assets || []
1254
+ // // }
1255
+
1256
+ // // // Hook to get a specific asset
1257
+ // // export const useAsset = (name: string): Asset | undefined => {
1258
+ // // const assets = useAssets()
1259
+ // // const asset = assets.find(a => a.name === name)
1260
+ // // return asset
1261
+ // // }
1262
+
1263
+ // // // Hook to get asset URL (most common use case)
1264
+ // // export const useAssetValue = (name: string): string | undefined => {
1265
+ // // const asset = useAsset(name)
1266
+ // // return asset?.url
1267
+ // // }
1268
+
1269
+ // // // Hook to get asset type
1270
+ // // export const useAssetType = (name: string): string | undefined => {
1271
+ // // const asset = useAsset(name)
1272
+ // // return asset?.file_type
1273
+ // // }
1274
+
1275
+ // // // Hook to get complete asset info
1276
+ // // export const useAssetInfo = (name: string): { value: string; type: string; name: string } | undefined => {
1277
+ // // const asset = useAsset(name)
1278
+ // // if (!asset) return undefined
1279
+
1280
+ // // return {
1281
+ // // value: asset.url,
1282
+ // // type: asset.file_type,
1283
+ // // name: asset.name
1284
+ // // }
1285
+ // // }
1286
+
1287
+ // // // Hook to filter assets by type
1288
+ // // export const useAssetsByType = (type: string): Asset[] => {
1289
+ // // const assets = useAssets()
1290
+ // // return assets.filter(asset => asset.file_type === type)
1291
+ // // }
1292
+
1293
+ // // // Hook to get image assets
1294
+ // // export const useImageAssets = (): Asset[] => {
1295
+ // // return useAssetsByType('image')
1296
+ // // }
1297
+
1298
+ // // // Hook to get video assets
1299
+ // // export const useVideoAssets = (): Asset[] => {
1300
+ // // return useAssetsByType('video')
1301
+ // // }
1302
+
1303
+ // // // Hook to get audio assets
1304
+ // // export const useAudioAssets = (): Asset[] => {
1305
+ // // return useAssetsByType('audio')
1306
+ // // }
1307
+
1308
+ // // // Hook to get document assets
1309
+ // // export const useDocumentAssets = (): Asset[] => {
1310
+ // // return useAssetsByType('document')
1311
+ // // }