Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: fragments generator #41

Merged
merged 48 commits into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
f976052
feat: add base fragments-cli
Amiditin Dec 2, 2024
87a57b7
feat: add base fragments-generator
Amiditin Dec 2, 2024
a7124f6
feat: add load-node method to figma-file-loader
Amiditin Dec 2, 2024
df80c7d
feat: add theme option to generate fragments command
Amiditin Dec 4, 2024
fb2d43d
feat: add process-file theme path
Amiditin Dec 4, 2024
82f386c
feat: add get-value-key-from-theme to fragments-generator strategy
Amiditin Dec 4, 2024
990b377
feat: add component wrapper fro fragments
Amiditin Dec 5, 2024
1f6b36b
feat: add create wrapper element for array text elements
Amiditin Dec 5, 2024
9d1519c
feat: add figma file-utils with process and write file utils
Amiditin Dec 6, 2024
3850bf3
refactor: parse node-id in load-node method
Amiditin Dec 6, 2024
6e4cc32
feat: add create dynamic fragments tree to fragments generator
Amiditin Dec 6, 2024
388370a
refactor: destructure create-fragment strategy
Amiditin Dec 10, 2024
a976f4b
feat: add dynamic imports when creating component
Amiditin Dec 10, 2024
96c8282
feat: add theme-mapping strategy
Amiditin Dec 10, 2024
fac044a
feat: add create-text strategy
Amiditin Dec 10, 2024
99c446f
feat: add create-box strategy
Amiditin Dec 10, 2024
9734654
refactor: move getting attributes to theme-mapping strategy
Amiditin Dec 11, 2024
935e763
feat: add to-px-string util
Amiditin Dec 11, 2024
b621cb3
refactor: move getting box attributes to theme-mapping strategy
Amiditin Dec 11, 2024
0572a6f
feat: add get-border-radius to theme-mapping strategy
Amiditin Dec 11, 2024
b5aee13
feat: add get-border to theme-mapping strategy
Amiditin Dec 11, 2024
d10f334
feat: add get-shadow to theme-mapping strategy
Amiditin Dec 11, 2024
6817d5e
feat: add background attribute to create box element
Amiditin Dec 11, 2024
dcf960f
feat: add width and height attributes to create-box-element
Amiditin Dec 13, 2024
daf6397
feat: add id attribute to formatted-message element
Amiditin Dec 13, 2024
0073703
feat: add text-align attribute to create-text-element
Amiditin Dec 13, 2024
e72805d
feat: add create-button strategy
Amiditin Dec 13, 2024
1491ea8
feat: add base create-input strategy
Amiditin Dec 13, 2024
2d86b02
feat: add create-input strategy to create-fragment
Amiditin Dec 16, 2024
93af260
feat: add theme-mapping strategy test
Amiditin Dec 16, 2024
065e7c6
feat: add create-box strategy test
Amiditin Dec 16, 2024
dcaf889
feat: add create-text strategy test
Amiditin Dec 16, 2024
a34be58
feat: add create-button strategy test
Amiditin Dec 16, 2024
5f2ba76
feat: add create-input strategy test
Amiditin Dec 16, 2024
41d7371
feat: add is-instance to figma utils
Amiditin Dec 16, 2024
50a9227
feat: add question to get figma access token to figma-cli
Amiditin Dec 16, 2024
db09f90
fix: eslint validation
Amiditin Dec 16, 2024
ec97298
chore: add yarn sdks
Amiditin Dec 16, 2024
86f7e04
chore: add exports to package json files
Amiditin Dec 16, 2024
a427d7e
chore: remove generate-fragments script
Amiditin Dec 16, 2024
3f575ef
refactor: use import from node
Amiditin Dec 16, 2024
c46e1a2
refactor: use pino logger instead of npmlog
Amiditin Dec 16, 2024
e47ec73
feat: add name option to generate fragment
Amiditin Dec 16, 2024
dbe6d48
refactor: change fragment generator strategies folder structure and f…
Amiditin Dec 16, 2024
5c37c9a
docs: add fragments-generator to readme
Amiditin Dec 16, 2024
10dd6c5
chore: remove exports from packages
Amiditin Dec 18, 2024
f14e36b
feat: add display-name prop to create-component result
Amiditin Dec 18, 2024
6acf4fc
feat: add options to pino logger
Amiditin Dec 18, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,7 @@ package.tgz

