Skip to content
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

WIP: CSF factories #30197

Draft
wants to merge 58 commits into
base: next
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
9415e47
Make a start on defineConfig, meta and story factory
kasperpeulen Jan 3, 2025
66c8234
CsfFile: Support CSF factories
shilman Jan 3, 2025
9fc6fbe
Try it out in Button.stories.tsx
kasperpeulen Jan 3, 2025
cdda2b5
Ignore eslint
kasperpeulen Jan 3, 2025
5d05b3c
Rename Docs, fix later
kasperpeulen Jan 3, 2025
628e657
Merge remote-tracking branch 'origin/shilman/csf-tools-typesafe-facto…
kasperpeulen Jan 3, 2025
79107a5
Get POC working!!
kasperpeulen Jan 3, 2025
60c6f26
ConfigFile: Handle defineConfig typesafe factory
shilman Jan 3, 2025
c2988dc
Merge remote-tracking branch 'origin/shilman/csf-tools-typesafe-facto…
kasperpeulen Jan 3, 2025
6b0abf0
take CSF factories into account in portable stories
yannbf Jan 3, 2025
b4e6e25
take CSF factories into account in the vitest plugin
yannbf Jan 3, 2025
c370fec
Handle project annotations
kasperpeulen Jan 6, 2025
1d2ee2f
Merge remote-tracking branch 'origin/kasper/csf-factories' into kaspe…
kasperpeulen Jan 6, 2025
d04b258
Fix runtime
kasperpeulen Jan 6, 2025
410f988
Fix loaders
kasperpeulen Jan 6, 2025
25e9575
ConfigFile: Fix tag parsing issue
shilman Jan 6, 2025
c061e82
CsfFile: Add CSF factories telemetry
shilman Jan 6, 2025
3466750
CsfFile: Fix bad meta definition
shilman Jan 6, 2025
84f2ae6
Build: Add CSF4 story in react-vite framework
yannbf Jan 6, 2025
3753cde
CsfFile: Improve meta error handling and fix failing test
shilman Jan 6, 2025
5f7c6af
Add addons array
kasperpeulen Jan 7, 2025
3eead18
Csf Tools: Allow ConfigFile to create new import types
yannbf Jan 7, 2025
5efce1e
fix sandbox annotations for react-vite
yannbf Jan 7, 2025
dd5d1be
fix test snapshots
yannbf Jan 7, 2025
b70fdcc
Add essentials preview entry
kasperpeulen Jan 7, 2025
e584c17
Merge pull request #30205 from storybookjs/yann/fix-sandbox-annotations
kasperpeulen Jan 7, 2025
1f3600e
fix portable stories annotations
yannbf Jan 7, 2025
03a8b9c
fix portable stories kitchen sink warning
yannbf Jan 7, 2025
b1bd157
fix portable stories tests
yannbf Jan 7, 2025
ea5b8cb
use globalThis to access feature flags
yannbf Jan 7, 2025
03f8b11
fix Storybook build with temporary workaround
yannbf Jan 7, 2025
07dcd78
do not use csf4 in bench sandboxes
yannbf Jan 8, 2025
e0ef30e
temporary attempt at fixing part of the E2E tests
yannbf Jan 8, 2025
309a435
Change to entry-preview
kasperpeulen Jan 8, 2025
86fe532
Merge remote-tracking branch 'origin/kasper/csf-factories' into kaspe…
kasperpeulen Jan 8, 2025
49353cb
Fix addon-essentials
kasperpeulen Jan 8, 2025
ec0c55f
Revert "Rename Docs, fix later"
kasperpeulen Jan 8, 2025
ef92a43
revert workaround
yannbf Jan 8, 2025
745f1b5
Fix docs and make a start on typesafety
kasperpeulen Jan 8, 2025
30511e9
Merge remote-tracking branch 'origin/kasper/csf-factories' into kaspe…
kasperpeulen Jan 8, 2025
f59916d
use correct setup file in csf4 sandbox
yannbf Jan 9, 2025
eb50508
fix essentials annotations
yannbf Jan 9, 2025
ddbe8b9
add mdx example with csf4
yannbf Jan 9, 2025
4127d87
skip svelte test for now
yannbf Jan 9, 2025
740c149
Revert "skip svelte test for now"
yannbf Jan 10, 2025
5349a7d
Codemod: Add CSF3 to CSF4 migration
yannbf Jan 6, 2025
f4e3a62
only apply meta related changes if there's a meta to change
yannbf Jan 10, 2025
0c7bace
in sandbox command, delete a sandbox directory if it already exists
yannbf Jan 10, 2025
23bcf14
add codemod migrations to sandbox creation
yannbf Jan 10, 2025
76b1b6c
fix tests
yannbf Jan 10, 2025
0ae3643
Fix controls
kasperpeulen Jan 10, 2025
4d2b108
Merge pull request #30194 from storybookjs/yann/csf3-to-4-codemod
yannbf Jan 10, 2025
53864bd
use canary of eslint-plugin which supports csf4 meta
yannbf Jan 10, 2025
6d08dd1
fix lint
yannbf Jan 10, 2025
dd7fd84
add CSF4 support in save from controls feature
yannbf Jan 10, 2025
126cd95
remove .only in test
yannbf Jan 10, 2025
19f4b0d
update snapshots
yannbf Jan 10, 2025
861625a
Merge branch 'next' into kasper/csf-factories
yannbf Jan 13, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions code/.storybook/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
import { DocsContext } from '@storybook/blocks';
import { global } from '@storybook/global';
import type { Decorator, Loader, ReactRenderer } from '@storybook/react';
import { defineConfig } from '@storybook/react/preview';

