Skip to content

Commit

Permalink
feat: add panels and story source to Playground
Browse files Browse the repository at this point in the history
  • Loading branch information
atanasster committed Mar 31, 2020
1 parent 0d90f10 commit 6371068
Show file tree
Hide file tree
Showing 7 changed files with 251 additions and 91 deletions.
16 changes: 16 additions & 0 deletions ui/blocks/src/Playground/Playground.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react';
import { Donut } from 'theme-ui';
import { Playground } from './Playground';
import { Story } from '../Story';
import { MockContext } from '../test/MockContext';
Expand Down Expand Up @@ -59,3 +60,18 @@ export const notCollapsible = () => (
</Playground>
</MockContext>
);

export const extraPanel = () => (
<MockContext storyId="blocks-core-story-plain--controls">
<Playground
actions={[
{
title: 'custom panel',
panel: <Donut value={1 / 2} />,
},
]}
>
<Story id="." />
</Playground>
</MockContext>
);
101 changes: 93 additions & 8 deletions ui/blocks/src/Playground/Playground.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
import React, { FC } from 'react';
import React, { FC, MouseEvent } from 'react';
import Octicon, { Plus, Dash, Sync } from '@primer/octicons-react';
import { Global, css } from '@emotion/core';
import {
Collapsible,
Tab,
Tabs,
TabList,
TabPanel,
} from '@component-controls/components';

import { Button } from 'theme-ui';
import { TransformComponent, TransformWrapper } from 'react-zoom-pan-pinch';
import {
getSortedPanels,
ActionItems,
ActionItem,
ActionContainer,
ActionContainerProps,
} from '@component-controls/components';
import { StorySource } from '../StorySource';

