Merge branch 'next' into tech/improve-perf-by-using-mjs

This commit is contained in:
Norbert de Langen 2022-12-02 20:35:17 +01:00
commit b54eea2591
No known key found for this signature in database
GPG Key ID: FD0E78AF9A837762
45 changed files with 1042 additions and 214 deletions

View File

@ -17,4 +17,5 @@
"editor.defaultFormatter": "dbaeumer.vscode-eslint",
"editor.formatOnSave": true
},
"prettier.ignorePath": "./code/.prettierignore"
}

View File

@ -22,9 +22,9 @@
- [7.0 feature flags removed](#70-feature-flags-removed)
- [CLI option `--use-npm` deprecated](#cli-option---use-npm-deprecated)
- [Vite builder uses vite config automatically](#vite-builder-uses-vite-config-automatically)
- [Vite cache moved to node_modules/.cache/.vite-storybook](#vite-cache-moved-to-node_modulescachevite-storybook)
- [Vite cache moved to node\_modules/.cache/.vite-storybook](#vite-cache-moved-to-node_modulescachevite-storybook)
- [Removed docs.getContainer and getPage parameters](#removed-docsgetcontainer-and-getpage-parameters)
- [Removed STORYBOOK_REACT_CLASSES global](#removed-storybook_react_classes-global)
- [Removed STORYBOOK\_REACT\_CLASSES global](#removed-storybook_react_classes-global)
- [Icons API changed](#icons-api-changed)
- ['config' preset entry replaced with 'previewAnnotations'](#config-preset-entry-replaced-with-previewannotations)
- [Dropped support for Angular 12 and below](#dropped-support-for-angular-12-and-below)
@ -39,6 +39,8 @@
- [Configuring the Docs Container](#configuring-the-docs-container)
- [External Docs](#external-docs)
- [MDX2 upgrade](#mdx2-upgrade)
- [Default docs styles will leak into non-story user components](#default-docs-styles-will-leak-into-non-story-user-components)
- [Explicit `<code>` elements are no longer syntax highlighted](#explicit-code-elements-are-no-longer-syntax-highlighted)
- [Dropped source loader / storiesOf static snippets](#dropped-source-loader--storiesof-static-snippets)
- [Dropped addon-docs manual configuration](#dropped-addon-docs-manual-configuration)
- [Autoplay in docs](#autoplay-in-docs)
@ -806,6 +808,47 @@ We will update this section with specific pointers based on user feedback during
As part of the upgrade we deleted the codemod `mdx-to-csf` and will be replacing it with a more sophisticated version prior to release.
#### Default docs styles will leak into non-story user components
Storybook's default styles in docs are now globally applied to any element instead of using classes. This means that any component that you add directly in a docs file will also get the default styles.
To mitigate this you need to wrap any content you don't want styled with the `Unstyled` block like this:
```mdx
import { Unstyled } from '@storybook/blocks';
import { MyComponent } from './MyComponent';
# This is a header
<Unstyled>
<MyComponent />
</Unstyled>
```
Components that are part of your stories or in a canvas will not need this mitigation, as the `Story` and `Canvas` blocks already have this built-in.
#### Explicit `<code>` elements are no longer syntax highlighted
Due to how MDX2 works differently from MDX1, manually defined `<code>` elements are no longer transformed to the `Code` component, so it will not be syntax highlighted. This is not the case for markdown \`\`\` code-fences, that will still end up as `Code` with syntax highlighting.
Luckily [MDX2 supports markdown (like code-fences) inside elements better now](https://mdxjs.com/blog/v2/#improvements-to-the-mdx-format), so most cases where you needed a `<code>` element before, you can use code-fences instead:
<!-- prettier-ignore-start -->
````md
<code>This will now be an unstyled line of code</code>
```js
const a = 'This is still a styled code block.';
```
<div style={{ background: 'red', padding: '10px' }}>
```js
const a = 'MDX2 supports markdown in elements better now, so this is possible.';
```
</div>
````
<!-- prettier-ignore-end -->
#### Dropped source loader / storiesOf static snippets
In SB 6.x, Storybook Docs used a webpack loader called `source-loader` to help display static code snippets. This was configurable using the `options.sourceLoaderOptions` field.
@ -3671,3 +3714,7 @@ If you **are** using these addons, it takes two steps to migrate:
```
<!-- markdown-link-check-enable -->
```
```

1
code/.prettierignore Normal file
View File

@ -0,0 +1 @@
*.mdx

View File

@ -1,13 +1,11 @@
import React from 'react';
import ReactDOM from 'react-dom';
import type { Renderer, Parameters, DocsContextProps, DocsRenderFunction } from '@storybook/types';
import { components as htmlComponents } from '@storybook/components';
import { Docs, CodeOrSourceMdx, AnchorMdx, HeadersMdx } from '@storybook/blocks';
import { MDXProvider } from '@mdx-js/react';
// TS doesn't like that we export a component with types that it doesn't know about (TS4203)
export const defaultComponents: Record<string, any> = {
...htmlComponents,
code: CodeOrSourceMdx,
a: AnchorMdx,
...HeadersMdx,

View File

@ -51,7 +51,7 @@
"global": "^4.4.0",
"jest-specific-snapshot": "^7.0.0",
"preact-render-to-string": "^5.1.19",
"pretty-format": "^28.0.0",
"pretty-format": "^29.0.0",
"react-test-renderer": "^16.8.0 || ^17.0.0 || ^18.0.0",
"read-pkg-up": "^7.0.1",
"ts-dedent": "^2.0.0"

View File

@ -95,14 +95,18 @@ test.describe('addon-interactions', () => {
const rerunInteractionButton = await panel.locator('[aria-label="Rerun"]');
await rerunInteractionButton.click();
await interactionsRow.first().isVisible();
await expect(await interactionsRow.count()).toEqual(3);
await interactionsRow.nth(1).isVisible();
await interactionsRow.nth(2).isVisible();
await expect(interactionsTab).toContainText(/(3)/);
await expect(interactionsTab).toBeVisible();
// Test remount state (from toolbar) - Interactions have rerun, count is correct and values are as expected
const remountComponentButton = await page.locator('[title="Remount component"]');
await remountComponentButton.click();
await interactionsRow.first().isVisible();
await expect(await interactionsRow.count()).toEqual(3);
await interactionsRow.nth(1).isVisible();
await interactionsRow.nth(2).isVisible();
await expect(interactionsTab).toContainText(/(3)/);
await expect(interactionsTab).toBeVisible();
});
});

View File

@ -251,7 +251,6 @@ export const extractArgTypesFromData = (componentData: Class | Directive | Injec
const argType = {
name: item.name,
description: item.rawdescription || item.description,
defaultValue,
type,
...action,
table: {

View File

@ -81,7 +81,7 @@
},
"devDependencies": {
"@storybook/addon-actions": "7.0.0-alpha.56",
"next": "^12.2.4",
"next": "^13.0.5",
"typescript": "^4.9.3",
"webpack": "^5.65.0"
},

View File

@ -1,32 +1,67 @@
/* eslint-disable no-underscore-dangle, @typescript-eslint/no-non-null-assertion */
import * as React from 'react';
import type * as _NextImage from 'next/image';
import type * as _NextLegacyImage from 'next/legacy/image';
import semver from 'semver';
// next v9 (doesn't have next/image)
if (semver.gt(process.env.__NEXT_VERSION!, '9.0.0')) {
const NextImage = require('next/image') as typeof _NextImage;
const OriginalNextImage = NextImage.default;
Object.defineProperty(NextImage, 'default', {
configurable: true,
value: (props: _NextImage.ImageProps) =>
typeof props.src === 'string' ? (
<OriginalNextImage {...props} unoptimized blurDataURL={props.src} />
) : (
<OriginalNextImage {...props} unoptimized />
),
});
// https://github.com/vercel/next.js/issues/36417#issuecomment-1117360509
if (
semver.gte(process.env.__NEXT_VERSION!, '12.1.5') &&
semver.lt(process.env.__NEXT_VERSION!, '12.2.0')
) {
Object.defineProperty(NextImage, '__esModule', {
configurable: true,
value: true,
});
const defaultLoader = ({ src, width, quality }: _NextImage.ImageLoaderProps) => {
const missingValues = [];
if (!src) {
missingValues.push('src');
}
if (!width) {
missingValues.push('width');
}
if (missingValues.length > 0) {
throw new Error(
`Next Image Optimization requires ${missingValues.join(
', '
)} to be provided. Make sure you pass them as props to the \`next/image\` component. Received: ${JSON.stringify(
{
src,
width,
quality,
}
)}`
);
}
return `${src}?w=${width}&q=${quality ?? 75}`;
};
const NextImage = require('next/image') as typeof _NextImage;
const OriginalNextImage = NextImage.default;
Object.defineProperty(NextImage, 'default', {
configurable: true,
value: (props: _NextImage.ImageProps) => {
return <OriginalNextImage {...props} loader={props.loader ?? defaultLoader} />;
},
});
if (semver.satisfies(process.env.__NEXT_VERSION!, '^13.0.0')) {
const LegacyNextImage = require('next/legacy/image') as typeof _NextLegacyImage;
const OriginalNextLegacyImage = LegacyNextImage.default;
Object.defineProperty(OriginalNextLegacyImage, 'default', {
configurable: true,
value: (props: _NextLegacyImage.ImageProps) => (
<OriginalNextLegacyImage {...props} loader={props.loader ?? defaultLoader} />
),
});
}
if (semver.satisfies(process.env.__NEXT_VERSION!, '^12.0.0')) {
const NextFutureImage = require('next/future/image') as typeof _NextImage;
const OriginalNextFutureImage = NextFutureImage.default;
Object.defineProperty(OriginalNextFutureImage, 'default', {
configurable: true,
value: (props: _NextImage.ImageProps) => (
<OriginalNextFutureImage {...props} loader={props.loader ?? defaultLoader} />
),
});
}

View File

@ -0,0 +1,29 @@
import type { Configuration as WebpackConfig } from 'webpack';
import semver from 'semver';
import { IgnorePlugin } from 'webpack';
import { getNextjsVersion } from '../utils';
export function configureNextImport(baseConfig: WebpackConfig) {
const nextJSVersion = getNextjsVersion();
const isNext12 = semver.satisfies(nextJSVersion, '~12');
const isNext13 = semver.satisfies(nextJSVersion, '~13');
baseConfig.plugins = baseConfig.plugins ?? [];
if (!isNext13) {
baseConfig.plugins.push(
new IgnorePlugin({
resourceRegExp: /next\/legacy\/image$/,
})
);
}
if (!isNext12) {
baseConfig.plugins.push(
new IgnorePlugin({
resourceRegExp: /next\/future\/image$/,
})
);
}
}

View File

@ -12,6 +12,7 @@ import { configureStyledJsx } from './styledJsx/webpack';
import { configureImages } from './images/webpack';
import { configureRuntimeNextjsVersionResolution } from './utils';
import type { FrameworkOptions, StorybookConfig } from './types';
import { configureNextImport } from './nextImport/webpack';
export const addons: PresetProperty<'addons', StorybookConfig> = [
dirname(require.resolve(join('@storybook/preset-react-webpack', 'package.json'))),
@ -122,6 +123,7 @@ export const webpackFinal: StorybookConfig['webpackFinal'] = async (baseConfig,
configDir: options.configDir,
});
configureNextImport(baseConfig);
configureRuntimeNextjsVersionResolution(baseConfig);
configureImports(baseConfig);
configureCss(baseConfig, nextConfig);

View File

@ -3,10 +3,48 @@ import Image from 'next/image';
// eslint-disable-next-line import/extensions
import StackAlt from '../../assets/colors.svg';
const Component = () => <Image src={StackAlt} placeholder="blur" />;
export default {
component: Component,
component: Image,
args: {
src: StackAlt,
alt: 'Stack Alt',
},
};
export const Default = {};
export const BlurredPlaceholder = {
args: {
placeholder: 'blur',
},
};
export const BlurredAbsolutePlaceholder = {
args: {
src: 'https://storybook.js.org/images/placeholders/50x50.png',
width: 50,
height: 50,
blurDataURL:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAABP5JREFUWEeNlwtz2zgMhEGKsv/k9XFp82xe11763yOJVGcXC4m2czM3GYa0E2s/LACSTi+vv1czM/7CvPpqxY/ejPeS3khmFiPHOiVLHKaZi4ux4j18GpMlS6cALupEQBCKQM4BdnGzjIcJgs//QBxAPQAem55f3yL4PWJIdyCyhlMfPdYZot0cwj3Ayg/5JwHA13paen7pADphxr/n5VI8JQsHCCGQ3gVGLLsxQ3h/LYSn5383B05rwNOws3Z576LOTOduvwfrOd5FtVat4akx0uPTrw8BNuUz23vLsY7hmg7i4ipqM2saiAdruNuirh4ff0bNdb3Qy3vkvfAQwrkHoDxTTZtDCOKrC1bMEkdnsQh/PLyetOGHkXeRAgQAQ84efQcBdUhvFofoulpdm9WGGTA+AJEe7l+i37a2c371tCFKs5zzJjxQNBMi1im7OCudP2aNghJuzZaGdSMEZjpwf/t0UgNdg9DOyLGLnY0BUHlzwVtNDkgEQhBeKkb1tUDgQrq7frwAiIJi5BKAeIFgHk5mOpPzvgltOfcoK0Rrs7lWHwsgqtXarK3N0u23h5Ne8+3Cqxn5RYSMfHCAMgDAx4CBWlA9RAGw0GA/ol0gvFB4WjAvBAFUa83SzdUdAbYMqp28uHpxCRefxwAYhksAFBlthxCiXig+zT4TYqkC+Hq7OdAfJv8lPpZiZShWBBIuRP+jspDb2lwcDkzz7OLzbO/zvAHAoXTz5eYMQL0t2yHAiCFcfPY1QDwNFylA5bPoFpsV9fsEiMl8dhcc4PP1CYD3drYcBYdIKQrx0cbRxd2JHSDcQ297/vvoZ5smRC+AyV2AQ+nm03evge08Tyy4jGqXzWWEoIvTgXHU38pWiNgH4ixB/ukAcy/xycXfp4kwdAAAt399W+OCgMjxILQacxvRQ3gEwHgKUIr/rz53CuDFNyP/Eob4+/vEWkBq6AAA/HIi62n/Lk67Q7wDYQ0UpQB7hc54T4E6gACLTYxeAwB0YKZL6U4ATEGIBwCs7qPfQJCCHkCnoK50noJKcXcAojsEAJZZKXhgCoziGKxqWV8IMNp4kP2aC+oB0TMFvhGxDQHQfIPhDrilwKOm/YCZASAHfgBABQjr3f7CyAkA0cPB03AQULRhKd4xAIjzHymo2Gp7gN0FAMAVOoA2fPz03a9ssh/RM7Iz8QKIzYF9HyB0XEZ1xJ4DzNoDOAfAslhDDTyjDfv8A2AcBeCiu/jBHQEgxnYW6Kp6BlCVAkQM8VnieF2Xyr0ivXy+XvsCzKOihwNHCCryw8HrQXVB8dgFeRfAVQiXjMbIIgXINQYB2H7Kf5wF/2Ar7h0AgKKGuAP4zOjhzlkLbpcRXKRZhNUjxG6HIQDOjN47gCn4+fWW3xVS9urPESEEwwHMo9IhAGxS2ISiA1iEnQOoA4hXRAwItp7WzL9Ow18ESJaw/ar4NgeOR49cAHCAnaH8swBhv+6CBGjeBSxEOUAI7HyKHkD4O9xKb3/feQouAI4uLBciHRRHmgbfA7h/xFc9AngNBADthvii1sMOiPwDAFeyt6s7FSFS4PmnA1v0vQvqDqQKAAPE/weAUuEgsj8c+H11Twdw/AKANXA82EDr5cJBEEzB3oI4Mb0AdR3nNw8vQnegWuvqAABwJFJEBwDgNdA7IOs3gL0LhuJdwBY8c4BfNnDdVgooHiOqn/b7JoSW/QODjTHXhU7hMQAAAABJRU5ErkJggg==',
placeholder: 'blur',
},
};
export const FilledParent = {
args: {
fill: true,
},
decorator: [
(Story) => <div style={{ width: 500, height: 500, position: 'relative' }}>{Story()}</div>,
],
};
export const Sized = {
args: {
fill: true,
sizes: '(max-width: 600px) 100vw, 600px',
decorator: [
(Story) => <div style={{ width: 800, height: 800, position: 'relative' }}>{Story()}</div>,
],
},
};

View File

@ -0,0 +1,50 @@
import React from 'react';
import Image from 'next/future/image';
// eslint-disable-next-line import/extensions
import StackAlt from '../../assets/colors.svg';
export default {
component: Image,
args: {
src: StackAlt,
alt: 'Stack Alt',
},
};
export const Default = {};
export const BlurredPlaceholder = {
args: {
placeholder: 'blur',
},
};
export const BlurredAbsolutePlaceholder = {
args: {
src: 'https://storybook.js.org/images/placeholders/50x50.png',
width: 50,
height: 50,
blurDataURL:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAABP5JREFUWEeNlwtz2zgMhEGKsv/k9XFp82xe11763yOJVGcXC4m2czM3GYa0E2s/LACSTi+vv1czM/7CvPpqxY/ejPeS3khmFiPHOiVLHKaZi4ux4j18GpMlS6cALupEQBCKQM4BdnGzjIcJgs//QBxAPQAem55f3yL4PWJIdyCyhlMfPdYZot0cwj3Ayg/5JwHA13paen7pADphxr/n5VI8JQsHCCGQ3gVGLLsxQ3h/LYSn5383B05rwNOws3Z576LOTOduvwfrOd5FtVat4akx0uPTrw8BNuUz23vLsY7hmg7i4ipqM2saiAdruNuirh4ff0bNdb3Qy3vkvfAQwrkHoDxTTZtDCOKrC1bMEkdnsQh/PLyetOGHkXeRAgQAQ84efQcBdUhvFofoulpdm9WGGTA+AJEe7l+i37a2c371tCFKs5zzJjxQNBMi1im7OCudP2aNghJuzZaGdSMEZjpwf/t0UgNdg9DOyLGLnY0BUHlzwVtNDkgEQhBeKkb1tUDgQrq7frwAiIJi5BKAeIFgHk5mOpPzvgltOfcoK0Rrs7lWHwsgqtXarK3N0u23h5Ne8+3Cqxn5RYSMfHCAMgDAx4CBWlA9RAGw0GA/ol0gvFB4WjAvBAFUa83SzdUdAbYMqp28uHpxCRefxwAYhksAFBlthxCiXig+zT4TYqkC+Hq7OdAfJv8lPpZiZShWBBIuRP+jspDb2lwcDkzz7OLzbO/zvAHAoXTz5eYMQL0t2yHAiCFcfPY1QDwNFylA5bPoFpsV9fsEiMl8dhcc4PP1CYD3drYcBYdIKQrx0cbRxd2JHSDcQ297/vvoZ5smRC+AyV2AQ+nm03evge08Tyy4jGqXzWWEoIvTgXHU38pWiNgH4ixB/ukAcy/xycXfp4kwdAAAt399W+OCgMjxILQacxvRQ3gEwHgKUIr/rz53CuDFNyP/Eob4+/vEWkBq6AAA/HIi62n/Lk67Q7wDYQ0UpQB7hc54T4E6gACLTYxeAwB0YKZL6U4ATEGIBwCs7qPfQJCCHkCnoK50noJKcXcAojsEAJZZKXhgCoziGKxqWV8IMNp4kP2aC+oB0TMFvhGxDQHQfIPhDrilwKOm/YCZASAHfgBABQjr3f7CyAkA0cPB03AQULRhKd4xAIjzHymo2Gp7gN0FAMAVOoA2fPz03a9ssh/RM7Iz8QKIzYF9HyB0XEZ1xJ4DzNoDOAfAslhDDTyjDfv8A2AcBeCiu/jBHQEgxnYW6Kp6BlCVAkQM8VnieF2Xyr0ivXy+XvsCzKOihwNHCCryw8HrQXVB8dgFeRfAVQiXjMbIIgXINQYB2H7Kf5wF/2Ar7h0AgKKGuAP4zOjhzlkLbpcRXKRZhNUjxG6HIQDOjN47gCn4+fWW3xVS9urPESEEwwHMo9IhAGxS2ISiA1iEnQOoA4hXRAwItp7WzL9Ow18ESJaw/ar4NgeOR49cAHCAnaH8swBhv+6CBGjeBSxEOUAI7HyKHkD4O9xKb3/feQouAI4uLBciHRRHmgbfA7h/xFc9AngNBADthvii1sMOiPwDAFeyt6s7FSFS4PmnA1v0vQvqDqQKAAPE/weAUuEgsj8c+H11Twdw/AKANXA82EDr5cJBEEzB3oI4Mb0AdR3nNw8vQnegWuvqAABwJFJEBwDgNdA7IOs3gL0LhuJdwBY8c4BfNnDdVgooHiOqn/b7JoSW/QODjTHXhU7hMQAAAABJRU5ErkJggg==',
placeholder: 'blur',
},
};
export const FilledParent = {
args: {
fill: true,
},
decorator: [
(Story) => <div style={{ width: 500, height: 500, position: 'relative' }}>{Story()}</div>,
],
};
export const Sized = {
args: {
fill: true,
sizes: '(max-width: 600px) 100vw, 600px',
decorator: [
(Story) => <div style={{ width: 800, height: 800, position: 'relative' }}>{Story()}</div>,
],
},
};

View File

@ -0,0 +1,31 @@
import React from 'react';
import Image from 'next/legacy/image';
// eslint-disable-next-line import/extensions
import StackAlt from '../../assets/colors.svg';
export default {
component: Image,
args: {
src: StackAlt,
alt: 'Stack Alt',
},
};
export const Default = {};
export const BlurredPlaceholder = {
args: {
placeholder: 'blur',
},
};
export const BlurredAbsolutePlaceholder = {
args: {
src: 'https://storybook.js.org/images/placeholders/50x50.png',
width: 50,
height: 50,
blurDataURL:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAABP5JREFUWEeNlwtz2zgMhEGKsv/k9XFp82xe11763yOJVGcXC4m2czM3GYa0E2s/LACSTi+vv1czM/7CvPpqxY/ejPeS3khmFiPHOiVLHKaZi4ux4j18GpMlS6cALupEQBCKQM4BdnGzjIcJgs//QBxAPQAem55f3yL4PWJIdyCyhlMfPdYZot0cwj3Ayg/5JwHA13paen7pADphxr/n5VI8JQsHCCGQ3gVGLLsxQ3h/LYSn5383B05rwNOws3Z576LOTOduvwfrOd5FtVat4akx0uPTrw8BNuUz23vLsY7hmg7i4ipqM2saiAdruNuirh4ff0bNdb3Qy3vkvfAQwrkHoDxTTZtDCOKrC1bMEkdnsQh/PLyetOGHkXeRAgQAQ84efQcBdUhvFofoulpdm9WGGTA+AJEe7l+i37a2c371tCFKs5zzJjxQNBMi1im7OCudP2aNghJuzZaGdSMEZjpwf/t0UgNdg9DOyLGLnY0BUHlzwVtNDkgEQhBeKkb1tUDgQrq7frwAiIJi5BKAeIFgHk5mOpPzvgltOfcoK0Rrs7lWHwsgqtXarK3N0u23h5Ne8+3Cqxn5RYSMfHCAMgDAx4CBWlA9RAGw0GA/ol0gvFB4WjAvBAFUa83SzdUdAbYMqp28uHpxCRefxwAYhksAFBlthxCiXig+zT4TYqkC+Hq7OdAfJv8lPpZiZShWBBIuRP+jspDb2lwcDkzz7OLzbO/zvAHAoXTz5eYMQL0t2yHAiCFcfPY1QDwNFylA5bPoFpsV9fsEiMl8dhcc4PP1CYD3drYcBYdIKQrx0cbRxd2JHSDcQ297/vvoZ5smRC+AyV2AQ+nm03evge08Tyy4jGqXzWWEoIvTgXHU38pWiNgH4ixB/ukAcy/xycXfp4kwdAAAt399W+OCgMjxILQacxvRQ3gEwHgKUIr/rz53CuDFNyP/Eob4+/vEWkBq6AAA/HIi62n/Lk67Q7wDYQ0UpQB7hc54T4E6gACLTYxeAwB0YKZL6U4ATEGIBwCs7qPfQJCCHkCnoK50noJKcXcAojsEAJZZKXhgCoziGKxqWV8IMNp4kP2aC+oB0TMFvhGxDQHQfIPhDrilwKOm/YCZASAHfgBABQjr3f7CyAkA0cPB03AQULRhKd4xAIjzHymo2Gp7gN0FAMAVOoA2fPz03a9ssh/RM7Iz8QKIzYF9HyB0XEZ1xJ4DzNoDOAfAslhDDTyjDfv8A2AcBeCiu/jBHQEgxnYW6Kp6BlCVAkQM8VnieF2Xyr0ivXy+XvsCzKOihwNHCCryw8HrQXVB8dgFeRfAVQiXjMbIIgXINQYB2H7Kf5wF/2Ar7h0AgKKGuAP4zOjhzlkLbpcRXKRZhNUjxG6HIQDOjN47gCn4+fWW3xVS9urPESEEwwHMo9IhAGxS2ISiA1iEnQOoA4hXRAwItp7WzL9Ow18ESJaw/ar4NgeOR49cAHCAnaH8swBhv+6CBGjeBSxEOUAI7HyKHkD4O9xKb3/feQouAI4uLBciHRRHmgbfA7h/xFc9AngNBADthvii1sMOiPwDAFeyt6s7FSFS4PmnA1v0vQvqDqQKAAPE/weAUuEgsj8c+H11Twdw/AKANXA82EDr5cJBEEzB3oI4Mb0AdR3nNw8vQnegWuvqAABwJFJEBwDgNdA7IOs3gL0LhuJdwBY8c4BfNnDdVgooHiOqn/b7JoSW/QODjTHXhU7hMQAAAABJRU5ErkJggg==',
placeholder: 'blur',
},
};

View File

@ -25,14 +25,15 @@ import { parseList, getEnvConfig } from './utils';
const pkg = readUpSync({ cwd: __dirname }).packageJson;
const consoleLogger = console;
program.option(
'--disable-telemetry',
'disable sending telemetry data',
// default value is false, but if the user sets STORYBOOK_DISABLE_TELEMETRY, it can be true
process.env.STORYBOOK_DISABLE_TELEMETRY && process.env.STORYBOOK_DISABLE_TELEMETRY !== 'false'
);
program.option('--enable-crash-reports', 'enable sending crash reports to telemetry data');
program
.option(
'--disable-telemetry',
'disable sending telemetry data',
// default value is false, but if the user sets STORYBOOK_DISABLE_TELEMETRY, it can be true
process.env.STORYBOOK_DISABLE_TELEMETRY && process.env.STORYBOOK_DISABLE_TELEMETRY !== 'false'
)
.option('--debug', 'Get more logs in debug mode', false)
.option('--enable-crash-reports', 'enable sending crash reports to telemetry data');
program
.command('init')

View File

@ -1,4 +1,39 @@
export const allTemplates = {
export type SkippableTask = 'smoke-test' | 'test-runner' | 'chromatic' | 'e2e-tests';
export type TemplateKey = keyof typeof allTemplates;
export type Cadence = keyof typeof templatesByCadence;
export type Template = {
/**
* Readable name for the template, which will be used for feedback and the status page
*/
name: string;
/**
* Script used to generate the base project of a template.
* The Storybook CLI will then initialize Storybook on top of that template.
* This is used to generate projects which are pushed to https://github.com/storybookjs/repro-templates-temp
*/
script: string;
/**
* Used to assert various things about the generated template.
* If the template is generated with a different expected framework, it will fail, detecting a possible regression.
*/
expected: {
framework: string;
renderer: string;
builder: string;
};
/**
* Some sandboxes might not work properly in specific tasks temporarily, but we might
* still want to run the other tasks. Set the ones to skip in this property.
*/
skipTasks?: SkippableTask[];
/**
* Set this only while developing a newly created framework, to avoid using it in CI.
* NOTE: Make sure to always add a TODO comment to remove this flag in a subsequent PR.
*/
inDevelopment?: boolean;
};
export const allTemplates: Record<string, Template> = {
'cra/default-js': {
name: 'Create React App (Javascript)',
script: 'npx create-react-app .',
@ -21,9 +56,19 @@ export const allTemplates = {
builder: '@storybook/builder-webpack5',
},
},
'nextjs/12-js': {
name: 'Next.js v12 (JavaScript)',
script:
'yarn create next-app {{beforeDir}} -e https://github.com/vercel/next.js/tree/next-12-3-2/examples/hello-world && cd {{beforeDir}} && npm pkg set "dependencies.next"="^12" && yarn && git add . && git commit --amend --no-edit && cd ..',
expected: {
framework: '@storybook/nextjs',
renderer: '@storybook/react',
builder: '@storybook/builder-webpack5',
},
},
'nextjs/default-js': {
name: 'Next.js (JavaScript)',
script: 'npx create-next-app {{beforeDir}}',
script: 'yarn create next-app {{beforeDir}} --javascript --eslint',
expected: {
framework: '@storybook/nextjs',
renderer: '@storybook/react',
@ -32,7 +77,7 @@ export const allTemplates = {
},
'nextjs/default-ts': {
name: 'Next.js (TypeScript)',
script: 'npx create-next-app {{beforeDir}} --typescript',
script: 'yarn create next-app {{beforeDir}} --typescript --eslint',
expected: {
framework: '@storybook/nextjs',
renderer: '@storybook/react',
@ -256,8 +301,6 @@ export const allTemplates = {
},
};
type TemplateKey = keyof typeof allTemplates;
export const ci: TemplateKey[] = ['cra/default-ts', 'react-vite/default-ts'];
export const pr: TemplateKey[] = [
...ci,

View File

@ -8,12 +8,12 @@
import path from 'path';
import fs from 'fs-extra';
import { normalizeStoriesEntry } from '@storybook/core-common';
import type { NormalizedStoriesSpecifier, StoryIndexer } from '@storybook/types';
import type { NormalizedStoriesSpecifier, StoryIndexer, StoryIndexEntry } from '@storybook/types';
import { loadCsf, getStorySortParameter } from '@storybook/csf-tools';
import { toId } from '@storybook/csf';
import { logger } from '@storybook/node-logger';
import { StoryIndexGenerator } from './StoryIndexGenerator';
import { StoryIndexGenerator, DuplicateEntriesError } from './StoryIndexGenerator';
jest.mock('@storybook/csf-tools');
jest.mock('@storybook/csf', () => {
@ -902,6 +902,27 @@ describe('StoryIndexGenerator', () => {
`"🚨 You have a story for A with the same name as your default docs entry name (Story One), so the docs page is being dropped. Consider changing the story name."`
);
});
it('warns when two duplicate stories exists, with duplicated entries details', async () => {
const generator = new StoryIndexGenerator([storiesSpecifier, docsSpecifier], {
...options,
});
await generator.initialize();
const mockEntry: StoryIndexEntry = {
id: 'StoryId',
name: 'StoryName',
title: 'ComponentTitle',
importPath: 'Path',
type: 'story',
};
expect(() => {
generator.chooseDuplicate(mockEntry, mockEntry);
}).toThrow(
new DuplicateEntriesError(`Duplicate stories with id: ${mockEntry.id}`, [
mockEntry,
mockEntry,
])
);
});
});
});

View File

@ -37,6 +37,16 @@ type StoriesCacheEntry = {
type CacheEntry = false | StoriesCacheEntry | DocsCacheEntry;
type SpecifierStoriesCache = Record<Path, CacheEntry>;
export class DuplicateEntriesError extends Error {
entries: IndexEntry[];
constructor(message: string, entries: IndexEntry[]) {
super();
this.message = message;
this.entries = entries;
}
}
const makeAbsolute = (otherImport: Path, normalizedPath: Path, workingDir: Path) =>
otherImport.startsWith('.')
? slash(
@ -352,7 +362,11 @@ export class StoryIndexGenerator {
const changeDocsName = 'Use `<Meta of={} name="Other Name">` to distinguish them.';
// This shouldn't be possible, but double check and use for typing
if (worseEntry.type === 'story') throw new Error(`Duplicate stories with id: ${firstEntry.id}`);
if (worseEntry.type === 'story')
throw new DuplicateEntriesError(`Duplicate stories with id: ${firstEntry.id}`, [
firstEntry,
secondEntry,
]);
if (betterEntry.type === 'story') {
const worseDescriptor = worseEntry.standalone

View File

@ -7,6 +7,10 @@ export default {
tags: ['docsPage'],
argTypes: {
backgroundColor: { control: 'color' },
size: {
control: { type: 'select' },
options: ['small', 'medium', 'large'],
},
},
};

View File

@ -7,15 +7,17 @@
export let primary = false;
/**
* What background color to use
* @type {string} What background color to use
*/
export let backgroundColor = undefined;
/**
* How large should the button be?
* @type {'small' | 'medium' | 'large'} How large should the button be?
*/
export let size = 'medium';
/**
* Button contents
* @type {string} Button contents
*/
export let label;

View File

@ -2,12 +2,17 @@ import type { Meta, StoryObj } from '@storybook/svelte';
import Button from './Button.svelte';
// More on how to set up stories at: https://storybook.js.org/docs/svelte/writing-stories/introduction#default-export
// More on how to set up stories at: https://storybook.js.org/docs/7.0/svelte/writing-stories/introduction
const meta: Meta<Button> = {
title: 'Example/Button',
component: Button,
tags: ['docsPage'],
argTypes: {
backgroundColor: { control: 'color' },
size: {
control: { type: 'select' },
options: ['small', 'medium', 'large'],
},
},
};

View File

@ -9,6 +9,10 @@ const meta = {
tags: ['docsPage'],
argTypes: {
backgroundColor: { control: 'color' },
size: {
control: { type: 'select' },
options: ['small', 'medium', 'large'],
},
},
} satisfies Meta<Button>;

View File

@ -5,6 +5,7 @@ import type { StorybookConfig } from '../../frameworks/react-vite/dist';
const isBlocksOnly = process.env.STORYBOOK_BLOCKS_ONLY === 'true';
const allStories = [
'../../lib/cli/rendererAssets/common/Introduction.stories.mdx',
{
directory: '../manager/src',
titlePrefix: '@storybook-manager',

View File

@ -17,6 +17,7 @@ import type { ReactRenderer } from '@storybook/react';
import type { Channel } from '@storybook/channels';
import { DocsContainer } from '../blocks/src/blocks/DocsContainer';
import { DocsContent, DocsWrapper } from '../blocks/src/components';
const { document } = global;
@ -142,6 +143,19 @@ export const decorators = [
) : (
<Story />
),
/**
* This decorator adds wrappers that contains global styles for stories to be targeted by.
* Activated with parameters.docsStyles = true
*/ (Story, { parameters: { docsStyles } }) =>
docsStyles ? (
<DocsWrapper className="sbdocs sbdocs-wrapper">
<DocsContent className="sbdocs sbdocs-content">
<Story />
</DocsContent>
</DocsWrapper>
) : (
<Story />
),
/**
* This decorator adds Symbols that the sidebar icons references.
* Any sidebar story that uses the icons must set the parameter withSymbols: true .

View File

@ -8,5 +8,7 @@ export interface AnchorProps {
}
export const Anchor: FC<AnchorProps> = ({ storyId, children }) => (
<div id={anchorBlockIdFromId(storyId)}>{children}</div>
<div id={anchorBlockIdFromId(storyId)} className="sb-anchor">
{children}
</div>
);

View File

@ -112,7 +112,7 @@ const Story: FC<StoryProps> = (props) => {
// FIXME: height/style/etc. lifted from PureStory
const { height } = storyProps;
return (
<div id={storyBlockIdFromId(story.id)}>
<div id={storyBlockIdFromId(story.id)} className="sb-story">
{height ? (
<style>{`#story--${story.id} { min-height: ${height}; transform: translateZ(0); overflow: auto }`}</style>
) : null}

View File

@ -0,0 +1,33 @@
import { Unstyled } from './Unstyled.tsx';
# The Unstyled Block
By default most elements in docs have a few default styles applied to ensure the docs look good. This is achieved by applying default styles to most elements like `h1`, `p`, etc..
However sometimes you might want some of your content to not have these styles applied, this is where the `Unstyled` block is useful. Wrap any content you want in the `Unstyled` block to remove the default styles:
```md
import { Unstyled } from '@storybook/blocks';
> This block quote will be styled
... and so will this paragraph.
<Unstyled>
> This block quote will not be styled
... neither will this paragraph, nor the following component:
<MyCustomComponent />
</Unstyled>
```
Yields:
> This block quote will be styled
... and so will this paragraph.
<Unstyled>
> This block quote will not be styled
... neither will this paragraph, nor the following component:
</Unstyled>

View File

@ -0,0 +1,3 @@
import React from 'react';
export const Unstyled: React.FC = (props) => <div {...props} className="sb-unstyled" />;

View File

@ -2,6 +2,7 @@ import { Description } from './Description';
export default {
component: Description,
parameters: { docsStyles: true },
};
const textCaption = `That was Wintermute, manipulating the lock the way it had manipulated the drone micro and the amplified breathing of the room where Case waited. The semiotics of the bright void beyond the chain link. The tug Marcus Garvey, a steel drum nine meters long and two in diameter, creaked and shuddered as Maelcum punched for a California gambling cartel, then as a paid killer in the dark, curled in his capsule in some coffin hotel, his hands clawed into the nearest door and watched the other passengers as he rode. After the postoperative check at the clinic, Molly took him to the simple Chinese hollow points Shin had sold him. Still it was a handgun and nine rounds of ammunition, and as he made his way down Shiga from the missionaries, the train reached Cases station. Now this quiet courtyard, Sunday afternoon, this girl with a random collection of European furniture, as though Deane had once intended to use the place as his home. Case felt the edge of the Flatline as a construct, a hardwired ROM cassette replicating a dead mans skills, obsessions, kneejerk responses. They were dropping, losing altitude in a canyon of rainbow foliage, a lurid communal mural that completely covered the hull of the console in faded pinks and yellows.`;

View File

@ -1,7 +1,7 @@
import type { FC } from 'react';
import React from 'react';
import Markdown from 'markdown-to-jsx';
import { components, ResetWrapper } from '@storybook/components';
import { components } from '@storybook/components';
export interface DescriptionProps {
markdown: string;
@ -12,7 +12,12 @@ export interface DescriptionProps {
* components docgen docs.
*/
export const Description: FC<DescriptionProps> = ({ markdown }) => (
<ResetWrapper>
<Markdown options={{ forceBlock: true, overrides: components }}>{markdown}</Markdown>
</ResetWrapper>
<Markdown
options={{
forceBlock: true,
overrides: { code: components.code, pre: components.pre, a: components.a },
}}
>
{markdown}
</Markdown>
);

View File

@ -1,3 +1,4 @@
/* eslint-disable jsx-a11y/anchor-is-valid */
import type { ComponentProps } from 'react';
import React from 'react';
import { Global, css } from '@storybook/theming';
@ -8,6 +9,7 @@ import * as Preview from './Preview.stories';
import * as argsTable from './ArgsTable/ArgsTable.stories';
import * as source from './Source.stories';
import * as description from './Description.stories';
import { Unstyled } from '../blocks/Unstyled';
export default {
component: DocsPageWrapper,
@ -95,3 +97,30 @@ export const Markdown = () => (
<Source {...source.JSX.args} />
</DocsPageWrapper>
);
export const Html = {
name: 'HTML',
render: () => (
<DocsPageWrapper>
<h1>Heading 1</h1>
<h2>Heading 2</h2>
<a>A tag</a>
<pre>pre tag</pre>
<div>
<div>Div</div>
<a>Nested A tag</a>
</div>
<div style={{ border: '2px solid red' }}>
<Unstyled>
<h1>Unstyled content</h1>
<h2>Heading 2</h2>
<a>A tag</a>
<div>
<div>Div</div>
<a>Nested A tag</a>
</div>
</Unstyled>
</div>
</DocsPageWrapper>
),
};

View File

@ -1,9 +1,33 @@
import { withReset } from '@storybook/components';
import type { CSSObject } from '@storybook/theming';
import { styled } from '@storybook/theming';
import { transparentize } from 'polished';
import type { FC } from 'react';
import React from 'react';
/**
* This selector styles all raw elements inside the DocsPage like this example with a `<div/>`:
* :where(div:not(.sb-unstyled, .sbdocs-preview... :where(.sb-unstyled, .sbdocs-preview...) div))
*
* 1. first ':where': ensures this has a specificity of 0, making it easy to override.
* 2. 'div:not(...)': selects all div elements that are not...
* 3. '.sb-unstyled, .sbdocs-preview...': any of the elements we don't want to style
* 3. ':where(.sb-unstyled, .sbdocs-preview...) div': or are descendants of an .sb-unstyled or .sbdocs-preview, etc. It is a shorthand for '.sb-unstyled div, sbdocs-preview div...'
* 4. .sb-unstyled is an escape hatch that allows the user to opt-out of the default styles
* by wrapping their content in an element with this class: <Unstyled />
* 5. the other UNSTYLED_SELECTORS are elements we don't want the styles to bleed into, like canvas, story and source blocks.
*/
const UNSTYLED_SELECTORS = [
'.sb-unstyled',
'.sbdocs-preview',
'.sbdocs-pre',
'.sb-story',
'.docblock-source',
'.sb-anchor',
].join(', ');
const toGlobalSelector = (element: string): string =>
`& :where(${element}:not(${UNSTYLED_SELECTORS}, :where(${UNSTYLED_SELECTORS}) ${element}))`;
const breakpoint = 600;
export interface DocsPageProps {
@ -40,9 +64,374 @@ export const Subtitle = styled.h2(withReset, ({ theme }) => ({
color: transparentize(0.25, theme.color.defaultText),
}));
export const DocsContent = styled.div({
maxWidth: 1000,
width: '100%',
export const DocsContent = styled.div(({ theme }) => {
const reset = {
fontFamily: theme.typography.fonts.base,
fontSize: theme.typography.size.s3,
margin: 0,
WebkitFontSmoothing: 'antialiased',
MozOsxFontSmoothing: 'grayscale',
WebkitTapHighlightColor: 'rgba(0, 0, 0, 0)',
WebkitOverflowScrolling: 'touch' as CSSObject['WebkitOverflowScrolling'],
};
const headers = {
margin: '20px 0 8px',
padding: 0,
cursor: 'text',
position: 'relative',
color: theme.color.defaultText,
'&:first-of-type': {
marginTop: 0,
paddingTop: 0,
},
'&:hover a.anchor': {
textDecoration: 'none',
},
'& code': {
fontSize: 'inherit',
},
};
const code = {
lineHeight: 1,
margin: '0 2px',
padding: '3px 5px',
whiteSpace: 'nowrap',
borderRadius: 3,
fontSize: theme.typography.size.s2 - 1,
border:
theme.base === 'light'
? `1px solid ${theme.color.mediumlight}`
: `1px solid ${theme.color.darker}`,
color:
theme.base === 'light'
? transparentize(0.1, theme.color.defaultText)
: transparentize(0.3, theme.color.defaultText),
backgroundColor: theme.base === 'light' ? theme.color.lighter : theme.color.border,
};
return {
maxWidth: 1000,
width: '100%',
...reset,
[toGlobalSelector('a')]: {
...reset,
fontSize: 'inherit',
lineHeight: '24px',
color: theme.color.secondary,
textDecoration: 'none',
'&.absent': {
color: '#cc0000',
},
'&.anchor': {
display: 'block',
paddingLeft: 30,
marginLeft: -30,
cursor: 'pointer',
position: 'absolute',
top: 0,
left: 0,
bottom: 0,
},
},
[toGlobalSelector('blockquote')]: {
...reset,
margin: '16px 0',
borderLeft: `4px solid ${theme.color.medium}`,
padding: '0 15px',
color: theme.color.dark,
'& > :first-of-type': {
marginTop: 0,
},
'& > :last-child': {
marginBottom: 0,
},
},
[toGlobalSelector('div')]: reset,
[toGlobalSelector('dl')]: {
...reset,
margin: '16px 0',
padding: 0,
'& dt': {
fontSize: '14px',
fontWeight: 'bold',
fontStyle: 'italic',
padding: 0,
margin: '16px 0 4px',
},
'& dt:first-of-type': {
padding: 0,
},
'& dt > :first-of-type': {
marginTop: 0,
},
'& dt > :last-child': {
marginBottom: 0,
},
'& dd': {
margin: '0 0 16px',
padding: '0 15px',
},
'& dd > :first-of-type': {
marginTop: 0,
},
'& dd > :last-child': {
marginBottom: 0,
},
},
[toGlobalSelector('h1')]: {
...reset,
...headers,
fontSize: `${theme.typography.size.l1}px`,
fontWeight: theme.typography.weight.black,
},
[toGlobalSelector('h2')]: {
...reset,
...headers,
fontSize: `${theme.typography.size.m2}px`,
paddingBottom: 4,
borderBottom: `1px solid ${theme.appBorderColor}`,
},
[toGlobalSelector('h3')]: {
...reset,
...headers,
fontSize: `${theme.typography.size.m1}px`,
},
[toGlobalSelector('h4')]: {
...reset,
...headers,
fontSize: `${theme.typography.size.s3}px`,
},
[toGlobalSelector('h5')]: {
...reset,
...headers,
fontSize: `${theme.typography.size.s2}px`,
},
[toGlobalSelector('h6')]: {
...reset,
...headers,
fontSize: `${theme.typography.size.s2}px`,
color: theme.color.dark,
},
[toGlobalSelector('hr')]: {
border: '0 none',
borderTop: `1px solid ${theme.appBorderColor}`,
height: 4,
padding: 0,
},
[toGlobalSelector('img')]: {
maxWidth: '100%',
},
[toGlobalSelector('li')]: {
...reset,
fontSize: theme.typography.size.s2,
color: theme.color.defaultText,
lineHeight: '24px',
'& + li': {
marginTop: '.25em',
},
'& ul, & ol': {
marginTop: '.25em',
marginBottom: 0,
},
'& code': code,
},
[toGlobalSelector('ol')]: {
...reset,
margin: '16px 0',
paddingLeft: 30,
'& :first-of-type': {
marginTop: 0,
},
'& :last-child': {
marginBottom: 0,
},
},
[toGlobalSelector('p')]: {
...reset,
margin: '16px 0',
fontSize: theme.typography.size.s2,
lineHeight: '24px',
color: theme.color.defaultText,
'& code': code,
},
[toGlobalSelector('pre')]: {
...reset,
// reset
fontFamily: theme.typography.fonts.mono,
WebkitFontSmoothing: 'antialiased',
MozOsxFontSmoothing: 'grayscale',
lineHeight: '18px',
padding: '11px 1rem',
whiteSpace: 'pre-wrap',
color: 'inherit',
borderRadius: 3,
margin: '1rem 0',
'&:not(.prismjs)': {
background: 'transparent',
border: 'none',
borderRadius: 0,
padding: 0,
margin: 0,
},
'& pre, &.prismjs': {
padding: 15,
margin: 0,
whiteSpace: 'pre-wrap',
color: 'inherit',
fontSize: '13px',
lineHeight: '19px',
code: {
color: 'inherit',
fontSize: 'inherit',
},
},
'& code': {
whiteSpace: 'pre',
},
'& code, & tt': {
border: 'none',
},
},
[toGlobalSelector('span')]: {
...reset,
'&.frame': {
display: 'block',
overflow: 'hidden',
'& > span': {
border: `1px solid ${theme.color.medium}`,
display: 'block',
float: 'left',
overflow: 'hidden',
margin: '13px 0 0',
padding: 7,
width: 'auto',
},
'& span img': {
display: 'block',
float: 'left',
},
'& span span': {
clear: 'both',
color: theme.color.darkest,
display: 'block',
padding: '5px 0 0',
},
},
'&.align-center': {
display: 'block',
overflow: 'hidden',
clear: 'both',
'& > span': {
display: 'block',
overflow: 'hidden',
margin: '13px auto 0',
textAlign: 'center',
},
'& span img': {
margin: '0 auto',
textAlign: 'center',
},
},
'&.align-right': {
display: 'block',
overflow: 'hidden',
clear: 'both',
'& > span': {
display: 'block',
overflow: 'hidden',
margin: '13px 0 0',
textAlign: 'right',
},
'& span img': {
margin: 0,
textAlign: 'right',
},
},
'&.float-left': {
display: 'block',
marginRight: 13,
overflow: 'hidden',
float: 'left',
'& span': {
margin: '13px 0 0',
},
},
'&.float-right': {
display: 'block',
marginLeft: 13,
overflow: 'hidden',
float: 'right',
'& > span': {
display: 'block',
overflow: 'hidden',
margin: '13px auto 0',
textAlign: 'right',
},
},
},
[toGlobalSelector('table')]: {
...reset,
margin: '16px 0',
fontSize: theme.typography.size.s2,
lineHeight: '24px',
padding: 0,
borderCollapse: 'collapse',
'& tr': {
borderTop: `1px solid ${theme.appBorderColor}`,
backgroundColor: theme.appContentBg,
margin: 0,
padding: 0,
},
'& tr:nth-of-type(2n)': {
backgroundColor: theme.base === 'dark' ? theme.color.darker : theme.color.lighter,
},
'& tr th': {
fontWeight: 'bold',
color: theme.color.defaultText,
border: `1px solid ${theme.appBorderColor}`,
margin: 0,
padding: '6px 13px',
},
'& tr td': {
border: `1px solid ${theme.appBorderColor}`,
color: theme.color.defaultText,
margin: 0,
padding: '6px 13px',
},
'& tr th :first-of-type, & tr td :first-of-type': {
marginTop: 0,
},
'& tr th :last-child, & tr td :last-child': {
marginBottom: 0,
},
},
[toGlobalSelector('ul')]: {
...reset,
margin: '16px 0',
paddingLeft: 30,
'& :first-of-type': {
marginTop: 0,
},
'& :last-child': {
marginBottom: 0,
},
listStyle: 'disc',
},
};
});
export const DocsWrapper = styled.div(({ theme }) => ({

View File

@ -1,13 +1,7 @@
import type { FC, MouseEvent } from 'react';
import React, { Fragment } from 'react';
import { styled } from '@storybook/theming';
import {
FlexBar,
Icons,
IconButton,
IconButtonSkeleton,
getStoryHref,
} from '@storybook/components';
import { FlexBar, Icons, IconButton, IconButtonSkeleton } from '@storybook/components';
interface ZoomProps {
zoom: (val: number) => void;
@ -64,17 +58,6 @@ const Zoom: FC<ZoomProps> = ({ zoom, resetZoom }) => (
</>
);
const Eject: FC<EjectProps> = ({ baseUrl, storyId }) => (
<IconButton
key="opener"
href={getStoryHref(baseUrl, storyId)}
target="_blank"
title="Open canvas in new tab"
>
<Icons icon="share" />
</IconButton>
);
const Bar = styled(FlexBar)({
position: 'absolute',
left: 0,
@ -99,8 +82,5 @@ export const Toolbar: FC<ToolbarProps> = ({
<Zoom {...{ zoom, resetZoom }} />
)}
</Fragment>
<Fragment key="right">
{storyId && (isLoading ? <IconButtonSkeleton /> : <Eject {...{ storyId, baseUrl }} />)}
</Fragment>
</Bar>
);

View File

@ -24,6 +24,7 @@ const DefaultCodeBlock = styled.code(
const StyledSyntaxHighlighter = styled(SyntaxHighlighter)(({ theme }) => ({
// DocBlocks-specific styling and overrides
fontFamily: theme.typography.fonts.mono,
fontSize: `${theme.typography.size.s2 - 1}px`,
lineHeight: '19px',
margin: '25px 0 40px',

View File

@ -141,7 +141,7 @@ const useTools = (
);
return useMemo(() => {
return entry?.type === 'story'
return ['story', 'docs'].includes(entry?.type)
? filterTools(tools, toolsExtra, tabs, {
viewMode,
entry,

View File

@ -12,7 +12,7 @@ const menuMapper = ({ api, state }: Combo) => ({
export const menuTool: Addon = {
title: 'menu',
id: 'menu',
match: ({ viewMode }) => viewMode === 'story',
match: ({ viewMode }) => ['story', 'docs'].includes(viewMode),
render: () => (
<Consumer filter={menuMapper}>
{({ isVisible, toggle, singleStory }) =>

View File

@ -4274,100 +4274,100 @@ __metadata:
languageName: node
linkType: hard
"@next/env@npm:12.3.4":
version: 12.3.4
resolution: "@next/env@npm:12.3.4"
checksum: 69d372906d54691e032b5d7481d56bc03698fe5a03ed76952e53236ae34b0e3a09b0e098dc1b1bfc4d7588b158392dc500888dbb01413883727adf9e0aec894b
"@next/env@npm:13.0.5":
version: 13.0.5
resolution: "@next/env@npm:13.0.5"
checksum: 9384acc57a9dcef8817af0d860910bfb0aeac94f29114da47ea3935ca74899a0e1b89afa96cd4b5d7c0dba2db5cd4f1384e05b105ab84b0cb4c266b636173b10
languageName: node
linkType: hard
"@next/swc-android-arm-eabi@npm:12.3.4":
version: 12.3.4
resolution: "@next/swc-android-arm-eabi@npm:12.3.4"
"@next/swc-android-arm-eabi@npm:13.0.5":
version: 13.0.5
resolution: "@next/swc-android-arm-eabi@npm:13.0.5"
conditions: os=android & cpu=arm
languageName: node
linkType: hard
"@next/swc-android-arm64@npm:12.3.4":
version: 12.3.4
resolution: "@next/swc-android-arm64@npm:12.3.4"
"@next/swc-android-arm64@npm:13.0.5":
version: 13.0.5
resolution: "@next/swc-android-arm64@npm:13.0.5"
conditions: os=android & cpu=arm64
languageName: node
linkType: hard
"@next/swc-darwin-arm64@npm:12.3.4":
version: 12.3.4
resolution: "@next/swc-darwin-arm64@npm:12.3.4"
"@next/swc-darwin-arm64@npm:13.0.5":
version: 13.0.5
resolution: "@next/swc-darwin-arm64@npm:13.0.5"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
"@next/swc-darwin-x64@npm:12.3.4":
version: 12.3.4
resolution: "@next/swc-darwin-x64@npm:12.3.4"
"@next/swc-darwin-x64@npm:13.0.5":
version: 13.0.5
resolution: "@next/swc-darwin-x64@npm:13.0.5"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
"@next/swc-freebsd-x64@npm:12.3.4":
version: 12.3.4
resolution: "@next/swc-freebsd-x64@npm:12.3.4"
"@next/swc-freebsd-x64@npm:13.0.5":
version: 13.0.5
resolution: "@next/swc-freebsd-x64@npm:13.0.5"
conditions: os=freebsd & cpu=x64
languageName: node
linkType: hard
"@next/swc-linux-arm-gnueabihf@npm:12.3.4":
version: 12.3.4
resolution: "@next/swc-linux-arm-gnueabihf@npm:12.3.4"
"@next/swc-linux-arm-gnueabihf@npm:13.0.5":
version: 13.0.5
resolution: "@next/swc-linux-arm-gnueabihf@npm:13.0.5"
conditions: os=linux & cpu=arm
languageName: node
linkType: hard
"@next/swc-linux-arm64-gnu@npm:12.3.4":
version: 12.3.4
resolution: "@next/swc-linux-arm64-gnu@npm:12.3.4"
"@next/swc-linux-arm64-gnu@npm:13.0.5":
version: 13.0.5
resolution: "@next/swc-linux-arm64-gnu@npm:13.0.5"
conditions: os=linux & cpu=arm64 & libc=glibc
languageName: node
linkType: hard
"@next/swc-linux-arm64-musl@npm:12.3.4":
version: 12.3.4
resolution: "@next/swc-linux-arm64-musl@npm:12.3.4"
"@next/swc-linux-arm64-musl@npm:13.0.5":
version: 13.0.5
resolution: "@next/swc-linux-arm64-musl@npm:13.0.5"
conditions: os=linux & cpu=arm64 & libc=musl
languageName: node
linkType: hard
"@next/swc-linux-x64-gnu@npm:12.3.4":
version: 12.3.4
resolution: "@next/swc-linux-x64-gnu@npm:12.3.4"
"@next/swc-linux-x64-gnu@npm:13.0.5":
version: 13.0.5
resolution: "@next/swc-linux-x64-gnu@npm:13.0.5"
conditions: os=linux & cpu=x64 & libc=glibc
languageName: node
linkType: hard
"@next/swc-linux-x64-musl@npm:12.3.4":
version: 12.3.4
resolution: "@next/swc-linux-x64-musl@npm:12.3.4"
"@next/swc-linux-x64-musl@npm:13.0.5":
version: 13.0.5
resolution: "@next/swc-linux-x64-musl@npm:13.0.5"
conditions: os=linux & cpu=x64 & libc=musl
languageName: node
linkType: hard
"@next/swc-win32-arm64-msvc@npm:12.3.4":
version: 12.3.4
resolution: "@next/swc-win32-arm64-msvc@npm:12.3.4"
"@next/swc-win32-arm64-msvc@npm:13.0.5":
version: 13.0.5
resolution: "@next/swc-win32-arm64-msvc@npm:13.0.5"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
"@next/swc-win32-ia32-msvc@npm:12.3.4":
version: 12.3.4
resolution: "@next/swc-win32-ia32-msvc@npm:12.3.4"
"@next/swc-win32-ia32-msvc@npm:13.0.5":
version: 13.0.5
resolution: "@next/swc-win32-ia32-msvc@npm:13.0.5"
conditions: os=win32 & cpu=ia32
languageName: node
linkType: hard
"@next/swc-win32-x64-msvc@npm:12.3.4":
version: 12.3.4
resolution: "@next/swc-win32-x64-msvc@npm:12.3.4"
"@next/swc-win32-x64-msvc@npm:13.0.5":
version: 13.0.5
resolution: "@next/swc-win32-x64-msvc@npm:13.0.5"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
@ -5507,7 +5507,7 @@ __metadata:
jest-specific-snapshot: ^7.0.0
jest-vue-preprocessor: ^1.7.1
preact-render-to-string: ^5.1.19
pretty-format: ^28.0.0
pretty-format: ^29.0.0
react-test-renderer: ^16
read-pkg-up: ^7.0.1
rxjs: ^6.6.3
@ -6643,7 +6643,7 @@ __metadata:
fs-extra: ^9.0.1
image-size: ^1.0.0
loader-utils: ^3.2.0
next: ^12.2.4
next: ^13.0.5
pnp-webpack-plugin: ^1.7.0
postcss-loader: ^6.2.1
resolve-url-loader: ^5.0.0
@ -7805,12 +7805,12 @@ __metadata:
languageName: node
linkType: hard
"@swc/helpers@npm:0.4.11":
version: 0.4.11
resolution: "@swc/helpers@npm:0.4.11"
"@swc/helpers@npm:0.4.14":
version: 0.4.14
resolution: "@swc/helpers@npm:0.4.14"
dependencies:
tslib: ^2.4.0
checksum: 8806dda1f3cc243d80b1270405142563ed425350f9f4c7cfdd84967ead094878a3135b7ca1185052af0faef99982e57bb22e7f5fa80f1e9a3dab20e9e1051dfc
checksum: a8bd2e291fca73aa35ff316fb1aa9fb9554856518c8bf64ab5a355fb587d79d04d67f95033012fcdc94f507d22484871d95dc72efdd9ff13cc5d0ac68dfba999
languageName: node
linkType: hard
@ -12545,6 +12545,13 @@ __metadata:
languageName: node
linkType: hard
"client-only@npm:0.0.1":
version: 0.0.1
resolution: "client-only@npm:0.0.1"
checksum: 9d6cfd0c19e1c96a434605added99dff48482152af791ec4172fb912a71cff9027ff174efd8cdb2160cc7f377543e0537ffc462d4f279bc4701de3f2a3c4b358
languageName: node
linkType: hard
"cliui@npm:^5.0.0":
version: 5.0.0
resolution: "cliui@npm:5.0.0"
@ -24217,34 +24224,33 @@ __metadata:
languageName: node
linkType: hard
"next@npm:^12.2.4":
version: 12.3.4
resolution: "next@npm:12.3.4"
"next@npm:^13.0.5":
version: 13.0.5
resolution: "next@npm:13.0.5"
dependencies:
"@next/env": 12.3.4
"@next/swc-android-arm-eabi": 12.3.4
"@next/swc-android-arm64": 12.3.4
"@next/swc-darwin-arm64": 12.3.4
"@next/swc-darwin-x64": 12.3.4
"@next/swc-freebsd-x64": 12.3.4
"@next/swc-linux-arm-gnueabihf": 12.3.4
"@next/swc-linux-arm64-gnu": 12.3.4
"@next/swc-linux-arm64-musl": 12.3.4
"@next/swc-linux-x64-gnu": 12.3.4
"@next/swc-linux-x64-musl": 12.3.4
"@next/swc-win32-arm64-msvc": 12.3.4
"@next/swc-win32-ia32-msvc": 12.3.4
"@next/swc-win32-x64-msvc": 12.3.4
"@swc/helpers": 0.4.11
"@next/env": 13.0.5
"@next/swc-android-arm-eabi": 13.0.5
"@next/swc-android-arm64": 13.0.5
"@next/swc-darwin-arm64": 13.0.5
"@next/swc-darwin-x64": 13.0.5
"@next/swc-freebsd-x64": 13.0.5
"@next/swc-linux-arm-gnueabihf": 13.0.5
"@next/swc-linux-arm64-gnu": 13.0.5
"@next/swc-linux-arm64-musl": 13.0.5
"@next/swc-linux-x64-gnu": 13.0.5
"@next/swc-linux-x64-musl": 13.0.5
"@next/swc-win32-arm64-msvc": 13.0.5
"@next/swc-win32-ia32-msvc": 13.0.5
"@next/swc-win32-x64-msvc": 13.0.5
"@swc/helpers": 0.4.14
caniuse-lite: ^1.0.30001406
postcss: 8.4.14
styled-jsx: 5.0.7
use-sync-external-store: 1.2.0
styled-jsx: 5.1.0
peerDependencies:
fibers: ">= 3.1.0"
node-sass: ^6.0.0 || ^7.0.0
react: ^17.0.2 || ^18.0.0-0
react-dom: ^17.0.2 || ^18.0.0-0
react: ^18.2.0
react-dom: ^18.2.0
sass: ^1.3.0
dependenciesMeta:
"@next/swc-android-arm-eabi":
@ -24282,7 +24288,7 @@ __metadata:
optional: true
bin:
next: dist/bin/next
checksum: b802fcaa8a184ed73419f0be36b18394e6898be059c5a9bda637ff8ad0a411b85588a323cc33147ed19f89038d00c43fbfdc99adba5f88e1d3d96f34141ae323
checksum: b0cf0079977c610cc99389744f2085f27b2c707e2ab27d39a5c07f411febdf9dd3e54a57d9d5db8c495b041dd1f1660c23e0d65d8e3c519fa1425e4a9f77f65a
languageName: node
linkType: hard
@ -30915,9 +30921,11 @@ __metadata:
languageName: node
linkType: hard
"styled-jsx@npm:5.0.7":
version: 5.0.7
resolution: "styled-jsx@npm:5.0.7"
"styled-jsx@npm:5.1.0":
version: 5.1.0
resolution: "styled-jsx@npm:5.1.0"
dependencies:
client-only: 0.0.1
peerDependencies:
react: ">= 16.8.0 || 17.x.x || ^18.0.0-0"
peerDependenciesMeta:
@ -30925,7 +30933,7 @@ __metadata:
optional: true
babel-plugin-macros:
optional: true
checksum: eb6f388c7611468bb0ba48ecb0d0b269de4a0079ebeea56c5157552a414f00d575bfa1c37dd07eed5bff6547462b496f12b448dedeca386f778f1ccb2cf8b4ef
checksum: 32caa4d0ccae0ed82d666127f9230e0608d1922ce4383572d2446de47c258eeb9ebe138271ded50a74846a41fc0e49a6894c9333ecf170231e5a8292083ccfae
languageName: node
linkType: hard
@ -32884,15 +32892,6 @@ __metadata:
languageName: node
linkType: hard
"use-sync-external-store@npm:1.2.0":
version: 1.2.0
resolution: "use-sync-external-store@npm:1.2.0"
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
checksum: ac4814e5592524f242921157e791b022efe36e451fe0d4fd4d204322d5433a4fc300d63b0ade5185f8e0735ded044c70bcf6d2352db0f74d097a238cebd2da02
languageName: node
linkType: hard
"use@npm:^3.1.0":
version: 3.1.1
resolution: "use@npm:3.1.1"

View File

@ -216,23 +216,25 @@ The **`key`** `cra/default-js` consists of two parts:
- The prefix is the tool that was used to generate the repro app
- The suffix is options that modify the default install, e.g. a specific version or options
The **`script`** field is what generates the application environment. The `.` argument is “the current working directory” which is auto-generated based on the key (e.g. `repros/cra/default-js/before-storybook`).
The **`script`** field is what generates the application environment. The `.` argument is “the current working directory” which is auto-generated based on the key (e.g. `repros/cra/default-js/before-storybook`). The `{{beforeDir}}` key can also be used, which will be replaced by the path of that directory.
The rest of the fields are self-explanatory:
- **`name`**: Human readable name/description
- **`cadence`:** How often this runs in CI (for now these are the same for every template)
- **`expected`**: What framework/renderer/builder we expect `sb init` to generate
The **`skipTasks`** field exists because some sandboxes might not work properly in specific tasks temporarily, but we might still want to run the other tasks. For instance, a bug was introduced outside of our control, which fails only in the `test-runner` task.
The **`name`** field should contain a human readable name/description of the template.
The **`expected`** field reflects what framework/renderer/builder we expect `sb init` to generate. This is useful for assertions while generating sandboxes. If the template is generated with a different expected framework, for instance, it will fail, serving as a way to detect regressions.
### Running a sandbox
If your template has a `inDevelopment` flag, it will be generated (locally) as part of the sandbox process. You can create the sandbox with:
If your template has a `inDevelopment` flag, it will be generated (locally) as part of the sandbox process. You can create the sandbox with the following command, where `<template-key>` is replaced by the id of the selected template e.g. `cra/default-js`:
```bash
yarn task --task dev --template cra/default-js --no-link --start-from=install
yarn task --task dev --template <template-key> --start-from=install
```
Make sure you pass the `--no-link` flag as it is required for the local template generation to work.
Templates with `inDevelopment` will automatically run with `--no-link` flag as it is required for the local template generation to work.
Once the PR is merged, the template will be generated on a nightly cadence and you can remove the `inDevelopment` flag and the sandbox will pull the code from our templates repository.

1
prettier.config.js Normal file
View File

@ -0,0 +1 @@
module.exports = require('./scripts/prettier.config');

View File

@ -1,16 +1,17 @@
import { readdir } from 'fs/promises';
import { pathExists } from 'fs-extra';
import { resolve } from 'path';
import { allTemplates, templatesByCadence } from '../code/lib/cli/src/repro-templates';
import {
allTemplates,
templatesByCadence,
type Cadence,
type Template as TTemplate,
type SkippableTask,
} from '../code/lib/cli/src/repro-templates';
const sandboxDir = process.env.SANDBOX_ROOT || resolve(__dirname, '../sandbox');
export type Cadence = keyof typeof templatesByCadence;
export type Template = {
cadence?: readonly Cadence[];
skipTasks?: string[];
// there are other fields but we don't use them here
};
type Template = Pick<TTemplate, 'inDevelopment' | 'skipTasks'>;
export type TemplateKey = keyof typeof allTemplates;
export type Templates = Record<TemplateKey, Template>;
@ -44,9 +45,13 @@ export async function getTemplate(
potentialTemplateKeys = cadenceTemplates.map(([k]) => k) as TemplateKey[];
}
potentialTemplateKeys = potentialTemplateKeys.filter(
(t) => !(allTemplates[t] as Template).skipTasks?.includes(scriptName)
);
potentialTemplateKeys = potentialTemplateKeys.filter((t) => {
const currentTemplate = allTemplates[t] as Template;
return (
currentTemplate.inDevelopment !== true &&
!currentTemplate.skipTasks?.includes(scriptName as SkippableTask)
);
});
if (potentialTemplateKeys.length !== total) {
throw new Error(`Circle parallelism set incorrectly.

View File

@ -27,12 +27,12 @@ const BEFORE_DIR_NAME = 'before-storybook';
const AFTER_DIR_NAME = 'after-storybook';
const SCRIPT_TIMEOUT = 5 * 60 * 1000;
const sbInit = async (cwd: string, flags?: string[]) => {
const sbInit = async (cwd: string, flags?: string[], debug?: boolean) => {
const sbCliBinaryPath = join(__dirname, `../../code/lib/cli/bin/index.js`);
console.log(`🎁 Installing storybook`);
const env = { STORYBOOK_DISABLE_TELEMETRY: 'true', STORYBOOK_REPRO_GENERATOR: 'true' };
const fullFlags = ['--yes', ...(flags || [])];
await runCommand(`${sbCliBinaryPath} init ${fullFlags.join(' ')}`, { cwd, env });
await runCommand(`${sbCliBinaryPath} init ${fullFlags.join(' ')}`, { cwd, env }, debug);
};
const LOCAL_REGISTRY_URL = 'http://localhost:6001';
@ -56,7 +56,17 @@ const withLocalRegistry = async (packageManager: JsPackageManager, action: () =>
}
};
const addStorybook = async (baseDir: string, localRegistry: boolean, flags?: string[]) => {
const addStorybook = async ({
baseDir,
localRegistry,
flags,
debug,
}: {
baseDir: string;
localRegistry: boolean;
flags?: string[];
debug?: boolean;
}) => {
const beforeDir = join(baseDir, BEFORE_DIR_NAME);
const afterDir = join(baseDir, AFTER_DIR_NAME);
const tmpDir = join(baseDir, 'tmp');
@ -71,23 +81,21 @@ const addStorybook = async (baseDir: string, localRegistry: boolean, flags?: str
await withLocalRegistry(packageManager, async () => {
packageManager.addPackageResolutions(storybookVersions);
await sbInit(tmpDir, flags);
await sbInit(tmpDir, flags, debug);
});
} else {
await sbInit(tmpDir, flags);
await sbInit(tmpDir, flags, debug);
}
await rename(tmpDir, afterDir);
};
export const runCommand = async (script: string, options: ExecaOptions) => {
const shouldDebug = !!process.env.DEBUG;
if (shouldDebug) {
export const runCommand = async (script: string, options: ExecaOptions, debug: boolean) => {
if (debug) {
console.log(`Running command: ${script}`);
}
return execaCommand(script, {
stdout: shouldDebug ? 'inherit' : 'ignore',
stdout: debug ? 'inherit' : 'ignore',
shell: true,
...options,
});
@ -113,7 +121,8 @@ const addDocumentation = async (
const runGenerators = async (
generators: (GeneratorConfig & { dirName: string })[],
localRegistry = true
localRegistry = true,
debug = false
) => {
console.log(`🤹‍♂️ Generating repros with a concurrency of ${maxConcurrentTasks}`);
@ -142,10 +151,17 @@ const runGenerators = async (
// handle different modes of operation.
if (script.includes('{{beforeDir}}')) {
const scriptWithBeforeDir = script.replace('{{beforeDir}}', BEFORE_DIR_NAME);
await runCommand(scriptWithBeforeDir, { cwd: createBaseDir, timeout: SCRIPT_TIMEOUT });
await runCommand(
scriptWithBeforeDir,
{
cwd: createBaseDir,
timeout: SCRIPT_TIMEOUT,
},
debug
);
} else {
await ensureDir(createBeforeDir);
await runCommand(script, { cwd: createBeforeDir, timeout: SCRIPT_TIMEOUT });
await runCommand(script, { cwd: createBeforeDir, timeout: SCRIPT_TIMEOUT }, debug);
}
await localizeYarnConfigFiles(createBaseDir, createBeforeDir);
@ -156,7 +172,7 @@ const runGenerators = async (
// Make sure there are no git projects in the folder
await remove(join(beforeDir, '.git'));
await addStorybook(baseDir, localRegistry, flags);
await addStorybook({ baseDir, localRegistry, flags, debug });
await addDocumentation(baseDir, { name, dirName });
@ -191,9 +207,18 @@ export const options = createOptions({
description: 'Generate reproduction from local registry?',
promptType: false,
},
debug: {
type: 'boolean',
description: 'Print all the logs to the console',
promptType: false,
},
});
export const generate = async ({ template, localRegistry }: OptionValues<typeof options>) => {
export const generate = async ({
template,
localRegistry,
debug,
}: OptionValues<typeof options>) => {
const generatorConfigs = Object.entries(reproTemplates)
.map(([dirName, configuration]) => ({
dirName,
@ -207,13 +232,14 @@ export const generate = async ({ template, localRegistry }: OptionValues<typeof
return true;
});
await runGenerators(generatorConfigs, localRegistry);
await runGenerators(generatorConfigs, localRegistry, debug);
};
if (require.main === module) {
program
.description('Create a reproduction from a set of possible templates')
.option('--template <template>', 'Create a single template') // change this to allow multiple templates or regex
.option('--template <template>', 'Create a single template')
.option('--debug', 'Print all the logs to the console')
.option('--local-registry', 'Use local registry', false)
.action((optionValues) => {
generate(optionValues)

View File

@ -23,7 +23,11 @@ import { testRunner } from './tasks/test-runner';
import { chromatic } from './tasks/chromatic';
import { e2eTests } from './tasks/e2e-tests';
import { allTemplates as TEMPLATES } from '../code/lib/cli/src/repro-templates';
import {
allTemplates as TEMPLATES,
type TemplateKey,
type Template,
} from '../code/lib/cli/src/repro-templates';
const sandboxDir = process.env.SANDBOX_ROOT || resolve(__dirname, '../sandbox');
const codeDir = resolve(__dirname, '../code');
@ -31,8 +35,6 @@ const junitDir = resolve(__dirname, '../test-results');
export const extraAddons = ['a11y', 'storysource'];
export type TemplateKey = keyof typeof TEMPLATES;
export type Template = typeof TEMPLATES[TemplateKey];
export type Path = string;
export type TemplateDetails = {
key: TemplateKey;

View File

@ -53,7 +53,7 @@ export const create: Task['run'] = async (
} else {
await executeCLIStep(steps.repro, {
argument: key,
optionValues: { output: sandboxDir, branch: 'next' },
optionValues: { output: sandboxDir, branch: 'next', debug },
cwd: parentDir,
dryRun,
debug,

View File

@ -8,8 +8,6 @@ export const sandbox: Task = {
description: 'Create the sandbox from a template',
dependsOn: ({ template }, { link }) => {
if ('inDevelopment' in template && template.inDevelopment) {
if (link) throw new Error('Cannot link an in development template');
return ['run-registry', 'generate'];
}
@ -20,6 +18,13 @@ export const sandbox: Task = {
return pathExists(sandboxDir);
},
async run(details, options) {
if (options.link && details.template.inDevelopment) {
logger.log(
`The ${options.template} has inDevelopment property enabled, therefore the sandbox for that template cannot be linked. Enabling --no-link mode..`
);
// eslint-disable-next-line no-param-reassign
options.link = false;
}
if (await this.ready(details)) {
logger.info('🗑 Removing old sandbox dir');
await remove(details.sandboxDir);

View File

@ -23,6 +23,7 @@ export const steps = {
output: { type: 'string' },
// TODO allow default values for strings
branch: { type: 'string', values: ['next'] },
debug: { type: 'boolean' },
}),
},
add: {