openpencil/packages/dom-css/src/export/grid.ts
Danila Poyarkov fb0cb9ee5f
feat: write variable-bound properties as tokens in exported code (#895)
* 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.
2026-10-05 09:15:44 +00:00

43 lines
1.8 KiB
TypeScript

import type { GridTrack, SceneNode } from '@open-pencil/scene-graph'
import type { DesignStyleDeclaration } from '../types'
function trackToCSS(track: GridTrack): string {
if (track.sizing === 'FR') return `${track.value}fr`
if (track.sizing === 'FIXED') return `${track.value}px`
return 'auto'
}
/** Equal `1fr` tracks use Tailwind's `grid-cols-N` form, which keeps them from overflowing. */
function tracksToCSS(tracks: GridTrack[]): string {
if (tracks.every((track) => track.sizing === 'FR' && track.value === 1))
return `repeat(${tracks.length}, minmax(0, 1fr))`
return tracks.map(trackToCSS).join(' ')
}
export function addGridContainer(
style: DesignStyleDeclaration,
node: SceneNode,
css: (field: string, literal: string) => string
): void {
style.display = 'grid'
if (node.gridTemplateColumns.length > 0)
style['grid-template-columns'] = tracksToCSS(node.gridTemplateColumns)
if (node.gridTemplateRows.length > 0)
style['grid-template-rows'] = tracksToCSS(node.gridTemplateRows)
if (node.gridColumnGap > 0) style['column-gap'] = css('gridColumnGap', `${node.gridColumnGap}px`)
if (node.gridRowGap > 0) style['row-gap'] = css('gridRowGap', `${node.gridRowGap}px`)
}
/** A span sets the whole placement, so the start follows it. */
export function addGridPlacement(style: DesignStyleDeclaration, node: SceneNode): void {
const position = node.gridPosition
if (!position) return
if (position.columnSpan > 1)
style['grid-column'] = `span ${position.columnSpan} / span ${position.columnSpan}`
if (position.column > 0) style['grid-column-start'] = String(position.column)
if (position.rowSpan > 1)
style['grid-row'] = `span ${position.rowSpan} / span ${position.rowSpan}`
if (position.row > 0) style['grid-row-start'] = String(position.row)
}