Skip to content

Commit

Permalink
feat: support all state props + options API
Browse files Browse the repository at this point in the history
  • Loading branch information
Akryum committed Apr 22, 2022
1 parent c6a5311 commit 1e1b1fc
Show file tree
Hide file tree
Showing 10 changed files with 264 additions and 31 deletions.
117 changes: 99 additions & 18 deletions docs/guide/controls.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,71 @@ Controls give you the ability to interact with your components arguments.

## Defining a state

The first step is to define the state that will be share to your story. You need to create a `state` data property or reactive object that will be automatically be picked up by Histoire. Then you can proceed using it as usual:
The first step is to define the state that will be share to your story. Histoire will automatically synchronize the `data` or reactive data returned in your `setup`. Then you can proceed using your state as usual.

```vue{5-8,14-15}
Example with Option API:

```vue{10-17}
<script lang="ts" setup>
import { reactive } from 'vue'
import MyButton from './MyButton.vue'
const state = reactive({
disabled: false,
content: "Hello world"
export default defineComponent({
components: {
MyButton,
},
data () {
// Histoire will inspect and synchronize this
return {
state: {
disabled: false,
content: "Hello world"
},
message: 'Meow!',
}
},
})
</script>
<template>
<Story>
<Variant>
<MyButton :disabled="state.disabled">
{{ state.content }}
</MyButton>
<input v-model.number="message">
</Variant>
</Story>
</template>
```

Example with Composition API:

```vue{18-22}
<script lang="ts" setup>
import { reactive, count } from 'vue'
import MyButton from './MyButton.vue'
export default defineComponent({
components: {
MyButton,
},
setup () {
const state = reactive({
disabled: false,
content: "Hello world"
})
const message = ref('Meow!')
// Histoire will inspect and synchronize this
return {
state,
message,
}
}
})
</script>
Expand All @@ -23,38 +78,52 @@ const state = reactive({
<MyButton :disabled="state.disabled">
{{ state.content }}
</MyButton>
<input v-model.number="message">
</Variant>
</Story>
</template>
```

::: tip
The reason you need to use the `state` name is that the preview might be rendered in a different context than the control pane, for example in an iframe. Therefore Histoire needs to handle the state and synchronize it between the different contexts. Currently, Histoire only detects the `state` property of your story component. If you use a different name, if will not be synchronized between the preview, the controls and the source code panes.
:::
If you are using the `<script setup>` syntax, the component is closed by default - meaning no properties can be accessed from the outside. We need to use [defineExpose](https://vuejs.org/api/sfc-script-setup.html#defineexpose) so that Histoire is able to access your state properties.

In the following example, since we are not using `state` as the name of our data, it won't be handled by Histoire:
Example with Composition API (Script Setup):

```vue{5-6}
```vue{12-16}
<script lang="ts" setup>
import { ref } from 'vue'
import { reactive, count } from 'vue'
import MyButton from './MyButton.vue'
// This is not named `state` therefore it will not be synchronized
const notSynced = ref('Meow')
const state = reactive({
disabled: false,
content: "Hello world"
})
const message = ref('Meow!')
// Histoire will inspect and synchronize this
defineExpose({
state,
message,
})
</script>
<template>
<Story>
<Variant>
<input v-model="notSynced">
<MyButton :disabled="state.disabled">
{{ state.content }}
</MyButton>
<input v-model.number="message">
</Variant>
</Story>
</template>
```

It can however be useful to declare some data that isn't going to be reactive, for example some fixture data or configuration:
It can also be useful to declare some data that isn't going to be reactive, for example some fixture data or configuration:

```vue{10-15}
```vue{14-19}
<script lang="ts" setup>
import { reactive } from 'vue'
import MyButton from './MyButton.vue'
Expand All @@ -64,6 +133,10 @@ const state = reactive({
colorId: 'primary',
})
defineExpose({
state,
})
// Some fixture/configuration data
const colors = {
primary: '#f00',
Expand Down Expand Up @@ -96,6 +169,10 @@ const state = reactive({
disabled: false,
content: "Hello world"
})
defineExpose({
state,
})
</script>
<template>
Expand All @@ -118,7 +195,7 @@ const state = reactive({

To build a control panel a bit more easily, Histoire provides builtin controls with design that fits the rest of the UI.

```vue{19-20}
```vue{23-24}
<script lang="ts" setup>
import { reactive } from 'vue'
import MyButton from './MyButton.vue'
Expand All @@ -127,6 +204,10 @@ const state = reactive({
disabled: false,
content: "Hello world"
})
defineExpose({
state,
})
</script>
<template>
Expand Down
30 changes: 30 additions & 0 deletions examples/vue3/src/components/StateOption.story.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<script>
export default {
data () {
return {
optionApiData: 'OPTION API',
}
},
}
</script>

<template>
<Story>
<Variant>
<h1>State</h1>
<div>
<pre>{{ { optionApiData } }}</pre>
<input
v-model="optionApiData"
>
</div>

<template #controls>
<HstText
v-model="optionApiData"
title="optionApiData"
/>
</template>
</Variant>
</Story>
</template>
17 changes: 13 additions & 4 deletions examples/vue3/src/components/StateSetup.story.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ const state = reactive({
text: 'Meow',
})
const notSynced = ref(42)
const synced = ref('hello')
defineExpose({
state,
synced,
})
</script>

<template>
Expand All @@ -17,7 +22,7 @@ const notSynced = ref(42)
<h1>State</h1>
<div>
<pre>{{ state }}</pre>
<div>{{ { notSynced } }}</div>
<div>{{ { synced } }}</div>
<input
v-model.number="state.count"
type="number"
Expand All @@ -26,8 +31,7 @@ const notSynced = ref(42)
v-model="state.text"
>
<input
v-model.number="notSynced"
type="number"
v-model="synced"
>
</div>

Expand All @@ -46,6 +50,11 @@ const notSynced = ref(42)
v-model="state.text"
title="Text"
/>

<HstText
v-model="synced"
title="synced"
/>
</template>
</Variant>
</Story>
Expand Down
83 changes: 83 additions & 0 deletions examples/vue3/src/components/StateSetup2.story.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<script lang="ts">
import { reactive, ref } from 'vue'
export default {
setup () {
const state = reactive({
count: 0,
text: 'Meow',
})
const synced = ref('hello')
return {
state,
synced,
}
}
}
</script>

<template>
<Story>
<Variant
title="default"
>
<h1>State</h1>
<div>
<pre>{{ state }}</pre>
<div>{{ { synced } }}</div>
<input
v-model.number="state.count"
type="number"
>
<input
v-model="state.text"
>
<input
v-model="synced"
>
</div>

<template #controls>
<div class="controls">
<button @click="state.count--">
-1
</button>
<button @click="state.count++">
+1
</button>
<span>{{ state.count }}</span>
</div>

<HstText
v-model="state.text"
title="Text"
/>

<HstText
v-model="synced"
title="synced"
/>
</template>
</Variant>
</Story>
</template>

<style scoped>
.controls {
margin: 12px;
display: flex;
align-items: center;
}
.controls > :not(:first-child) {
margin-left: 12px;
}
button {
padding: 6px;
border-radius: 4px;
cursor: pointer;
}
</style>
4 changes: 4 additions & 0 deletions examples/vue3/src/histoire.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,7 @@ html.htw-dark {
background: #27272a ;
color: #e9e9ed;
}

input {
border: solid 1px #ccc;
}
16 changes: 13 additions & 3 deletions packages/histoire/src/client/app/components/exposed/Story.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts">
import { computed, defineComponent, provide, useAttrs, VNode, h, PropType, getCurrentInstance } from 'vue'
import { computed, defineComponent, provide, useAttrs, VNode, h, PropType, getCurrentInstance, proxyRefs, markRaw, isRef } from 'vue'
import { Story } from '../../types.js'
import Variant from './Variant.vue'
Expand Down Expand Up @@ -27,8 +27,18 @@ export default defineComponent({
const storyComponent: any = getCurrentInstance().parent
// Allows tracking reactivity with watchers to sync state
const implicitState = storyComponent.proxy.state
provide('implicitState', implicitState)
const implicitState = {
$data: storyComponent.data,
}
// From `<script setup>`'s `defineExpose`
for (const key in storyComponent.exposed) {
implicitState[key] = storyComponent.exposed[key]
}
// We needs __VUE_PROD_DEVTOOLS__ flag set to `true` to enable `devtoolsRawSetupState`
for (const key in storyComponent.devtoolsRawSetupState) {
implicitState[key] = storyComponent.devtoolsRawSetupState[key]
}
provide('implicitState', () => implicitState)
return {
story,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ export default defineComponent({
const vm = getCurrentInstance()
const implicitState = inject('implicitState')
const implicitState = inject<() => any>('implicitState')
watch(() => implicitState, value => {
if (typeof props.initState !== 'function') {
attrs.variant.state = value
attrs.variant.state = value()
}
}, {
immediate: true,
Expand Down
Loading

0 comments on commit 1e1b1fc

Please sign in to comment.