How to get storybook addon options - storybook

When we load a storybook addon we can pass some options:
// .storybook/main.js
module.exports = {
addons: [
'a-storybook-addon-without-options',
{
name: 'a-storybook-addon-with-options',
options: {
mainColor: 'hotpink',
},
},
],
};
To write my addon I use the addon API:
// /a-storybook-addon-with-options/src/register.js
import React from 'react';
import { addons, types } from '#storybook/addons';
import MyComponent from './myComponent.js';
const ADDON_ID = 'myaddon';
const PANEL_ID = `${ADDON_ID}/panel`;
addons.register(ADDON_ID, (api) => {
addons.add(PANEL_ID, {
type: types.PANEL,
title: 'My Addon',
render: MyComponent,
});
});
I don't know how to get the developer options from the addon code. Is there an official way? I didn't get a clear help from the documentation.

I think here you should use a parameter instead of this option in the config.
This kind of config (the options field) is more for presets that alter webpack or babel config.
If you add a parameter in the preview with the key that you specify in the register file then you can easily access that parameter in the addon code.
in preview.js
export const parameters = {
myAddon: {
mainColor: 'red'
},
};
in the addon code
import { useParameter } from '#storybook/api';
const PARAM_KEY = 'myAddon';
const MyPanel = () => {
const value = useParameter(PARAM_KEY, null);
const mainColor = value ? value.mainColor : 'No mainColor defined';
return <div>{mainColor}</div>;
};

You could set some default by setting a value when they don't set any value on the parameter like:
const mainColor = value ? value.mainColor : 'red';

Related

getStatic Path not working for base URL "/" in NextJS

I'm using Prismic and NextJS for the first time.
What I'm trying to is make it so when the user goes to the base url for the page localhost:3000/ in dev something will load. /About and /Pricing are working fine the base url doesn't work.
import { GetStaticPaths, GetStaticProps } from 'next'
import { SliceZone } from '#prismicio/react'
import * as prismicH from "#prismicio/helpers";
import { createClient, linkResolver } from '../../prismicio'
import { components } from '../../slices'
interface HomePageProps {
page: any
}
const HomePage = (props:HomePageProps) => {
return <SliceZone slices={props.page.data.slices} components={components} />
}
export default HomePage
interface HomePageStaticProps {
params: any
previewData:any
}
export async function getStaticProps(props:HomePageStaticProps) {
console.log("DOES NOT FIRE FOR localhost:3000")
const client = createClient({ previewData:props.previewData })
const params = props.params;
const uid = params?.pagePath?.[params.pagePath.length - 1] || "home";
const page = await client.getByUID("page", uid);
return {
props: {
page,
},
}
}
export const getStaticPaths: GetStaticPaths = async () => {
const client = createClient();
const pages = await client.getAllByType("page");
const paths = pages.map((page) => prismicH.asLink(page, linkResolver)) as string[];
console.log(paths) // [ '/pricing', '/about', '/' ]
return {
paths,
fallback: false,
};
}
or to simplify it further
[[...pagePath]].tsx fails when going to localhost:3000/ but does not fail on localhost:3000/about or localhost:3000/pricing.
import { GetStaticPaths, GetStaticProps } from 'next'
interface HomePageProps {
page: string
}
const HomePage = (props:HomePageProps) => {
return <>{props.page}</>
}
export default HomePage
interface HomePageStaticProps {
params: any
previewData:any
}
export async function getStaticProps(props:HomePageStaticProps) {
const params = props.params;
const uid = params?.pagePath?.[params.pagePath.length - 1] || "home";
//const page = await client.getByUID("page", uid);
return {
props: {
page:uid,
},
}
}
export const getStaticPaths: GetStaticPaths = async () => {
const paths = [ '/pricing', '/about', '/' ];
return {
paths,
fallback: false,
};
}
As far as I can see your'e not fetching the right way. In order to to have a clean project I would recommend to use a const var where you can determine between dev and production enviorenment. To do so you can simply create a file for example: constants.js containing the following:
export const baseurl = process.env.NODE_ENV === "production"
? process.env.NEXT_PUBLIC_DOMAIN // <-- your domain on live
: "http://localhost:3000"; // localhost on dev
Now with this you automatically have localhost on your dev. Notice that you need http:// which your'e missing at the moment. Now the next example shows you how to fetch something on your root / by entering the following code:
import { baseurl } from "../utils/constants"; // <-- importing the constant
// This function gets called at build time on server-side.
// It won't be called on client-side, so you can even do
// direct database queries.
export async function getStaticProps() {
// Call an external API endpoint to get posts.
// You can use any data fetching library
const res = await fetch(`${baseurl}/api/posts`)
const posts = await res.json()
// By returning { props: { posts } }, the Blog component
// will receive `posts` as a prop at build time
return {
props: {
posts,
},
}
}
If you are using Create-T3-App
https://github.com/t3-oss/create-t3-app
then
your next.config.mjs will default to this as of Nov 7, 2022
const config = {
reactStrictMode: true,
swcMinify: true,
i18n: {
locales: ["en"],
defaultLocale: "en",
},
};
export default config;
remove
const config = {
reactStrictMode: true,
swcMinify: true,
//i18n: {
// locales: ["en"],
// defaultLocale: "en",
//},
};
export default config;
This will make default "/" pathing work, if you require i18n, I'm sorry I can't help.

