Generate presets

Last updated on August 26, 2022

Presets are useful configurations of your component that act as starting points for your designers to use - they are the component configurations you can drag and drop onto the canvas in Figma. After dropping the preset the designer can adjust the component properties to tailor their component instance as needed.

Approach

You can create individual presets by configuring component instances in the Interplay UI, but for large numbers it is usually easier to generate presets from your repo.

You do this by:

  1. Configuring the CLI to parse components in your repo using an index file as an entry point
  1. Configuring the CLI to generate preset config from specific instances of each component:
      • presetPaths controls which files are parsed looking for component instances
      • presetRules controls which components instances are imported as presets from those files. Typically the rules would import the Button instance from Button.story.js and ignore the Button instance in Card.story.js

Once these settings are configured in interplay.config.js, you can run the CLI parsing with:

interplay parse

During its run, the CLI will:

  • Find all the components in the index file
  • Parse the presetPaths files, find all component instances that match the components in the index
  • Use the presetRules to select which instances to use to generate preset config

Example preset file

For example, when importing the reactstrap components, we may also configure this file as one of the presetPaths for the repo:

//buttongroup.example.js
import React from 'react';
import { Button, ButtonGroup, Stack } from 'reactstrap';

export const Example = () => {
	return (
	<Stack>
	    <ButtonGroup>
	      <Button>Button</Button>
	      <Button>Button</Button>
	      <Button>Button</Button>
	    </ButtonGroup>
	</Stack>
	);
}

The CLI would find the instances of Stack, ButtonGroup and Button. These instances are then compared with presetRules to determine which should be created as presets in Interplay. The default rules match the ButtonGroup instance in the buttongroup.story.js file, Card in card.story.js file etc.

So in this case the ButtonGroup instance would be imported as a preset and the Stack and Button instances would be ignored. This approach allows you to parse your existing storybook or documentation files but control which instances are used as presets.

For even greater control of your presets, we recommend either:

  • Create dedicated preset files for Interplay to parse.
  • Adding data attributes to your specific examples to mark them for import.

See Curating presets below for more details.

Supported File Formats

You may already have suitable configurations of your components that you use as documentation files (e.g. storybook files), in which case you can configure those files as your presetPaths in interplay.config.js.

Javascript and Typescript

The CLI extracts examples from .js and .jsx files by first attempting to require the files using babel-register, so that data imported from other files and dynamically generated component instances can be found.

If the file cannot be loaded (required), or no component instances are exported from the file then the CLI falls back to using static analysis. In this mode the file is parsed to AST and the syntax scanned - this allows basic component usages to be found but does not support dynamically generated content.

The parser will recognise exported instances of components as in the example above and Storybook format files using both Storybook's Component Story Format (CSF) and the older storiesOf format, in both typescript and javascript. We recommend using the newer Component Story Format.

Markdown

When extracting examples from Markdown files the CLI first examines the Markdown file to extract javascript or JSX code blocks. It then processes those sections in turn, treating them as independent fragments of code to process.

In this case Interplay will find the jsx fragment and then parse the 5 different Button configurations from within it.

As outlined above, Interplay can parse your existing repo files to find examples to get you up and running quickly. Depending on how your existing files are structured, this approach may be sufficient. Running the CLI again will import any example changes in those files into Interplay.

Preset Rules

You specify presetRules in interplay.config.js to specify which instances of components found in your presetPaths should be matched and used as presets.

Preset rules use simple regex patterns to match component instances. A component instance will be converted into a preset if it matches any of your preset rules. If you use the {componentName} string in a rule, it will be substituted before attempting to match the rule.

Here are some examples of preset rules:

//interplay.config.js

presetRules: [
{ //match component instances whose name matches first part of filename
	"presetPath": "/{componentName}.",
	limit: 5 //per-component, per-file limit
},
{ //match component instances whose name matches folder in preset file path
	"presetPath": "/{componentName}/",
	 limit: 5
},
{ //match all component instances in Icons.stories.js whose source file path matches a pattern
    "presetPath": "lib/components/icons/Icons.stories.js",   //rule only applies to this file
    "componentPath": "lib/components/icons/*.*"              //match components whose source resolves here
},
{ //always true - matches every component instance
    "presetPath": ".*",
},
],
 

Subcomponents

Most re-usable code components are written to be independent, and can run without any special consideration of which parent component contains them. You can import these components by adding them to your index file and build/parsing them as described above.

From a CLI perspective, "subcomponents" are components that cannot function unless they are inside a particular parent component. e.g. some MenuItem components must run inside a Menu component.

Dev teams often highlight this requirement by accessing the subcomponent only via their parent components (e.g. as Menu.Item), but this is not required for Interplay.

Subcomponents error if they are not run in their parent components, so we can't create a file of subcomponent (e.g. Menu.Item) presets.

Instead we tell the CLI they are subcomponents under the packages section of your CLI settings:

//interplay.config.js
packages: [
   {
     name: "@interplayapp/mission",
     packagePath: "src/packages/mission/package.json",
     src: "src/packages/mission/src/index.js",
     ignoreExports: ["theme"],

     //e.g. subcomponent Tour.Step within parent export Tour
     //specify source file and export to parse as the subcomponent is not in the component index
     subcomponents: {
        Tour: {
           "Tour.Step": {
              relativePath: "src/packages/mission/src/components/Tour/TourStep.tsx",
              exportName: "default"
           }
        },
     },
  }
 ],

Instead of creating standalone presets the for subcomponents, the CLI will create presets that point to instances inside their parent component.

Curating Presets

Interplay can parse exported instances of your components from files to use as presets from Storybook stories in both Component Story Format and the older storiesOf format. Stories can be in either javascript or typescript files.

For best control over your presets in Interplay, we recommend writing your presets in Component Story Format, an open format proposed by the creators of Storybook.

Using Storybook CSF

Component Story Format is an open format proposed by the creators of Storybook and includes provision for adding metadata and including/excluding stories from processing.

Here is a CSF file that exports 2 presets (stories) for the Button component:

//import your components via relative path or package alias so Interplay can find your code
import Button from './Button';
export default {
  title: 'Button'
}
//export presets individually
export const primaryButton = () => <Button appearance="primary">Primary Button</Button>;
export const secondaryButton = () => <Button appearance="secondary">Secondary Button</Button>;

//The preset name is determined by the named export, or you can optionally set metadata:
primaryButton.story = {
    name: "Primary Button example"
    description: "Here is a description for the primary button example."
}

When writing presets to parse:

  • Use a separate named export for each preset to retain control of the metadata. e.g. the file above has 4 named exports with one Button configuration in each, not 1 export containing 4 Buttons.
  • Import the components used in your presets from their source files using relative paths to help the parser resolve the component.

Selectively including exports

If you are parsing existing storybook files you may want to include or exclude particular stories. Storybook CSF provides a syntax for includes and excludes, via array or regex pattern.

In the preset above we could modify the default export as follows:

export default {
    title: 'Button',
    component: Button,
    includeStories: ['primaryButton', 'secondaryButton']
}<br>

Interplay will obey the includeStories and excludeStories settings that you set here and only import those stories.

For full details of Component Story Format please see the Storybook website

Overriding CSF metadata for Interplay

Alternatively you can override the core CSF metadata with Interplay-specific metadata as follows:

import Button from './Button';
export default {
  title: 'Button',
  component: Button,
  interplay: {
      includeStories: ['primaryButton'] //interplay-specific
  }
}

export const primaryButton = () => <Button appearance="primary">Primary Button</Button>;

//Overriding CSF metadata for Interplay
primaryButton.story = {
    name: "Primary Button"             //Storybook name
    description: "Primary Button"      //Storybook description
    interplay: {
        name: "Main Button",           //Interplay name
        description: "The description in Interplay"  //Interplay description
    }
}

Using Data Attributes

You can use data attributes such as data-interplay-name and data-interplay-description tags in your the JSX you parse to generate presets, to control:

  • Which instances in your JSX are imported as presets - to cherry-pick component instances in addition to using presetRules.
  • Metadata on the resulting preset - in this case name and description.

All instances parsed in your presetPaths files that are tagged with one of these data attributes will be imported to Interplay, even if they are not unique or do not match your presetRules. This can be a useful way of selecting particular instances to use as presets in existing files.

	import React from 'react';
import { Button, ButtonGroup } from 'reactstrap';
const Example = (props) => {
  return (
    <ButtonGroup data-interplay-name="ButtonGroup Example" data-interplay-description="Basic example with 3 Buttons">
      <Button data-interplay-name="Button Example" data-interplay-description="Default Button">Button</Button>
      <Button>Button</Button>
      <Button>Button</Button>
    </ButtonGroup>
  );
}
export default Example;

Troubleshooting Preset Parsing

Presets not imported

If you create preset files using the recommended format, the CLI parsing should find the instances of your components and generate preset configuration for them.

When parsing existing repo files that contain instances of your components, some instances may not be discovered or may not be converted to presets.

You can find two log files output by the CLI for further information:

.interplay/logs/summary.txt

This file contains a table for all the exports found in your component index, showing how was resolved to its source file, and any component properties that were found when parsing this source file.

.interplay/logs/presetSummary.json

This file contains an entry for each preset file parsed. If your file does not show up here then the CLI did not find your preset file - please check your presetPaths setting in interplay.config.js

For each file, counters are added for the number of instances of all the recognised components found in the preset file, which ones matched your presetRules.

For the instances that matched your preset rules, a further validation status is shown:

Imported - instance was imported as a preset.

UnknownComponent - instance was rejected because it contains a component that is not in recognised as being in component index - usually either a local formatting component (i.e. not exported from your index) or one that has been imported from a different location from where your component index resolved.

Did this answer your question?
😞
😐
🤩