mirror of
https://github.com/storybookjs/storybook.git
synced 2025-04-04 16:51:09 +08:00
FIX a problem in router && FIX a problem in api/version && MIGRATE lib/ui further
This commit is contained in:
parent
7f2b3e0cc4
commit
9c176c618d
@ -8,7 +8,7 @@ import { Module, API } from '../index';
|
||||
|
||||
export interface Version {
|
||||
version: string;
|
||||
info?: string;
|
||||
info?: { plain: string };
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
|
@ -33,12 +33,12 @@ interface QueryMatchProps {
|
||||
}
|
||||
interface RouteProps {
|
||||
path: string;
|
||||
startsWith: boolean;
|
||||
hideOnly: boolean;
|
||||
children: (renderData: RenderData) => ReactNode;
|
||||
startsWith?: boolean;
|
||||
hideOnly?: boolean;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
interface QueryLinkProps {
|
||||
export interface QueryLinkProps {
|
||||
to: string;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
@ -36,6 +36,7 @@
|
||||
"@storybook/core-events": "6.0.0-alpha.6",
|
||||
"@storybook/router": "6.0.0-alpha.6",
|
||||
"@storybook/theming": "6.0.0-alpha.6",
|
||||
"@types/markdown-to-jsx":"^6.9.1",
|
||||
"copy-to-clipboard": "^3.0.8",
|
||||
"core-js": "^3.0.1",
|
||||
"core-js-pure": "^3.0.1",
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { Fragment, Component, FunctionComponent } from 'react';
|
||||
import React, { Fragment, Component, FunctionComponent, SyntheticEvent } from 'react';
|
||||
|
||||
import { Icons, IconButton } from '@storybook/components';
|
||||
|
||||
@ -22,15 +22,28 @@ class Provider extends Component<{}, { value: number }> {
|
||||
|
||||
const { Consumer } = Context;
|
||||
|
||||
const cancel = (e: SyntheticEvent) => {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
};
|
||||
|
||||
const Zoom: FunctionComponent<{ set: Function; reset: Function }> = ({ set, reset }) => (
|
||||
<Fragment>
|
||||
<IconButton key="zoomin" onClick={e => e.preventDefault() || set(0.8)} title="Zoom in">
|
||||
<IconButton key="zoomin" onClick={(e: SyntheticEvent) => cancel(e) || set(0.8)} title="Zoom in">
|
||||
<Icons icon="zoom" />
|
||||
</IconButton>
|
||||
<IconButton key="zoomout" onClick={e => e.preventDefault() || set(1.25)} title="Zoom out">
|
||||
<IconButton
|
||||
key="zoomout"
|
||||
onClick={(e: SyntheticEvent) => cancel(e) || set(1.25)}
|
||||
title="Zoom out"
|
||||
>
|
||||
<Icons icon="zoomout" />
|
||||
</IconButton>
|
||||
<IconButton key="zoomreset" onClick={e => e.preventDefault() || reset()} title="Reset zoom">
|
||||
<IconButton
|
||||
key="zoomreset"
|
||||
onClick={(e: SyntheticEvent) => cancel(e) || reset()}
|
||||
title="Reset zoom"
|
||||
>
|
||||
<Icons icon="zoomreset" />
|
||||
</IconButton>
|
||||
</Fragment>
|
||||
|
@ -1,4 +1,6 @@
|
||||
import React from 'react';
|
||||
import { DecoratorFn } from '@storybook/react';
|
||||
|
||||
import { Spaced } from '@storybook/components';
|
||||
|
||||
import SidebarStories from './SidebarStories';
|
||||
@ -7,7 +9,7 @@ import { mockDataset } from './treeview/treeview.mockdata';
|
||||
export default {
|
||||
component: SidebarStories,
|
||||
title: 'UI/Sidebar/SidebarStories',
|
||||
decorators: [s => <Spaced>{s()}</Spaced>],
|
||||
decorators: [s => <Spaced>{s()}</Spaced>] as DecoratorFn[],
|
||||
excludeStories: /.*Data$/,
|
||||
};
|
||||
|
||||
|
@ -73,7 +73,18 @@ export const viewMode = (
|
||||
const targetId = (childIds?: string[]) =>
|
||||
childIds && childIds.find((childId: string) => /.*--.*/.exec(childId));
|
||||
|
||||
export const Link = ({
|
||||
export const Link: FunctionComponent<{
|
||||
id: string;
|
||||
name: string;
|
||||
isLeaf: boolean;
|
||||
prefix: string;
|
||||
onKeyUp: Function;
|
||||
onClick: Function;
|
||||
childIds: string[] | null;
|
||||
isExpanded: boolean;
|
||||
isComponent: boolean;
|
||||
parameters: Record<string, any>;
|
||||
}> = ({
|
||||
id,
|
||||
prefix,
|
||||
name,
|
||||
@ -82,8 +93,8 @@ export const Link = ({
|
||||
isComponent,
|
||||
onClick,
|
||||
onKeyUp,
|
||||
childIds,
|
||||
isExpanded,
|
||||
childIds = null,
|
||||
isExpanded = false,
|
||||
parameters,
|
||||
}) => {
|
||||
return isLeaf || (isComponent && !isExpanded) ? (
|
||||
@ -109,21 +120,6 @@ export const Link = ({
|
||||
);
|
||||
};
|
||||
Link.displayName = 'Link';
|
||||
Link.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
id: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
isLeaf: PropTypes.bool.isRequired,
|
||||
prefix: PropTypes.string.isRequired,
|
||||
onKeyUp: PropTypes.func.isRequired,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
childIds: PropTypes.arrayOf(PropTypes.string),
|
||||
isExpanded: PropTypes.bool,
|
||||
};
|
||||
Link.defaultProps = {
|
||||
childIds: null,
|
||||
isExpanded: false,
|
||||
};
|
||||
|
||||
export interface StoriesProps {
|
||||
loading: boolean;
|
||||
|
@ -1,5 +1,4 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { FunctionComponent, ReactNode, SyntheticEvent } from 'react';
|
||||
import { styled } from '@storybook/theming';
|
||||
|
||||
export const DefaultSection = styled.div({});
|
||||
@ -17,9 +16,14 @@ export const DefaultFilter = styled(props => <input placeholder="search..." {...
|
||||
border: '1px solid black',
|
||||
});
|
||||
|
||||
export const prevent = (e: SyntheticEvent) => {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
};
|
||||
|
||||
export const DefaultMessage = styled.div({});
|
||||
|
||||
export const LeafStyle = styled.div(
|
||||
export const LeafStyle = styled.div<{ depth: number; isSelected: boolean }>(
|
||||
{
|
||||
minHeight: 24,
|
||||
display: 'flex',
|
||||
@ -34,13 +38,23 @@ export const LeafStyle = styled.div(
|
||||
})
|
||||
);
|
||||
|
||||
export const DefaultLeaf = ({ name, ...rest }) => <LeafStyle {...rest}>{name}</LeafStyle>;
|
||||
export const DefaultLeaf: FunctionComponent<{ name: ReactNode; depth: number } & Record<
|
||||
string,
|
||||
any
|
||||
>> = ({ name, isSelected, depth, ...rest }) => (
|
||||
<LeafStyle isSelected={isSelected} depth={depth} {...rest}>
|
||||
{name}
|
||||
</LeafStyle>
|
||||
);
|
||||
DefaultLeaf.displayName = 'DefaultLeaf';
|
||||
DefaultLeaf.propTypes = {
|
||||
name: PropTypes.node.isRequired,
|
||||
depth: PropTypes.number.isRequired,
|
||||
};
|
||||
export const DefaultHead = ({ name, depth, isExpanded = true, isSelected, isComponent }) => (
|
||||
|
||||
export const DefaultHead: FunctionComponent<{
|
||||
name: ReactNode;
|
||||
depth: number;
|
||||
isExpanded?: boolean;
|
||||
isSelected?: boolean;
|
||||
isComponent?: boolean;
|
||||
}> = ({ name, depth, isExpanded = false, isSelected = false, isComponent = false }) => (
|
||||
<LeafStyle isSelected={isSelected} depth={depth}>
|
||||
<span>
|
||||
{isExpanded ? '-' : '+'}
|
||||
@ -50,29 +64,17 @@ export const DefaultHead = ({ name, depth, isExpanded = true, isSelected, isComp
|
||||
</LeafStyle>
|
||||
);
|
||||
DefaultHead.displayName = 'DefaultHead';
|
||||
DefaultHead.propTypes = {
|
||||
name: PropTypes.node.isRequired,
|
||||
depth: PropTypes.number.isRequired,
|
||||
isExpanded: PropTypes.bool,
|
||||
isSelected: PropTypes.bool,
|
||||
isComponent: PropTypes.bool,
|
||||
};
|
||||
DefaultHead.defaultProps = {
|
||||
isExpanded: false,
|
||||
isComponent: false,
|
||||
isSelected: false,
|
||||
};
|
||||
|
||||
export const DefaultRootTitle = styled.h4({});
|
||||
|
||||
export const DefaultLink = ({ id, prefix, children, ...rest }) => (
|
||||
<A href={`#!${prefix}${id}`} {...rest} onClick={e => e.preventDefault() || rest.onClick(e)}>
|
||||
export const DefaultLink: FunctionComponent<{
|
||||
id: string;
|
||||
prefix: string;
|
||||
children: string[];
|
||||
onClick: Function;
|
||||
}> = ({ id, prefix, children, ...rest }) => (
|
||||
<A href={`#!${prefix}${id}`} {...rest} onClick={e => prevent(e) || rest.onClick(e)}>
|
||||
{children}
|
||||
</A>
|
||||
);
|
||||
DefaultLink.displayName = 'DefaultLink';
|
||||
DefaultLink.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
prefix: PropTypes.string.isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
};
|
@ -1,6 +1,11 @@
|
||||
import { document } from 'global';
|
||||
import React, { PureComponent, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, {
|
||||
PureComponent,
|
||||
Fragment,
|
||||
ComponentType,
|
||||
FunctionComponent,
|
||||
SyntheticEvent,
|
||||
} from 'react';
|
||||
import memoize from 'memoizerific';
|
||||
import addons from '@storybook/addons';
|
||||
import { STORIES_COLLAPSE_ALL, STORIES_EXPAND_ALL } from '@storybook/core-events';
|
||||
@ -8,7 +13,6 @@ import debounce from 'lodash/debounce';
|
||||
|
||||
import {
|
||||
createId,
|
||||
prevent,
|
||||
keyEventToAction,
|
||||
getParent,
|
||||
getParents,
|
||||
@ -16,6 +20,10 @@ import {
|
||||
getMains,
|
||||
getNext,
|
||||
toFiltered,
|
||||
Dataset,
|
||||
ExpandedSet,
|
||||
SelectedSet,
|
||||
Item,
|
||||
} from './utils';
|
||||
|
||||
import {
|
||||
@ -29,13 +37,22 @@ import {
|
||||
DefaultMessage,
|
||||
} from './components';
|
||||
|
||||
const createHandler = memoize(10000)((item, cb) => (...args) => cb(...args, item));
|
||||
const createHandler = memoize(10000)((item, cb) => (...args: any[]) => cb(...args, item));
|
||||
|
||||
const linked = (C, { onClick, onKeyUp, prefix = '', Link: L }) => {
|
||||
const linked = (
|
||||
C: ComponentType,
|
||||
{
|
||||
onClick,
|
||||
onKeyUp,
|
||||
prefix = '',
|
||||
Link: L,
|
||||
}: { onClick: Function; onKeyUp: Function; Link: ComponentType; prefix: string }
|
||||
) => {
|
||||
const Linked = React.memo(p => (
|
||||
<L
|
||||
prefix={prefix}
|
||||
{...p}
|
||||
// @ts-ignore
|
||||
onKeyUp={createHandler(p, onKeyUp)}
|
||||
onClick={createHandler(p, onClick)}
|
||||
>
|
||||
@ -47,35 +64,62 @@ const linked = (C, { onClick, onKeyUp, prefix = '', Link: L }) => {
|
||||
return Linked;
|
||||
};
|
||||
|
||||
const getLink = memoize(1)(Link => Link || DefaultLink);
|
||||
const getLink = memoize(1)((Link: ComponentType<any>) => Link || DefaultLink);
|
||||
|
||||
const getHead = memoize(1)((Head, Link, prefix, events) =>
|
||||
linked(Head || DefaultHead, {
|
||||
onClick: events.onClick,
|
||||
onKeyUp: events.onKeyUp,
|
||||
prefix,
|
||||
Link: getLink(Link),
|
||||
})
|
||||
const getHead = memoize(1)(
|
||||
(
|
||||
Head: ComponentType<any>,
|
||||
Link: ComponentType<any>,
|
||||
prefix: string,
|
||||
events: Record<string, any>
|
||||
) =>
|
||||
linked(Head || DefaultHead, {
|
||||
onClick: events.onClick,
|
||||
onKeyUp: events.onKeyUp,
|
||||
prefix,
|
||||
Link: getLink(Link),
|
||||
})
|
||||
);
|
||||
|
||||
const getLeaf = memoize(1)((Leaf, Link, prefix, events) =>
|
||||
linked(Leaf || DefaultLeaf, {
|
||||
onClick: events.onClick,
|
||||
onKeyUp: events.onKeyUp,
|
||||
prefix,
|
||||
Link: getLink(Link),
|
||||
})
|
||||
const getLeaf = memoize(1)(
|
||||
(
|
||||
Leaf: ComponentType<any>,
|
||||
Link: ComponentType<any>,
|
||||
prefix: string,
|
||||
events: Record<string, any>
|
||||
) =>
|
||||
linked(Leaf || DefaultLeaf, {
|
||||
onClick: events.onClick,
|
||||
onKeyUp: events.onKeyUp,
|
||||
prefix,
|
||||
Link: getLink(Link),
|
||||
})
|
||||
);
|
||||
|
||||
const getFilter = memoize(1)(Filter => Filter || DefaultFilter);
|
||||
const getTitle = memoize(1)(Title => Title || DefaultRootTitle);
|
||||
const getContainer = memoize(1)(Section => Section || DefaultSection);
|
||||
const getMessage = memoize(1)(Message => Message || DefaultMessage);
|
||||
const getFilter = memoize(1)((Filter: ComponentType<any>) => Filter || DefaultFilter);
|
||||
const getTitle = memoize(1)((Title: ComponentType<any>) => Title || DefaultRootTitle);
|
||||
const getContainer = memoize(1)((Section: ComponentType<any>) => Section || DefaultSection);
|
||||
const getMessage = memoize(1)((Message: ComponentType<any>) => Message || DefaultMessage);
|
||||
|
||||
const branchOrLeaf = (
|
||||
// eslint-disable-next-line react/prop-types
|
||||
{ Branch, Leaf, Head, List },
|
||||
{ root, dataset, expanded, selected, depth }
|
||||
{
|
||||
Branch,
|
||||
Leaf,
|
||||
Head,
|
||||
List,
|
||||
}: {
|
||||
Branch: ComponentType<any>;
|
||||
Leaf: ComponentType<any>;
|
||||
Head: ComponentType<any>;
|
||||
List: ComponentType<any>;
|
||||
},
|
||||
{
|
||||
root,
|
||||
dataset,
|
||||
expanded,
|
||||
selected,
|
||||
depth,
|
||||
}: { root: string; dataset: Dataset; expanded: ExpandedSet; selected: SelectedSet; depth: number }
|
||||
) => {
|
||||
const node = dataset[root];
|
||||
return node.children ? (
|
||||
@ -98,7 +142,19 @@ const branchOrLeaf = (
|
||||
);
|
||||
};
|
||||
|
||||
const Tree = props => {
|
||||
const Tree: FunctionComponent<{
|
||||
root: string;
|
||||
depth: number;
|
||||
dataset: Dataset;
|
||||
expanded: ExpandedSet;
|
||||
selected: SelectedSet;
|
||||
events?: any;
|
||||
Branch: ComponentType<any>;
|
||||
Link?: ComponentType<any>;
|
||||
List?: ComponentType<any>;
|
||||
Leaf?: ComponentType<any>;
|
||||
Head?: ComponentType<any>;
|
||||
}> = props => {
|
||||
const {
|
||||
root,
|
||||
depth,
|
||||
@ -110,9 +166,16 @@ const Tree = props => {
|
||||
Leaf = DefaultLeaf,
|
||||
Head = DefaultHead,
|
||||
} = props;
|
||||
const { children, ...node } = dataset[root] || {};
|
||||
|
||||
const mapNode = i =>
|
||||
const item = dataset[root];
|
||||
|
||||
if (!item) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { children, ...node } = item;
|
||||
|
||||
const mapNode = (i: string) =>
|
||||
branchOrLeaf(
|
||||
{ Branch, Leaf, Head, List },
|
||||
{ dataset, selected, expanded, root: i, depth: depth + 1 }
|
||||
@ -146,7 +209,13 @@ const Tree = props => {
|
||||
};
|
||||
|
||||
const calculateTreeState = memoize(50)(
|
||||
({ dataset, selectedId }, { lastSelectedId, unfilteredExpanded }) => {
|
||||
(
|
||||
{ dataset, selectedId }: { dataset: Dataset; selectedId: string },
|
||||
{
|
||||
lastSelectedId,
|
||||
unfilteredExpanded,
|
||||
}: { lastSelectedId: string; unfilteredExpanded: ExpandedSet }
|
||||
) => {
|
||||
if (selectedId === lastSelectedId) {
|
||||
return null;
|
||||
}
|
||||
@ -156,7 +225,7 @@ const calculateTreeState = memoize(50)(
|
||||
const newExpanded = Object.keys(dataset).reduce((acc, key) => {
|
||||
acc[key] = selectedAncestorIds.includes(key) || unfilteredExpanded[key];
|
||||
return acc;
|
||||
}, {});
|
||||
}, {} as SelectedSet);
|
||||
|
||||
return {
|
||||
lastSelectedId: selectedId,
|
||||
@ -165,57 +234,116 @@ const calculateTreeState = memoize(50)(
|
||||
}
|
||||
);
|
||||
|
||||
const getExpanded = ({ unfilteredExpanded, filteredExpanded, filter }) =>
|
||||
filter ? filteredExpanded : unfilteredExpanded;
|
||||
const getExpanded = ({
|
||||
unfilteredExpanded,
|
||||
filteredExpanded,
|
||||
filter,
|
||||
}: {
|
||||
unfilteredExpanded: ExpandedSet;
|
||||
filteredExpanded: ExpandedSet;
|
||||
filter: string;
|
||||
}) => (filter ? filteredExpanded : unfilteredExpanded);
|
||||
|
||||
const getFilteredDataset = memoize(50)(({ dataset, filter }) =>
|
||||
const getFilteredDataset = memoize(
|
||||
50
|
||||
)(({ dataset, filter }: { dataset: Dataset; filter: string }) =>
|
||||
filter ? toFiltered(dataset, filter) : dataset
|
||||
);
|
||||
|
||||
// Update the set of expansions we are currently working with
|
||||
const updateExpanded = fn => ({ unfilteredExpanded, filteredExpanded, filter }) => {
|
||||
const updateExpanded = (fn: (expandedset: ExpandedSet) => ExpandedSet) => ({
|
||||
unfilteredExpanded,
|
||||
filteredExpanded,
|
||||
filter,
|
||||
...rest
|
||||
}: TreeStateState): TreeStateState => {
|
||||
if (filter) {
|
||||
return {
|
||||
...rest,
|
||||
unfilteredExpanded,
|
||||
filter,
|
||||
filteredExpanded: fn(filteredExpanded),
|
||||
};
|
||||
}
|
||||
return { unfilteredExpanded: fn(unfilteredExpanded) };
|
||||
return {
|
||||
...rest,
|
||||
filteredExpanded,
|
||||
filter,
|
||||
unfilteredExpanded: fn(unfilteredExpanded),
|
||||
};
|
||||
};
|
||||
|
||||
const getPropsForTree = memoize(50)(({ dataset, selectedId }) => {
|
||||
const selected = Object.keys(dataset).reduce(
|
||||
(acc, k) => Object.assign(acc, { [k]: k === selectedId }),
|
||||
{}
|
||||
);
|
||||
const getPropsForTree = memoize(50)(
|
||||
({ dataset, selectedId }: { dataset: Dataset; selectedId: string }) => {
|
||||
const selected = Object.keys(dataset).reduce(
|
||||
(acc, k) => Object.assign(acc, { [k]: k === selectedId }),
|
||||
{} as SelectedSet
|
||||
);
|
||||
|
||||
const { roots, others } = getMains(dataset).reduce(
|
||||
(acc, item) => {
|
||||
const { isRoot } = item;
|
||||
return isRoot
|
||||
? { ...acc, roots: [...acc.roots, item] }
|
||||
: { ...acc, others: [...acc.others, item] };
|
||||
},
|
||||
{ roots: [], others: [] }
|
||||
);
|
||||
const { roots, others } = getMains(dataset).reduce(
|
||||
(acc, item) => {
|
||||
const { isRoot } = item;
|
||||
return isRoot
|
||||
? { ...acc, roots: [...acc.roots, item] }
|
||||
: { ...acc, others: [...acc.others, item] };
|
||||
},
|
||||
{ roots: [] as Item[], others: [] as Item[] }
|
||||
);
|
||||
|
||||
return { selected, roots, others };
|
||||
});
|
||||
return { selected, roots, others };
|
||||
}
|
||||
);
|
||||
|
||||
class TreeState extends PureComponent {
|
||||
interface TreeStateState {
|
||||
unfilteredExpanded: SelectedSet;
|
||||
filteredExpanded: ExpandedSet;
|
||||
filter: string | null;
|
||||
lastSelectedId: string | null;
|
||||
}
|
||||
|
||||
interface TreeStateProps {
|
||||
dataset: Dataset;
|
||||
selectedId: string;
|
||||
prefix: string;
|
||||
filter?: string;
|
||||
event?: any;
|
||||
Filter: ComponentType<any>;
|
||||
List: ComponentType<any>;
|
||||
Title: ComponentType<any>;
|
||||
Link: ComponentType<any>;
|
||||
Leaf: ComponentType<any>;
|
||||
Head: ComponentType<any>;
|
||||
Section: ComponentType<any>;
|
||||
Message: ComponentType<any>;
|
||||
}
|
||||
|
||||
class TreeState extends PureComponent<TreeStateProps, TreeStateState> {
|
||||
state = {
|
||||
// We maintain two sets of expanded nodes, so we remember which were expanded if we clear the filter
|
||||
unfilteredExpanded: {},
|
||||
filteredExpanded: {},
|
||||
filter: null,
|
||||
lastSelectedId: null,
|
||||
};
|
||||
} as TreeStateState;
|
||||
|
||||
static getDerivedStateFromProps(props, state) {
|
||||
static getDerivedStateFromProps(props: TreeStateProps, state: TreeStateState) {
|
||||
return calculateTreeState(props, state);
|
||||
}
|
||||
|
||||
static defaultProps: Partial<TreeStateProps> = {
|
||||
selectedId: null,
|
||||
Filter: undefined,
|
||||
List: undefined,
|
||||
Title: undefined,
|
||||
Link: undefined,
|
||||
Leaf: undefined,
|
||||
Head: undefined,
|
||||
Section: undefined,
|
||||
Message: undefined,
|
||||
};
|
||||
|
||||
events = {
|
||||
onClick: (e, item) => {
|
||||
onClick: (e: SyntheticEvent, item: Item) => {
|
||||
this.setState(
|
||||
updateExpanded(expanded => ({
|
||||
...expanded,
|
||||
@ -236,7 +364,7 @@ class TreeState extends PureComponent {
|
||||
Object.keys(filteredDataset).reduce((acc, k) => Object.assign(acc, { [k]: true }), {}),
|
||||
});
|
||||
}, 100),
|
||||
onKeyUp: (e, item) => {
|
||||
onKeyUp: (e: KeyboardEvent, item: Item) => {
|
||||
const { prefix, dataset } = this.props;
|
||||
const { filter } = this.state;
|
||||
const filteredDataset = getFilteredDataset({ dataset, filter });
|
||||
@ -244,7 +372,7 @@ class TreeState extends PureComponent {
|
||||
|
||||
const action = keyEventToAction(e);
|
||||
if (action) {
|
||||
prevent(e);
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
if (action === 'RIGHT') {
|
||||
@ -319,7 +447,7 @@ class TreeState extends PureComponent {
|
||||
addons.getChannel().off(STORIES_EXPAND_ALL, this.onExpandAll);
|
||||
}
|
||||
|
||||
updateExpanded = expanded => {
|
||||
updateExpanded = (expanded: string | boolean) => {
|
||||
const { dataset, selectedId } = this.props;
|
||||
this.setState(({ unfilteredExpanded }) => {
|
||||
const selectedAncestorIds = selectedId ? getParents(selectedId, dataset).map(i => i.id) : [];
|
||||
@ -410,29 +538,4 @@ class TreeState extends PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
TreeState.propTypes = {
|
||||
prefix: PropTypes.string.isRequired,
|
||||
dataset: PropTypes.shape({}).isRequired,
|
||||
selectedId: PropTypes.string,
|
||||
Filter: PropTypes.elementType,
|
||||
List: PropTypes.elementType,
|
||||
Title: PropTypes.elementType,
|
||||
Link: PropTypes.elementType,
|
||||
Leaf: PropTypes.elementType,
|
||||
Head: PropTypes.elementType,
|
||||
Section: PropTypes.elementType,
|
||||
Message: PropTypes.elementType,
|
||||
};
|
||||
TreeState.defaultProps = {
|
||||
selectedId: null,
|
||||
Filter: undefined,
|
||||
List: undefined,
|
||||
Title: undefined,
|
||||
Link: undefined,
|
||||
Leaf: undefined,
|
||||
Head: undefined,
|
||||
Section: undefined,
|
||||
Message: undefined,
|
||||
};
|
||||
|
||||
export { TreeState, Tree };
|
@ -1,14 +1,41 @@
|
||||
import memoize from 'memoizerific';
|
||||
import Fuse from 'fuse.js';
|
||||
import { DOCS_MODE } from 'global';
|
||||
import { SyntheticEvent } from 'react';
|
||||
|
||||
const FUZZY_SEARCH_THRESHOLD = 0.4;
|
||||
|
||||
export const prevent = e => e.preventDefault();
|
||||
export const prevent = (e: SyntheticEvent) => {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
};
|
||||
|
||||
const toList = memoize(1)(dataset => Object.values(dataset));
|
||||
const toList = memoize(1)((dataset: Dataset) => Object.values(dataset));
|
||||
|
||||
export const keyEventToAction = ({ keyCode, ctrlKey, shiftKey, altKey, metaKey }) => {
|
||||
export interface Item {
|
||||
id: string;
|
||||
depth: number;
|
||||
name: string;
|
||||
kind: string;
|
||||
isLeaf: boolean;
|
||||
isComponent: boolean;
|
||||
isRoot: boolean;
|
||||
parent: string;
|
||||
children: string[];
|
||||
parameters: Record<string, any>;
|
||||
}
|
||||
|
||||
export type Dataset = Record<string, Item>;
|
||||
export type SelectedSet = Record<string, boolean>;
|
||||
export type ExpandedSet = Record<string, boolean>;
|
||||
|
||||
export const keyEventToAction = ({
|
||||
keyCode,
|
||||
ctrlKey,
|
||||
shiftKey,
|
||||
altKey,
|
||||
metaKey,
|
||||
}: KeyboardEvent) => {
|
||||
if (shiftKey || metaKey || ctrlKey || altKey) {
|
||||
return false;
|
||||
}
|
||||
@ -37,9 +64,9 @@ export const keyEventToAction = ({ keyCode, ctrlKey, shiftKey, altKey, metaKey }
|
||||
}
|
||||
};
|
||||
|
||||
export const createId = (id, prefix) => `${prefix}_${id}`;
|
||||
export const createId = (id: string, prefix: string) => `${prefix}_${id}`;
|
||||
|
||||
export const get = memoize(1000)((id, dataset) => dataset[id]);
|
||||
export const get = memoize(1000)((id: string, dataset: Dataset) => dataset[id]);
|
||||
export const getParent = memoize(1000)((id, dataset) => {
|
||||
const item = get(id, dataset);
|
||||
if (!item || item.isRoot) {
|
||||
@ -47,7 +74,7 @@ export const getParent = memoize(1000)((id, dataset) => {
|
||||
}
|
||||
return get(item.parent, dataset);
|
||||
});
|
||||
export const getParents = memoize(1000)((id, dataset) => {
|
||||
export const getParents = memoize(1000)((id: string, dataset: Dataset): Item[] => {
|
||||
const parent = getParent(id, dataset);
|
||||
|
||||
if (!parent) {
|
||||
@ -56,10 +83,20 @@ export const getParents = memoize(1000)((id, dataset) => {
|
||||
return [parent, ...getParents(parent.id, dataset)];
|
||||
});
|
||||
|
||||
export const getMains = memoize(1)(dataset => toList(dataset).filter(m => m.depth === 0));
|
||||
const getMainsKeys = memoize(1)(dataset => getMains(dataset).map(m => m.id));
|
||||
export const getMains = memoize(1)((dataset: Dataset) =>
|
||||
toList(dataset).filter(m => m.depth === 0)
|
||||
);
|
||||
const getMainsKeys = memoize(1)((dataset: Dataset) => getMains(dataset).map(m => m.id));
|
||||
|
||||
export const getPrevious = ({ id, dataset, expanded }) => {
|
||||
export const getPrevious = ({
|
||||
id,
|
||||
dataset,
|
||||
expanded,
|
||||
}: {
|
||||
id: string;
|
||||
dataset: Dataset;
|
||||
expanded: ExpandedSet;
|
||||
}): Item | undefined => {
|
||||
// STEP 1
|
||||
// find parent
|
||||
// if no previous sibling, use parent
|
||||
@ -99,7 +136,15 @@ export const getPrevious = ({ id, dataset, expanded }) => {
|
||||
return item;
|
||||
};
|
||||
|
||||
export const getNext = ({ id, dataset, expanded }) => {
|
||||
export const getNext = ({
|
||||
id,
|
||||
dataset,
|
||||
expanded,
|
||||
}: {
|
||||
id: string;
|
||||
dataset: Dataset;
|
||||
expanded: ExpandedSet;
|
||||
}): Item | undefined => {
|
||||
// STEP 1:
|
||||
// find any children if the node is expanded, first child
|
||||
//
|
||||
@ -122,7 +167,8 @@ export const getNext = ({ id, dataset, expanded }) => {
|
||||
|
||||
const mains = getMainsKeys(dataset);
|
||||
|
||||
const parents = getParents(id, dataset).concat([{ children: mains }]);
|
||||
const parents = getParents(id, dataset);
|
||||
// .concat([{ children: mains }]);
|
||||
|
||||
const next = parents.reduce(
|
||||
(acc, item) => {
|
||||
@ -155,15 +201,17 @@ const fuse = memoize(5)(
|
||||
})
|
||||
);
|
||||
|
||||
const exactMatch = memoize(1)(filter => i =>
|
||||
const exactMatch = memoize(1)(filter => (i: Item) =>
|
||||
(i.kind && 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))
|
||||
);
|
||||
|
||||
export const toId = (base, addition) => (base === '' ? `${addition}` : `${base}-${addition}`);
|
||||
export const toFiltered = (dataset, filter) => {
|
||||
export const toId = (base: string, addition: string) =>
|
||||
base === '' ? `${addition}` : `${base}-${addition}`;
|
||||
|
||||
export const toFiltered = (dataset: Dataset, filter: string) => {
|
||||
let found;
|
||||
if (filter.length && filter.length > 2) {
|
||||
found = fuse(dataset).search(filter);
|
||||
@ -179,11 +227,11 @@ export const toFiltered = (dataset, filter) => {
|
||||
|
||||
acc[item.id] = item;
|
||||
return acc;
|
||||
}, {});
|
||||
}, {} as Dataset);
|
||||
|
||||
// filter the children of the found items (and their parents) so only found entries are present
|
||||
return Object.entries(result).reduce((acc, [k, v]) => {
|
||||
acc[k] = v.children ? { ...v, children: v.children.filter(c => !!result[c]) } : v;
|
||||
return acc;
|
||||
}, {});
|
||||
}, {} as Dataset);
|
||||
};
|
@ -1,12 +1,14 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
// A small utility to add before/afterEach to stories.
|
||||
class WithLifecyle extends Component<{
|
||||
interface WithLifecyleProps {
|
||||
storyFn: Function;
|
||||
beforeEach?: Function;
|
||||
afterEach?: Function;
|
||||
}> {
|
||||
constructor(props) {
|
||||
}
|
||||
|
||||
// A small utility to add before/afterEach to stories.
|
||||
class WithLifecyle extends Component<WithLifecyleProps> {
|
||||
constructor(props: WithLifecyleProps) {
|
||||
super(props);
|
||||
|
||||
if (props.beforeEach) {
|
||||
@ -29,6 +31,6 @@ class WithLifecyle extends Component<{
|
||||
}
|
||||
}
|
||||
|
||||
export default ({ beforeEach, afterEach }) => storyFn => (
|
||||
<WithLifecyle beforeEach={beforeEach} afterEach={afterEach} storyFn={storyFn} />
|
||||
);
|
||||
export default ({ beforeEach, afterEach }: { beforeEach: Function; afterEach: Function }) => (
|
||||
storyFn: Function
|
||||
) => <WithLifecyle beforeEach={beforeEach} afterEach={afterEach} storyFn={storyFn} />;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { FunctionComponent } from 'react';
|
||||
|
||||
import { styled } from '@storybook/theming';
|
||||
import { Link } from '@storybook/components';
|
||||
@ -14,7 +14,7 @@ const Footer = styled.div(({ theme }) => ({
|
||||
marginLeft: 20,
|
||||
},
|
||||
}));
|
||||
const SettingsFooter = props => (
|
||||
const SettingsFooter: FunctionComponent<any> = props => (
|
||||
<Footer {...props}>
|
||||
<Link secondary href="https://storybook.js.org" cancel={false} target="_blank">
|
||||
Docs
|
@ -1,7 +1,7 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import React, { Fragment, FunctionComponent, SyntheticEvent } from 'react';
|
||||
import semver from 'semver';
|
||||
import PropTypes from 'prop-types';
|
||||
import { styled } from '@storybook/theming';
|
||||
import { State } from '@storybook/api';
|
||||
import { GlobalHotKeys } from 'react-hotkeys';
|
||||
import Markdown from 'markdown-to-jsx';
|
||||
|
||||
@ -55,7 +55,7 @@ const Subheader = styled.div({
|
||||
marginBottom: '.75rem',
|
||||
});
|
||||
|
||||
const UpdateMessage = styled.div(
|
||||
const UpdateMessage = styled.div<{ status: 'positive' | 'negative' | string }>(
|
||||
({ status, theme }) => {
|
||||
if (status === 'positive') {
|
||||
return { background: theme.background.positive, color: theme.color.positive };
|
||||
@ -93,7 +93,11 @@ const Container = styled.div({
|
||||
margin: '0 auto',
|
||||
});
|
||||
|
||||
const AboutScreen = ({ latest, current, onClose }) => {
|
||||
const AboutScreen: FunctionComponent<{
|
||||
latest: State['versions']['latest'];
|
||||
current: State['versions']['current'];
|
||||
onClose: (e?: KeyboardEvent) => void;
|
||||
}> = ({ latest = null, current, onClose }) => {
|
||||
const canUpdate = latest && semver.gt(latest.version, current.version);
|
||||
|
||||
let updateMessage;
|
||||
@ -126,7 +130,7 @@ const AboutScreen = ({ latest, current, onClose }) => {
|
||||
tools={
|
||||
<Fragment>
|
||||
<IconButton
|
||||
onClick={e => {
|
||||
onClick={(e: SyntheticEvent) => {
|
||||
e.preventDefault();
|
||||
return onClose();
|
||||
}}
|
||||
@ -206,21 +210,4 @@ const AboutScreen = ({ latest, current, onClose }) => {
|
||||
);
|
||||
};
|
||||
|
||||
AboutScreen.propTypes = {
|
||||
current: PropTypes.shape({
|
||||
version: PropTypes.string.isRequired,
|
||||
}).isRequired,
|
||||
latest: PropTypes.shape({
|
||||
version: PropTypes.string.isRequired,
|
||||
info: PropTypes.shape({
|
||||
plain: PropTypes.string.isRequired,
|
||||
}).isRequired,
|
||||
}),
|
||||
onClose: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
AboutScreen.defaultProps = {
|
||||
latest: null,
|
||||
};
|
||||
|
||||
export { AboutScreen as default };
|
@ -1,14 +1,13 @@
|
||||
import { history } from 'global';
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { Route } from '@storybook/router';
|
||||
import { Consumer } from '@storybook/api';
|
||||
import { Consumer, API, Combo } from '@storybook/api';
|
||||
|
||||
import AboutScreen from './about';
|
||||
|
||||
// Clear a notification on mount. This could be exported by core/notifications.js perhaps?
|
||||
class NotificationClearer extends Component {
|
||||
class NotificationClearer extends Component<{ api: API; notificationId: string }> {
|
||||
componentDidMount() {
|
||||
const { api, notificationId } = this.props;
|
||||
api.clearNotification(notificationId);
|
||||
@ -20,18 +19,10 @@ class NotificationClearer extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
NotificationClearer.propTypes = {
|
||||
api: PropTypes.shape({
|
||||
clearNotification: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
notificationId: PropTypes.string.isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
};
|
||||
|
||||
export default () => (
|
||||
<Route path="about">
|
||||
<Consumer>
|
||||
{({ api }) => (
|
||||
{({ api }: Combo) => (
|
||||
<NotificationClearer api={api} notificationId="update">
|
||||
<AboutScreen
|
||||
current={api.getCurrentVersion()}
|
@ -2,9 +2,14 @@
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"noImplicitAny": false,
|
||||
"strictNullChecks": true
|
||||
"types": ["webpack-env"],
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["src/__tests__/**/*"]
|
||||
"include": [
|
||||
"src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"src/**/*.test.*",
|
||||
"src/__tests__/**/*"
|
||||
]
|
||||
}
|
||||
|
@ -4254,6 +4254,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.149.tgz#1342d63d948c6062838fbf961012f74d4e638440"
|
||||
integrity sha512-ijGqzZt/b7BfzcK9vTrS6MFljQRPn5BFWOx8oE0GYxribu6uV+aA9zZuXI1zc/etK9E8nrgdoF2+LgUw7+9tJQ==
|
||||
|
||||
"@types/markdown-to-jsx@^6.9.1":
|
||||
version "6.9.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/markdown-to-jsx/-/markdown-to-jsx-6.9.1.tgz#538ce2b1d1af39da4e9e1b7e3dcf536dbede1f62"
|
||||
integrity sha512-WwG7BusI7neX26NtkbOZepTzA3W+RpNvEKl0GhO9l+CDIVPg6cWD2LhfREaSFQjXwvkj3YwP9FLUFbBP19VEig==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/mime-types@^2.1.0":
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/mime-types/-/mime-types-2.1.0.tgz#9ca52cda363f699c69466c2a6ccdaad913ea7a73"
|
||||
|
Loading…
x
Reference in New Issue
Block a user