I'm in the process of creating an internal component library using Storybook and Kendo React and I'm having to explicitly define argTypes for controls for every prop in order to get them to give me anything but a string input (with the sole exception of booleans).
Here's a sample of one of my stories:
import React from "react";
import { ComponentStory, ComponentMeta } from "#storybook/react";
import { TestAvatar } from "../../components/Avatar/TestAvatar";
export default {
title: "Atomic Components/Test Avatar",
component: TestAvatar,
} as ComponentMeta<typeof TestAvatar>;
const Template: ComponentStory<typeof TestAvatar> = ({ ...args }: any) => (
<TestAvatar {...args} />
);
export const Standard = Template.bind({});
Standard.args = {
border: false,
themeColor: "base",
fillMode: "solid",
rounded: "medium",
size: "medium",
type: undefined,
};
Standard.argTypes = {
themeColor: {
options: ["base", "primary", "secondary", "tertiary", "success", "error"],
control: { type: "select" },
},
rounded: {
options: ["small", "medium", "large", "full", null],
control: { type: "select" },
},
size: {
options: ["small", "medium", "large"],
control: { type: "select" },
},
fillMode: {
options: ["solid", "outline"],
control: { type: "radio" },
},
type: {
options: ["image", "text", "icon"],
control: { type: "select" },
},
};
I have all the standard addons, although I haven't created any typescript options in main.js - I've tried, using info I got from this question but they don't seem to make any difference.
I've tried a few things, using Story and Meta from Storybook instead of the newer ComponentStory and ComponentMeta, I've tried extending the AvatarProps interface and I have also tried using the Kendo AvatarProps interface directly, but I can't find a way around it.
I suspect it's to do with how the Kendo library is typed.
I want to have to avoid creating controls for every single component I'm creating - does anyone have any ideas on how I can circumvent that amount of unnecessary work?
Related
I'm trying to set custom labels for my controls in Storybook as outlined in the instructions here, but it's not working as expected. According to the instructions you can specify control.labels to configure custom labels for your checkbox, radio, or select input.
Right now I have a prop of size that allows the user to select the size of the component, but in Storybook it's showing the number value as opposed to name. e.g.
Instead of the number values I want the labels to read the names from the enum below.
export enum sizes {
small = 32,
default = 50,
large = 100,
};
How can I update Storybook to use the enum sizes name instead of the value?
// storybook
export default {
title: 'Components/Spinner',
component: Spinner,
controls: { expanded: true },
argTypes: {
type: {
options: ['primary', 'secondary', 'success', 'warning', 'danger', 'info', 'light'],
control: { type: 'radio'},
},
size: {
options: [sizes.default, sizes.small, sizes.large],
control: {
type: 'radio',
labels: {
Default: 'Default',
Small: 'Small',
Large: 'Large'
},
},
}
}
} as Meta;
FYI: If I update options to the following:
options: sizes,
I get both the name and the value and only the name works
In case anyone else comes across this issue I solved it by manually typing in the values. According to this Stackoverflow post enums end up as object. So it was outputting both the key and values.
export default {
title: 'Components/Spinner',
component: Spinner,
controls: { expanded: true },
argTypes: {
type: {
options: ['primary', 'secondary', 'success', 'warning', 'danger', 'info', 'light'],
control: { type: 'radio'},
},
size: {
options: [32, 50, 100],
control: {
type: 'radio',
labels: {
32: 'small',
50: 'default',
100: 'large',
},
},
}
}
} as Meta;
I have the following globalTypes to enable a toolbar in storybook that lets me select the theme:
export const globalTypes = {
theme: {
name: 'Theme',
description: 'Global theme',
defaultValue: MyTheme.Light,
toolbar: {
icon: 'mirror',
items: [MyTheme.Light, MyTheme.Dark],
showName: true,
dynamicTitle: true,
},
},
};
This works fine and I can switch the theme through the toolbar:
Now I want to set the background color of the story (background-color of the body) according to the theme, but I cannot figure out how to do that for all stories globally.
I know how to configure different background colors, but I have no idea how to switch them based on the theme set in context.globals. How does this work?
You can use decorators to set global view and se
Like here
import { useEffect } from "react";
import "./preview.css";
enum MyTheme {
Light = "light",
Dark = "dark",
Tomato = "tomato"
}
export const globalTypes = {
theme: {
name: "Theme",
description: "Global theme",
defaultValue: MyTheme.Light,
toolbar: {
icon: "mirror",
items: [
{
title: "light",
value: MyTheme.Light
},
{ title: "dark", value: MyTheme.Dark },
{ title: "tomato", value: MyTheme.Tomato }
],
showName: true,
dynamicTitle: true
}
}
};
const clearStyles = (element: HTMLElement) => {
for (const className of Object.values(MyTheme)) {
element.classList.remove(className);
}
};
const applyStyle = (element: HTMLElement, className: string) => {
element.classList.add(className);
};
const WithThemeProvider = (Story, context) => {
useEffect(() => {
const body = window.document.body;
clearStyles(body);
applyStyle(body, context.globals.theme);
return () => {
clearStyles(body);
};
}, [context.globals.theme]);
return <Story />;
};
export const decorators = [WithThemeProvider];
I know it might feel "dirty" to work directly with body. But it is suggested way for instance addons decorator.
I'm attempting to add a new variant to the Container component, directly in the ThemeOptions.
The documentation explains that we need to use module augmentation to extend the interface so that the ts compiler knows about the new prop we are adding:
import { red } from '#mui/material/colors';
import { ThemeOptions } from '#mui/material/styles';
import '#mui/material/Container';
// -------------------
// This throws error:
// Duplicate identifier 'ContainerProps'.ts(2300)
// Container.d.ts(53, 13): 'ContainerProps' was also declared here.
// -------------------
declare module '#mui/material/Container' {
interface ContainerProps {
variant?: string;
};
}
const defaultThemeOpts: ThemeOptions = {
palette: {
primary: {
main: '#1BA4DD',
},
secondary: {
main: '#094074',
},
error: {
main: red.A400,
},
},
components: {
MuiContainer: {
variants: [
{
props: { variant: 'centered' },
style: {
textAlign: 'center',
},
},
],
},
},
};
export default defaultThemeOpts;
MUI V5 documentation isn't very clear on how to tackle this and I'm a bit confused as to why module augmentation is not working here.
I am trying to use a plugin with a react-chartjs doughnut chart. In order to use the plugin (https://www.npmjs.com/package/#scottalan/chartjs-plugin-doughnutlabel), I have to pass options to the component. But when I try to pass options, I get a type error
Type '{ doughnutlabel: { labels: { text: string; font: { size: string; family: string; style: string; weight: string; }; color: string; }[]; }; }' is not assignable to type '_DeepPartialObject<PluginOptionsByType<keyof ChartTypeRegistry>>'.
Object literal may only specify known properties, and 'doughnutlabel' does not exist in type '_DeepPartialObject<PluginOptionsByType<keyof ChartTypeRegistry>>'.
My best guess is that ChartOptions is the wrong type to assign my options to because I get the same error event without trying to use the plugin with const options: ChartOptions = {}. My full code is below, any help is appreciated.
import React, { useEffect, ReactNode, Component } from "react"
import { Chart, ChartOptions, registerables } from 'chart.js'
import { Doughnut } from 'react-chartjs-2';
Chart.register(...registerables);
const MyDoughnut = (props: ComponentProps) => {
const renderChart = (percents: number[]) => {
const data = {
datasets: [{
label: 'Progress',
data: percents,
backgroundColor: [
'rgb(255, 99, 132)',
'transparent',
],
hoverOffset: 4,
cutout: "75%",
radius: "100%"
}]
}
const options: ChartOptions = {
responsive: true,
legend: {
display: false,
position: 'top',
},
title: {
display: true,
fontSize: 20,
text: 'My Title'
},
plugins: {
doughnutlabel: {
labels: [
{
text: "Foo",
font: {
size: '60',
family: 'Arial, Helvetica, sans-serif',
style: 'italic',
weight: 'bold'
},
color: '#bc2c1a'
}
]
}
}
}
return <Doughnut options={options} data={data}></Doughnut>
}
const render = (): ReactNode => {
// const percents = props.args["percents"]
const percents = [76, 24];
return (
<span>
{renderChart(percents)}
</span>
)
}
return <div>{render()}</div>
};
I am using react-chartjs-2 version 4.0.0 and react version 17.0.2.
It turns out the typing error can be solved by using any instead of ChartOptions.
const options: any = {
...
}
As LeeLenalee pointed out, there is also an issue using this specific plugin with V3 of Chart.js, but using any worked with another similar plugin.
types are exported from chart.js
import type { ChartData, ChartOptions } from 'chart.js';
interface DoughnutProps {
options: ChartOptions<'doughnut'>;
data: ChartData<'doughnut'>;
}
I looked at the source of the plugin and it seems like even if you solve the typing issue it wont work. The reason for this is that you are using V3 of Chart.js while the plugin was written for V2 and never updated. It is self registering and it does this in the wrong way, it defines its default options in the wrong place (both namespaces dont exist anymore).
So if you really want to use this plugin I suggest you take a look at the source and modify it so it works with V3 or downgrade to chart.js v2 where there is no build in typing or find another plugin.
I am new to the storybook. When I go through the documentation and videos about the storybook I read about knobs Addon. Knobs addon and control looks similar. What is the difference between those two things?
Controls were introduced with Storybook version 6. They replace knobs for most of the use cases. However, there may be some edge cases were you still want to use knobs for dynamic values. For example, see this Github discussion on this topic: https://github.com/storybookjs/storybook/issues/11984
controls addon is a companion to the docs addon so it interfaces with the ArgsTable which by itself is designed to automatically extract your components' propTypes & defaultProps (although I found this not to work)
So, with Knobs you define each prop (which you wish to be dynamic) yourself, manually, and this requires some more manual sync when your component changes and also more work, and also Knobs variables definitions might be scattered all across your story's file, where controls are all defined in one place, though the same "order" can also be done with Knobs, it does not enforces it (for good reasons).
If you want to have an interactive propTypes documentation for your components, then I suggest using controls with addon-docs, and I've been using knobs for years, but that's it, it's time to upgrade.
If, for some reason, your component's propTypes where not auto-detected (in the story) then you can define then (with controls) like so:
import Alert from './';
export default {
title: 'General/Alert',
component: Alert,
parameters: {
controls: { expanded: true }, // Show full documentation for each property
},
argTypes: {
type: {
description: 'Alert.Types',
defaultValue: Alert.Types.WARNING,
table: {
type: {
summary: 'string',
},
defaultValue: {
summary: Alert.Types.WARNING,
},
},
options: Alert.Types,
control: {
type: 'select', // for selecting between the array of options above
},
},
title: {
defaultValue: '',
table: {
type: {
summary: 'string',
},
},
description: 'An optional title',
control: {
type: 'text',
},
},
onClose: {
table: {
type: {
summary: 'func',
},
},
description: '× button click callback',
control: { type: null },
},
children: {
description: 'The message body (mandatory)',
type : {
required: true,
},
table: {
type: {
summary: 'node',
},
},
control: { type: null },
},
},
}
//...export your story...
Notes:
How to migrate dynamic knobs to controls?