Skip to content

Commit

Permalink
#14: make the component aggregator be framework agnostic
Browse files Browse the repository at this point in the history
  • Loading branch information
Leo Y. Li authored and ndelangen committed Apr 16, 2019
1 parent 9a74c19 commit 94bb77d
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 29 deletions.
19 changes: 9 additions & 10 deletions addons/addon-contexts/src/@types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@ declare type Channel = import('@storybook/channels').Channel;
declare type ComponentType = import('react').ComponentType;
declare type ComponentProps<C> = import('react').ComponentProps<C>;
declare type ReactNode = import('react').ReactNode;
declare type ReactElement = import('react').ReactElement;
declare type FC<P> = import('react').FunctionComponent<P>;

// auxiliary types
declare type FCNoChildren<P> = FC<{ children?: never } & P>;
declare type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
declare type GenericFnWithReturn<T> = (...args: any[]) => T;
declare type AnyFunctionReturns<T> = (...args: any[]) => T;
declare type GenericProps = { [key: string]: GenericProps } | null;
export declare type StringObject = { [key: string]: string };
export declare type StringTuple = [string, string];
Expand Down Expand Up @@ -51,23 +50,23 @@ export declare type PropsMapUpdaterType = (
) => ([nodeId, name]: StringTuple) => UPDATE_PROPS_MAP;

// helper types
export declare type RenderAggregatedComponents = (
...arg: [ComponentType[], GenericProps, AddonOptions, number]
) => GenericFnWithReturn<unknown>;
export declare type RenderAggregatedContexts = (
...arg: [ContextNode[], Exclude<GenericProps, null>]
) => GenericFnWithReturn<ReactElement>;
export declare type AggregateComponents = <T>(
h: (...arg: any[]) => T
) => (...arg: [ComponentType[], GenericProps, AddonOptions, number]) => AnyFunctionReturns<T>;
export declare type AggregateContexts = <T>(
h: (...arg: any[]) => T
) => (...arg: [ContextNode[], Exclude<GenericProps, null>]) => AnyFunctionReturns<T>;
export declare type MergeSettings = (
...args: [Partial<AddonSetting>, Partial<AddonSetting>]
) => ContextNode;
export declare type GetNodes = (settings: WrapperSettings) => ContextNode[];
export declare type GetContextNodes = (settings: WrapperSettings) => ContextNode[];
export declare type Memorize = <T, U extends any[]>(
fn: (...arg: U) => T,
resolver: (...arg: U) => unknown
) => (...arg: U) => T;
export declare type UseChannel = (
event: string,
eventHandler: GenericFnWithReturn<void>,
eventHandler: AnyFunctionReturns<void>,
input?: unknown[]
) => void;

Expand Down
6 changes: 3 additions & 3 deletions addons/addon-contexts/src/containers/ReactWrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useEffect, useReducer, useState } from 'react';
import { createElement as h, useEffect, useReducer, useState } from 'react';
import { INIT_WRAPPER, UPDATE_MANAGER, UPDATE_WRAPPER } from '../libs/constants';
import { propsMapsReducer, PropsMapUpdater } from '../libs/ducks';
import { renderAggregatedContexts } from '../libs/helpers';
import { aggregateContexts } from '../libs/helpers';
import { useChannel } from '../libs/hooks';
import { TAddonWrapper, StringObject, StringTuple } from '../@types';

Expand All @@ -26,5 +26,5 @@ export const ReactWrapper: TAddonWrapper = ({ channel, nodes, children }) => {
// push state to the manager (and wait to be initialized)
useEffect(() => channel.emit(UPDATE_MANAGER, nodes), []);

return renderAggregatedContexts(nodes, propsMap)(children(ready));
return aggregateContexts(h)(nodes, propsMap)(children(ready));
};
34 changes: 18 additions & 16 deletions addons/addon-contexts/src/libs/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { createElement as h } from 'react';
import {
Memorize,
GetNodes,
GetContextNodes,
MergeSettings,
RenderAggregatedComponents,
RenderAggregatedContexts,
AggregateComponents,
AggregateContexts,
} from '../@types';

/**
Expand All @@ -15,7 +14,7 @@ import {
export const _memorize: Memorize = (fn, resolver) => {
const memo = new Map();
return (...arg) => {
const key = resolver && resolver(...arg) || arg[0];
const key = (resolver && resolver(...arg)) || arg[0];
return memo.get(key) || memo.set(key, fn(...arg)).get(key);
};
};
Expand Down Expand Up @@ -52,7 +51,7 @@ export const _mergeSettings: MergeSettings = (
* pairs up settings for merging normalizations to produce the contextual definitions (-> nodes);
* it guarantee the adding order can be respected but not duplicated.
*/
export const _getNodes: GetNodes = ({ options, parameters }) => {
export const _getContextNodes: GetContextNodes = ({ options, parameters }) => {
const titles = Array()
.concat(options, parameters)
.map(({ title } = {}) => title);
Expand All @@ -70,8 +69,10 @@ export const _getNodes: GetNodes = ({ options, parameters }) => {
* @private
* Aggregates components with activated props in a descending order,
* based on the given options in the contextual environment setup.
* @param {function} h - the associated `createElement` vNode creator from the framework
*/
export const _renderAggregatedComponents: RenderAggregatedComponents = (
export const _aggregateComponents: AggregateComponents = (h) => (
components,
props,
options,
Expand All @@ -85,21 +86,22 @@ export const _renderAggregatedComponents: RenderAggregatedComponents = (
(props === undefined && !options.cancelable)
? next()
: components
.reverse()
.reduce(
(acc, C, index) => h(C, options.deep || index === last ? props : null, acc),
next()
);
.reverse()
.reduce(
(acc, C, index) => h(C, options.deep || index === last ? props : null, acc),
next()
);

/**
* Aggregate aggregated-components among all contextual nodes in a descending order.
*
* @param {function} h - the associated `createElement` vNode creator from the framework
*/
export const renderAggregatedContexts: RenderAggregatedContexts = (nodes, propsMap) => (next) =>
export const aggregateContexts: AggregateContexts = (h) => (nodes, propsMap) => (next) =>
nodes
.map(({ nodeId, components = [], options = {} }) =>
_renderAggregatedComponents(components, propsMap[nodeId], options, components.length - 1)
_aggregateComponents(h)(components, propsMap[nodeId], options, components.length - 1)
)
.reduce((acc, agg) => agg(() => acc), next());


export const getNodes = _memorize(_getNodes, ({ parameters }) => parameters);
export const getNodes = _memorize(_getContextNodes, ({ parameters }) => parameters);

0 comments on commit 94bb77d

Please sign in to comment.