Кажете дека имате збир на икони во Figma и проект во React/Vue/што и да било друго. Не е изненадување, ќе треба да ги користите тие икони. Како можеме да ги увеземе?
Ако сте доволно тврдоглави, можете да ги преземете еден по еден и да ги додавате како средства. Но, тоа е тажен начин. Нема слично.
Ајде да престанеме да бидеме тажни и наместо тоа да бидеме прекрасни. Еве што ќе ни треба:
*не е обезбедено дополнително време*
Ќе ни треба токен Figma. Мислам дека не е неопходно да се објасни како да се направи ова, затоа наместо тоа, еве дел за помош: https://help.figma.com/hc/en-us/articles/8085703771159-Manage-personal-access-tokens .
Стандардните поставки се доволно добри: потребен ни е пристап само за читање до содржината на датотеката.
Како пример ќе ги користам иконите за дизајн на материјали (Заедница) .
Ќе ни треба клуч за датотека и идентификација на главниот јазол:
Сега кога имаме доволно предуслови, да започнеме со кодирање. Ми се допаѓаат моите скрипти одвоени од главниот код на проектот, па затоа ќе создадам папка scripts
во root и fetch-icons.ts
во неа. Нема потреба да се прави во TypeScript, но тоа го прави овој напис некако фенси, па јас ќе одам со него.
Сепак, ова ќе бара уште неколку зависности:
yarn add -D tsx dotenv axios @figma/rest-api-spec
dotenv
е тука за да ги користи променливите .env
, tsx
— за да ја изврши TS скриптата (фенси, се сеќавате?), @figma/rest-api-spec
е за безбедност на типот, а axios
е само затоа што сум навикнат на тоа. Потребно е само dotenv
(освен ако не сте во ред со додавањето на клучот API на кодот), а јас ќе ја додадам „чистата“ верзија на крајот. Засега, ќе го имаме ова поставување, а еве со што започнуваме:
import * as dotenv from "dotenv"; dotenv.config(); const PERSONAL_ACCESS_TOKEN = String( process.env.VITE_FIGMA_PERSONAL_ACCESS_TOKEN ); // I've added my Figma token to the .env file, and so should you const FIGMA_API_URL = "https://api.figma.com/v1"; const FILE_KEY = "v50KJO82W9bBJUppE8intT"; // this one is from the URL const NODE_ID = "2402-2207"; // node-id query param, also from the URL
Најпрво, ќе ги добиеме метаподатоците за главната рамка:
import { GetFileNodesResponse, GetImagesResponse, HasChildrenTrait, } from "@figma/rest-api-spec"; // ... const fetchFrameData = async ( nodeId: string ): Promise<HasChildrenTrait> => { const { data } = await axios.get<GetFileNodesResponse>( `${FIGMA_API_URL}/files/${FILE_KEY}/nodes?ids=${nodeId}`, getConfig() ); return data.nodes[nodeId].document as HasChildrenTrait; };
Ќе ги врати информациите за своите деца. Тоа може да бидат слики (нивниот тип ќе биде INSTANCE
) или други рамки што ќе ги анализираме рекурзивно:
const getImageNodes = ( frameData: HasChildrenTrait ): Record<string, string> => { const nodes: Record<string, string> = {}; for (const image of frameData.children.filter( (node) => node.type === 'INSTANCE' )) { // normalizeName simply converts 'check-box' to 'CheckBox' const name = normalizeName(image.name); const id = image.id; nodes[id] = name; } for (const frame of frameData.children.filter( (node) => node.type === 'FRAME' )) { Object.assign(nodes, getImageNodes(frame)); } return nodes; };
Конечно, кога ќе имаме куп ID на слики, ќе преземеме URL-адреси на датотеки SVG:
const fetchImageUrls = async ( nodeIds: string[] ): Promise<GetImagesResponse> => { const { data } = await axios.get<GetImagesResponse>( `${FIGMA_API_URL}/images/${FILE_KEY}?ids=${nodeIds.join(",")}&format=svg`, getConfig() ); return data; };
И ќе завршиме со низа од такви објекти:
{ name: 'CheckBox', url: 'https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/7dd5bc31-4f26-4701-b155-7bf7dea45824' }
Како што веќе погодивте, ќе ги преземеме сите, но тоа не е најинтересниот дел.
Секој пат кога ќе преземеме SVG, добиваме нешто слично:
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M17 6H7C3.69 6 1 8.69 [...] 9 17 9Z" fill="black" /> </svg>
Ќе ги отстраниме ознаките svg
бидејќи тие се идентични за секоја икона (претпоставувам дека се со иста големина заради едноставност). Но, она што е поинтересно е што ќе го замениме целото fill=”black”
со место за место во буквален шаблон, и како резултат на тоа, ќе имаме таква функција:
export const IconToggleOn = (color: string) => `<path d="M17 6H7C3.69 [...] 9 17 9Z" fill="${color}"/>`;
Еве како го добиваме:
const cleanupSvg = (data: string): string => { const svgTags = /<\/?svg.*?>/g; const colorFills = /fill=["'].+?["']/g; return data .replace(svgTags, "") .replace(colorFills, 'fill="${color}"') .replace(/\n/g, ""); };
Очигледно, нема да треба да ги замените боите ако треба да внесете некои логоа, но правењето поинаква логика за нив ќе биде вашата домашна задача.
Добро, еве го нашиот план:
Нема да ја рецитирам целата датотека - можете да ја проверите на GitHub . Ајде само да го погледнеме самото преземање:
const FILE_BATCH_SIZE = 10; const fileContent: string[] = []; const getPromise = async (item: IconData) => { try { const { data } = await axios.get<string>( item.url, getConfig("image/svg+xml") // returns necessary options and headers ); return { name: item.name, data }; } catch (err) { console.error(err); } }; for (let i = 0; i < iconsData.length; i += FILE_BATCH_SIZE) { const batch = await Promise.allSettled( iconsData.slice(i, i + FILE_BATCH_SIZE).map(getPromise) ); for (const icon of batch) { if (icon?.status === "fulfilled" && icon.value) { const iconName = `Icon${icon.value.name}`; const svgData = cleanupSvg(icon.value.data); fileContent.push( `export const ${iconName} = (color: string) =>\n \`${svgData}\`;\n` ); } } }
Партијата е важна овде бидејќи во спротивно, Figma нема да ви се допадне ако одлучите да преземете неколку илјади датотеки паралелно.
Ако треба да добиете икона по името што го добивате од задниот дел, можете да креирате и речник:
const iconArray: string[] = []; // ... for (const icon of batch) { if (icon?.status === "fulfilled" && icon.value) { const iconName = `Icon${icon.value.name}`; const svgData = cleanupSvg(icon.value.data); fileContent.push( `export const ${iconName} = (color: string) =>\n \`${svgData}\`;\n` ); iconArray.push(iconName); // <-- we added this } } // ... when all files are processed fileContent.push( `\r\nexport const iconDictionary: Record<string, (color: string) => string> = {\r\n ${iconArray.join( ",\r\n " )}\r\n};\r\n` );
Овој пристап е полош бидејќи целиот сет ќе биде додаден во пакетот дури и ако користите само една икона преку речник, но понекогаш мора да направите нешто непријатно. Исто така, сите \r\n
и празни места се тука за да го израдуваат мојот ESLint: можеби ќе треба да ја прилагодите нивната сума.
Еве го последниот чекор; ајде да направиме нова датотека:
import path from "path"; import { fileURLToPath } from "url"; // ... const PATH_TO_ICONS = "../src/assets/icons"; const currentPath = fileURLToPath(import.meta.url); const currentDirectory = path.dirname(currentPath); const fileName = "index.ts"; const filePath = path.join( path.resolve(currentDirectory, PATH_TO_ICONS), fileName ); // ... fs.writeFileSync(filePath, fileContent.join(""));
Добра идеја е да ги сортирате иконите по преземањето на URL-адресите, што е едноставно како ова:
iconsData.sort((a, b) => a.name.localeCompare(b.name));
Подредената листа ќе биде полесна за читање и, што е уште поважно, ќе имаме читлива разлика секогаш кога ќе добиеме нови икони. Во спротивно, нарачката не е загарантирана, а додавањето на една икона може да збрка сè.
Потребна ни е компонентата што ги прифаќа низата, бојата и големината на иконата како реквизити и црта нешто слатко:
import React, { useMemo } from "react"; import { IconSvg } from "../types/IconSvg"; interface IconProps { svg: IconSvg; // type IconSvg = (color: string) => string; color?: string; size?: number | string; } const refine = (x: string | number) => { return typeof x === "number" || !/\D+/.test(x) ? `${x}px` : x; }; const MyIcon: React.FC<IconProps> = ({ svg, color, size, }) => { const iconColor = useMemo(() => color ?? "black", [color]); const refinedSize = useMemo(() => refine(size ?? 24), [size]); const currentIcon = useMemo(() => svg(currentColor), [svg, currentColor]); return ( <svg viewBox="0 0 24 24" // make sure it's the same as in Figma dangerouslySetInnerHTML={{ __html: currentIcon }} style={{ width: refinedSize, minWidth: refinedSize, height: refinedSize, minHeight: refinedSize, }} ></svg> ); }; export default MyIcon;
Верувам дека е прилично јасен, освен за рафинирање на големината: кога го имплементирав за прв пат, Chrome прифати бројки како ширина/висина на CSS својства, но Firefox не, па додадов px
. Дури и да не е веќе случај, да го задржиме како што треба според стандардите.
И еве го автоматското комплетирање што го ветив:
Јас вака го готвам. Се надевам дека ви е корисно.
Недостаток на овој пристап е што иконите ќе бидат вметнати и нема да се кешираат поради тоа. Сепак, мислам дека не е голема работа за неколку бајти. Покрај тоа, можете да додадете ротирачки, менувајќи ја бојата при лебдење, што било друго, и ќе имате мал швајцарски армиски нож од тие икони.
Исто така, ќе бара организирана датотека во Figma. Но, еј, ако можете да го напишете целиот овој код, можете исто така да ги убедите вашите дизајнери да го средат.
Можете да го најдете целосниот код, вклучувајќи ги компонентите React и Vue, како и верзијата Node.js со помалку зависности овде: https://github.com/Smileek/fetch-icons