mirror of
https://github.com/storybookjs/storybook.git
synced 2025-04-08 06:41:50 +08:00
Merge pull request #19805 from storybookjs/jeppe/sb-898-blocks-story
This commit is contained in:
commit
2d815dc3cd
@ -76,9 +76,9 @@
|
||||
"publish:debug": "npm run publish:latest -- --npm-tag=debug --no-push",
|
||||
"publish:latest": "lerna publish --exact --concurrency 1 --force-publish",
|
||||
"publish:next": "npm run publish:latest -- --npm-tag=next",
|
||||
"storybook:blocks": "BLOCKS_ONLY=true yarn storybook:ui",
|
||||
"storybook:blocks:build": "BLOCKS_ONLY=true yarn storybook:ui:build",
|
||||
"storybook:blocks:chromatic": "BLOCKS_ONLY=true yarn storybook:ui:chromatic --project-token=${CHROMATIC_TOKEN_STORYBOOK_BLOCKS:-MISSING_PROJECT_TOKEN}",
|
||||
"storybook:blocks": "STORYBOOK_BLOCKS_ONLY=true yarn storybook:ui",
|
||||
"storybook:blocks:build": "STORYBOOK_BLOCKS_ONLY=true yarn storybook:ui:build",
|
||||
"storybook:blocks:chromatic": "STORYBOOK_BLOCKS_ONLY=true yarn storybook:ui:chromatic --project-token=${CHROMATIC_TOKEN_STORYBOOK_BLOCKS:-MISSING_PROJECT_TOKEN}",
|
||||
"storybook:ui": "NODE_OPTIONS=\"--preserve-symlinks --preserve-symlinks-main\" ./lib/cli/bin/index.js dev --port 6006 --config-dir ./ui/.storybook --no-manager-cache",
|
||||
"storybook:ui:build": "NODE_OPTIONS=\"--preserve-symlinks --preserve-symlinks-main\" ./lib/cli/bin/index.js build --config-dir ./ui/.storybook",
|
||||
"storybook:ui:chromatic": "yarn chromatic --build-script-name storybook:ui:build --storybook-config-dir ./ui/.storybook --storybook-base-dir ./code/ui --project-token=${CHROMATIC_TOKEN_STORYBOOK_UI:-MISSING_PROJECT_TOKEN} --only-changed --exit-zero-on-changes --exit-once-uploaded",
|
||||
|
@ -2,7 +2,7 @@ import { vite as csfPlugin } from '@storybook/csf-plugin';
|
||||
import pluginTurbosnap from 'vite-plugin-turbosnap';
|
||||
import type { StorybookConfig } from '../../frameworks/react-vite/dist';
|
||||
|
||||
const isBlocksOnly = process.env.BLOCKS_ONLY === 'true';
|
||||
const isBlocksOnly = process.env.STORYBOOK_BLOCKS_ONLY === 'true';
|
||||
|
||||
const allStories = [
|
||||
{
|
||||
@ -56,6 +56,9 @@ const config: StorybookConfig = {
|
||||
core: {
|
||||
disableTelemetry: true,
|
||||
},
|
||||
features: {
|
||||
interactionsDebugger: true,
|
||||
},
|
||||
viteFinal: (viteConfig, { configType }) => ({
|
||||
...viteConfig,
|
||||
plugins: [
|
||||
|
@ -1,3 +1,5 @@
|
||||
/// <reference types="vite/client" />
|
||||
import React from 'react';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { Story as StoryComponent } from './Story';
|
||||
@ -6,15 +8,109 @@ import * as BooleanStories from '../controls/Boolean.stories';
|
||||
const meta: Meta<typeof StoryComponent> = {
|
||||
component: StoryComponent,
|
||||
parameters: {
|
||||
relativeCsfPaths: ['../controls/Boolean.stories'],
|
||||
relativeCsfPaths: ['../controls/Boolean.stories', '../blocks/Story.stories'],
|
||||
},
|
||||
};
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const BasicOf: Story = {
|
||||
export const Of: Story = {
|
||||
args: {
|
||||
of: BooleanStories.Undefined,
|
||||
},
|
||||
};
|
||||
|
||||
export const OfWithMeta: Story = {
|
||||
args: {
|
||||
of: BooleanStories.True,
|
||||
meta: BooleanStories.default,
|
||||
},
|
||||
};
|
||||
|
||||
const blocksAwareId = `${
|
||||
import.meta.env.STORYBOOK_BLOCKS_ONLY === 'true' ? '' : 'storybook-blocks-'
|
||||
}controls-boolean--false`;
|
||||
|
||||
export const Id: Story = {
|
||||
args: {
|
||||
id: blocksAwareId,
|
||||
},
|
||||
};
|
||||
|
||||
export const Name: Story = {
|
||||
args: {
|
||||
name: 'True',
|
||||
},
|
||||
};
|
||||
|
||||
export const SimpleSizeTest: Story = {
|
||||
render: () => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
background: '#fd5c9355',
|
||||
padding: '3rem',
|
||||
height: '1000px',
|
||||
width: '800px',
|
||||
// a global decorator is applying a default padding that we want to negate here
|
||||
margin: '-4rem -20px',
|
||||
}}
|
||||
>
|
||||
<p>
|
||||
This story does nothing. Its only purpose is to show how its size renders in different
|
||||
conditions (inline/iframe/fixed height) when used in a <code>{'<Story />'}</code> block.
|
||||
</p>
|
||||
<p>
|
||||
It has a fixed <code>height</code> of <code>1000px</code> and a fixed <code>width</code>{' '}
|
||||
of <code>800px</code>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const Inline: Story = {
|
||||
args: {
|
||||
of: SimpleSizeTest,
|
||||
inline: true,
|
||||
},
|
||||
};
|
||||
export const InlineWithHeight: Story = {
|
||||
...Inline,
|
||||
args: {
|
||||
of: SimpleSizeTest,
|
||||
inline: true,
|
||||
height: '300px',
|
||||
},
|
||||
};
|
||||
export const Iframe: Story = {
|
||||
...Inline,
|
||||
args: {
|
||||
of: SimpleSizeTest,
|
||||
inline: false,
|
||||
},
|
||||
};
|
||||
export const IframeWithHeight: Story = {
|
||||
...Inline,
|
||||
args: {
|
||||
of: SimpleSizeTest,
|
||||
inline: false,
|
||||
height: '300px',
|
||||
},
|
||||
};
|
||||
|
||||
export const WithDefaultInteractions: Story = {
|
||||
args: {
|
||||
of: BooleanStories.Toggling,
|
||||
},
|
||||
};
|
||||
export const WithInteractionsAutoplayInStory: Story = {
|
||||
args: {
|
||||
of: BooleanStories.TogglingInDocs,
|
||||
},
|
||||
};
|
||||
|
||||
// TODO: types suggest that <Story /> can take ProjectAnnotations, but it doesn't seem to do anything with them
|
||||
// Such as parameters, decorators, etc.
|
||||
// they seem to be taken from the story itself, and not from the <Story /> call
|
||||
|
@ -1,4 +1,4 @@
|
||||
import type { FC, ReactNode, ElementType, ComponentProps } from 'react';
|
||||
import type { FC, ComponentProps } from 'react';
|
||||
import React, { useContext, useRef, useEffect, useState } from 'react';
|
||||
import type {
|
||||
Renderer,
|
||||
@ -25,7 +25,6 @@ type CommonProps = StoryAnnotations & {
|
||||
|
||||
type StoryDefProps = {
|
||||
name: string;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
type StoryRefProps = {
|
||||
@ -36,7 +35,6 @@ type StoryRefProps = {
|
||||
|
||||
type StoryImportProps = {
|
||||
name: string;
|
||||
story: ElementType;
|
||||
};
|
||||
|
||||
export type StoryProps = (StoryDefProps | StoryRefProps | StoryImportProps) & CommonProps;
|
||||
@ -49,8 +47,7 @@ export const getStoryId = (props: StoryProps, context: DocsContextProps): StoryI
|
||||
}
|
||||
|
||||
const { name } = props as StoryDefProps;
|
||||
const inputId = id;
|
||||
return inputId || context.storyIdByName(name);
|
||||
return id || context.storyIdByName(name);
|
||||
};
|
||||
|
||||
export const getStoryProps = <TFramework extends Renderer>(
|
||||
@ -87,14 +84,16 @@ const Story: FC<StoryProps> = (props) => {
|
||||
const [showLoader, setShowLoader] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
let cleanup: () => void;
|
||||
if (story && storyRef.current) {
|
||||
const element = storyRef.current as HTMLElement;
|
||||
const { autoplay } = story.parameters.docs || {};
|
||||
cleanup = context.renderStoryToElement(story, element, { autoplay });
|
||||
setShowLoader(false);
|
||||
if (!(story && storyRef.current)) {
|
||||
return () => {};
|
||||
}
|
||||
return () => cleanup && cleanup();
|
||||
const element = storyRef.current as HTMLElement;
|
||||
const { autoplay } = story.parameters.docs || {};
|
||||
const cleanup = context.renderStoryToElement(story, element, { autoplay });
|
||||
setShowLoader(false);
|
||||
return () => {
|
||||
cleanup();
|
||||
};
|
||||
}, [context, story]);
|
||||
|
||||
if (!story) {
|
||||
@ -115,7 +114,7 @@ const Story: FC<StoryProps> = (props) => {
|
||||
return (
|
||||
<div id={storyBlockIdFromId(story.id)}>
|
||||
{height ? (
|
||||
<style>{`#story--${story.id} { min-height: ${height}px; transform: translateZ(0); overflow: auto }`}</style>
|
||||
<style>{`#story--${story.id} { min-height: ${height}; transform: translateZ(0); overflow: auto }`}</style>
|
||||
) : null}
|
||||
{showLoader && <StorySkeleton />}
|
||||
<div
|
||||
@ -134,9 +133,4 @@ const Story: FC<StoryProps> = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
Story.defaultProps = {
|
||||
children: null,
|
||||
name: null,
|
||||
};
|
||||
|
||||
export { Story };
|
||||
|
@ -1,10 +1,20 @@
|
||||
import { expect } from '@storybook/jest';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { within, fireEvent } from '@storybook/testing-library';
|
||||
import { addons } from '@storybook/addons';
|
||||
import { RESET_STORY_ARGS, STORY_ARGS_UPDATED } from '@storybook/core-events';
|
||||
import { BooleanControl } from './Boolean';
|
||||
|
||||
const meta = {
|
||||
component: BooleanControl,
|
||||
tags: ['docsPage'],
|
||||
parameters: { withRawArg: 'value', controls: { include: ['value'] } },
|
||||
parameters: {
|
||||
withRawArg: 'value',
|
||||
controls: { include: ['value'] },
|
||||
notes: 'These are notes for the Boolean control stories',
|
||||
info: 'This is info for the Boolean control stories',
|
||||
jsx: { useBooleanShorthandSyntax: false },
|
||||
},
|
||||
args: { name: 'boolean' },
|
||||
} as Meta<typeof BooleanControl>;
|
||||
|
||||
@ -26,3 +36,45 @@ export const Undefined: StoryObj<typeof BooleanControl> = {
|
||||
value: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
export const Toggling: StoryObj<typeof BooleanControl> = {
|
||||
args: {
|
||||
value: undefined,
|
||||
},
|
||||
play: async ({ canvasElement, id }) => {
|
||||
const channel = addons.getChannel();
|
||||
|
||||
channel.emit(RESET_STORY_ARGS, { storyId: id });
|
||||
await new Promise<void>((resolve) => {
|
||||
channel.once(STORY_ARGS_UPDATED, resolve);
|
||||
});
|
||||
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
// from Undefined to False
|
||||
const setBooleanControl = canvas.getByText('Set boolean');
|
||||
await fireEvent.click(setBooleanControl);
|
||||
|
||||
let toggle = await canvas.findByTitle('Change to true');
|
||||
expect(toggle).toBeInTheDocument();
|
||||
|
||||
// from False to True
|
||||
await fireEvent.click(toggle);
|
||||
toggle = await canvas.findByTitle('Change to false');
|
||||
expect(toggle).toBeInTheDocument();
|
||||
|
||||
// from True to False
|
||||
await fireEvent.click(toggle);
|
||||
toggle = await canvas.findByTitle('Change to true');
|
||||
expect(toggle).toBeInTheDocument();
|
||||
},
|
||||
};
|
||||
|
||||
export const TogglingInDocs: StoryObj<typeof BooleanControl> = {
|
||||
...Toggling,
|
||||
parameters: {
|
||||
docs: {
|
||||
autoplay: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -86,6 +86,11 @@ const Label = styled.label(({ theme }) => ({
|
||||
const parse = (value: string | null): boolean => value === 'true';
|
||||
|
||||
export type BooleanProps = ControlProps<BooleanValue> & BooleanConfig;
|
||||
/**
|
||||
* # Boolean control
|
||||
* Renders a switch toggle with "True" or "False".
|
||||
* or if the value is `undefined`, renders a button to set the boolean.
|
||||
*/
|
||||
export const BooleanControl: FC<BooleanProps> = ({ name, value, onChange, onBlur, onFocus }) => {
|
||||
const onSetFalse = useCallback(() => onChange(false), [onChange]);
|
||||
if (value === undefined) {
|
||||
@ -95,13 +100,14 @@ export const BooleanControl: FC<BooleanProps> = ({ name, value, onChange, onBlur
|
||||
</Form.Button>
|
||||
);
|
||||
}
|
||||
const controlId = getControlId(name);
|
||||
|
||||
const parsedValue = typeof value === 'string' ? parse(value) : value;
|
||||
|
||||
return (
|
||||
<Label htmlFor={name} title={parsedValue ? 'Change to false' : 'Change to true'}>
|
||||
<Label htmlFor={controlId} title={parsedValue ? 'Change to false' : 'Change to true'}>
|
||||
<input
|
||||
id={getControlId(name)}
|
||||
id={controlId}
|
||||
type="checkbox"
|
||||
onChange={(e) => onChange(e.target.checked)}
|
||||
checked={parsedValue}
|
||||
|
@ -1,6 +1,7 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"module": "esnext",
|
||||
"rootDir": "./src",
|
||||
"types": ["jest"]
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user