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">
{iconClass && <img className={`icon ${iconClass}`} alt="" />} <div>
<div className="message">{message}</div> {iconClass && <img className={`icon ${iconClass}`} alt="" />}
<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 Typography = () => { export const TypographyUpdated = () => {
return ( return (
<> <>
<h1>Headings</h1> <StorySection title="Headlines">
<hr /> <>
<h1>Raw h1</h1> <h1 className="text-headline-w1">Heading with .text-headline-w1</h1>
<h2>Raw h2</h2> <h1 className="text-headline-w2">Heading with .text-headline-w2</h1>
<h3>Raw h3</h3> <h1 className="text-headline-w3">Heading with .text-headline-w3</h1>
<h4>Raw h4</h4> </>
<h5>Raw h5</h5> </StorySection>
<h6>Raw h6</h6> <StorySection title="Titles">
<hr /> <>
<h1 className="title-1">Heading with .title-1</h1> <h1 className="text-title-w1">Title with .text-title-w1</h1>
<h1 className="title-2">Heading with .title-2</h1> <h1 className="text-title-w2">Title with .text-title-w2</h1>
<h1 className="title-3">Heading with .title-3</h1> <h1 className="text-title-w3">Title with .text-title-w3</h1>
<h1 className="subtitle-1">Heading with .subtitle-1</h1> </>
<h1 className="subtitle-2">Heading with .subtitle-2</h1> </StorySection>
<h1 className="subtitle-3">Heading with .subtitle-3</h1> <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>
</> </>
); );
}; };
Typography.story = { TypographyUpdated.story = {
name: 'Headings', name: 'Typography',
}; };
export const Body = () => { export const Typography = () => {
return ( return (
<> <>
<h1>Body</h1> <StorySection title="Headings">
<hr /> <>
<p>Raw p</p> <h1>Raw h1</h1>
<hr /> <h2>Raw h2</h2>
<p className="body-1">Paragraph with .body-1</p> <h3>Raw h3</h3>
<p className="body-2">Paragraph with .body-2</p> <h4>Raw h4</h4>
<p className="body-3">Paragraph with .body-3</p> <h5>Raw h5</h5>
<p className="body-secondary-3">Paragraph with .body-secondary-3</p> <h6>Raw h6</h6>
<p className="body-placeholder">Paragraph with .body-placeholder</p> <hr />
<p className="body-link">Paragraph with .body-link</p> <h1 className="title-1">Heading with .title-1</h1>
<p className="caption">Paragraph with .caption</p> <h1 className="title-2">Heading with .title-2</h1>
<p className="column-name">Paragraph with .column-name</p> <h1 className="title-3">Heading with .title-3</h1>
<p className="resource-type">Paragraph with .resource-type</p> <h1 className="subtitle-1">Heading with .subtitle-1</h1>
<p className="helper-text">Paragraph with .helper-text</p> <h1 className="subtitle-2">Heading with .subtitle-2</h1>
<p className="text-placeholder">Paragraph with .text-placeholder</p> <h1 className="subtitle-3">Heading with .subtitle-3</h1>
<p className="text-secondary">Paragraph with .text-secondary</p> </>
<p className="text-primary">Paragraph with .text-primary</p> </StorySection>
<StorySection title="Body Text">
<>
<p>Raw p</p>
<hr />
<p className="body-1">Paragraph with .body-1</p>
<p className="body-2">Paragraph with .body-2</p>
<p className="body-3">Paragraph with .body-3</p>
<p className="body-secondary-3">Paragraph with .body-secondary-3</p>
<p className="body-placeholder">Paragraph with .body-placeholder</p>
<p className="body-link">Paragraph with .body-link</p>
<p className="caption">Paragraph with .caption</p>
<p className="column-name">Paragraph with .column-name</p>
<p className="resource-type">Paragraph with .resource-type</p>
<p className="helper-text">Paragraph with .helper-text</p>
<p className="text-placeholder">Paragraph with .text-placeholder</p>
<p className="text-secondary">Paragraph with .text-secondary</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,31 +11,36 @@ export default { ...@@ -9,31 +11,36 @@ 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!"
A development area for developing new{' '} text={
<strong>presentational components</strong> <h3 className="text-subtitle-w2">
</h3> A development area for developing new{' '}
<p> <strong>presentational components</strong>
Do you ever miss having a “workshop” to develop new components before </h3>
hooking them with the real data? Look no more, here is the place! }
</p> >
<p>In this environment you can:</p> <p>
<ul> Do you ever miss having a “workshop” to develop new components before
<li>Quickly develop new components for using them on Amundsen</li> hooking them with the real data? Look no more, here is the place!
<li>See what components are available already</li> </p>
<li>Be consistent with the Amundsen styling</li> <p>In this environment you can:</p>
<li>Create manual tests for your components</li> <ul>
<li> <li>Quickly develop new components for using them on Amundsen</li>
Avoid the whole syncing thing while developing your presentational <li>See what components are available already</li>
components <li>Be consistent with the Amundsen styling</li>
</li> <li>Create manual tests for your components</li>
<li> <li>
Clear the path to eventually move reusable components into the Data Avoid the whole syncing thing while developing your presentational
Product Language (DPL) components
</li> </li>
<li>Prototype something really quick to show around</li> <li>
</ul> Clear the path to eventually move reusable components into the Data
Product Language (DPL)
</li>
<li>Prototype something really quick to show around</li>
</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