export interface TransformOptions {
limitToBounds?: boolean;
Expand Down Expand Up @@ -61,13 +72,62 @@ export type PlaygroundProps = PlaygroundOwnProps &

export const Playground: FC<PlaygroundProps> = ({
transform,
actions = [],
actions: userActions = [],
children,
}) => {
const [tabsIndex, setTabsIndex] = React.useState<number | undefined>(
undefined,
);
const childStories = <>{children}</>;
const zoomEnabled = !transform?.options?.disabled;
return zoomEnabled ? (
<>

let storyId: string;
const childArr = React.Children.toArray(children);
if (childArr.length === 1) {
//@ts-ignore
storyId = childArr[0].props.id;
userActions.push({
title: 'source',
id: 'source',
'aria-label': 'display story source code',
panel: <StorySource id={storyId} />,
});
}
const panels: ActionItems = getSortedPanels(userActions);
const panelActions = userActions.map((panel: ActionItem) => {
return panel.panel
? {
...panel,
onClick: (e: MouseEvent<HTMLButtonElement>) => {
const index = panels.findIndex(
(p: ActionItem) => p.title === panel.title,
);
if (index < 0) {
return undefined;
}
if (tabsIndex === index) {
setTabsIndex(undefined);
} else {
if (panel.onClick) {
const ret = panel.onClick(e);
if (ret === true) {
setTabsIndex(index);
return ret;
} else if (ret === false) {
setTabsIndex(undefined);
return ret;
}
}
setTabsIndex(index);
}
return undefined;
},
}
: panel;
});

return (
<ActionContainer>
<Global
styles={css`
.react-transform-component,
Expand Down Expand Up @@ -112,19 +172,44 @@ export const Playground: FC<PlaygroundProps> = ({
group: 'zoom',
},
];
const actions: ActionItem[] = [];
actions.push.apply(actions, panelActions);
const actionsItems = zoomEnabled
? [...zoomActions, ...actions]
: actions;
return (
<ActionContainer actions={actionsItems}>
<ActionContainer plain={true} actions={actionsItems}>
<TransformComponent>{childStories}</TransformComponent>
</ActionContainer>
);
}}
</TransformWrapper>
</>
) : (
childStories
<Collapsible isOpen={tabsIndex !== undefined}>
{panels.length === 1 ? (
panels[0].panel
) : (
<Tabs
selectedIndex={tabsIndex || 0}
onSelect={(index: number) => setTabsIndex(index)}
>
<TabList
style={{
textAlign: 'right',
}}
>
{panels.map((panel: ActionItem) => (
<Tab key={`playground_tab_${panel.title}`}>{panel.title}</Tab>
))}
</TabList>
{panels.map((panel: ActionItem) => (
<TabPanel key={`playground_panel_${panel.title}`}>
{panel.panel}
</TabPanel>
))}
</Tabs>
)}
</Collapsible>
</ActionContainer>
);
};

Expand Down
73 changes: 7 additions & 66 deletions ui/components/src/ActionBar/ActionBar.tsx
Original file line number Diff line number Diff line change
@@ -1,51 +1,14 @@
/** @jsx jsx */
import React, { FunctionComponent, MouseEvent } from 'react';
import { FunctionComponent } from 'react';
import { transparentize } from 'polished';
import { Theme, Box, Flex, Button, jsx, useThemeUI } from 'theme-ui';
import { getSortedActions, ActionItems } from './utils';

/**
* an item in the ActionBar component
*/
export interface ActionItem {
/**
* optional id, used if title is not set
*/
id?: string;
/**
* title - if a string, will use the Button component, else can prvide custom React component
*/
title: React.ReactNode;
/**
* onClick event when passing a string as the title
*/
onClick?: (e: MouseEvent<HTMLButtonElement>) => void;
/**
* displays the Button as disabled
*/
disabled?: boolean;
/**
* hide an action item
*/
hidden?: boolean;

/**
* optional order, if not provided will use the natural order of items from right to left
*/
order?: number;

/**
* optional group. ActionItems in the same group will not be separated by horizonal margin
*/
group?: string | number;

export interface ActionBarProps {
/**
* optional label visible to screen readers for aria accessibility.
* collection of action items
*/
'aria-label'?: string;
}

export interface ActionBarProps {
actions: ActionItem[];
actions: ActionItems;
}

const ActionColors = ({
Expand Down Expand Up @@ -83,30 +46,8 @@ export const ActionBar: FunctionComponent<ActionBarProps> = ({
actions = [],
}) => {
const { theme } = useThemeUI();
const sortedItems = actions
.filter(({ hidden }) => !hidden)
.reduce((acc: ActionItem[], item: ActionItem) => {
const accIndex = acc.findIndex(
accItem => (accItem.id ?? accItem.title) === (item.id ?? item.title),
);
if (accIndex > -1) {
acc[accIndex] = { ...acc[accIndex], ...item };
return acc;
} else {
return [...acc, item];
}
}, [])
.map(
({ order, ...item }, index) =>
({
...item,
order: order ?? index,
} as ActionItem),
)
.sort((a: ActionItem, b: ActionItem) => {
//@ts-ignore
return a.order - b.order;
});
const sortedItems = getSortedActions(actions);

return (
<Box
sx={{
Expand Down
1 change: 1 addition & 0 deletions ui/components/src/ActionBar/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './ActionBar';
export * from './utils';
90 changes: 90 additions & 0 deletions ui/components/src/ActionBar/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import React, { MouseEvent } from 'react';

/**
* an item in the ActionBar component
*/
export interface ActionItem {
/**
* optional id, used if title is not set
*/
id?: string;
/**
* title - if a string, will use the Button component, else can prvide custom React component
*/
title: React.ReactNode;
/**
* onClick event when passing a string as the title
*/
onClick?: (e: MouseEvent<HTMLButtonElement>) => void | boolean;
/**
* displays the Button as disabled
*/
disabled?: boolean;
/**
* hide an action item
*/
hidden?: boolean;

/**
* optional order, if not provided will use the natural order of items from right to left
*/
order?: number;

/**
* optional group. ActionItems in the same group will not be separated by horizonal margin
*/
group?: string | number;

/**
* optional label visible to screen readers for aria accessibility.
*/
'aria-label'?: string;

/**
* panel for Tab-enabled UI, where an action item can open up a panel with tabs
* in this case, the onClick function can return true/false whether to open up the panel
*/
panel?: React.ReactNode;
}

export type ActionItems = ActionItem[];

/**
* returns a sorted and grouped list of the actions
*/
export const getSortedActions = (actions: ActionItems): ActionItems =>
actions
.filter(({ hidden }) => !hidden)
.reduce((acc: ActionItem[], item: ActionItem) => {
const accIndex = acc.findIndex(
accItem => (accItem.id ?? accItem.title) === (item.id ?? item.title),
);
if (accIndex > -1) {
acc[accIndex] = { ...acc[accIndex], ...item };
return acc;
} else {
return [...acc, item];
}
}, [])
.map(
({ order, ...item }, index) =>
({
...item,
order: order ?? index,
} as ActionItem),
)
.sort((a: ActionItem, b: ActionItem) => {
//@ts-ignore
return a.order - b.order;
});

/**
* returns a sorted and grouped list of all the actions that have a panel property
*/
export const getSortedPanels = (actions: ActionItems): ActionItems =>
getSortedActions(actions)
.filter(action => action.panel)
.sort((a: ActionItem, b: ActionItem) => {
//@ts-ignore
return b.order - a.order;
});
Loading

0 comments on commit 6371068

Please sign in to comment.