Next.js middleware get available locales

i created i18n.json
i18n.json
{
"locales": ["en-US", "tr-TR"],
"defaultLocale": "tr-TR",
"defaultNS": "common",
"pages": {
"*": ["common"]
}
}
i want to get locales ["en-US", "tr-TR"] values in _middleware.tsx
i created _middleware.tsx and
I have read the directions in this document
https://nextjs.org/docs/api-reference/next/server#nextrequest
nextUrl includes i18n
so i want to reach locales in i18n object but I get undefined value.
import { NextResponse, NextRequest } from "next/server";
export async function middleware(req: NextRequest) {
const { i18n } = req.nextUrl;
const { locales } = i18n;
console.log(locales);
return NextResponse.next();
}
How can I get the available locales values?
I'm not quite sure why you need middleware for this case, you can get all locales from the SSR props, but you need to configure
First, you need to define your locales in next-18next.config.js
module.exports = {
i18n: {
defaultLocale: "de",
locales: ["de", "en"],
},
};
Then in next page get all locales with SSR Props
export const getServerSideProps: GetServerSideProps = async ({ locales }) => {
console.log(locales) // You should get an array of all locales
}
For now, best way to do this is importing next-i18next.config.js manually.
next-i18next.config.js
module.exports = {
i18n: {
defaultLocale: "en",
locales: ["en", "tr"],
localeDetection: false,
},
};
middleware.js
import { i18n } from "./next-i18next.config.js";
export function middleware(req) {
const availableLocales = i18n.locales;
}
You can import your i18n.json to achieve same.

How to use SSR with Stencil in a Nuxt 3 Vite project?

In Nuxt 2 I could use server-side rendered Stencil components by leveraging the renderToString() method provided in the Stencil package in combination with a Nuxt hook, like this:
import { renderToString } from '[my-components]/dist-hydrate'
export default function () {
this.nuxt.hook('generate:page', async (page) => {
const render = await renderToString(page.html, {
prettyHtml: false
})
page.html = render.html
})
}
Since the recent release of Stencil 2.16.0 I'm able to use native web components in Nuxt 3 that is powered by Vite. However I haven't found a way to hook into the template hydration process. Unfortunately there is no documentation for the composable useHydration() yet.
Does anybody know how I could get this to work in Nuxt 3?
I had the same problem. I solved it via a module.
Make a new custom nuxt module. documentation for creating a module
In the setup method hook into the generate:page hook:
nuxt.hook('generate:page', async (page) => {
const render = await renderToString(page.html, {
prettyHtml: true,
});
page.html = render.html;
});
documentation for nuxt hooks
documentation for stencil hydration (renderToString)
Register the css classes you need via nuxt.options.css.push(PATH_TO_CSS)
Register the module in the nuxt config.
Note: Make sure in the nuxt.config.ts the defineNuxtConfig gets exported as default.
Tap the vue compiler options in the nuxt config:
vue: {
compilerOptions: {
isCustomElement: (tag) => TEST_TAG_HERE,
},
},
This depends on how you wan't to use the custom elements. In my case I defined the elements over the stencil loader in my app.vue file:
import { defineCustomElements } from '<package>/<path_to_loader>';
defineCustomElements();
You could also import the elements you need in your component and then define them right there, for example in a example.vue component:
import { CustomElement } from '<package>/custom-elements';
customElements.define('custom-element', CustomElement);
Here is an example from my module and config:
./modules/sdx.ts
import { defineNuxtModule } from '#nuxt/kit';
import { renderToString } from '#swisscom/sdx/hydrate';
export default defineNuxtModule({
meta: {
name: '#nuxt/sdx',
configKey: 'sdx',
},
setup(options, nuxt) {
nuxt.hook('generate:page', async (page) => {
const render = await renderToString(page.html, {
prettyHtml: true,
});
page.html = render.html;
});
nuxt.options.css.push('#swisscom/sdx/dist/css/webcomponents.css');
nuxt.options.css.push('#swisscom/sdx/dist/css/sdx.css');
},
});
Important: This only works if the stenciljs package supports hydration or in other words has a hydrate output. Read more here
./nuxt.config.ts
import { defineNuxtConfig } from 'nuxt';
//v3.nuxtjs.org/api/configuration/nuxt.config export default
export default defineNuxtConfig({
typescript: { shim: false },
vue: {
compilerOptions: {
isCustomElement: (tag) => /sdx-.+/.test(tag),
},
},
modules: ['./modules/sdx'],
});
./app.vue
<template>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</template>
<script setup lang="ts">
import { defineCustomElements } from '#swisscom/sdx/dist/js/webcomponents/loader';
defineCustomElements();
// https://v3.nuxtjs.org/guide/features/head-management/
useHead({
title: 'demo',
viewport: 'width=device-width, initial-scale=1, maximum-scale=1',
charset: 'utf-8',
meta: [{ name: 'description', content: 'demo for using a stencil package in a nuxt ssr app' }],
bodyAttrs: {
class: 'sdx',
},
});
</script>
Update
I tested my setup with multiple components and it looks like you cannot define your components in the module. I updated the answer to my working solution.
I've found defining a plugin using the 'render:response' hook to work for me:
server/plugins/ssr-components.plugin.ts
import { renderToString } from '#my-lib/components/hydrate';
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('render:response', async (response) => {
response.body = (await renderToString(response.body)).html;
});
});
Perhaps it will work for you :)
Try this in defineNuxtPlugin
nuxtApp.hook('app:rendered', () => {
const response = nuxtApp.ssrContext?.res
if (!response)
return
const end = response.end
response.end = function(chunk) {
chunk = 'hijacked'
end(chunk)
}
})

