-
Notifications
You must be signed in to change notification settings - Fork 143
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: figma icon importer script (#2163)
- Loading branch information
1 parent
c481e15
commit 64108d7
Showing
1,008 changed files
with
17,257 additions
and
6,374 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@razorpay/blade": minor | ||
--- | ||
|
||
feat(blade): add new icons and add figma icon importer script |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { default } from './{{name}}Icon'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import {{name}}Icon from '.'; | ||
import renderWithTheme from '~utils/testing/renderWithTheme.native'; | ||
|
||
describe('<{{name}}Icon />', () => { | ||
it('should render {{name}}Icon', () => { | ||
const renderTree = renderWithTheme( | ||
<{{name}}Icon color="feedback.icon.neutral.intense" size="large" />, | ||
).toJSON(); | ||
expect(renderTree).toMatchSnapshot(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { IMPORTED_SVG_COMPONENTS } from '../_Svg'; | ||
import type { IconComponent } from '..'; | ||
import useIconProps from '../useIconProps'; | ||
|
||
const {{name}}Icon: IconComponent = ({ size, color, ...styledProps }) => { | ||
const { height, width, iconColor } = useIconProps({ size, color }); | ||
|
||
return ( | ||
REPLACE_SVG | ||
); | ||
}; | ||
|
||
export default {{name}}Icon; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import {{name}}Icon from './'; | ||
import renderWithTheme from '~utils/testing/renderWithTheme.web'; | ||
|
||
describe('<{{name}}Icon />', () => { | ||
it('should render {{name}}Icon', () => { | ||
const { container } = renderWithTheme( | ||
<{{name}}Icon color="feedback.icon.neutral.intense" size="large" />, | ||
); | ||
expect(container).toMatchSnapshot(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
// This file is auto generated | ||
// Modify at blade/plop/icon/iconMap.ts.hbs | ||
{{{iconImports}}} | ||
import type { IconComponent } from './'; | ||
|
||
const iconMap: Record<string, IconComponent> = { | ||
{{{iconMap}}} | ||
}; | ||
|
||
export default iconMap; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
// This file is auto generated | ||
// Modify at blade/plop/icon/iconReexports.ts.hbs | ||
export * from './types'; | ||
{{{iconReexports}}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
/* eslint-disable prefer-const */ | ||
/* eslint-disable import/no-extraneous-dependencies */ | ||
const fs = require('fs'); | ||
const { stringify, parseSync } = require('svgson'); | ||
const { startCase } = require('lodash'); | ||
const prettier = require('prettier'); | ||
|
||
const transformSvgNode = (node, components = new Set()) => { | ||
if (node.name === 'svg') { | ||
node.attributes = { | ||
styledProps: '', | ||
width: '{width}', | ||
height: '{height}', | ||
viewBox: '0 0 24 24', | ||
fill: 'none', | ||
}; | ||
} | ||
|
||
// title case component names | ||
node.name = startCase(node.name).replace(/\s/g, ''); | ||
// gather imported components | ||
components.add(node.name); | ||
|
||
// update iconColor in stroke & fill | ||
Object.keys(node.attributes).forEach((attribute) => { | ||
if (['stroke', 'fill'].includes(attribute) && node.attributes[attribute] !== 'none') { | ||
node.attributes[attribute] = `{iconColor}`; | ||
} | ||
}); | ||
|
||
// recursively go to child | ||
if (node.children) node.children.forEach((child) => transformSvgNode(child, components)); | ||
|
||
return { node, components }; | ||
}; | ||
|
||
/** | ||
* @param {import("plop").NodePlopAPI} plop | ||
*/ | ||
module.exports = (plop) => { | ||
plop.setGenerator('generate-reexports', { | ||
description: 'Generates re-exports for all icon components', | ||
prompts: [], | ||
actions: () => { | ||
const actions = []; | ||
|
||
// get all icon components | ||
const iconsFolder = './src/components/Icons'; | ||
const icons = fs.readdirSync(iconsFolder); | ||
const allIcons = icons | ||
.map((icon) => { | ||
if (!fs.statSync(`${iconsFolder}/${icon}`).isDirectory()) return null; | ||
const files = fs.readdirSync(`${iconsFolder}/${icon}`); | ||
if (files.length === 0) return null; | ||
if (icon.endsWith('Icon')) { | ||
return icon; | ||
} | ||
return null; | ||
}) | ||
.filter(Boolean) | ||
.sort(); | ||
|
||
const imports = allIcons | ||
.map((icon) => { | ||
return `import ${icon}Component from './${icon}';`; | ||
}) | ||
.join('\n'); | ||
|
||
const map = allIcons | ||
.map((icon) => { | ||
return ` ${icon}: ${icon}Component,`; | ||
}) | ||
.join('\n'); | ||
|
||
const reexports = allIcons | ||
.map((icon) => { | ||
return `export { default as ${icon} } from './${icon}';`; | ||
}) | ||
.join('\n'); | ||
|
||
actions.push({ | ||
type: 'add', | ||
path: './src/components/Icons/iconMap.ts', | ||
templateFile: 'plop/iconMap.ts.hbs', | ||
data: { | ||
iconMap: map, | ||
iconImports: imports, | ||
}, | ||
force: true, | ||
}); | ||
|
||
actions.push({ | ||
type: 'add', | ||
path: './src/components/Icons/index.ts', | ||
templateFile: 'plop/iconReexports.ts.hbs', | ||
data: { | ||
iconReexports: reexports, | ||
}, | ||
force: true, | ||
}); | ||
|
||
return actions; | ||
}, | ||
}); | ||
|
||
plop.setGenerator('generate-icons', { | ||
description: 'Generates a icon component', | ||
prompts: [ | ||
{ | ||
type: 'input', | ||
name: 'iconName', | ||
message: 'Enter icon name:', | ||
}, | ||
{ | ||
type: 'input', | ||
name: 'svgContents', | ||
message: 'Paste svg contents:', | ||
validate: (value) => !!value, | ||
}, | ||
], | ||
actions: (answers) => { | ||
const actions = []; | ||
|
||
let { iconName, svgContents } = answers; | ||
|
||
let name = startCase(iconName).trim().replace(/\s/g, ''); | ||
// populate the template code | ||
actions.push({ | ||
type: 'addMany', | ||
templateFiles: 'plop/icon/**', | ||
destination: `./src/components/Icons/{{name}}Icon`, | ||
base: 'plop/icon', | ||
data: { name }, | ||
abortOnFail: true, | ||
force: true, | ||
}); | ||
|
||
// modify svg -> jsx | ||
actions.push({ | ||
type: 'modify', | ||
path: `./src/components/Icons/{{name}}Icon/{{name}}Icon.tsx`, | ||
data: { name }, | ||
transform(fileContents) { | ||
let final = fileContents; | ||
let importedComponents = []; | ||
|
||
// parse svg contents to ast and modify the ast with transformSvgNode | ||
const svgAst = parseSync(svgContents, { | ||
camelcase: true, | ||
// transform each node and gather imported components | ||
transformNode: (transformNode) => { | ||
const { node, components } = transformSvgNode(transformNode); | ||
importedComponents = [...components]; | ||
return node; | ||
}, | ||
}); | ||
|
||
// stringify svg ast | ||
const svgString = stringify(svgAst, { | ||
selfClose: true, | ||
// transform jsx props | ||
transformAttr: (key, value) => { | ||
if (key === 'styledProps') { | ||
return '{...styledProps}'; | ||
} | ||
if (value.startsWith('{')) { | ||
return `${key}=${value}`; | ||
} | ||
return `${key}="${value}"`; | ||
}, | ||
}); | ||
|
||
// replace template svg placeholder | ||
final = final.replace(/REPLACE_SVG/g, svgString); | ||
// update imported svg components | ||
final = final.replace(/IMPORTED_SVG_COMPONENTS/g, importedComponents.join(', ')); | ||
|
||
return prettier.format(final, { | ||
parser: 'typescript', | ||
singleQuote: true, | ||
}); | ||
}, | ||
}); | ||
|
||
return actions; | ||
}, | ||
}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
/* eslint-disable import/no-extraneous-dependencies */ | ||
import fs from 'node:fs'; | ||
import nodePlop from 'node-plop'; | ||
|
||
const generateIcons = async () => { | ||
const plop = await nodePlop('./plopfile.js'); | ||
const iconGenerator = plop.getGenerator('generate-icons'); | ||
const indexGenerator = plop.getGenerator('generate-reexports'); | ||
const iconsJsonFile = JSON.parse(fs.readFileSync('./scripts/icons.json', 'utf-8')); | ||
|
||
const processedIcons = iconsJsonFile.map((icon) => { | ||
const name = Object.keys(icon)[0]; | ||
const svg = icon[name]; | ||
return iconGenerator.runActions({ iconName: name, svgContents: svg }).then((results) => { | ||
console.log(results); | ||
}); | ||
}); | ||
|
||
Promise.all(processedIcons); | ||
// wait 1 second | ||
await new Promise((resolve) => setTimeout(resolve, 1000)); | ||
// generate re-exports | ||
await indexGenerator.runActions({}).then((results) => { | ||
console.log(results); | ||
}); | ||
}; | ||
|
||
generateIcons(); |
Oops, something went wrong.