Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Addon-contexts improvements: bug-fixing, testing, typing #6572

Merged
merged 7 commits into from
Apr 21, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions addons/contexts/src/@types/_Storybook__component.d.ts

This file was deleted.

58 changes: 0 additions & 58 deletions addons/contexts/src/@types/index.ts

This file was deleted.

49 changes: 0 additions & 49 deletions addons/contexts/src/@types/manager.ts

This file was deleted.

35 changes: 0 additions & 35 deletions addons/contexts/src/@types/preview.ts

This file was deleted.

14 changes: 9 additions & 5 deletions addons/contexts/src/manager/AddonManager.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import React, { useEffect, useState, useCallback } from 'react';
import { useChannel } from './libs/useChannel';
import { useChannel, Channel } from './libs/useChannel';
import { ToolBar } from './ToolBar';
import { REBOOT_MANAGER, UPDATE_MANAGER, UPDATE_PREVIEW } from '../constants';
import { TAddonManager, StringObject } from '../@types';
import { SelectionState, FCNoChildren } from '../types';

/**
* Control addon states and addon-story interactions
*/
export const AddonManager: TAddonManager = ({ channel }) => {
type AddonManager = FCNoChildren<{
channel: Channel;
}>;

export const AddonManager: AddonManager = ({ channel }) => {
const [nodes, setNodes] = useState([]);
const [state, setState] = useState(undefined);
const [state, setState] = useState<SelectionState>(undefined);
const setSelected = useCallback(
(nodeId, name) => setState((obj: StringObject = {}) => ({ ...obj, [nodeId]: name })),
(nodeId, name) => setState((obj = {}) => ({ ...obj, [nodeId]: name })),
[]
);

Expand Down
12 changes: 9 additions & 3 deletions addons/contexts/src/manager/ToolBar.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import React from 'react';
import React, { ComponentProps } from 'react';
import { Separator } from '@storybook/components';
import { ToolbarControl } from './ToolbarControl';
import { TToolBar } from '../@types';
import { ContextNode, FCNoChildren, SelectionState } from '../types';

export const ToolBar: TToolBar = React.memo(({ nodes, state, setSelected }) =>
type ToolBar = FCNoChildren<{
nodes: ContextNode[];
state: SelectionState;
setSelected: ComponentProps<typeof ToolbarControl>['setSelected'];
}>;

export const ToolBar: ToolBar = React.memo(({ nodes, state, setSelected }) =>
nodes.length ? (
<>
<Separator />
Expand Down
44 changes: 27 additions & 17 deletions addons/contexts/src/manager/ToolBarMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,36 @@
import React from 'react';
import React, { ComponentProps } from 'react';
import { Icons, IconButton, WithTooltip } from '@storybook/components';
import { ToolBarMenuOptions } from './ToolBarMenuOptions';
import { TToolBarMenu } from '../@types';
import { ContextNode, FCNoChildren } from '../types';

export const ToolBarMenu: TToolBarMenu = ({
type ToolBarMenu = FCNoChildren<{
icon: ContextNode['icon'];
title: ContextNode['title'];
active: boolean;
expanded: boolean;
setExpanded: (state: boolean) => void;
optionsProps: ComponentProps<typeof ToolBarMenuOptions>;
}>;

export const ToolBarMenu: ToolBarMenu = ({
icon,
title,
active,
expanded,
setExpanded,
optionsProps,
}) => (
<WithTooltip
closeOnClick
trigger="click"
placement="top"
tooltipShown={expanded}
onVisibilityChange={setExpanded}
tooltip={<ToolBarMenuOptions {...optionsProps} />}
>
<IconButton active={active} title={title}>
<Icons icon={icon} />
</IconButton>
</WithTooltip>
);
}) =>
icon ? (
<WithTooltip
closeOnClick
trigger="click"
placement="top"
tooltipShown={expanded}
onVisibilityChange={setExpanded}
tooltip={<ToolBarMenuOptions {...optionsProps} />}
>
<IconButton active={active} title={title}>
<Icons icon={icon} />
</IconButton>
</WithTooltip>
) : null;
10 changes: 8 additions & 2 deletions addons/contexts/src/manager/ToolBarMenuOptions.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import React from 'react';
import { TooltipLinkList } from '@storybook/components';
import { OPT_OUT } from '../constants';
import { TToolBarMenuOptions } from '../@types';
import { FCNoChildren } from '../types';

export const ToolBarMenuOptions: TToolBarMenuOptions = ({ activeName, list, onSelectOption }) => (
type ToolBarMenuOptions = FCNoChildren<{
activeName: string;
list: string[];
onSelectOption: (name: string) => () => void;
}>;

export const ToolBarMenuOptions: ToolBarMenuOptions = ({ activeName, list, onSelectOption }) => (
<TooltipLinkList
links={list.map(name => ({
key: name,
Expand Down
14 changes: 12 additions & 2 deletions addons/contexts/src/manager/ToolbarControl.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
import React from 'react';
import { ToolBarMenu } from './ToolBarMenu';
import { OPT_OUT } from '../constants';
import { TToolbarControl } from '../@types';
import { ContextNode, FCNoChildren, Omit } from '../types';

export const ToolbarControl: TToolbarControl = ({
type ToolbarControl = FCNoChildren<
Omit<
ContextNode & {
selected: string;
setSelected: (nodeId: string, name: string) => void;
},
'components'
>
>;

export const ToolbarControl: ToolbarControl = ({
nodeId,
icon,
title,
Expand Down
12 changes: 11 additions & 1 deletion addons/contexts/src/manager/libs/useChannel.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
export { Channel } from '@storybook/channels';
import addons from '@storybook/addons';
import { useEffect } from 'react';
import { UseChannel } from '../../@types';
import { AnyFunctionReturns } from '../../types';

/**
* The React hook version of Storybook Channel API.
*/
type UseChannel = (
event: string,
eventHandler: AnyFunctionReturns<void>,
input?: unknown[]
) => void;

export const useChannel: UseChannel = (event, eventHandler, inputs = []) =>
useEffect(() => {
Expand Down
6 changes: 3 additions & 3 deletions addons/contexts/src/preview/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import addons from '@storybook/addons';
import { FORCE_RE_RENDER, SET_CURRENT_STORY } from '@storybook/core-events';
import { REBOOT_MANAGER, UPDATE_PREVIEW, UPDATE_MANAGER } from '../constants';
import { getContextNodes, getPropsMap, getRendererFrom, singleton } from './libs';
import { ContextNode, GenericObject, GetContextNodes } from '../@types';
import { ContextNode, PropsMap } from '../types';

/**
* @Public
Expand All @@ -20,7 +20,7 @@ export const addonContextsAPI = singleton(() => {
channel.on(UPDATE_PREVIEW, () => channel.emit(FORCE_RE_RENDER));

// to manager
const getContextNodesWithSideEffects: GetContextNodes = (...arg) => {
const getContextNodesWithSideEffects: typeof getContextNodes = (...arg) => {
// we want to notify the manager only when the story changed since `parameter` can be changed
if (memorizedNodes === null) {
memorizedNodes = getContextNodes(...arg);
Expand All @@ -35,7 +35,7 @@ export const addonContextsAPI = singleton(() => {
* which is the reason why we have to keep an internal reference and use `Object.assign` to update it.
*/
let reactivePropsMap = {};
const updateReactiveSystem = (propsMap: GenericObject) =>
const updateReactiveSystem = (propsMap: PropsMap) =>
/* tslint:disable:prefer-object-spread */
Object.assign(reactivePropsMap, propsMap);

Expand Down
7 changes: 3 additions & 4 deletions addons/contexts/src/preview/frameworks/react.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import React from 'react';
import { createAddonDecorator } from '../index';
import { createAddonDecorator, Renderer } from '../index';
import { addonContextsAPI } from '../api';
import { Renderer } from '../../@types';

/**
* This is the framework specific bindings for React.
* '@storybook/react' expects the returning object from a decorator to be a 'React Element' (vNode).
*/
export const renderReact: Renderer = (nodes, props, next) => {
export const renderReact: Renderer = (contextNodes, propsMap, getStoryVNode) => {
const { getRendererFrom } = addonContextsAPI();
return getRendererFrom(React.createElement)(nodes, props, next);
return getRendererFrom(React.createElement)(contextNodes, propsMap, getStoryVNode);
};

export const withContexts = createAddonDecorator(renderReact);
7 changes: 3 additions & 4 deletions addons/contexts/src/preview/frameworks/vue.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import Vue, { Component } from 'vue';
import { createAddonDecorator } from '../index';
import { createAddonDecorator, Renderer } from '../index';
import { addonContextsAPI } from '../api';
import { ID } from '../../constants';
import { Renderer } from '../../@types';

/**
* This is the framework specific bindings for Vue.
* '@storybook/vue' expects the returning object from a decorator to be a 'VueComponent'.
*/
export const renderVue: Renderer = (nodes, propsMap, next) => {
export const renderVue: Renderer = (contextNodes, propsMap, getStoryVNode) => {
const { getRendererFrom, updateReactiveSystem } = addonContextsAPI();
const reactiveProps = updateReactiveSystem(propsMap);
return Vue.extend({
Expand All @@ -17,7 +16,7 @@ export const renderVue: Renderer = (nodes, propsMap, next) => {
render: createElement =>
getRendererFrom((component, props, children) =>
createElement(component, { props }, [children])
)(nodes, reactiveProps, () => createElement(next() as Component)),
)(contextNodes, reactiveProps, () => createElement(getStoryVNode() as Component)),
});
};

Expand Down
7 changes: 5 additions & 2 deletions addons/contexts/src/preview/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { makeDecorator, StoryWrapper } from '@storybook/addons';
import { addonContextsAPI } from './api';
import { ID, PARAM } from '../constants';
import { CreateAddonDecorator } from '../@types';
import { AddonSetting, AnyFunctionReturns, ContextNode, PropsMap } from '../types';

/**
* This file serves a idiomatic facade of a Storybook decorator.
Expand All @@ -14,8 +14,11 @@ import { CreateAddonDecorator } from '../@types';
* who is also knowing how to communicate with the Storybook manager (in React) via the Storybook
* event system.
*
* @param render - framework specific bindings
* @param {Renderer} render - framework specific bindings
*/
export type Renderer = (...args: [ContextNode[], PropsMap, AnyFunctionReturns<unknown>]) => unknown;
type CreateAddonDecorator = (renderer: Renderer) => (contexts: AddonSetting[]) => unknown;

export const createAddonDecorator: CreateAddonDecorator = render => {
const wrapper: StoryWrapper = (getStory, context, settings: any) => {
const { getContextNodes, getSelectionState, getPropsMap } = addonContextsAPI();
Expand Down
Loading