├── .gitattributes ├── .gitignore ├── README.md ├── dist ├── code.js ├── index.html └── manifest.json ├── gulpfile.js ├── package-lock.json ├── package.json ├── src ├── main │ └── code.ts ├── manifest.json └── ui │ ├── index.html │ ├── scripts │ ├── helper-functions.js │ └── scripts.js │ └── styles │ └── styles.scss ├── static └── plugin-experiment-docs.gif └── tsconfig.json /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .DS_Store 3 | package-lock.json -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # External Docs - Figma Plugin Experiment 2 | 3 | 4 | A plugin experiment to show related external documentation for a selected component/instance. 5 | 6 | ## How it works 7 | The plugin reads the component description of the currently selected component and searches for the term `@api` (this could be anything). If there's a match, it uses the component name to serve the corresponding page of the documentation (by simple URL matching). 8 | 9 | I've used Github Primer and the Primer docs as an example as this requires cross-origin requests and not a lot of public styleguides have this enabled :) 10 | 11 | ## Thoughts 12 | This is a very simple approach to a very powerful concept. A lot of design systems and/or dev teams have existing docs that would be valuable to reference while designing - why not pull them straight in! 13 | 14 | There's a lot of fragility and room for human error by matching text strings and I could imagine a much more robust approach with: 15 | - uuid references instead of text 16 | - actual API request for urls 17 | - two-way data to populate the Figma canvas with useful info from the docs 18 | - local and external docs 19 | 20 | ## Next steps 21 | I don't have any further plans for this experiment - but perhaps it will be useful for someone else! 22 | 23 | ## Thanks 24 | - [@thomas-lowry](https://github.com/thomas-lowry) and the invaluable [Figma Plugin Boilerplate (FPB)](https://github.com/thomas-lowry/figma-plugin-boilerplate#intro) 25 | - The [Primer](https://github.com/primer) team for their amazing [styleguide](https://primer.style/) and their [Primer Web Figma Community File](https://www.figma.com/community/file/854767373644076713) 26 | -------------------------------------------------------------------------------- /dist/code.js: -------------------------------------------------------------------------------- 1 | // This plugin will open a modal to prompt the user to enter a number, and 2 | // it will then create that many of the chose shape on screen 3 | // This shows the HTML page in "index.html". 4 | figma.showUI(__html__, { width: 400, height: 700 }); 5 | const node = figma.currentPage.selection[0]; 6 | sendDescription(node); 7 | function sendDescription(nodeSelected) { 8 | if (nodeSelected.masterComponent) { 9 | figma.ui.postMessage(nodeSelected.masterComponent.description); 10 | } 11 | else { 12 | figma.ui.postMessage(nodeSelected.description); 13 | } 14 | } 15 | figma.on("selectionchange", () => { 16 | sendDescription(figma.currentPage.selection[0]); 17 | }); 18 | -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 2057 | 2068 | 2069 | 2070 | 2071 |
2072 | 2073 | 2074 | 2075 |
2076 | 2077 | 2569 | 2570 | -------------------------------------------------------------------------------- /dist/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Docs Plugin Demo", 3 | "id": "", 4 | "api": "1.0.0", 5 | "main": "code.js", 6 | "ui": "index.html" 7 | } -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | // Initialize gulp + modules 2 | const { src, dest, watch, series, parallel } = require('gulp'); 3 | 4 | // gulp plugins 5 | const sass = require('gulp-sass'); //import SCSS compiler 6 | const concat = require('gulp-concat'); //enables gulp to concatenate multiple JS files into one 7 | const minify = require('gulp-minify'); //minifies JS 8 | const postcss = require('gulp-postcss'); //handles processing of CSS and enables use of cssnano and autoprefixer 9 | const autoprefixer = require('autoprefixer'); //handles autoprefixing for browser support 10 | const csso = require('gulp-csso'); //css minification 11 | const ts = require('gulp-typescript'); //typescript compiler 12 | const replace = require('gulp-replace'); //replace a string in a file being processed 13 | const base64 = require('gulp-base64-inline'); //inline any css background images with base64 14 | const inlinesource = require('gulp-inline-source'); //inline js and css and images 15 | const htmlmin = require('gulp-htmlmin'); //minify html 16 | 17 | //for signalling dev vs. prod build 18 | const util = require('gulp-util'); //enables a dev and production build with minification 19 | var production = !!util.env.production; //this keeps track of whether or not we are doing a normal or priduction build 20 | 21 | //clean up post build 22 | const purgecss = require('gulp-purgecss'); //remove unused css 23 | const del = require('del'); //plugin to delete temp files after build 24 | 25 | // TS Config 26 | const tsProject = ts.createProject('tsconfig.json', { noImplicitAny: true, outFile: 'code.js' }); 27 | 28 | // File paths 29 | const files = { 30 | scssPath: 'src/ui/styles/**/*.scss', //path to your CSS/SCSS folder 31 | jsPath: 'src/ui/scripts/**/*.js', //path to any javascript that you use in your UI 32 | tsPath: 'src/main/**/*.ts', //location of typescript files for the main plugin code that interfaces with the Figma API 33 | html: 'src/ui/index.html', //this is your main index file where you will create your UI markup 34 | manifest: 'src/manifest.json', //location of manifest file 35 | assetsPath: 'src/ui/img/*.{png,gif,jpg,svg,jpeg}' //path to image assets for your UI 36 | } 37 | 38 | // SCSS task: compiles the styles.scss file into styles.css 39 | function scssTask(){ 40 | return src(files.scssPath) 41 | .pipe(sass()) //compile to css 42 | .pipe(replace('background-image: url(', 'background-image: inline(')) 43 | .pipe(base64('')) //base 64 encode any background images 44 | .pipe(postcss([ autoprefixer()])) // PostCSS plugins 45 | .pipe(production ? csso() : util.noop()) //minify css on production build 46 | .pipe(dest('src/ui/tmp') //put in temporary directory 47 | ); 48 | } 49 | 50 | //CSS Task: Process Figma Plugin DS CSS 51 | function cssTask() { 52 | return src('node_modules/figma-plugin-ds/dist/figma-plugin-ds.css') 53 | .pipe(production ? purgecss({ 54 | content: ['src/ui/index.html', 'src/ui/tmp/scripts.js'], 55 | whitelistPatterns: [/select-menu(.*)/], 56 | }) : util.noop()) //remove unused CSS 57 | .pipe(production ? csso() : util.noop()) //minify css on production build 58 | .pipe(dest('src/ui/tmp') //put in temporary directory 59 | ); 60 | } 61 | 62 | // JS task: concatenates JS files to scripts.js (minifies on production build) 63 | function jsTask(){ 64 | return src(['node_modules/figma-plugin-ds/dist/iife/figma-plugin-ds.js', files.jsPath]) 65 | .pipe(concat('scripts.js')) 66 | .pipe(dest('src/ui/tmp') 67 | ); 68 | } 69 | 70 | //TS task: compiles the typescript main code that interfaces with the figma plugin API 71 | function tsTask() { 72 | return src([files.tsPath]) 73 | .pipe(tsProject()) 74 | .pipe(production ? minify({ 75 | ext: { 76 | min: '.js' 77 | }, 78 | noSource: true 79 | }) : util.noop()) 80 | .pipe(dest('dist')); 81 | } 82 | 83 | //HTML task: copies and minifies 84 | function htmlTask() { 85 | return src([files.html]) 86 | .pipe(inlinesource({ 87 | attribute: false, 88 | compress: production ? true : false, 89 | pretty: true 90 | })) 91 | .pipe(production ? htmlmin({ collapseWhitespace: true }) : util.noop()) 92 | .pipe(dest('dist')); 93 | } 94 | 95 | //Clean up temporary files 96 | function cleanUp() { 97 | return del(['src/ui/tmp']); 98 | } 99 | 100 | //copy manifest file to dist 101 | function manifestTask() { 102 | return src([files.manifest]) 103 | .pipe(dest('dist') 104 | ); 105 | } 106 | 107 | 108 | // Watch all key files for changes, if there is a change saved, create a build 109 | function watchTask(){ 110 | watch([files.scssPath, files.jsPath, files.tsPath, files.html, files.manifest], 111 | {interval: 1000, usePolling: true}, 112 | series( 113 | parallel(jsTask, tsTask), 114 | scssTask, 115 | cssTask, 116 | htmlTask, 117 | manifestTask, 118 | cleanUp 119 | ) 120 | ); 121 | } 122 | 123 | // Export the default Gulp task so it can be run 124 | // Runs the scss, js, and typescript tasks simultaneously 125 | exports.default = series( 126 | parallel(jsTask, tsTask), 127 | scssTask, 128 | cssTask, 129 | htmlTask, 130 | manifestTask, 131 | cleanUp, 132 | watchTask 133 | ); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "figma-plugin-external-docs-experiment", 3 | "version": "1.0.0", 4 | "description": "External Docs - Figma Plugin Experiment", 5 | "main": "gulpfile.js", 6 | "scripts": { 7 | "build": "gulp --production", 8 | "dev": "gulp" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/adispezio/figma-plugin-external-docs-experiment.git" 13 | }, 14 | "keywords": [ 15 | "gulp", 16 | "boilerplate", 17 | "html", 18 | "css", 19 | "javascript" 20 | ], 21 | "author": "@adispezio", 22 | "license": "ISC", 23 | "bugs": { 24 | "url": "" 25 | }, 26 | "homepage": "https://github.com/adispezio/figma-plugin-external-docs-experiment#readme", 27 | "devDependencies": { 28 | "@figma/plugin-typings": "^1.15.0", 29 | "autoprefixer": "^9.7.6", 30 | "del": "^5.1.0", 31 | "figma-plugin-ds": "^0.1.8", 32 | "gulp": "^4.0.2", 33 | "gulp-base64-inline": "^1.0.6", 34 | "gulp-concat": "^2.6.1", 35 | "gulp-csso": "^4.0.1", 36 | "gulp-inline-source": "^4.0.0", 37 | "gulp-minify": "^3.1.0", 38 | "gulp-postcss": "^8.0.0", 39 | "gulp-purgecss": "^2.1.2", 40 | "gulp-replace": "^1.0.0", 41 | "gulp-sass": "^4.1.0", 42 | "gulp-typescript": "^6.0.0-alpha.1", 43 | "gulp-util": "^3.0.8", 44 | "typescript": "^3.8.3" 45 | }, 46 | "dependencies": { 47 | "gulp-htmlmin": "^5.0.1" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/code.ts: -------------------------------------------------------------------------------- 1 | // This plugin will open a modal to prompt the user to enter a number, and 2 | // it will then create that many of the chose shape on screen 3 | 4 | // This shows the HTML page in "index.html". 5 | figma.showUI(__html__, {width: 400, height: 700 }); 6 | 7 | const node:any = figma.currentPage.selection[0] 8 | 9 | sendDescription(node); 10 | 11 | function sendDescription(nodeSelected:any) { 12 | if(nodeSelected.masterComponent) { 13 | figma.ui.postMessage(nodeSelected.masterComponent.description); 14 | } else { 15 | figma.ui.postMessage(nodeSelected.description); 16 | } 17 | } 18 | 19 | figma.on("selectionchange", () => { 20 | sendDescription(figma.currentPage.selection[0]) 21 | }); 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Docs Plugin Demo", 3 | "id": "", 4 | "api": "1.0.0", 5 | "main": "code.js", 6 | "ui": "index.html" 7 | } -------------------------------------------------------------------------------- /src/ui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/ui/scripts/helper-functions.js: -------------------------------------------------------------------------------- 1 | function helper() { 2 | console.log('testing'); 3 | } -------------------------------------------------------------------------------- /src/ui/scripts/scripts.js: -------------------------------------------------------------------------------- 1 | const docFrame = document.querySelector('#docFrame'); 2 | const srcURI = 'https://primer.style/components/' 3 | 4 | onmessage = (event) => { 5 | updateiFrame(event.data.pluginMessage); 6 | } 7 | 8 | function updateiFrame(string) { 9 | const regex = /(.*@api\/components\/)(.*)/; 10 | const found = string.match(regex); 11 | //console.log(found[2]); 12 | 13 | docFrame.src = srcURI + found[2]; 14 | } 15 | 16 | function updateURL(component) { 17 | docFrame.src = component; 18 | } -------------------------------------------------------------------------------- /src/ui/styles/styles.scss: -------------------------------------------------------------------------------- 1 | /* Add your own scripts */ 2 | html, body { 3 | margin: 0; 4 | padding: 0; 5 | } 6 | 7 | iframe { 8 | width: 100%; 9 | height: 100%; 10 | } -------------------------------------------------------------------------------- /static/plugin-experiment-docs.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adispezio/figma-plugin-external-docs-experiment/916ee952aff0b9ed6f1130d8dc08ecadb37743a3/static/plugin-experiment-docs.gif -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "typeRoots": [ 5 | "./node_modules/@types", 6 | "./node_modules/@figma" 7 | ] 8 | }, 9 | } --------------------------------------------------------------------------------