mirror of
https://github.com/storybookjs/storybook.git
synced 2025-03-19 05:02:40 +08:00
Merge pull request #10801 from storybookjs/tech/cleanup-docs-code-fix-zoom
FIX zoom in docs
This commit is contained in:
commit
0f4e98d009
@ -1,7 +1,7 @@
|
||||
import React, { FunctionComponent, ReactNode } from 'react';
|
||||
import React, { FunctionComponent, ReactNode, ComponentProps } from 'react';
|
||||
import { MDXProvider } from '@mdx-js/react';
|
||||
import { resetComponents } from '@storybook/components/html';
|
||||
import { Story as PureStory, StoryProps as PureStoryProps } from '@storybook/components';
|
||||
import { Story as PureStory } from '@storybook/components';
|
||||
import { toId, storyNameFromExport } from '@storybook/csf';
|
||||
import { CURRENT_SELECTION } from './types';
|
||||
|
||||
@ -9,6 +9,8 @@ import { DocsContext, DocsContextProps } from './DocsContext';
|
||||
|
||||
export const storyBlockIdFromId = (storyId: string) => `story--${storyId}`;
|
||||
|
||||
type PureStoryProps = ComponentProps<typeof PureStory>;
|
||||
|
||||
interface CommonProps {
|
||||
height?: string;
|
||||
inline?: boolean;
|
||||
@ -48,14 +50,13 @@ export const getStoryProps = (props: StoryProps, context: DocsContextProps): Pur
|
||||
const { name } = props as StoryDefProps;
|
||||
const inputId = id === CURRENT_SELECTION ? context.id : id;
|
||||
const previewId = inputId || lookupStoryId(name, context);
|
||||
const data = context.storyStore.fromId(previewId) || {};
|
||||
|
||||
const { height, inline } = props;
|
||||
const data = context.storyStore.fromId(previewId);
|
||||
const { framework = null } = (data && data.parameters) || {};
|
||||
const { parameters = {}, docs = {} } = data;
|
||||
const { framework = null } = parameters;
|
||||
|
||||
const docsParam = (data && data.parameters && data.parameters.docs) || {};
|
||||
|
||||
if (docsParam.disable) {
|
||||
if (docs.disable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -64,8 +65,8 @@ export const getStoryProps = (props: StoryProps, context: DocsContextProps): Pur
|
||||
inlineStories = inferInlineStories(framework),
|
||||
iframeHeight = undefined,
|
||||
prepareForInline = undefined,
|
||||
} = docsParam;
|
||||
const { storyFn = undefined, name: storyName = undefined } = data || {};
|
||||
} = docs;
|
||||
const { storyFn = undefined, name: storyName = undefined } = data;
|
||||
|
||||
const storyIsInline = typeof inline === 'boolean' ? inline : inlineStories;
|
||||
if (storyIsInline && !prepareForInline && framework !== 'react') {
|
||||
@ -75,6 +76,7 @@ export const getStoryProps = (props: StoryProps, context: DocsContextProps): Pur
|
||||
}
|
||||
|
||||
return {
|
||||
parameters,
|
||||
inline: storyIsInline,
|
||||
id: previewId,
|
||||
storyFn: prepareForInline && storyFn ? () => prepareForInline(storyFn) : storyFn,
|
||||
|
@ -14,4 +14,4 @@ import { Meta, DocsContainer } from '@storybook/addon-docs/blocks';
|
||||
}}
|
||||
/>
|
||||
|
||||
<Story name='dummy'><div>some content</div></Story>
|
||||
<Story name='dummy' parameters={{ layout: 'fullscreen' }}><div>some content</div></Story>
|
||||
|
@ -5,6 +5,9 @@ import markdown from './markdown.stories.mdx';
|
||||
export default {
|
||||
title: 'Addons/Docs/mdx-in-story',
|
||||
decorators: [(storyFn) => <DocsContainer context={{}}>{storyFn()}</DocsContainer>],
|
||||
parameters: {
|
||||
layout: 'fullscreen',
|
||||
},
|
||||
};
|
||||
|
||||
// This renders the contents of the docs panel into story content
|
||||
|
@ -1,24 +1,44 @@
|
||||
import { Meta, Preview, Story } from '@storybook/addon-docs/blocks';
|
||||
import { Button } from '@storybook/react/demo';
|
||||
|
||||
<Meta
|
||||
title="Core/Layout MDX"
|
||||
component={Button}
|
||||
id="core-layout-mdx"
|
||||
decorators={[storyFn => <div style={{ backgroundColor: 'yellow' }}>{storyFn()}</div>]}
|
||||
decorators={[(storyFn) => <div style={{ backgroundColor: 'yellow' }}>{storyFn()}</div>]}
|
||||
/>
|
||||
|
||||
# Selected
|
||||
# Layout parameter
|
||||
|
||||
<Preview>
|
||||
This tests Storybook's built-in `layout` parameter, both as its applied in the canvas, and also how it's handled by the `Preview` block in `addon-docs`.
|
||||
|
||||
## Default
|
||||
|
||||
<Preview withToolbar>
|
||||
<Story name="defaultValue">
|
||||
<Button>Hello world</Button>
|
||||
</Story>
|
||||
</Preview>
|
||||
|
||||
## Padded
|
||||
|
||||
<Preview withToolbar>
|
||||
<Story name="padded" parameters={{ layout: 'padded' }}>
|
||||
<Button>Hello world</Button>
|
||||
</Story>
|
||||
</Preview>
|
||||
|
||||
## Fullscreen
|
||||
|
||||
<Preview withToolbar>
|
||||
<Story name="fullscreen" parameters={{ layout: 'fullscreen' }}>
|
||||
<Button>Hello world</Button>
|
||||
</Story>
|
||||
</Preview>
|
||||
|
||||
## Centered
|
||||
|
||||
<Preview withToolbar>
|
||||
<Story name="centered" parameters={{ layout: 'centered' }}>
|
||||
<Button>Hello world</Button>
|
||||
</Story>
|
||||
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { useEffect, useRef, useState } from '@storybook/client-api';
|
||||
|
||||
export default {
|
||||
title: 'Hooks',
|
||||
title: 'Core/Hooks',
|
||||
};
|
||||
|
||||
export const Checkbox = () => {
|
||||
|
@ -9,6 +9,9 @@ import * as descriptionStories from './Description.stories';
|
||||
export default {
|
||||
title: 'Docs/DocsPage',
|
||||
component: DocsWrapper,
|
||||
parameters: {
|
||||
layout: 'fullscreen',
|
||||
},
|
||||
decorators: [
|
||||
(storyFn) => (
|
||||
<DocsWrapper>
|
||||
|
@ -110,3 +110,29 @@ export const withToolbarMulti = () => (
|
||||
<Story inline storyFn={buttonFn} title="story2" />
|
||||
</Preview>
|
||||
);
|
||||
|
||||
export const withFullscreenSingle = () => (
|
||||
<Preview withToolbar>
|
||||
<Story inline storyFn={buttonFn} title="story1" parameters={{ layout: 'fullscreen' }} />
|
||||
</Preview>
|
||||
);
|
||||
|
||||
export const withFullscreenMulti = () => (
|
||||
<Preview withToolbar>
|
||||
<Story inline storyFn={buttonFn} title="story1" parameters={{ layout: 'fullscreen' }} />
|
||||
<Story inline storyFn={buttonFn} title="story2" parameters={{ layout: 'fullscreen' }} />
|
||||
</Preview>
|
||||
);
|
||||
|
||||
export const withCenteredSingle = () => (
|
||||
<Preview withToolbar>
|
||||
<Story inline storyFn={buttonFn} title="story1" parameters={{ layout: 'centered' }} />
|
||||
</Preview>
|
||||
);
|
||||
|
||||
export const withCenteredMulti = () => (
|
||||
<Preview withToolbar>
|
||||
<Story inline storyFn={buttonFn} title="story1" parameters={{ layout: 'centered' }} />
|
||||
<Story inline storyFn={buttonFn} title="story2" parameters={{ layout: 'centered' }} />
|
||||
</Preview>
|
||||
);
|
||||
|
@ -1,12 +1,12 @@
|
||||
import React, { Children, FunctionComponent, ReactElement, ReactNode, useState } from 'react';
|
||||
import { styled } from '@storybook/theming';
|
||||
import { darken } from 'polished';
|
||||
import { logger } from '@storybook/client-logger';
|
||||
import { styled } from '@storybook/theming';
|
||||
|
||||
import { getBlockBackgroundStyle } from './BlockBackgroundStyles';
|
||||
import { Source, SourceProps } from './Source';
|
||||
import { ActionBar, ActionItem } from '../ActionBar/ActionBar';
|
||||
import { Toolbar } from './Toolbar';
|
||||
import { ZoomContext } from './ZoomContext';
|
||||
|
||||
export interface PreviewProps {
|
||||
isColumn?: boolean;
|
||||
@ -17,20 +17,54 @@ export interface PreviewProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const ChildrenContainer = styled.div<PreviewProps>(({ isColumn, columns }) => ({
|
||||
display: 'flex',
|
||||
position: 'relative',
|
||||
flexWrap: 'wrap',
|
||||
padding: '10px 20px 30px 20px',
|
||||
overflow: 'auto',
|
||||
flexDirection: isColumn ? 'column' : 'row',
|
||||
type layout = 'padded' | 'fullscreen' | 'centered';
|
||||
|
||||
'> *': {
|
||||
flex: columns ? `1 1 calc(100%/${columns} - 20px)` : `1 1 0%`,
|
||||
marginTop: 20,
|
||||
maxWidth: '100%',
|
||||
},
|
||||
}));
|
||||
const ChildrenContainer = styled.div<PreviewProps & { zoom: number; layout: layout }>(
|
||||
({ isColumn, columns }) => ({
|
||||
display: isColumn || !columns ? 'block' : 'flex',
|
||||
position: 'relative',
|
||||
flexWrap: 'wrap',
|
||||
overflow: 'auto',
|
||||
flexDirection: isColumn ? 'column' : 'row',
|
||||
|
||||
'& > *': isColumn
|
||||
? {
|
||||
width: '100%',
|
||||
display: 'block',
|
||||
}
|
||||
: {
|
||||
maxWidth: '100%',
|
||||
display: 'inline-block',
|
||||
},
|
||||
}),
|
||||
({ layout = 'padded' }) =>
|
||||
layout === 'centered' || layout === 'padded'
|
||||
? {
|
||||
padding: '30px 20px',
|
||||
margin: -10,
|
||||
'& > *': {
|
||||
border: '10px solid transparent!important',
|
||||
},
|
||||
}
|
||||
: {},
|
||||
({ layout = 'padded' }) =>
|
||||
layout === 'centered'
|
||||
? {
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
justifyItems: 'center',
|
||||
alignContent: 'center',
|
||||
alignItems: 'center',
|
||||
}
|
||||
: {},
|
||||
({ zoom = 1 }) => ({
|
||||
'> *': {
|
||||
zoom: 1 / zoom,
|
||||
},
|
||||
}),
|
||||
({ columns }) =>
|
||||
columns && columns > 1 ? { '> *': { minWidth: `calc(100% / ${columns} - 20px)` } } : {}
|
||||
);
|
||||
|
||||
const StyledSource = styled(Source)<{}>(({ theme }) => ({
|
||||
margin: 0,
|
||||
@ -107,23 +141,6 @@ function getStoryId(children: ReactNode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const Relative = styled.div({
|
||||
position: 'relative',
|
||||
});
|
||||
|
||||
const Scale = styled.div<{ scale: number }>(
|
||||
{
|
||||
position: 'relative',
|
||||
},
|
||||
({ scale }) =>
|
||||
scale
|
||||
? {
|
||||
transform: `scale(${1 / scale})`,
|
||||
transformOrigin: 'top left',
|
||||
}
|
||||
: {}
|
||||
);
|
||||
|
||||
const PositionedToolbar = styled(Toolbar)({
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
@ -132,6 +149,23 @@ const PositionedToolbar = styled(Toolbar)({
|
||||
height: 40,
|
||||
});
|
||||
|
||||
const Relative = styled.div({
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
});
|
||||
|
||||
const getLayout = (children: ReactElement[]) => {
|
||||
return children.reduce((result, c) => {
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
if (typeof c === 'string' || typeof c === 'number') {
|
||||
return 'padded';
|
||||
}
|
||||
return (c.props && c.props.parameters && c.props.parameters.layout) || 'padded';
|
||||
}, undefined);
|
||||
};
|
||||
|
||||
/**
|
||||
* A preview component for showing one or more component `Story`
|
||||
* items. The preview also shows the source for the component
|
||||
@ -150,19 +184,18 @@ const Preview: FunctionComponent<PreviewProps> = ({
|
||||
const [expanded, setExpanded] = useState(isExpanded);
|
||||
const { source, actionItem } = getSource(withSource, expanded, setExpanded);
|
||||
const [scale, setScale] = useState(1);
|
||||
const previewClasses = className ? `${className} sbdocs sbdocs-preview` : 'sbdocs sbdocs-preview';
|
||||
const previewClasses = [className].concat(['sbdocs', 'sbdocs-preview']);
|
||||
|
||||
// @ts-ignore
|
||||
const layout = getLayout(Children.count(children) === 1 ? [children] : children);
|
||||
|
||||
if (withToolbar && Array.isArray(children)) {
|
||||
logger.warn('Cannot use toolbar with multiple preview children, disabling');
|
||||
}
|
||||
const showToolbar = withToolbar && !Array.isArray(children);
|
||||
return (
|
||||
<PreviewContainer
|
||||
{...{ withSource, withToolbar: showToolbar }}
|
||||
{...{ withSource, withToolbar }}
|
||||
{...props}
|
||||
className={previewClasses}
|
||||
className={previewClasses.join(' ')}
|
||||
>
|
||||
{showToolbar && (
|
||||
{withToolbar && (
|
||||
<PositionedToolbar
|
||||
border
|
||||
zoom={(z) => setScale(scale * z)}
|
||||
@ -171,16 +204,19 @@ const Preview: FunctionComponent<PreviewProps> = ({
|
||||
baseUrl="./iframe.html"
|
||||
/>
|
||||
)}
|
||||
<Relative>
|
||||
<ChildrenContainer isColumn={isColumn} columns={columns}>
|
||||
{Array.isArray(children) ? (
|
||||
children.map((child, i) => <div key={i.toString()}>{child}</div>)
|
||||
) : (
|
||||
<Scale scale={scale}>{children}</Scale>
|
||||
)}
|
||||
</ChildrenContainer>
|
||||
{withSource && <ActionBar actionItems={[actionItem]} />}
|
||||
</Relative>
|
||||
<ZoomContext.Provider value={{ scale }}>
|
||||
<Relative>
|
||||
<ChildrenContainer isColumn={isColumn} columns={columns} zoom={scale} layout={layout}>
|
||||
{Array.isArray(children) ? (
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
children.map((child, i) => <div key={i}>{child}</div>)
|
||||
) : (
|
||||
<div>{children}</div>
|
||||
)}
|
||||
</ChildrenContainer>
|
||||
{withSource && <ActionBar actionItems={[actionItem]} />}
|
||||
</Relative>
|
||||
</ZoomContext.Provider>
|
||||
{withSource && source}
|
||||
</PreviewContainer>
|
||||
);
|
||||
|
@ -1,4 +1,8 @@
|
||||
import React, { createElement, ElementType, FunctionComponent } from 'react';
|
||||
import React, { createElement, ElementType, FunctionComponent, Fragment } from 'react';
|
||||
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import { Parameters } from '@storybook/api';
|
||||
|
||||
import { IFrame } from './IFrame';
|
||||
import { EmptyBlock } from './EmptyBlock';
|
||||
import { ZoomContext } from './ZoomContext';
|
||||
@ -21,48 +25,22 @@ interface CommonProps {
|
||||
id: string;
|
||||
}
|
||||
|
||||
type InlineStoryProps = {
|
||||
interface InlineStoryProps extends CommonProps {
|
||||
parameters: Parameters;
|
||||
storyFn: ElementType;
|
||||
} & CommonProps;
|
||||
}
|
||||
|
||||
type IFrameStoryProps = CommonProps;
|
||||
|
||||
type ErrorProps = {
|
||||
error?: StoryError;
|
||||
} & CommonProps;
|
||||
|
||||
// How do you XOR properties in typescript?
|
||||
export type StoryProps = (InlineStoryProps | IFrameStoryProps | ErrorProps) & {
|
||||
inline: boolean;
|
||||
};
|
||||
|
||||
const InlineZoomWrapper: FunctionComponent<{ scale: number }> = ({ scale, children }) => {
|
||||
return scale === 1 ? (
|
||||
<>{children}</>
|
||||
) : (
|
||||
<div style={{ overflow: 'hidden' }}>
|
||||
<div
|
||||
style={{
|
||||
transform: `scale(${1 / scale})`,
|
||||
transformOrigin: 'top left',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
type StoryProps = InlineStoryProps | IFrameStoryProps;
|
||||
|
||||
const InlineStory: FunctionComponent<InlineStoryProps> = ({ storyFn, height, id }) => (
|
||||
<div style={{ height }}>
|
||||
<ZoomContext.Consumer>
|
||||
{({ scale }) => (
|
||||
<InlineZoomWrapper scale={scale}>
|
||||
{storyFn ? createElement(storyFn) : <EmptyBlock>{MISSING_STORY(id)}</EmptyBlock>}
|
||||
</InlineZoomWrapper>
|
||||
)}
|
||||
</ZoomContext.Consumer>
|
||||
</div>
|
||||
<Fragment>
|
||||
{height ? <style>{`#story--${id} { min-height: ${height} }`}</style> : null}
|
||||
<Fragment>
|
||||
{storyFn ? createElement(storyFn) : <EmptyBlock>{MISSING_STORY(id)}</EmptyBlock>}
|
||||
</Fragment>
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
const IFrameStory: FunctionComponent<IFrameStoryProps> = ({ id, title, height = '500px' }) => (
|
||||
@ -90,19 +68,22 @@ const IFrameStory: FunctionComponent<IFrameStoryProps> = ({ id, title, height =
|
||||
);
|
||||
|
||||
/**
|
||||
* A story element, either renderend inline or in an iframe,
|
||||
* A story element, either rendered inline or in an iframe,
|
||||
* with configurable height.
|
||||
*/
|
||||
const Story: FunctionComponent<StoryProps> = (props) => {
|
||||
const { error } = props as ErrorProps;
|
||||
const { storyFn } = props as InlineStoryProps;
|
||||
const { id, inline, title, height } = props;
|
||||
const Story: FunctionComponent<StoryProps & { inline: boolean; error?: StoryError }> = ({
|
||||
children,
|
||||
error,
|
||||
inline,
|
||||
...props
|
||||
}) => {
|
||||
const { id, title, height } = props;
|
||||
|
||||
if (error) {
|
||||
return <EmptyBlock>{error}</EmptyBlock>;
|
||||
}
|
||||
return inline ? (
|
||||
<InlineStory id={id} storyFn={storyFn} title={title} height={height} />
|
||||
<InlineStory {...(props as InlineStoryProps)} />
|
||||
) : (
|
||||
<IFrameStory id={id} title={title} height={height} />
|
||||
);
|
||||
|
@ -7,15 +7,7 @@ import markdownSample from './DocumentFormattingSample.md';
|
||||
export default {
|
||||
component: DocumentWrapper,
|
||||
title: 'Basics/DocumentFormatting',
|
||||
decorators: [
|
||||
(storyFn: any) => (
|
||||
<div
|
||||
style={{ width: '600px', margin: '3rem auto', padding: '40px 20px', background: 'white' }}
|
||||
>
|
||||
{storyFn()}
|
||||
</div>
|
||||
),
|
||||
],
|
||||
decorators: [(storyFn: any) => <div style={{ width: '600px' }}>{storyFn()}</div>],
|
||||
};
|
||||
|
||||
export const withMarkdown = () => (
|
||||
|
Loading…
x
Reference in New Issue
Block a user