I've used vue create to setup a new Vue project, and have installed Storybook - all working correctly.
I have then installed storybook-addon-designs and followed the readme on adding to my story, but it gives me the following error in my console: h is not defined.
Here's my files:
stories/2-PageTitle.stories.js:
import { withDesign } from 'storybook-addon-designs'
import {Button} from '../src/components/Button'
export default {
title: 'My stories',
component: Button,
decorators: [withDesign]
}
export const myStory = () => <Button>Hello, World!</Button>
myStory.story = {
name: 'My awesome story',
parameters: {
design: {
type: 'figma',
url: 'https://www.figma.com/file/LKQ4FJ4bTnCSjedbRpk931/Sample-File'
}
}
}
babel.config.js:
module.exports = {
presets: [
'#vue/cli-plugin-babel/preset'
]
}
.storybook/main.js:
module.exports = {
stories: ['../stories/**/*.stories.js'],
addons: ['storybook-addon-designs']
};
src/components/Button.vue:
<template>
<button>
{{ label }}
</button>
</template>
<script>
export default {
name: 'Button',
props: {
label: String
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
button {
background: red;
}
</style>
Can anyone see what I'm doing wrong here?
Full code here (I'd of done a Sandbox but because it uses Storybook this seems like a better way?): https://github.com/A7DC/storybookvueaddonstest
The author of storybook-addon-designs suggests the following:
You have to replace the export
const myStory = () => <Button>Hello, World!</Button>
You need to change this line (React story) to Vue's one. For example,
export const myStory = () => ({
components: { Button },
template: '<Button>Hello, World!</Button>'
})
Updated answer -
import { withDesign } from "storybook-addon-designs";
import Button from "../src/components/Button";
export default {
title: "My Stories",
decorators: [withDesign],
};
export const myStory = () => ({
components: { Button },
template: "<Button> Hello, World!</Button >",
});
myStory.story = {
name: "My awesome story",
parameters: {
design: {
type: "figma",
url: "https://www.figma.com/file/LKQ4FJ4bTnCSjedbRpk931/Sample-File",
},
},
};
Related
I have components like these
type TestComponentProps = {
title: string;
}
const TestComponent: React.FC<TestComponentProps> = ({
title,
}) => {
return <div>TestComponent: {title}</div>;
};
type TestComponent2Props = {
body: string;
}
const TestComponent2: React.FC<TestComponent2Props> = ({ body }) => {
return <div>TestComponent2: {body}</div>;
};
I would need an interface that would allow me to configure which component to render and get the props of that particular component
const dataToRender:Array<{
component: TestComponent | TestComponent2,
data: propsOf<component>
}> = [
{
component: TestComponent,
data: { title: '123' }
},
{
component: TestComponent2,
data: { body: 'lorem ipsum' }
}
];
Ideally I'd need to get the props of the particular component in a way "I want to render this component and I can only accept the correct props based on the props of that component"
You can do this, but not with plain typesciprt type annotations. In vue js for particular, for better typing you need to use defineComponent wrapper, that just return it argument (id) but with types.
In you case you can use this function.
function defineComponent<TProps, TModal extends React.ComponentType<TProps>>(x: {
component: TModal & React.ComponentType<TProps>
data: TProps
}) {
return x
}
And use it like so:
const dataToRender = [
defineComponent({
component: TestComponent,
data: { title: "123" },
}),
defineComponent({
component: TestComponent2,
data: { title: "lorem ipsum" }, // error here
}),
] as const
I created a server-side rendered Vue.js blog using Nuxt.js with Typescript and Ghost but I'm having some issues to update html metatag using data from asyncData().
From Nuxt.js documentation I know that asyncData() is called every time before loading page components and merges with component data.
I'm getting this error:
Property 'title' does not exist on type '{ asyncData({ app, params }: Context): Promise<{ title: string | undefined; excerpt: string | undefined; feature_image: Nullable | undefined; html: Nullable | undefined; }>; head(): any; }'.
This is my code:
<script lang="ts">
import { Context } from '#nuxt/types'
import { PostOrPage } from 'tryghost__content-api'
export default {
async asyncData ({ app, params }: Context) {
const post: PostOrPage = await app.$ghost.posts.read(
{
slug: params.slug
},
{ include: 'tags' }
)
return {
title: post.title,
excerpt: post.excerpt,
feature_image: post.feature_image,
html: post.html
}
},
head () {
return {
title: this.title,
meta: [
{
hid: 'description',
name: 'description',
content: this.excerpt
}
]
}
}
}
</script>
I already tried some solutions like using data() to set a default value for each item but nothing. Do you have any suggestion?
Without a typescript plugin like nuxt-property-decorator you won't have Typescript support for nuxt (either way, type checking and autocomplete still won't work).
That's why asyncData & fetch should be in Component options.
#Component({
asyncData (ctx) {
...
}
})
export default class YourClass extends Vue {
...
}
instead of
#Component
export default class YourClass extends Vue {
asyncData (ctx) {
...
}
}
If you still want to use asyncData() inside of your component class instead of setting the option, see this working example using the npm module nuxt-property-decorator.
Here is the working code after implementing the suggestion from #nonNumericalFloat :
import { Component, Vue } from 'nuxt-property-decorator'
import { Context } from '#nuxt/types'
import { PostOrPage } from 'tryghost__content-api'
import Title from '~/components/Title.vue'
#Component({
components: {
Title
}
})
export default class Page extends Vue {
post!: PostOrPage
async asyncData ({ app, params }: Context) {
const post: PostOrPage = await app.$ghost.posts.read(
{
slug: params.slug
},
{ include: 'tags' }
)
return {
post
}
}
head () {
return {
title: this.post.title,
meta: [
{
hid: 'description',
name: 'description',
content: this.post.excerpt
}
]
}
}
}
I have the following component, meant to minimally reproduce this issue:
<template>
<div>
foo is: {{foo}}
</div>
</template>
<script>
export default {
name: 'demo',
data: function() {
return {
foo: 'bar',
}
},
}
</script>
<style scoped>
</style>
With a corresponding story:
import { storiesOf } from '#storybook/vue';
import fetchMock from 'fetch-mock';
import Demo from '../src/components/Demo';
storiesOf('Demo', module)
.add('default', () => {
return {
components: {Demo},
template: '<div style="width: 500px"><Demo/></div>',
}
})
.add('foo is baz', () => {
return {
components: {Demo},
template: '<div style="width: 500px"><Demo/></div>',
data: () => ({
foo: 'baz',
}),
}
});
As you can see from this screenshot, the value of "foo" in the component doesn't update to "baz", even though I have specified data as a function in the return value from the story. What is going wrong?
I'm trying to close the configuration options of the vue-i18n behind a helper method that will generate them and use during creating Vue component.
If I'm setting the configuration options directly - everything is working. After moving the configuration into separate helper method the feature is
This is working:
<script>
export default {
name: 'Products',
nuxtI18n: {
paths: {
'de/de': '/produkte',
'ch/en': '/products',
'ch/de': '/produkte',
'eu/en': '/products',
}
}
}
</script>
This is not working:
<script>
const i18nPathTranslator = ({ en = false, de = false }) => ({
nuxtI18n: {
paths: {
'de/de': de,
'ch/en': en,
'ch/de': de,
'eu/en': en,
}
}
})
export default {
name: 'Products',
...i18nPathTranslator({
en: '/products',
de: '/produkte'
})
}
</script>
The result of the second code example is that the routes are not affected by the provided instructions.
I started to integrate a WYSIWYG into a blog project, I'm using Quill for this (I had no experience with it before). I was able to customize my editor the way it was required, what I don't understand is how to deal with text format and embed videos. I have two fields in my post form, "preview" and "content" (two quill editors) while introducing the text I can give format to it (header, italic, underline...etc) and when click the embed video option the editor allows me to add the link and visualize the embed video in that moment. When I press my save button it stores the post in my db but in my single post page I visualize all the fields without format (header, italic, underline...etc) and also no embed video. How can I give format and show the video? Any help would be appreciated.
I read the Quill documentation and tried to understand how to deal with this using deltas but I don't know how to make this work.
I'm using Meteor + React, this is my code (I'll show only relevant code):
This is my lib, quill.jsx
import React, { Component } from 'react';
import QuillLib from './vendor/quill.js';
import { ud } from '/helpers/lib/main.jsx';
class Quill extends Component {
constructor(props) {
super(props);
this.id = ud.shortUID();
}
componentDidMount() {
const that = this;
const toolbarOptions = [
[{ font: [] }],
[{ header: 1 }, { header: 2 }],
[{ header: [1, 2, 3, 4, 5, 6, false] }],
[{ align: [] }],
['bold', 'italic', 'underline', 'strike'],
['blockquote', 'code-block'],
[{ script: 'sub' }, { script: 'super' }],
[{ indent: '-1' }, { indent: '+1' }],
[{ color: [] }, { background: [] }],
['video'],
['image'],
];
const quill = new QuillLib(`#quill-editor-container-${this.id}`, {
modules: {
toolbar: toolbarOptions,
},
theme: 'snow',
});
const content = this.props.content;
quill.setContents(content);
quill.on('text-change', (delta) => {
if (that.props.onChange) {
that.props.onChange(quill);
}
});
}
render() {
return (
<div className="wysiwyg-wrapper">
<div id={`quill-editor-container-${this.id}`}></div>
</div>
);
}
}
export default Quill;
This is my input form component, list.jxs
import { Meteor } from 'meteor/meteor';
import { PostSchema } from '/modules/blog/lib/collections.jsx';
import Quill from '/modules/quill/client/main.jsx';
export class BlogCategory extends Component {
constructor(props) {
super(props);
this.state = {
post: {
content: '',
preview: '',
},
};
this.onPreviewChange = this.onPreviewChange.bind(this);
this.onContentChange = this.onContentChange.bind(this);
}
onPreviewChange(content) {
this.state.post.preview = content.getText();
this.setState(this.state);
}
onContentChange(content) {
this.state.post.content = content.getText();
this.setState(this.state);
}
save() {
const content = this.state.post.content;
const preview = this.state.post.preview;
const post = new PostSchema();
post.set({
content,
preview,
});
if (post.validate(false)) {
const id = post.save();
}
console.log(post.getValidationErrors(false));
}
renderCreatePostForm() {
let content;
if (this.state.showForm) {
content = (
<form action="">
<Quill
content={this.state.post.preview}
onChange={this.onPreviewChange}
/>
<Quill
content={this.state.post.content}
onChange={this.onContentChange}
/>
</form>
);
}
return content;
}
render() {
let content = (
<div className="col-xs-12">
{this.renderActions()}
</div>
);
if (!this.props.ready) {
content = <p>LOADING...</p>;
}
return content;
}
}
export default createContainer(() => {
const handleValidPost = Meteor.subscribe('posts');
return {
ready: handleValidPost.ready(),
posts: PostSchema.find({}).fetch(),
};
}, BlogCategory);
And finally my collections.jsx
import { Mongo } from 'meteor/mongo';
export const PostCollection = new Mongo.Collection('Posts');
export const PostSchema = Astro.Class({
name: 'PostSchema',
collection: PostCollection,
fields: {
content: {
validator : Validators.and([
Validators.required(),
Validators.string(),
Validators.minLength(3)
])
},
preview: {
validator : Validators.and([
Validators.required(),
Validators.string(),
Validators.minLength(3)
])
},
}
});
While getting Quill contents by getText, you lost your text format and video information. Using getText, all non-string data will be omitted. Quill data are defined as Delta (which is a JSON object).
You can fix this by updating your onPreviewChange and onContentChange to use getContents instead of getText. Save these Delta to DB and load it again.
onPreviewChange(content) {
this.state.post.preview = content.getContents();
this.setState(this.state);
}
onContentChange(content) {
this.state.post.content = content.getContents();
this.setState(this.state);
}