# Generated files
generated/

# VS Code
.yarn/sdks
Nelfimov marked this conversation as resolved.
Show resolved Hide resolved
.vscode
196 changes: 174 additions & 22 deletions .pnp.cjs

Large diffs are not rendered by default.

42 changes: 42 additions & 0 deletions fragments/fragments-cli/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"name": "@atls/figma-fragments-cli",
"version": "0.0.1",
"license": "BSD-3-Clause",
"type": "module",
"main": "src/index.ts",
Nelfimov marked this conversation as resolved.
Show resolved Hide resolved
"bin": {
"generate-fragments": "dist/index.js"
},
"files": [
"dist"
],
"scripts": {
"build": "yarn library build",
"generate-fragments": "ts-node-esm src/index.ts",
"generate-fragments-new": "node --import @swc-node/register/esm-register src/index.ts",
Nelfimov marked this conversation as resolved.
Show resolved Hide resolved
"prepack": "yarn run build",
"postpack": "rm -rf dist"
},
"dependencies": {
"@atls/figma-file-loader": "workspace:*",
"@atls/figma-file-utils": "workspace:*",
"@atls/figma-fragments-generator": "workspace:*",
"commander": "12.1.0",
"figma-js": "1.16.1-0",
"npmlog": "7.0.1"
},
"devDependencies": {
"@swc-node/register": "1.9.0",
"@swc/core": "1.6.1",
"@types/node": "18.19.34",
"@types/npmlog": "7.0.0",
"@yarnpkg/builder": "4.1.1",
"ts-node": "10.9.2",
"typescript": "5.2.2"
},
"publishConfig": {
"access": "public",
"main": "dist/index.js",
"typings": "dist/index.d.ts"
}
}
44 changes: 44 additions & 0 deletions fragments/fragments-cli/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { createInterface } from 'node:readline'

import logger from 'npmlog'
Nelfimov marked this conversation as resolved.
Show resolved Hide resolved
import { program } from 'commander'

import { run } from './run.js'

logger.heading = 'figma-fragments'

program
.option('-o, --output [output]', 'Output dir')
.option('-t, --theme <theme>', 'Path to theme file')
.option('-v, --verbose', 'Verbose output')
.option('-n, --node-id <nodeId>', 'Node id for generating')
.arguments('<fileId>')
.parse(process.argv)

const fileId = program.args.at(0)
const options = program.opts()

if (options.verbose) {
logger.level = 'verbose'
}

if (!fileId) {
logger.error('fileId', 'Figma file id required.')
} else {
const readline = createInterface({
input: process.stdin,
output: process.stdout,
})

readline.question(`Enter your Figma access token:\n`, (id) => {
if (!id || id === '') throw Error('ID must not be empty')
Nelfimov marked this conversation as resolved.
Show resolved Hide resolved
// eslint-disable-next-line dot-notation
process.env['FIGMA_TOKEN'] = id

readline.close()

run(fileId, options.nodeId, options.output, options.theme)
.then(() => logger.info('info', 'Fragments successful generated'))
.catch((error) => logger.error('error', error))
})
}
34 changes: 34 additions & 0 deletions fragments/fragments-cli/src/run.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import assert from 'node:assert'
Nelfimov marked this conversation as resolved.
Show resolved Hide resolved

import { join } from 'path'
Nelfimov marked this conversation as resolved.
Show resolved Hide resolved

import { FigmaFileLoader } from '@atls/figma-file-loader'
import { FigmaThemeFragmentsGenerator } from '@atls/figma-fragments-generator'
import { processFile } from '@atls/figma-file-utils'
import { writeFile } from '@atls/figma-file-utils'

