* feat: write variable-bound properties as tokens in exported code HTML and Tailwind JSX export reference the design tokens a layer is bound to, var(--color-primary) or bg-primary, instead of baking in their values. A binding becomes a reference only where the stylesheet resolves it as the canvas draws it: the layer still draws the value, the token's unit fits the property, and an attribute such as data-theme="dark" can put the element in the layer's mode. Standalone HTML includes the stylesheet for the tokens it uses, compiled into Tailwind's @theme for Tailwind pages. * refactor: share the collection variable list and tighten token helpers Read every variable in collection order through one collectionVariables helper built on es-toolkit's compact, used by the stylesheet and the token references, and map padding sides and color channels instead of repeating them.
47 lines
2 KiB
TypeScript
47 lines
2 KiB
TypeScript
import type { Effect, Fill, Stroke } from '@open-pencil/scene-graph'
|
|
import { colorToCSS, colorToHex } from '@open-pencil/scene-graph/color'
|
|
import type { Color } from '@open-pencil/scene-graph/primitives'
|
|
|
|
import type { DesignStyleDeclaration } from '../types'
|
|
|
|
/** Opaque colors as hex, which Tailwind matches to its palette; translucent ones as `rgba()`. */
|
|
export function cssColor(color: Color): string {
|
|
return color.a >= 1 ? colorToHex(color) : colorToCSS(color)
|
|
}
|
|
|
|
export function fillToCSS(fill: Fill | undefined): string | undefined {
|
|
if (fill?.type !== 'SOLID' || !fill.visible) return undefined
|
|
return cssColor({ ...fill.color, a: fill.opacity })
|
|
}
|
|
|
|
export function strokeColorToCSS(stroke: Stroke | undefined): string | undefined {
|
|
if (!stroke?.visible) return undefined
|
|
return cssColor({ ...stroke.color, a: stroke.opacity })
|
|
}
|
|
|
|
function shadowToCSS(effect: Effect): string {
|
|
const inset = effect.type === 'INNER_SHADOW' ? 'inset ' : ''
|
|
return `${inset}${effect.offset.x}px ${effect.offset.y}px ${effect.radius}px ${effect.spread}px ${cssColor(effect.color)}`
|
|
}
|
|
|
|
export function dropShadowToCSS(effect: Effect | undefined): string | undefined {
|
|
if (effect?.type !== 'DROP_SHADOW' || !effect.visible) return undefined
|
|
return shadowToCSS(effect)
|
|
}
|
|
|
|
/** Every visible shadow, in order, plus layer and background blur. */
|
|
export function effectsToCSS(effects: Effect[]): DesignStyleDeclaration {
|
|
const style: DesignStyleDeclaration = {}
|
|
const visible = effects.filter((effect) => effect.visible)
|
|
const shadows = visible.filter(
|
|
(effect) => effect.type === 'DROP_SHADOW' || effect.type === 'INNER_SHADOW'
|
|
)
|
|
if (shadows.length > 0) style['box-shadow'] = shadows.map(shadowToCSS).join(', ')
|
|
for (const effect of visible) {
|
|
if (effect.type === 'LAYER_BLUR' || effect.type === 'FOREGROUND_BLUR')
|
|
style.filter = `blur(${effect.radius}px)`
|
|
if (effect.type === 'BACKGROUND_BLUR') style['backdrop-filter'] = `blur(${effect.radius}px)`
|
|
}
|
|
return style
|
|
}
|