How to use a Vue slot with a template #12691
-
I'm using the general pattern that this guy uses here. What can I do if I need a new variation called "Secondary", which needs to specify markup for a slot in the my-button component? Do I have to create a new template that has that slot configured, or is there some other way to accomplish that? Example: const Template = (args, { argTypes }) => ({ export const Primary = Template.bind({}); |
Beta Was this translation helpful? Give feedback.
Replies: 14 comments 26 replies
-
You can use the storybook props and use it within the slot, e.g.: const Template = (args, { argTypes }) => ({
props: Object.keys(argTypes),
components: { MyButton },
template: '<my-button @OnClick="onClick" v-bind="$props" > {{ slotcontent }} </my-button>',
});
export const Primary = Template.bind({});
Primary.args = {
primary: true,
label: 'Button',
};
export const Secondary = Template.bind({});
Primary.args = {
slotcontent: "Slot content here",
}; (I'm an absolute n00b on this, and found this question when I faced the same challenge. The above worked for me) |
Beta Was this translation helpful? Give feedback.
-
With Vue 3 and Storybook 6.2.0 beta we can make use slots as part of import { Meta, Story } from '@storybook/vue3'
import { defineComponent } from 'vue'
import Badge from './Badge.vue'
export default {
title: 'Badge',
component: Badge,
argTypes: {
default: {
control: 'text',
description: 'Slot content',
defaultValue: 'Badge'
}
}
} as Meta
export const Default: Story = args => defineComponent({
components: { Badge },
setup: () => ({ args }),
template: '<p-badge>{{ args.default }}</p-badge>'
}) And even change slot’ content: export const AnotherText = Template.bind({})
AnotherText.args = {
default: 'Another text'
} |
Beta Was this translation helpful? Give feedback.
-
Here's how I did it in Vue 2. I'm using this example button component with a default slot and an optional "hint" slot. Button.vue: <template>
<button @click="$emit('onClick')">
<slot>
Submit
</slot>
<div style="font-size:x-small;color:gray;">
<slot name="hint">
Click me
</slot>
</div>
</button>
</template>
<script>
export default {
}
</script> Button.stories.js: const Template = (args, { argTypes }) => {
return {
props: Object.keys(argTypes),
components: { Button },
template:
`
<Button @onClick="onClick" v-bind="$props">
<template v-if="${'default' in args}" v-slot>${args.default}</template>
<template v-if="${'hint' in args}" v-slot:hint>${args.hint}</template>
</Button>
`,
}
}
export const Base = Template.bind({})
Base.args = {}
export const WithDefaultSlotContent = Template.bind({})
WithDefaultSlotContent.args = {
default: `<strong>Go!</strong>`,
}
export const WithOptionalSlotContent = Template.bind({})
WithOptionalSlotContent.args = {
hint: `<div style="color:red;">Hurry</div>`,
} You can see we're using |
Beta Was this translation helpful? Give feedback.
-
Not directly answering the question here, but providing something useful which is related to the issue. If you define your templates using string literal syntax, then you can grab the template string from Button.vue <template>
<button>
<slot/>
</button>
</template> Foo.js export default {
template: `<p>Custom foo</p>`
}; Bar.js export default {
template: `<p>Custom bar</p>`
}; Button.stories.js import Button from './Button.vue';
import Foo from './Foo.js';
import Bar from './Bar.js';
const Template = (args, { argTypes }) => {
return {
props: Object.keys(argTypes),
components: { Button },
template:
`
<Button @onClick="onClick" v-bind="$props">
${args.slotTemplate}
</Button>
`,
}
}
export const Foo = Template.bind({})
Foo.args = {
slotTemplate: Foo.template
}
export const Bar = Template.bind({})
Bar.args = {
slotTemplate: Bar.template
} |
Beta Was this translation helpful? Give feedback.
-
Submit
Click me
<script>
export default {
}
</script>
|
Beta Was this translation helpful? Give feedback.
-
I got a new way use It use The complete example can be referred to <!-- args定义参考:https://storybook.js.org/docs/vue/writing-stories/args -->
<!-- argTypes定义参考:https://storybook.js.org/docs/react/essentials/controls#annotation -->
import {
Meta,
Canvas,
Story,
ArgsTable,
Description
} from '@storybook/addon-docs/blocks'
import BaButton from '../src/button.vue'
<Meta
title="Components/BaButton"
component={BaButton}
argTypes={{
backgroundColor: { control: 'color' },
size: { control: { type: 'select', options: ['small', 'medium', 'large'] } }
}}
/>
<!-- 创建容器组件 -->
export const Template = (args, storyCtx) => ({
// args包含storybook的所有参数,包含props、slots、listeners
// storyCtx除了包含args外,还包含更多额外信息
props: Object.keys(args), // 接收所有args上的参数,并将其定义为props,直接通过this可访问
mounted() {
console.log('storyCtx', storyCtx)
// 修复component name错误
const {
parameters: {
component: { name }
}
} = storyCtx
this._vnode.tag = name || 'BaButton'
},
render(h) {
return h(BaButton, {
props: this.$props,
scopedSlots: {
// 默认插槽
default: (props) => {
// default通过args绑定到props上,所以可以通过this.props直接访问
return this.default ? h(this.default, { props }) : void 0
},
// 具名插槽
icon: (props) => {
return this.icon ? h(this.icon, { props }) : void 0
}
}
})
}
})
# BaButton
<!-- 读取组件描述 -->
<Description of={BaButton} />
<Canvas>
<Story
name="Primary"
args={{
label: '按钮'
}}
>
{Template.bind({})}
</Story>
</Canvas>
<ArgsTable of={BaButton} />
## 尺寸
可以通过设置`size`控制按钮大小;可选值有`['small', 'medium', 'large']`;默认`medium`
<Canvas>
<Story
name="Large"
args={{
size: 'large',
label: '大小'
}}
>
{Template.bind({})}
</Story>
</Canvas>
## 颜色
可通过设置`backgroundColor`控制背景颜色;支持`hex、rgb、rgba、关键字`
<Canvas>
<Story
name="BgColor"
args={{
backgroundColor: 'red',
label: '颜色'
}}
>
{Template.bind({})}
</Story>
</Canvas>
## slot:icon
可通过`icon`插槽自定义图标样式
<!-- 由于story自动生成的code样例无法展示slot,建议通过docs.source.code手动定义 -->
<Canvas>
<Story
name="SlotIcon"
args={{
label: '按钮',
icon: {
// 以template形式声明组件
template: '<span>x</span>'
}
}}
parameters={{
docs: {
source: {
code: `
<template>
<BaButton>
<template #icon>
<span>x</span>
</template>
</BaButton>
</template>`
}
}
}}
>
{Template.bind({})}
</Story>
</Canvas>
|
Beta Was this translation helpful? Give feedback.
-
Here's how I am doing it. It's a mix between @camslice's answer and vue-kitchen-sink's example: Imagine you have this component: <template>
<button type="button">
<slot />
</button>
</template> And this is how currently our stories in MDX look like: import { Meta, Canvas, Story } from '@storybook/addon-docs'
import MyButton from './Button.vue'
<Meta title="Button" component={MyButton} />
export const Template = (args, { argTypes }) => ({
props: Object.keys(argTypes),
components: { MyButton },
template: `
<MyButton v-bind="filteredProps">
${args.slotTemplate}
</MyButton>`,
computed: {
/*
This excludes "slotTemplate" from props, so it's not shown
on the "Show code" block as a prop.
*/
filteredProps() {
const { slotTemplate, ...props } = this.$props
return props
},
},
})
# Button
These are our buttons:
<Canvas>
<Story
name="Basic"
args={{
slotTemplate: `Basic`,
}}
>
{Template.bind({})}
</Story>
<Story
name="Nested HTML"
args={{
slotTemplate: `<span>Some text <em> with nested <strong>HTML</strong></em></span>`,
}}
>
{Template.bind({})}
</Story>
<Story
name="Disabled"
args={{
slotTemplate: `Can't click me :(`,
disabled: true,
}}
>
{Template.bind({})}
</Story>
</Canvas> This is the result: The component's code is wrapped in a 💡
|
Beta Was this translation helpful? Give feedback.
-
This solution works for now, but as mentioned above in comments, you can edit only in "Docs"
|
Beta Was this translation helpful? Give feedback.
-
Only way I got it working as I wanted to was with const Template = (args: any) => ({
components: { ... },
setup() {
return { args };
},
template: `<widget v-bind="args" > ` +
`<template v-if="${'slotLeft' in args}" #left><div v-html="args.slotLeft"/></template> ` +
`<template v-if="${'slotRight' in args}" #right><div v-html="args.slotRight"/></template> ` +
`<template v-if="${'slotBottom' in args}" #bottom><div v-html="args.slotBottom"/></template> ` +
' </dt-widget-header>',
});
export const Default: Story = Template.bind({});
Default.args = {
//PROPS
...,
//SLOTS
slotLeft: '<span>LEFT SLOT</span>',
slotRight: '<span>RIGHT SLOT</span>',
slotBottom: '<span>BOTTOM SLOT</span>',
} |
Beta Was this translation helpful? Give feedback.
-
Another solution import { Meta, Story } from '@storybook/vue3'
import { defineComponent, h } from 'vue'
...
const Template: Story<typeof Component> = (args) =>
defineComponent({
components: {
Component,
},
setup() {
const slots = Object.keys(args.slots).reduce((prev, curr) => {
if (typeof args.slots[curr] === 'string') {
return { ...prev, ...{ [curr]: () => args.slots[curr] } }
}
return { ...prev, ...{ [curr]: () => h(args.slots[curr]) } }
}, {})
return function () {
return h(
Component,
{
...args.props,
},
slots
)
}
},
})
export const Default = Template.bind({})
Default.args = {
props: {
search: true,
searchPlaceholder: 'Zoek product',
addButton: true,
},
slots: {
button: 'Nieuw product1', // string is a valid VNode
sort: { // everything else lets wrap it into a h
data() {
return {
test: '1',
}
},
template: `{{ test }}`,
},
},
} |
Beta Was this translation helpful? Give feedback.
-
I have simplified template for multislots components. <my-button v-bind="args">
<template v-for="[name, content] in args.slots" #[name]>
{{ content }}
</template>
</my-button> then you have several options how to fill in slots // PrependSlot.args = { slots: Object.entries({ prepend: '#' })};
// PrependSlot.args = { slots: new Map([['prepend', '#']])};
PrependSlot.args = { slots: [['prepend', '# ']]};
// etc.. Happy coding :) |
Beta Was this translation helpful? Give feedback.
-
Best way I found:
|
Beta Was this translation helpful? Give feedback.
-
is this fixed in storybook 7? |
Beta Was this translation helpful? Give feedback.
Here's how I did it in Vue 2. I'm using this example button component with a default slot and an optional "hint" slot.
Button.vue:
Button.stories.js: