funuicss 3.7.16 → 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.
- package/css/fun.css +324 -202
- package/demo/theme.tsx +1311 -0
- package/index.d.ts +2 -0
- package/index.js +5 -1
- package/package.json +1 -1
- package/ui/button/Button.d.ts +2 -1
- package/ui/button/Button.js +3 -3
- package/ui/components/ImageScaler.d.ts +6 -0
- package/ui/components/ImageScaler.js +17 -0
- package/ui/div/Div.d.ts +3 -1
- package/ui/div/Div.js +2 -2
- package/ui/empty/Empty.d.ts +17 -0
- package/ui/empty/Empty.js +66 -0
- package/ui/flex/Flex.d.ts +2 -1
- package/ui/flex/Flex.js +3 -3
- package/ui/modal/Modal.d.ts +1 -1
- package/ui/products/CartModal.d.ts +20 -0
- package/ui/products/CartModal.js +85 -0
- package/ui/products/ProductCard.d.ts +13 -0
- package/ui/products/ProductCard.js +56 -0
- package/ui/products/ProductDetail.d.ts +14 -0
- package/ui/products/ProductDetail.js +249 -0
- package/ui/products/ProductDetailModal.d.ts +17 -0
- package/ui/products/ProductDetailModal.js +99 -0
- package/ui/products/Products.d.ts +60 -0
- package/ui/products/Products.js +312 -0
- package/ui/products/Store.d.ts +99 -0
- package/ui/products/Store.js +515 -0
- package/ui/sidebar/SideBar.d.ts +3 -1
- package/ui/sidebar/SideBar.js +50 -11
- package/ui/specials/Circle.d.ts +2 -1
- package/ui/specials/Circle.js +2 -2
- package/ui/table/Table.d.ts +15 -1
- package/ui/table/Table.js +143 -15
- package/ui/theme/theme.d.ts +90 -0
- package/ui/theme/theme.js +440 -545
- package/utils/Buckets.d.ts +0 -0
- 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
|
+
// // }
|