What are component presets?

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 telling the CLI .

  1. Configuring the CLI to parse components in your repo using an index file as an entry point
  2. Configuring the CLI to generate preset config from specific instances of each component:

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

interplay parse

During its run, the CLI will:

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 component instances are then compared with presetRules to determine which should be created as presets in Interplay. By default these rules pattern-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:

See Curating presets below for more details.

Supported Preset 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. For more details of this format, please see Curating your import

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:

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 path 
        "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": ".*",
    },
],

Wrapper components

<aside> 👉 You can provide a wrapper component to supply required context and styles to your components.

</aside>

Using an existing wrapper component

If the packages the CLI is importing already export a wrapper component you can configure this in the advanced settings page of the import wizard, or add it directly to the interplay.json settings file.

The setting takes the form packagename/ExportName:

//e.g. package
"wrapperComponent": "mypackagename/ThemeProvider"

//e.g scoped package
"wrapperComponent": "@myscope/mypackagename/ThemeProvider"

Interplay will wrap your components in this wrapper component wherever it displays your components.

Your wrapper component must be exported from one of the packages you are importing to Interplay (otherwise Interplay doesn't know about it).

Note that if your components require a theme to be present, your wrapper component should set a default theme. Interplay will soon support supplying configuration to the wrapper component such as multiple themes.

Create a custom wrapper component

If you don't have an existing wrapper component you can implement a custom wrapper as follows.

1. Create the wrapper component code

Create a wrapper component that supplies any required Providers.

Here is an example wrapper. As you can see, you can use multiple layers of providers if required:

import React from 'react';
import {Provider as StyletronProvider} from 'styletron-react';
import {Client as Styletron} from 'styletron-engine-atomic';
import {BaseProvider} from '../../src/index.js';
import {LightTheme} from '../../src/themes/index.js';
const engine = new Styletron();
export constThemeWrapper = ({children}) => (
    <>
      <StyletronProvidervalue={engine}>
      <BaseProvider
theme={LightTheme}
      >
        {children}
      </BaseProvider>
    </StyletronProvider>
    </>
  );

Once you've created your wrapper code, you can save this to your existing components folder, or store in the interplay folder in your repo.

2. Add the wrapper component to your index file

For Interplay to be able to run your wrapper component, it needs to be included in the components build created by the CLI.

To do this, add your wrapper to the index file exporting your components:

//index.js
export { ThemeWrapper } from "./components/ThemeWrapper.js"

3. Configure the wrapper component in interplay.json

Next we tell Interplay which of your components to use as the wrapper. To do this you add a setting to interplay.json as outlined above for existing wrapper components.

"wrapperComponent": "your-package-name/ThemeWrapper"

4. Re-run the CLI

Once the wrapper is added to the index, the CLI will then pick it up on the next import run, bundling it with the other components as usual.

If you are using a custom build process you will need to re-build your index after this wrapper component has been added, and then run the CLI.

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:

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']
}

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:

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