Open andywd7 opened 4 years ago
@backbone87 we should figure out a way to trigger a re-render when globals change, similar to what we did for args.
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!
+1
@phated can you take a look at this in vue3?
Hi, is this still being looked at for Vue2? I tried both 6.2.9 and the latest 6.3.0 pre-release and it seems to still occur.
+1
Still occurs with v6.3.2, using Vue. I am unable to change locale with i18n, based on a global. Is this planned be fixed at last?
+1 Still occurs in 6.3.8. Have to use deprecated addon-contexts
package instead.
For anyone who wants some spaghetti code that forces it to work in the latest storybook by manually creating dom listeners to update the state:
// These should line up with the toolbar->items config in globalTypes
const themes = ['theme1', 'theme2', 'theme3', 'theme4']
const themeProvider = (story, context) => ({
components: { story },
data() {
return { theme: context.globals.theme, listenerElement: null }
},
// You have a reactive "theme" data object that you can reference in your template
template: `<div :data-theme="theme" class="@m-2"><story /></div>`,
// Functionality added below is for facilitating changes to the global theme variable
methods: {
handleThemeButtonClick() {
// Window of Story is encapsulated within canvas
if (window && window.parent) {
// Give some buffer time so that the storybook has time to re-render the state change after clicking the toolbar button
setTimeout(() => {
themes.forEach((themeId) => {
window.parent.document
.getElementById(themeId)
.addEventListener('click', () => {
this.theme = themeId
})
})
}, 100)
}
},
},
mounted() {
setTimeout(() => {
// Window of Story is encapsulated within canvas
if (window && window.parent) {
const themeButton = window.parent.document.querySelector(
// title value corresponds to description in globalTypes config
'[title="Global theme for components"]'
)
this.listenerElement = themeButton
themeButton.addEventListener('click', this.handleThemeButtonClick)
}
}, 100)
},
beforeDestroy() {
if (this.listenerElement) {
this.listenerElement.removeEventListener(
'click',
this.handleThemeButtonClick
)
}
},
})
For anyone facing this issue with Vue 2, here is how I solved it:
// .storybook/preview.js
import { i18n } from '@/plugins'; // the i18n variable contains a new VueI18n({...})
// Create a locale global and add it to toolbar
export const globalTypes = {
locale: {
name: 'Locale',
description: 'Internationalization locale',
defaultValue: 'en',
toolbar: {
icon: 'globe',
items: [
{ value: 'en', right: '🇺🇸', title: 'English' },
{ value: 'fr', right: '🇫🇷', title: 'Français' },
],
},
},
};
// IMPORTANT PART: This creates an observable variable that can be watched by vue
const locale = Vue.observable({ value: null })
// This is a storybook decorator
const withLocale= (story, context) => {
locale.value = context.globals.locale // Assign Storybook global to Observable variable
return {
i18n, // <= Add i18n to the decorator
template: '<story />',
created() {
this.$watch(() => {
// This function will be called whenever the storybook global changes
// In my case, I just want to update the i18n locale
this.$i18n.locale = locale.value
})
}
}
}
export const decorators = [withLocale];
Is there any workaround for React
@backbone87 we should figure out a way to trigger a re-render when globals change, similar to what we did for args.
@shilman I'm finding that decorators are passed stale args
(not globals) when you use change the story's controls. Your comment suggests that shouldn't be the case. Can you point me towards a PR that might shed some light on this?
In particular, "what we did for args": what did you do, and what did it fix/change?
I'm finding that decorators are passed stale
args
(not globals) when you use change the story's controls.
This is what brought me here as well. I'm trying to learn whether storybook currently supports a way for decorators to receive updated control (args) values when the args are changed via the storybook controls UI. Is this documented somewhere?
Any work around for react?
For people using Vue 3
import { ref, watch } from "vue";
const scheme = ref('light')
const withColorScheme: Decorator = (Story, context) => {
watch(
() => context.globals.scheme,
(newScheme) => scheme.value = newScheme,
{ immediate: true }
)
return {
components: { Story },
setup: () => ({ scheme }),
template: `
<div v-if="['both', 'light'].includes(scheme)">
<story />
</div>
<div v-if="['both', 'dark'].includes(scheme)" class="dark">
<story />
</div>
`
}
}
const withLocale: Decorator = (Story, context) => {
watch(
() => context.globals.locale,
(newLocale) => i18n.global.locale = newLocale,
{ immediate: true }
)
return {
components: { Story },
template: `<story />`
}
}
const preview: Preview = {
decorators: [withColorScheme, withLocale],
globalTypes: {
locale: {
description: "Internationalization locale",
defaultValue: "en",
toolbar: {
icon: "globe",
items: [
{ value: "en", left: "🇺🇸", title: "English" },
{ value: "fr", left: "🇫🇷", title: "Français" },
],
dynamicTitle: true
},
},
scheme: {
name: "Scheme",
description: "Select light or dark mode",
defaultValue: "Light",
toolbar: {
icon: "mirror",
items: [
{ value: "both", left: "🌗", title: "Both" },
{ value: "dark", left: "🌚", title: "Dark" },
{ value: "light", left: "🌝", title: "Light" },
],
dynamicTitle: true
}
}
},
// ...
}
@LeCoupa Worked for me, thank you! What is nice about this solution is it actually works for both single stories and Autodocs. It also helped me isolate my styles from storybook's styles, so they don't interfere that much.
However, I feel like this kind of functionality should be provided by the Toggles add-on instead of workarounds like this.
Is this still an issue? anyone have a solution for react?
Still not working on Vue
@marco-gagliardi Just scroll up and you'll see how to make it work for Vue 2 and Vue 3 !
Your comment adds no value to this thread
@bokub will I see a workaround or a final solution?
The issue would not be "open" if the problem was resolved by a long-term solution
That's why commenting "still not working" is pointless. We already know that it's "not working", we gave you workarounds, and you still think it's a good idea to spam the issue with complaints that add no value to the conversation?
It could have been defined spam or complaints if a solution plan was shared, otherwise the correct name is upvoting, and yes, it is generally a good idea to be sure an issue doesn't get stale (other people did the same). Cheers
For people using Vue 3
import { ref, watch } from "vue"; const scheme = ref('light') const withColorScheme: Decorator = (Story, context) => { watch( () => context.globals.scheme, (newScheme) => scheme.value = newScheme, { immediate: true } ) return { components: { Story }, setup: () => ({ scheme }), template: ` <div v-if="['both', 'light'].includes(scheme)"> <story /> </div> <div v-if="['both', 'dark'].includes(scheme)" class="dark"> <story /> </div> ` } } const withLocale: Decorator = (Story, context) => { watch( () => context.globals.locale, (newLocale) => i18n.global.locale = newLocale, { immediate: true } ) return { components: { Story }, template: `<story />` } } const preview: Preview = { decorators: [withColorScheme, withLocale], globalTypes: { locale: { description: "Internationalization locale", defaultValue: "en", toolbar: { icon: "globe", items: [ { value: "en", left: "🇺🇸", title: "English" }, { value: "fr", left: "🇫🇷", title: "Français" }, ], dynamicTitle: true }, }, scheme: { name: "Scheme", description: "Select light or dark mode", defaultValue: "Light", toolbar: { icon: "mirror", items: [ { value: "both", left: "🌗", title: "Both" }, { value: "dark", left: "🌚", title: "Dark" }, { value: "light", left: "🌝", title: "Light" }, ], dynamicTitle: true } } }, // ... }
it works but only in the current component
if I change to another vue storybook component and go back to the previous one the language change is no longer reactive
i mean: i18n.global.locale.value is now inmutable
You don't really need to watch for changes like the example above.
But, for some reason, you need to declare a ref
var outside the decorator method.
So, this will work:
import { Decorator } from '@storybook/vue3'
import { ref } from 'vue'
const theme = ref('light')
const withThemeDecorator: Decorator = (Story, context) => {
theme.value = context.globals.theme
return {
components: { Story },
setup: () => ({ theme }),
template: `
{{ theme }}
`
}
}
export default withThemeDecorator
I'm using Vue 3
+ SB 8.2.9
, and @LeCoupa 's solution works fine.
I've only tested theme switching, between component views, docs views and hard reloads. Everything seems to work. Did not try the language switcher though. Will make a note to scrutinize again in the future; perhaps one day the watcher isn't needed.
Describe the bug I have setup a toolbar and global decorator for changing a class to show theme changes. When I select an option from the toolbar, in Canvas, the session storage changes, if I console.log it it changes but the class doesn't change. It does work in Docs tab and if I select a different story. I'm guessing because it re-renders the DOM.
My example scenario is switching a light and dark theme class in a decorator using toolbar and globals.
To Reproduce Example repo: https://github.com/andywd7/storybook Example demo: https://trusting-montalcini-282027.netlify.app
Expected behavior I would expect the class in the decorator to change.
System