Adding External Script to Specific Story in Storybook

I wanted to know how I can load in external javascript into a specific story in storybook. The only documentation I can find right now is how to do it globally https://storybook.js.org/docs/react/configure/story-rendering. Doing this works, i would just like to save on performance since only one of my stories uses an external js script.
No, there isn't a standard way to do this in storybook currently (version 6.5).
However you can achieve it with a decorator.
Depending on your needs it could look something like this (this is for a React story):
import { Story, Meta } from '#storybook/react';
import { useEffect } from '#storybook/addons';
export default {
title: 'My Story',
component: MyStory,
decorators: [
(Story) => {
useEffect(() => {
const script = document.createElement('script');
script.src = '/my-script';
document.body.appendChild(script);
}, []);
return <Story />;
},
],
};
There are some caveats here though:
The scripts will remain loaded as you navigate to other stories.
The story will render before the script has loaded.
To handle these caveats you can:
Add a cleanup handler to useEffect.
Don't render your <Story/> until the script has loaded.
For example:
import { Story, Meta } from '#storybook/react';
import { useEffect, useState } from '#storybook/addons';
export default {
title: 'My Story',
component: MyStory,
decorators: [
(Story) => {
const [isLoaded, setIsLoaded] = useState(false);
useEffect(() => {
const script = document.createElement('script');
script.onload = () => {
setIsLoaded(true);
};
script.src = '/my-script';
document.body.appendChild(script);
return () => {
// clean up effects of script here
};
}, []);
return isLoaded ? <Story /> : <div>Loading...</div>;
},
],
};
If you have multiple scripts you'll have to wrap all the onload events into a Promise.all.
This could be wrapped up in an addon similar to storybook-addon-run-script.

Vue3 testing ElementPlus controls with vitest

I am running Vue3 with vite and can't write any tests for components that use the ElementPlus library. Something else needs to be injected apparently but I don't know how to do that.
I have the following dateControl.test.js:
import { describe, expect, test } from 'vitest';
import { ref } from 'vue';
import DateCtrl from '#/components/DateCtrl.vue';
import { mount } from "#vue/test-utils";
import ElementPlus from "element-plus";
describe("DateCtrl.vue", () => {
const messages = {
"en-US" : {
strings: {
placeholder: 'a',
label: 'b'
}
}
};
const locale = "en-US";
const data = ref ({
date: ''
});
test ("Arrange DateCtrl", async () => {
const component = mount(DateCtrl, {
props: {
vModel: data.value.date,
modelValue: data.value.date,
labelLoc: "label",
className: "w1x5",
placeholderLoc: "date"
},
global: {
plugins: [ElementPlus],
mocks: {
$t: (msg) => {
const params = msg.split('.');
return messages[locale][params[0]][params[1]];
}
}
}
});
//fails on previous lines.
expect(typeof component !== "undefined", "component created").toBeTruthy();
let h3Text = component.findAll('h3')[0].element.innerHTML;
expect(component.findAll('.form').length === 1, "form element rendered").toBeTruthy();
expect(h3Text === "d", "locale strings correct").toBeTruthy();
});
});
It doesn't even get to the "expect" tests, fails with message:
Error: Cannot find module 'C:\source\mySite\node_modules\dayjs\plugin\customParseFormat'
imported from
C:\source\mySite\node_modules\element-plus\es\components\date-picker\src\date-picker.mjs
Did you mean to import dayjs/plugin/customParseFormat.js?
This bit seems to indicate that node expects you to use .js extension and element is not doing that.
Error: Cannot find module 'C:\source\mySite\node_modules\dayjs\plugin\customParseFormat'
imported from
C:\source\mySite\node_modules\element-plus\es\components\date-picker\src\date-picker.mjs
Did you mean to import dayjs/plugin/customParseFormat.js?
I'm guessing this is because you may be running an older node version. Element requires at least node v16.
I have this problem too.
It seems that this problem is already solved in this pull request - https://github.com/element-plus/element-plus/pull/6811

Resources