storybook
storybook copied to clipboard
(addon-controls) Support destructering object properties into individual controls
Is your feature request related to a problem? Please describe.
Im often passing objects to my (Vue) components which by default will be added as JSON.stringified text control by @storybook/addon-controls
. As I feel this is confusing/limited functionality (also because the JSON object isnt pretty printed (ie JSON.stringify(obj, null, 2)
) I'd like to destructure my object properties into separate controls for each individual object property.
I have currently implemented this as follows in this stripped-down User example below
User.vue
// User.vue
<template>
<p>The name of the user is: {{ user.name }}</p>
</template>
<script>
export default {
props: {
user: {
type: Object,
required: true
}
}
}
</script>
User.story.js
import User from '../User.vue'
export default {
title: 'User',
component: User,
argTypes: {
user_name: {
control: 'text',
defaultValue: 'pimlie',
table: {
category: 'user',
type: {
summary: 'text',
}
}
}
}
}
const Template = (args, { argTypes }) => {
console.log(argTypes)
return {
components: {
User
},
props: Object.keys(argTypes),
watch: {
user_name: {
immediate: true,
handler(value) {
// use $set to ensure reactivity in case name
// prop doesnt exist on this.user yet
this.$set(this.user, 'name', value)
}
}
},
template: '<User :user="user" />',
}
}
export const Primary = Template.bind({});
Primary.args = {
user: {
name: ''
}
}
(Note: in reality I have abstracted creating the argTypes & watchers to also easily support nested objects, eg to support a dot-path like blog.message.user.name
)
Describe the solution you'd like
I would like that addon-controls
would have improved support for the above (or a similar) strategy as the current approach of stringifying any object prop doesnt seem very user friendly
Also the above approach has several disadvantages:
- in your ArgsTable you will have both a
user
control with type object as auser_name
control with type text - the actual values of these 2 controls are in sync, so if you change the value of
user_name
the rendered component will show the new value. But the stringified value in theuser
object control will not be in sync, it will only show the second to last update. To explain by example:- Start with
user
&user_name
both with valuepimlie
for name - Change
user_name
control topimlie2
- The rendered component updates to
pimlie2
, but theuser
object control will still showpimlie
- Change
user_name
control topimlie
again - The rendered component updates to
pimlie
, but theuser
object control will now showpimlie2
- Start with
- atm it seems you are unable to change the name of a custom
argType
in the table. See screenshot below, you can group theuser_name
prop but I would also like the label in theArgTable
to read just name instead of user_name
Describe alternatives you've considered see above
Are you able to assist bring the feature to reality? sure, I can help with testing
Additional context
@shilman So what you think about my solution described in #12362? I can maybe PR that, if you accept my specs
@Gieted I like the proposal, but I need a few days to let it roll around in my head. Thanks for thinking the problem through and being patient about this!
Hi everyone! Seems like there hasn't been much going on in this issue lately. If there are still questions, comments, or bugs, please feel free to continue the discussion. Unfortunately, we don't have time to get to every issue. We are always open to contributions so please send us a pull request if you would like to help. Inactive issues will be closed after 30 days. Thanks!
What do you think about this as a workaround: https://github.com/storybookjs/storybook/pull/12685
By default, each field in a component's args is rendered as a single row. If a field is a complex type, it will get rendered using a JSON editor per #12599 (existing behavior but better UX).
As an opt-in, the user can also specify a custom argType:
export default {
title: ...,
component: ...,
argTypes: {
foo: { table: { expanded: true } }
}
}
In this case, the ArgsTable
will generate an extra row for each field of foo
with design TBD.
I duplicate our use-case from https://github.com/storybookjs/storybook/issues/12362
This is exactly what we need in our case where we feed our components with VOs wrapper around real data (JSON) with additional properties from other parts of the system, or precomputed (so component won't know about the store or any data transformation logic):
So VO looks like this:
class OrganizationInfoVO {
constructor(data, isDefaultOrganization, isCurrentUserOrganization) {
this.data = data;
this.isDefaultOrganization = isDefaultOrganization;
this.isCurrentUserOrganization = isCurrentUserOrganization;
}
get id() { return this.data.id; }
get name() { return this.data.name; }
get country() { return this.data.country; }
get picture() { return this.data.picture; }
get count() { return this.data.count; }
}
export default OrganizationInfoVO;
And it would be great to have a way of changing properties or visualize them in storybook panels. I think having at least some kind of "update callback" from control which called before component re-render would be great.
By default, each field in a component's args is rendered as a single row. If a field is a complex type, it will get rendered using a JSON editor per #12599 (existing behavior but better UX).
As an opt-in, the user can also specify a custom argType:
export default { title: ..., component: ..., argTypes: { foo: { table: { expanded: true } } } }
In this case, the
ArgsTable
will generate an extra row for each field offoo
with design TBD.
@shilman I'm not seeing this functionality. I have the custom argTypes
configuration as listed above, but it is still showing the JSON editor instead of a row for each field.
@jpmarra the issue is still open, meaning that it hasn't been added to storybook yet
@shilman Ah I misread, my bad!
Also really wanting this feature for a client. It seems like an obvious one. Passing a large object as a prop happens quite often. Or arrays of objects as well. That would another good one.
- An object prop - A control for editing properties of objects.
- An array of objects prop A control for editing adding an object to the array and then editing properties of the objects.
please, this is such a great improvement
🙏
It would be good if restructuring will be happening from TypeScript annotations.
Waiting on it
We'll be iterating https://github.com/storybookjs/storybook/pull/12824 and releasing in 6.2 as a short-term improvement in this space. We definitely see value in making the destructured UI as requested here, but don't have the bandwidth to handle it now. If anybody wants to take it on, I'd be happy to advise.
@shilman What happened to #12362?
Hey! I've found workaround for this case.
I'm using flat to flatten args
and unflatten flattenedArgs
.
Follow the below code example.
import React from 'react';
import { flatten, unflatten } from 'flat';
import { ComponentStory } from '@storybook/react';
import MyComponent from './MyComponent';
const delimiter = '__';
const getControlInfo = (name: string) => {
const [_root, group, subcategory, keyName] = name.split(delimiter);
return {
table: {
category: group,
subcategory: subcategory,
},
group,
subcategory,
keyName,
};
};
const getBaseControlInfo = (name: string) => {
const controlInfo = getControlInfo(name);
return {
name: controlInfo.keyName,
table: controlInfo.table,
};
};
export default {
title: 'MyComponent',
component: MyComponent,
};
const Template: ComponentStory<any> = (flattenedArgs) => {
const args: any = unflatten(flattenedArgs, { delimiter });
return <MyComponent {...args} />;
};
export const Primary = Template.bind({});
Primary.args = flatten(
{
root: {
group1: {
subcategory1: {
name: 'ashnamuh',
},
subcategory2: {
key: 'value!',
},
},
group2: {
subcategory3: {
foo: 'bar',
},
},
},
},
{ delimiter }
);
Primary.argTypes = {
root: {
table: { disable: true },
},
// self
root__group1__subcategory1__name: {
...getBaseControlInfo('root__group1__subcategory1__name'),
// Add your argTypes options
},
root__group1__subcategory2__key: {
...getBaseControlInfo('root__group1__subcategory2__key'),
// Add your argTypes options
},
root__group2__subcategory3__foo: {
...getBaseControlInfo('root__group2__subcategory3__foo'),
// Add your argTypes options
},
};
The solution doesn't work with autogenerated controls. Would be nice to have something native, coming from storybook
@ashnamuh thanks for the suggestion Flattening and unflattening worked wonders for my nested objects ❤️
Is there any progress with this?
would also love this feature 🥺🥺🥺
This seems so essential! I was shocked when reading the docs that this is not available :(
Hi. Any update on this @shilman ? It would be very valuable indeed
I'm facing the same problem.
Unlike most people, my aim is to specify a nested property as an action
in Storybook.
Either way, I haven't found anything in the docs or heard about a fix for this coming soon.
[...] my aim is to specify a nested property as an
action
in Storybook. [...]
@Victor-Nyagudi, I faced the same problem recently and found a way to solve it. It's not an automatic way to fix it, but it worked for me.
First, import the action factory from the Storybook action library:
import { action } from '@storybook/addon-actions'
Then, in the arguments of your story, create an action by calling the factory method, passing it the name you want to be displayed in Storybook when calling the action:
const onChangeAction = action('onChange') // the name 'onChange' can be anything you want
Finally, run the function you created earlier, passing it the values you want to display in Storybook:
// const value = "foo"
onChangeAction({ value }) // this will display 'onChange { value: "foo" }' in Storybook
Example:
import { action } from '@storybook/addon-actions'
// [...]
const SomeExampleStory = // ...
SomeExampleStory.args = {
someDeepObjectProp: {
onChange({ target: { value } }) {
const onChangeAction = action('onChange')
onChangeAction({ value })
}
},
// ...other props...
}
Of course, you need @storybook/addon-actions
to be installed as a dependency in your project, or @storybook/addon-essentials (because @storybook/addon-actions
is part of it).
⚠️ IMPORTANT: Please do not install
@storybook/addon-actions
and@storybook/addon-essentials
at the same time or it will not work.
+1 for this feature
+1
+1
+1
+100000