mirror of
https://github.com/penpot/penpot-export.git
synced 2025-01-06 14:50:10 -05:00
refactor(core): decouple name scoping logic from API adapters
Introduce the concept of transformer functions. A transformer function will receive an assets as an input and will output the same kind of asset. Scoping class names in CSS was a transformation applied to typography and page components assets when the output is CSS. Decoupling this transformation from the rest of the adapter results in cleaner API inbound adapters, without any project-specific logic, so now they can be co-located close to the PenpotClient code.
This commit is contained in:
parent
07977124da
commit
660f2b9ae9
13 changed files with 245 additions and 124 deletions
|
@ -1,35 +0,0 @@
|
||||||
import { PenpotApiTypography, CssTextProperty, PenpotApiFile } from '../../api'
|
|
||||||
import { CSSClassDefinition } from '../../types'
|
|
||||||
|
|
||||||
const getTypographyAssetCssProps = (
|
|
||||||
typography: PenpotApiTypography,
|
|
||||||
): Record<CssTextProperty, string> => {
|
|
||||||
return {
|
|
||||||
lineHeight: typography.lineHeight,
|
|
||||||
fontStyle: typography.fontStyle,
|
|
||||||
textTransform: typography.textTransform,
|
|
||||||
fontWeight: typography.fontWeight,
|
|
||||||
fontSize: `${typography.fontSize}px`,
|
|
||||||
letterSpacing: `${typography.letterSpacing}px`,
|
|
||||||
fontFamily: `"${typography.fontFamily}"`,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const adaptTypographiesToCssClassDefinitions = (
|
|
||||||
penpotFile: PenpotApiFile,
|
|
||||||
): CSSClassDefinition[] => {
|
|
||||||
const fileName = penpotFile.name
|
|
||||||
const typographies = Object.values(penpotFile.data.typographies ?? {})
|
|
||||||
|
|
||||||
const cssClassDefinitions = typographies.map((typography) => {
|
|
||||||
const cssProps = getTypographyAssetCssProps(typography)
|
|
||||||
|
|
||||||
return {
|
|
||||||
scope: fileName,
|
|
||||||
name: typography.name,
|
|
||||||
cssProps,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return cssClassDefinitions
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
import { PenpotApiFile } from '../../api'
|
|
||||||
import { FontsSummary } from '../../types'
|
|
||||||
|
|
||||||
export const summarizeTypographies = (
|
|
||||||
penpotFile: PenpotApiFile,
|
|
||||||
): FontsSummary => {
|
|
||||||
const typographies = Object.values(penpotFile.data.typographies ?? {})
|
|
||||||
|
|
||||||
const separator = '|>'
|
|
||||||
const dedupedFontsKeys = Array.from(
|
|
||||||
typographies.reduce((set, typography) => {
|
|
||||||
const { fontId, fontFamily, fontWeight } = typography
|
|
||||||
const typographyKey = [fontId, fontFamily, fontWeight].join(separator)
|
|
||||||
|
|
||||||
set.add(typographyKey)
|
|
||||||
return set
|
|
||||||
}, new Set<string>()),
|
|
||||||
)
|
|
||||||
|
|
||||||
const fontsSummary = dedupedFontsKeys.reduce<FontsSummary>(
|
|
||||||
(summary, typographyKey) => {
|
|
||||||
const [fontId, fontFamily, fontWeight] = typographyKey.split(separator)
|
|
||||||
const fontSource = fontId.startsWith('gfont-')
|
|
||||||
? 'googleFonts'
|
|
||||||
: 'userCustomFonts'
|
|
||||||
|
|
||||||
summary[fontSource][fontFamily] ??= []
|
|
||||||
summary[fontSource][fontFamily].push(fontWeight)
|
|
||||||
|
|
||||||
return summary
|
|
||||||
},
|
|
||||||
{ googleFonts: {}, userCustomFonts: {} },
|
|
||||||
)
|
|
||||||
|
|
||||||
return fontsSummary
|
|
||||||
}
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { PenpotApiFile } from '../../api'
|
import { CSSCustomPropertyDefinition, ColorAssets } from '../../types'
|
||||||
import { CSSCustomPropertyDefinition } from '../../types'
|
|
||||||
|
import { PenpotApiFile } from '../types'
|
||||||
|
|
||||||
const toHexQuartet = (hexTriplet: string, alpha: number = 1) => {
|
const toHexQuartet = (hexTriplet: string, alpha: number = 1) => {
|
||||||
const alphaChannel = Math.trunc(alpha * 0x100) - 1
|
const alphaChannel = Math.trunc(alpha * 0x100) - 1
|
||||||
|
@ -20,19 +21,21 @@ const toHexQuartet = (hexTriplet: string, alpha: number = 1) => {
|
||||||
return ('#' + RR + GG + BB + AA).toLowerCase()
|
return ('#' + RR + GG + BB + AA).toLowerCase()
|
||||||
}
|
}
|
||||||
|
|
||||||
export const adaptColorsToCssVariables = (
|
const adaptColorsToCssVariables = (penpotFile: PenpotApiFile): ColorAssets => {
|
||||||
penpotFile: PenpotApiFile,
|
|
||||||
): CSSCustomPropertyDefinition[] => {
|
|
||||||
const fileName = penpotFile.name
|
const fileName = penpotFile.name
|
||||||
const colors = Object.values(penpotFile.data.colors ?? {})
|
const colors = Object.values(penpotFile.data.colors ?? {})
|
||||||
|
|
||||||
const cssPropsEntries = colors.map((color) => {
|
const cssPropsEntries = colors.map<CSSCustomPropertyDefinition>((color) => {
|
||||||
return {
|
return {
|
||||||
scope: fileName,
|
|
||||||
name: color.name,
|
name: color.name,
|
||||||
value: toHexQuartet(color.color, color.opacity),
|
value: toHexQuartet(color.color, color.opacity),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return cssPropsEntries
|
return {
|
||||||
|
scope: fileName,
|
||||||
|
colors: cssPropsEntries,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default adaptColorsToCssVariables
|
|
@ -1,11 +1,11 @@
|
||||||
|
import { CSSClassDefinition, PageComponentAssets } from '../../types'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getObjectShapesFromPage,
|
getObjectShapesFromPage,
|
||||||
isComponent,
|
isComponent,
|
||||||
pickObjectProps,
|
pickObjectProps,
|
||||||
} from '../../api/helpers'
|
} from '../helpers'
|
||||||
|
import { PenpotApiFile, PenpotApiObject } from '../types'
|
||||||
import { PenpotApiFile, PenpotApiObject } from '../../api'
|
|
||||||
import { CSSClassDefinition } from '../../types'
|
|
||||||
|
|
||||||
const extractObjectCssProps = (object: PenpotApiObject) => {
|
const extractObjectCssProps = (object: PenpotApiObject) => {
|
||||||
let { textDecoration, ...styles } = object.positionData[0]
|
let { textDecoration, ...styles } = object.positionData[0]
|
||||||
|
@ -33,10 +33,10 @@ const getTextObjectCssProps = (object: PenpotApiObject) => {
|
||||||
return pickObjectProps(objectCssProps, textCssProps)
|
return pickObjectProps(objectCssProps, textCssProps)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const adaptPageComponentsToCssClassDefinitions = (
|
const adaptPageComponentsToCssClassDefinitions = (
|
||||||
penpotFile: PenpotApiFile,
|
penpotFile: PenpotApiFile,
|
||||||
options: { pageId: string },
|
options: { pageId: string },
|
||||||
): CSSClassDefinition[] => {
|
): PageComponentAssets => {
|
||||||
const pages = penpotFile.data.pagesIndex ?? {}
|
const pages = penpotFile.data.pagesIndex ?? {}
|
||||||
const page = pages[options.pageId]
|
const page = pages[options.pageId]
|
||||||
const pageObjects = Object.values(page.objects)
|
const pageObjects = Object.values(page.objects)
|
||||||
|
@ -44,14 +44,13 @@ export const adaptPageComponentsToCssClassDefinitions = (
|
||||||
.filter(isComponent)
|
.filter(isComponent)
|
||||||
.map((object) => getObjectShapesFromPage(object, page))
|
.map((object) => getObjectShapesFromPage(object, page))
|
||||||
|
|
||||||
const cssClassDefinitions = []
|
const cssClassDefinitions: CSSClassDefinition[] = []
|
||||||
for (const component of components) {
|
for (const component of components) {
|
||||||
for (const objectId in component.objects) {
|
for (const objectId in component.objects) {
|
||||||
const object = component.objects[objectId]
|
const object = component.objects[objectId]
|
||||||
if (object.type === 'text') {
|
if (object.type === 'text') {
|
||||||
const cssProps = getTextObjectCssProps(object)
|
const cssProps = getTextObjectCssProps(object)
|
||||||
cssClassDefinitions.push({
|
cssClassDefinitions.push({
|
||||||
scope: page.name,
|
|
||||||
name: object.name,
|
name: object.name,
|
||||||
cssProps,
|
cssProps,
|
||||||
})
|
})
|
||||||
|
@ -59,5 +58,10 @@ export const adaptPageComponentsToCssClassDefinitions = (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return cssClassDefinitions
|
return {
|
||||||
|
scope: page.name,
|
||||||
|
pageComponents: cssClassDefinitions,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default adaptPageComponentsToCssClassDefinitions
|
79
packages/core/src/lib/api/adapters/typographies.ts
Normal file
79
packages/core/src/lib/api/adapters/typographies.ts
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
import { CSSClassDefinition, FontsSummary, TypographyAssets } from '../../types'
|
||||||
|
|
||||||
|
import { PenpotApiTypography, CssTextProperty, PenpotApiFile } from '../types'
|
||||||
|
|
||||||
|
const mapTypographyAssetCssProps = (
|
||||||
|
typography: PenpotApiTypography,
|
||||||
|
): Record<CssTextProperty, string> => {
|
||||||
|
return {
|
||||||
|
lineHeight: typography.lineHeight,
|
||||||
|
fontStyle: typography.fontStyle,
|
||||||
|
textTransform: typography.textTransform,
|
||||||
|
fontWeight: typography.fontWeight,
|
||||||
|
fontSize: `${typography.fontSize}px`,
|
||||||
|
letterSpacing: `${typography.letterSpacing}px`,
|
||||||
|
fontFamily: `"${typography.fontFamily}"`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const adaptTypographiesToCssClassDefinitions = (
|
||||||
|
typographies: PenpotApiTypography[],
|
||||||
|
): CSSClassDefinition[] => {
|
||||||
|
const cssClassDefinitions = typographies.map<CSSClassDefinition>(
|
||||||
|
(typography) => {
|
||||||
|
const cssProps = mapTypographyAssetCssProps(typography)
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: typography.name,
|
||||||
|
cssProps,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return cssClassDefinitions
|
||||||
|
}
|
||||||
|
|
||||||
|
const summarizeTypographies = (
|
||||||
|
typographies: PenpotApiTypography[],
|
||||||
|
): FontsSummary => {
|
||||||
|
const separator = '|>'
|
||||||
|
const dedupedFontsKeys = Array.from(
|
||||||
|
typographies.reduce((set, typography) => {
|
||||||
|
const { fontId, fontFamily, fontWeight } = typography
|
||||||
|
const typographyKey = [fontId, fontFamily, fontWeight].join(separator)
|
||||||
|
|
||||||
|
set.add(typographyKey)
|
||||||
|
return set
|
||||||
|
}, new Set<string>()),
|
||||||
|
)
|
||||||
|
|
||||||
|
const fontsSummary = dedupedFontsKeys.reduce<FontsSummary>(
|
||||||
|
(summary, typographyKey) => {
|
||||||
|
const [fontId, fontFamily, fontWeight] = typographyKey.split(separator)
|
||||||
|
const fontSource = fontId.startsWith('gfont-')
|
||||||
|
? 'googleFonts'
|
||||||
|
: 'userCustomFonts'
|
||||||
|
|
||||||
|
summary[fontSource][fontFamily] ??= []
|
||||||
|
summary[fontSource][fontFamily].push(fontWeight)
|
||||||
|
|
||||||
|
return summary
|
||||||
|
},
|
||||||
|
{ googleFonts: {}, userCustomFonts: {} },
|
||||||
|
)
|
||||||
|
|
||||||
|
return fontsSummary
|
||||||
|
}
|
||||||
|
|
||||||
|
const adaptTypographies = (penpotFile: PenpotApiFile): TypographyAssets => {
|
||||||
|
const fileName = penpotFile.name
|
||||||
|
const typographies = Object.values(penpotFile.data.typographies ?? {})
|
||||||
|
|
||||||
|
return {
|
||||||
|
scope: fileName,
|
||||||
|
typographies: adaptTypographiesToCssClassDefinitions(typographies),
|
||||||
|
typographiesSummary: summarizeTypographies(typographies),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default adaptTypographies
|
|
@ -1,2 +1,6 @@
|
||||||
|
export { default as adaptColors } from './adapters/colors'
|
||||||
|
export { default as adaptTypographies } from './adapters/typographies'
|
||||||
|
export { default as adaptPageComponents } from './adapters/pageComponents'
|
||||||
|
|
||||||
export * from './types'
|
export * from './types'
|
||||||
export { Penpot as default } from './penpot'
|
export { Penpot as default } from './penpot'
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
|
|
||||||
import { adaptTypographiesToCssClassDefinitions } from './adapters/inbound/typographiesToCssClasses'
|
import {
|
||||||
import { summarizeTypographies } from './adapters/inbound/typographiesToFontSummary'
|
default as PenpotApiClient,
|
||||||
import { adaptColorsToCssVariables } from './adapters/inbound/colorsToCssVariables'
|
adaptColors,
|
||||||
import { adaptPageComponentsToCssClassDefinitions } from './adapters/inbound/pageComponentsToCssClasses'
|
adaptTypographies,
|
||||||
|
adaptPageComponents,
|
||||||
import { Penpot } from './api/penpot'
|
} from './api'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
parseUserConfig,
|
parseUserConfig,
|
||||||
normalizePenpotExportUserConfig,
|
normalizePenpotExportUserConfig,
|
||||||
|
@ -20,17 +19,24 @@ import {
|
||||||
jsonOutputter,
|
jsonOutputter,
|
||||||
OutputterFunction,
|
OutputterFunction,
|
||||||
} from './outputters'
|
} from './outputters'
|
||||||
|
import {
|
||||||
|
scopeTypographiesClassNames,
|
||||||
|
scopePageComponentsClassNames,
|
||||||
|
TransformerFunction,
|
||||||
|
} from './transformers'
|
||||||
import { PenpotExportAssets } from './types'
|
import { PenpotExportAssets } from './types'
|
||||||
|
|
||||||
const processOutput = ({
|
function processOutput<T extends PenpotExportAssets>({
|
||||||
outputFormat = 'css',
|
outputFormat,
|
||||||
outputPath,
|
outputPath,
|
||||||
assets,
|
assets,
|
||||||
|
transform,
|
||||||
}: {
|
}: {
|
||||||
outputFormat: AssetConfig['format']
|
outputFormat: AssetConfig['format']
|
||||||
outputPath: string
|
outputPath: string
|
||||||
assets: PenpotExportAssets
|
assets: T
|
||||||
}) => {
|
transform?: TransformerFunction<T>
|
||||||
|
}) {
|
||||||
const outputter: OutputterFunction | null =
|
const outputter: OutputterFunction | null =
|
||||||
outputFormat === 'css'
|
outputFormat === 'css'
|
||||||
? cssOutputter
|
? cssOutputter
|
||||||
|
@ -43,7 +49,8 @@ const processOutput = ({
|
||||||
if (outputter === null)
|
if (outputter === null)
|
||||||
throw new PenpotExportInternalError('Unable to process output format')
|
throw new PenpotExportInternalError('Unable to process output format')
|
||||||
|
|
||||||
const textContents = outputter(assets)
|
const transformedAssets = transform !== undefined ? transform(assets) : assets
|
||||||
|
const textContents = outputter(transformedAssets)
|
||||||
return writeTextFile(outputPath, textContents)
|
return writeTextFile(outputPath, textContents)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,7 +61,7 @@ export default async function penpotExport(
|
||||||
const parsedUserConfig = parseUserConfig(userConfig)
|
const parsedUserConfig = parseUserConfig(userConfig)
|
||||||
|
|
||||||
const config = normalizePenpotExportUserConfig(parsedUserConfig)
|
const config = normalizePenpotExportUserConfig(parsedUserConfig)
|
||||||
const penpot = new Penpot({
|
const penpot = new PenpotApiClient({
|
||||||
baseUrl: config.instance,
|
baseUrl: config.instance,
|
||||||
accessToken: config.accessToken,
|
accessToken: config.accessToken,
|
||||||
})
|
})
|
||||||
|
@ -70,9 +77,7 @@ export default async function penpotExport(
|
||||||
processOutput({
|
processOutput({
|
||||||
outputFormat: colorsConfig.format,
|
outputFormat: colorsConfig.format,
|
||||||
outputPath: path.resolve(rootProjectPath, colorsConfig.output),
|
outputPath: path.resolve(rootProjectPath, colorsConfig.output),
|
||||||
assets: {
|
assets: adaptColors(penpotFile),
|
||||||
colors: adaptColorsToCssVariables(penpotFile),
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log('✅ Colors: %s', colorsConfig.output)
|
console.log('✅ Colors: %s', colorsConfig.output)
|
||||||
|
@ -82,10 +87,11 @@ export default async function penpotExport(
|
||||||
processOutput({
|
processOutput({
|
||||||
outputFormat: typographiesConfig.format,
|
outputFormat: typographiesConfig.format,
|
||||||
outputPath: path.resolve(rootProjectPath, typographiesConfig.output),
|
outputPath: path.resolve(rootProjectPath, typographiesConfig.output),
|
||||||
assets: {
|
assets: adaptTypographies(penpotFile),
|
||||||
typographies: adaptTypographiesToCssClassDefinitions(penpotFile),
|
transform:
|
||||||
typographiesSummary: summarizeTypographies(penpotFile),
|
typographiesConfig.format === 'css'
|
||||||
},
|
? scopeTypographiesClassNames
|
||||||
|
: undefined,
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log('✅ Typographies: %s', typographiesConfig.output)
|
console.log('✅ Typographies: %s', typographiesConfig.output)
|
||||||
|
@ -95,11 +101,13 @@ export default async function penpotExport(
|
||||||
processOutput({
|
processOutput({
|
||||||
outputFormat: pagesConfig.format,
|
outputFormat: pagesConfig.format,
|
||||||
outputPath: path.resolve(rootProjectPath, pagesConfig.output),
|
outputPath: path.resolve(rootProjectPath, pagesConfig.output),
|
||||||
assets: {
|
assets: adaptPageComponents(penpotFile, {
|
||||||
pageComponents: adaptPageComponentsToCssClassDefinitions(penpotFile, {
|
pageId: pagesConfig.pageId,
|
||||||
pageId: pagesConfig.pageId,
|
}),
|
||||||
}),
|
transform:
|
||||||
},
|
pagesConfig.format === 'css'
|
||||||
|
? scopePageComponentsClassNames
|
||||||
|
: undefined,
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log('✅ Page components: %s', pagesConfig.output)
|
console.log('✅ Page components: %s', pagesConfig.output)
|
||||||
|
|
|
@ -18,9 +18,8 @@ import {
|
||||||
} from './syntax'
|
} from './syntax'
|
||||||
|
|
||||||
const serializeCssClass = (cssClassDefinition: CSSClassDefinition): string => {
|
const serializeCssClass = (cssClassDefinition: CSSClassDefinition): string => {
|
||||||
const selector = textToCssClassSelector(
|
const selector = textToCssClassSelector(cssClassDefinition.name)
|
||||||
`${cssClassDefinition.scope}--${cssClassDefinition.name}`,
|
|
||||||
)
|
|
||||||
const cssValidProps = Object.keys(cssClassDefinition.cssProps).map(
|
const cssValidProps = Object.keys(cssClassDefinition.cssProps).map(
|
||||||
(key) => ` ${camelToKebab(key)}: ${cssClassDefinition.cssProps[key]};`,
|
(key) => ` ${camelToKebab(key)}: ${cssClassDefinition.cssProps[key]};`,
|
||||||
)
|
)
|
||||||
|
@ -56,6 +55,7 @@ const composeFileHeader = (fontsSummary: FontsSummary) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const serializeCss: OutputterFunction = ({
|
const serializeCss: OutputterFunction = ({
|
||||||
|
scope,
|
||||||
colors,
|
colors,
|
||||||
typographies,
|
typographies,
|
||||||
typographiesSummary,
|
typographiesSummary,
|
||||||
|
@ -64,7 +64,7 @@ const serializeCss: OutputterFunction = ({
|
||||||
if (colors) {
|
if (colors) {
|
||||||
return serializeCssCustomPropertiesRoot(colors)
|
return serializeCssCustomPropertiesRoot(colors)
|
||||||
} else if (typographies) {
|
} else if (typographies) {
|
||||||
const body = typographies.map(serializeCssClass).join('\n\n')
|
const body: string = typographies.map(serializeCssClass).join('\n\n')
|
||||||
const header: string = composeFileHeader(typographiesSummary)
|
const header: string = composeFileHeader(typographiesSummary)
|
||||||
|
|
||||||
return header + '\n\n' + body
|
return header + '\n\n' + body
|
||||||
|
|
|
@ -1,18 +1,66 @@
|
||||||
import { ColorAssets, PageComponentAssets, TypographyAssets } from '../types'
|
import {
|
||||||
|
CSSClassDefinition,
|
||||||
|
CSSCustomPropertyDefinition,
|
||||||
|
ColorAssets,
|
||||||
|
PageComponentAssets,
|
||||||
|
TypographyAssets,
|
||||||
|
} from '../types'
|
||||||
|
|
||||||
import { PenpotExportInvalidAssetsError } from './errors'
|
import { PenpotExportInvalidAssetsError } from './errors'
|
||||||
import { OutputterFunction } from './types'
|
import { OutputterFunction } from './types'
|
||||||
|
|
||||||
|
interface JSONCustomPropertyDefinition extends CSSCustomPropertyDefinition {
|
||||||
|
scope: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface JSONClassDefinition extends CSSClassDefinition {
|
||||||
|
scope: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const appendScopeToCustomProperties = (
|
||||||
|
cssCustomPropertiesDefinitions: CSSCustomPropertyDefinition[],
|
||||||
|
scope: string,
|
||||||
|
): JSONCustomPropertyDefinition[] => {
|
||||||
|
return cssCustomPropertiesDefinitions.map<JSONCustomPropertyDefinition>(
|
||||||
|
(customProperty) => {
|
||||||
|
return {
|
||||||
|
scope,
|
||||||
|
...customProperty,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const appendScopeToClasses = (
|
||||||
|
cssClassDefinition: CSSClassDefinition[],
|
||||||
|
scope: string,
|
||||||
|
): JSONClassDefinition[] => {
|
||||||
|
return cssClassDefinition.map<JSONClassDefinition>((classDefinition) => {
|
||||||
|
return {
|
||||||
|
scope,
|
||||||
|
...classDefinition,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const serializeJson: OutputterFunction = ({
|
const serializeJson: OutputterFunction = ({
|
||||||
|
scope,
|
||||||
colors,
|
colors,
|
||||||
typographies,
|
typographies,
|
||||||
pageComponents,
|
pageComponents,
|
||||||
}: ColorAssets | TypographyAssets | PageComponentAssets): string => {
|
}: ColorAssets | TypographyAssets | PageComponentAssets): string => {
|
||||||
const assets = colors ?? typographies ?? pageComponents ?? null
|
if (colors) {
|
||||||
|
const scopedColors = appendScopeToCustomProperties(colors, scope)
|
||||||
|
return JSON.stringify(scopedColors, null, 2)
|
||||||
|
} else if (typographies || pageComponents) {
|
||||||
|
const scopedAssets = appendScopeToClasses(
|
||||||
|
typographies ?? pageComponents,
|
||||||
|
scope,
|
||||||
|
)
|
||||||
|
return JSON.stringify(scopedAssets, null, 2)
|
||||||
|
}
|
||||||
|
|
||||||
if (assets === null) throw new PenpotExportInvalidAssetsError()
|
throw new PenpotExportInvalidAssetsError()
|
||||||
|
|
||||||
return JSON.stringify(assets, null, 2)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default serializeJson
|
export default serializeJson
|
||||||
|
|
2
packages/core/src/lib/transformers/index.ts
Normal file
2
packages/core/src/lib/transformers/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './scopeClassNames'
|
||||||
|
export type { TransformerFunction } from './types'
|
40
packages/core/src/lib/transformers/scopeClassNames.ts
Normal file
40
packages/core/src/lib/transformers/scopeClassNames.ts
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import {
|
||||||
|
CSSClassDefinition,
|
||||||
|
PageComponentAssets,
|
||||||
|
TypographyAssets,
|
||||||
|
} from '../types'
|
||||||
|
|
||||||
|
const scopeClassNames = (
|
||||||
|
cssClassDefinition: CSSClassDefinition[],
|
||||||
|
scope: string,
|
||||||
|
): CSSClassDefinition[] => {
|
||||||
|
return cssClassDefinition.map<CSSClassDefinition>(({ name, cssProps }) => {
|
||||||
|
return {
|
||||||
|
name: `${scope}--${name}`,
|
||||||
|
cssProps,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function scopeTypographiesClassNames(
|
||||||
|
assets: TypographyAssets,
|
||||||
|
): TypographyAssets {
|
||||||
|
const scopedTypographies = scopeClassNames(assets.typographies, assets.scope)
|
||||||
|
return {
|
||||||
|
...assets,
|
||||||
|
typographies: scopedTypographies,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function scopePageComponentsClassNames(
|
||||||
|
assets: PageComponentAssets,
|
||||||
|
): PageComponentAssets {
|
||||||
|
const scopedPageComponents = scopeClassNames(
|
||||||
|
assets.pageComponents,
|
||||||
|
assets.scope,
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
...assets,
|
||||||
|
pageComponents: scopedPageComponents,
|
||||||
|
}
|
||||||
|
}
|
5
packages/core/src/lib/transformers/types.ts
Normal file
5
packages/core/src/lib/transformers/types.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import { ColorAssets, PageComponentAssets, TypographyAssets } from '../types'
|
||||||
|
|
||||||
|
export type TransformerFunction<
|
||||||
|
T extends ColorAssets | TypographyAssets | PageComponentAssets,
|
||||||
|
> = (assets: T) => T
|
|
@ -1,11 +1,9 @@
|
||||||
export interface CSSClassDefinition {
|
export interface CSSClassDefinition {
|
||||||
scope: string
|
|
||||||
name: string
|
name: string
|
||||||
cssProps: Record<string, string>
|
cssProps: Record<string, string>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CSSCustomPropertyDefinition {
|
export interface CSSCustomPropertyDefinition {
|
||||||
scope: string
|
|
||||||
name: string
|
name: string
|
||||||
value: string
|
value: string
|
||||||
}
|
}
|
||||||
|
@ -20,6 +18,7 @@ export interface FontsSummary {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BaseAssets {
|
interface BaseAssets {
|
||||||
|
scope: string
|
||||||
colors?: never
|
colors?: never
|
||||||
typographies?: never
|
typographies?: never
|
||||||
typographiesSummary?: never
|
typographiesSummary?: never
|
||||||
|
|
Loading…
Reference in a new issue