Skip to content

Commit

Permalink
Merge pull request #12368 from storybookjs/feat/revamp-addon-backgrounds
Browse files Browse the repository at this point in the history
Addon-backgrounds: Add docs support and extend grid configuration
  • Loading branch information
shilman committed Sep 25, 2020
2 parents be9ac8d + 6c1bb7c commit eca16d9
Show file tree
Hide file tree
Showing 29 changed files with 651 additions and 246 deletions.
32 changes: 32 additions & 0 deletions MIGRATION.md
Expand Up @@ -3,6 +3,7 @@
- [From version 6.0.x to 6.1.0](#from-version-60x-to-610)
- [6.1 deprecations](#61-deprecations)
- [Deprecated onBeforeRender](#deprecated-onbeforerender)
- [Deprecated grid parameter](#deprecated-grid-parameter)
- [From version 5.3.x to 6.0.x](#from-version-53x-to-60x)
- [Hoisted CSF annotations](#hoisted-csf-annotations)
- [Zero config typescript](#zero-config-typescript)
Expand Down Expand Up @@ -142,6 +143,37 @@ The `@storybook/addon-docs` previously accepted a `jsx` option called `onBeforeR

We've renamed it `transformSource` and also allowed it to receive the `StoryContext` in case source rendering requires additional information.

#### Deprecated grid parameter

Previously when using `@storybook/addon-backgrounds` if you wanted to customize the grid, you would define a parameter like this:

```js
export const Basic = () => <Button />
Basic.parameters: {
grid: {
cellSize: 10
}
},
```

As grid is not an addon, but rather backgrounds is, the grid configuration was moved to be inside `backgrounds` parameter instead. Also, there are new properties that can be used to further customize the grid. Here's an example with the default values:

```js
export const Basic = () => <Button />
Basic.parameters: {
backgrounds: {
grid: {
disable: false,
cellSize: 20,
opacity: 0.5,
cellAmount: 5,
offsetX: 16, // default is 0 if story has 'fullscreen' layout, 16 if layout is 'padded'
offsetY: 16, // default is 0 if story has 'fullscreen' layout, 16 if layout is 'padded'
}
}
},
```

## From version 5.3.x to 6.0.x

### Hoisted CSF annotations
Expand Down
7 changes: 5 additions & 2 deletions addons/backgrounds/package.json
Expand Up @@ -39,10 +39,13 @@
"@storybook/core-events": "6.1.0-alpha.15",
"@storybook/theming": "6.1.0-alpha.15",
"core-js": "^3.0.1",
"global": "^4.3.2",
"memoizerific": "^1.11.3",
"react": "^16.8.3",
"react-dom": "^16.8.3",
"regenerator-runtime": "^0.13.3"
"regenerator-runtime": "^0.13.3",
"ts-dedent": "^1.1.1",
"util-deprecate": "^1.0.2"
},
"devDependencies": {
"@types/webpack-env": "^1.15.2"
Expand All @@ -58,4 +61,4 @@
]
}
}
}
}
242 changes: 84 additions & 158 deletions addons/backgrounds/src/containers/BackgroundSelector.tsx
@@ -1,59 +1,23 @@
import React, { Component, Fragment, ReactElement } from 'react';
import React, { FunctionComponent, Fragment, useCallback, useMemo, memo } from 'react';
import memoize from 'memoizerific';

import { Combo, Consumer, API } from '@storybook/api';
import { Global, Theme } from '@storybook/theming';
import { useParameter, useGlobals } from '@storybook/api';
import { logger } from '@storybook/client-logger';

import { Icons, IconButton, WithTooltip, TooltipLinkList } from '@storybook/components';

import { PARAM_KEY as BACKGROUNDS_PARAM_KEY, EVENTS } from '../constants';
import { PARAM_KEY as BACKGROUNDS_PARAM_KEY } from '../constants';
import { ColorIcon } from '../components/ColorIcon';

interface GlobalState {
name: string | undefined;
selected: string | undefined;
}

interface Props {
api: API;
}

interface BackgroundSelectorItem {
id: string;
title: string;
onClick: () => void;
value: string;
right?: ReactElement;
}

interface Background {
name: string;
value: string;
}

interface BackgroundsParameter {
default?: string;
disable?: boolean;
values: Background[];
}

interface BackgroundsConfig {
backgrounds: Background[] | null;
selectedBackground: string | null;
defaultBackgroundName: string | null;
disable: boolean;
}

const iframeId = 'storybook-preview-iframe';
import { BackgroundSelectorItem, Background, BackgroundsParameter, GlobalState } from '../types';
import { getBackgroundColorByName } from '../helpers';

const createBackgroundSelectorItem = memoize(1000)(
(
id: string,
name: string,
value: string,
hasSwatch: boolean,
change: (arg: { selected: string; name: string }) => void
change: (arg: { selected: string; name: string }) => void,
active: boolean
): BackgroundSelectorItem => ({
id: id || name,
title: name,
Expand All @@ -62,6 +26,7 @@ const createBackgroundSelectorItem = memoize(1000)(
},
value,
right: hasSwatch ? <ColorIcon background={value} /> : undefined,
active,
})
);

Expand All @@ -72,12 +37,26 @@ const getDisplayedItems = memoize(10)(
change: (arg: { selected: string; name: string }) => void
) => {
const backgroundSelectorItems = backgrounds.map(({ name, value }) =>
createBackgroundSelectorItem(null, name, value, true, change)
createBackgroundSelectorItem(
null,
name,
value,
true,
change,
value === selectedBackgroundColor
)
);

if (selectedBackgroundColor !== 'transparent') {
return [
createBackgroundSelectorItem('reset', 'Clear background', 'transparent', null, change),
createBackgroundSelectorItem(
'reset',
'Clear background',
'transparent',
null,
change,
false
),
...backgroundSelectorItems,
];
}
Expand All @@ -86,131 +65,78 @@ const getDisplayedItems = memoize(10)(
}
);

const getSelectedBackgroundColor = (
backgrounds: Background[] = [],
currentSelectedValue: string,
defaultName: string
): string => {
if (currentSelectedValue === 'transparent') {
return 'transparent';
}

if (backgrounds.find((background) => background.value === currentSelectedValue)) {
return currentSelectedValue;
}
const DEFAULT_BACKGROUNDS_CONFIG: BackgroundsParameter = {
default: null,
disable: true,
values: [],
};

const defaultBackground = backgrounds.find((background) => background.name === defaultName);
if (defaultBackground) {
return defaultBackground.value;
}
export const BackgroundSelector: FunctionComponent = memo(() => {
const backgroundsConfig = useParameter<BackgroundsParameter>(
BACKGROUNDS_PARAM_KEY,
DEFAULT_BACKGROUNDS_CONFIG
);

if (defaultName) {
const availableColors = backgrounds.map((background) => background.name).join(', ');
logger.warn(
`Backgrounds Addon: could not find the default color "${defaultName}".
These are the available colors for your story based on your configuration: ${availableColors}`
);
}
const [globals, updateGlobals] = useGlobals();

return 'transparent';
};
const globalsBackgroundColor = globals[BACKGROUNDS_PARAM_KEY]?.value;

const getBackgroundsConfig = ({ api, state }: Combo): BackgroundsConfig => {
const backgroundsParameter = api.getCurrentParameter<BackgroundsParameter>(BACKGROUNDS_PARAM_KEY);
const selectedBackgroundValue = state.addons[BACKGROUNDS_PARAM_KEY] || null;
const selectedBackgroundColor = useMemo(() => {
return getBackgroundColorByName(
globalsBackgroundColor,
backgroundsConfig.values,
backgroundsConfig.default
);
}, [backgroundsConfig, globalsBackgroundColor]);

if (Array.isArray(backgroundsParameter)) {
if (Array.isArray(backgroundsConfig)) {
logger.warn(
'Addon Backgrounds api has changed in Storybook 6.0. Please refer to the migration guide: https://github.com/storybookjs/storybook/blob/next/MIGRATION.md'
);
}

const isBackgroundsEmpty = !backgroundsParameter?.values?.length;
if (backgroundsParameter?.disable || isBackgroundsEmpty) {
// other null properties are necessary to keep the same return shape for Consumer memoization
return {
disable: true,
backgrounds: null,
selectedBackground: null,
defaultBackgroundName: null,
};
}

return {
disable: false,
backgrounds: backgroundsParameter?.values,
selectedBackground: selectedBackgroundValue,
defaultBackgroundName: backgroundsParameter?.default,
};
};

export class BackgroundSelector extends Component<Props> {
change = ({ selected, name }: GlobalState) => {
const { api } = this.props;
if (typeof selected === 'string') {
api.setAddonState<string>(BACKGROUNDS_PARAM_KEY, selected);
}
api.emit(EVENTS.UPDATE, { selected, name });
};

render() {
return (
<Consumer filter={getBackgroundsConfig}>
{({
disable,
backgrounds,
selectedBackground,
defaultBackgroundName,
}: BackgroundsConfig) => {
if (disable) {
return null;
}
const onBackgroundChange = useCallback(
(value: string) => {
updateGlobals({ [BACKGROUNDS_PARAM_KEY]: { ...globals[BACKGROUNDS_PARAM_KEY], value } });
},
[backgroundsConfig, globals, updateGlobals]
);

const selectedBackgroundColor = getSelectedBackgroundColor(
backgrounds,
selectedBackground,
defaultBackgroundName
);
if (backgroundsConfig.disable) {
return null;
}

return (
<Fragment>
<WithTooltip
placement="top"
trigger="click"
closeOnClick
tooltip={({ onHide }) => {
return (
<Fragment>
{selectedBackgroundColor ? (
<Global
styles={(theme: Theme) => ({
[`#${iframeId}`]: {
background:
selectedBackgroundColor === 'transparent'
? theme.background.content
: selectedBackgroundColor,
},
})}
/>
) : null}
<WithTooltip
placement="top"
trigger="click"
closeOnClick
tooltip={({ onHide }) => (
<TooltipLinkList
links={getDisplayedItems(backgrounds, selectedBackgroundColor, (i) => {
this.change(i);
onHide();
})}
/>
)}
>
<IconButton
key="background"
title="Change the background of the preview"
active={selectedBackgroundColor !== 'transparent'}
>
<Icons icon="photo" />
</IconButton>
</WithTooltip>
</Fragment>
<TooltipLinkList
links={getDisplayedItems(
backgroundsConfig.values,
selectedBackgroundColor,
({ selected }: GlobalState) => {
if (selectedBackgroundColor !== selected) {
onBackgroundChange(selected);
}
onHide();
}
)}
/>
);
}}
</Consumer>
);
}
}
>
<IconButton
key="background"
title="Change the background of the preview"
active={selectedBackgroundColor !== 'transparent'}
>
<Icons icon="photo" />
</IconButton>
</WithTooltip>
</Fragment>
);
});

0 comments on commit eca16d9

Please sign in to comment.