Browse Source

feat(ui): Upgrade to storybook v6, add dark mode toggle (#23303)

Scott Cooper 4 years ago
parent
commit
c1d485b896

+ 0 - 0
.storybook/.babelrc → .storybook/.babelrc.js


+ 2 - 6
.storybook/main.js

@@ -4,13 +4,9 @@ module.exports = {
   stories: ['../docs-ui/components/*.stories.*'],
   addons: [
     {
-      name: '@storybook/addon-docs',
-      options: {configureJSX: true},
+      name: '@storybook/addon-essentials',
+      options: {},
     },
-    '@storybook/addon-storysource',
-    '@storybook/addon-knobs',
-    '@storybook/addon-actions',
     '@storybook/addon-a11y',
-    '@storybook/addon-options',
   ],
 };

+ 57 - 13
.storybook/preview.js

@@ -2,22 +2,28 @@ import 'focus-visible';
 import React from 'react';
 import {ThemeProvider} from 'emotion-theming';
 
-import {create} from '@storybook/theming/create';
+import {addParameters, addDecorator} from '@storybook/react';
 
-import {
-  addParameters,
-  configure,
-  setAddon,
-  getStorybook,
-  addDecorator,
-} from '@storybook/react';
-import {withInfo} from '@storybook/addon-info';
-import {setOptions} from '@storybook/addon-options';
-
-import theme from '../src/sentry/static/sentry/app/utils/theme';
+import theme, {darkTheme} from '../src/sentry/static/sentry/app/utils/theme';
+import GlobalStyles from '../src/sentry/static/sentry/app/styles/global';
 import '../docs-ui/index.js';
 
-const withTheme = storyFn => <ThemeProvider theme={theme}>{storyFn()}</ThemeProvider>;
+const withTheme = (Story, context) => {
+  const isDark = context.globals.theme === 'dark';
+  const currentTheme = isDark ? darkTheme : theme;
+
+  // Set @storybook/addon-backgrounds current color based on theme
+  if (context.globals.theme) {
+    context.globals.backgrounds = {value: currentTheme.bodyBackground};
+  }
+
+  return (
+    <ThemeProvider theme={currentTheme}>
+      <GlobalStyles isDark={isDark} theme={currentTheme} />
+      <Story {...context} />
+    </ThemeProvider>
+  );
+};
 
 addDecorator(withTheme);
 
@@ -88,3 +94,41 @@ addParameters({
     storySort: undefined,
   },
 });
+
+export const globalTypes = {
+  theme: {
+    name: 'Theme',
+    description: 'Global theme for components',
+    defaultValue: 'light',
+    toolbar: {
+      icon: 'circlehollow',
+      // array of plain string values or MenuItem shape (see below)
+      items: [
+        {value: 'light', icon: 'circlehollow', title: 'light'},
+        {value: 'dark', icon: 'circle', title: 'dark'},
+      ],
+    },
+  },
+};
+
+export const parameters = {
+  /**
+   * @storybook/addon-backgrounds background is controlled via theme
+   */
+  backgrounds: {
+    grid: {
+      disable: true,
+    },
+    default: 'light',
+    values: [
+      {
+        name: 'light',
+        value: theme.background,
+      },
+      {
+        name: 'dark',
+        value: darkTheme.background,
+      },
+    ],
+  },
+};

+ 6 - 6
docs-ui/.eslintrc.js

@@ -2,18 +2,18 @@ var path = require('path');
 
 module.exports = {
   parserOptions: {
-    sourceType: 'module'
+    sourceType: 'module',
   },
   env: {
     node: true,
-    es6: true
+    es6: true,
   },
   settings: {
     'import/resolver': {
       webpack: {
-        config: path.join(__dirname, '../.storybook/webpack.config.js')
-      }
+        config: path.join(__dirname, '../.storybook/webpack.config.js'),
+      },
     },
-    'import/extensions': ['.js', '.jsx']
-  }
+    'import/extensions': ['.js', '.jsx'],
+  },
 };

+ 83 - 34
docs-ui/components/_activity.stories.js

@@ -1,10 +1,8 @@
 import React from 'react';
-import {boolean, color} from '@storybook/addon-knobs';
-import {withInfo} from '@storybook/addon-info';
 
+import ActivityItem from 'app/components/activity/item';
 import ActivityAvatar from 'app/components/activity/item/avatar';
 import ActivityBubble from 'app/components/activity/item/bubble';
