storybookjs / storybook

Storybook is the industry standard workshop for building, documenting, and testing UI components in isolation
https://storybook.js.org
MIT License
84.82k stars 9.34k forks source link

[Bug]: Multiple mapped args return array of labels, not args #22042

Open drunkenvalley opened 1 year ago

drunkenvalley commented 1 year ago

Describe the bug

Using a multi-select control such as multi-select, check, etc, will cause the mapped argument to only return an array containing the labels of the arguments.

For example, using the example on Dealing with complex values we have this story:

// Button.stories.ts|tsx

// Replace your-framework with the name of your framework
import type { Meta } from '@storybook/your-framework';

import { Button } from './Button';

import { ArrowUp, ArrowDown, ArrowLeft, ArrowRight } from './icons';

const arrows = { ArrowUp, ArrowDown, ArrowLeft, ArrowRight };

const meta: Meta<typeof Button> = {
  /* 👇 The title prop is optional.
   * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading
   * to learn how to generate automatic titles
   */
  title: 'Button',
  component: Button,
  argTypes: {
    arrow: {
      options: Object.keys(arrows), // An array of serializable values
      mapping: arrows, // Maps serializable option values to complex arg values
      control: {
        type: 'select', // Type 'select' is automatically inferred when 'options' is defined
        labels: {
          // 'labels' maps option values to string labels
          ArrowUp: 'Up',
          ArrowDown: 'Down',
          ArrowLeft: 'Left',
          ArrowRight: 'Right',
        },
      },
    },
  },
};

export default meta;

In the given example with the control's type set to select, selecting an item returns that item. For example, selecting "Up" will return ArrowUp.

However, if we replace that control's type to multi-select, selecting an additional item will return an array of labels instead, not an array of the selected items. For example, selecting "Up" and "Down" will return ["Up", "Down"] instead. (Or it should. I only tested with implicit labels. It may return ["ArrowUp", "ArrowDown"] instead.)

Current behavior:

When selecting one item it will return the object containing that item. When selecting multiple it returns an array of the options' labels.

Expected behavior:

When selecting one item it should return the object containing that item. When selecting multiple it should return an array or object containing all selected values.

Alternatively, when using controls allowing selecting multiple it should always return an array containing the selected objects.

To Reproduce

See reproduction stackblitz here: https://stackblitz.com/edit/github-yw4xwr?file=src/stories/List.stories.ts

// List.vue

<template>
  <ul>
    <li v-for="item in $slots.default" :key="item.key">
      <VNodes :vnode="item" />
    </li>
  </ul>
</template>
<script>
export default {
  components: {
    VNodes: {
      functional: true,
      render: (h, ctx) => ctx.props.vnode,
    },
  },
};
</script>
// ListTemplate.vue

<template>
  <List>
    <a v-for="{ slot, ...item } in parsedItems" :key="slot" v-bind="item">
      {{ slot }}
    </a>
    <div>
      Raw input from `items`: <br />
      <code>
        {{ items }}
      </code>
    </div>
  </List>
</template>
<script>
import List from './List.vue';

export default {
  components: {
    List,
  },
  props: {
    items: {
      type: [Array | Object],
      required: false,
      default: () => ({}),
    },
  },
  computed: {
    parsedItems() {
      if (Array.isArray(this.items)) {
        // since we currently receive strings with multiple it would break the template;
        // this is a patch to not disturb the reproduction
        const fixedStrings = this.items.map((item) => {
          if (typeof item === 'string') {
            return {
              href: 'https://fakedurl.com',
              slot: item,
            };
          }
          return item;
        });

        return fixedStrings;
      }
      if (Object.keys(this.items)) {
        return [this.items];
      }
      return [];
    },
  },
};
</script>
// List.stories.ts

import type { Meta, StoryObj } from '@storybook/vue';
import { StoryFn } from '@storybook/vue';

import Vue from 'vue';
// @ts-ignore
import Component from './List.vue';
// @ts-ignore
import Template from './ListTemplate.vue';

const items = {
  'Annual Report 2017': {
    slot: 'Annual Report 2017',
    href: 'https://example.com',
  },
  'Annual Report 2018': {
    slot: 'Annual Report 2018',
    href: 'https://example.com',
  },
};

const meta: Meta<typeof Component> = {
  title: 'List',
  component: Component,
};
export default meta;

const render: StoryFn = (args, { argTypes }) =>
  Vue.extend({
    props: Object.keys(argTypes),
    components: { Template },
    template: `<Template v-bind="$props"></Template>`,
  });

export const PageList: StoryObj<typeof Component> = {
  render,
  argTypes: {
    // default slot disabled here to avoid keyword error
    default: {
      table: {
        disable: true,
      },
    },
    items: {
      name: 'default',
      control: 'check',
      options: Object.keys(items),
      mapping: items,
      table: {
        category: 'slots',
      },
    },
  },
  args: {
    items: ['Annual Report 2017'],
    margin: true,
  },
};

System

Environment Info:

  System:
    OS: Windows 10 10.0.19045
    CPU: (32) x64 AMD Ryzen 9 7950X 16-Core Processor              
  Binaries:
    Node: 16.18.1 - C:\Program Files\nodejs\node.EXE
    npm: 8.19.2 - C:\Program Files\nodejs\npm.CMD
  Browsers:
    Edge: Spartan (44.19041.1266.0), Chromium (112.0.1722.39)      
  npmPackages:
    @storybook/addon-essentials: ^7.1.0-alpha.1 => 7.1.0-alpha.1   
    @storybook/addon-interactions: ^7.1.0-alpha.1 => 7.1.0-alpha.1 
    @storybook/addon-links: ^7.1.0-alpha.1 => 7.1.0-alpha.1        
    @storybook/blocks: ^7.1.0-alpha.1 => 7.1.0-alpha.1 
    @storybook/testing-library: ^0.0.14-next.2 => 0.0.14-next.2    
    @storybook/vue: ^7.1.0-alpha.1 => 7.1.0-alpha.1 
    @storybook/vue-vite: ^7.1.0-alpha.1 => 7.1.0-alpha.1 

Additional context

Reproduction should show as following.

No items

image

One item

image

Two items

image

Updated System info to result from reproduction repo.

shilman commented 1 year ago

ZOMG!! I just released https://github.com/storybookjs/storybook/releases/tag/v7.1.0-alpha.10 containing PR #22169 that references this issue. Upgrade today to the @future NPM tag to try it out!

npx sb@next upgrade --tag future

Closing this issue. Please re-open if you think there's still more to do.

shilman commented 1 year ago

ZOMG!! I just released https://github.com/storybookjs/storybook/releases/tag/v7.0.8 containing PR #22169 that references this issue. Upgrade today to the @latest NPM tag to try it out!

npx sb@latest upgrade