0
Fork 0
mirror of https://github.com/penpot/penpot-export.git synced 2025-03-12 07:21:20 -05:00

feat: allow exporting typographies CSS

This commit is contained in:
Roberto Redradix 2023-08-22 18:19:28 +02:00 committed by Roberto RedRadix
parent 028e29f4c0
commit e19ea35b84
6 changed files with 127 additions and 7 deletions

View file

@ -5,6 +5,12 @@ require('dotenv').config()
*/
const config = {
accessToken: process.env.PENPOT_ACCESS_TOKEN,
typographies: [
{
output: 'src/styles/typographies.css', // 👈🏻 Path where your css should be generated.
fileId: '4a499800-872e-80e1-8002-fc0b585dc061'
},
],
pages: [
{
output: 'src/styles/ui.css', // 👈🏻 Path where your css should be generated.

View file

@ -12,6 +12,9 @@ import type {
FetcherOptions,
PenpotGetPageOptions,
PenpotApiErrorResponse,
PenpotTypography,
PenpotGetFileOptions,
PenpotFile,
} from './types'
class PenpotApiError extends Error {
@ -111,4 +114,35 @@ export class Penpot {
return pickObjectProps(objectCssProps, textCssProps)
}
async getFileTypographies(
options: PenpotGetFileOptions,
): Promise<{ fileName: string; typographies: PenpotTypography[] }> {
const file = await this.fetcher<PenpotFile>({
command: 'get-file',
body: {
id: options.fileId,
},
})
const typographies = Object.values(file.data.typographies)
return { fileName: file.name, typographies }
}
static getTypographyAssetCssProps(typography: PenpotTypography) {
const textCssProps = [
'lineHeight',
'fontStyle',
'textTransform',
'fontWeight',
'direction',
]
return {
...pickObjectProps(typography, textCssProps),
fontSize: `${typography.fontSize}px`,
letterSpacing: `${typography.letterSpacing}px`,
fontFamily: `"${typography.fontFamily}"`,
}
}
}

View file

@ -12,6 +12,10 @@ export interface PenpotGetPageOptions {
pageId: string
}
export interface PenpotGetFileOptions {
fileId: string
}
export interface PenpotObject {
id: string
name: string
@ -21,11 +25,42 @@ export interface PenpotObject {
shapes?: string[]
}
type CssTextProperty =
| 'lineHeight'
| 'fontStyle'
| 'textTransform'
| 'fontSize'
| 'fontWeight'
| 'letterSpacing'
| 'fontFamily'
export type PenpotTypography = {
id: string
name: string
modifiedAt: string
path: string
fontId: string
fontVariantId: string
} & Record<CssTextProperty, string>
export interface PenpotPage {
name: string
objects: Record<string, PenpotObject>
}
export interface PenpotFile {
id: string
name: string
revn: number
modifiedAt: string
data: {
id: string
version: number
typographies: PenpotTypography[]
pages: PenpotPage[]
}
}
export interface PenpotApiErrorResponse {
type: string
code: string

View file

@ -1,6 +1,6 @@
import { Config, PagesConfig } from './types'
const BASE_CONFIG: Omit<Config, 'pages'> = {
const BASE_CONFIG: Omit<Config, 'pages' | 'typographies'> = {
accessToken: '',
}
@ -43,25 +43,42 @@ export function validateAndNormalizePenpotExportConfig(config: Config): Config {
...BASE_CONFIG,
...config,
pages: [],
typographies: [],
}
for (const [index, typographiesConfig] of config.typographies.entries()) {
const { output, fileId } = typographiesConfig
for (const [index, page] of config.pages.entries()) {
if (!page.output) {
if (!output) {
throw new MissingOutputPathError(index)
}
if (!page.fileId) {
if (!fileId) {
throw new MissingFileIdError(index)
}
if (!page.pageId) {
normalizedConfig.typographies.push({
output,
fileId,
})
}
for (const [index, pageConfig] of config.pages.entries()) {
if (!pageConfig.output) {
throw new MissingOutputPathError(index)
}
if (!pageConfig.fileId) {
throw new MissingFileIdError(index)
}
if (!pageConfig.pageId) {
throw new MissingPageIdError(index)
}
normalizedConfig.pages.push({
...PAGES_CONFIG,
...page,
...pageConfig,
})
}

View file

@ -12,6 +12,28 @@ export async function generateCssFromConfig(
const validatedConfig = validateAndNormalizePenpotExportConfig(config)
const penpot = new Penpot({ accessToken: config.accessToken })
for (const typographiesConfig of validatedConfig.typographies) {
const cssClassDefinitions: CSSClassDefinition[] = []
const { fileName, typographies } = await penpot.getFileTypographies({
fileId: typographiesConfig.fileId,
})
const fileClassname = textToValidClassname(fileName)
for (const typography of typographies) {
const cssProps = Penpot.getTypographyAssetCssProps(typography)
const objectClassname = textToValidClassname(typography.name)
const className = `${fileClassname}--${objectClassname}`
cssClassDefinitions.push({ className, cssProps })
}
const cssPath = path.resolve(rootProjectPath, typographiesConfig.output)
writeCssFile(cssPath, cssClassDefinitions)
console.log('✅ Typographies: %s', typographiesConfig.output)
}
for (const page of validatedConfig.pages) {
const cssClassDefinitions: CSSClassDefinition[] = []
@ -36,6 +58,6 @@ export async function generateCssFromConfig(
writeCssFile(cssPath, cssClassDefinitions)
console.log('✅ %s', page.output)
console.log('✅ Page components: %s', page.output)
}
}

View file

@ -4,8 +4,14 @@ export interface PagesConfig {
pageId: string
}
export interface TypographiesConfig {
output: string
fileId: string
}
export interface Config {
accessToken: string
typographies: TypographiesConfig[]
pages: PagesConfig[]
}