-import ActivityItem from 'app/components/activity/item';
 
 const user = {
   username: 'billy@sentry.io',
@@ -20,25 +18,32 @@ export default {
   title: 'Core/_Activity/Item',
 };
 
-export const DefaultActivityItem = withInfo(
-  'An Activity Item is composed of: an author, header, body, and additionally timestamp and a status.'
-)(() => (
+export const DefaultActivityItem = ({hideDate}) => (
   <ActivityItem
     author={{type: 'user', user}}
     item={{id: '123'}}
     date={new Date()}
     header={<div>{user.email}</div>}
-    hideDate={boolean('Hide Date', false)}
+    hideDate={hideDate}
   >
     Activity Item
   </ActivityItem>
-));
+);
 
-DefaultActivityItem.story = {
-  name: 'default ActivityItem',
+DefaultActivityItem.storyName = 'default ActivityItem';
+DefaultActivityItem.args = {
+  hideDate: false,
+};
+DefaultActivityItem.parameters = {
+  docs: {
+    description: {
+      story:
+        'An Activity Item is composed of: an author, header, body, and additionally timestamp and a status.',
+    },
+  },
 };
 
-export const WithCustomHeader = withInfo('Activity Item with a custom header')(() => (
+export const WithCustomHeader = ({hideDate}) => (
   <ActivityItem
     author={{type: 'user', user}}
     item={{id: '123'}}
@@ -46,64 +51,101 @@ export const WithCustomHeader = withInfo('Activity Item with a custom header')((
     header={() => (
       <div style={{backgroundColor: '#ccc'}}>Custom header (no timestamp)</div>
     )}
+    hideDate={hideDate}
   >
     Activity Item
   </ActivityItem>
-));
+);
 
-WithCustomHeader.story = {
-  name: 'with custom Header',
+WithCustomHeader.storyName = 'with custom Header';
+WithCustomHeader.args = {
+  hideDate: false,
+};
+WithCustomHeader.parameters = {
+  docs: {
+    description: {
+      story: 'Activity Item with a custom header',
+    },
+  },
 };
 
-export const WithFooter = withInfo('Activity Item with a footer')(() => (
+export const WithFooter = ({hideDate}) => (
   <ActivityItem
     author={{type: 'user', user}}
     item={{id: '123'}}
     date={new Date()}
-    hideDate={boolean('Hide Date', false)}
     header={<div>{user.email}</div>}
     footer={<div>Footer</div>}
+    hideDate={hideDate}
   >
     Activity Item
   </ActivityItem>
-));
+);
 
-WithFooter.story = {
-  name: 'with footer',
+WithFooter.storyName = 'with footer';
+WithFooter.args = {
+  hideDate: false,
+};
+WithFooter.parameters = {
+  docs: {
+    description: {
+      story: 'Activity Item with a footer',
+    },
+  },
 };
 
-export const SystemActivity = withInfo('An ActivityItem generated by Sentry')(() => (
+export const SystemActivity = ({hideDate}) => (
   <ActivityItem
     author={{type: 'system'}}
     item={{id: '123'}}
     date={new Date()}
     header={<div>Sentry detected something</div>}
-    hideDate={boolean('Hide Date', false)}
+    hideDate={hideDate}
   >
     Sentry did something
   </ActivityItem>
-));
+);
 
-SystemActivity.story = {
-  name: 'system activity',
+SystemActivity.storyName = 'system activity';
+SystemActivity.args = {
+  hideDate: false,
+};
+SystemActivity.parameters = {
+  docs: {
+    description: {
+      story: 'An ActivityItem generated by Sentry',
+    },
+  },
 };
 
-export const Bubble = withInfo(
-  'Activity bubble with arrow at the top-left. This should probably not be used directly unless creating a new component.'
-)(() => (
-  <ActivityBubble
-    backgroundColor={color('Background', '#fff')}
-    borderColor={color('Border', 'red')}
-  >
+export const Bubble = ({...args}) => (
+  <ActivityBubble {...args}>
     <div>Activity Bubble</div>
     <div>Activity Bubble</div>
     <div>Activity Bubble</div>
     <div>Activity Bubble</div>
     <div>Activity Bubble</div>
   </ActivityBubble>
-));
+);
+Bubble.component = ActivityBubble;
+Bubble.args = {
+  backgroundColor: '#fff',
+  borderColor: 'red',
+};
+Bubble.argTypes = {
+  backgroundColor: {control: 'color'},
+  borderColor: {control: 'color'},
+};
+Bubble.parameters = {
+  docs: {
+    description: {
+      story:
+        'Activity bubble with arrow at the top-left. This should probably not be used directly unless creating a new component.',
+    },
+  },
+};
 
-export const Avatar = withInfo('Avatar based on the author type.')(() => (
+export const Avatar = () => (
   <div>
     <h3>User</h3>
     <ActivityAvatar type="user" user={user} size={48} />
@@ -111,4 +153,11 @@ export const Avatar = withInfo('Avatar based on the author type.')(() => (
     <h3>System</h3>
     <ActivityAvatar type="system" size={48} />
   </div>
-));
+);
+Avatar.parameters = {
+  docs: {
+    description: {
+      story: 'Avatar based on the author type.',
+    },
+  },
+};

+ 36 - 14
docs-ui/components/alert.stories.js

@@ -1,17 +1,20 @@
 import React from 'react';
 import styled from '@emotion/styled';
-import {withInfo} from '@storybook/addon-info';
 
-import space from 'app/styles/space';
 import Alert from 'app/components/alert';
 import ExternalLink from 'app/components/links/externalLink';
-import {IconInfo, IconCheckmark, IconWarning, IconNot} from 'app/icons';
+import {IconCheckmark, IconInfo, IconNot, IconWarning} from 'app/icons';
+import space from 'app/styles/space';
 
 export default {
   title: 'Core/Alerts/Alert',
+  component: Alert,
+  parameters: {
+    controls: {hideNoControlsWarning: true},
+  },
 };
 
-export const Default = withInfo('Inline alert messages')(() => (
+export const Default = () => (
   <Grid>
     <Alert type="info">
       <ExternalLink href="#">Info message with a url</ExternalLink>
@@ -26,9 +29,16 @@ export const Default = withInfo('Inline alert messages')(() => (
       configuration or a serious backlog in tasks.
     </Alert>
   </Grid>
-));
+);
+Default.parameters = {
+  docs: {
+    description: {
+      story: 'Inline alert messages',
+    },
+  },
+};
 
-export const WithIcons = withInfo('Inline alert messages')(() => (
+export const WithIcons = () => (
   <Grid>
     <Alert type="info" icon={<IconInfo size="md" />}>
       <ExternalLink href="#">Info message with a url</ExternalLink>
@@ -47,15 +57,18 @@ export const WithIcons = withInfo('Inline alert messages')(() => (
       configuration or a serious backlog in tasks.
     </Alert>
   </Grid>
-));
-
-WithIcons.story = {
-  name: 'With icons',
+);
+
+WithIcons.storyName = 'With icons';
+WithIcons.parameters = {
+  docs: {
+    description: {
+      story: 'Inline alert messages',
+    },
+  },
 };
 
-export const System = withInfo(
-  'System-level alert messages that appear at the top of the viewport, or embedded in a panel'
-)(() => (
+export const System = () => (
   <Grid>
     <Alert type="info" system>
       <ExternalLink href="#">Info message with a url</ExternalLink>
@@ -74,7 +87,16 @@ export const System = withInfo(
       configuration or a serious backlog in tasks.
     </Alert>
   </Grid>
-));
+);
+
+System.parameters = {
+  docs: {
+    description: {
+      story:
+        'System-level alert messages that appear at the top of the viewport, or embedded in a panel',
+    },
+  },
+};
 
 const Grid = styled('div')`
   display: grid;

+ 27 - 15
docs-ui/components/alertLink.stories.js

@@ -1,5 +1,4 @@
 import React from 'react';
-import {withInfo} from '@storybook/addon-info';
 
 import AlertLink from 'app/components/alertLink';
 import {IconDocs, IconGeneric, IconMail, IconStack, IconStar} from 'app/icons';
@@ -8,9 +7,7 @@ export default {
   title: 'Core/Alerts/AlertLink',
 };
 
-export const Default = withInfo(
-  'A way to loudly link between different parts of the application'
-)(() => [
+export const Default = () => [
   <AlertLink to="/settings/account/notifications" key="1">
     Check out the notifications settings panel.
   </AlertLink>,
@@ -26,13 +23,18 @@ export const Default = withInfo(
   <AlertLink to="/settings/account/notifications" priority="muted" key="5">
     I am saying nothing, ok?
   </AlertLink>,
-]);
+];
 
-Default.story = {
-  name: 'default',
+Default.storyName = 'default';
+Default.parameters = {
+  docs: {
+    description: {
+      story: 'A way to loudly link between different parts of the application',
+    },
+  },
 };
 
-export const WithAnIcon = withInfo('You can optionally pass an icon src')(() => [
+export const WithAnIcon = () => [
   <AlertLink to="/settings/account/notifications" icon={<IconMail />} key="1">
     Gumbo beet greens corn soko endive gumbo gourd. Parsley shallot courgette tatsoi pea
     sprouts fava bean collard greens dandelion okra wakame tomato. Dandelion cucumber
@@ -70,13 +72,18 @@ export const WithAnIcon = withInfo('You can optionally pass an icon src')(() =>
   >
     I am saying nothing, ok?
   </AlertLink>,
-]);
+];
 
-WithAnIcon.story = {
-  name: 'with an icon',
+WithAnIcon.storyName = 'with an icon';
+WithAnIcon.parameters = {
+  docs: {
+    description: {
+      story: 'You can optionally pass an icon src',
+    },
+  },
 };
 
-export const Small = withInfo('You can optionally pass an icon src')(() => [
+export const Small = () => [
   <AlertLink to="/settings/account/notifications" size="small" key="1">
     Check out the notifications settings panel.
   </AlertLink>,
@@ -92,8 +99,13 @@ export const Small = withInfo('You can optionally pass an icon src')(() => [
   <AlertLink to="/settings/account/notifications" priority="muted" size="small" key="5">
     I am saying nothing, ok?
   </AlertLink>,
-]);
+];
 
-Small.story = {
-  name: 'small',
+Small.storyName = 'small';
+Small.parameters = {
+  docs: {
+    description: {
+      story: 'You can optionally pass an icon src',
+    },
+  },
 };

+ 13 - 5
docs-ui/components/areaChart.stories.js

@@ -1,13 +1,16 @@
 import React from 'react';
-import {withInfo} from '@storybook/addon-info';
 
 import AreaChart from 'app/components/charts/areaChart';
 
 export default {
   title: 'DataVisualization/Charts/AreaChart',
+  component: AreaChart,
+  parameters: {
+    controls: {hideNoControlsWarning: true},
+  },
 };
 
-export const _AreaChart = withInfo('Stacked AreaChart with previous period')(() => {
+export const _AreaChart = () => {
   const TOTAL = 6;
   const NOW = new Date().getTime();
   const getValue = () => Math.round(Math.random() * 1000);
@@ -37,8 +40,13 @@ export const _AreaChart = withInfo('Stacked AreaChart with previous period')(()
       />
     </div>
   );
-});
+};
 
-_AreaChart.story = {
-  name: 'AreaChart',
+_AreaChart.storyName = 'AreaChart';
+_AreaChart.parameters = {
+  docs: {
+    description: {
+      story: 'Stacked AreaChart with previous period',
+    },
+  },
 };

+ 13 - 3
docs-ui/components/autoComplete.stories.js

@@ -1,5 +1,4 @@
 import React from 'react';
-import {withInfo} from '@storybook/addon-info';
 
 import AutoComplete from 'app/components/autoComplete';
 
@@ -17,9 +16,12 @@ const items = [
 
 export default {
   title: 'Core/Forms/AutoComplete',
+  parameters: {
+    controls: {hideNoControlsWarning: true},
+  },
 };
 
-export const Input = withInfo('Autocomplete on an input')(() => (
+export const Input = () => (
   <AutoComplete itemToString={item => item.name}>
     {({
       getRootProps,
@@ -77,4 +79,12 @@ export const Input = withInfo('Autocomplete on an input')(() => (
       );
     }}
   </AutoComplete>
-));
+);
+
+Input.parameters = {
+  docs: {
+    description: {
+      story: 'Autocomplete on an input',
+    },
+  },
+};

+ 9 - 7
docs-ui/components/autoSelectText.stories.js

@@ -1,16 +1,18 @@
 import React from 'react';
-import {withInfo} from '@storybook/addon-info';
 
 import AutoSelectText from 'app/components/autoSelectText';
 
 export default {
   title: 'Utilities/AutoSelectText',
+  component: AutoSelectText,
 };
 
-export const Default = withInfo('Select text on click')(() => (
-  <AutoSelectText>Click to highlight text</AutoSelectText>
-));
-
-Default.story = {
-  name: 'default',
+export const Default = () => <AutoSelectText>Click to highlight text</AutoSelectText>;
+Default.storyName = 'AutoSelectText';
+Default.parameters = {
+  docs: {
+    description: {
+      story: 'Select text on click',
+    },
+  },
 };

Some files were not shown because too many files changed in this diff