import { DocsPageWrapper } from '../lib/blocks/src/components';
import { isChromatic } from './isChromatic';
Expand Down Expand Up @@ -361,3 +362,9 @@ export const parameters = {
};

export const tags = ['test', 'vitest', '!a11ytest'];

export const config = defineConfig({
parameters,
tags,
decorators,
});
3 changes: 2 additions & 1 deletion code/addons/test/src/vitest-plugin/test-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,12 @@ export const testStory = (
return async (context: TestContext & TaskContext & { story: ComposedStoryFn }) => {
const composedStory = composeStory(
story,
meta,
'isCSFFactory' in story ? (meta as any).annotations : meta,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Type casting to 'any' should be avoided. Consider creating proper type definitions for CSF factories to maintain type safety.

{ initialGlobals: (await getInitialGlobals?.()) ?? {} },
undefined,
exportName
);

if (composedStory === undefined || skipTags?.some((tag) => composedStory.tags.includes(tag))) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Optional chaining on skipTags is unnecessary since it's a required parameter

context.skip();
}
Expand Down
13 changes: 11 additions & 2 deletions code/builders/builder-vite/src/codegen-modern-iframe-script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export async function generateModernIframeScriptCode(options: Options, projectRo
[],
options
);
const previewAnnotationURLs = [...previewAnnotations, previewOrConfigFile]
const [previewFileUrl, ...previewAnnotationURLs] = [...previewAnnotations, previewOrConfigFile]
.filter(Boolean)
.map((path) => processPreviewAnnotation(path, projectRoot));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Destructuring could fail if previewAnnotations and previewOrConfigFile are both empty arrays after filtering. Add a check to handle this case.


Expand All @@ -23,14 +23,23 @@ export async function generateModernIframeScriptCode(options: Options, projectRo
// modules are provided, the rest are null. We can just re-import everything again in that case.
const getPreviewAnnotationsFunction = `
const getProjectAnnotations = async (hmrPreviewAnnotationModules = []) => {
const preview = await import('${previewFileUrl}');
const csfFactoryPreview = Object.values(preview).find(module => {
return 'isCSFFactoryPreview' in module
});
Comment on lines +26 to +29
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Object.values() and find() could return undefined. Need type checking and error handling for when no CSF factory preview is found.


if (csfFactoryPreview) {
return csfFactoryPreview.annotations;
}

const configs = await Promise.all([${previewAnnotationURLs
.map(
(previewAnnotation, index) =>
// Prefer the updated module from an HMR update, otherwise import the original module
`hmrPreviewAnnotationModules[${index}] ?? import('${previewAnnotation}')`
)
.join(',\n')}])
return composeConfigs(configs);
return composeConfigs([...configs, preview]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: preview is used in composeConfigs even when csfFactoryPreview is found, which could cause conflicts. Should only include preview in non-factory case.

}`;

// eslint-disable-next-line @typescript-eslint/no-shadow
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,21 @@ import { global } from '@storybook/global';

import { importFn } from '{{storiesFilename}}';

const getProjectAnnotations = () => composeConfigs(['{{previewAnnotations_requires}}']);
const getProjectAnnotations = () => {
const previewAnnotations = ['{{previewAnnotations_requires}}'];
// the last one in this array is the user preview
const preview = previewAnnotations[previewAnnotations.length - 1];
Comment on lines +9 to +11
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Assuming the last preview is the user preview could be fragile if the array order changes. Consider adding a more explicit way to identify the user preview.


const csfFactoryPreview = Object.values(preview).find((module) => {
return 'isCSFFactoryPreview' in module;
});
Comment on lines +13 to +15
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Object.values() on preview could be undefined if preview is not an object. Add type checking or error handling.


if (csfFactoryPreview) {
return csfFactoryPreview.annotations;
}

return composeConfigs(previewAnnotations);
};

const channel = createBrowserChannel({ page: 'preview' });
addons.setChannel(channel);
Expand Down
257 changes: 128 additions & 129 deletions code/core/src/components/components/Button/Button.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,17 @@ import type { ReactNode } from 'react';
import React from 'react';

import { FaceHappyIcon } from '@storybook/icons';
import type { Meta, StoryObj } from '@storybook/react';

import { config } from '../../../../../.storybook/preview';
import { Button } from './Button';

const meta = {
// eslint-disable-next-line storybook/default-exports
const meta = config.meta({
id: 'button-component',
title: 'Button',
component: Button,
args: { children: 'Button' },
} satisfies Meta<typeof Button>;

export default meta;
type Story = StoryObj<typeof meta>;
});

const Stack = ({ children }: { children: ReactNode }) => (
<div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>{children}</div>
Expand All @@ -23,9 +22,9 @@ const Row = ({ children }: { children: ReactNode }) => (
<div style={{ display: 'flex', alignItems: 'center', gap: '1rem' }}>{children}</div>
);

export const Base: Story = {};
export const Base = meta.story({});

export const Variants: Story = {
export const Variants = meta.story({
render: (args) => (
<Stack>
<Row>
Expand Down Expand Up @@ -63,125 +62,125 @@ export const Variants: Story = {
</Row>
</Stack>
),
};

export const Active: Story = {
args: {
active: true,
children: (
<>
<FaceHappyIcon />
Button
</>
),
},
render: (args) => (
<Row>
<Button variant="solid" {...args} />
<Button variant="outline" {...args} />
<Button variant="ghost" {...args} />
</Row>
),
};

export const WithIcon: Story = {
args: {
children: (
<>
<FaceHappyIcon />
Button
</>
),
},
render: (args) => (
<Row>
<Button variant="solid" {...args} />
<Button variant="outline" {...args} />
<Button variant="ghost" {...args} />
</Row>
),
};

export const IconOnly: Story = {
args: {
children: <FaceHappyIcon />,
padding: 'small',
},
render: (args) => (
<Row>
<Button variant="solid" {...args} />
<Button variant="outline" {...args} />
<Button variant="ghost" {...args} />
</Row>
),
};
});

export const Sizes: Story = {
render: () => (
<Row>
<Button size="small">Small Button</Button>
<Button size="medium">Medium Button</Button>
</Row>
),
};

export const Disabled: Story = {
args: {
disabled: true,
children: 'Disabled Button',
},
};

export const WithHref: Story = {
render: () => (
<Row>
<Button onClick={() => console.log('Hello')}>I am a button using onClick</Button>
<Button asChild>
<a href="https://storybook.js.org/">I am an anchor using Href</a>
</Button>
</Row>
),
};

export const Animated: Story = {
args: {
variant: 'outline',
},
render: (args) => (
<Stack>
<Row>
<Button animation="glow" {...args}>
Button
</Button>
<Button animation="jiggle" {...args}>
Button
</Button>
<Button animation="rotate360" {...args}>
Button
</Button>
</Row>
<Row>
<Button animation="glow" {...args}>
<FaceHappyIcon /> Button
</Button>
<Button animation="jiggle" {...args}>
<FaceHappyIcon /> Button
</Button>
<Button animation="rotate360" {...args}>
<FaceHappyIcon /> Button
</Button>
</Row>
<Row>
<Button animation="glow" padding="small" {...args}>
<FaceHappyIcon />
</Button>
<Button animation="jiggle" padding="small" {...args}>
<FaceHappyIcon />
</Button>
<Button animation="rotate360" padding="small" {...args}>
<FaceHappyIcon />
</Button>
</Row>
</Stack>
),
};
// export const Active: Story = {
// args: {
// active: true,
// children: (
// <>
// <FaceHappyIcon />
// Button
// </>
// ),
// },
// render: (args) => (
// <Row>
// <Button variant="solid" {...args} />
// <Button variant="outline" {...args} />
// <Button variant="ghost" {...args} />
// </Row>
// ),
// };
//
// export const WithIcon: Story = {
// args: {
// children: (
// <>
// <FaceHappyIcon />
// Button
// </>
// ),
// },
// render: (args) => (
// <Row>
// <Button variant="solid" {...args} />
// <Button variant="outline" {...args} />
// <Button variant="ghost" {...args} />
// </Row>
// ),
// };
//
// export const IconOnly: Story = {
// args: {
// children: <FaceHappyIcon />,
// padding: 'small',
// },
// render: (args) => (
// <Row>
// <Button variant="solid" {...args} />
// <Button variant="outline" {...args} />
// <Button variant="ghost" {...args} />
// </Row>
// ),
// };
//
// export const Sizes: Story = {
// render: () => (
// <Row>
// <Button size="small">Small Button</Button>
// <Button size="medium">Medium Button</Button>
// </Row>
// ),
// };
//
// export const Disabled: Story = {
// args: {
// disabled: true,
// children: 'Disabled Button',
// },
// };
//
// export const WithHref: Story = {
// render: () => (
// <Row>
// <Button onClick={() => console.log('Hello')}>I am a button using onClick</Button>
// <Button asChild>
// <a href="https://storybook.js.org/">I am an anchor using Href</a>
// </Button>
// </Row>
// ),
// };
//
// export const Animated: Story = {
// args: {
// variant: 'outline',
// },
// render: (args) => (
// <Stack>
// <Row>
// <Button animation="glow" {...args}>
// Button
// </Button>
// <Button animation="jiggle" {...args}>
// Button
// </Button>
// <Button animation="rotate360" {...args}>
// Button
// </Button>
// </Row>
// <Row>
// <Button animation="glow" {...args}>
// <FaceHappyIcon /> Button
// </Button>
// <Button animation="jiggle" {...args}>
// <FaceHappyIcon /> Button
// </Button>
// <Button animation="rotate360" {...args}>
// <FaceHappyIcon /> Button
// </Button>
// </Row>
// <Row>
// <Button animation="glow" padding="small" {...args}>
// <FaceHappyIcon />
// </Button>
// <Button animation="jiggle" padding="small" {...args}>
// <FaceHappyIcon />
// </Button>
// <Button animation="rotate360" padding="small" {...args}>
// <FaceHappyIcon />
// </Button>
// </Row>
// </Stack>
// ),
// };
Loading
Loading