Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Core: Add 'mapping' to support complex arg values #14100

Merged
merged 8 commits into from
Mar 3, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions docs/writing-stories/args.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,37 @@ The `args` param is always a set of `key:value` pairs delimited with a semicolon

Args specified through the URL will extend and override any default values of args specified on the story.

## Mapping to complex arg values

Complex values such as JSX elements cannot be serialized to the manager (e.g. the Controls addon) or synced with the URL. To work around this limitation, arg values can be "mapped" from a simple string to a complex type using the `mapping` property in `argTypes`. This works on any type of arg, but makes most sense when used with the 'select' control.

```
argTypes: {
label: {
control: {
type: 'select',
options: ['Normal', 'Bold', 'Italic']
},
mapping: {
Bold: <b>Bold</b>,
Italic: <i>Italic</i>
}
}
}
```

Note that `mapping` does not have to be exhaustive. If the arg value is not a property of `mapping`, the value will be used directly. Keys in `mapping` always correspond to arg *values*, even when `options` is an object. Specifying `options` as an object (key-value pairs) is useful if you want to use special characters in the input label. For example:

```
{
control: {
type: 'select',
options: { да: 'yes', нет: 'no' }
},
mapping: { yes: 'да', no: 'нет' }
}
```

<details>
<summary>Using args in addons</summary>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,15 @@ export default {
title: 'Addons/Controls',
component: Button,
argTypes: {
children: { control: 'text', name: 'Children' },
children: { control: 'text', name: 'Children', mapping: { basic: 'BASIC' } },
type: { control: 'text', name: 'Type' },
json: { control: 'object', name: 'JSON' },
imageUrls: { control: { type: 'file', accept: '.png' }, name: 'Image Urls' },
label: {
name: 'Label',
control: { type: 'select', options: ['Plain', 'Bold'] },
mapping: { Bold: <b>Bold</b> },
},
},
parameters: { chromatic: { disable: true } },
};
Expand All @@ -17,7 +22,7 @@ const DEFAULT_NESTED_OBJECT = { a: 4, b: { c: 'hello', d: [1, 2, 3] } };

const Template = (args) => (
<div>
<Button type={args.type}>{args.children}</Button>
<Button type={args.type}>{args.label || args.children}</Button>
{args.json && <pre>{JSON.stringify(args.json, null, 2)}</pre>}
</div>
);
Expand Down
30 changes: 27 additions & 3 deletions lib/client-api/src/story_store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import deprecate from 'util-deprecate';

import { Channel } from '@storybook/channels';
import Events from '@storybook/core-events';
import { logger } from '@storybook/client-logger';
import { logger, once } from '@storybook/client-logger';
import {
Comparator,
Parameters,
Expand Down Expand Up @@ -383,8 +383,32 @@ export default class StoryStore {
const loaders = [...this._globalMetadata.loaders, ...kindMetadata.loaders, ...storyLoaders];

const finalStoryFn = (context: StoryContext) => {
const { passArgsFirst = true } = context.parameters;
return passArgsFirst ? (original as ArgsStoryFn)(context.args, context) : original(context);
const { args, argTypes, parameters } = context;
if (
argTypes &&
Object.values(argTypes).some(({ control }) => {
if (!control?.options) return false;
return Object.values(control.options).some((v: any) => v && typeof v === 'object');
})
) {
once.warn(
'Objects cannot be used as values in control options. Use a `mapping` instead.\n\nMore info: https://storybook.js.org/docs/react/writing-stories/args#mapping-to-complex-arg-values'
);
}

const { passArgsFirst = true } = parameters;
if (args) {
const mapped = {
...context,
args: Object.entries(args).reduce((acc, [key, val]) => {
const { mapping } = argTypes?.[key] || {};
acc[key] = mapping && val in mapping ? mapping[val] : val;
return acc;
}, {} as Args),
};
return passArgsFirst ? (original as ArgsStoryFn)(mapped.args, mapped) : original(mapped);
}
return passArgsFirst ? (original as ArgsStoryFn)(args, context) : original(context);
};

// lazily decorate the story when it's loaded
Expand Down