export const run = async (
fileId: string,
nodeId: string,
output: string,
themeFilePath: string
) => {
const absoluteThemeFilePath = join(process.cwd(), themeFilePath)
const exports = processFile(absoluteThemeFilePath)

const theme = Object.values(exports)?.[0] as Record<string, Record<string, string>>

assert.ok(
theme,
`Could not process the theme with path ${absoluteThemeFilePath}. Please try again`
)

const loader = new FigmaFileLoader()
const generator = new FigmaThemeFragmentsGenerator()

const response = await loader.loadNode(fileId, nodeId)

const component = generator.generate(response, theme)

await writeFile(output, 'fragments.tsx', component)
}
33 changes: 33 additions & 0 deletions fragments/fragments-generator/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "@atls/figma-fragments-generator",
"version": "0.0.1",
"license": "BSD-3-Clause",
"type": "module",
"main": "src/index.ts",
Nelfimov marked this conversation as resolved.
Show resolved Hide resolved
"files": [
"dist"
],
"scripts": {
"build": "yarn library build",
"prepack": "yarn run build",
"postpack": "rm -rf dist"
},
"dependencies": {
"@atls/figma-utils": "workspace:*",
"pretty-format": "29.7.0",
"react": "18.3.1"
},
"devDependencies": {
"@types/node": "18.19.34",
"@types/react": "18.3.12",
"figma-js": "1.16.1-0"
},
"peerDependencies": {
"figma-js": "*"
},
"publishConfig": {
"access": "public",
"main": "dist/index.js",
"typings": "dist/index.d.ts"
}
}
24 changes: 24 additions & 0 deletions fragments/fragments-generator/src/figma-fragments.generator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { FileNodesResponse } from 'figma-js'

import { CreateFragmentStrategy } from './strategy/index.js'

export class FigmaThemeFragmentsGenerator {
readonly name = 'fragments'

createComponent(fragment: string, imports?: Array<string>): string {
return `
import React from 'react'
import { memo } from 'react'
${imports?.join('\n')}

export const GeneratedFragment = memo(() => (${fragment}))`
Nelfimov marked this conversation as resolved.
Show resolved Hide resolved
}

generate(response: FileNodesResponse, theme: Record<string, Record<string, string>>): string {
const strategy = new CreateFragmentStrategy(theme)

const { fragment, imports } = strategy.execute(response.nodes)

return this.createComponent(fragment, imports)
}
}
1 change: 1 addition & 0 deletions fragments/fragments-generator/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './figma-fragments.generator.js'
43 changes: 43 additions & 0 deletions fragments/fragments-generator/src/strategy/create-box.strategy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Frame } from 'figma-js'
import { createElement } from 'react'

import { ThemeMappingStrategy } from './theme-mapping.strategy.js'

export class CreateBoxStrategy extends ThemeMappingStrategy {
getImports() {
return [`import { Box } from '@ui/layout'`]
}

createElement(node: Frame) {
const {
layoutMode,
itemSpacing,
counterAxisAlignItems,
primaryAxisAlignItems,
paddingBottom,
paddingLeft,
paddingRight,
paddingTop,
cornerRadius,
strokes,
strokeWeight,
background,
effects,
absoluteBoundingBox,
} = node

return createElement('Box', {
width: absoluteBoundingBox.width ? `${absoluteBoundingBox.width}px` : undefined,
height: absoluteBoundingBox.height ? `${absoluteBoundingBox.height}px` : undefined,
flexDirection: this.getFlexDirection(layoutMode),
justifyContent: this.getJustifyContent(primaryAxisAlignItems),
alignItems: this.getAlignItems(counterAxisAlignItems),
background: this.getColor(background),
gap: this.getGap(itemSpacing),
border: this.getBorder(strokes, strokeWeight),
borderRadius: this.getBorderRadius(cornerRadius),
boxShadow: this.getShadow(effects),
...this.getPaddings({ paddingBottom, paddingLeft, paddingRight, paddingTop }),
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Instance } from 'figma-js'
import { Fragment } from 'react'
import { createElement } from 'react'

import { ComponentProperties } from './strategy.interfaces.js'

export class CreateButtonStrategy {
getImports() {
return [`import { Button } from '@ui/button'`]
}

createElement(node: Instance) {
if ('componentProperties' in node) {
const style = (node.componentProperties as ComponentProperties).Style

return createElement('Button', { variant: style?.value.toString().toLocaleLowerCase() })
}

return createElement(Fragment)
}
}
Loading
Loading