Render dynamic Vue 3 slot content in StorybookJS - vuejs3

Using the example Storybook code at the bottom of this post, I expect to see a Primary button rendered containing the text, "Primary Button", but instead the button renders following code, verbatim:
(...args) => {
// If a user calls a compiled slot inside a template expression (#1745), it
// can mess up block tracking, so by default we disable block tracking and
// force bail out when invoking a compiled slot (indicated by the ._d flag).
// This isn't necessary if rendering a compiled `<slot>`, so we flip the
// ._d flag off when invoking the wrapped fn inside `renderSlot`.
if (renderFnWithContext._d) {
setBlockTracking(-1);
}
const prevInstance = setCurrentRenderingInstance(ctx);
const res = fn(...args);
setCurrentRenderingInstance(prevInstance);
if (renderFnWithContext._d) {
setBlockTracking(1);
}
if (true) {
devtoolsComponentUpdated(ctx);
}
return res;
}
How do I render Vue component slot content in Storybook?
The Storybook code:
// ./components/button/Button.stories.js
import AtxButton from './Button';
import ButtonTypes from './Button.types';
export default {
/* πŸ‘‡ The title prop is optional.
* See https://storybook.js.org/docs/vue/configure/overview#configure-story-loading
* to learn how to generate automatic titles
*/
title: 'Button',
component: AtxButton,
argTypes: {
default: {
description: 'The default Vue slot',
control: 'text'
}
}
};
//πŸ‘‡ We create a β€œtemplate” of how args map to rendering
const Template = (args, { argTypes }) => ({
props: Object.keys(argTypes),
components: { AtxButton },
setup() {
//πŸ‘‡ The args will now be passed down to the template
return { args };
},
template: `
<AtxButton v-bind="args">{{ args.default }}
</AtxButton>
`
});
//πŸ‘‡ Each story then reuses that template
export const Primary = Template.bind({});
Primary.args = {
intensity: ButtonTypes.Intensity.Primary,
default: 'Primary Button'
};
export const Secondary = Template.bind({});
Secondary.args = {
intensity: ButtonTypes.Intensity.Secondary,
default: 'Secondary Button'
};

I had syntax error in my component code. The Button component is created using a render function, which includes slot content by accessing this.slots.default(). I was getting a console error claiming "this.slots.default() is not a function", so I changed it to this.slots.default, assuming it might have been a getter.
That wasn't the right solution, of course. I changed it back to this.slots.default(), and then ensured that slot content was always included in the Storybook story. Now it works perfectly!

Related

Gutenberg Richtext generating "&#xFEFF" in editor

I have used rich text element of Gutenberg, in editor i found this kind of entity coming when we are editing - https://prnt.sc/uqJZokTorIK_ , that is creating issue in layout, when we click its showing blank space.
/**
* WordPress dependencies
*/
import { __ } from "#wordpress/i18n";
import { RichText, useBlockProps } from "#wordpress/block-editor";
import { Platform } from "#wordpress/element";
export default function edit({ attributes, setAttributes }) {
const {circleHeadline,circleText} = attributes;
const blockProps = useBlockProps({
className: 'visual-circle__item',
});
const onTitleChange = (value) => {
const newTitle = { circleHeadline: value };
setAttributes(newTitle);
};
const onTextChange = (value) => {
const newText = { circleText: value };
setAttributes(newText);
};
return (
<li {...blockProps}>
<RichText
identifier="circleHeadline"
tagName="span"
className="visual-circle__title"
value={circleHeadline}
onChange={onTitleChange}
withoutInteractiveFormatting={true}
aria-label={__("Main Text")}
placeholder={__("Lorem")}
{...(Platform.isNative && { deleteEnter: true })} // setup RichText on native mobile to delete the "Enter" key as it's handled by the JS/RN side
allowedFormats={[""]}
/>
<RichText
identifier="circleText"
tagName="span"
className="visual-circle__text"
value={circleText}
onChange={onTextChange}
withoutInteractiveFormatting={true}
aria-label={__("Sub Text")}
placeholder={__("Nullam dictum eu pede")}
{...(Platform.isNative && { deleteEnter: true })} // setup RichText on native mobile to delete the "Enter" key as it's handled by the JS/RN side
allowedFormats={[""]}
/>
</li>
);
}
I have tried diffrent tag instead of span tag in rich text but issue is same.
I fixed this issue, i found that this character is coming in core blocks rich text element also, the issue in my code is i used span tag with display block css.

How to update Chakra UI theme.js file with Redux store value?

What do I want to do?
I want to change Chakra UI global theme.js file with redux store value which can be changed from the front end. Like from the front end a button to change all H1 tag colors to brand colors.
theme.js file
import { extendTheme } from "#chakra-ui/react";
import { store } from "./redux/store";
const getBgColor = (state) => {
return state.theme.bgColor;
};
let test = "red";
const Helper = () => {
const state = store.getState();
const color = getBgColor(state);
console.log("color", color);
test = color;
console.log(test);
};
store.subscribe(Helper);
console.log("test", test);
export const theme = extendTheme({
colors: {
brand: {
100: `${test}`
}
}
});
Currently, this is taking value from the redux store for the first time but not changing as per store value change. Inside the Helper function, it is listening changes but theme.js file is not getting updated.
You can check this code sandbox I am testing on...
CodeSandBox link: https://codesandbox.io/s/upbeat-khayyam-rietmr?file=/src/theme.js
Thanks in advance!

Storybook: how to validate form inside component on user interaction?

A Storybook newbie here. I have an Angular component with a FormControl, which when set to touched, might show an error (depending on the user interaction with the component itself). I've read this: https://storybook.js.org/docs/angular/writing-stories/decorators and tried adding a wrapper for the component which only contained a button. The idea was that user can click a button and thus validate the control inside the component. Didn't happen. Here is my setup:
export default {
title: 'Some title',
component: Component,
decorators: [
moduleMetadata: [...],
componentWrapperDecorator((story)=>`
<div>${story}</div>
<button (click)="doValidate"></button>
`),
]
} as Meta<Component>;
const template: Story<Component> = ( args: Component ) => {
component: Component,
props: args,
methods: {
doValidate(): void {
args.formControl.markAsTouched();
}
}
});
export const validate = template.bind({});
I concur that the method definition is out of place here. So here is the question: how do I trigger/set/mutate something inside a component from that wrapper?

Using Storybook v6 useArgs throws a TypeError

I'm trying to use the useArgs() hook to update args. Since #storybook/client-api is deprecated, I'm using the #storybook/api library instead. However, it throws a TypeError when I try to use it in a template:
import { useArgs } from "#storybook/api";
import Button from "./Button";
export default {
title: "Button",
component: Button,
} as ComponentMeta<typeof Button>;
const ToggleTemplate: ComponentStory<typeof Button> = (args) => {
const [_, updateArgs] = useArgs();
return <Button {...args} />;
};
export const Toggle: typeof Template = ToggleTemplate.bind({});
Toggle.args = {
children: "Toggle me!",
toggle: {
toggleFunc: () => {},
value: false,
},
};
The above throws TypeError: Cannot read properties of undefined (reading 'getCurrentStoryData'). When I comment out useArgs(), the error disappears. Does anyone know what I'm doing wrong?
You are using the useArgs from the wrong package. The #storybook/api is for creating an addon, not using it in a story (Storybook Docs: Addon API). For inside a story, install #storybook/client-api and change the import to import { useArgs } from '#storybook/client-api';

onMounted hook is not called by Vue

I have two components, one is managing data, the other is a vue template. I have simplified them here. The problem is that when the locations come in via the fetch, the locations in the vue template stays empty. I've checked with isRef() and that returns true, but it's just an empty array. Looking in the Vue dev tools panel, the locations does have elements in the array.
Locations.js
import {
ref,
isRef,
onMounted,
} from 'vue';
export default function useLocations () {
const locations = ref([]);
const loadImageData = (locId) => {
isRef(locations); // === true
// #FIXME locations.value is always empty here.
locations.value.forEach( (loc,key) => {
console.debug( loc.id, locId )
})
};
const getLocations = async () => {
const locs = await apiFetch({ path: '/wp/v2/tour-location'});
locations.value = locs;
};
onMounted( getLocations );
return {
locations,
getLocations,
loadImageData,
};
}
App.vue
<template>
<div class="location">
<h1>{{ location.name }}</h1>
<img :src="location.main_image" />
</div>
</template>
<script>
import useLocations from '#/composables/Locations';
export default {
name: 'Location',
props: [,'location'],
data () {return {}},
watch: {
location: {
// deep: true,
immediate: true,
handler: function(){
const { loadImageData } = useLocations();
loadImageData( location.id );
}
}
},
}
</script>
When loadImageData() is called from the Location.vue component, locations is always an empty array. Why doesn't it get updated in that function as it does in other places within the app?
onMounted is a hook registration function.
These lifecycle hook registration functions can only be used synchronously during setup(), since they rely on internal global state to locate the current active instance (the component instance whose setup() is being called right now). Calling them without a current active instance will result in an error.
[emphasis mine]
Docs
As you are using your useLocations composition function outside setup(), your getLocations function is never called and locations is always empty array
To explain it further. You do not have to call onMounted (or any other hook registration function) directly inside setup(). It is perfectly fine to place that call into separate composition function outside any component (as you did) but that function must then be used from inside the setup()

Resources