woocommerce/plugins/woocommerce-blocks/storybook
Alexandre Lara 4efd2123a3
[Docs] Update links from WooCommerce.com to Woo.com for the Woo Blocks documentation (#43055)
* Update woocommerce.com URLs in documentation and code files

* Add changelog

* Fix github repository link in extend-rest-api-add-custom-fields.md

Co-authored-by: Albert Juhé Lluveras <contact@albertjuhe.com>

* Add github reporter (#42974)

* Add github reporter

* Add changelog

---------

Co-authored-by: Jon Lane <jon.lane@automattic.com>

* Fix product task redirect to support grouped and external products (#43051)

* Rearrange product redirection logic to better accept grouped and external produc types

* Add changelog

* Modify feedback modal actions (#43005)

* Adapt feedback modal actions

* Add changelogs

* Modify comments

* Fix tests

* Fix test

* Update class-wc-gateway-bacs.php (#43054)

* Update class-wc-gateway-bacs.php

Fix typo in textdomain

* Add changefile(s) from automation for the following project(s): woocommerce

---------

Co-authored-by: github-actions <github-actions@github.com>

* [Product Block Editor]: Add `Linked product` tab (#43009)

* add linked-products to group IDs

* add Linked Products tab

* tweak hideConditions condition

* changelog

* fix typo in doc comment

* Introduce a product type selection within the new experience (#41823)

* Create a relation between the product type and the product block template

* Add 'patterns' to name the kind of products that can be created for a specific template

* Resolve template using its id as a template query param

* Rename ProductEditPattern to ProductTemplate

* Rename get_patterns hook to woocommerce_product_editor_get_product_templates

* Return the list of templates to the client

* Set layout template events as array

* Register the layout template based on the product template or the post type in case of product variations

* Registering non supported product types

* Create and register the woocommerce/product-details-section-description block

* Add the product type to the section description

* Create product type selector

* Fix menu item style

* Highlight selected menu item

* Set the selected product template

* Set product template title to lowercase in the content description

* Rename blocks by blockTemplates under the AbstractBlockTemplate class

* Rename to woocommerce_product_editor_product_templates filter

* Remove product_template_ prefix from the supported_product_types map

* Rename get_formatted to to_JSON and convert the props to client side like

* Refactor get_product_templates

* Fix icon resolution

* Add a confirmation modal for unsupported product templates

* Add changelog files

* Remove product types using for testing

* Fix redirection when changing to a non supported product template

* Set the change button state to busy when it is saving the product

* Fix php linter errors

* Fix rebase conflict

* Move ProductTemplate to Automattic\WooCommerce\Admin\Features\ProductBlockEditor namespace

* Add the to_json definition to the BlockTemplateInterface

* Create default product template by custom product type if it does not have a template associated yet

* Fix some comments and product template creation validation

* Add support to load the product template icon from an external resource

* Fix php linter

* Fix the changelog description

* [Experimental] Interactivity Dropdown multi-select mode, ratings filter and introduce each directive (#42981)


---------
Co-authored-by: David Arenas <david.arenas@automattic.com>

* Introduce the transient files engine (#42877)

Co-authored-by: Corey McKrill <916023+coreymckrill@users.noreply.github.com>

* Change marketplace install API request to POST instead of GET (#43033)

* Change marketplace install API to using POST instead of GET
* Fix linting error
* Add changefile(s) from automation for the following project(s): woocommerce

---------

Co-authored-by: github-actions <github-actions@github.com>

* Prep trunk for 8.6 cycle (#43021)

Prep trunk for 8.6 cycle with version bump to 8.6.0-dev

Co-authored-by: WooCommerce Bot <no-reply@woo.com>

* Add Playwright tests for All Reviews, Reviews by Product and Reviews by Category blocks (#42903)

* Remove Reviews blocks Puppeteer tests

* Minor code cleanup

* Typos

* Create publishAndVisitPost() editor util

* Fix subcategories when importing products in Playwright and add reviews

* Add Reviews blocks tests in Playwright

* More typos

* Add changefile(s) from automation for the following project(s): woocommerce-blocks

* Create a 'reviews' object in data.ts so we can store reviews data in one single place

* Update test so instead of creating a new post in each test, we go to the already-created post

* Add source comments to reviews data to match it with the script

---------

Co-authored-by: github-actions <github-actions@github.com>

* Release: Remove 8.5 change files (#43022)

Delete changelog files from 8.5 release

Co-authored-by: WooCommerce Bot <no-reply@woo.com>
Co-authored-by: Alex López <alex.lopez@automattic.com>

* Delete changelog files based on PR 43033 (#43079)

Delete changelog files for 43033

Co-authored-by: WooCommerce Bot <no-reply@woo.com>

* Delete changelog files based on PR 43051 (#43081)

Delete changelog files for 43051

Co-authored-by: WooCommerce Bot <no-reply@woo.com>

* Interactive Price Filter: use `context` instead of `state` (#42980)

* feat: use context instead of state

* fix: temporary move the context to inner element for diffing to work

* fix: update context before navigation for optimistic UI

* Load google analytics gtag script asynchronously in WooCommerce Blocks (#43040)

Co-authored-by: github-actions <github-actions@github.com>

* set WOOCOMMERCE_BLOCKS_PHASE to 1 for the production build (#43074)

* set WOOCOMMERCE_BLOCKS_PHASE to 1 for the production build

* Add changefile(s) from automation for the following project(s): woocommerce

---------

Co-authored-by: github-actions <github-actions@github.com>

* Revert "Fix schedule sales error" (#43094)

Revert "Fix schedule sales error (#42700)"

This reverts commit 9b800aa179.

* [Product Block Editor]: add Linked product sections. First approach. (#43013)

* add Linked products, Upsell section

* changelog

* add Cross-lens section

* add links to the Upsell sections

* changelog

* fix lint issues

* fix lint issus

* fix linting issue :-|

* check whether the linked product group is defined

* [Product Block Editor]: introduce ShoppingBags component (#43042)

* add ShoppingBags component

* Add ShoppingBag story

* changelog

* Fix: Collection data being leaked between Collection Filters blocks (#43044)

* fix: CYS - change heading color (#43076)

* fix: CYS - change heading color

* Add changefile(s) from automation for the following project(s): woocommerce

---------

Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: Patricia Hillebrandt <patriciahillebrandt@gmail.com>

* Delete changelog files based on PR 43074 (#43118)

Delete changelog files for 43074

Co-authored-by: WooCommerce Bot <no-reply@woo.com>

* [Product Block Editor]: fix feature flag to hide the Linked products (#43119)

* fix flag to hide/show product editor

* changelog

* Add changelog

---------

Co-authored-by: Albert Juhé Lluveras <contact@albertjuhe.com>
Co-authored-by: Jonathan Lane <lanej0@users.noreply.github.com>
Co-authored-by: Jon Lane <jon.lane@automattic.com>
Co-authored-by: louwie17 <lourensschep@gmail.com>
Co-authored-by: Fernando Marichal <fernando.marichal@automattic.com>
Co-authored-by: Marc Guay <marc.guay@gmail.com>
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: Damián Suárez <rdsuarez@gmail.com>
Co-authored-by: Maikel David Pérez Gómez <maikel.perez@automattic.com>
Co-authored-by: Sam Seay <samueljseay@gmail.com>
Co-authored-by: Néstor Soriano <konamiman@konamiman.com>
Co-authored-by: Corey McKrill <916023+coreymckrill@users.noreply.github.com>
Co-authored-by: Kyle Nel <22053773+kdevnel@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: WooCommerce Bot <no-reply@woo.com>
Co-authored-by: Alex López <alex.lopez@automattic.com>
Co-authored-by: Tung Du <dinhtungdu@gmail.com>
Co-authored-by: Thomas Roberts <5656702+opr@users.noreply.github.com>
Co-authored-by: Luigi Teschio <gigitux@gmail.com>
Co-authored-by: Patricia Hillebrandt <patriciahillebrandt@gmail.com>
2023-12-29 12:28:11 -03:00
..
__mocks__ Expose cart errors as notices (https://github.com/woocommerce/woocommerce-blocks/pull/8162) 2023-01-19 16:40:52 +00:00
custom-controls Fix/you do not need lodash (https://github.com/woocommerce/woocommerce-blocks/pull/9161) 2023-04-28 11:29:45 +01:00
README.md [Docs] Update links from WooCommerce.com to Woo.com for the Woo Blocks documentation (#43055) 2023-12-29 12:28:11 -03:00
main.js Move all totals components into components package (https://github.com/woocommerce/woocommerce-blocks/pull/11773) 2023-11-20 12:53:41 +00:00
preview.js Add `FormStep` to Storybook (https://github.com/woocommerce/woocommerce-blocks/pull/11489) 2023-11-10 17:20:20 +00:00
style.scss Add Notices Documentation to Storybook and upgrade to Storybook 7 (https://github.com/woocommerce/woocommerce-blocks/pull/11524) 2023-11-02 13:01:42 +00:00
webpack.config.js Fix for storybook to ensure all components render (https://github.com/woocommerce/woocommerce-blocks/pull/11465) 2023-10-27 02:05:43 -07:00

README.md

Working with Storybook

Table of Contents

This document is meant to make contributing to our Storybook a bit easier by giving some tips, pointing out a few gotchas, making the editing experience smoother, and reducing the friction to adding a new story.

Adding new stories

To easily scaffold a new story for your component you need a few steps, but most things are already configured to reduce friction.

  1. Make sure you add a stories directory within your component directory.
  2. You can name the files within the directory however you want. Usually, it's best to have one index.tsx file including all your stories. However, if you are having several components under the same directory (e.g. Chip and RemovableChip), it might be best to have one file per component (see below).
  3. You will need a default export which defines the metadata for the component and one named export for each story you want to create.

Let's see the code in detail.

Scaffold tour

This is the minimal scaffold you need for your new Story:

import type { Story, Meta } from '@storybook/react';
import MyComponent, { MyComponentProps } from '..';

export default {
	title: 'WooCommerce Blocks/${MyCategory}/${MyComponent}',
	component: MyComponent,
} as Meta< MyComponentProps >;

const Template: Story< MyComponentProps > = ( args ) => (
	<MyComponent { ...args } />
);

export const Default = Template.bind( {} );
Default.args = {};

The above code will already generate a story that you can tweak, but let's look at the code in-depth.

First of all, by using TypeScript and exporting your component's props, Storybook will automatically show the documentation associated with your props and also generate the controls which are more appropriate for each property.

Also, if you follow the naming convention of starting your event handlers with on (e.g. onChange, onRemove, etc.), Storybook will automatically make those props uncontrollable and generate events from them (this convention can be changed in preview.js).

Sometimes you will need to manually change those assumptions, and we'll see how in a moment. But let's go step-by-step:

The default export

The default export defines the metadata for your component. The most important things you should be aware of:

{
  /**
   * This is how your component is going to be named in the Storybook UI.
   * You can use slashed-paths to define hierarchies and categories.
   * At the time of writing, we don't have well defined categories, but we
   * are working on making that clearer.
   */
  title: string;

  /**
   * You should always pass your component here.
   */
  component: ComponentType< any >;

  /**
   * You can define here the default props for the components on all your
   * stories. This is pretty useful when you have required props.
   */
  args: Partial< MyComponentProps >;

  /**
   * Here you can define how the Storybook controls look like. More info
   * below.
   */
  argTypes: Partial< ArgTypes< MyComponentProps > >;
}

Defining controls

As mentioned, Storybook will try to infer the best control for your property type (e.g. an on/off switch for a boolean, etc.). However, there are times in which you want different options.

Here is a link to the official Storybook documentation: https://storybook.js.org/docs/react/essentials/controls

But a TL;DR of most common usecases:

  • The shape of the prop looks like so:
{
  [$myPropName]: {
    control: { type: $controlType },
    // Only applicable for selects/radios and such
    options: $controlOptions
  }
}
  • You can disable a control like so:
{
  [$myPropName]: { control: false }
}

The Story template

The recommended way to create a story is by creating a template function and duplicating it for each story, to avoid extra scaffolding. The simplest form of the template is as follows:

const Template: Story< MyComponentProps > = ( args ) => (
	<MyComponent { ...args } />
);

In this way, you are rendering the component in the viewport and binding the component properties to the Storybook controls.

In this template function, however, you might put any extra logic that your component rendering might need. Common use-cases are wrapping them in context providers, or simulating controlled components.

Defining Stories

If you have followed this scaffold, defining a new story is as simple as this:

export const Default = Template.bind( {} );

Usually, you want a story with the name Default, and other stories with interesting variations of your component (see below). In order to create those variations, you should work with some of the properties of this function prototype. Here are the most common:

{
  /**
   * Define the properties to pass for this specific story. Often,
   * `Default` stories wouldn't even need this.
   */
  args: Partial< MyComponentProps >;

  /**
   * You will rarely need this, as story names are automatically generated
   * from your constant name. But that's good to know if you ever need.
   */
  storyName: string;
}

Full official docs: https://storybook.js.org/docs/react/writing-stories/introduction

Snippets

If you are using VSCode, this repo includes some helpful snippets that will become available inside .ts and .tsx files. You can find them inside .vscode/storybook.code-snippets.

The sbs (“Storybook story”) will scaffold the entire code in the section above. If you have respected the naming conventions, it will also properly import your component and the properties with the correct names, saving you a bunch of time.

The sbt (“Storybook story template”) will create a new story by binding your default template and prompting you to provide specific arguments.

FAQ

What should constitute a story?

Stories should show the component in its different shapes.

The most obvious way is when a component has a few variations (such as a button with a primary attribute being larger and bolder, for example).

However, a common misconception is that, since full controls are enabled and allow the user to explore the component by playing around with its properties interactively, there is no need to provide stories which are just the component with different default properties.

While this is true for many cases (often you don't need to create a different story by replacing the label of a component, if that's just text; though it might be argued that it could be interesting to have a story showing how a very long text behaves, especially in combined stories), it is very useful to create stories for components in their loading or error states.

Can I create stories with mixed components?

Yes, and it'd be awesome to see how our components interact with each other, especially in the context of blocks. At the time of writing, we have no such stories, however, here is a link to the official docs if you want to give a go at implementing this: https://storybook.js.org/docs/react/writing-stories/stories-for-multiple-components

Tips

One file per component

While stories for closely related components could technically be living within the same file, we advise that you keep one file per component, as this works best with the automatic organization of the components within storybook and using default exports in the Story.

An exception to this, might be when there is only one mainly used component and the other defined ones are just used internally.

E.g. compare Chip/RemovableChip with ProductPrice/Sale/Range. In the latter case, Sale and Range components are actually rendered depending on the props passed to the ProductPrice component, so they might as well be stories of their parent component.

Custom controls

Sometimes the inferred text input is not good enough, and you need something more complex. If those controls happen to be shared among many components, please write them in storybook/custom-controls.

An example there is the currency control. Since many of our components expect Currency objects in their props, we can make sure we give a few examples Storybook users can play with, without having to manually create the object themselves.

Let's take a look:

export const currencies: Record< string, Currency > = {
	EUR: {
		// ...
	},
	USD: {
		// ...
	},
} as const;

export const currencyControl = {
	control: 'select',
	defaultValue: currencies.USD,
	// This maps string keys to their values
	mapping: currencies,
	// These are the options which will appear in the <select> control
	options: Object.keys( currencies ),
};

Then we can just pass the currencyControl to our component argTypes.

export default {
	// ...
	argTypes: {
		currency: currencyControl,
	},
};

Common gotchas and examples

Named exports

Your component files can have default exports, but in order to play well with the automatic doc generation, they should always have named exports as well.

See: https://github.com/strothj/react-docgen-typescript-loader/issues/75

Controlled components

Your component is not managing its own state and expects it to be passed as a prop, but you want to create a self-contained story. You can then edit your main Template function to manage the state, for example through hooks.

const Template: Story< MyControlledComponentProps > = ( args ) => {
	const [ myState, setMyState ] = useState( 0 );

	const onChange = ( newVal ) => {
		args.onChange?.( newVal );
		setMyState( newVal );
	};

	return (
		<MyControlledComponent
			{ ...args }
			onChange={ onChange }
			state={ myState }
		/>
	);
};

You will notice that when you do this, your controls will not be in sync anymore: when you change the state through your story interaction, the control for state will not equal myState.

Often, it is enough to disable the control for state as it's not required.

If you want to keep them in sync, you'll have to use useArgs from the Storybook client API.

import { useArgs } from '@storybook/client-api';

const Template: Story< MyControlledComponentProps > = ( args ) => {
	const [ _, setArgs ] = useArgs();

	const onChange = ( newVal ) => {
		args.onChange?.( newVal );
		setArgs( { state: newVal } );
	};

	return <MyControlledComponent { ...args } onChange={ onChange } />;
};

Note that this makes things a bit more complex, so it is recommended to do only when it makes sense for a controlled value to still have a Storybook control.

At the time of writing, there is a known bug that doesn't keep number inputs in sync: https://github.com/storybookjs/storybook/issues/15924

Simulating interactions

First of all, note that all the props starting with on as described above trigger “actions” in Storybook, which is basically just a way to see how data gets passed to a handler via the Actions tab.

You can also manually mark props as actions in the argTypes, like so:

export default {
	// ...
	argTypes: {
		myHandler: {
			// <- this doesn't start with `on` so it needs manual config
			action: 'This text will show in the panel along with the data',
		},
	},
};

Full action docs: https://storybook.js.org/docs/react/essentials/actions

However, you might want to simulate some sort of behavior from your component, for example show how a Retry button triggers a loading state. In this case you can use useArgs:

const Template: Story< MyComponentProps > = ( args ) => {
	const [ { isLoading }, setArgs ] = useArgs();

	const onRetry = () => {
		args.onRetry?.();
		setArgs( { isLoading: true } );

		setTimeout(
			() => setArgs( { isLoading: false } ),
			INTERACTION_TIMEOUT
		);
	};

	return (
		<MyComponent { ...args } onRetry={ onRetry } isLoading={ isLoading } />
	);
};

We expose the INTERACTION_TIMEOUT constant from @woocommerce/storybook-controls' as a simple way to set our timeouts across our stories.

Context providers

See example: assets/js/base/components/country-input/stories/index.tsx


We're hiring! Come work with us!

🐞 Found a mistake, or have a suggestion? Leave feedback about this document here.