Skip to content

Commit

Permalink
Merge pull request #6572 from storybooks/add/addon-contexts-tests
Browse files Browse the repository at this point in the history
Addon-contexts improvements: bug-fixing, testing, typing
  • Loading branch information
leoyli authored Apr 21, 2019
2 parents 5dd0ee3 + 8caf900 commit e579414
Show file tree
Hide file tree
Showing 27 changed files with 557 additions and 220 deletions.
2 changes: 0 additions & 2 deletions addons/contexts/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
# Storybook Addon Contexts

[![npm version](https://badge.fury.io/js/%40storybook%2Faddon-contexts.svg)](https://badge.fury.io/js/%40storybook%2Faddon-contexts)

**Storybook Addon Contexts** is an addon for driving your components under dynamic contexts in
[Storybook](https://storybook.js.org/).

Expand Down
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.

6 changes: 3 additions & 3 deletions addons/contexts/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// configs
export const ID = 'addon-contexts';
export const PARAM = 'contexts';
export const ID = 'addon-contexts' as const;
export const PARAM = 'contexts' as const;

// tokens
/**
* OPT_OUT is a token for skipping a context, dundering the string to avoid name collisions;
* ES6 Symbol is not available due to stringify used in Storybook event system via the channel.
*/
export const OPT_OUT = '__OPT_OUT__';
export const OPT_OUT = '__OPT_OUT__' as const;

// events
export const REBOOT_MANAGER = `${ID}/REBOOT_MANAGER`;
Expand Down
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, Render } 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: Render<React.ReactElement> = (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);
Loading

0 comments on commit e579414

Please sign in to comment.