hololake-system-architecture/product-source/hololake-platform/src/hooks/useTheme.ts

60 lines
1.9 KiB
TypeScript

import { useMemo } from 'react'
import themeConfig from '../theme.json'
type ThemeValue = string | number | Record<string, unknown> | unknown[]
function isThemeBranch(value: ThemeValue): value is Record<string, ThemeValue> {
return typeof value === 'object' && value !== null && !Array.isArray(value)
}
function isUnitlessThemeNumber(key: string, cssKey: string): boolean {
return /weight|lineHeight|opacity/i.test(key)
|| cssKey.includes('line-height')
|| cssKey.includes('font-weight')
}
function themeCssValue(key: string, cssKey: string, value: string | number): string {
if (typeof value !== 'number') return String(value)
return isUnitlessThemeNumber(key, cssKey) ? String(value) : `${value}px`
}
/** Convert a nested theme config object into a flat map of CSS custom properties */
function flattenTheme(
obj: Record<string, ThemeValue>,
prefix = '--'
): Record<string, string> {
const result: Record<string, string> = {}
for (const [key, value] of Object.entries(obj)) {
const cssKey = `${prefix}${camelToKebab(key)}`
if (value === null || value === undefined) continue
if (Array.isArray(value)) continue // skip arrays (e.g. nestedBulletSymbols)
if (isThemeBranch(value)) {
Object.assign(result, flattenTheme(value, `${cssKey}-`))
continue
}
if (typeof value !== 'string' && typeof value !== 'number') continue
Reflect.set(result, cssKey, themeCssValue(key, cssKey, value))
}
return result
}
function camelToKebab(str: string): string {
return str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase()
}
export function useEditorTheme() {
const { cssVars, styleString } = useMemo(() => {
const vars = flattenTheme(themeConfig as Record<string, ThemeValue>)
const str = Object.entries(vars)
.map(([k, v]) => `${k}: ${v};`)
.join('\n')
return { cssVars: vars, styleString: str }
}, [])
return { themeConfig, cssVars, styleString }
}