mirror of
https://github.com/penpot/penpot-export.git
synced 2025-02-12 18:18:01 -05:00
feat(cli): allow exporting colors CSS vars
This commit is contained in:
parent
c5288352ab
commit
e2812c99ea
7 changed files with 105 additions and 7 deletions
|
@ -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.
|
||||
|
|
|
@ -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<PenpotFile>({
|
||||
command: 'get-file',
|
||||
body: {
|
||||
id: options.fileId,
|
||||
},
|
||||
})
|
||||
const colors = Object.values(file.data.colors)
|
||||
|
||||
return { fileName: file.name, colors }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<CssTextProperty, string>
|
||||
|
@ -56,6 +64,7 @@ export interface PenpotFile {
|
|||
data: {
|
||||
id: string
|
||||
version: number
|
||||
colors: PenpotColor[]
|
||||
typographies: PenpotTypography[]
|
||||
pages: PenpotPage[]
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Config, PagesConfig } from './types'
|
||||
|
||||
const BASE_CONFIG: Omit<Config, 'pages' | 'typographies'> = {
|
||||
const BASE_CONFIG: Omit<Config, 'pages' | 'typographies' | 'colors'> = {
|
||||
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()) {
|
||||
|
|
|
@ -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
|
||||
* <custom-property-name> production corresponds to this: it’s defined as any <dashed-ident> (a valid identifier that
|
||||
* starts with two dashes), except -- itself, which is reserved for future use by CSS.
|
||||
*
|
||||
* The <dashed-ident> production is a <custom-ident>, 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 <custom-ident>, 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 {
|
||||
|
|
|
@ -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[] = []
|
||||
|
||||
|
|
|
@ -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[]
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue