Merge pull request #10040 from storybookjs/tech/use-getdata

Tech/improvements extracted from composition
This commit is contained in:
Norbert de Langen 2020-03-04 12:06:48 +01:00 committed by GitHub
commit 6ba9b32221
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 385 additions and 269 deletions

View File

@ -64,7 +64,7 @@ const getSelectedBackgroundColor = (list: Input[], currentSelectedValue: string)
};
const mapper = ({ api, state }: Combo): { items: Input[]; selected: string | null } => {
const story = state.storiesHash[state.storyId];
const story = api.getData(state.storyId);
const list = story ? api.getParameters(story.id, PARAM_KEY) : [];
const selected = state.addons[PARAM_KEY] || null;

View File

@ -1,5 +1,4 @@
import React, { ChangeEvent, Component } from 'react';
import PropTypes from 'prop-types';
import { polyfill } from 'react-lifecycles-compat';
import isEqual from 'lodash/isEqual';
@ -109,19 +108,7 @@ interface ItemState {
}
class Item extends Component<ItemProps, ItemState> {
static propTypes = {
name: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
onEmit: PropTypes.func.isRequired,
// eslint-disable-next-line react/forbid-prop-types
payload: PropTypes.any,
};
static defaultProps = {
payload: {},
};
static getDerivedStateFromProps = ({ payload }: ItemProps, { prevPayload }: ItemState) => {
static getDerivedStateFromProps = ({ payload = {} }: ItemProps, { prevPayload }: ItemState) => {
if (!isEqual(payload, prevPayload)) {
const payloadString = json.plain(payload);
const refinedPayload = getJSONFromString(payloadString);

View File

@ -1,5 +1,4 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { styled } from '@storybook/theming';
import { API } from '@storybook/api';
@ -24,15 +23,6 @@ interface EventsPanelState {
}
export default class EventsPanel extends Component<EventsPanelProps, EventsPanelState> {
static propTypes = {
active: PropTypes.bool.isRequired,
api: PropTypes.shape({
emit: PropTypes.func,
off: PropTypes.func,
on: PropTypes.func,
}).isRequired,
};
state: EventsPanelState = {
events: [],
};

View File

@ -1,10 +1,6 @@
import React, { FunctionComponent } from 'react';
import PropTypes from 'prop-types';
import { style } from './style';
export const FullScreen: FunctionComponent = ({ children }) => {
return <div style={style.wrapper}>{children}</div>;
};
FullScreen.defaultProps = { children: null };
FullScreen.propTypes = { children: PropTypes.node };

View File

@ -1,5 +1,4 @@
import React, { FunctionComponent } from 'react';
import PropTypes from 'prop-types';
import GraphiQL from 'graphiql';
import 'graphiql/graphiql.css';
@ -16,7 +15,7 @@ const GQL: FunctionComponent<GQLProps> = ({ active }) => {
return active ? (
<Consumer>
{({ api, state }: Combo) => {
const story = state.storiesHash[state.storyId];
const story = api.getData(state.storyId);
const parameters = story ? api.getParameters(story.id, PARAM_KEY) : null;
if (parameters) {
@ -32,8 +31,5 @@ const GQL: FunctionComponent<GQLProps> = ({ active }) => {
</Consumer>
) : null;
};
GQL.propTypes = {
active: PropTypes.bool.isRequired,
};
export default GQL;

View File

@ -5,7 +5,6 @@ import React, {
FunctionComponent,
HTMLAttributes,
} from 'react';
import PropTypes from 'prop-types';
type MainProps = Omit<DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>, 'style'>;
const Main: FunctionComponent<MainProps> = props => (
@ -25,12 +24,6 @@ type TitleProps = DetailedHTMLProps<HTMLAttributes<HTMLHeadingElement>, HTMLHead
const Title: FunctionComponent<TitleProps> = ({ children, ...props }) => (
<h1 {...props}>{children}</h1>
);
Title.propTypes = {
children: PropTypes.node,
};
Title.defaultProps = {
children: undefined,
};
type NoteProps = Omit<
DetailedHTMLProps<HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>,
@ -85,14 +78,6 @@ const Link: FunctionComponent<LinkProps> = ({ children, href, target, rel, ...pr
{children}
</a>
);
Link.propTypes = {
href: PropTypes.string,
children: PropTypes.node,
};
Link.defaultProps = {
href: undefined,
children: undefined,
};
type NavButtonProps = Omit<
DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>,
@ -120,12 +105,6 @@ const NavButton: FunctionComponent<NavButtonProps> = ({ children, onClick, ...pr
{children}
</button>
);
NavButton.propTypes = {
children: PropTypes.node,
};
NavButton.defaultProps = {
children: undefined,
};
type WelcomeProps = {
showApp: () => void;
@ -176,8 +155,5 @@ const Welcome: FunctionComponent<WelcomeProps> = ({ showApp }) => (
</Main>
);
Welcome.displayName = 'Welcome';
Welcome.defaultProps = {
showApp: null,
};
export { Welcome as default };

View File

@ -1 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="132.948" height="37.739"><path fill="#currentColor" d="M31.314 14.575c.36 0 .48.13.48.48v7.92h4.07c.35 0 .48.13.48.48v.7c0 .35-.13.48-.48.48h-5.36c-.35 0-.49-.13-.49-.48v-9.1c0-.35.14-.48.49-.48zM39.894 18.065c0-2.31 1.46-3.7 4-3.7s4 1.39 4 3.7v3.08c0 2.31-1.47 3.7-4 3.7s-4-1.39-4-3.7zm6.32 0c0-1.3-.81-2-2.27-2s-2.26.71-2.26 2v3.1c0 1.31.8 2 2.26 2s2.27-.72 2.27-2zM59.804 19.125c.39 0 .48.13.48.48v1.9a3 3 0 01-1.06 2.36 4.36 4.36 0 01-3 .94c-2.59 0-4-1.39-4-3.7v-3.1c0-2.28 1.46-3.68 4-3.68 2 0 3.3.79 3.85 2.37a.43.43 0 01-.3.62l-.78.27c-.34.12-.48.05-.6-.3a2.06 2.06 0 00-2.17-1.3c-1.47 0-2.27.71-2.27 2v3.18c0 1.31.81 2 2.27 2s2.36-.67 2.36-1.65v-.74h-2.17c-.36 0-.49-.14-.49-.49v-.68c0-.35.13-.48.49-.48zM64.414 18.065c0-2.31 1.46-3.7 4-3.7s4 1.39 4 3.7v3.08c0 2.31-1.47 3.7-4 3.7s-4-1.39-4-3.7zm6.31 0c0-1.3-.81-2-2.26-2s-2.27.71-2.27 2v3.1c0 1.31.81 2 2.27 2s2.26-.72 2.26-2zM77.124 14.865c0-.22.07-.29.29-.29h.46c.22 0 .27.07.27.29v9.48c0 .22 0 .29-.27.29h-.46c-.22 0-.29-.07-.29-.29zM87.254 14.575a3.14 3.14 0 110 6.28h-3v3.49c0 .22-.05.29-.26.29h-.47c-.21 0-.29-.07-.29-.29v-9.48c0-.22.08-.29.29-.29zm-.09 5.29a2.18 2.18 0 100-4.36h-3v4.36zM98.204 14.375a3.61 3.61 0 013.72 2.18c.08.16 0 .29-.16.37l-.44.2c-.18.07-.25.06-.36-.13a2.72 2.72 0 00-2.76-1.64c-1.69 0-2.61.67-2.61 1.87a1.52 1.52 0 001.27 1.54 6.79 6.79 0 001.66.32 6.88 6.88 0 012 .41 2.25 2.25 0 011.56 2.37c0 1.87-1.36 3-3.86 3a3.61 3.61 0 01-3.83-2.43.27.27 0 01.17-.38l.44-.16a.27.27 0 01.36.17 2.86 2.86 0 002.86 1.8c1.89 0 2.82-.66 2.82-2a1.49 1.49 0 00-1.17-1.53 7 7 0 00-1.59-.28l-1.08-.14a9.5 9.5 0 01-1-.27 2.63 2.63 0 01-.89-.47 2.44 2.44 0 01-.8-1.91c.07-1.75 1.38-2.89 3.69-2.89zM107.474 21.215a2.78 2.78 0 005.55 0v-6.35c0-.22.07-.29.29-.29h.46c.22 0 .29.07.29.29v6.34c0 2.27-1.34 3.64-3.81 3.64s-3.81-1.37-3.81-3.64v-6.34c0-.22.07-.29.28-.29h.47c.21 0 .28.07.28.29zM127.864 14.575c.22 0 .29.07.29.29v9.48c0 .22-.07.29-.29.29h-.42c-.21 0-.28-.07-.28-.29v-5.77a18.55 18.55 0 01.17-2.51h-.06a18 18 0 01-1.09 2.21l-2.15 3.79a.35.35 0 01-.33.22h-.28a.37.37 0 01-.34-.22l-2.18-3.83a16.07 16.07 0 01-1-2.18h-.06a21.76 21.76 0 01.16 2.53v5.76c0 .22-.07.29-.29.29h-.39c-.22 0-.29-.07-.29-.29v-9.48c0-.22.07-.29.29-.29h.36a.4.4 0 01.4.23l3.5 6.22 3.48-6.16c.11-.21.17-.24.39-.24zM13.524 22.415v7.23a1 1 0 01-2.09 0v-7.22a1.77 1.77 0 001 .34 1.72 1.72 0 001.09-.35zm8.9-2.09a1 1 0 00-1 1v1.26a1 1 0 102.09 0v-1.21a1 1 0 00-1.09-1.05zm-13.26 4.83a1.8 1.8 0 01-1-.34v7.25a1.05 1.05 0 002.1 0v-7.2a1.83 1.83 0 01-1.1.29zm10-7a1 1 0 00-1.05 1v5.57a1.05 1.05 0 102.1 0v-5.5a1 1 0 00-1.1-1.05zm-3.32 2.14a1.83 1.83 0 01-1.05-.34v7.27a1.05 1.05 0 102.1 0v-7.26a1.77 1.77 0 01-1.1.35zm-8.95 5.57V5.555a.94.94 0 00-.94-.93h-.22a.94.94 0 00-.94.93v20.31a.94.94 0 00.94.94h.22a.94.94 0 00.94-.94zm2.38-1.48h-.22a.94.94 0 01-.94-.94V7.975a.94.94 0 01.94-.93h.22a.94.94 0 01.94.93v15.49a.94.94 0 01-.94.94zm3.31-2.38h-.23a.93.93 0 01-.93-.93v-10.7a.93.93 0 01.93-.94h.23a.94.94 0 01.93.94v10.7a.93.93 0 01-.93.93zm3.31-2.45h-.21a.94.94 0 01-.94-.93v-5.76a.94.94 0 01.94-1h.22a.94.94 0 01.94.94v5.8a.94.94 0 01-.94.95zm3.32-2.15h-.22a.94.94 0 01-.94-.94v-1.49a.94.94 0 01.94-.93h.22a.94.94 0 01.94.93v1.49a.94.94 0 01-.93.94z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="132.948" height="37.739">
<path fill="white" d="M31.314 14.575c.36 0 .48.13.48.48v7.92h4.07c.35 0 .48.13.48.48v.7c0 .35-.13.48-.48.48h-5.36c-.35 0-.49-.13-.49-.48v-9.1c0-.35.14-.48.49-.48zM39.894 18.065c0-2.31 1.46-3.7 4-3.7s4 1.39 4 3.7v3.08c0 2.31-1.47 3.7-4 3.7s-4-1.39-4-3.7zm6.32 0c0-1.3-.81-2-2.27-2s-2.26.71-2.26 2v3.1c0 1.31.8 2 2.26 2s2.27-.72 2.27-2zM59.804 19.125c.39 0 .48.13.48.48v1.9a3 3 0 01-1.06 2.36 4.36 4.36 0 01-3 .94c-2.59 0-4-1.39-4-3.7v-3.1c0-2.28 1.46-3.68 4-3.68 2 0 3.3.79 3.85 2.37a.43.43 0 01-.3.62l-.78.27c-.34.12-.48.05-.6-.3a2.06 2.06 0 00-2.17-1.3c-1.47 0-2.27.71-2.27 2v3.18c0 1.31.81 2 2.27 2s2.36-.67 2.36-1.65v-.74h-2.17c-.36 0-.49-.14-.49-.49v-.68c0-.35.13-.48.49-.48zM64.414 18.065c0-2.31 1.46-3.7 4-3.7s4 1.39 4 3.7v3.08c0 2.31-1.47 3.7-4 3.7s-4-1.39-4-3.7zm6.31 0c0-1.3-.81-2-2.26-2s-2.27.71-2.27 2v3.1c0 1.31.81 2 2.27 2s2.26-.72 2.26-2zM77.124 14.865c0-.22.07-.29.29-.29h.46c.22 0 .27.07.27.29v9.48c0 .22 0 .29-.27.29h-.46c-.22 0-.29-.07-.29-.29zM87.254 14.575a3.14 3.14 0 110 6.28h-3v3.49c0 .22-.05.29-.26.29h-.47c-.21 0-.29-.07-.29-.29v-9.48c0-.22.08-.29.29-.29zm-.09 5.29a2.18 2.18 0 100-4.36h-3v4.36zM98.204 14.375a3.61 3.61 0 013.72 2.18c.08.16 0 .29-.16.37l-.44.2c-.18.07-.25.06-.36-.13a2.72 2.72 0 00-2.76-1.64c-1.69 0-2.61.67-2.61 1.87a1.52 1.52 0 001.27 1.54 6.79 6.79 0 001.66.32 6.88 6.88 0 012 .41 2.25 2.25 0 011.56 2.37c0 1.87-1.36 3-3.86 3a3.61 3.61 0 01-3.83-2.43.27.27 0 01.17-.38l.44-.16a.27.27 0 01.36.17 2.86 2.86 0 002.86 1.8c1.89 0 2.82-.66 2.82-2a1.49 1.49 0 00-1.17-1.53 7 7 0 00-1.59-.28l-1.08-.14a9.5 9.5 0 01-1-.27 2.63 2.63 0 01-.89-.47 2.44 2.44 0 01-.8-1.91c.07-1.75 1.38-2.89 3.69-2.89zM107.474 21.215a2.78 2.78 0 005.55 0v-6.35c0-.22.07-.29.29-.29h.46c.22 0 .29.07.29.29v6.34c0 2.27-1.34 3.64-3.81 3.64s-3.81-1.37-3.81-3.64v-6.34c0-.22.07-.29.28-.29h.47c.21 0 .28.07.28.29zM127.864 14.575c.22 0 .29.07.29.29v9.48c0 .22-.07.29-.29.29h-.42c-.21 0-.28-.07-.28-.29v-5.77a18.55 18.55 0 01.17-2.51h-.06a18 18 0 01-1.09 2.21l-2.15 3.79a.35.35 0 01-.33.22h-.28a.37.37 0 01-.34-.22l-2.18-3.83a16.07 16.07 0 01-1-2.18h-.06a21.76 21.76 0 01.16 2.53v5.76c0 .22-.07.29-.29.29h-.39c-.22 0-.29-.07-.29-.29v-9.48c0-.22.07-.29.29-.29h.36a.4.4 0 01.4.23l3.5 6.22 3.48-6.16c.11-.21.17-.24.39-.24zM13.524 22.415v7.23a1 1 0 01-2.09 0v-7.22a1.77 1.77 0 001 .34 1.72 1.72 0 001.09-.35zm8.9-2.09a1 1 0 00-1 1v1.26a1 1 0 102.09 0v-1.21a1 1 0 00-1.09-1.05zm-13.26 4.83a1.8 1.8 0 01-1-.34v7.25a1.05 1.05 0 002.1 0v-7.2a1.83 1.83 0 01-1.1.29zm10-7a1 1 0 00-1.05 1v5.57a1.05 1.05 0 102.1 0v-5.5a1 1 0 00-1.1-1.05zm-3.32 2.14a1.83 1.83 0 01-1.05-.34v7.27a1.05 1.05 0 102.1 0v-7.26a1.77 1.77 0 01-1.1.35zm-8.95 5.57V5.555a.94.94 0 00-.94-.93h-.22a.94.94 0 00-.94.93v20.31a.94.94 0 00.94.94h.22a.94.94 0 00.94-.94zm2.38-1.48h-.22a.94.94 0 01-.94-.94V7.975a.94.94 0 01.94-.93h.22a.94.94 0 01.94.93v15.49a.94.94 0 01-.94.94zm3.31-2.38h-.23a.93.93 0 01-.93-.93v-10.7a.93.93 0 01.93-.94h.23a.94.94 0 01.93.94v10.7a.93.93 0 01-.93.93zm3.31-2.45h-.21a.94.94 0 01-.94-.93v-5.76a.94.94 0 01.94-1h.22a.94.94 0 01.94.94v5.8a.94.94 0 01-.94.95zm3.32-2.15h-.22a.94.94 0 01-.94-.94v-1.49a.94.94 0 01.94-.93h.22a.94.94 0 01.94.93v1.49a.94.94 0 01-.93.94z"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -13,6 +13,7 @@ addons.setConfig({
brandImage: logo,
brandTitle: 'Custom - Storybook',
...themes.dark,
appContentBg: 'white',
},
panelPosition: 'bottom',
selectedPanel: 'storybook/roundtrip',

View File

@ -33,6 +33,7 @@
"@storybook/client-logger": "6.0.0-alpha.20",
"@storybook/core-events": "6.0.0-alpha.20",
"@storybook/router": "6.0.0-alpha.20",
"@storybook/theming": "6.0.0-alpha.20",
"core-js": "^3.0.1",
"global": "^4.3.2",
"regenerator-runtime": "^0.13.3",

View File

@ -4,6 +4,7 @@ import { Channel } from '@storybook/channels';
import { API } from '@storybook/api';
import { RenderData as RouterData } from '@storybook/router';
import { logger } from '@storybook/client-logger';
import { ThemeVars } from '@storybook/theming';
import { types, Types } from './types';
export { Channel };
@ -38,6 +39,7 @@ interface Elements {
}
interface Config {
theme?: ThemeVars;
[key: string]: any;
}

View File

@ -68,6 +68,7 @@ export type Module = StoreData &
ProviderData & {
mode?: 'production' | 'development';
state: State;
fullAPI: API;
};
export type State = Other &
@ -124,7 +125,7 @@ type StatePartial = Partial<State>;
export type ManagerProviderProps = Children & RouterData & ProviderData & DocsModeData;
class ManagerProvider extends Component<ManagerProviderProps, State> {
api: API;
api: API = {} as API;
modules: any[];
@ -178,15 +179,17 @@ class ManagerProvider extends Component<ManagerProviderProps, State> {
initStories,
initURL,
initVersions,
].map(initModule => initModule({ ...routeData, ...apiData, state: this.state }));
].map(initModule =>
initModule({ ...routeData, ...apiData, state: this.state, fullAPI: this.api })
);
// Create our initial state by combining the initial state of all modules, then overlaying any saved state
const state = getInitialState(...this.modules.map(m => m.state));
// Get our API by combining the APIs exported by each module
const combo = Object.assign({ navigate }, ...this.modules.map(m => m.api));
const api: API = Object.assign(this.api, { navigate }, ...this.modules.map(m => m.api));
const api = initProviderApi({ provider, store, api: combo });
initProviderApi({ provider, store, api });
api.on(STORY_CHANGED, (id: string) => {
const options = api.getParameters(id, 'options');
@ -223,7 +226,8 @@ class ManagerProvider extends Component<ManagerProviderProps, State> {
...state,
location: props.location,
path: props.path,
viewMode: props.viewMode,
// if its a docsOnly page, even the 'story' view mode is considered 'docs'
viewMode: (props.docsMode && props.viewMode) === 'story' ? 'docs' : props.viewMode,
storyId: props.storyId,
};
}

View File

@ -247,7 +247,7 @@ export const transformStoriesRawToStoriesHash = (
return acc;
}
return Object.values(storiesHashOutOfOrder).reduce(addItem, base);
return Object.values(storiesHashOutOfOrder).reduce(addItem, { ...base });
};
export type Item = StoriesHash[keyof StoriesHash];

View File

@ -85,20 +85,20 @@ export function ensurePanel(panels: Panels, selectedPanel?: string, currentPanel
return currentPanel;
}
export default ({ provider, store }: Module) => {
export default ({ provider, store, fullAPI }: Module) => {
const api: SubAPI = {
getElements: type => provider.getElements(type),
getPanels: () => api.getElements(types.PANEL),
getStoryPanels: () => {
const allPanels = api.getPanels();
const { storyId, storiesHash } = store.getState();
const storyInput = storyId && (storiesHash[storyId] as StoryInput);
const { storyId } = store.getState();
const story = fullAPI.getData(storyId);
if (!allPanels || !storyInput) {
if (!allPanels || !story) {
return allPanels;
}
const { parameters } = storyInput;
const { parameters } = story;
const filteredPanels: Collection = {};
Object.entries(allPanels).forEach(([id, panel]) => {

View File

@ -61,7 +61,7 @@ describe('Addons API', () => {
describe('#getStoryPanels', () => {
it('should return all panels by default', () => {
// given
const { api } = initAddons({ provider, store });
const { api } = initAddons({ provider, store, fullAPI: { getData: () => undefined } });
// when
const filteredPanels = api.getStoryPanels();
@ -73,21 +73,27 @@ describe('Addons API', () => {
it('should filter disabled addons', () => {
// given
const storyId = 'story 1';
const storiesHash = {
[storyId]: {
parameters: {
a11y: { disabled: true },
},
},
};
const storeWithStory = {
getState: () => ({
storyId,
storiesHash: {
[storyId]: {
parameters: {
a11y: { disabled: true },
},
},
},
storiesHash,
}),
setState: jest.fn(),
};
const { api } = initAddons({ provider, store: storeWithStory });
const { api } = initAddons({
provider,
store: storeWithStory,
fullAPI: { getData: id => storiesHash[id] },
});
// when
const filteredPanels = api.getStoryPanels();

View File

@ -4,10 +4,6 @@ import { logger } from '@storybook/client-logger';
import { isJSON, parse, stringify } from 'telejson';
interface RawEvent {
data: string;
}
interface Config {
page: 'manager' | 'preview';
}
@ -106,7 +102,7 @@ export class PostmsgTransport {
return window.parent;
}
private handleEvent(rawEvent: RawEvent): void {
private handleEvent(rawEvent: MessageEvent): void {
try {
const { data } = rawEvent;
const { key, event } = typeof data === 'string' && isJSON(data) ? parse(data) : data;

View File

@ -138,7 +138,7 @@ export class Channel {
private handleEvent(event: ChannelEvent, isPeer = false) {
const listeners = this.listeners(event.type);
if (listeners && (isPeer || event.from !== this.sender)) {
listeners.forEach(fn => !(isPeer && fn.ignorePeer) && fn(...event.args));
listeners.forEach(fn => !(isPeer && fn.ignorePeer) && fn.apply(event, event.args));
}
this.data[event.type] = event.args;
}

View File

@ -1,14 +1,14 @@
import React, { FunctionComponent } from 'react';
import React, { FunctionComponent, ComponentProps } from 'react';
import { styled } from '@storybook/theming';
import icons, { IconKey } from './icons';
import Svg, { SvgProps } from './svg';
import Svg from './svg';
const Path = styled.path({
fill: 'currentColor',
});
export interface IconsProps extends SvgProps {
export interface IconsProps extends ComponentProps<typeof Svg> {
icon: IconKey;
}

View File

@ -1,6 +1,6 @@
import { styled } from '@storybook/theming';
export interface SvgProps {
interface SvgProps {
inline?: boolean;
}

View File

@ -121,12 +121,12 @@ const WithToolTipState: FunctionComponent<WithTooltipPureProps & {
try {
iframe.contentWindow.document.removeEventListener('click', hide);
} catch (e) {
logger.warn('Removing a click listener from iframe failed: ', e);
logger.debug('Removing a click listener from iframe failed: ', e);
}
});
}
} catch (e) {
logger.warn('Adding a click listener to iframe failed: ', e);
logger.debug('Adding a click listener to iframe failed: ', e);
}
};

View File

@ -38,6 +38,7 @@
"@storybook/router": "6.0.0-alpha.20",
"@storybook/theming": "6.0.0-alpha.20",
"@types/markdown-to-jsx": "^6.9.1",
"@types/rfdc": "^1.1.0",
"copy-to-clipboard": "^3.0.8",
"core-js": "^3.0.1",
"core-js-pure": "^3.0.1",
@ -50,6 +51,7 @@
"memoizerific": "^1.11.3",
"polished": "^3.4.4",
"qs": "^6.6.0",
"rfdc": "^1.1.4",
"react": "^16.8.3",
"react-dom": "^16.8.3",
"react-draggable": "^4.0.3",

View File

@ -1,6 +1,5 @@
import React, { FunctionComponent } from 'react';
import React, { FunctionComponent, useMemo } from 'react';
import { Global, createGlobal, styled } from '@storybook/theming';
import memoize from 'memoizerific';
import sizeMe from 'react-sizeme';
import { Route } from '@storybook/router';
@ -15,24 +14,6 @@ import Notifications from './containers/notifications';
import SettingsPages from './settings';
const createProps = memoize(1)(() => ({
Sidebar,
Preview,
Panel,
Notifications,
pages: [
{
key: 'settings',
render: () => <SettingsPages />,
route: (({ children }) => (
<Route path="/settings" startsWith>
{children}
</Route>
)) as FunctionComponent,
},
],
}));
const View = styled.div({
position: 'fixed',
overflow: 'hidden',
@ -53,10 +34,29 @@ export interface AppProps {
const App = React.memo<AppProps>(
({ viewMode, docsOnly, layout, panelCount, size: { width, height } }) => {
const props = createProps();
let content;
const props = useMemo(
() => ({
Sidebar,
Preview,
Panel,
Notifications,
pages: [
{
key: 'settings',
render: () => <SettingsPages />,
route: (({ children }) => (
<Route path="/settings" startsWith>
{children}
</Route>
)) as FunctionComponent,
},
],
}),
[]
);
if (!width || !height) {
content = (
<div>

View File

@ -4,7 +4,7 @@ import { DOCS_MODE } from 'global';
import { SyntheticEvent } from 'react';
import { StoriesHash, isRoot, isStory } from '@storybook/api';
const FUZZY_SEARCH_THRESHOLD = 0.4;
const FUZZY_SEARCH_THRESHOLD = 0.35;
export const prevent = (e: SyntheticEvent) => {
e.preventDefault();
@ -14,7 +14,6 @@ export const prevent = (e: SyntheticEvent) => {
const toList = memoize(1)((dataset: Dataset) => Object.values(dataset));
export type Item = StoriesHash[keyof StoriesHash];
export type Dataset = Record<string, Item>;
export type SelectedSet = Record<string, boolean>;
export type ExpandedSet = Record<string, boolean>;
@ -192,12 +191,20 @@ const fuse = memoize(5)(
})
);
const exactMatch = memoize(1)(filter => (i: Item) =>
(isStory(i) && i.kind.includes(filter)) ||
(i.name && i.name.includes(filter)) ||
(i.parameters && i.parameters.fileName && i.parameters.fileName.includes(filter)) ||
(i.parameters && typeof i.parameters.notes === 'string' && i.parameters.notes.includes(filter))
);
const exactMatch = (filter: string) => {
const reg = new RegExp(filter, 'i');
return (i: Item) =>
i.isLeaf &&
((isStory(i) && reg.test(i.kind)) ||
(i.name && reg.test(i.name)) ||
(i.parameters &&
typeof i.parameters.fileName === 'string' &&
reg.test(i.parameters.fileName.toString())) ||
(i.parameters &&
typeof i.parameters.notes === 'string' &&
reg.test(i.parameters.notes.toString())));
};
export const toId = (base: string, addition: string) =>
base === '' ? `${addition}` : `${base}-${addition}`;
@ -207,7 +214,8 @@ export const toFiltered = (dataset: Dataset, filter: string) => {
if (filter.length && filter.length > 2) {
found = fuse(dataset).search(filter);
} else {
found = toList(dataset).filter(exactMatch(filter));
const matcher = exactMatch(filter);
found = toList(dataset).filter(matcher);
}
// get all parents for all results

View File

@ -0,0 +1,218 @@
import React, { useMemo, ComponentProps } from 'react';
import { Badge, Icons } from '@storybook/components';
import { API } from '@storybook/api';
import { styled } from '@storybook/theming';
import { shortcutToHumanString } from '../libs/shortcut';
const focusableUIElements = {
storySearchField: 'storybook-explorer-searchfield',
storyListMenu: 'storybook-explorer-menu',
storyPanelRoot: 'storybook-panel-root',
};
const shortcutToHumanStringIfEnabled = (shortcuts: string[], enableShortcuts: boolean) =>
enableShortcuts ? shortcutToHumanString(shortcuts) : null;
const sharedStyles = {
height: 10,
width: 10,
marginLeft: -5,
marginRight: -5,
display: 'block',
};
const Icon = styled(Icons)(sharedStyles, ({ theme }) => ({
color: theme.color.secondary,
}));
const Img = styled.img(sharedStyles);
const Placeholder = styled.div({ sharedStyles });
export interface ListItemIconProps {
icon?: ComponentProps<typeof Icons>['icon'];
imgSrc?: string;
}
export const MenuItemIcon = ({ icon, imgSrc }: ListItemIconProps) => {
if (icon) {
return <Icon icon={icon} />;
}
if (imgSrc) {
return <Img src={imgSrc} alt="image" />;
}
return <Placeholder />;
};
export const useMenu = (
api: API,
isFullscreen: boolean,
showPanel: boolean,
showNav: boolean,
enableShortcuts: boolean
) => {
const shortcutKeys = api.getShortcutKeys();
const sidebarToggle = useMemo(
() => ({
id: 'S',
title: 'Show sidebar',
onClick: () => api.toggleNav(),
right: shortcutToHumanStringIfEnabled(shortcutKeys.toggleNav, enableShortcuts),
left: showNav ? <MenuItemIcon icon="check" /> : <MenuItemIcon />,
}),
[api, shortcutToHumanStringIfEnabled, enableShortcuts, shortcutKeys, showNav]
);
const addonsToggle = useMemo(
() => ({
id: 'A',
title: 'Show addons',
onClick: () => api.togglePanel(),
right: shortcutToHumanStringIfEnabled(shortcutKeys.togglePanel, enableShortcuts),
left: showPanel ? <MenuItemIcon icon="check" /> : <MenuItemIcon />,
}),
[api, shortcutToHumanStringIfEnabled, enableShortcuts, shortcutKeys, showPanel]
);
const addonsOrientationToggle = useMemo(
() => ({
id: 'D',
title: 'Change addons orientation',
onClick: () => api.togglePanelPosition(),
right: shortcutToHumanStringIfEnabled(shortcutKeys.panelPosition, enableShortcuts),
left: <MenuItemIcon />,
}),
[api, shortcutToHumanStringIfEnabled, enableShortcuts, shortcutKeys]
);
const fullscreenToggle = useMemo(
() => ({
id: 'F',
title: 'Go full screen',
onClick: () => api.toggleFullscreen(),
right: shortcutToHumanStringIfEnabled(shortcutKeys.fullScreen, enableShortcuts),
left: isFullscreen ? 'check' : <MenuItemIcon />,
}),
[api, shortcutToHumanStringIfEnabled, enableShortcuts, shortcutKeys, isFullscreen]
);
const searchToggle = useMemo(
() => ({
id: '/',
title: 'Search',
onClick: () => api.focusOnUIElement(focusableUIElements.storySearchField),
right: shortcutToHumanStringIfEnabled(shortcutKeys.search, enableShortcuts),
left: <MenuItemIcon />,
}),
[api, shortcutToHumanStringIfEnabled, enableShortcuts, shortcutKeys]
);
const up = useMemo(
() => ({
id: 'up',
title: 'Previous component',
onClick: () => api.jumpToComponent(-1),
right: shortcutToHumanStringIfEnabled(shortcutKeys.prevComponent, enableShortcuts),
left: <MenuItemIcon />,
}),
[api, shortcutToHumanStringIfEnabled, enableShortcuts, shortcutKeys]
);
const down = useMemo(
() => ({
id: 'down',
title: 'Next component',
onClick: () => api.jumpToComponent(1),
right: shortcutToHumanStringIfEnabled(shortcutKeys.nextComponent, enableShortcuts),
left: <MenuItemIcon />,
}),
[api, shortcutToHumanStringIfEnabled, enableShortcuts, shortcutKeys]
);
const prev = useMemo(
() => ({
id: 'prev',
title: 'Previous story',
onClick: () => api.jumpToStory(-1),
right: shortcutToHumanStringIfEnabled(shortcutKeys.prevStory, enableShortcuts),
left: <MenuItemIcon />,
}),
[api, shortcutToHumanStringIfEnabled, enableShortcuts, shortcutKeys]
);
const next = useMemo(
() => ({
id: 'next',
title: 'Next story',
onClick: () => api.jumpToStory(1),
right: shortcutToHumanStringIfEnabled(shortcutKeys.nextStory, enableShortcuts),
left: <MenuItemIcon />,
}),
[api, shortcutToHumanStringIfEnabled, enableShortcuts, shortcutKeys]
);
const about = useMemo(
() => ({
id: 'about',
title: 'About your Storybook',
onClick: () => api.navigate('/settings/about'),
right: api.versionUpdateAvailable() && <Badge status="positive">Update</Badge>,
left: <MenuItemIcon />,
}),
[api, shortcutToHumanStringIfEnabled, enableShortcuts, shortcutKeys]
);
const shortcuts = useMemo(
() => ({
id: 'shortcuts',
title: 'Keyboard shortcuts',
onClick: () => api.navigate('/settings/shortcuts'),
right: shortcutToHumanStringIfEnabled(shortcutKeys.shortcutsPage, enableShortcuts),
left: <MenuItemIcon />,
}),
[api, shortcutToHumanStringIfEnabled, enableShortcuts, shortcutKeys]
);
const collapse = useMemo(
() => ({
id: 'collapse',
title: 'Collapse all',
onClick: () => api.collapseAll(),
right: shortcutToHumanString(shortcutKeys.collapseAll),
left: <MenuItemIcon />,
}),
[api, shortcutToHumanStringIfEnabled, enableShortcuts, shortcutKeys]
);
return useMemo(
() => [
sidebarToggle,
addonsToggle,
addonsOrientationToggle,
fullscreenToggle,
searchToggle,
up,
down,
prev,
next,
about,
shortcuts,
collapse,
],
[
sidebarToggle,
addonsToggle,
addonsOrientationToggle,
fullscreenToggle,
searchToggle,
up,
down,
prev,
next,
about,
shortcuts,
collapse,
]
);
};

View File

@ -1,11 +1,13 @@
import { PREVIEW_URL } from 'global';
import React from 'react';
import { State, Consumer, Combo, StoriesHash } from '@storybook/api';
import { Consumer, Combo, StoriesHash, isRoot, isGroup, isStory } from '@storybook/api';
import { Preview } from '../components/preview/preview';
import { PreviewProps } from '../components/preview/PreviewProps';
type Item = StoriesHash[keyof StoriesHash];
const nonAlphanumSpace = /[^a-z0-9 ]/gi;
const doubleSpace = /\s\s/gi;
const replacer = (match: string) => ` ${match} `;
@ -13,40 +15,46 @@ const replacer = (match: string) => ` ${match} `;
const addExtraWhiteSpace = (input: string) =>
input.replace(nonAlphanumSpace, replacer).replace(doubleSpace, ' ');
const getDescription = (storiesHash: StoriesHash, storyId: string) => {
const storyInfo = storiesHash[storyId];
if (storyInfo) {
// @ts-ignore
const { kind, name } = storyInfo;
return kind && name ? addExtraWhiteSpace(`${kind} - ${name}`) : '';
const getDescription = (item: Item) => {
if (isRoot(item)) {
return item.name ? `${item.name} ⋅ Storybook` : 'Storybook';
}
if (isGroup(item)) {
return item.name ? `${item.name} ⋅ Storybook` : 'Storybook';
}
if (isStory(item)) {
const { kind, name } = item;
return kind && name ? addExtraWhiteSpace(`${kind} - ${name} ⋅ Storybook`) : 'Storybook';
}
return '';
return 'Storybook';
};
const mapper = ({ api, state }: Combo) => {
const { layout, location, customQueryParams, storiesHash, storyId } = state;
const { parameters } = storiesHash[storyId] || {};
const { layout, location, customQueryParams, storyId } = state;
const story = api.getData(storyId);
const parameters = story ? story.parameters : {};
const docsOnly = story && story.parameters ? !!story.parameters.docsOnly : false;
return {
api,
options: layout,
description: getDescription(storiesHash, storyId),
description: getDescription(story),
...api.getUrlState(),
queryParams: customQueryParams,
docsOnly: (parameters && parameters.docsOnly) as boolean,
docsOnly,
location,
parameters,
};
};
function getBaseUrl(): string {
const getBaseUrl = (): string => {
try {
return PREVIEW_URL || 'iframe.html';
} catch (e) {
return 'iframe.html';
}
}
};
const PreviewConnected = React.memo<{ id: string; withLoader: boolean }>(props => (
<Consumer filter={mapper}>

View File

@ -1,115 +1,14 @@
import { DOCS_MODE } from 'global';
import React, { FunctionComponent } from 'react';
import memoize from 'memoizerific';
import React, { FunctionComponent, useMemo } from 'react';
import rfdc from 'rfdc';
import { Badge } from '@storybook/components';
import { Consumer, Combo, StoriesHash, Story } from '@storybook/api';
import { shortcutToHumanString } from '../libs/shortcut';
import ListItemIcon from '../components/sidebar/ListItemIcon';
import SidebarComponent from '../components/sidebar/Sidebar';
import { useMenu } from './menu';
type Item = StoriesHash[keyof StoriesHash];
const focusableUIElements = {
storySearchField: 'storybook-explorer-searchfield',
storyListMenu: 'storybook-explorer-menu',
storyPanelRoot: 'storybook-panel-root',
};
const shortcutToHumanStringIfEnabled = (shortcuts: string[], enableShortcuts: boolean) =>
enableShortcuts ? shortcutToHumanString(shortcuts) : null;
const createMenu = memoize(1)(
(api, shortcutKeys, isFullscreen, showPanel, showNav, enableShortcuts) => [
{
id: 'S',
title: 'Show sidebar',
onClick: () => api.toggleNav(),
right: shortcutToHumanStringIfEnabled(shortcutKeys.toggleNav, enableShortcuts),
left: showNav ? <ListItemIcon icon="check" /> : <ListItemIcon />,
},
{
id: 'A',
title: 'Show addons',
onClick: () => api.togglePanel(),
right: shortcutToHumanStringIfEnabled(shortcutKeys.togglePanel, enableShortcuts),
left: showPanel ? <ListItemIcon icon="check" /> : <ListItemIcon />,
},
{
id: 'D',
title: 'Change addons orientation',
onClick: () => api.togglePanelPosition(),
right: shortcutToHumanStringIfEnabled(shortcutKeys.panelPosition, enableShortcuts),
left: <ListItemIcon />,
},
{
id: 'F',
title: 'Go full screen',
onClick: api.toggleFullscreen,
right: shortcutToHumanStringIfEnabled(shortcutKeys.fullScreen, enableShortcuts),
left: isFullscreen ? 'check' : <ListItemIcon />,
},
{
id: '/',
title: 'Search',
onClick: () => api.focusOnUIElement(focusableUIElements.storySearchField),
right: shortcutToHumanStringIfEnabled(shortcutKeys.search, enableShortcuts),
left: <ListItemIcon />,
},
{
id: 'up',
title: 'Previous component',
onClick: () => api.jumpToComponent(-1),
right: shortcutToHumanStringIfEnabled(shortcutKeys.prevComponent, enableShortcuts),
left: <ListItemIcon />,
},
{
id: 'down',
title: 'Next component',
onClick: () => api.jumpToComponent(1),
right: shortcutToHumanStringIfEnabled(shortcutKeys.nextComponent, enableShortcuts),
left: <ListItemIcon />,
},
{
id: 'prev',
title: 'Previous story',
onClick: () => api.jumpToStory(-1),
right: shortcutToHumanStringIfEnabled(shortcutKeys.prevStory, enableShortcuts),
left: <ListItemIcon />,
},
{
id: 'next',
title: 'Next story',
onClick: () => api.jumpToStory(1),
right: shortcutToHumanStringIfEnabled(shortcutKeys.nextStory, enableShortcuts),
left: <ListItemIcon />,
},
{
id: 'about',
title: 'About your Storybook',
onClick: () => api.navigate('/settings/about'),
right: api.versionUpdateAvailable() && <Badge status="positive">Update</Badge>,
left: <ListItemIcon />,
},
{
id: 'shortcuts',
title: 'Keyboard shortcuts',
onClick: () => api.navigate('/settings/shortcuts'),
right: shortcutToHumanStringIfEnabled(shortcutKeys.shortcutsPage, enableShortcuts),
left: <ListItemIcon />,
},
{
id: 'collapse',
title: 'Collapse all',
onClick: () => api.collapseAll(),
right: shortcutToHumanString(shortcutKeys.collapseAll),
left: <ListItemIcon />,
},
]
);
export const collapseAllStories = (stories: StoriesHash) => {
// keep track of component IDs that have been rewritten to the ID of their first leaf child
const componentIdToLeafId: Record<string, string> = {};
@ -221,34 +120,48 @@ export const collapseDocsOnlyStories = (storiesHash: StoriesHash) => {
return result;
};
export const mapper = ({ state, api }: Combo) => {
const {
ui: { name, url, enableShortcuts },
viewMode,
storyId,
layout: { isFullscreen, showPanel, showNav },
storiesHash,
storiesConfigured,
} = state;
const stories = DOCS_MODE
? collapseAllStories(storiesHash)
: collapseDocsOnlyStories(storiesHash);
const clone = rfdc({ circles: true });
const shortcutKeys = api.getShortcutKeys();
return {
loading: !storiesConfigured,
title: name,
url,
stories,
storyId,
viewMode,
menu: createMenu(api, shortcutKeys, isFullscreen, showPanel, showNav, enableShortcuts),
menuHighlighted: api.versionUpdateAvailable(),
const Sidebar: FunctionComponent<{}> = React.memo(() => {
const mapper = ({ state, api }: Combo) => {
const {
storiesConfigured,
ui: { name, url, enableShortcuts },
viewMode,
storyId,
layout: { isFullscreen, showPanel, showNav },
storiesHash,
refs,
} = state;
const stories = useMemo(() => {
// protect against mutation
const copy = clone(storiesHash);
return DOCS_MODE ? collapseAllStories(copy) : collapseDocsOnlyStories(copy);
}, [DOCS_MODE, storiesHash]);
const menu = useMenu(api, isFullscreen, showPanel, showNav, enableShortcuts);
return {
loading: !storiesConfigured,
title: name,
url,
stories,
refs,
storyId,
viewMode,
menu,
menuHighlighted: api.versionUpdateAvailable(),
};
};
};
const Sidebar: FunctionComponent<any> = props => (
<Consumer filter={mapper}>{fromState => <SidebarComponent {...props} {...fromState} />}</Consumer>
);
return (
<Consumer filter={mapper}>
{fromState => {
return <SidebarComponent {...fromState} />;
}}
</Consumer>
);
});
export default Sidebar;

View File

@ -43,7 +43,7 @@ export const Root: FunctionComponent<RootProps> = ({ provider }) => (
>
{({ state, api }: Combo) => {
const panelCount = Object.keys(api.getPanels()).length;
const story = state.storiesHash[state.storyId];
const story = api.getData(state.storyId);
return (
<ThemeProvider key="theme.provider" theme={ensureTheme(state.theme)}>

View File

@ -4362,6 +4362,11 @@
"@types/prop-types" "*"
csstype "^2.2.0"
"@types/rfdc@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@types/rfdc/-/rfdc-1.1.0.tgz#1fc5ffdc679575e2ca31399d4ee75f353afdd37b"
integrity sha512-Ez0Pc0H6m8C2L3Wif9SR5YlJTB/UnZIq0N9G/dPB2fmGo42oLo95o73hHHtoGvUucMD4OdlquscflSuKCZE8qA==
"@types/rimraf@^2.0.2":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@types/rimraf/-/rimraf-2.0.3.tgz#0199a46af106729ba14213fda7b981278d8c84f2"
@ -26249,6 +26254,11 @@ rfc6902@^3.0.1:
resolved "https://registry.yarnpkg.com/rfc6902/-/rfc6902-3.0.4.tgz#82965f13536fd20cb7799ce0376e9ce7cd3ebfe6"
integrity sha512-OnzreaZXrwT5w2ikKXWr5QcuI7NZpL+J3hIkAwozjOnKVUL7fPsB8Vcmu8YBiiou1/r3V0Jc0T1uQDyfAPvLzA==
rfdc@^1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.1.4.tgz#ba72cc1367a0ccd9cf81a870b3b58bd3ad07f8c2"
integrity sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug==
rgb-regex@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/rgb-regex/-/rgb-regex-1.0.1.tgz#c0e0d6882df0e23be254a475e8edd41915feaeb1"