'How to map enum to select dropdown in Storybook?
I have a simple JS "enum" like this
const MyEnum = {
Aaa: 1,
Bbb: 84,
};
And I have a simple story:
import MyEnum from 'models/my-enum';
import HotSpot from 'hot-spot/hot-spot.vue';
import hotSpotProp from './hot-spot.stories.defaults';
export default {
title: 'components/catalog/images/HotSpot',
args: {
hotspotProp: hotSpotProp,
currentWidth: 360,
selectedCallouts: [],
calloutMode: true,
originalWidth: 2100,
title: 'Example tooltip',
},
argTypes: {
oemId: {
options: Object.keys(MyEnum), // an array of serializable values
mapping: MyEnum, // maps serializable option values to complex arg values
control: {
type: 'select', // type 'select' is automatically inferred when 'options' is defined
// labels: MyEnum,
},
},
},
};
const Template = (args, { argTypes }) => ({
components: { HotSpot },
template: `<HotSpot v-bind="$props" />`,
props: Object.keys(argTypes),
});
export const Default = Template.bind({});
Example from docs is not working.
I have a select dropdown working, but it returns a String instead of a Number from mapping.
I get an error in my storybook in the console:
[Vue warn]: Invalid prop: type check failed for prop "oemId". Expected Number with value NaN, got String with value "Aaa".
How to map enum to select dropdown in Storybook?
Solution 1:[1]
That storybook doc example is absolute horror. Here's an example that will instantly show you what to do.
myValueList: {
options: [0, 1, 2], // iterator
mapping: [12, 13, 14], // values
control: {
type: 'select',
labels: ['twelve', 'thirteen', 'fourteen'],
},
}
Solution 2:[2]
Enums end up as Objects, so:
enum Nums {
Zero,
One,
Two,
Three,
}
Seems to become an Object that looks like:
{
0: "Zero",
1: "One",
2: "Two",
3: "Three",
One: 1,
Three: 3,
Two: 2,
Zero: 0,
}
Since all object keys are strings or symbols in JavaScript, the only way I've been able to guarantee I only get the string values from an Enum is to use Object.values and filter strings:
oemId: {
options: Object.values(MyEnum).filter(x => typeof x === "string"),
mapping: MyEnum,
control: {
type: 'select',
},
},
Or, filter out the keys and retain an Object - this way Storybook can still default the value without issues:
options: Object.entries(MyEnum)
.filter(([, value]) => typeof value !== "string")
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {})
Solution 3:[3]
"Easy" (with this little helper):
function enumOptions(someEnum) {
return {
options: Object.keys(someEnum)
.filter((key) => !isNaN(parseInt(key)))
.map((key) => parseInt(key)),
mapping: someEnum,
control: {
type: 'select',
labels: Object.values(someEnum).filter(
(value) => typeof value === 'string'
),
},
}
}
This can the be used in the OP's example code as follows:
export default {
title: 'components/catalog/images/HotSpot',
args: {
oemId: MyEnum.MyValue // default value for all stories can be used,
// here "MyValue" will be preselected in dropdown
// (or individual `story.args` oemId value from MyEnum)
},
argTypes: {
oemId: enumOptions(MyEnum)
},
};
It is indeed surprising that this is not an out-of-the-box feature in storybook, requiring such a rather contrived workaround.
Thanks to @Anthony's and @Lee Chase's answers pointing in the right direction.
Solution 4:[4]
You are looking for Object.values not the .keys.
const MyEnum = {
Aaa: 1,
Bbb: 84,
};
Object.values(MyEnum); // -> [ 1, 84 ]
Object.keys(MyEnum); // -> [ "Aaa", "Bbb" ]
Solution 5:[5]
I have given up on mapping for now and use a computed value, it pollutes the template a bit but a utility function or two can make it look a little tidier.
argTypes: {
myValueList: {
options: [0, 1, 2], // iterator
control: {
type: 'select',
labels: ['twelve', 'thirteen', 'fourteen'],
},
}
}
// .
// .
// .
const mappingMyValueList = [12, 13, 14];
// .
// .
// .
computed() {
computedMyValueList() {
return () => mappingMyValueList[this.$props.myValueList];
}
}
// .
// .
// .
<div>{{computedMyValueList}}</div>
Solution 6:[6]
The easiest way without any helper for me was:
export default {
title: 'One/SingleBarItem',
component: SingleBarItem,
// ? Creates drowdown to select Phase enum values
argTypes: {
phase: {
options: Object.values(NodeExecutionPhase),
mapping: Object.values(NodeExecutionPhase),
control: {
type: 'select',
labels: Object.keys(NodeExecutionPhase),
},
},
},
} as ComponentMeta<typeof SingleBarItem>;
Where NodeExecutionPhase defined as:
enum Phase {
UNDEFINED = 0,
QUEUED = 1,
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Jingle |
| Solution 2 | |
| Solution 3 | Philzen |
| Solution 4 | Filip Seman |
| Solution 5 | Lee Chase |
| Solution 6 | Nastya Rusina |

