diff --git a/addons/backgrounds/package.json b/addons/backgrounds/package.json index ec3ccb3da524..dcb89bca0ffa 100644 --- a/addons/backgrounds/package.json +++ b/addons/backgrounds/package.json @@ -19,7 +19,7 @@ "license": "MIT", "author": "jbaxleyiii", "main": "dist/index.js", - "jsnext:main": "src/index.js", + "types": "dist/index.d.ts", "scripts": { "prepare": "node ../../scripts/prepare.js" }, @@ -30,13 +30,14 @@ "@storybook/core-events": "5.0.0-rc.4", "@storybook/theming": "5.0.0-rc.4", "core-js": "^2.6.2", - "eventemitter3": "^3.1.0", "global": "^4.3.2", "memoizerific": "^1.11.3", - "prop-types": "^15.6.2", "react": "^16.8.1", "util-deprecate": "^1.0.2" }, + "devDependencies": { + "@types/util-deprecate": "^1.0.0" + }, "publishConfig": { "access": "public" } diff --git a/addons/backgrounds/src/Tool.js b/addons/backgrounds/src/Tool.js deleted file mode 100644 index d05a7658936d..000000000000 --- a/addons/backgrounds/src/Tool.js +++ /dev/null @@ -1,149 +0,0 @@ -import React, { Component, Fragment } from 'react'; -import PropTypes from 'prop-types'; -import memoize from 'memoizerific'; - -import { Global, styled } from '@storybook/theming'; - -import { SET_STORIES } from '@storybook/core-events'; - -import { Icons, IconButton, WithTooltip, TooltipLinkList } from '@storybook/components'; - -import { PARAM_KEY } from './constants'; - -export const ColorIcon = styled.span(({ background }) => ({ - borderRadius: '1rem', - display: 'block', - height: '1rem', - width: '1rem', - background, -})); - -const iframeId = 'storybook-preview-background'; - -const createItem = memoize(1000)((id, name, value, hasSwatch, change) => - hasSwatch - ? { - id: id || name, - title: name, - onClick: () => { - change({ selected: value, expanded: false }); - }, - value, - right: , - } - : { - id: id || name, - title: name, - onClick: () => { - change({ selected: value, expanded: false }); - }, - value, - } -); - -const getSelected = (list, state) => { - if (!list.length) { - return 'transparent'; - } - - if (state === 'transparent') { - return state; - } - - if (list.find(i => i.value === state)) { - return state; - } - - if (list.find(i => i.default)) { - return list.find(i => i.default).value; - } - - return 'transparent'; -}; - -const getState = memoize(10)((props, state, change) => { - const data = props.api.getCurrentStoryData(); - const list = (data && data.parameters && data.parameters[PARAM_KEY]) || []; - - const selected = getSelected(list, state.selected); - - const initial = - selected !== 'transparent' - ? [createItem('reset', 'Clear background', 'transparent', false, change)] - : []; - - const items = list.length - ? initial.concat(list.map(({ id, name, value }) => createItem(id, name, value, true, change))) - : list; - - return { - items, - selected, - }; -}); - -export default class BackgroundTool extends Component { - static propTypes = { - api: PropTypes.shape({ - getQueryParam: PropTypes.func, - setQueryParams: PropTypes.func, - }).isRequired, - }; - - constructor(props) { - super(props); - - this.state = { - items: [], - selected: undefined, - expanded: false, - }; - - this.listener = () => { - this.setState({ selected: null }); - }; - } - - componentDidMount() { - const { api } = this.props; - api.on(SET_STORIES, this.listener); - } - - componentWillUnmount() { - const { api } = this.props; - api.off(SET_STORIES, this.listener); - } - - change = (...args) => this.setState(...args); - - render() { - const { expanded } = this.state; - const { items, selected } = getState(this.props, this.state, this.change); - - return items.length ? ( - - {selected ? ( - - ) : null} - this.setState({ expanded: s })} - tooltip={} - closeOnClick - > - - - - - - ) : null; - } -} diff --git a/addons/backgrounds/src/components.js b/addons/backgrounds/src/components.js deleted file mode 100644 index 9bc6443aaf80..000000000000 --- a/addons/backgrounds/src/components.js +++ /dev/null @@ -1,5 +0,0 @@ -import { styled } from '@storybook/theming'; - -export const ColorIcon = styled.span(({ background }) => ({ - background, -})); diff --git a/addons/backgrounds/src/components/ColorIcon.tsx b/addons/backgrounds/src/components/ColorIcon.tsx new file mode 100644 index 000000000000..aabdce3ffd46 --- /dev/null +++ b/addons/backgrounds/src/components/ColorIcon.tsx @@ -0,0 +1,9 @@ +import { styled } from '@storybook/theming'; + +export const ColorIcon = styled.span(({ background }: { background: string }) => ({ + borderRadius: '1rem', + display: 'block', + height: '1rem', + width: '1rem', + background, +})); diff --git a/addons/backgrounds/src/components/index.ts b/addons/backgrounds/src/components/index.ts new file mode 100644 index 000000000000..db2a966bc4fe --- /dev/null +++ b/addons/backgrounds/src/components/index.ts @@ -0,0 +1 @@ +export * from './ColorIcon'; diff --git a/addons/backgrounds/src/constants.js b/addons/backgrounds/src/constants.ts similarity index 86% rename from addons/backgrounds/src/constants.js rename to addons/backgrounds/src/constants.ts index 39f865027c50..df63c53889d1 100644 --- a/addons/backgrounds/src/constants.js +++ b/addons/backgrounds/src/constants.ts @@ -1,7 +1,7 @@ export const ADDON_ID = 'storybook/background'; export const PARAM_KEY = 'backgrounds'; -export default { +export const EVENTS = { SET: `${ADDON_ID}:set`, UNSET: `${ADDON_ID}:unset`, }; diff --git a/addons/backgrounds/src/containers/BackgroundSelector.tsx b/addons/backgrounds/src/containers/BackgroundSelector.tsx new file mode 100644 index 000000000000..9dd602b9a72e --- /dev/null +++ b/addons/backgrounds/src/containers/BackgroundSelector.tsx @@ -0,0 +1,165 @@ +import React, { Component, Fragment } from 'react'; +import memoize from 'memoizerific'; + +import { Global } from '@storybook/theming'; + +import { SET_STORIES } from '@storybook/core-events'; + +import { Icons, IconButton, WithTooltip, TooltipLinkList } from '@storybook/components'; + +import { PARAM_KEY } from '../constants'; +import { ColorIcon } from '../components'; +import { BackgroundConfig, BackgroundSelectorItem } from '../models'; + +const iframeId = 'storybook-preview-background'; + +const createBackgroundSelectorItem = memoize(1000)( + ( + id: string, + name: string, + value: string, + hasSwatch: boolean, + change: (arg: { selected: string; expanded: boolean }) => void + ): BackgroundSelectorItem => ({ + id: id || name, + title: name, + onClick: () => { + change({ selected: value, expanded: false }); + }, + value, + right: hasSwatch ? : undefined, + }) +); + +const getSelectedBackgroundColor = ( + list: BackgroundConfig[], + currentSelectedValue: string +): string => { + if (!list.length) { + return 'transparent'; + } + + if (currentSelectedValue === 'transparent') { + return currentSelectedValue; + } + + if (list.find(i => i.value === currentSelectedValue)) { + return currentSelectedValue; + } + + if (list.find(i => i.default)) { + return list.find(i => i.default).value; + } + + return 'transparent'; +}; + +const getDisplayableState = memoize(10)( + (props: BackgroundToolProps, state: BackgroundToolState, change) => { + const data = props.api.getCurrentStoryData(); + const list: BackgroundConfig[] = (data && data.parameters && data.parameters[PARAM_KEY]) || []; + + const selectedBackgroundColor = getSelectedBackgroundColor(list, state.selected); + + let availableBackgroundSelectorItems: BackgroundSelectorItem[] = []; + + if (selectedBackgroundColor !== 'transparent') { + availableBackgroundSelectorItems.push( + createBackgroundSelectorItem('reset', 'Clear background', 'transparent', false, change) + ); + } + + if (list.length) { + availableBackgroundSelectorItems = [ + ...availableBackgroundSelectorItems, + ...list.map(({ name, value }) => + createBackgroundSelectorItem(null, name, value, true, change) + ), + ]; + } + + return { + items: availableBackgroundSelectorItems, + selectedBackgroundColor, + }; + } +); + +interface BackgroundToolProps { + api: { + on(event: string, callback: (data: any) => void): void; + off(event: string, callback: (data: any) => void): void; + getCurrentStoryData(): any; + }; +} + +interface BackgroundToolState { + items: BackgroundSelectorItem[]; + selected: string; + expanded: boolean; +} + +export class BackgroundSelector extends Component { + private listener = () => { + this.setState({ selected: null }); + }; + + constructor(props: BackgroundToolProps) { + super(props); + + this.state = { + items: [], + selected: null, + expanded: false, + }; + } + + componentDidMount() { + const { api } = this.props; + api.on(SET_STORIES, this.listener); + } + + componentWillUnmount() { + const { api } = this.props; + api.off(SET_STORIES, this.listener); + } + + change = (args: { selected: string; expanded: boolean }) => this.setState(args); + + render() { + const { expanded } = this.state; + const { items, selectedBackgroundColor } = getDisplayableState( + this.props, + this.state, + this.change + ); + + return items.length ? ( + + {selectedBackgroundColor ? ( + + ) : null} + + this.setState({ expanded: newVisibility }) + } + tooltip={} + closeOnClick + > + + + + + + ) : null; + } +} diff --git a/addons/backgrounds/src/containers/index.ts b/addons/backgrounds/src/containers/index.ts new file mode 100644 index 000000000000..87f68e7c7ae1 --- /dev/null +++ b/addons/backgrounds/src/containers/index.ts @@ -0,0 +1 @@ +export * from './BackgroundSelector'; diff --git a/addons/backgrounds/src/deprecated.js b/addons/backgrounds/src/deprecated.ts similarity index 100% rename from addons/backgrounds/src/deprecated.js rename to addons/backgrounds/src/deprecated.ts diff --git a/addons/backgrounds/src/index.js b/addons/backgrounds/src/index.ts similarity index 68% rename from addons/backgrounds/src/index.js rename to addons/backgrounds/src/index.ts index 460a270e44b9..7bccd015db8f 100644 --- a/addons/backgrounds/src/index.js +++ b/addons/backgrounds/src/index.ts @@ -1,14 +1,15 @@ -import addons, { makeDecorator } from '@storybook/addons'; -import CoreEvents from '@storybook/core-events'; +import { addons, makeDecorator } from '@storybook/addons'; import deprecate from 'util-deprecate'; -import Events from './constants'; +import { REGISTER_SUBSCRIPTION } from '@storybook/core-events'; +import { EVENTS } from './constants'; +import { BackgroundConfig } from './models'; -let prevBackgrounds; +let prevBackgrounds: BackgroundConfig[]; const subscription = () => () => { prevBackgrounds = null; - addons.getChannel().emit(Events.UNSET); + addons.getChannel().emit(EVENTS.UNSET); }; export const withBackgrounds = makeDecorator({ @@ -25,10 +26,10 @@ export const withBackgrounds = makeDecorator({ } if (prevBackgrounds !== backgrounds) { - addons.getChannel().emit(Events.SET, backgrounds); + addons.getChannel().emit(EVENTS.SET, backgrounds); prevBackgrounds = backgrounds; } - addons.getChannel().emit(CoreEvents.REGISTER_SUBSCRIPTION, subscription); + addons.getChannel().emit(REGISTER_SUBSCRIPTION, subscription); return getStory(context); }, diff --git a/addons/backgrounds/src/models/BackgroundConfig.ts b/addons/backgrounds/src/models/BackgroundConfig.ts new file mode 100644 index 000000000000..f48826281bd5 --- /dev/null +++ b/addons/backgrounds/src/models/BackgroundConfig.ts @@ -0,0 +1,5 @@ +export interface BackgroundConfig { + name: string; + value: string; + default?: boolean; +} diff --git a/addons/backgrounds/src/models/BackgroundSelectorItem.ts b/addons/backgrounds/src/models/BackgroundSelectorItem.ts new file mode 100644 index 000000000000..fdc35e55c10a --- /dev/null +++ b/addons/backgrounds/src/models/BackgroundSelectorItem.ts @@ -0,0 +1,7 @@ +export interface BackgroundSelectorItem { + id: string; + title: string; + onClick: () => void; + value: string; + right?: any; +} diff --git a/addons/backgrounds/src/models/index.ts b/addons/backgrounds/src/models/index.ts new file mode 100644 index 000000000000..dc3676a463d3 --- /dev/null +++ b/addons/backgrounds/src/models/index.ts @@ -0,0 +1,2 @@ +export * from './BackgroundConfig'; +export * from './BackgroundSelectorItem'; diff --git a/addons/backgrounds/src/register.js b/addons/backgrounds/src/register.tsx similarity index 54% rename from addons/backgrounds/src/register.js rename to addons/backgrounds/src/register.tsx index 7f398493438a..d592fdd43ef0 100644 --- a/addons/backgrounds/src/register.js +++ b/addons/backgrounds/src/register.tsx @@ -1,13 +1,14 @@ import React from 'react'; -import addons, { types } from '@storybook/addons'; +import { addons, types } from '@storybook/addons'; import { ADDON_ID } from './constants'; -import Tool from './Tool'; +import { BackgroundSelector } from './containers'; addons.register(ADDON_ID, api => { addons.add(ADDON_ID, { + title: 'Backgrounds', type: types.TOOL, match: ({ viewMode }) => viewMode === 'story', - render: () => , + render: () => , }); }); diff --git a/addons/backgrounds/src/typings.d.ts b/addons/backgrounds/src/typings.d.ts new file mode 100644 index 000000000000..0ca9d4552932 --- /dev/null +++ b/addons/backgrounds/src/typings.d.ts @@ -0,0 +1,2 @@ +// TODO: following packages need definition files or a TS migration +declare module '@storybook/components'; diff --git a/addons/backgrounds/tsconfig.json b/addons/backgrounds/tsconfig.json new file mode 100644 index 000000000000..8876bb6737a1 --- /dev/null +++ b/addons/backgrounds/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "./src", + "types": ["webpack-env"] + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "src/__tests__/**/*" + ] +}