Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Addon-backgrounds: Add docs support and extend grid configuration #12368

Merged
merged 17 commits into from Sep 25, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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 });

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yannbf sorry to disturb you, but I can't understand why emitting of EVENTS.UPDATE was removed?

In my case I want to run some code when changing background. Previously I used the event introduced in #6561

Maybe you know is there any other way to track background change with code?

Thanks for you answer in advance!

};

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>
);
});