diff --git a/README.md b/README.md index 61d593b..a4d73a1 100644 --- a/README.md +++ b/README.md @@ -1,76 +1,76 @@ -# storybook-utils - -Utilities for Storybook. - -## createAddon - -Storybook addons are React components. -The `createAddon` function returns a React component that wraps a custom element and passes on properties and events. -This allows for creating addons with web components (and therefore LitElement). - -The wrapper can forward specific events to your addon (web component) as they occur. -Your addon can listen for these events. -Some useful Storybook events are forwarded by default (specifically `STORY_SPECIFIED`, `STORY_CHANGED`, `STORY_RENDERED`). -An `options` parameter can be passed to `createAddon` that contains additional events that you may need for your use case. - -`api` and `active` are required props when rendering the React component. - -```js // my-addon/manager.js import React from 'react'; import { STORY_RENDERED } from '@storybook/core-events'; import { addons, types } from '@storybook/manager-api'; import { createAddon } from '@web/storybook-utils'; +import { html, LitElement } from 'lit'; const { createElement } = React; +// Define the web component for the addon class MyAddonElement extends LitElement { constructor() { super(); - this.addEventListener(STORY_RENDERED, event => { - // handle Storybook event - }); - this.addEventListener('my-addon:custom-event-name', event => { - // handle my custom event - }); + // Bind the event handlers for consistent reference + this._handleStoryRendered = this._handleStoryRendered.bind(this); + this._handleCustomEvent = this._handleCustomEvent.bind(this); + } + + connectedCallback() { + super.connectedCallback(); + // Add event listeners when the component is attached to the DOM + this.addEventListener(STORY_RENDERED, this._handleStoryRendered); + this.addEventListener('my-addon:custom-event-name', this._handleCustomEvent); + } + + disconnectedCallback() { + // Remove event listeners when the component is detached from the DOM + this.removeEventListener(STORY_RENDERED, this._handleStoryRendered); + this.removeEventListener('my-addon:custom-event-name', this._handleCustomEvent); + super.disconnectedCallback(); + } + + _handleStoryRendered(event) { + // Handle the STORY_RENDERED event + console.log('Story rendered', event); + } + + _handleCustomEvent(event) { + // Handle custom addon events + console.log('Custom event', event); } render() { return html`
- + +

My addon content goes here

`; } } +// Define the custom element customElements.define('my-addon', MyAddonElement); +// Create the React wrapper using the `createAddon` utility const MyAddonReactComponent = createAddon('my-addon', { events: ['my-addon:custom-event-name'], }); -addons.register('my-addon', api => { +// Register the addon in Storybook +addons.register('my-addon', (api) => { addons.add('my-addon/panel', { type: types.PANEL, title: 'My Addon', render: ({ active }) => createElement(MyAddonReactComponent, { api, active }), }); }); -``` - -```js // my-addon/decorator.js import { addons } from '@storybook/preview-api'; -// ... -addons.getChannel().emit('my-addon:custom-event-name', {}); -// ... -``` - -Storybook expects only 1 addon to be in the DOM, which is the addon that is selected (active). -This means addons can be continuously connected/disconnected when switching between addons and stories. -This is important to understand to work effectively with LitElement lifecycle methods and events. -Addons that rely on events that might occur when it is not active, should have their event listeners set up in the `constructor`. -Event listeners set up in the `connectedCallback` should always also be disconnected. +// Emitting the custom event to the addon from a story +addons.getChannel().emit('my-addon:custom-event-name', { + detail: { message: 'This is a custom event message' }, +});