Unverified Commit 7c7c8e92 authored by Marcos Iglesias's avatar Marcos Iglesias Committed by GitHub

chore: Updating Storybook with Cards, Colors and Typography (#620)

Signed-off-by: 's avatarMarcos Iglesias Valle <golodhros@gmail.com>
parent 8b9ce930
...@@ -6,6 +6,9 @@ ...@@ -6,6 +6,9 @@
$icon-size: 24px; $icon-size: 24px;
$icon-small-size: 16px; $icon-small-size: 16px;
// Icons
// Lookout! When you update one of these, please update the enums on
// /static/js/interfaces/Enums.ts
// Map of Database names and icon paths // Map of Database names and icon paths
$data-stores: ( $data-stores: (
database: '/static/images/icons/Database.svg', database: '/static/images/icons/Database.svg',
......
...@@ -4,28 +4,23 @@ ...@@ -4,28 +4,23 @@
import * as React from 'react'; import * as React from 'react';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { RouteComponentProps } from 'react-router'; import { RouteComponentProps } from 'react-router';
import * as qs from 'simple-query-string';
import * as ReactMarkdown from 'react-markdown'; import * as ReactMarkdown from 'react-markdown';
import AvatarLabel from 'components/common/AvatarLabel'; import { getDashboard } from 'ducks/dashboard/reducer';
import { GetDashboardRequest } from 'ducks/dashboard/types';
import { GlobalState } from 'ducks/rootReducer';
import { logClick } from 'ducks/utilMethods';
import Breadcrumb from 'components/common/Breadcrumb'; import Breadcrumb from 'components/common/Breadcrumb';
import BookmarkIcon from 'components/common/Bookmark/BookmarkIcon'; import BookmarkIcon from 'components/common/Bookmark/BookmarkIcon';
import EditableSection from 'components/common/EditableSection'; import EditableSection from 'components/common/EditableSection';
import LoadingSpinner from 'components/common/LoadingSpinner'; import LoadingSpinner from 'components/common/LoadingSpinner';
import TabsComponent from 'components/common/TabsComponent'; import TabsComponent from 'components/common/TabsComponent';
import { getDashboard } from 'ducks/dashboard/reducer';
import { GetDashboardRequest } from 'ducks/dashboard/types';
import { GlobalState } from 'ducks/rootReducer';
import { logClick } from 'ducks/utilMethods';
import { DashboardMetadata } from 'interfaces/Dashboard';
import DashboardOwnerEditor from 'components/DashboardPage/DashboardOwnerEditor'; import DashboardOwnerEditor from 'components/DashboardPage/DashboardOwnerEditor';
import QueryList from 'components/DashboardPage/QueryList'; import QueryList from 'components/DashboardPage/QueryList';
import ChartList from 'components/DashboardPage/ChartList'; import ChartList from 'components/DashboardPage/ChartList';
import ResourceStatusMarker from 'components/common/ResourceStatusMarker'; import ResourceStatusMarker from 'components/common/ResourceStatusMarker';
import { formatDateTimeShort } from 'utils/dateUtils';
import ResourceList from 'components/common/ResourceList'; import ResourceList from 'components/common/ResourceList';
import { import {
ADD_DESC_TEXT, ADD_DESC_TEXT,
...@@ -37,14 +32,15 @@ import { ...@@ -37,14 +32,15 @@ import {
STATUS_TEXT, STATUS_TEXT,
} from 'components/DashboardPage/constants'; } from 'components/DashboardPage/constants';
import TagInput from 'components/common/Tags/TagInput'; import TagInput from 'components/common/Tags/TagInput';
import { ResourceType } from 'interfaces'; import { NO_TIMESTAMP_TEXT } from 'components/constants';
import { getSourceDisplayName, getSourceIconClass } from 'config/config-utils'; import { getSourceDisplayName, getSourceIconClass } from 'config/config-utils';
import { BadgeStyle } from 'config/config-types'; import { formatDateTimeShort } from 'utils/dateUtils';
import { getLoggingParams } from 'utils/logUtils'; import { getLoggingParams } from 'utils/logUtils';
import { NO_TIMESTAMP_TEXT } from 'components/constants'; import { ResourceType } from 'interfaces';
import { DashboardMetadata } from 'interfaces/Dashboard';
import ImagePreview from './ImagePreview'; import ImagePreview from './ImagePreview';
import './styles.scss'; import './styles.scss';
......
...@@ -3,9 +3,6 @@ ...@@ -3,9 +3,6 @@
import * as React from 'react'; import * as React from 'react';
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import FlashMessage from 'components/common/FlashMessage';
import globalState from 'fixtures/globalState'; import globalState from 'fixtures/globalState';
import { import {
NotificationType, NotificationType,
......
...@@ -16,12 +16,12 @@ import { ...@@ -16,12 +16,12 @@ import {
} from 'interfaces'; } from 'interfaces';
import FlashMessage from 'components/common/FlashMessage'; import FlashMessage from 'components/common/FlashMessage';
import { ImageIconType } from 'interfaces/Enums';
import { GlobalState } from 'ducks/rootReducer'; import { GlobalState } from 'ducks/rootReducer';
import { import {
CloseRequestAction, CloseRequestAction,
OpenRequestAction,
SubmitNotificationRequest, SubmitNotificationRequest,
} from 'ducks/notification/types'; } from 'ducks/notification/types';
import { import {
...@@ -84,7 +84,7 @@ export class RequestMetadataForm extends React.Component< ...@@ -84,7 +84,7 @@ export class RequestMetadataForm extends React.Component<
renderFlashMessage = () => { renderFlashMessage = () => {
return ( return (
<FlashMessage <FlashMessage
iconClass="icon-mail" iconClass={ImageIconType.MAIL}
message={this.getFlashMessageString()} message={this.getFlashMessageString()}
onClose={this.closeDialog} onClose={this.closeDialog}
/> />
......
import React from 'react';
import { storiesOf } from '@storybook/react';
import StorySection from '../StorySection';
import Card from '.';
const stories = storiesOf('Components/Cards', module);
stories.add('Cards', () => (
<>
<StorySection title="Loading Card">
<Card isLoading />
</StorySection>
<StorySection title="Basic Card">
<Card
title="Card Title"
copy="Lorem, ipsum dolor sit amet consectetur adipisicing elit. Minima autem dolorum incidunt quaerat perspiciatis! Totam est, ab molestiae magnam quisquam eligendi enim eum, iste excepturi mollitia laboriosam cumque, vitae reiciendis."
/>
</StorySection>
<StorySection title="Full Card">
<Card
title="Card Title"
subtitle="Card Subtitle"
copy="Lorem, ipsum dolor sit amet consectetur adipisicing elit. Minima autem dolorum incidunt quaerat perspiciatis! Totam est, ab molestiae magnam quisquam eligendi enim eum, iste excepturi mollitia laboriosam cumque, vitae reiciendis."
/>
</StorySection>
</>
));
import React from 'react';
import { storiesOf } from '@storybook/react';
import { ImageIconType } from 'interfaces/Enums';
import StorySection from '../StorySection';
import FlashMessage from '.';
const stories = storiesOf('Components/Flash Message', module);
stories.add('Flash Message', () => (
<>
<StorySection title="Flash Message">
<FlashMessage
message="Flash message text that can be short"
onClose={() => {
alert('message closed!');
}}
/>
</StorySection>
<StorySection title="Flash Message with Icon">
<FlashMessage
message="Flash message text that can be short"
iconClass={ImageIconType.ALERT}
onClose={() => {
alert('message closed!');
}}
/>
</StorySection>
</>
));
...@@ -5,6 +5,7 @@ import * as React from 'react'; ...@@ -5,6 +5,7 @@ import * as React from 'react';
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import { ImageIconType } from 'interfaces/Enums';
import FlashMessage, { FlashMessageProps } from '.'; import FlashMessage, { FlashMessageProps } from '.';
describe('FlashMessage', () => { describe('FlashMessage', () => {
...@@ -22,20 +23,23 @@ describe('FlashMessage', () => { ...@@ -22,20 +23,23 @@ describe('FlashMessage', () => {
describe('render', () => { describe('render', () => {
let props: FlashMessageProps; let props: FlashMessageProps;
let wrapper; let wrapper;
beforeAll(() => { beforeAll(() => {
const setupResult = setup(); ({ props, wrapper } = setup());
props = setupResult.props;
wrapper = setupResult.wrapper;
}); });
describe('iconClass logic', () => { describe('iconClass logic', () => {
it('if no iconClass, does not render img', () => { it('if no iconClass, does not render img', () => {
expect(wrapper.find('img').exists()).toBe(false); const expected = 0;
const actual = wrapper.find('img').length;
expect(actual).toEqual(expected);
}); });
it('if iconClass, renders img with correct className', () => { it('if iconClass, renders img with correct className', () => {
const testClass = 'icon-mail'; const testClass = ImageIconType.MAIL;
const { wrapper } = setup({ iconClass: testClass }); const { wrapper } = setup({ iconClass: testClass });
expect(wrapper.find('img').props()).toMatchObject({ expect(wrapper.find('img').props()).toMatchObject({
className: `icon ${testClass}`, className: `icon ${testClass}`,
}); });
...@@ -43,13 +47,17 @@ describe('FlashMessage', () => { ...@@ -43,13 +47,17 @@ describe('FlashMessage', () => {
}); });
it('renders correct message text', () => { it('renders correct message text', () => {
expect(wrapper.find('div.message').text()).toBe(props.message); const expected = props.message;
const actual = wrapper.find('.message').text();
expect(actual).toEqual(expected);
}); });
it('renders correct button', () => { it('renders correct button', () => {
expect(wrapper.find('button.btn.btn-close').props().onClick).toBe( const expected = props.onClose;
props.onClose const actual = wrapper.find('button.btn.btn-close').props().onClick;
);
expect(actual).toEqual(expected);
}); });
}); });
}); });
...@@ -2,12 +2,13 @@ ...@@ -2,12 +2,13 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
import * as React from 'react'; import * as React from 'react';
import { ImageIconType } from 'interfaces/Enums';
import * as Constants from './constants'; import * as Constants from './constants';
import './styles.scss'; import './styles.scss';
export interface FlashMessageProps { export interface FlashMessageProps {
iconClass?: string | null; iconClass?: ImageIconType | null;
message: string; message: string;
onClose: (event: React.MouseEvent<HTMLButtonElement>) => void; onClose: (event: React.MouseEvent<HTMLButtonElement>) => void;
} }
...@@ -19,8 +20,10 @@ const FlashMessage: React.FC<FlashMessageProps> = ({ ...@@ -19,8 +20,10 @@ const FlashMessage: React.FC<FlashMessageProps> = ({
}: FlashMessageProps) => { }: FlashMessageProps) => {
return ( return (
<div className="flash-message"> <div className="flash-message">
<div>
{iconClass && <img className={`icon ${iconClass}`} alt="" />} {iconClass && <img className={`icon ${iconClass}`} alt="" />}
<div className="message">{message}</div> <p className="message">{message}</p>
</div>
<button type="button" className="btn btn-close" onClick={onClose}> <button type="button" className="btn btn-close" onClick={onClose}>
<span className="sr-only">{Constants.CLOSE}</span> <span className="sr-only">{Constants.CLOSE}</span>
</button> </button>
......
...@@ -2,22 +2,35 @@ ...@@ -2,22 +2,35 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
@import 'variables'; @import 'variables';
@import 'typography';
$flash-message-height: 56px;
$flash-message-border-radius: 4px;
$flash-message-message-line-height: 24px;
.flash-message { .flash-message {
background-color: $body-bg-dark; background-color: $body-bg-dark;
border-radius: 6px; border-radius: $flash-message-border-radius;
color: $white; color: $white;
display: flex; display: flex;
height: 56px; height: $flash-message-height;
padding: $spacer-2 $spacer-1; padding: $spacer-2;
padding-right: $spacer-1 * 1.5;
justify-content: space-between;
.message { .message {
line-height: 24px; @extend %text-body-w3;
margin-right: $spacer-2;
line-height: $flash-message-message-line-height;
margin: 0;
display: inline;
}
.icon {
margin: 0 $spacer-1 0 0;
} }
.icon,
.btn-close { .btn-close {
margin: auto $spacer-1; margin: auto 0 auto $spacer-3;
} }
} }
import React, { ReactNode } from 'react';
type BlockProps = {
children: ReactNode;
title: string;
text?: string | ReactNode;
};
const StorySection: React.FC<BlockProps> = ({
children,
text,
title,
}: BlockProps) => (
<div style={{ padding: '2em', maxWidth: 600 }}>
<h1 className="text-headline-w1">{title}</h1>
{text && <p className="text-body-w1">{text}</p>}
{children}
</div>
);
export default StorySection;
...@@ -17,3 +17,47 @@ export enum SearchType { ...@@ -17,3 +17,47 @@ export enum SearchType {
PAGINATION = 'update_page', PAGINATION = 'update_page',
SUBMIT_TERM = 'submit_search_term', SUBMIT_TERM = 'submit_search_term',
} }
// Image-based Icon types from _icons.scss
export enum ImageIconType {
ALERT = 'icon-alert',
BOOKMARK = 'icon-bookmark',
BOOKMARK_FILLED = 'icon-bookmark-filled',
DELETE = 'icon-delete',
RED_TRIANGLE_WARNING = 'icon-red-triangle-warning',
DOWN = 'icon-down',
EDIT = 'icon-edit',
HELP = 'icon-help',
GITHUB = 'icon-github',
LEFT = 'icon-left',
LOADING = 'icon-loading',
MAIL = 'icon-mail',
PLUS = 'icon-plus',
PLUS_CIRCLE = 'icon-plus-circle',
PREVIEW = 'icon-preview',
REFRESH = 'icon-refresh',
RIGHT = 'icon-right',
SEARCH = 'icon-search',
SEND = 'icon-send',
SLACK = 'icon-slack',
UP = 'icon-up',
USER = 'icon-user',
MORE = 'icon-more',
}
// Icon types from _icons.scss
export enum IconType {
CHECK = 'icon-check',
USERS = 'icon-users',
DASHBOARD = 'icon-dashboard',
MODE = 'icon-mode',
REDASH = 'icon-redash',
TABLEAU = 'icon-tableau',
DATABASE = 'icon-database',
HIVE = 'icon-hive',
BIGQUERY = 'icon-bigquery',
DRUID = 'icon-druid',
PRESTO = 'icon-presto',
POSTGRES = 'icon-postgres',
REDSHIFT = 'icon-redshift',
}
import React from 'react';
import { storiesOf } from '@storybook/react';
import StorySection from '../components/common/StorySection';
const stories = storiesOf('Attributes/Colors', module);
const COLOR_BLOCK_SIZE = '80px';
type ColorBlockProps = {
color: string;
title: string;
};
const ColorBlock: React.FC<ColorBlockProps> = ({
color,
title,
}: ColorBlockProps) => (
<div style={{ margin: '20px' }}>
<h3 className="text-subtitle-w2">{title}</h3>
<div
style={{
width: COLOR_BLOCK_SIZE,
height: COLOR_BLOCK_SIZE,
backgroundColor: `${color}`,
borderRadius: '3px',
}}
/>
</div>
);
stories.add('Colors', () => (
<>
<StorySection title="Brand Colors">
<div style={{ display: 'flex' }}>
<ColorBlock title="$brand-color-1" color="#DCDCFF" />
<ColorBlock title="$brand-color-2" color="#BABAFF" />
<ColorBlock title="$brand-color-3" color="#8481FF" />
<ColorBlock title="$brand-color-4" color="#8481FF" />
<ColorBlock title="$brand-color-5" color="#523BE4" />
</div>
</StorySection>
<StorySection title="Text Colors">
<div style={{ display: 'flex' }}>
<ColorBlock title="$text-primary" color="#292936" />
<ColorBlock title="$text-secondary" color="#292936" />
<ColorBlock title="$text-tertiary" color="#9191A8" />
<ColorBlock title="$text-placeholder" color="#9191A8" />
<ColorBlock title="$text-inverse" color="#FFFFFF" />
</div>
</StorySection>
</>
));
import React from 'react'; import React, { ReactNode } from 'react';
import StorySection from '../components/common/StorySection';
export default { export default {
title: 'Attributes/Typography', title: 'Attributes/Typography',
}; };
export const TypographyUpdated = () => {
return (
<>
<StorySection title="Headlines">
<>
<h1 className="text-headline-w1">Heading with .text-headline-w1</h1>
<h1 className="text-headline-w2">Heading with .text-headline-w2</h1>
<h1 className="text-headline-w3">Heading with .text-headline-w3</h1>
</>
</StorySection>
<StorySection title="Titles">
<>
<h1 className="text-title-w1">Title with .text-title-w1</h1>
<h1 className="text-title-w2">Title with .text-title-w2</h1>
<h1 className="text-title-w3">Title with .text-title-w3</h1>
</>
</StorySection>
<StorySection title="Subtitles">
<>
<h1 className="text-subtitle-w1">Subtitle with .text-subtitle-w1</h1>
<h1 className="text-subtitle-w2">Subtitle with .text-subtitle-w2</h1>
<h1 className="text-subtitle-w3">Subtitle with .text-subtitle-w3</h1>
</>
</StorySection>
<StorySection title="Body Text">
<>
<p className="text-body-w1">Subtitle with .text-body-w1</p>
<p className="text-body-w2">Subtitle with .text-body-w2</p>
<p className="text-body-w3">Subtitle with .text-body-w3</p>
</>
</StorySection>
</>
);
};
TypographyUpdated.story = {
name: 'Typography',
};
export const Typography = () => { export const Typography = () => {
return ( return (
<> <>
<h1>Headings</h1> <StorySection title="Headings">
<hr /> <>
<h1>Raw h1</h1> <h1>Raw h1</h1>
<h2>Raw h2</h2> <h2>Raw h2</h2>
<h3>Raw h3</h3> <h3>Raw h3</h3>
...@@ -23,18 +64,9 @@ export const Typography = () => { ...@@ -23,18 +64,9 @@ export const Typography = () => {
<h1 className="subtitle-2">Heading with .subtitle-2</h1> <h1 className="subtitle-2">Heading with .subtitle-2</h1>
<h1 className="subtitle-3">Heading with .subtitle-3</h1> <h1 className="subtitle-3">Heading with .subtitle-3</h1>
</> </>
); </StorySection>
}; <StorySection title="Body Text">
Typography.story = {
name: 'Headings',
};
export const Body = () => {
return (
<> <>
<h1>Body</h1>
<hr />
<p>Raw p</p> <p>Raw p</p>
<hr /> <hr />
<p className="body-1">Paragraph with .body-1</p> <p className="body-1">Paragraph with .body-1</p>
...@@ -51,9 +83,11 @@ export const Body = () => { ...@@ -51,9 +83,11 @@ export const Body = () => {
<p className="text-secondary">Paragraph with .text-secondary</p> <p className="text-secondary">Paragraph with .text-secondary</p>
<p className="text-primary">Paragraph with .text-primary</p> <p className="text-primary">Paragraph with .text-primary</p>
</> </>
</StorySection>
</>
); );
}; };
Body.story = { Typography.story = {
name: 'Body Text', name: 'Deprecated: Headings & Body',
}; };
import React from 'react'; import React from 'react';
import { Welcome } from '@storybook/react/demo'; import { Welcome } from '@storybook/react/demo';
import StorySection from '../components/common/StorySection';
export default { export default {
title: 'Overview/Introduction', title: 'Overview/Introduction',
component: Welcome, component: Welcome,
...@@ -9,11 +11,15 @@ export default { ...@@ -9,11 +11,15 @@ export default {
export const Introduction = () => { export const Introduction = () => {
return ( return (
<> <>
<h1>Welcome to Amundsen's Component Library!</h1> <StorySection
<h3> title="Welcome to Amundsen's Component Library!"
text={
<h3 className="text-subtitle-w2">
A development area for developing new{' '} A development area for developing new{' '}
<strong>presentational components</strong> <strong>presentational components</strong>
</h3> </h3>
}
>
<p> <p>
Do you ever miss having a “workshop” to develop new components before Do you ever miss having a “workshop” to develop new components before
hooking them with the real data? Look no more, here is the place! hooking them with the real data? Look no more, here is the place!
...@@ -34,6 +40,7 @@ export const Introduction = () => { ...@@ -34,6 +40,7 @@ export const Introduction = () => {
</li> </li>
<li>Prototype something really quick to show around</li> <li>Prototype something really quick to show around</li>
</ul> </ul>
</StorySection>
</> </>
); );
}; };
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
"stylelint": "stylelint '**/*.scss'", "stylelint": "stylelint '**/*.scss'",
"stylelint:fix": "stylelint --fix '**/*.scss'", "stylelint:fix": "stylelint --fix '**/*.scss'",
"format": "prettier --loglevel warn --write \"**/*.{ts,tsx,css,scss}\"", "format": "prettier --loglevel warn --write \"**/*.{ts,tsx,css,scss}\"",
"storybook": "cross-env TS_NODE_PROJECT='tsconfig.webpack.json' start-storybook -p 6006", "storybook": "cross-env TS_NODE_PROJECT='tsconfig.webpack.json' start-storybook -s ../ -p 6006",
"build-storybook": "cross-env TS_NODE_PROJECT='tsconfig.webpack.json' build-storybook", "build-storybook": "cross-env TS_NODE_PROJECT='tsconfig.webpack.json' build-storybook",
"betterer": "betterer", "betterer": "betterer",
"betterer:update": "betterer --update" "betterer:update": "betterer --update"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment