Utilizing React for UI in Adobe XD

You are currently viewing Utilizing React for UI in Adobe XD

Product teams are essential for bridging the gap between the project’s design and development. There may be a lot of guidelines for help, but until the execution is done right, guidelines don’t guarantee a perfect result. To close this gap between design and development, React was introduced.   

React was brought in as a sophisticated and vital framework library of JavaScript. In the front-end development space, it is different from its competitors in a way that it does not attempt to provide a complete app framework that other mainstream frameworks offer. But rather, it provides mastering the art of user interfaces. And this is mainly the reason why React is majorly symbolized with UI/UX. 

React Native – a UI framework for building native apps uses native components like View, Text and Image for building the UI blocks. Diagram 1 summarizes this in a visual format.  

Diagram 1: React UI Framework 

This component approach yields the following benefits:  

  • Components let you build a domain specific language  
  • They are shared across the boundaries meaning for a component on the design canvas, source code is given for it too.  
  • Platform specific versions get established so a single codebase can share code across platforms. React components wrap existing native code and interact with native API’s via React’s UI paradigm and JS.    
Diagram 2: Cross Platform Design in React 

Adobe XD is a fast and powerful UI/UX Design and collaboration tool. It offers a vector-based user experience design for web and mobile apps. It is actively used to create the overall prototype or wireframe for the app. Once the designing process is completed, the project may be exported to any other app framework. It is important to note that while exporting is done, it may disturb some of the design elements interfaces. To avoid this, there is a need for a plug-in to integrate the Adobe XD with React. Plug-in, in this scenario, would utilize react Framework using JSX, in conjunction with webpack to transform and bundle the source into JavaScript consumable by XD.   

It is important to note that the XD plugin environment is not a browser. Instead, there is a technology called Unified Extensibility Platform (UXP) developed by Adobe that powers the plug-in’s code. It aims to map closely to web standards for HTML, CSS and JS. It should be noted that not all node packages are expected to work. XD’s plug-in sandbox supports NPM modules using only the core JavaScript API’s i.e., no file system access or external process launching.  

Following is the mechanism to create the plug-in.  

STEP 1: Choosing the kind of plug-in – Modal or Panel  

XD supports two kinds of plug-in flows, namely Modal (also known as diagonal) and Panel. The difference between these two is that dialogue plug-ins drop down from the top of the application and block further user interaction until dismissed. They have their place, but not all tasks benefit from the need to interrupt workflow. Panel plug-ins integrate into a dedicated area off to the side. The plug-in effectively becomes another tool to use, embedded right into the XD’s native application. UXP engine renders true application UI elements from the plug-ins code. Here, we’ll proceed with panel type plug-in, but it’s completely optional which one needs to be used.      

STEP 2: Project Set Up  

Have the following up and running before starting with XD. 

  • JS IDE like VSCode, WebStorm, Atom or Notepad++.  
  • Repository management via Git. There will be a need to pull & push codes to the project report.  
  • Installation of JS sources from a remote registry and then build node packages: node with npm and yarn.   

Once XD has been started, next step is to locate the plug-in development folder. This can be found in the path: XD > Main menu > Plug-Ins > Development > Show Develop Folder.  

STEP 3: Project Installation 

From git, clone the project files. Once the project has been pulled down, execute yarn install within the projects root directory to pull in the necessary dependencies. A good way is to keep a file watcher going on to recompile the project automatically by typing npm run watch (or yarn watch) in the terminal also from the project’s root folder. Project debugging can be done in the developer output console in XD, where runtime errors and console.log () statements are recorded. Diagram 3 shows a sample screenshot.  

Diagram 3: Project Installation 

STEP 4: XD Resources Configuration  

In the project files, manifest.json would be present which contains metadata pertaining to the plug-in. Externals entry in the webpack.config.js is important because this is where native XD modules are listed. The webpack code will use externals from the XD binary instead of bundling them with build. Projects src folder will have main.jsx file. This is a standard looking entry point that requires a helper file react-shim.js as shown in code below.   

if (window.setTimeout == null) { 

  window.setTimeout = function(fn) { fn() }; 

} 

if (window.clearTimeout == null) { 

  window.clearTimeout = function() {}; 

} 

if (window.cancelAnimationFrame == null) { 

  window.cancelAnimationFrame = function() {}; 

} 

if (window.requestAnimationFrame == null) { 

  window.requestAnimationFrame = function( {   

   console.log("requestAnimationFrame is not supported yet"); 

 } 

} 

if (window.HTMLIFrameElement == null) { 

  window.HTMLIFrameElement = class HTMLIFrameElement {}; 

} 

The shim provides XD’s JS engine with functions that are not there. It assigns the windows iframe element to HTMLFrameElement built into XD. 

PanelController mounts the app and holds three functions implemented by the panel plug in specification: show()hide (), and update ().  See the code below.  

const React = require('react'); 

const ReactDOM = require('react-dom'); 

 

class PanelController { 

  constructor(App) { 

   this.App = App;  

   this.instance = null; 

   this.rootNode = document.createElement('div'); 

    this.attachment = null; 

 

   ['show', 'hide', 'update'].forEach((fn) => 

     this[fn] = this[fn].bind(this) 

    );   

} 

  show(event) { 

  const { selection, root } = require('scenegraph'); 

    const App = this.App; 

 

  this.attachment = event.node; 

    this.attachment.appendChild(this.rootNode); 

 

    if (!this.instance) { 

      this.instance = ReactDOM.render(<App />, this.rootNode); 

    } 

 

    this.update(selection, root); 

  } 

 

  hide(event) { 

    this.attachment.removeChild(this.rootNode); 

} 

update(selection, root) { 

    if (this.instance.documentStateChanged) { 

    this.instance.documentStateChanged(selection, root); 

  } 

 }   

} 

After the plug-in loads successfully, show () is called where the app instance attached to a generated root div element, then rendered in typical React fashion. Then whenever the ScenegGraph changes, the update () function is called which in turn calls documentStateChanged() on app passing along the current selection with the SceneGraph root node. When the plug-in unloads, hide () is invoked and the app is then unmounted.  

STEP 5: UI Components Addition  

The app has three inner components within the plug-in panel area handling a couple of actions and functions. EditDocument function’s first parameter is the undo label. The second is the function passing the current selection + SceneGraph root node. This is the only place where document changes can be made from the code. SceneGraph can only be invoked by a user interaction like a button click or input change originating from the plug-ins UI. If the action is asynchronous, the operation must use a Promise or the sync await keywords in order to pause the plug-in until the action completes.  

UI capabilities can be seen in MainContent.jsx. Let’s have a look.  

const React = require('react'); 

const styles = require('./MainContent.css'); 

const btoa = require('btoa'); 

const application = require('application'); 

const { selection, Artboard, ImageFill, Line, Text } = require('scenegraph'); 

const fs = require('uxp').storage.localFileSystem; 

const UNSPLASH_API = 'https://api.unsplash.com/photos/random?client_id=b21ca0eaeafd7b2d3ad04aea61bc1ca8516fe43dfdb22ebd93e39aa25049a014'; 

class MainContent extends React.Component { 

  constructor(props) { 

   super(props); 

   this.state= { 

    }; 

   ['handleCreateRendition', 'handleFetchImage'].forEach((fn) => { 

     this[fn] = this[fn].bind(this); 

 }); 

} 

  handleCreateRendition = () => { 

  application.editDocument({ editLabel : 'Export Rendition' }, (selection, documentRoot)  

=> { 

   selection.items.forEachasync(node) => { 

     const folder = await fs.getFolder();         

     const filename = `${node.name}_${node.guid}${(node.fill instanceof ImageFill) ? '@2x.png' : '.svg'}`; 

        const file = await folder.createFile(filename, { overwrite : true }); 

        const renditionSettings = [(node.fill instanceof ImageFill) ? { 

          node        : node, 

          outputFile  : file, 

          type        : application.RenditionType.PNG, 

          scale       : 2 

      } : { 

          node        : node, 

          outputFile  : file,  

         type        : application.RenditionType.SVG, 

          minify      : true, 

          embedImages : false, 

  scale       : 1  

    }]; 

… 

… 

… 

The UI in the above code shows some plug-in capability regarding buttons. The disabled attributes are set based on the current selection and a uxp-variant attribute provides basic built-in styling loosely adhering to XD’s native UI.  

In a nutshell, the first button passes its click handler up the app to pan and zoom the viewport. The second button fills a shape type object with a random image provided by the API. Within the handlers editDocument(), a first async call is made which returns a JSON object describing the image. The description entry is used to rename the layer, one of the source URL’s is also grabbed for use on the following fetch call. Taking the entry’s full size image URL through the second and last async fetch call, the response is converted from a data buffer to a binary string.  

Finally, the selected node gets filled with the base 64-encoded representation of that binary image data. The third button simply exports the image. A file handler is created at that path with a name generated from the layer’s name plus it’s a guid (a UIUD) properties. A set of export options are defined, createRendition() is called, ultimately writing the file contents and closing the handle.  

The final steps are to package the plug-in. First execute the yarn build within the project’s root folder. The bundled JS is written by webpack to main.js located off the projects root folder. Files are imported in the source by using the require statement. A standard zip file would contain main.jsmanifest.json and the images folder, plus any other assets placed on the root folder by webpack. Then change the file extension from .zip to .xdx so Adobe XD recognizes it as a plug-in package.