-
Notifications
You must be signed in to change notification settings - Fork 287
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Using Solid with Storybook [How-To Guide] #35
Comments
I created storybook-solid a little while ago but haven't had the time to really try it out much. This follows the guide from Storybook you linked. It's not ideal yet as it would need buy-in from Storybook to add it as a community supported framework to they cli tool, but it saves you from having to write boilerplate. It would be great to get some feedback on this and once it has been tested out a bit more, then maybe we could get it added to the docs. |
Yeah I'm all for this. I just haven't had the reason to look into it. I was hoping that someone would update the docs at some point. I'm not sure if stuff is different now. If anyone wants to help putting such a plugin together I'd support it and do what is needed to get it recognized. I'm just not working in Storybook personally. |
On further review I think we are best setting up a repo for a plugin following the steps and not bothering with docs here. It probably should be managed independently. |
Sounds good to me! Let me know if there's anything I can help with. |
I was able to set up Storybook (v6.3.4) in my project using the @storybook/html mode and without the solidjs/solid#553 bug. Turns out we just need to create our own root and call import { render } from "solid-js/web";
let disposeStory;
export const decorators = [
Story => {
if (disposeStory) {
disposeStory();
}
const root = document.getElementById("root");
const solid = document.createElement("div");
solid.setAttribute('id', 'solid-root');
root.appendChild(solid);
disposeStory = render(Story, solid);
return solid;
// return createRoot(() => Story()); // do not work correctly https://github.com/solidjs/solid/issues/553
}
] |
@grenierdev How did you accomplish this? I followed your UPDATE: I use module.exports = {
stories: [
'../src/**/*.stories.@(mdx|jsx)'
],
addons: [
'@storybook/addon-essentials',
'@storybook/addon-links'
],
babel: async (options) => ({
...options,
presets: [
'solid'
]
})
} |
@zettadam do you happen to have a working storybook repo I/the community can take a look at? Super keen to use storybook with solid and not quite sure what piece I'm missing.. |
Anyone had success with using mdx with solidjs ? |
If someone wants to use storybook with vite, this would help And with pnpm you need to add more devDependencies to avoid errors. Here is part of my package.json {
"devDependencies": {
"@babel/core": " ^7.0.0",
"@babel/preset-env": "^7.18.0",
"@mdx-js/react": "1.6.22",
"@storybook/addon-actions": "6.5.0-alpha.61",
"@storybook/addon-backgrounds": "6.5.0-alpha.61",
"@storybook/addon-docs": "6.5.0-alpha.61",
"@storybook/addon-essentials": "6.5.0-alpha.61",
"@storybook/addon-links": "6.5.0-alpha.61",
"@storybook/addon-measure": "6.5.0-alpha.61",
"@storybook/addon-outline": "6.5.0-alpha.61",
"@storybook/builder-vite": "^0.1.27",
"@storybook/client-api": "6.5.0-alpha.61",
"@storybook/client-logger": "6.5.0-alpha.61",
"@storybook/core-common": "6.5.0-alpha.61",
"@storybook/html": "6.5.0-alpha.61",
"react": "16.14.0",
"react-dom": "16.14.0",
"typescript": "^4.6.4",
"vite": "^2.9.9",
"vite-plugin-solid": "^2.2.6"
},
}
|
What about https://github.com/histoire-dev/histoire they are planing solid support. This seems like great alternative. |
Hello, is there anyone succeed to use the Args of Story book with SolidJs ? I tried to implement the types ComponentStoryFn and ComponentMeta, by taking example over storybook/react, but it did not work, Typescript don't want to compile. |
Ok I may have something 😄 So first here is the button component from the codesandbox example used on this thread: import { For } from 'solid-js'
export const Button = ({ count }: { count: number }) => {
const labels = Array(count)
.fill(null)
.map((_, i) => i + 1)
return <For each={labels}>{label => <button>{label}</button>}</For>
} And then the story import { Meta, StoryFn } from '@storybook/html'
import { Component, ComponentProps } from 'solid-js'
import { Button } from '../components/Button'
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type ComponentStoryFn<T extends Component<any>> = StoryFn<ComponentProps<T>>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type ComponentMeta<T extends Component<any>> = Meta<ComponentProps<T>>
export default {
title: 'Example/Button',
argTypes: {
count: { control: 'number' },
},
} as ComponentMeta<typeof Button>
const Template = (args => <Button {...args} />) as ComponentStoryFn<typeof Button>
export const Primary = Template.bind({})
Primary.args = {
count: 5,
} Since, I'm coming from FP land (Rescript), i use PS: I'm also using vite, the config from the codesandbox for vite, is still correct. |
If somebody wants to see the repo with examples: https://github.com/elite174/storybook-solid-js @joel1st it might be useful for you |
(Storybook maintainer here 👋) @elite174 that looks pretty dope actually, and I'm astounded that it works with so little configuration. If you or anyone else ever want to try and turn this into an actual Solid addon/renderer let me know and I'd be happy to assist you with whatever you need. |
@elite174 @JReinhold we'd really love for Solid to have an official Storybook adapter. Myself and core will support and provide insight wherever is needed. ❤️ |
Honestly, I prefer framework-agnostic approach. In my repo it's clear enough how to setup storybook with just simple html setup. IMO it would be better if a tool provided plain JS API which can be used by any framework. Just imagine that tommorow there will be Solid-Vue-Svelte super framework and you need to write an integration for it... UPD: Added instructions to my repo. @JReinhold All hope is on you :) |
I was able to get this set up using storybook 7 (currently
I'm staring from the html-vite preset, so framework: {
name: "@storybook/html-vite"
options: {},
} Story exampleThese examples use CSF 3.0 - https://storybook.js.org/blog/component-story-format-3-0/ // Button.tsx
// `children` would be better than `text`, but I went with this to test the handling of prop types
export function Button({ text }: { text: string }) {
return <button>{text}</button>;
} // Button.stories.tsx
import type {
ComponentAnnotations,
StoryAnnotations,
WebRenderer,
} from "@storybook/types";
import type { Component, ComponentProps } from "solid-js";
import { Button } from "./Button";
// I'm including all these types here, but in a real project, I'd define them in a separate file.
// Only Meta and StoryObj would need to be exported from that file.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type AnyComponent = Component<any>;
interface SolidRenderer extends WebRenderer {
component: AnyComponent;
storyResult: ReturnType<AnyComponent>;
}
type Meta<T extends AnyComponent> = ComponentAnnotations<
SolidRenderer,
ComponentProps<T>
>;
type StoryObj<T extends AnyComponent> = StoryAnnotations<
SolidRenderer,
ComponentProps<T>
>;
// And here's the actual story content
export default {
title: "MyButton",
component: Button,
} satisfies Meta<typeof Button>;
export const FooButton: StoryObj<typeof Button> = {
args: {
text: "Foo",
},
}; Edit: Here's a import type { AnnotatedStoryFn } from "@storybook/types";
// AnyComponent and SolidRenderer are the same as above
type StoryFn<T extends AnyComponent> = AnnotatedStoryFn<
SolidRenderer,
ComponentProps<T>
>;
const Template: StoryFn<typeof Button> = (args) => <Button {...args} />;
export const BarButton = Template.bind({});
Bar.args = {
text: "Bar",
}; |
@noahbrenner Can you share with us your package.json, main.js, and preview.js ? |
Guys, storybook 7 is just in beta |
@carere Absolutely, I'll paste those files below. The storybook-related dependencies were installed by running This isn't a fully minimal repro, since I have some non-essential deps (prettier, eslint, husky, lint-staged, vitest, jsdom, tauri), but it is still just the initial setup of the project. package.json{
"version": "0.0.0",
"description": "",
"license": "MIT",
"scripts": {
"prepare": "husky install",
"start": "vite",
"dev": "vite",
"build": "vite build",
"serve": "vite preview",
"lint": "tsc --noEmit && eslint --ext .ts,.tsx --ignore-path .gitignore . && prettier --check ./*.* src",
"test": "vitest --reporter=verbose",
"tauri": "tauri",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
},
"lint-staged": {
"**/*": "prettier --write --ignore-unknown"
},
"devDependencies": {
"@storybook/addon-essentials": "^7.0.0-beta.17",
"@storybook/addon-interactions": "^7.0.0-beta.17",
"@storybook/addon-links": "^7.0.0-beta.17",
"@storybook/blocks": "^7.0.0-beta.17",
"@storybook/html": "^7.0.0-beta.17",
"@storybook/html-vite": "^7.0.0-beta.17",
"@storybook/testing-library": "^0.0.13",
"@tauri-apps/cli": "^1.2.2",
"@types/babel__core": "^7.1.20",
"@types/node": "^18.7.10",
"@typescript-eslint/eslint-plugin": "^5.47.1",
"@typescript-eslint/parser": "^5.47.1",
"eslint": "^8.30.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-storybook": "^0.6.8",
"husky": "^8.0.2",
"jsdom": "^20.0.3",
"lint-staged": "^13.1.0",
"prettier": "^2.8.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"storybook": "^7.0.0-beta.17",
"typescript": "^4.7.4",
"vite": "^4.0.0",
"vite-plugin-solid": "^2.3.0",
"vitest": "^0.26.2"
},
"dependencies": {
"@tauri-apps/api": "^1.2.0",
"solid-js": "^1.4.7"
}
} .storybook/main.jsmodule.exports = {
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
addons: [
"@storybook/addon-links",
"@storybook/addon-essentials",
"@storybook/addon-interactions",
],
framework: {
name: "@storybook/html-vite",
options: {},
},
docs: {
autodocs: "tag",
},
}; .storybook/preview.jsimport { render } from "solid-js/web"
let disposeStory = () => {};
export const decorators = [
(Story) => {
disposeStory();
const solidRoot = document.createElement("div");
disposeStory = render(Story, solidRoot);
return solidRoot;
},
];
// This isn't part of the solution, it's just what `storybook init` scaffolded and I didn't delete it
export const parameters = {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
}; And just so everything is in one place, here are the component and story files again: src/components/Button.tsxexport function Button({ text }: { text: string }) {
return <button>{text}</button>;
} src/components/Button.stories.tsximport type {
AnnotatedStoryFn,
ComponentAnnotations,
StoryAnnotations,
WebRenderer,
} from "@storybook/types";
import type { Component, ComponentProps } from "solid-js";
import { Button } from "./Button";
// Types that should eventually be defined in their own file
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type AnyComponent = Component<any>;
interface SolidRenderer extends WebRenderer {
component: AnyComponent;
storyResult: ReturnType<AnyComponent>;
}
type Meta<T extends AnyComponent> = ComponentAnnotations<
SolidRenderer,
ComponentProps<T>
>;
type StoryObj<T extends AnyComponent> = StoryAnnotations<
SolidRenderer,
ComponentProps<T>
>;
type StoryFn<T extends AnyComponent> = AnnotatedStoryFn<
SolidRenderer,
ComponentProps<T>
>;
// Meta is the same for both CSF 2 and 3
export default {
title: "MyButton",
component: Button,
} satisfies Meta<typeof Button>;
// Using StoryObj (CSF 3)
export const FooButton: StoryObj<typeof Button> = {
args: {
text: "Foo",
},
};
// Using StoryFn (CSF 2)
const Template: StoryFn<typeof Button> = (args) => <Button {...args} />;
export const BarButton = Template.bind({});
Bar.args = {
text: "Bar",
}; |
Ah, it broke down when using a more complex component (the component example using The important change here is that we're not inserting the created element into the DOM directly, we're just returning the element and letting Storybook handle inserting it. // .storybook/preview.js
import { render } from "solid-js/web"
let disposeStory = () => {};
export const decorators = [
(Story) => {
disposeStory();
const solidRoot = document.createElement("div");
disposeStory = render(Story, solidRoot);
return solidRoot;
},
]; |
Still left to solve: hmr updates
Any ideas on this, @JReinhold? I don't know if this would need to be addressed from the Solid side or the Storybook side. |
I also have the same problem on 7.x and 6.x |
@carere @noahbrenner |
@elite174 i've followed your repo instruction and got this error on a pnpm monorepo : Failed to parse source for import analysis because the content contains invalid JS syntax. If you are using JSX, make sure to name the file with the .jsx or .tsx extension. My components and stories files are all |
@fabien-ml That's not a lot of info to go on. Since you're getting a syntax error, that doesn't sound like it has to do with the Storybook/Solid integration. It's possible that question might be better suited to StackOverflow or something (along with a minimal repro of your issue, versions you're using, and the command you ran to get that message). As a first step, you might try just running |
Yeah I underdstand, sorry. I was able to get it work by using the webpack5 builder instead of vite. I don't know why but the problem seem related to the combo of storybook + vite builder + pnpm monorepo |
The Storybook API has changed quite a lot since Solid's Storybook docs were written in 2019. It would be really helpful (and I think go a loooong way toward helping drive adoption, long-term) if Solid could:
Storybook's become a pretty essential piece of the UI development pipeline, and at least for me, official support for it is a prerequisite for seriously considering any frontend frameworks. I miiiight be able to help with setting this up for Solid, although I haven't tried adding support for a new framework to Storybook before. 😅
The text was updated successfully, but these errors were encountered: