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.
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 .
presetPaths
controls which files are parsed looking for component instancespresetRules
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.jsOnce these settings are configured in interplay.config.js
, you can run the CLI parsing with:
interplay parse
During its run, the CLI will:
presetPaths
files, find all component instances that match the components in the indexFor 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.
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
.
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
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.
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": ".*",
},
],
<aside> 👉 You can provide a wrapper component to supply required context and styles to your components.
</aside>
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.
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.
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.
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.
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:
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']
}