Storybook with web components - changing arguments dynamically on code

Viewed 39

I have a modal component and I'm writing the story for it. It looks something like this:

import { Story, Meta } from '@storybook/html';

export default {
  title: 'Components/Modal',
  argTypes: {
    open: {
      name: 'Opened',
      control: 'boolean'
    },
  },
  args: {
    open: false,
  }
} as Meta;

const Template: Story = (args) => {
  return `
    <my-modal open="${args.open}">
      Some example content inside the modal
    </my-modal>
  `;
};

export const Modal: Story = Template.bind({});

I have the arg open on the controls and I can change its value to true and the modal shows. But I would like the story to have a button and when it's clicked, the modal shows. I can't find a way to do this in the current version of Storybook for web components.

I've seen there are some hooks available for React (import { useArgs } from '@storybook/api';) that allows you to change the arguments value dynamically but I can't see how to do this for web components?

Any helps will be highly appreciated.

2 Answers

Doing that with native code isn't rocket science...

<my-dialog id="DIALOG" open>
  Hello *Native* Web Components world!
</my-dialog>

<button onclick="DIALOG.open()">OPEN</button>

<script>
  customElements.define("my-dialog", class extends HTMLElement {
    static get observedAttributes() {
      return ["open"];
    }
    constructor() {
      super() // sets and returns 'this'
        .attachShadow({mode:"open"}) // sets and return this.shadowRoot
        .innerHTML = `<dialog><slot></slot><button>Close</button></dialog>`;
      this.dialog = this.shadowRoot.querySelector("dialog");
    }
    connectedCallback() {
      this.onclick = () => this.close(); // or attach to button
    }
    attributeChangedCallback() {
      this.open();
    }
    open() {
      this.dialog.showModal(); // or .show()
    }
    close() {
      this.dialog.close();
    }
  });
</script>

Just add that button to the template:

import { Story, Meta } from '@storybook/html';

export default {
  title: 'Components/Modal',
  argTypes: {
    open: {
      name: 'Opened',
      control: 'boolean'
    },
  },
  args: {
    open: false,
  }
} as Meta;

const Template: Story = (args) => {
  return `
    <button 
      type="button" 
      onclick="this.nextElementSibling.open = !this.nextElementSibling.open">
        Toggle Modal
    </button>
    <my-modal .open=${args.open}>
      Some example content inside the modal
    </my-modal>
  `;
};

export const Modal: Story = Template.bind({});

Also, for boolean attributes - if implemented properly - you should work with the property (prefix it in the template with a .) rather than the attribute.

Related