Skip to content

Commit

Permalink
Chore: Bump storybook to v6 (grafana#28926)
Browse files Browse the repository at this point in the history
* Wip

* feat: get storybook and app building locally

* docs: comment webpack react alias

* build(grafana-ui): put back ts-loader

* build: prefer storybook essentials over actions and docs. bump dark-mode

* chore(storybook): migrate to latest config

* build: prevent test env throwing Invalid hook call errors

* chore: lodash resolves to package dependency rather than project

* use decorators as variable instead of function

* perf(storybook): reduce bundling time by splitting type check and compilation

* refactor(storybook): use sortOrder.order param to sort intro story first

* build: use yarn workspace command

* refactor(storybook): use previous knobs addon registration

* migrate button story to controls

* build(storybook): silence warnings in console

* build: bump storybook related ts packages

* style: remove trailing whitespace

* refactor(graphng): export interface for storybook

* controls migration guide

* fix typo

* docs(storybook): default docs to use dark theme as per current implementation

* revert(grafana-ui): put back react-is namedExport

this was changed for react 17 bump but causes rollup to fail during bundling

* chore: bump storybook to 6.1, enable fastRefresh, silence eslint prop-types

* docs(grafana-ui): move knobs -> controls migration guide to storybook style-guide

* chore(storybook): silence terminal warning about order of docs addon

* Update contribute/style-guides/storybook.md

Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>

* Apply documentation suggestions

Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>

* chore(storybook): bump to 6.1.2

Co-authored-by: Peter Holmberg <peter.hlmbrg@gmail.com>
Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
  • Loading branch information
3 people authored Nov 24, 2020
1 parent ce39e12 commit 0fc8426
Show file tree
Hide file tree
Showing 18 changed files with 3,737 additions and 3,268 deletions.
93 changes: 60 additions & 33 deletions contribute/style-guides/storybook.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,13 @@ To link a component’s stories with an MDX file you have to do this:
```jsx
// In TabsBar.story.tsx

import { TabsBar } from "./TabsBar";
import { TabsBar } from './TabsBar';

// Import the MDX file
import mdx from "./TabsBar.mdx";
import mdx from './TabsBar.mdx';

export default {
title: "General/Tabs/TabsBar",
title: 'General/Tabs/TabsBar',
component: TabsBar,
parameters: {
docs: {
Expand All @@ -93,8 +93,8 @@ There are some things that the MDX file should contain:
```jsx
// In MyComponent.mdx

import { Props } from "@storybook/addon-docs/blocks";
import { MyComponent } from "./MyComponent";
import { Props } from '@storybook/addon-docs/blocks';
import { MyComponent } from './MyComponent';

<Props of={MyComponent} />;
```
Expand Down Expand Up @@ -141,39 +141,66 @@ interface MyProps {
}
```

### Knobs
### Controls

Knobs is an [addon to Storybook](https://github.com/storybookjs/storybook/tree/master/addons/knobs) which can be used to easily switch values in the UI. A good use case for it is to try different props for the component. Using knobs is easy. Grafana is set up so knobs can be used straight out of the box. Here is an example of how you might use it.
The [controls addon](https://storybook.js.org/docs/react/essentials/controls) provides a way to interact with a component's properties dynamically and requires much less code than knobs. We're deprecating knobs in favor of using controls.

```jsx
// In MyComponent.story.tsx
#### Migrating a story from Knobs to Controls

import { number, text } from "@storybook/addon-knobs";
As a test, we migrated the [button story](https://github.com/grafana/grafana/blob/master/packages/grafana-ui/src/components/Button/Button.story.tsx). Here's the guide on how to migrate a story to controls.

export const basicStory = () => (
<MyComponent
max={number("Max value", 10)}
min={number("Min value", -10)}
title={text("Title", "Look at the value!")}
/>
);
```
1. Remove the `@storybook/addon-knobs` dependency.
2. Import the Story type from `@storybook/react`

`import { Story } from @storybook/react`

3. Import the props interface from the component you're working on (these must be exported in the component).

`import { Props } from './Component'`

4. Add the Story type to all stories in the file, then replace the props sent to the component
and remove any knobs.

Before

```tsx
export const Simple = () => {
const prop1 = text('Prop1', 'Example text');
const prop2 = select('Prop2', ['option1', 'option2'], 'option1');

return <Component prop1={prop1} prop2={prop2} />;
};
```

After

```tsx
export const Simple: Story<Props> = ({ prop1, prop2 }) => {
return <Component prop1={prop1} prop2={prop2} />;
};
```

5. Add default props (or args in Storybook language).

```tsx
Simple.args = {
prop1: 'Example text',
prop2: 'option 1',
};
```

6. If the component has advanced props type (ie. other than string, number, boolean), you need to
specify these in an `argTypes`. This is done in the default export of the story.

The general convention is that the first parameter of the knob is its name and the second is the default value. There are some more types:

| Knob | Description |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `text` | Any text field |
| `number` | Any number input. Also [available as range](https://github.com/storybookjs/storybook/tree/master/addons/knobs#number-bound-by-range) |
| `boolean` | A switch between true/false |
| `color` | Color picker |
| `object` | JSON input or array. Good to use if the property requires more complex data structures. |
| `array` | Array of strings separated by a comma |
| `select` | Select a value from an options object. Good for trying different test cases. |
| `options` | Configurable UI for selecting a range of options |
| `files` | File selector |
| `date` | Select date as stringified Unix timestamp |
| `button` | Has a handler which is called when clicked |
```tsx
export default {
title: 'Component/Component',
component: Component,
argTypes: {
prop2: { control: { type: 'select', options: ['option1', 'option2'] } },
},
};
```

## Best practices

Expand Down
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ module.exports = {
'\\.svg': '<rootDir>/public/test/mocks/svg.ts',
'\\.css': '<rootDir>/public/test/mocks/style.ts',
'monaco-editor/esm/vs/editor/editor.api': '<rootDir>/public/test/mocks/monaco.ts',
'^react($|/.+)': '<rootDir>/node_modules/react$1',
},
watchPathIgnorePatterns: ['<rootDir>/node_modules/'],
};
9 changes: 6 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@
"start:ignoreTheme": "grafana-toolkit core:start --hot",
"start:noTsCheck": "grafana-toolkit core:start --noTsCheck",
"stats": "webpack --mode production --config scripts/webpack/webpack.prod.js --profile --json > compilation-stats.json",
"storybook": "cd packages/grafana-ui && yarn storybook --ci",
"storybook:build": "cd packages/grafana-ui && yarn storybook:build",
"storybook": "yarn workspace @grafana/ui storybook --ci",
"storybook:build": "yarn workspace @grafana/ui storybook:build",
"themes:generate": "ts-node --project ./scripts/cli/tsconfig.json ./scripts/cli/generateSassVariableFiles.ts",
"typecheck": "tsc --noEmit",
"plugins:build-bundled": "grafana-toolkit plugin:bundle-managed",
Expand Down Expand Up @@ -308,7 +308,10 @@
],
"nohoist": [
"**/@types/*",
"**/@types/*/**"
"**/@types/*/**",
"@storybook",
"**/@storybook",
"**/@storybook/**"
]
},
"_moduleAliases": {
Expand Down
3 changes: 2 additions & 1 deletion packages/grafana-ui/.eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
{
"files": ["**/*.{test,story}.{ts,tsx}"],
"rules": {
"no-restricted-imports": "off"
"no-restricted-imports": "off",
"react/prop-types": "off"
}
}
]
Expand Down
137 changes: 136 additions & 1 deletion packages/grafana-ui/.storybook/main.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
const path = require('path');
const TerserPlugin = require('terser-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const FilterWarningsPlugin = require('webpack-filter-warnings-plugin');

const stories = ['../src/**/*.story.{js,jsx,ts,tsx,mdx}'];

if (process.env.NODE_ENV !== 'production') {
Expand All @@ -7,10 +12,140 @@ if (process.env.NODE_ENV !== 'production') {
module.exports = {
stories: stories,
addons: [
'@storybook/addon-docs',
'@storybook/addon-controls',
'@storybook/addon-knobs',
'@storybook/addon-actions',
'@storybook/addon-docs',
'storybook-dark-mode/register',
'@storybook/addon-storysource',
],
reactOptions: {
fastRefresh: true,
},
typescript: {
check: true,
reactDocgen: 'react-docgen-typescript',
reactDocgenTypescriptOptions: {
shouldExtractLiteralValuesFromEnum: true,
propFilter: (prop: any) => (prop.parent ? !/node_modules/.test(prop.parent.fileName) : true),
},
},
webpackFinal: async (config: any, { configType }: any) => {
const isProductionBuild = configType === 'PRODUCTION';
config.module.rules = [
...(config.module.rules || []),
{
test: /\.tsx?$/,
use: [
{
loader: require.resolve('ts-loader'),
options: {
transpileOnly: true,
configFile: path.resolve(__dirname, 'tsconfig.json'),
},
},
{
loader: require.resolve('react-docgen-typescript-loader'),
options: {
tsconfigPath: path.resolve(__dirname, 'tsconfig.json'),
// https://github.com/styleguidist/react-docgen-typescript#parseroptions
// @ts-ignore
propFilter: prop => {
if (prop.parent) {
return !prop.parent.fileName.includes('node_modules/@types/react/');
}

return true;
},
},
},
],
},
{
test: /\.scss$/,
use: [
{
loader: 'style-loader',
options: { injectType: 'lazyStyleTag' },
},
{
loader: 'css-loader',
options: {
importLoaders: 2,
},
},
{
loader: 'postcss-loader',
options: {
sourceMap: false,
config: { path: __dirname + '../../../../scripts/webpack/postcss.config.js' },
},
},
{
loader: 'sass-loader',
options: {
sourceMap: false,
},
},
],
},
{
test: require.resolve('jquery'),
use: [
{
loader: 'expose-loader',
query: 'jQuery',
},
{
loader: 'expose-loader',
query: '$',
},
],
},
];

config.optimization = {
nodeEnv: 'production',
moduleIds: 'hashed',
runtimeChunk: 'single',
splitChunks: {
chunks: 'all',
minChunks: 1,
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/].*[jt]sx?$/,
chunks: 'initial',
priority: -10,
reuseExistingChunk: true,
enforce: true,
},
default: {
priority: -20,
chunks: 'all',
test: /.*[jt]sx?$/,
reuseExistingChunk: true,
},
},
},
minimize: isProductionBuild,
minimizer: isProductionBuild
? [
new TerserPlugin({ cache: false, parallel: false, sourceMap: false, exclude: /monaco|bizcharts/ }),
new OptimizeCSSAssetsPlugin({}),
]
: [],
};

config.resolve.alias['@grafana/ui'] = path.resolve(__dirname, '..');

// Silence "export not found" webpack warnings with transpileOnly
// https://github.com/TypeStrong/ts-loader#transpileonly
config.plugins.push(
new FilterWarningsPlugin({
exclude: /export .* was not found in/,
})
);

return config;
},
};
37 changes: 18 additions & 19 deletions packages/grafana-ui/.storybook/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import lightTheme from '../../../public/sass/grafana.light.scss';
// @ts-ignore
import darkTheme from '../../../public/sass/grafana.dark.scss';
import { GrafanaLight, GrafanaDark } from './storybookTheme';
import { configure, addDecorator, addParameters } from '@storybook/react';
import { withKnobs } from '@storybook/addon-knobs';
import { configure } from '@storybook/react';
import addons from '@storybook/addons';

const handleThemeChange = (theme: any) => {
if (theme !== 'light') {
Expand All @@ -27,37 +27,36 @@ const handleThemeChange = (theme: any) => {
lightTheme.use();
}
};
addDecorator(withTheme(handleThemeChange));
addDecorator(withKnobs);
addDecorator(withPaddedStory);

addParameters({
addons.setConfig({
showRoots: false,
theme: GrafanaDark,
});

export const decorators = [withTheme(handleThemeChange), withPaddedStory];

export const parameters = {
info: {},
docs: {
theme: GrafanaDark,
},
darkMode: {
dark: GrafanaDark,
light: GrafanaLight,
},
options: {
theme: GrafanaDark,
showPanel: true,
showRoots: true,
panelPosition: 'right',
showNav: true,
isFullscreen: false,
isToolshown: true,
storySort: (a: any, b: any) => {
if (a[1].kind.split('/')[0] === 'Docs Overview') {
return -1;
} else if (b[1].kind.split('/')[0] === 'Docs Overview') {
return 1;
}
return a[1].id.localeCompare(b[1].id);
storySort: {
method: 'alphabetical',
// Order Docs Overview and Docs Overview/Intro story first
order: ['Docs Overview', ['Intro']],
},
},
knobs: {
escapeHTML: false,
},
});

// @ts-ignore
configure(require.context('../src', true, /\.story\.(js|jsx|ts|tsx|mdx)$/), module);
};
Loading

0 comments on commit 0fc8426

Please sign in to comment.