Build CMS independent Twig Components in Storybook

A modern way of building, maintaining and documenting your visual driven twig components in storybook with an automated and opinionated integration workflow for your CMS.

1. Define your Design Tokens

Everything starts with well defined Design Tokens.
Collect all your spaces colors typography from your design before you start coding components. Good Design Tokens are a big timesaver!

Choose your technology:
TailwindCSS
SCSS

TailwindCSS is the API for your design system. All tailwind.config.js settings are automaticly documented in Storybook.

module.exports = {
 ...
 theme: {
   colors: {
     red: colors.rose,
     teal: {
       200: '#e6fffa'
       300: '#b2f5ea'
       500: '#00ffd1'
     },
     purple: {
       200: '#faf5ff'
       500: '#8000ff'
     },
   },
 ...
 };
import { Meta, ColorPalette, ColorItem, Subtitle } from '@storybook/addon-docs/blocks';
const tailwindConfig = require('../../config/silo/tailwind.json');
const colors = tailwindConfig.tailwind.theme.colors;

<Meta title="Tokens/Colors" />

# Colors Matter

## A palette of great-looking, well-balanced colors.
<ColorPalette>
{
  Object.keys(colors).map((key)=>{
      const color = typeof colors[ key ] === 'string' ? [ colors[ key ] ]: colors[ key ];
      const title = `.${ key }`;
      return (
        <ColorItem key={ key } title={ title } colors={ color } />
      )
  })
}
</ColorPalette>

Not every SCSS variable is a Design Token. Collect your Tokens in SCSS collections and export them. Wingsuit will document automaticly exported SCSS variables.

$primary:       $blue !default;
$secondary:     $gray-600 !default;
$success:       $green !default;
$info:          $cyan !default;
$warning:       $yellow !default;
$danger:        $red !default;
$light:         $gray-100 !default;
$dark:          $gray-800 !default;

$theme-colors: () !default;
$theme-colors: map-merge(
(
"primary":    $primary,
"secondary":  $secondary,
"success":    $success,
"info":       $info,
"warning":    $warning,
"danger":     $danger,
"light":      $light,
"dark":       $dark
),
$theme-colors
);
import { Meta, ColorPalette, ColorItem, Subtitle } from '@storybook/addon-docs/blocks';
const colors = require('tokens/exports/_colors.scss');

<Meta title=\"Tokens/Colors\" />

# Colors Matter

## A palette of great-looking, well-balanced colors.
<ColorPalette>
{
  Object.keys(colors).map((key)=>{
                                   const color = typeof colors[key] === 'string' ? [colors[key]] : colors[key];
                                   const title = `.${key}`;
                                   return (
  <ColorItem key={key} title={title} colors={color} />
                                   )
})
}
</ColorPalette>

2.1. Build your UI Patterns

When you are done with your design tokens you can continue with building your patterns. Start with your smallest components first, followed by the larger ones.

Choose your wingsuit.yml sections:
Fields
Settings
Variants

A field holds your data from your CMS.

Define settings to make your pattern configurable. Typical settings are colors, alignment or sizes.

A variant is a configuration of the pattern.

use: "@molecules/avatar/avatar.twig"
label: Avatar
description: "An avatar represents an ...."
fields: image: type: pattern label: Image preview: id: placeholder variant: image settings: style: 1x1_xs_sc text: type: text label: Text preview: faker: lorem.paragraph
settings: text_align: type: select label: Text align options: left: Left center: Center
variants: default: label: Default horizontal: label: Horizontal
{% set classes = [
  text_align == 'center' ? 'text-center',
  'bg-gray-100 rounded-xl overflow-hidden p-8',
  variant == 'horizontal' ? 'flex flex-row'
] %}

{% set image_classes = [
  'relative overflow-hidden flex-none w-48',
variant == 'default'? 'h-48 rounded-full mx-auto', variant == 'horizontal' ? 'h-auto -m-8 mr-8'
] %} <figure {{ attributes.addClass(classes) }} > <div {{ image_attributes.addClass(image_classes) }}>
{{ image }}
</div> <div class="pt-6 space-y-4 flex-1"> <blockquote> <p class="text-lg font-semibold">
{{ text }}
</p> </blockquote> <figcaption> <p class="text-teal-600">
{{ full_name }}
</p> </figcaption> </div> </figure>

2.2. Document your components

Choose your documentation way:
Automatic
MDX

Wingsuit provides Storybook DocBlocks as the building blocks to create full featured documentation right away from the wingsuit.yml.

avatar:
  use: "@molecules/avatar/avatar.twig"
  label: Avatar
  description: "An avatar represent a user, and displays the profile picture."
  fields:
    image:
      type: pattern
      label: Image
      description: The profile picture.
      preview:
        id: placeholder
        variant: image
        settings:
          style: 1x1_xs_sc
    text:
      type: text
      label: Text
      description: A short description of the avatar.
      preview:
        faker: lorem.paragraph
    full_name:
      type: text
      label: Full name
      description: The full name of the profile.
      preview:
        faker: name.findName
    button:
      type: pattern
      label: Button
      description: Button with link to profile detail page.
      preview:
        id: button
        variant: default
        settings:
          expanded: true

  settings:
    text_align:
      type: select
      label: Text align
      description: Align the text of the avatar.
      options:
        left: Left
        center: Center

  variants:
    default:
      label: Default
      description: Show
    horizontal:
      label: Horizontal
      fields:
        image:
          id: placeholder
          variant: image
          settings:
            style: 1x2_xs_sc'

You want to tell more about your component. With Wingsuit you can render your Twig component in your Storyboook MDX documentantion file.

import { Meta, Title, Subtitle } from '@storybook/addon-docs/blocks';
import { PatternLoad, PatternPreview } from '@wingsuit-designsystem/pattern-react/server';

<PatternLoad patternId="grid">
  {(pattern) => (
  <>
    <Title />
    <Subtitle />

## Simple usage

```twig dark
  {% set cells = [
    'Cell 1',
    'Cell 2'
  ] %}
  {% include "@organisms/grid/grid.twig" with {
    attributes: create_attribute(),
    cells: cells,
    columns: 2,
    columns_width: 'equal',
    gutter: 'default'
  } only %}
`} />
```

## Cells
The pattern loops through a list of cells and print each cell.<br/>
To adjust the way the blocks are printed use twig blocks.

## Blocks
* cell_outer: <br/>Use block cell_outer to adjust the markup of each cell. Variables: `cell`, `cell_counter`.
* cell_inner:<br/>Use block cell_inner to print the cell variable. Variables: `cell`, `cell_counter`
### Example: cell_inner
```twig dark
  {% set cells = [
        {content: '<div class="w-full h-20 bg-primary">1</div>'},
        {content: '<div class="w-full h-20 bg-primary">2</div>'},
        {content: '<div class="w-full h-20 bg-primary">3</div>'},
        {content: '<div class="w-full h-20 bg-primary">4</div>'},
        {content: '<div class="w-full h-20 bg-primary">5</div>'},
        {content: '<div class="w-full h-20 bg-primary">6</div>'},
      ] %}
      {% embed "@organisms/grid/grid.twig" with {
        attributes: create_attribute(),
        cells: cells,
        columns: 2,
        columns_width: 'equal',
        gutter: 'default'
      } only %}
        {% block cell_inner %}
          {{ cell.content }}
        {% endblock %}
      {% endembed %}
```

### Example: cell_outer
```twig dark
  {% set cells = [
        {content: '<div class="w-full h-20 bg-primary">1</div>'},
        {content: '<div class="w-full h-20 bg-primary">2</div>'},
        {content: '<div class="w-full h-20 bg-primary">3</div>'},
        {content: '<div class="w-full h-20 bg-primary">4</div>'},
        {content: '<div class="w-full h-20 bg-primary">5</div>'},
        {content: '<div class="w-full h-20 bg-primary">6</div>'},
      ] %}
      {% embed "@organisms/grid/grid.twig" with {
        attributes: create_attribute(),
        cells: cells,
        columns: 2,
        columns_width: 'equal',
        gutter: 'default'
      } only %}
      {% block cell_outer %}
        <div>
        {{ cell.content }} + { cell_counter }
        </div>
      {% endblock %}
      {% endembed %}
```

## Column widths
The pattern comes with most common column widths configurations like 50x50 or 33x66.
If you need additional configuration you can extends the columns configuration inside the `grid.wingsuit.yml`.

```yaml dark
columns:
2: << Columns count
  equal: 'grid-cols-1 md:grid-cols-2' << Column width 50x50
  '66x33': 'grid-cols-1 md:grid-cols-66/33' << Column width 66x33
  '33x66': 'grid-cols-1 md:grid-cols-33/66' << Column width 33x66
```

## Gutter
To configure the spacing between the columns use the gutter configuration in the grid.wingsuit.yml.
```yaml dark
  gutter:
    ...
    default: 'gap-4 md:gap-5 lg:gap-7'
    ...
```

## Examples
### 4 equal columns.
```twig dark
  {% include "@organisms/grid/grid.twig" with {
    cells: cells,
    columns: 4,
    columns_width: 'equal',
    gutter: 'default'
  } only %}
```

<PatternPreview variant={pattern.getDefaultVariant()} columns="4" config="equal"/>

### 3 Columns with 25% 50% 25%.

```twig dark
 {% include "@organisms/grid/grid.twig" with {
   cells: cells,
   columns: 3,
   columns_width: '25x50x25',
   gutter: 'default'
 } only %}
```

<PatternPreview variant={pattern.getDefaultVariant()} columns="3" columns_width="25x50x25"/>

  </>
  )}
</PatternLoad>

3. Make Love.

After finalizing your component, you can easily use it in other apps like Drupal.

Choose your flavor:
Drupal opinionated
Other CMS
UI Patterns
Presenter template

With UI Patterns no code is needed. The UI Patterns ecosystem offers a lot of modules that help you to manage the mapping without presenter templates. But there are still corners in Drupal where you need a presenter templates.

Don't forget to run yarn ws dev drupal

The video shows how to map an Drupal block to a pattern

{% set button %}
{% include "@atoms/button/button.twig" with {
  "text": "More"|t,
  "link": content.url
} %}
{% endset %}

{% include "@molecules/avatar/avatar.twig" with {
  "full_name": content.field_full_name,
  "text": content.body,
  "button": button
} %}

If you don't use Drupal install the cms app with yarn ws generate-app and run yarn ws dev cms to compile your CSS and templates.

You can also install the drupal app with yarn ws generate-app and run yarn ws dev drupal

{% set button %}
{% include "@atoms/button/button.twig" with {
  "text": "More"|t,
  "link": content.url
} %}
{% endset %}

{% include "@molecules/avatar/avatar.twig" with {
  "full_name": content.field_full_name,
  "text": content.body,
  "button": button
} %}

Try it now!

Get started using the automated command line tool. This command creates a Wingsuit demo project. The demo page uses the atomic design principle to structure the patterns..

Choose your flavor:
Drupal opinionated
Vanilla
npx @wingsuit-designsystem/cli init -k tailwind
npx @wingsuit-designsystem/cli init -k bootstrap
npx @wingsuit-designsystem/cli init -k vanilla-tailwind

# Add "cms" app and configure the `distFolder`, `assetAtomicFolder` to
# your template folder in your `wingsuit.config.js`:
yarn ws generate-app

# Start your app:
yarn ws dev cms
npx @wingsuit-designsystem/cli init -k vanilla-scss

# Add "cms" app and configure the `distFolder`, `assetAtomicFolder` to
# your template folder in your `wingsuit.config.js`:
yarn ws generate-app

# Start your app:
yarn ws dev cms

You might also try ...
Drupal Kickstarter

Wingsuit Kickstarter is the fastest way to start your Drupal project with Wingsuit. The Kickstarter uses UI Patterns to map Patterns to Drupal and Acquia Blt for automation.