From e2812c99ea0f6f60ab2be15bec3633221c17c55a Mon Sep 17 00:00:00 2001 From: Roberto Redradix Date: Wed, 23 Aug 2023 21:05:39 +0200 Subject: [PATCH] feat(cli): allow exporting colors CSS vars --- packages/demo/penpot-css-export.config.js | 6 ++++ .../penpot-css-export/src/lib/api/penpot.ts | 15 ++++++++++ .../penpot-css-export/src/lib/api/types.ts | 19 +++++++++---- packages/penpot-css-export/src/lib/config.ts | 20 ++++++++++++- packages/penpot-css-export/src/lib/css.ts | 18 ++++++++++++ packages/penpot-css-export/src/lib/index.ts | 28 ++++++++++++++++++- packages/penpot-css-export/src/lib/types.ts | 6 ++++ 7 files changed, 105 insertions(+), 7 deletions(-) diff --git a/packages/demo/penpot-css-export.config.js b/packages/demo/penpot-css-export.config.js index 016e3df..1539c46 100644 --- a/packages/demo/penpot-css-export.config.js +++ b/packages/demo/penpot-css-export.config.js @@ -5,6 +5,12 @@ require('dotenv').config() */ const config = { accessToken: process.env.PENPOT_ACCESS_TOKEN, + colors: [ + { + output: 'src/styles/colors.css', // 👈🏻 Path where your css should be generated. + fileId: '4a499800-872e-80e1-8002-fc0b585dc061' + }, + ], typographies: [ { output: 'src/styles/typographies.css', // 👈🏻 Path where your css should be generated. diff --git a/packages/penpot-css-export/src/lib/api/penpot.ts b/packages/penpot-css-export/src/lib/api/penpot.ts index 56ea7ce..d76a8b4 100644 --- a/packages/penpot-css-export/src/lib/api/penpot.ts +++ b/packages/penpot-css-export/src/lib/api/penpot.ts @@ -15,6 +15,7 @@ import type { PenpotTypography, PenpotGetFileOptions, PenpotFile, + PenpotColor, } from './types' class PenpotApiError extends Error { @@ -145,4 +146,18 @@ export class Penpot { fontFamily: `"${typography.fontFamily}"`, } } + + async getFileColors( + options: PenpotGetFileOptions, + ): Promise<{ fileName: string; colors: PenpotColor[] }> { + const file = await this.fetcher({ + command: 'get-file', + body: { + id: options.fileId, + }, + }) + const colors = Object.values(file.data.colors) + + return { fileName: file.name, colors } + } } diff --git a/packages/penpot-css-export/src/lib/api/types.ts b/packages/penpot-css-export/src/lib/api/types.ts index 58fbc39..3f72925 100644 --- a/packages/penpot-css-export/src/lib/api/types.ts +++ b/packages/penpot-css-export/src/lib/api/types.ts @@ -25,6 +25,18 @@ export interface PenpotObject { shapes?: string[] } +type PenpotAsset = { + id: string + name: string + modifiedAt: string + path: string +} + +export interface PenpotColor extends PenpotAsset { + color: string + opacity: number +} + type CssTextProperty = | 'lineHeight' | 'fontStyle' @@ -34,11 +46,7 @@ type CssTextProperty = | 'letterSpacing' | 'fontFamily' -export type PenpotTypography = { - id: string - name: string - modifiedAt: string - path: string +export type PenpotTypography = PenpotAsset & { fontId: string fontVariantId: string } & Record @@ -56,6 +64,7 @@ export interface PenpotFile { data: { id: string version: number + colors: PenpotColor[] typographies: PenpotTypography[] pages: PenpotPage[] } diff --git a/packages/penpot-css-export/src/lib/config.ts b/packages/penpot-css-export/src/lib/config.ts index 50231ca..50bd3b6 100644 --- a/packages/penpot-css-export/src/lib/config.ts +++ b/packages/penpot-css-export/src/lib/config.ts @@ -1,6 +1,6 @@ import { Config, PagesConfig } from './types' -const BASE_CONFIG: Omit = { +const BASE_CONFIG: Omit = { accessToken: '', } @@ -44,6 +44,24 @@ export function validateAndNormalizePenpotExportConfig(config: Config): Config { ...config, pages: [], typographies: [], + colors: [], + } + + for (const [index, colorsConfig] of config.colors.entries()) { + const { output, fileId } = colorsConfig + + if (!output) { + throw new MissingOutputPathError(index) + } + + if (!fileId) { + throw new MissingFileIdError(index) + } + + normalizedConfig.colors.push({ + output, + fileId, + }) } for (const [index, typographiesConfig] of config.typographies.entries()) { diff --git a/packages/penpot-css-export/src/lib/css.ts b/packages/penpot-css-export/src/lib/css.ts index bfad605..ab7a847 100644 --- a/packages/penpot-css-export/src/lib/css.ts +++ b/packages/penpot-css-export/src/lib/css.ts @@ -61,6 +61,24 @@ export function textToCssClassSelector(str: string) { return '.' + ident } +/** From: https://www.w3.org/TR/css-variables-1/#custom-property + * A custom property is any property whose name starts with two dashes (U+002D HYPHEN-MINUS), like --foo. The + * production corresponds to this: it’s defined as any (a valid identifier that + * starts with two dashes), except -- itself, which is reserved for future use by CSS. + * + * The production is a , with all the case-sensitivity that implies, with the additional + * restriction that it must start with two dashes (U+002D HYPHEN-MINUS). + * + * This generic data type is denoted by , and represents any valid CSS identifier that would not be + * misinterpreted as a pre-defined keyword in that property’s value definition. Such identifiers are fully + * case-sensitive (meaning they’re compared using the "identical to" operation), even in the ASCII range (e.g. example + * and EXAMPLE are two different, unrelated user-defined identifiers). + */ +export function textToCssCustomProperyName(str: string) { + const unescapedDashedIdentifier = '--' + str.trimStart() + return textToCssIdentToken(unescapedDashedIdentifier) +} + export function cssClassDefinitionToCSS( cssClassDefinition: CSSClassDefinition, ): string { diff --git a/packages/penpot-css-export/src/lib/index.ts b/packages/penpot-css-export/src/lib/index.ts index 922201c..c99af49 100644 --- a/packages/penpot-css-export/src/lib/index.ts +++ b/packages/penpot-css-export/src/lib/index.ts @@ -1,7 +1,11 @@ import Penpot from '../lib/api' import { validateAndNormalizePenpotExportConfig } from './config' import { CSSClassDefinition, Config } from './types' -import { textToCssClassSelector, writeCssFile } from './css' +import { + textToCssClassSelector, + textToCssCustomProperyName, + writeCssFile, +} from './css' import path from 'path' export async function generateCssFromConfig( @@ -11,6 +15,28 @@ export async function generateCssFromConfig( const validatedConfig = validateAndNormalizePenpotExportConfig(config) const penpot = new Penpot({ accessToken: config.accessToken }) + for (const colorsConfig of validatedConfig.colors) { + const cssClassDefinition: CSSClassDefinition = { + selector: ':root', + cssProps: {}, + } + + const { colors } = await penpot.getFileColors({ + fileId: colorsConfig.fileId, + }) + + for (const color of colors) { + const objectClassname = textToCssCustomProperyName(color.name) + cssClassDefinition.cssProps[objectClassname] = color.color // FIXME Add opacity with rgba() + } + + const cssPath = path.resolve(rootProjectPath, colorsConfig.output) + + writeCssFile(cssPath, [cssClassDefinition]) + + console.log('✅ Colors: %s', colorsConfig.output) + } + for (const typographiesConfig of validatedConfig.typographies) { const cssClassDefinitions: CSSClassDefinition[] = [] diff --git a/packages/penpot-css-export/src/lib/types.ts b/packages/penpot-css-export/src/lib/types.ts index 60137b8..01c97eb 100644 --- a/packages/penpot-css-export/src/lib/types.ts +++ b/packages/penpot-css-export/src/lib/types.ts @@ -9,8 +9,14 @@ export interface TypographiesConfig { fileId: string } +export interface ColorsConfig { + output: string + fileId: string +} + export interface Config { accessToken: string + colors: ColorsConfig[] typographies: TypographiesConfig[] pages: PagesConfig[] }