Skip to content

Instantly share code, notes, and snippets.

@a-type
Last active March 16, 2023 20:04
Show Gist options
  • Save a-type/e8eaf3a9818667a5f466750ef0b49df9 to your computer and use it in GitHub Desktop.
Save a-type/e8eaf3a9818667a5f466750ef0b49df9 to your computer and use it in GitHub Desktop.
Figma icon script
const axios = require('axios');
const fs = require('fs-extra');
const path = require('path');
const prettier = require('prettier');
const { camelCase } = require('change-case');
const figmaClient = axios.create({
baseURL: 'https://api.figma.com/v1',
headers: {
'Content-Type': 'application/json',
'X-Figma-Token': process.env.FIGMA_TOKEN,
},
});
const FILE_ID = '<< GET FROM THE URL >>';
const PAGE = 'Page 1'; // change these to your structure
const FRAME = 'glyph';
const baseDir = path.join(__dirname, '../web/components/icons/generated');
// const nameMatch = /^Name=(.+?),/;
const nameMatch = /^(.*)$/; // at the moment icons are just named, no metadata
function getIconName(iconNode) {
return nameMatch.exec(iconNode.name)[1];
}
function renderIconDef(icon) {
return `<symbol id="icon-${icon.name}" viewBox="0 0 ${icon.width} ${icon.height}">${icon.svg}</symbol>`;
}
function stripSvg(svgString) {
return svgString.replaceAll(/<svg[^>]*>/g, '').replace(/<\/svg>/g, '');
}
// any svg with a fill of #484440 will be replaced with 'currentColor
// this allows for secondary colors to exist in the svg
function fillCurrentColor(svgString) {
return svgString.replaceAll(/fill="#484440"/g, `fill="currentColor"`);
}
function strokeCurrentColor(svgString) {
return svgString.replaceAll(/stroke="#484440"/g, `stroke="currentColor"`);
}
function reactifyAttributes(svgString) {
return svgString.replaceAll(
/(\w+?(?:-\w+?)+)=/g,
(match, p1) => `${camelCase(p1)}=`,
);
}
function processIconSvg(svgString) {
return reactifyAttributes(
fillCurrentColor(strokeCurrentColor(stripSvg(svgString))),
);
}
async function downloadImage(url) {
const response = await axios.get(url, { responseType: 'arrayBuffer' });
return Buffer.from(response.data, 'binary').toString('utf-8');
}
async function genIcons() {
const prettierConfig = await prettier.resolveConfig(__dirname);
prettierConfig.parser = 'babel-ts';
const figmaFile = await figmaClient.get(`/files/${FILE_ID}`);
const figmaPage = figmaFile.data.document.children.find(
(page) => page.name === PAGE,
);
const figmaFrame = figmaPage.children.find((frame) => frame.name === FRAME);
const iconIds = figmaFrame.children.map((icon) => icon.id);
const iconImagesResponse = await figmaClient.get(
`/images/${FILE_ID}?format=svg&ids=${iconIds.join(',')}`,
);
const iconImages = iconImagesResponse.data.images;
// match up images to icon metadata
const icons = await Promise.all(
figmaFrame.children.map(async (iconNode) => {
const iconName = getIconName(iconNode);
const iconImage = iconImages[iconNode.id];
return {
name: iconName,
width: iconNode.absoluteBoundingBox.width,
height: iconNode.absoluteBoundingBox.height,
svg: processIconSvg(await downloadImage(iconImage)),
};
}),
);
// compile icons into a spritesheet
const spritesheetSvg = `
<svg xmlns="http://www.w3.org/2000/svg" style={{ display: 'none' }} {...props}>
<defs>
${icons.map(renderIconDef).join('\n')}
</defs>
</svg>
`;
if (!fs.existsSync(baseDir)) {
fs.mkdirSync(baseDir);
}
fs.writeFileSync(
path.join(baseDir, 'IconSpritesheet.tsx'),
prettier.format(
`// WARNING: generated file! See 'yarn gen:icons'. Do not modify!
export const IconSpritesheet = (props: any) => (
${spritesheetSvg}
);
`,
prettierConfig,
),
);
fs.writeFileSync(
path.join(baseDir, 'iconNames.ts'),
prettier.format(
`// WARNING: generated file! See 'yarn gen:icons'. Do not modify!
export const iconNames = [${icons
.map((i) => `'${i.name}'`)
.join(', ')}] as const;
export type IconName = typeof iconNames[number];
`,
prettierConfig,
),
);
}
genIcons();
import { ComponentProps, forwardRef, SVGAttributes } from 'react';
import { styled } from 'stitches.config';
import { IconName } from './generated/iconNames';
export interface IconProps extends ComponentProps<typeof StyledSvg> {
name: IconName;
}
export const Icon = forwardRef<SVGSVGElement, IconProps>(function Icon(
{ name, ...rest },
ref,
) {
return (
<StyledSvg ref={ref} width={24} height={24} {...rest}>
<use xlinkHref={`#icon-${name}`} />
</StyledSvg>
);
});
const StyledSvg = styled('svg', {
flexShrink: 0,
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment