├── .editorconfig ├── .github └── FUNDING.yml ├── .gitignore ├── .prettierrc ├── README.md ├── dist ├── index.html └── plugin.js ├── manifest.json └── samples ├── color-styles-v2.png ├── icon-set-hint-v2.png ├── icon-set-v2.png ├── icon-sets-v2.png ├── multi-select-v2.png └── search-v2.png /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | indent_style = tab 7 | indent_size = 4 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | 11 | [{*.json,*.yml}] 12 | indent_style = space 13 | indent_size = 2 14 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: cyberalien 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .vscode 3 | .DS_Store 4 | node_modules 5 | npm-debug.log 6 | lib 7 | # Keeping package-lock.json guarantees merge conflicts when updating forks, so it is better to ignore it. 8 | package-lock.json 9 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "singleQuote": true, 4 | "useTabs": true, 5 | "semi": true, 6 | "quoteProps": "consistent" 7 | } 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Iconify plug-in for Figma 2 | 3 | Import Material Design Icons, FontAwesome, Jam Icons, EmojiOne, Twitter Emoji and many other icons (more than 150 icon sets containing over 200,000 icons) to Figma document as vector shapes. 4 | 5 | ## Pre-release 6 | 7 | This is a pre-release branch for version 4 of plug-in. 8 | 9 | It contains built files that you can use in Figma. 10 | 11 | ## Usage 12 | 13 | 1. Clone or download this repository 14 | 2. Open Figma 15 | 3. In menu go to Plugins -> Development -> Manage plugins in development 16 | 4. Click "+" icon to create a new plugin, select option "Import plugin from manifest" 17 | 5. Select `manifest.json` from this repository. 18 | 19 | This will install plugin to your Figma app. 20 | 21 | To run it, go to Plugins -> Development, select "Iconify Dev". 22 | 23 | ## Functionality 24 | 25 | This plugin is fully functional. Everything should work correctly. 26 | 27 | However, it is currently available only here. Figma community shows old version. 28 | 29 | ### New features 30 | 31 | Version 4 has been completely rewritten. It includes many new shiny features, which should all work in development preview. 32 | 33 | ### New UI 34 | 35 | Plugin has completely new UI. 36 | 37 | Many Figma users have small monitors, plugin takes precious space. 38 | UI has been re-designed to fit as much information as possible in a small plugin window. 39 | 40 | ### Redesign attempt 2 41 | 42 | Initial redesign was a failure. It resulted in very bad UX, making things worse. 43 | 44 | New version of plugin was live for only 3 days before it was restored to old version. 45 | 46 | So after many months of hard work, UI has been redesigned again. 47 | 48 | Screenshots below show new redesigned UI, not initial mess. 49 | 50 | ### Icon sets 51 | 52 | ![Iconify for Figma - icon sets](https://iconify.github.io/iconify-figma/samples/icon-sets-v2.png) 53 | 54 | Icon sets list is shorter, but offers a lot more: 55 | 56 | - Hints when you hover icon set, showing various details, including extended license information. 57 | - Favorite and recent icon sets. You can mark any icon set as favorite to access it quicker. 58 | - Advanced filters. You can filter icon sets by category, grid, license, license details. 59 | - Custom lists of icon sets. 60 | 61 | ### Icon set 62 | 63 | ![Iconify for Figma - icon set](https://iconify.github.io/iconify-figma/samples/icon-set-v2.png) 64 | 65 | Icon set page now has: 66 | 67 | - Advanced license details: attribution, commercial use. 68 | - Scrolling for icons. No more pagination (though it is available if you prefer to click pages). 69 | - You can select multiple icons. To enable multi-select, click "Select multiple icons" box. 70 | - Quick import for icons. Click menu (3 dots icon) button for any icon, you'll see buttons to quickly import it. Drag/drop is also supported, so you can use whatever you prefer. 71 | 72 | ![Iconify for Figma - multi-select](https://iconify.github.io/iconify-figma/samples/multi-select-v2.png) 73 | 74 | ### Search 75 | 76 | ![Iconify for Figma - search results](https://iconify.github.io/iconify-figma/samples/search-v2.png) 77 | 78 | Search results feature: 79 | 80 | - Search is now shown on all pages, making it easy to access at all times. 81 | - If you are viewing filtered icon sets (such as filtered by category) or custom icon sets list (favorite, recent or any other list), you can search only icon sets in that list. 82 | - Infinite scroll for icons, same as in icon set view. It is not typical slow infinite scroll, it is very fast and renders only icons that are visible. 83 | - License information for each icon. 84 | 85 | ### Drag and drop 86 | 87 | Drag and drop has been redesigned. It is now more precise, dropping icon to correct layer. 88 | 89 | There are some caveats though: 90 | 91 | - Cannot drop icon to component instance: Figma plugin system limitation. Must drop it to main component. 92 | - If layer has auto-layout, icon will be imported as first item in frame, not necessary where you drop it. 93 | 94 | ### Color styles 95 | 96 | Plugin now supports Figma color styles. 97 | 98 | Color styles are shown in color picker. When importing icon, if color style is selected, it will be applied to icon. 99 | 100 | ![Iconify for Figma - color styles](https://iconify.github.io/iconify-figma/samples/color-styles-v2.png) 101 | 102 | Behavior: 103 | 104 | - Works when importing icon, works when using drag/drop. 105 | - When replacing icon, color value is ignored. Instead, plugin will reuse color or color style from old icon. 106 | - In Figma, plugins can find only local styles. Shared styles from other documents (remote styles) are not available. However, when selecting a layer, plugin can see styles that are used in that layer, including remote style. Iconify plugin checks selected layers for remote styles, so if you want to use a remote style, select any layer that uses it and it will appear in plugin's color picker. 107 | 108 | ## Feedback 109 | 110 | Feedback is very welcome. 111 | 112 | Please open issues at this repository if: 113 | 114 | - You think this version is missing a critical feature that older version used to have. 115 | - You have a suggestion. 116 | - You found something that does not feel right, which might be a bug or bad design. 117 | 118 | ## Source code 119 | 120 | Source code for this version is not available yet. 121 | -------------------------------------------------------------------------------- /dist/plugin.js: -------------------------------------------------------------------------------- 1 | const M={compactWidth:!1,v4Notice:!1},p=Object.assign({},M);function se(e){var o;p.v4Notice=!0;const t=["compactWidth","windowAction","selectAfterImport","customizeDrop","dropToFrame"];for(const n of t){const i=(o=e.options)==null?void 0:o[n];i!==void 0&&(p[n]=i)}return p}async function ae(){try{const e=await figma.clientStorage.getAsync("config");switch(e==null?void 0:e.version){case 2:se(e);break;case 3:Object.assign(p,e);break}}catch(e){}return p}function W(){const e={version:3};let t;for(t in M){const o=p[t],n=M[t];n!==void 0&&n!==o&&(e[t]=o)}figma.clientStorage.setAsync("config",e).catch()}function le(){const e=figma;try{const t=e.editorType;switch(t){case"figma":case"figjam":return t}}catch(t){console.error(t)}try{if(typeof e.createSticky=="function")return"figjam"}catch(t){console.error(t)}return"figma"}const h={app:le(),loaded:!1,minimized:!1,config:p};function R(){const e=h.app;return e==="figjam"?"FigJam":e.slice(0,1).toUpperCase()+e.slice(1)}const z=32,O={mini:{width:200,height:88},full:{width:64*8+z+24,min:580,max:720},compact:{width:56*7+z+12,min:520,max:640},innerDiff:90,outerDiff:150};function x(){const e=h.config.compactWidth,t=O[e?"compact":"full"];let o;if(h.windowInnerHeight){const n=h.windowOuterHeight?h.windowOuterHeight-O.outerDiff:h.windowInnerHeight-O.innerDiff;o=Math.max(Math.min(n,t.max),t.min)}else o=t.max;return{width:t.width,height:o}}function m(e){figma.ui.postMessage(e)}function fe(e){switch(e.type){case"PAGE":case"COMPONENT":case"FRAME":case"SECTION":case"GROUP":return e}}function ue(e){switch(e.type){case"FRAME":case"COMPONENT":return e}}function G(e){return{type:"icon-set",prefix:e,state:{},parent:{type:"icon-sets",state:{}}}}function ge(e){function t(){if(e.getSharedPluginData("iconify","source")==="iconify"){const n=JSON.parse(e.getSharedPluginData("iconify","props")),c=n.name.split(":");if(c.length===2)switch(n.version){case 3:{const r=n.route||G(c[0]);return Object.assign(n,{route:r})}default:{const r=n.props;let s;return typeof n.color=="string"?s=n.color:typeof r=="object"&&(s=r.color),{version:3,name:n.name,props:{color:s},route:G(c[0])}}}}}try{return t()}catch(o){}}function J(e){return{nodes:e.nodes||Object.create(null),ignoreIconNode:e.ignoreIconNode||!1,iconNodeID:e.iconNodeID||null,iconNodeData:e.iconNodeData||null}}function T(e,t,o){function n(i,c,r){const s=i.id,f=t.nodes[s];if(f)return r&&f.children.push(r),f;let a="invalid",g=!1,l,u;const y=ue(i);y&&(l=ge(y),l&&(u=y.height,t.iconNodeID?t.ignoreIconNode=!0:(t.iconNodeID=s,t.iconNodeData=l)));const d=fe(i);d?d.type==="PAGE"?(a="valid",g=!0):(d.locked||(a="valid"),(!c||d.locked)&&(t.ignoreIconNode=!0),g=d.type!=="FRAME"):i.type==="COMPONENT_SET"?a="ignored":t.ignoreIconNode=!0;const w={type:i.type,id:s,name:i.name,target:a,children:r?[r]:[],primary:c,relative:g,icon:l,height:u};return t.nodes[s]=w,i.parent&&i.type!=="PAGE"&&n(i.parent,c,i.id),w}return n(e,o)}const de=16;function L(){const e=figma.currentPage,t=e.selection,o=J({}),n=T(e,o,!1);t.forEach((d,w)=>{T(d,o,!w)});const{nodes:i,iconNodeData:c,iconNodeID:r,ignoreIconNode:s}=o,f=[];let a=n.id,g;function l(d,w=0){switch(d.target){case"ignored":{d.children.forEach(I=>{l(i[I],w)});break}case"valid":switch(d.type){case"PAGE":case"COMPONENT":case"FRAME":case"SECTION":case"GROUP":const I={id:d.id,name:d.name,depth:w,type:d.type};!s&&c&&r===d.id&&(I.icon=c,I.height=d.height,g=I),d.icon||(f.push(I),d.primary&&!d.icon&&(a=d.id),d.children.forEach(re=>{l(i[re],w+1)}))}}}l(n);let u=!1,y=de;for(;f.length>y;)f.pop()===g&&(u=!0,y--);return u&&g&&f.push(g),{nodes:f,defaultNode:a,iconNode:g}}let _=L();function Y(){return _}function he(e){_=e}const D="style:";function X(e){function t(o){const n=Math.round(o*255).toString(16);return n.length<2?"0"+n:n}return"#"+t(e.r)+t(e.g)+t(e.b)}function F(e){const t=e.paints[0].color;return{id:e.id,name:e.name,color:X(t),remote:e.remote}}function H(e){return e.type==="SOLID"&&e.visible!==!1&&e.blendMode==="NORMAL"&&e.opacity===1?e:void 0}function k(e){if(e.paints.length===1)return H(e.paints[0])}function q(e){const t=figma.getStyleById(e);if((t==null?void 0:t.type)==="PAINT"&&k(t))return t.id}const v=new Set;async function pe(){const e=[];return(await figma.getLocalPaintStylesAsync()).forEach(t=>{try{v.add(t.id),k(t)&&e.push(F(t))}catch(o){console.log("Error parsing color style:",o)}}),e}function Q(){const e=figma.getSelectionColors(),t=[];return e==null||e.styles.forEach(o=>{const n=o.id;v.has(n)||(v.add(n),k(o)&&t.push(F(o)))}),t.length?t:void 0}function me(e){async function t(o){const n=[];for(const i of o)if(i.startsWith(D)){const c=i.slice(D.length);if(!v.has(c)){v.add(c);const r=await figma.getStyleByIdAsync(c);(r==null?void 0:r.type)==="PAINT"&&k(r)&&n.push(F(r))}}n.length&&m({type:"plugin:color-styles",styles:n})}e&&setTimeout(()=>{t(e).catch(console.error)})}let b=!1;function ye(){const e=L();if(JSON.stringify(e)!==JSON.stringify(Y())){he(e);const t=Q();m({type:"plugin:nodes",nodes:e,styles:t})}}function E(){b||(b=!0,setTimeout(()=>{b=!1,ye()},250))}async function S(e,t=void 0,o){try{const n=await figma.clientStorage.getAsync(e);if(n)return o?o(n):n}catch(n){}return t}function P(e,t){figma.clientStorage.setAsync(e,t).catch()}const Z="recent";let N=[];const Ne=128;async function we(){const e=await S(Z);return e&&(N=e),e}let C=!1;function ee(){C||(C=!0,setTimeout(()=>{C=!1,P(Z,N),m({type:"plugin:recent-icons",icons:N})},1e3))}function te(e){for(const t of e){const o=N.indexOf(t);o!==-1&&N.splice(o,1),N.unshift(t),N.length>Ne&&N.pop(),ee()}}function Ie(){N=[],ee()}async function ne(e,t){function o(c){try{if(c.length===1){const r=c[0];if(H(c[0]))return!0}}catch(r){}}async function n(c){o(c.fills)&&(typeof t=="string"?await c.setFillStyleIdAsync(t):c.fills=[t]),o(c.strokes)&&(typeof t=="string"?await c.setStrokeStyleIdAsync(t):c.strokes=[t])}async function i(c){const r=c;if(r.locked||r.isMask)return;try{await n(r)}catch(f){}const s=c.children;if(s)for(const f of s)await i(f)}for(const c of e.children)await i(c)}const Se={replace:"Replace icon",code:"Icon code"};function oe(e,t,o,n){e.name=t,e.setSharedPluginData("iconify","source","iconify");const i={version:3,name:t,props:o,route:n};e.setSharedPluginData("iconify","props",JSON.stringify(i)),e.setRelaunchData(Se)}function ie(e,t){const o=figma.createNodeFromSvg(e);let n;if(t.replace?n=t.replace:t.component&&(n=figma.createComponent()),n){for(;n.children.length>0;)n.children[0].remove();n.resizeWithoutConstraints(o.width,o.height);for(const c of o.children)n.appendChild(c);if(o.remove(),t.replace)return n}const i=n||o;if(t.parent){const c=t.parent;let r=!0;const s=c.layoutMode;s&&s!=="NONE"&&(r=!1),r?c.appendChild(i):c.insertChild(0,i),t.x&&(i.x=t.x),t.y&&(i.y=t.y)}return i.constrainProportions=!0,i}function U(e,t,o,n){if(e.type==="PAGE")return t;let i=0,c=n?e.width:e.height;if(e.type==="GROUP"&&(i=n?e.x:e.y),tr?Math.max(i,r):t}function ce(e,t,o){const n=t.icons.reduce((s,f)=>s+Math.ceil(f.width),0);let i=U(e,Math.round(o.targetX-n/2),n,!0),c;t.props.style&&(c=q(t.props.style));const r=[];for(const s of t.icons){const f=U(e,Math.round(o.targetY-s.height/2),s.height,!1),a=ie(s.content,{parent:e,x:i,y:f,component:o.component});oe(a,s.name,t.props,t.route),i+=Math.ceil(s.width),r.push(a),s.monotone&&c&&ne(a,c).catch(console.error)}return te(t.icons.map(s=>s.name)),m({type:"plugin:notice",color:"success",text:`Imported ${t.icons.length>1?t.icons.length+" icons":t.icons[0].name} to ${R()}`}),figma.currentPage.selection=r,E(),r}function j(){m({type:"plugin:notice",color:"error",text:"Error dropping icon(s) to "+R()})}function Pe(e){try{if(e.dropMetadata.source!=="iconify")return!0}catch(u){return!0}let t=e.x,o=e.y;const n=e.node;n.type==="GROUP"&&(t+=n.x,o+=n.y);const i=J({});T(n,i,!0);const c=i.nodes[figma.currentPage.id];if(!c)return console.error("Cannot find current page item in scanned nodes list"),j(),!0;let r=0,s=0,f=!0,a=c,g=c;for(;a.children.length;){const u=a.children[0];if(a=i.nodes[u],a.icon||a.target==="invalid"?f=!1:f&&a.target==="valid"&&(g=a),!f&&a.relative){const y=figma.getNodeById(a.id);y&&(r+=y.x||0,s+=y.y||0)}}const l=figma.getNodeById(g.id);return l?(ce(l,e.dropMetadata,{targetX:r+t,targetY:s+o}),!1):(console.error("Failed to get target node"),j(),!0)}function A(){m({type:"plugin:notice",color:"error",text:"Error importing icon(s) to "+R()})}function ve(e,t){const o=figma.getNodeById(e);if(!o){A();return}const n=o.name;let i,c;const r=figma.currentPage.selection;if(r.length===1&&r[0].id===o.id){const s=figma.getSelectionColors();if(s){const f=s.paints,a=s.styles;f.length+a.length===1?(a.length&&k(a[0])&&(i=a[0].id,c=D+i),f.length&&(i=H(f[0]),i&&(c=X(i.color)))):t.props.style&&(i=q(t.props.style))}}for(const s of t.icons){const f=s.name,a=ie(s.content,{replace:o,x:o.x,y:o.y});oe(a,f,t.props,t.route),te([f]),m({type:"plugin:notice",color:"success",text:`Replaced ${n} with ${f}`}),figma.currentPage.selection=[a],E(),s.monotone&&i&&ne(a,i).catch(console.error),s.monotone&&c&&m({type:"plugin:recent-color",color:c});return}A()}function ke(e,t,o){const n=figma.getNodeById(e);if(!n){A();return}let i,c;switch(n.type){case"PAGE":{const r=figma.viewport.center;i=r.x,c=r.y;break}case"GROUP":{i=n.x+n.width/2,c=n.y+n.height/2;break}default:{i=n.width/2,c=n.height/2;break}}ce(n,t,{component:o,targetX:i,targetY:c})}const V="icon-sets",B="route-v31",K="recent-colors",$="customisations";(async()=>{var a;console.log("Starting plugin..."),await ae();const e=await S(V,void 0,g=>g.version===3?g.data:void 0);let t=await S(B);const o=await we(),n=await S(K);let i=await S($);const c=Y(),r=Q();let s;if(figma.command==="replace"){const g=(a=c.iconNode)==null?void 0:a.icon;if(g){s=g.name,g.route&&(t=g.route);const l=g.props;l&&(i={size:l.size,color:l.style?D+l.style:l.color})}}figma.on("selectionchange",E),figma.on("currentpagechange",E),figma.on("drop",Pe),figma.ui.onmessage=g=>{try{if(typeof g.type!="string")return}catch(u){console.error(u)}const l=g;switch(l.type){case"ui:fatal-error":{figma.closePlugin(l.error);break}case"ui:loaded":{if(l.innerHeight||l.outerHeight){h.windowInnerHeight=l.innerHeight,h.windowOuterHeight=l.outerHeight;const u=x();u.height!==h.lastWindowHeight&&(h.lastWindowHeight=u.height,figma.ui.resize(u.width,u.height))}m({type:"plugin:starting",app:h.app,command:figma.command,config:p,nodes:c,styles:r,lists:e,recent:o,route:t,selectIcon:s,recentColors:n,custom:i}),pe().then(u=>{u.length&&m({type:"plugin:color-styles",styles:u})}).catch(console.error),me(n);break}case"ui:close":{figma.closePlugin();break}case"ui:compact":{p.compactWidth=!p.compactWidth;const u=x();u.height!==h.lastWindowHeight&&(h.lastWindowHeight=u.height,figma.ui.resize(u.width,u.height)),m({type:"plugin:resize",compactWidth:p.compactWidth,minimized:!1}),W();break}case"ui:dismiss-v4":{p.v4Notice=!1,W();break}case"ui:lists":{const u={version:3,data:l.lists};P(V,u);break}case"ui:route":{P(B,l.route);break}case"ui:replace-icon":{ve(l.node,l.data);break}case"ui:import-icons":{ke(l.node,l.data,l.component||!1);break}case"ui:recent-colors":{P(K,l.colors);break}case"ui:customisations":{P($,l.custom);break}case"ui:reset-recent-icons":{Ie();break}}};const f=x();h.lastWindowHeight=f.height,figma.showUI(__html__,Object.assign({themeColors:!1},f))})(); 2 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Iconify Dev", 3 | "id": "735098390272716381", 4 | "api": "1.0.0", 5 | "main": "dist/plugin.js", 6 | "ui": "dist/index.html", 7 | "relaunchButtons": [ 8 | { "command": "replace", "name": "Iconify", "multipleSelection": true } 9 | ], 10 | "editorType": ["figma", "figjam"], 11 | "networkAccess": { 12 | "allowedDomains": [ 13 | "https://code.iconify.design", 14 | "https://api.iconify.design", 15 | "https://api.simplesvg.com", 16 | "https://api.unisvg.com" 17 | ], 18 | "reasoning": "Network access to Iconify API is required to retrieve icons" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /samples/color-styles-v2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iconify/iconify-figma/ce6a349c0a031ad272e7336aa1fab4316c494e05/samples/color-styles-v2.png -------------------------------------------------------------------------------- /samples/icon-set-hint-v2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iconify/iconify-figma/ce6a349c0a031ad272e7336aa1fab4316c494e05/samples/icon-set-hint-v2.png -------------------------------------------------------------------------------- /samples/icon-set-v2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iconify/iconify-figma/ce6a349c0a031ad272e7336aa1fab4316c494e05/samples/icon-set-v2.png -------------------------------------------------------------------------------- /samples/icon-sets-v2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iconify/iconify-figma/ce6a349c0a031ad272e7336aa1fab4316c494e05/samples/icon-sets-v2.png -------------------------------------------------------------------------------- /samples/multi-select-v2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iconify/iconify-figma/ce6a349c0a031ad272e7336aa1fab4316c494e05/samples/multi-select-v2.png -------------------------------------------------------------------------------- /samples/search-v2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iconify/iconify-figma/ce6a349c0a031ad272e7336aa1fab4316c494e05/samples/search-v2.png --------------------------------------------------------------------------------