Skip to content

Commit

Permalink
Improving storybook for current widgets and in general (mattermost#3736)
Browse files Browse the repository at this point in the history
* Improving storybook components information

* Adding a mattermost theme

* Moving menu_item_blockable_link outside of the widgets

* Upgrade to storybook 5.2.0

* Upgrade to storybook 5.2.1
  • Loading branch information
jespino authored Sep 30, 2019
1 parent 4fb96e5 commit ebb3b0b
Show file tree
Hide file tree
Showing 32 changed files with 1,468 additions and 687 deletions.
11 changes: 8 additions & 3 deletions .storybook/config.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import { configure } from '@storybook/react';
import {configure, addParameters} from '@storybook/react';
import theme from './theme.js';

// automatically import all files ending in *.stories.js
const reqComponents = require.context('../components', true, /\.stories\.js$/);
const reqCommon = require.context('../storybook', true, /\.stories\.js$/);
function loadStories() {
reqComponents.keys().forEach(filename => reqComponents(filename));
reqCommon.keys().forEach(filename => reqCommon(filename));
reqComponents.keys().forEach((filename) => reqComponents(filename));
reqCommon.keys().forEach((filename) => reqCommon(filename));
}

addParameters({
options: {theme},
});

configure(loadStories, module);
37 changes: 37 additions & 0 deletions .storybook/theme.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import {create} from '@storybook/theming';

export default create({
base: 'light',

colorPrimary: '#166de0',
colorSecondary: 'rgb(35, 137, 215)',

// UI
appBg: 'white',
appContentBg: 'white',
appBorderColor: 'rgba(0, 0, 0, .15)',
appBorderRadius: 4,

// Typography
fontBase: '"Open Sans", sans-serif',
fontCode: 'monospace',

// Text colors
textColor: 'black',
textInverseColor: 'rgba(255,255,255,0.9)',

// Toolbar default and active colors
barTextColor: 'rgb(215, 215, 215);',
barSelectedColor: 'white',
barBg: '#166de0',

// Form colors
inputBg: 'white',
inputBorder: 'silver',
inputTextColor: 'black',
inputBorderRadius: 4,

brandTitle: 'Mattermost',
brandUrl: 'https://mattermost.org',
brandImage: 'https://www.mattermost.org/wp-content/uploads/2016/03/logoHorizontal.png',
});
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import AboutBuildModal from 'components/about_build_modal';

import Menu from 'components/widgets/menu/menu';

import MenuItemBlockableLink from './menu_item_blockable_link';

export default class AdminNavbarDropdown extends React.Component {
static propTypes = {
locale: PropTypes.string.isRequired,
Expand Down Expand Up @@ -50,7 +52,7 @@ export default class AdminNavbarDropdown extends React.Component {

for (const team of teamsArray) {
teamToRender.push(
<Menu.ItemBlockableLink
<MenuItemBlockableLink
key={'team_' + team.name}
to={'/' + team.name}
text={Utils.localizeMessage('navbar_dropdown.switchTo', 'Switch to ') + ' ' + team.display_name}
Expand All @@ -59,7 +61,7 @@ export default class AdminNavbarDropdown extends React.Component {
}
} else {
switchTeams = (
<Menu.ItemBlockableLink
<MenuItemBlockableLink
to={'/select_team'}
icon={
<FormattedMessage
Expand Down
140 changes: 140 additions & 0 deletions components/widgets/admin_console/admin_console.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

import React, {useState} from 'react';

import {storiesOf} from '@storybook/react';
import {withKnobs, text, boolean} from '@storybook/addon-knobs';
import {action} from '@storybook/addon-actions';

import AdminPanel from './admin_panel';
import AdminPanelTogglable from './admin_panel_togglable';
import AdminPanelWithButton from './admin_panel_with_button';
import AdminPanelWithLink from './admin_panel_with_link';
import AdminHeader from './admin_header';
import FormattedAdminHeader from './formatted_admin_header';

storiesOf('Admin Console', module).
addDecorator(withKnobs).
add(
'admin panel',
() => {
const content = text('Content', 'Content');
const onHeaderClick = action('clicked header');
const onButtonClick = action('clicked button');
const title = text('Title', 'title');
const subtitle = text('Subtitle', 'subtitle');
const hasButton = boolean('HasButton', false);
return (
<AdminPanel
onHeaderClick={onHeaderClick}
titleId='not-valid-id'
titleDefault={title}
subtitleId='not-valid-id'
subtitleDefault={subtitle}
button={hasButton ? <button onClick={onButtonClick}>{'Button'}</button> : null}
>
{content}
</AdminPanel>
);
}
).
add(
'admin panel with button',
() => {
const content = text('Content', 'Content');
const onHeaderClick = action('clicked header');
const onButtonClick = action('clicked button');
const title = text('Title', 'title');
const subtitle = text('Subtitle', 'subtitle');
const buttonText = text('Button text', 'Button');
const disabled = boolean('Button disabled', false);
return (
<AdminPanelWithButton
onHeaderClick={onHeaderClick}
titleId='not-valid-id'
titleDefault={title}
subtitleId='not-valid-id'
subtitleDefault={subtitle}
buttonTextId='not-valid-id'
buttonTextDefault={buttonText}
onButtonClick={onButtonClick}
disabled={disabled}
>
{content}
</AdminPanelWithButton>
);
}
).
add(
'admin panel with link',
() => {
const content = text('Content', 'Content');
const onHeaderClick = action('clicked header');
const title = text('Title', 'title');
const subtitle = text('Subtitle', 'subtitle');
const linkText = text('Link text', 'Home');
const url = text('Link url', '/');
const disabled = boolean('Button disabled', false);
return (
<AdminPanelWithLink
onHeaderClick={onHeaderClick}
titleId='not-valid-id'
titleDefault={title}
subtitleId='not-valid-id'
subtitleDefault={subtitle}
linkTextId='not-valid-id'
linkTextDefault={linkText}
url={url}
disabled={disabled}
>
{content}
</AdminPanelWithLink>
);
}
).
add(
'togglable admin panel',
() => {
const content = text('Content', 'Content');
const title = text('Title', 'title');
const subtitle = text('Subtitle', 'subtitle');
const Wrapper = () => {
const [open, setOpen] = useState(true);
return (
<AdminPanelTogglable
titleId='not-valid-id'
titleDefault={title}
subtitleId='not-valid-id'
subtitleDefault={subtitle}
open={open}
onToggle={() => setOpen(!open)}
>
{content}
</AdminPanelTogglable>
);
};
return <Wrapper/>;
}
).
add(
'admin header',
() => {
const title = text('Title', 'title');
return (
<AdminHeader>{title}</AdminHeader>
);
}
).
add(
'formatted admin header',
() => {
const markdown = text('Markdown', '**Markdown** text');
return (
<FormattedAdminHeader
id='not-valid-id'
defaultMessage={markdown}
/>
);
}
);
2 changes: 1 addition & 1 deletion components/widgets/badges/badges.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ storiesOf('Badges', module).
add(
'regular badge',
() => {
const content = text('Text', 'Text');
const content = text('Text', 'BADGE');
return (<Badge show={boolean('Show', true)}>{content}</Badge>);
}
).
Expand Down
107 changes: 107 additions & 0 deletions components/widgets/inputs/inputs.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

import React, {useState} from 'react';

import {storiesOf} from '@storybook/react';
import {withKnobs, text} from '@storybook/addon-knobs';

import ChannelsInput from './channels_input';
import UsersEmailsInput from './users_emails_input';

storiesOf('Inputs', module).
addDecorator(withKnobs).
add(
'channels input',
() => {
const WrapperComponent = () => {
const placeholder = text('Placeholder', 'Placeholder');
const loadingMessageDefault = text('Loading Message', 'Loading');
const noOptionsMessageDefault = text('No Options Message', 'No channels found');
const options = [
{id: '1', name: 'public', display_name: 'Public Channel', type: 'O'},
{id: '2', name: 'private', display_name: 'Private Channel', type: 'P'},
{id: '3', name: 'town-square', display_name: 'Town Square', type: 'O'},
{id: '4', name: 'off-topic', display_name: 'Off Topic', type: 'O'},
];

const [value, setValue] = useState([]);
const [inputValue, setInputValue] = useState('');

const channelsLoader = (input, callback) => {
const values = options.filter((channel) => channel.display_name.toLowerCase().startsWith(input.toLowerCase()));
setTimeout(() => callback(values), 500);
};

return (
<ChannelsInput
channelsLoader={channelsLoader}
placeholder={placeholder}
onChange={setValue}
value={value}
onInputChange={setInputValue}
inputValue={inputValue}
loadingMessageDefault={loadingMessageDefault}
loadingMessageId='not-existing-id'
noOptionsMessageDefault={noOptionsMessageDefault}
noOptionsMessageId='not-existing-id'
/>
);
};
return (
<WrapperComponent/>
);
}
).
add(
'users emails input',
() => {
const WrapperComponent = () => {
const placeholder = text('Placeholder', 'Placeholder');
const loadingMessageDefault = text('Loading Message', 'Loading');
const noMatchMessageDefault = text('No Match Message', 'No one found matching **{text}**, type email address');
const validAddressMessageDefault = text('Valid Address', 'Add **{email}**');
const options = [
{id: '1', username: 'jesus.espino', first_name: 'Jesús', last_name: 'Espino', nickname: 'jespino'},
{id: '2', username: 'jora.wilander', first_name: 'Joram', last_name: 'Wilander', nickname: 'jwilander'},
{id: '3', username: 'ben.schumaher', first_name: 'Ben', last_name: 'Schumacher', nickname: 'Hanzei'},
{id: '4', username: 'martin.kraft', first_name: 'Martin', last_name: 'Kraft', nickname: 'mkraft'},
];

const [value, setValue] = useState([]);
const [inputValue, setInputValue] = useState('');

const usersLoader = (input, callback) => {
const values = options.filter((user) => {
return (
user.first_name.toLowerCase().startsWith(input.toLowerCase()) ||
user.last_name.toLowerCase().startsWith(input.toLowerCase()) ||
user.username.toLowerCase().startsWith(input.toLowerCase()) ||
user.nickname.toLowerCase().startsWith(input.toLowerCase())
);
});
setTimeout(() => callback(values), 500);
};

return (
<UsersEmailsInput
usersLoader={usersLoader}
placeholder={placeholder}
onChange={setValue}
value={value}
onInputChange={setInputValue}
inputValue={inputValue}
loadingMessageDefault={loadingMessageDefault}
loadingMessageId='not-existing-id'
noMatchMessageDefault={noMatchMessageDefault}
noMatchMessageId='not-existing-id'
validAddressMessageDefault={validAddressMessageDefault}
validAddressMessageId='not-existing-id'
/>
);
};
return (
<WrapperComponent/>
);
}
);
31 changes: 6 additions & 25 deletions components/widgets/loading/loading.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,11 @@ import LoadingWrapper from './loading_wrapper';

storiesOf('Loading', module).
addDecorator(withKnobs).
add('loadingSpinner',
() => {
return (
<div>
<h2>{'Loading spinner without text'}</h2>
<LoadingSpinner/>
<h2>{'Loading spinner without text'}</h2>
<LoadingSpinner text={text('Text', 'Loading')}/>
</div>
);
},
{
notes: {markdown: 'Loading spinner'},
}
add('LoadingSpinner without text',
() => (<LoadingSpinner/>),
).
add('LoadingSpinner with text',
() => <LoadingSpinner text={text('Text', 'Loading')}/>
).
add('loadingWrapper', () => {
const LoadingExample = () => {
Expand All @@ -51,15 +42,5 @@ storiesOf('Loading', module).
);
};

const content = text('Content', 'Content');
return (
<div>
<h2>{'Wrapped contend not loading'}</h2>
<LoadingWrapper loading={false}>{content}</LoadingWrapper>
<h2>{'Wrapped contend loading'}</h2>
<LoadingWrapper loading={true}>{content}</LoadingWrapper>
<h2>{'Sample'}</h2>
<LoadingExample/>
</div>
);
return (<LoadingExample/>);
});
Loading

0 comments on commit ebb3b0b

Please sign in to comment.