should render a simple Button correctly 1`] = `
57 |
63 | Submit
64 |
65 | `;
66 |
--------------------------------------------------------------------------------
/src/lib/components/Card/Card.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | class Card extends React.Component {
5 | constructor() {
6 | super();
7 | this.getClassString = this.getClassString.bind(this);
8 | this.renderHeader = this.renderHeader.bind(this);
9 | }
10 |
11 | getClassString() {
12 | let classString = 'p-card';
13 |
14 | if (this.props.highlighted) classString = `${classString} p-card--highlighted`;
15 | if (this.props.overlay) classString = `${classString} p-card--overlay`;
16 |
17 | return classString;
18 | }
19 |
20 | renderHeader() {
21 | const { header, image } = this.props;
22 |
23 | return (
24 |
25 | {image ? : {header} }
26 |
27 | );
28 | }
29 |
30 | render() {
31 | return (
32 |
33 | { (this.props.header || this.props.image) && this.renderHeader() }
34 | { this.props.title &&
35 |
{this.props.title}
36 | }
37 | { this.props.children }
38 |
39 | );
40 | }
41 | }
42 |
43 | Card.defaultProps = {
44 | header: null,
45 | highlighted: false,
46 | image: null,
47 | overlay: false,
48 | title: null,
49 | };
50 |
51 | Card.propTypes = {
52 | children: PropTypes.node.isRequired,
53 | header: PropTypes.string,
54 | highlighted: PropTypes.bool,
55 | image: PropTypes.shape({
56 | src: PropTypes.string,
57 | alt: PropTypes.string,
58 | }),
59 | overlay: PropTypes.bool,
60 | title: PropTypes.string,
61 | };
62 |
63 | Card.displayName = 'Card';
64 |
65 | export default Card;
66 |
--------------------------------------------------------------------------------
/src/lib/components/Card/Card.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { storiesOf } from '@storybook/react';
3 | import { text, boolean } from '@storybook/addon-knobs';
4 | import { withInfo } from '@storybook/addon-info';
5 |
6 | import Card from './Card';
7 | import Strip from '../Strip/Strip';
8 | import StripColumn from '../Strip/StripColumn';
9 | import StripRow from '../Strip/StripRow';
10 |
11 | storiesOf('Card', module)
12 | .add('Default',
13 | withInfo('The purpose of the basic card is to display information, without user interaction.')(() => (
14 |
20 | {text('Text', 'Lorem ipsum dolor sit amet, consectetur adipisicing.')}
21 | ),
22 | ),
23 | )
24 |
25 | .add('With Header',
26 | withInfo('Card components accept either a header or image prop for an optional header.')(() => (
27 |
33 | {text('Text', 'Lorem ipsum dolor sit amet, consectetur adipisicing.')}
34 | ),
35 | ),
36 | )
37 |
38 | .add('With Image',
39 | withInfo('Card components accept either a header or image prop for an optional header.')(() => (
40 |
49 | {text('Text', 'Lorem ipsum dolor sit amet, consectetur adipisicing.')}
50 | ),
51 | ),
52 | )
53 |
54 | .add('Highlighted',
55 | withInfo('The highlighted card should be used when you can interact with the content.')(() => (
56 |
65 | {text('Text', 'Lorem ipsum dolor sit amet, consectetur adipisicing.')}
66 | ),
67 | ),
68 | )
69 |
70 | .add('Overlay',
71 | withInfo('The purpose of the overlay prop is to make the text visible in conjunction with a Strip image component.')(() => (
72 |
77 |
78 |
79 |
80 |
89 | {text('Text', 'Lorem ipsum dolor sit amet, consectetur adipisicing.')}
90 |
91 |
92 |
93 | ),
94 | ),
95 | );
96 |
--------------------------------------------------------------------------------
/src/lib/components/Card/Card.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactTestRenderer from 'react-test-renderer';
3 | import Card from './Card';
4 |
5 | describe('Card component', () => {
6 | it('should render a basic Card correctly', () => {
7 | const card = ReactTestRenderer.create(
8 |
9 | Lorem ipsum dolor sit amet, consectetur adipisicing.
10 | );
11 | const json = card.toJSON();
12 | expect(json).toMatchSnapshot();
13 | });
14 |
15 | it('should render Card with an image correctly', () => {
16 | const card = ReactTestRenderer.create(
17 |
24 | Lorem ipsum dolor sit amet, consectetur adipisicing.
25 | );
26 | const json = card.toJSON();
27 | expect(json).toMatchSnapshot();
28 | });
29 |
30 | it('should render a highlighted Card correctly', () => {
31 | const card = ReactTestRenderer.create(
32 |
40 | Lorem ipsum dolor sit amet, consectetur adipisicing.
41 | );
42 | const json = card.toJSON();
43 | expect(json).toMatchSnapshot();
44 | });
45 |
46 | it('should render an overlay Card correctly', () => {
47 | const card = ReactTestRenderer.create(
48 |
56 | Lorem ipsum dolor sit amet, consectetur adipisicing.
57 | );
58 | const json = card.toJSON();
59 | expect(json).toMatchSnapshot();
60 | });
61 | });
62 |
--------------------------------------------------------------------------------
/src/lib/components/Card/__snapshots__/Card.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Card component should render Card with an image correctly 1`] = `
4 |
7 |
10 |
14 |
15 |
18 | Card title
19 |
20 |
21 | Lorem ipsum dolor sit amet, consectetur adipisicing.
22 |
23 |
24 | `;
25 |
26 | exports[`Card component should render a basic Card correctly 1`] = `
27 |
30 |
33 | Card title
34 |
35 |
36 | Lorem ipsum dolor sit amet, consectetur adipisicing.
37 |
38 |
39 | `;
40 |
41 | exports[`Card component should render a highlighted Card correctly 1`] = `
42 |
45 |
48 |
52 |
53 |
56 | Card title
57 |
58 |
59 | Lorem ipsum dolor sit amet, consectetur adipisicing.
60 |
61 |
62 | `;
63 |
64 | exports[`Card component should render an overlay Card correctly 1`] = `
65 |
68 |
71 |
75 |
76 |
79 | Card title
80 |
81 |
82 | Lorem ipsum dolor sit amet, consectetur adipisicing.
83 |
84 |
85 | `;
86 |
--------------------------------------------------------------------------------
/src/lib/components/CodeBlock/CodeBlock.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOMServer from 'react-dom/server';
3 | import PropTypes from 'prop-types';
4 | import dedent from 'dedent-js';
5 | import pretty from 'pretty';
6 |
7 | const CodeBlock = (props) => {
8 | const { numbered } = props;
9 | let code = props.children;
10 |
11 | if (typeof code === 'string') {
12 | code = dedent(code);
13 | } else {
14 | code = pretty(ReactDOMServer.renderToStaticMarkup(code), { ocd: true });
15 | }
16 |
17 | if (numbered) {
18 | const codeArray = code.split(/\r?\n/);
19 | code = codeArray.map((line, i) => (
20 | { codeArray[i] }
21 | ));
22 | }
23 |
24 | return (
25 |
26 |
27 | { code }
28 |
29 |
30 | );
31 | };
32 |
33 | CodeBlock.defaultProps = {
34 | children: '',
35 | numbered: false,
36 | };
37 |
38 | CodeBlock.propTypes = {
39 | children: PropTypes.node,
40 | numbered: PropTypes.bool,
41 | };
42 |
43 | CodeBlock.displayName = 'CodeBlock';
44 |
45 | export default CodeBlock;
46 |
--------------------------------------------------------------------------------
/src/lib/components/CodeBlock/CodeBlock.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { storiesOf } from '@storybook/react';
3 | import { boolean } from '@storybook/addon-knobs';
4 | import { withInfo } from '@storybook/addon-info';
5 |
6 | import CodeBlock from './CodeBlock';
7 |
8 | storiesOf('Code Block', module)
9 | .add('Default',
10 | withInfo('The Code Block component is used to display a large amount of code. The preferred prop is a single template literal, to preserve formatting. Alternatively, markup can be used but will be formatted automatically. When you refer to code inline with other text, use the tag instead.')(() => (
11 |
12 | {`this is code sample line 1
13 | this is code sample line 2
14 | this is code sample line 3
15 |
16 | this is code sample line 4 this is code sample line 4 this is code sample line 4 this is code sample line 4 this is code sample line 4 this is code sample line 4 this is code sample line 4 this is code sample line 4 this is code sample line 4 this is code sample line 4
17 | this is code sample line 5`}
18 | ),
19 | ),
20 | ).add('Numbered',
21 | withInfo('The code numbered pattern can be used when displaying large blocks of code to enable users to quickly reference a specific line.')(() => (
22 |
23 | {`this is code sample line 1
24 | this is code sample line 2
25 | this is code sample line 3
26 |
27 | this is code sample line 4 this is code sample line 4 this is code sample line 4 this is code sample line 4 this is code sample line 4 this is code sample line 4 this is code sample line 4 this is code sample line 4 this is code sample line 4 this is code sample line 4
28 | this is code sample line 5`}
29 | ),
30 | ),
31 | );
32 |
--------------------------------------------------------------------------------
/src/lib/components/CodeBlock/CodeBlock.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactTestRenderer from 'react-test-renderer';
3 | import CodeBlock from './CodeBlock';
4 |
5 | describe('CodeBlock component', () => {
6 | it('should render one line of code correctly', () => {
7 | const codeBlock = ReactTestRenderer.create(
8 |
9 | {'this is a single line of code'}
10 | ,
11 | );
12 | const json = codeBlock.toJSON();
13 | expect(json).toMatchSnapshot();
14 | });
15 |
16 | it('should render multiple lines of code correctly', () => {
17 | const codeBlock = ReactTestRenderer.create(
18 |
19 | {`this is code sample line 1
20 | this is code sample line 2
21 | this is code sample line 3`}
22 | ,
23 | );
24 | const json = codeBlock.toJSON();
25 | expect(json).toMatchSnapshot();
26 | });
27 |
28 | it('should render one line of code correctly with numbered modifier', () => {
29 | const codeBlock = ReactTestRenderer.create(
30 |
31 | {'this is a single line of code'}
32 | ,
33 | );
34 | const json = codeBlock.toJSON();
35 | expect(json).toMatchSnapshot();
36 | });
37 |
38 | it('should render multiple lines of code correctly with numbered modifier', () => {
39 | const codeBlock = ReactTestRenderer.create(
40 |
41 | {`this is code sample line 1
42 | this is code sample line 2
43 | this is code sample line 3`}
44 | ,
45 | );
46 | const json = codeBlock.toJSON();
47 | expect(json).toMatchSnapshot();
48 | });
49 |
50 | it('should display a single blank line if component is empty', () => {
51 | const codeBlock = ReactTestRenderer.create(
52 | ,
53 | );
54 | const json = codeBlock.toJSON();
55 | expect(json).toMatchSnapshot();
56 | });
57 |
58 | it('should render markup correctly', () => {
59 | const codeBlock = ReactTestRenderer.create(
60 |
61 |
62 | Markup Example
63 |
64 |
65 | This is an example of using markup in the CodeBlock component
66 | This & should escape
67 |
68 | ,
69 | );
70 | const json = codeBlock.toJSON();
71 | expect(json).toMatchSnapshot();
72 | });
73 | });
74 |
--------------------------------------------------------------------------------
/src/lib/components/CodeBlock/__snapshots__/CodeBlock.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`CodeBlock component should display a single blank line if component is empty 1`] = `
4 |
7 |
8 |
9 |
10 |
11 | `;
12 |
13 | exports[`CodeBlock component should render markup correctly 1`] = `
14 |
17 |
18 | <head>
19 | <title>Markup Example</title>
20 | </head>
21 | <body>
22 | <div>This is an example of using markup in the CodeBlock component</div>
23 | <div>This & should escape</div>
24 | </body>
25 |
26 |
27 | `;
28 |
29 | exports[`CodeBlock component should render multiple lines of code correctly 1`] = `
30 |
33 |
34 | this is code sample line 1
35 | this is code sample line 2
36 | this is code sample line 3
37 |
38 |
39 | `;
40 |
41 | exports[`CodeBlock component should render multiple lines of code correctly with numbered modifier 1`] = `
42 |
45 |
46 |
49 | this is code sample line 1
50 |
51 |
54 | this is code sample line 2
55 |
56 |
59 | this is code sample line 3
60 |
61 |
62 |
63 | `;
64 |
65 | exports[`CodeBlock component should render one line of code correctly 1`] = `
66 |
69 |
70 | this is a single line of code
71 |
72 |
73 | `;
74 |
75 | exports[`CodeBlock component should render one line of code correctly with numbered modifier 1`] = `
76 |
79 |
80 |
83 | this is a single line of code
84 |
85 |
86 |
87 | `;
88 |
--------------------------------------------------------------------------------
/src/lib/components/CodeSnippet/CodeSnippet.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | class CodeSnippet extends React.Component {
5 | constructor(props) {
6 | super(props);
7 | this.handleInputClick = this.handleInputClick.bind(this);
8 | this.handleButtonClick = this.handleButtonClick.bind(this);
9 | }
10 |
11 | handleInputClick() {
12 | this.textInput.select();
13 | }
14 |
15 | handleButtonClick() {
16 | this.textInput.select();
17 | try {
18 | document.execCommand('copy');
19 | } catch (err) {
20 | console.warn('Unable to copy'); // eslint-disable-line no-console
21 | }
22 | }
23 |
24 | render() {
25 | return (
26 |
27 | { this.textInput = input; }}
33 | />
34 | Copy to clipboard
38 |
39 |
40 | );
41 | }
42 | }
43 |
44 | CodeSnippet.propTypes = {
45 | value: PropTypes.string.isRequired,
46 | };
47 |
48 | CodeSnippet.displayName = 'CodeSnippet';
49 |
50 | export default CodeSnippet;
51 |
--------------------------------------------------------------------------------
/src/lib/components/CodeSnippet/CodeSnippet.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { storiesOf } from '@storybook/react';
3 | import { text } from '@storybook/addon-knobs';
4 | import { withInfo } from '@storybook/addon-info';
5 |
6 | import CodeSnippet from './CodeSnippet';
7 |
8 | storiesOf('Code Snippet', module)
9 | .add('Default',
10 | withInfo('Code snippet should be used when presenting the user with a small snippet of code that they will likely want to copy and paste.')(() =>
11 | ,
12 | ),
13 | );
14 |
--------------------------------------------------------------------------------
/src/lib/components/CodeSnippet/CodeSnippet.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactTestRenderer from 'react-test-renderer';
3 | import CodeSnippet from './CodeSnippet';
4 |
5 | describe('CodeSnippet component', () => {
6 | it('should compare with a snapshot', () => {
7 | const codesnippet = ReactTestRenderer.create( );
8 | const json = codesnippet.toJSON();
9 | expect(json).toMatchSnapshot();
10 | });
11 | });
12 |
13 |
--------------------------------------------------------------------------------
/src/lib/components/CodeSnippet/__snapshots__/CodeSnippet.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`CodeSnippet component should compare with a snapshot 1`] = `
4 |
7 |
13 |
17 | Copy to clipboard
18 |
19 |
20 | `;
21 |
--------------------------------------------------------------------------------
/src/lib/components/ContextualMenu/ContextualMenu.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import getClassName from '../../utils/getClassName';
4 |
5 | import ContextualMenuDropdown from './ContextualMenuDropdown';
6 |
7 | class ContextualMenu extends React.Component {
8 | constructor() {
9 | super();
10 |
11 | this.toggleOpen = this.toggleOpen.bind(this);
12 | this.state = { open: false };
13 | }
14 |
15 | toggleOpen() {
16 | this.setState({ open: !this.state.open });
17 | }
18 |
19 | render() {
20 | const {
21 | className, children, id, position, ...otherProps
22 | } = this.props;
23 |
24 | const classNames = getClassName({
25 | [className]: className,
26 | 'p-contextual-menu': true,
27 | [`p-contextual-menu--${position}`]: position,
28 | }) || undefined;
29 |
30 | const toggle = React.Children.map(children, (child) => {
31 | if (child.type !== ContextualMenuDropdown) {
32 | return React.cloneElement(child, {
33 | className: 'p-contextual-menu__toggle',
34 | 'aria-controls': id,
35 | 'aria-expanded': this.state.open,
36 | 'aria-haspopup': 'true',
37 | role: 'link',
38 | onClick: this.toggleOpen,
39 | tabIndex: 0,
40 | });
41 | } else if (child.type === ContextualMenuDropdown) {
42 | return React.cloneElement(child, {
43 | id,
44 | 'aria-hidden': !this.state.open,
45 | });
46 | }
47 | return child;
48 | });
49 |
50 | return (
51 |
55 | {toggle}
56 |
57 | );
58 | }
59 | }
60 |
61 | ContextualMenu.defaultProps = {
62 | children: null,
63 | className: null,
64 | position: null,
65 | };
66 |
67 | ContextualMenu.propTypes = {
68 | children: (props, propName, componentName) => {
69 | const prop = props[propName];
70 | let error = null;
71 | let count = 0;
72 |
73 | React.Children.forEach(prop, (child) => {
74 | if (child.type === ContextualMenuDropdown) {
75 | count += 1;
76 | }
77 | });
78 |
79 | if (count !== 1) {
80 | error = new Error(`${componentName} should have exactly one "ContextualMenuDropdown" child.`);
81 | }
82 |
83 | return error;
84 | },
85 | className: PropTypes.string,
86 | id: PropTypes.string.isRequired,
87 | position: PropTypes.oneOf(['left', 'center']),
88 | };
89 |
90 | ContextualMenu.displayName = 'ContextualMenu';
91 |
92 | export default ContextualMenu;
93 |
--------------------------------------------------------------------------------
/src/lib/components/ContextualMenu/ContextualMenu.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { storiesOf } from '@storybook/react';
3 | import { select } from '@storybook/addon-knobs';
4 | import { withInfo } from '@storybook/addon-info';
5 |
6 | import ContextualMenu from './ContextualMenu';
7 | import ContextualMenuDropdown from './ContextualMenuDropdown';
8 | import ContextualMenuGroup from './ContextualMenuGroup';
9 | import Button from '../Button/Button';
10 | import Link from '../Link/Link';
11 |
12 | storiesOf('ContextualMenu', module)
13 | .add('Default',
14 | withInfo('A ContextualMenu component can be wrapped around any component (button, link, navigation item etc.) to give it a secondary dropdown menu on click. One of the children must be a ContextualMenuDropdown that contains the links (or other elements) you would like to display. ContextualMenus must be given an "id" prop so the dropdown knows where in the DOM to appear, and it also accepts a "position" prop to determine which side of the element appears (default "right").')(() => (
15 |
16 |
24 |
),
25 | ),
26 | )
27 |
28 | .add('Groups',
29 | withInfo('Dropdown links can be grouped when placed inside a ContextualMenuGroup component, which separates groups by a dividing line.')(() => (
30 |
31 |
44 |
),
45 | ),
46 | )
47 |
48 | .add('Link',
49 | withInfo('ContextualMenus can be applied to any element, including links within paragraph text.')(() => (
50 |
51 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Modi natus atque eligendi
52 | deleniti hic, dolores veritatis reiciendis officiis illo, facere facilis accusamus
53 | similique nulla nesciunt.
54 |
66 | reprehenderit officia assumenda error enim. Recusandae
67 | reiciendis ipsum, mollitia illo iusto excepturi alias dolore fugit eligendi nostrum, unde
68 | architecto consequuntur similique quo! Maxime iusto facere, commodi iste fuga officiis.
69 |
),
70 | ),
71 | );
72 |
--------------------------------------------------------------------------------
/src/lib/components/ContextualMenu/ContextualMenu.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactTestRenderer from 'react-test-renderer';
3 | import { shallow } from 'enzyme';
4 | import ContextualMenu from './ContextualMenu';
5 | import ContextualMenuDropdown from './ContextualMenuDropdown';
6 | import ContextualMenuGroup from './ContextualMenuGroup';
7 |
8 | describe('', () => {
9 | it('should render a simple ContextualMenu correctly', () => {
10 | const menu = ReactTestRenderer.create(
11 | );
19 | const json = menu.toJSON();
20 | expect(json).toMatchSnapshot();
21 | });
22 |
23 | it('should render with position prop correctly', () => {
24 | const menu = ReactTestRenderer.create(
25 |
26 |
34 |
42 |
);
43 | const json = menu.toJSON();
44 | expect(json).toMatchSnapshot();
45 | });
46 |
47 | it('should render ContextualMenuGroups correctly', () => {
48 | const menu = ReactTestRenderer.create(
49 | );
62 | const json = menu.toJSON();
63 | expect(json).toMatchSnapshot();
64 | });
65 |
66 | it('should open the ContextualMenu when clicked', () => {
67 | const menu = shallow(
68 | );
76 |
77 | expect(menu.find('[aria-expanded=true]')).toHaveLength(0);
78 | expect(menu.find('[aria-hidden=true]')).toHaveLength(1);
79 |
80 | menu.find('button').hostNodes().simulate('click');
81 | expect(menu.find('[aria-expanded=true]')).toHaveLength(1);
82 | expect(menu.find('[aria-hidden=true]')).toHaveLength(0);
83 | });
84 | });
85 |
--------------------------------------------------------------------------------
/src/lib/components/ContextualMenu/ContextualMenuDropdown.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import getClassName from '../../utils/getClassName';
4 |
5 | import ContextualMenuGroup from './ContextualMenuGroup';
6 |
7 | const ContextualMenuDropdown = (props) => {
8 | const { children, className, ...otherProps } = props;
9 |
10 | const classNames = getClassName({
11 | [className]: className,
12 | 'p-contextual-menu__dropdown': true,
13 | }) || undefined;
14 |
15 | const dropdownItems = React.Children.map(children, (child) => {
16 | if (child.type !== ContextualMenuGroup) {
17 | const childClasses = getClassName({
18 | [child.props.className]: child.props.className,
19 | 'p-contextual-menu__link': true,
20 | });
21 | return React.cloneElement(child, {
22 | className: childClasses,
23 | });
24 | }
25 | return child;
26 | });
27 |
28 | return (
29 |
30 | { dropdownItems }
31 |
32 | );
33 | };
34 |
35 | ContextualMenuDropdown.defaultProps = {
36 | children: null,
37 | className: null,
38 | };
39 |
40 | ContextualMenuDropdown.propTypes = {
41 | children: PropTypes.node,
42 | className: PropTypes.string,
43 | };
44 |
45 | ContextualMenuDropdown.displayName = 'ContextualMenuDropdown';
46 |
47 | export default ContextualMenuDropdown;
48 |
--------------------------------------------------------------------------------
/src/lib/components/ContextualMenu/ContextualMenuGroup.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import getClassName from '../../utils/getClassName';
4 |
5 | const ContextualMenuGroup = (props) => {
6 | const { children, className, ...otherProps } = props;
7 |
8 | const classNames = getClassName({
9 | [className]: className,
10 | 'p-contextual-menu__group': true,
11 | }) || undefined;
12 |
13 | const items = React.Children.map(children, (child) => {
14 | const childClasses = getClassName({
15 | [child.props.className]: child.props.className,
16 | 'p-contextual-menu__link': true,
17 | });
18 | return React.cloneElement(child, {
19 | className: childClasses,
20 | });
21 | });
22 |
23 | return (
24 |
25 | {items}
26 |
27 | );
28 | };
29 |
30 | ContextualMenuGroup.defaultProps = {
31 | children: null,
32 | className: null,
33 | };
34 |
35 | ContextualMenuGroup.propTypes = {
36 | children: PropTypes.node,
37 | className: PropTypes.string,
38 | };
39 |
40 | ContextualMenuGroup.displayName = 'ContextualMenuGroup';
41 |
42 | export default ContextualMenuGroup;
43 |
--------------------------------------------------------------------------------
/src/lib/components/DividerList/DividerList.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const DividerList = (props) => {
5 | const dividerItems = React.Children.map(props.children,
6 | child => React.cloneElement(child, {
7 | items: React.Children.count(props.children),
8 | }),
9 | );
10 |
11 | return (
12 |
13 | {dividerItems}
14 |
15 | );
16 | };
17 |
18 | DividerList.propTypes = {
19 | children: PropTypes.node.isRequired,
20 | };
21 |
22 | DividerList.displayName = 'DividerList';
23 |
24 | export default DividerList;
25 |
--------------------------------------------------------------------------------
/src/lib/components/DividerList/DividerList.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactTestRenderer from 'react-test-renderer';
3 | import DividerList from './DividerList';
4 | import DividerListItem from './DividerListItem';
5 |
6 | describe('DividerList component', () => {
7 | it('should render two children correctly', () => {
8 | const dividerList = ReactTestRenderer.create(
9 |
10 |
11 | One
12 |
13 |
14 | Two
15 |
16 | ,
17 | );
18 | const json = dividerList.toJSON();
19 | expect(json).toMatchSnapshot();
20 | });
21 |
22 | it('should render three children correctly', () => {
23 | const dividerList = ReactTestRenderer.create(
24 |
25 |
26 | One
27 |
28 |
29 | Two
30 |
31 |
32 | Three
33 |
34 | ,
35 | );
36 | const json = dividerList.toJSON();
37 | expect(json).toMatchSnapshot();
38 | });
39 |
40 | it('should render four children correctly', () => {
41 | const dividerList = ReactTestRenderer.create(
42 |
43 |
44 | One
45 |
46 |
47 | Two
48 |
49 |
50 | Three
51 |
52 |
53 | Four
54 |
55 | ,
56 | );
57 | const json = dividerList.toJSON();
58 | expect(json).toMatchSnapshot();
59 | });
60 | });
61 |
--------------------------------------------------------------------------------
/src/lib/components/DividerList/DividerListItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const DividerListItem = props => (
5 |
6 | {props.children}
7 |
8 | );
9 |
10 | DividerListItem.defaultProps = {
11 | items: 3,
12 | };
13 |
14 | DividerListItem.propTypes = {
15 | children: PropTypes.node.isRequired,
16 | items: PropTypes.oneOf([2, 3, 4, 6]),
17 | };
18 |
19 | DividerListItem.displayName = 'DividerListItem';
20 |
21 | export default DividerListItem;
22 |
--------------------------------------------------------------------------------
/src/lib/components/DividerList/__snapshots__/DividerList.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`DividerList component should render four children correctly 1`] = `
4 |
7 |
10 | One
11 |
12 |
15 | Two
16 |
17 |
20 | Three
21 |
22 |
25 | Four
26 |
27 |
28 | `;
29 |
30 | exports[`DividerList component should render three children correctly 1`] = `
31 |
34 |
37 | One
38 |
39 |
42 | Two
43 |
44 |
47 | Three
48 |
49 |
50 | `;
51 |
52 | exports[`DividerList component should render two children correctly 1`] = `
53 |
56 |
59 | One
60 |
61 |
64 | Two
65 |
66 |
67 | `;
68 |
--------------------------------------------------------------------------------
/src/lib/components/Footer/Footer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const Footer = props => (
5 |
6 | { props.children }
7 |
8 | );
9 |
10 | Footer.propTypes = {
11 | children: PropTypes.node.isRequired,
12 | };
13 |
14 | Footer.displayName = 'Footer';
15 |
16 | export default Footer;
17 |
--------------------------------------------------------------------------------
/src/lib/components/Footer/Footer.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { storiesOf } from '@storybook/react';
3 | import { withInfo } from '@storybook/addon-info';
4 |
5 | import Footer from './Footer';
6 | import FooterNav from './FooterNav';
7 | import FooterNavContainer from './FooterNavContainer';
8 |
9 | storiesOf('Footer', module)
10 | .add('Default',
11 | withInfo('You can use this simple footer pattern to display simple HTML elements: lists, headings, columns, icons, buttons, etc. Centering is optional.')(() => (
12 | ),
18 | ),
19 | )
20 | .add('With Links',
21 | withInfo('You can use this simple footer pattern to display copyright notices and a selection of links, using the FooterNavContainer and FooterNav sub-components.')(() => (
22 | ),
31 | ),
32 | );
33 |
--------------------------------------------------------------------------------
/src/lib/components/Footer/Footer.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactTestRenderer from 'react-test-renderer';
3 | import Footer from './Footer';
4 | import FooterNavContainer from './FooterNavContainer';
5 | import FooterNav from './FooterNav';
6 |
7 | describe('Footer component', () => {
8 | it('should render basic HTML elements correctly', () => {
9 | const footer = ReactTestRenderer.create(
10 | ,
18 | );
19 | const json = footer.toJSON();
20 | expect(json).toMatchSnapshot();
21 | });
22 |
23 | it('should render FooterNavContainer and FooterNav components correctly', () => {
24 | const footer = ReactTestRenderer.create(
25 | ,
34 | );
35 | const json = footer.toJSON();
36 | expect(json).toMatchSnapshot();
37 | });
38 | });
39 |
--------------------------------------------------------------------------------
/src/lib/components/Footer/FooterNav.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const FooterNav = props => (
5 |
6 | { props.title }
7 |
8 | );
9 |
10 | FooterNav.propTypes = {
11 | title: PropTypes.string.isRequired,
12 | link: PropTypes.string.isRequired,
13 | };
14 |
15 | FooterNav.displayName = 'FooterNav';
16 |
17 | export default FooterNav;
18 |
--------------------------------------------------------------------------------
/src/lib/components/Footer/FooterNavContainer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const FooterNavContainer = props => (
5 |
6 |
7 | { props.children }
8 |
9 |
10 | );
11 |
12 | FooterNavContainer.propTypes = {
13 | children: PropTypes.node.isRequired,
14 | };
15 |
16 | FooterNavContainer.displayName = 'FooterNavContainer';
17 |
18 | export default FooterNavContainer;
19 |
--------------------------------------------------------------------------------
/src/lib/components/Footer/__snapshots__/Footer.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Footer component should render FooterNavContainer and FooterNav components correctly 1`] = `
4 |
59 | `;
60 |
61 | exports[`Footer component should render basic HTML elements correctly 1`] = `
62 |
80 | `;
81 |
--------------------------------------------------------------------------------
/src/lib/components/Form/FieldSet.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import getClassName from '../../utils/getClassName';
4 |
5 | const FieldSet = (props) => {
6 | const { children, className, ...otherProps } = props;
7 |
8 | const classNames = getClassName({
9 | [className]: className,
10 | }) || undefined;
11 |
12 | return (
13 |
14 | { children }
15 |
16 | );
17 | };
18 |
19 | FieldSet.defaultProps = {
20 | className: undefined,
21 | };
22 |
23 | FieldSet.propTypes = {
24 | children: PropTypes.node.isRequired,
25 | className: PropTypes.string,
26 | };
27 |
28 | FieldSet.displayName = 'FieldSet';
29 |
30 | export default FieldSet;
31 |
--------------------------------------------------------------------------------
/src/lib/components/Form/Form.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import getClassName from '../../utils/getClassName';
4 |
5 | const Form = (props) => {
6 | const {
7 | children, className, inline, stacked, ...otherProps
8 | } = props;
9 |
10 | const classNames = getClassName({
11 | [className]: className,
12 | 'p-form': true,
13 | 'p-form--inline': inline && !stacked,
14 | 'p-form--stacked': stacked && !inline,
15 | }) || undefined;
16 |
17 | return (
18 |
21 | );
22 | };
23 |
24 | Form.defaultProps = {
25 | children: null,
26 | className: undefined,
27 | inline: false,
28 | stacked: false,
29 | };
30 |
31 | Form.propTypes = {
32 | children: PropTypes.node,
33 | className: PropTypes.string,
34 | inline: PropTypes.bool,
35 | stacked: PropTypes.bool,
36 | };
37 |
38 | Form.displayName = 'Form';
39 |
40 | export default Form;
41 |
--------------------------------------------------------------------------------
/src/lib/components/Form/Form.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactTestRenderer from 'react-test-renderer';
3 | import { shallow } from 'enzyme';
4 | import Form from './Form';
5 | import FormControl from './FormControl';
6 | import Input from '../Input/Input';
7 | import Label from '../Input/Label';
8 |
9 | describe(');
12 |
13 | expect(form.type()).toBe('form');
14 | });
15 |
16 | it('should render children', () => {
17 | const form = shallow();
18 |
19 | expect(form.text()).toBe('Form');
20 | });
21 |
22 | it('should contain the inline form class if inline prop exists', () => {
23 | const inlineClass = 'p-form--inline';
24 | const form = shallow();
25 |
26 | expect(form.hasClass(inlineClass)).toBe(true);
27 | });
28 |
29 | it('should contain the stacked form class if inline prop exists', () => {
30 | const inlineClass = 'p-form--stacked';
31 | const form = shallow();
32 |
33 | expect(form.hasClass(inlineClass)).toBe(true);
34 | });
35 |
36 | it('should render additional classes', () => {
37 | const form = shallow();
38 |
39 | expect(form.hasClass('class')).toBe(true);
40 | });
41 |
42 | it('should match snapshot of empty Form', () => {
43 | const form = ReactTestRenderer.create(
44 | );
45 | const json = form.toJSON();
46 | expect(json).toMatchSnapshot();
47 | });
48 |
49 | it('should match snapshot of Form with Label and Input', () => {
50 | const form = ReactTestRenderer.create(
51 | );
55 | const json = form.toJSON();
56 | expect(json).toMatchSnapshot();
57 | });
58 |
59 | it('should match snapshot of Form with FormControl', () => {
60 | const form = ReactTestRenderer.create(
61 | );
67 | const json = form.toJSON();
68 | expect(json).toMatchSnapshot();
69 | });
70 |
71 | it('should match snapshot of Form with FormControl and help text', () => {
72 | const form = ReactTestRenderer.create(
73 | );
79 | const json = form.toJSON();
80 | expect(json).toMatchSnapshot();
81 | });
82 |
83 | it('should match snapshot of Form with FormControl and validation', () => {
84 | const form = ReactTestRenderer.create(
85 | );
97 | const json = form.toJSON();
98 | expect(json).toMatchSnapshot();
99 | });
100 | });
101 |
--------------------------------------------------------------------------------
/src/lib/components/Form/FormControl.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import getClassName from '../../utils/getClassName';
4 | import FormHelpText from './FormHelpText';
5 | import Input from '../Input/Input';
6 |
7 | const FormControl = (props) => {
8 | const {
9 | children, className, help, validation,
10 | } = props;
11 |
12 | const classNames = getClassName({
13 | [className]: className,
14 | 'p-form__control': true,
15 | 'p-form-validation': validation.status,
16 | [`is-${validation.status}`]: validation && validation.status,
17 | }) || undefined;
18 |
19 | const formControlItems = React.Children.map(children, (child) => {
20 | if (validation.status && child.type === Input) {
21 | return React.cloneElement(child, { hasValidation: true });
22 | }
23 | return child;
24 | });
25 |
26 | return (
27 |
28 | { formControlItems }
29 | { validation.status && (
30 |
31 | { validation.tag && {validation.tag} }
32 | { validation.message }
33 |
)
34 | }
35 | { help &&
{ help } }
36 |
37 | );
38 | };
39 |
40 | FormControl.defaultProps = {
41 | children: null,
42 | className: undefined,
43 | help: null,
44 | validation: {
45 | tag: null,
46 | status: null,
47 | message: null,
48 | },
49 | };
50 |
51 | FormControl.propTypes = {
52 | children: PropTypes.node,
53 | className: PropTypes.string,
54 | help: PropTypes.node,
55 | validation: PropTypes.shape({
56 | tag: PropTypes.string,
57 | status: PropTypes.oneOf([null, 'caution', 'success', 'error']),
58 | message: PropTypes.node,
59 | }),
60 | };
61 |
62 | FormControl.displayName = 'FormControl';
63 |
64 | export default FormControl;
65 |
--------------------------------------------------------------------------------
/src/lib/components/Form/FormGroup.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import getClassName from '../../utils/getClassName';
4 |
5 | const FormGroup = (props) => {
6 | const { children, className } = props;
7 |
8 | const classNames = getClassName({
9 | [className]: className,
10 | 'p-form__group': true,
11 | }) || undefined;
12 |
13 | return (
14 |
15 | { children }
16 |
17 | );
18 | };
19 |
20 | FormGroup.defaultProps = {
21 | children: undefined,
22 | className: '',
23 | };
24 |
25 | FormGroup.propTypes = {
26 | children: PropTypes.node,
27 | className: PropTypes.string,
28 | };
29 |
30 | FormGroup.displayName = 'FormGroup';
31 |
32 | export default FormGroup;
33 |
--------------------------------------------------------------------------------
/src/lib/components/Form/FormHelpText.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import getClassName from '../../utils/getClassName';
4 |
5 | const FormHelpText = (props) => {
6 | const { children, className } = props;
7 |
8 | const classNames = getClassName({
9 | [className]: className,
10 | 'p-form-help-text': true,
11 | }) || undefined;
12 |
13 | return (
14 | { children }
15 | );
16 | };
17 |
18 | FormHelpText.defaultProps = {
19 | children: null,
20 | className: undefined,
21 | };
22 |
23 | FormHelpText.propTypes = {
24 | children: PropTypes.node,
25 | className: PropTypes.string,
26 | };
27 |
28 | FormHelpText.displayName = 'FormHelpText';
29 |
30 | export default FormHelpText;
31 |
--------------------------------------------------------------------------------
/src/lib/components/Form/__snapshots__/Form.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`
23 | `;
24 |
25 | exports[`
50 | `;
51 |
52 | exports[`
80 | `;
81 |
82 | exports[`
98 | `;
99 |
100 | exports[`
104 | `;
105 |
--------------------------------------------------------------------------------
/src/lib/components/HeadingIcon/HeadingIcon.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const HeadingIcon = props => (
5 |
6 |
7 |
8 |
{props.title}
9 |
10 | {props.children}
11 |
12 | );
13 |
14 | HeadingIcon.defaultProps = {
15 | alt: '',
16 | children: '',
17 | };
18 |
19 | HeadingIcon.propTypes = {
20 | title: PropTypes.string.isRequired,
21 | src: PropTypes.string.isRequired,
22 | alt: PropTypes.string,
23 | children: PropTypes.node,
24 | };
25 |
26 | HeadingIcon.displayName = 'HeadingIcon';
27 |
28 | export default HeadingIcon;
29 |
--------------------------------------------------------------------------------
/src/lib/components/HeadingIcon/HeadingIcon.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { storiesOf } from '@storybook/react';
3 | import { text } from '@storybook/addon-knobs';
4 | import { withInfo } from '@storybook/addon-info';
5 |
6 | import HeadingIcon from './HeadingIcon';
7 |
8 | storiesOf('Heading Icon', module)
9 | .add('Default',
10 | withInfo('The HeadingIcon component can be used to add an icon to a standard header.')(() => (
11 | ),
16 | ),
17 | )
18 |
19 | .add('With Children',
20 | withInfo('The HeadingIcon component can be used to add an icon to a standard header.')(() => (
21 |
26 |
27 | {text('Text', 'Sit amet, consectetur adipisicing elit. Enim excepturi, repudiandae blanditiis odio perferendis voluptatibus dolorum, dicta illum quae ipsa voluptatum, sunt quasi? Nulla reiciendis magnam nostrum aliquam, beatae doloribus.')}
28 |
29 | ),
30 | ),
31 | );
32 |
--------------------------------------------------------------------------------
/src/lib/components/HeadingIcon/HeadingIcon.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactTestRenderer from 'react-test-renderer';
3 | import HeadingIcon from './HeadingIcon';
4 |
5 | describe('HeadingIcon component', () => {
6 | it('should render an icon and title without children correctly', () => {
7 | const headingIcon = ReactTestRenderer.create(
8 | ,
13 | );
14 | const json = headingIcon.toJSON();
15 | expect(json).toMatchSnapshot();
16 | });
17 |
18 | it('should render children correctly', () => {
19 | const headingIcon = ReactTestRenderer.create(
20 |
25 |
26 | sit amet, consectetur adipisicing elit. Enim excepturi, repudiandae blanditiis odio
27 | perferendis voluptatibus dolorum, dicta illum quae ipsa voluptatum, sunt quasi? Nulla
28 | reiciendis magnam nostrum aliquam, beatae doloribus.
29 |
30 | ,
31 | );
32 | const json = headingIcon.toJSON();
33 | expect(json).toMatchSnapshot();
34 | });
35 | });
36 |
--------------------------------------------------------------------------------
/src/lib/components/HeadingIcon/__snapshots__/HeadingIcon.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`HeadingIcon component should render an icon and title without children correctly 1`] = `
4 |
7 |
10 |
15 |
18 | Lorem ipsum dolor
19 |
20 |
21 |
22 |
23 | `;
24 |
25 | exports[`HeadingIcon component should render children correctly 1`] = `
26 |
29 |
32 |
37 |
40 | Lorem ipsum dolor
41 |
42 |
43 |
44 | sit amet, consectetur adipisicing elit. Enim excepturi, repudiandae blanditiis odio perferendis voluptatibus dolorum, dicta illum quae ipsa voluptatum, sunt quasi? Nulla reiciendis magnam nostrum aliquam, beatae doloribus.
45 |
46 |
47 | `;
48 |
--------------------------------------------------------------------------------
/src/lib/components/Image/Image.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const Image = (props) => {
5 | let classArray = [];
6 |
7 | if (props.bordered) {
8 | classArray = [...classArray, 'p-image--bordered'];
9 | }
10 | if (props.shadowed) {
11 | classArray = [...classArray, 'p-image--shadowed'];
12 | }
13 |
14 | const classString = classArray.join(' ');
15 |
16 | return (
17 |
23 | );
24 | };
25 |
26 | Image.defaultProps = {
27 | alt: '',
28 | bordered: false,
29 | shadowed: false,
30 | style: null,
31 | };
32 |
33 | Image.propTypes = {
34 | src: PropTypes.string.isRequired,
35 | alt: PropTypes.string,
36 | bordered: PropTypes.bool,
37 | shadowed: PropTypes.bool,
38 | style: PropTypes.object,
39 | };
40 |
41 | Image.displayName = 'Image';
42 |
43 | export default Image;
44 |
--------------------------------------------------------------------------------
/src/lib/components/Image/Image.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { storiesOf } from '@storybook/react';
3 | import { text, boolean } from '@storybook/addon-knobs';
4 | import { withInfo } from '@storybook/addon-info';
5 |
6 | import Image from './Image';
7 |
8 | storiesOf('Image', module)
9 | .add('Default',
10 | withInfo('Default Image component.')(() => (
11 | ),
17 | ),
18 | )
19 | .add('Bordered',
20 | withInfo('Bordered Image component.')(() => (
21 | ),
27 | ),
28 | )
29 | .add('Shadowed',
30 | withInfo('Image component with drop-shadow.')(() => (
31 | ),
37 | ),
38 | );
39 |
--------------------------------------------------------------------------------
/src/lib/components/Image/Image.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactTestRenderer from 'react-test-renderer';
3 | import Image from './Image';
4 |
5 | describe('Image component', () => {
6 | it('should render a default Image correctly', () => {
7 | const image = ReactTestRenderer.create(
8 | );
9 | const json = image.toJSON();
10 | expect(json).toMatchSnapshot();
11 | });
12 |
13 | it('should render bordered prop correctly', () => {
14 | const image = ReactTestRenderer.create(
15 | );
16 | const json = image.toJSON();
17 | expect(json).toMatchSnapshot();
18 | });
19 |
20 | it('should render shadowed prop correctly', () => {
21 | const image = ReactTestRenderer.create(
22 | );
23 | const json = image.toJSON();
24 | expect(json).toMatchSnapshot();
25 | });
26 |
27 | it('should render both bordered and shadowed props correctly', () => {
28 | const image = ReactTestRenderer.create(
29 | );
30 | const json = image.toJSON();
31 | expect(json).toMatchSnapshot();
32 | });
33 | });
34 |
--------------------------------------------------------------------------------
/src/lib/components/Image/__snapshots__/Image.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Image component should render a default Image correctly 1`] = `
4 |
10 | `;
11 |
12 | exports[`Image component should render bordered prop correctly 1`] = `
13 |
19 | `;
20 |
21 | exports[`Image component should render both bordered and shadowed props correctly 1`] = `
22 |
28 | `;
29 |
30 | exports[`Image component should render shadowed prop correctly 1`] = `
31 |
37 | `;
38 |
--------------------------------------------------------------------------------
/src/lib/components/InlineImages/InlineImages.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const InlineImages = (props) => {
5 | const images = React.Children.map(props.children, child => (
6 |
7 | {child}
8 | ),
9 | );
10 |
11 | return (
12 |
15 | );
16 | };
17 |
18 | InlineImages.propTypes = {
19 | children: PropTypes.oneOfType([
20 | PropTypes.element,
21 | PropTypes.arrayOf(PropTypes.element),
22 | ]).isRequired,
23 | };
24 |
25 | InlineImages.displayName = 'InlineImages';
26 |
27 | export default InlineImages;
28 |
--------------------------------------------------------------------------------
/src/lib/components/InlineImages/InlineImages.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { storiesOf } from '@storybook/react';
3 | import { withInfo } from '@storybook/addon-info';
4 |
5 | import InlineImages from './InlineImages';
6 | import Image from '../Image/Image';
7 |
8 | storiesOf('Inline Images', module)
9 | .add('Default',
10 | withInfo("The InlineImages component can be used to showcase a group of related images, such as a group of customer or partner logos. Vanilla's Image components and tags are accepted children.")(() => (
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | ),
19 | ),
20 | );
21 |
--------------------------------------------------------------------------------
/src/lib/components/InlineImages/InlineImages.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactTestRenderer from 'react-test-renderer';
3 | import InlineImages from './InlineImages';
4 | import Image from '../Image/Image';
5 |
6 | describe('InlineImages component', () => {
7 | it('should render the InlineImages component correctly', () => {
8 | const inlineImages = ReactTestRenderer.create(
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | );
17 | const json = inlineImages.toJSON();
18 | expect(json).toMatchSnapshot();
19 | });
20 | });
21 |
--------------------------------------------------------------------------------
/src/lib/components/InlineImages/__snapshots__/InlineImages.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`InlineImages component should render the InlineImages component correctly 1`] = `
4 |
7 |
10 |
16 |
17 |
20 |
26 |
27 |
30 |
36 |
37 |
40 |
46 |
47 |
50 |
56 |
57 |
60 |
66 |
67 |
68 | `;
69 |
--------------------------------------------------------------------------------
/src/lib/components/Input/Input.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import getClassName from '../../utils/getClassName';
4 |
5 | function inputTag(type) {
6 | if (type === 'select') {
7 | return 'select';
8 | } else if (type === 'textarea') {
9 | return 'textarea';
10 | }
11 | return 'input';
12 | }
13 |
14 | const Input = (props) => {
15 | const {
16 | className, hasValidation, type, ...otherProps
17 | } = props;
18 |
19 | const classNames = getClassName({
20 | [className]: className,
21 | 'p-form-validation__input': hasValidation,
22 | }) || undefined;
23 |
24 | const Tag = inputTag(type);
25 |
26 | return (
27 |
32 | );
33 | };
34 |
35 | Input.defaultProps = {
36 | className: undefined,
37 | type: 'text',
38 | hasValidation: false,
39 | };
40 |
41 | Input.propTypes = {
42 | className: PropTypes.string,
43 | id: PropTypes.string.isRequired,
44 | type: PropTypes.oneOf([
45 | 'text', 'password', 'file', 'checkbox', 'radio', 'select', 'textarea', 'date',
46 | 'hidden', 'month', 'time', 'week', 'color', 'number', 'email', 'url', 'tel',
47 | ]),
48 | hasValidation: PropTypes.bool,
49 | };
50 |
51 | Input.displayName = 'Input';
52 |
53 | export default Input;
54 |
--------------------------------------------------------------------------------
/src/lib/components/Input/Input.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactTestRenderer from 'react-test-renderer';
3 | import { shallow } from 'enzyme';
4 | import Input from './Input';
5 | import Label from './Label';
6 |
7 | describe(' ', () => {
8 | it('should match the default snapshot', () => {
9 | const input = ReactTestRenderer.create(
10 | );
11 | const json = input.toJSON();
12 | expect(json).toMatchSnapshot();
13 | });
14 |
15 | it('should render an input tag by default', () => {
16 | const input = shallow(
17 | ,
18 | );
19 |
20 | expect(input.type()).toBe('input');
21 | });
22 |
23 | it('should render a select tag if type is "select"', () => {
24 | const input = shallow(
25 | ,
26 | );
27 |
28 | expect(input.type()).toBe('select');
29 | });
30 |
31 | it('should render a textarea tag if type is "textarea"', () => {
32 | const input = shallow(
33 | ,
34 | );
35 |
36 | expect(input.type()).toBe('textarea');
37 | });
38 |
39 | it('should render an input tag if type is not a special case', () => {
40 | const input = shallow(
41 | ,
42 | );
43 |
44 | expect(input.type()).toBe('input');
45 | });
46 |
47 | it('should have appropriate validation class if hasValidation prop is true', () => {
48 | const validationClass = 'p-form-validation__input';
49 | const input = shallow(
50 | ,
51 | );
52 |
53 | expect(input.hasClass(validationClass)).toBe(true);
54 | });
55 |
56 | it('should accept custom classnames', () => {
57 | const input = shallow(
58 | ,
59 | );
60 |
61 | expect(input.hasClass('custom-class')).toBe(true);
62 | });
63 | });
64 |
65 | describe('', () => {
66 | it('should match the default snapshot', () => {
67 | const label = ReactTestRenderer.create(
68 |
69 |
70 |
71 |
);
72 | const json = label.toJSON();
73 | expect(json).toMatchSnapshot();
74 | });
75 | });
76 |
--------------------------------------------------------------------------------
/src/lib/components/Input/Label.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import getClassName from '../../utils/getClassName';
4 |
5 | const Label = (props) => {
6 | const {
7 | children, htmlFor, className, ...otherProps
8 | } = props;
9 |
10 | const classNames = getClassName({
11 | [className]: className,
12 | 'p-form__label': true,
13 | }) || undefined;
14 |
15 | return (
16 | {children} // eslint-disable-line
17 | );
18 | };
19 |
20 | Label.defaultProps = {
21 | children: null,
22 | className: undefined,
23 | };
24 |
25 | Label.propTypes = {
26 | children: PropTypes.node,
27 | className: PropTypes.string,
28 | htmlFor: PropTypes.string.isRequired,
29 | };
30 |
31 | Label.displayName = 'Label';
32 |
33 | export default Label;
34 |
--------------------------------------------------------------------------------
/src/lib/components/Input/__snapshots__/Input.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[` should match the default snapshot 1`] = `
4 |
9 | `;
10 |
11 | exports[` should match the default snapshot 1`] = `
12 |
13 |
18 |
22 |
23 | `;
24 |
--------------------------------------------------------------------------------
/src/lib/components/Link/Link.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import getClassName from '../../utils/getClassName';
4 |
5 | class Link extends React.Component {
6 | constructor() {
7 | super();
8 | this.onClick = this.onClick.bind(this);
9 | }
10 |
11 | onClick(event) {
12 | const { onClick } = this.props;
13 |
14 | if (onClick) {
15 | event.preventDefault();
16 | onClick(event);
17 | }
18 | }
19 |
20 | render() {
21 | const {
22 | children, className, soft, strong, inverted, external, top, href,
23 | } = this.props;
24 |
25 | const classNames = getClassName({
26 | [className]: className,
27 | 'p-link': true,
28 | 'p-link--soft': soft,
29 | 'p-link--strong': strong,
30 | 'p-link--inverted': inverted,
31 | 'p-link--external': external,
32 | 'p-top__link': top,
33 | });
34 |
35 | if (top) {
36 | return (
37 |
40 | );
41 | }
42 |
43 | return (
44 | {children}
45 | );
46 | }
47 | }
48 |
49 | Link.defaultProps = {
50 | className: '',
51 | soft: false,
52 | strong: false,
53 | inverted: false,
54 | external: false,
55 | top: false,
56 | onClick: null,
57 | };
58 |
59 | Link.propTypes = {
60 | children: PropTypes.node.isRequired,
61 | className: PropTypes.string,
62 | href: PropTypes.string.isRequired,
63 | soft: PropTypes.bool,
64 | strong: PropTypes.bool,
65 | inverted: PropTypes.bool,
66 | external: PropTypes.bool,
67 | top: PropTypes.bool,
68 | onClick: PropTypes.func,
69 | };
70 |
71 | Link.displayName = 'Link';
72 |
73 | export default Link;
74 |
--------------------------------------------------------------------------------
/src/lib/components/Link/Link.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactTestRenderer from 'react-test-renderer';
3 | import Link from './Link';
4 |
5 | describe('Link component', () => {
6 | it('should render a simple link correctly', () => {
7 | const link = ReactTestRenderer.create(
8 | Default Link,
9 | );
10 | const json = link.toJSON();
11 | expect(json).toMatchSnapshot();
12 | });
13 |
14 | it('should accept modifiers correctly', () => {
15 | const link = ReactTestRenderer.create(
16 |
17 | External Link
18 | Soft Link
19 | Strong Link
20 | Inverted Link
21 | Back to top
22 | External/Strong Link
23 | External/Soft Link
24 | Strong/Back to top Link
25 |
,
26 | );
27 | const json = link.toJSON();
28 | expect(json).toMatchSnapshot();
29 | });
30 | });
31 |
--------------------------------------------------------------------------------
/src/lib/components/Link/__snapshots__/Link.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Link component should accept modifiers correctly 1`] = `
4 |
70 | `;
71 |
72 | exports[`Link component should render a simple link correctly 1`] = `
73 |
78 | Default Link
79 |
80 | `;
81 |
--------------------------------------------------------------------------------
/src/lib/components/List/List.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const getClassName = (modifier) => {
5 | switch (modifier) {
6 | case 'divided':
7 | return 'p-list--divided';
8 | case 'inline':
9 | return 'p-inline-list';
10 | case 'middot':
11 | return 'p-inline-list--middot';
12 | case 'split':
13 | return 'p-list--divided is-split';
14 | default:
15 | return 'p-list';
16 | }
17 | };
18 |
19 | class List extends React.Component {
20 | render() {
21 | const listItems = React.Children.map(this.props.children,
22 | child => React.cloneElement(child, {
23 | modifier: this.props.modifier,
24 | }),
25 | );
26 |
27 | return (
28 |
31 | );
32 | }
33 | }
34 |
35 | List.defaultProps = {
36 | modifier: null,
37 | };
38 |
39 | List.propTypes = {
40 | children: PropTypes.node.isRequired,
41 | modifier: PropTypes.oneOf(['divided', 'inline', 'middot', 'split']),
42 | };
43 |
44 | List.displayName = 'List';
45 |
46 | export default List;
47 |
--------------------------------------------------------------------------------
/src/lib/components/List/List.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactTestRenderer from 'react-test-renderer';
3 | import List from './List';
4 | import ListItem from './ListItem';
5 |
6 | describe('List component', () => {
7 | it('should render a simple list correctly', () => {
8 | const list = ReactTestRenderer.create(
9 |
10 | Lorem
11 | Ipsum
12 | Dolor
13 |
,
14 | );
15 | const json = list.toJSON();
16 | expect(json).toMatchSnapshot();
17 | });
18 |
19 | it('should render a ticked list correctly', () => {
20 | const list = ReactTestRenderer.create(
21 |
22 | Lorem
23 | Ipsum
24 | Dolor
25 |
,
26 | );
27 | const json = list.toJSON();
28 | expect(json).toMatchSnapshot();
29 | });
30 |
31 | it('should render a divided list correctly', () => {
32 | const list = ReactTestRenderer.create(
33 |
34 | Lorem
35 | Ipsum
36 | Dolor
37 |
,
38 | );
39 | const json = list.toJSON();
40 | expect(json).toMatchSnapshot();
41 | });
42 |
43 | it('should render a ticked and divided list correctly', () => {
44 | const list = ReactTestRenderer.create(
45 |
46 | Lorem
47 | Ipsum
48 | Dolor
49 |
,
50 | );
51 | const json = list.toJSON();
52 | expect(json).toMatchSnapshot();
53 | });
54 |
55 | it('should render an inline list correctly', () => {
56 | const list = ReactTestRenderer.create(
57 |
58 | Lorem
59 | Ipsum
60 | Dolor
61 |
,
62 | );
63 | const json = list.toJSON();
64 | expect(json).toMatchSnapshot();
65 | });
66 |
67 | it('should render a middot list correctly', () => {
68 | const list = ReactTestRenderer.create(
69 |
70 | Lorem
71 | Ipsum
72 | Dolor
73 |
,
74 | );
75 | const json = list.toJSON();
76 | expect(json).toMatchSnapshot();
77 | });
78 |
79 | it('should render a split list correctly', () => {
80 | const list = ReactTestRenderer.create(
81 |
82 | Lorem
83 |
84 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean nec ipsum augue. Ut arcu
85 | erat, lacinia sit ametjusto quis. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
86 |
87 | Dolor
88 |
89 | Aenean nec ipsum augue. Ut arcu erat, lacinia sit amet justo quis.
90 |
91 | Ipsum
92 | Ut arcu erat, lacinia sit amet justo quis.
93 |
,
94 | );
95 | const json = list.toJSON();
96 | expect(json).toMatchSnapshot();
97 | });
98 | });
99 |
--------------------------------------------------------------------------------
/src/lib/components/List/ListItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const ListItem = (props) => {
5 | let listItemType;
6 | switch (props.modifier) {
7 | case 'inline':
8 | case 'middot':
9 | listItemType = 'p-inline-list__item';
10 | break;
11 | default:
12 | listItemType = 'p-list__item';
13 | }
14 |
15 | return (
16 |
17 | { props.children }
18 |
19 | );
20 | };
21 |
22 | ListItem.defaultProps = {
23 | ticked: false,
24 | modifier: '',
25 | };
26 |
27 | ListItem.propTypes = {
28 | children: PropTypes.node.isRequired,
29 | ticked: PropTypes.bool,
30 | modifier: PropTypes.string,
31 | };
32 |
33 | ListItem.displayName = 'ListItem';
34 |
35 | export default ListItem;
36 |
--------------------------------------------------------------------------------
/src/lib/components/List/__snapshots__/List.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`List component should render a divided list correctly 1`] = `
4 |
7 |
10 | Lorem
11 |
12 |
15 | Ipsum
16 |
17 |
20 | Dolor
21 |
22 |
23 | `;
24 |
25 | exports[`List component should render a middot list correctly 1`] = `
26 |
29 |
32 | Lorem
33 |
34 |
37 | Ipsum
38 |
39 |
42 | Dolor
43 |
44 |
45 | `;
46 |
47 | exports[`List component should render a simple list correctly 1`] = `
48 |
51 |
54 | Lorem
55 |
56 |
59 | Ipsum
60 |
61 |
64 | Dolor
65 |
66 |
67 | `;
68 |
69 | exports[`List component should render a split list correctly 1`] = `
70 |
73 |
76 | Lorem
77 |
78 |
81 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean nec ipsum augue. Ut arcu erat, lacinia sit ametjusto quis. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
82 |
83 |
86 | Dolor
87 |
88 |
91 | Aenean nec ipsum augue. Ut arcu erat, lacinia sit amet justo quis.
92 |
93 |
96 | Ipsum
97 |
98 |
101 | Ut arcu erat, lacinia sit amet justo quis.
102 |
103 |
104 | `;
105 |
106 | exports[`List component should render a ticked and divided list correctly 1`] = `
107 |
110 |
113 | Lorem
114 |
115 |
118 | Ipsum
119 |
120 |
123 | Dolor
124 |
125 |
126 | `;
127 |
128 | exports[`List component should render a ticked list correctly 1`] = `
129 |
132 |
135 | Lorem
136 |
137 |
140 | Ipsum
141 |
142 |
145 | Dolor
146 |
147 |
148 | `;
149 |
150 | exports[`List component should render an inline list correctly 1`] = `
151 |
154 |
157 | Lorem
158 |
159 |
162 | Ipsum
163 |
164 |
167 | Dolor
168 |
169 |
170 | `;
171 |
--------------------------------------------------------------------------------
/src/lib/components/ListTree/ListTree.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const ListTree = (props) => {
5 | const listTreeChildren = React.Children.map(props.children, (child, index) =>
6 | React.cloneElement(child, { index: `${index}` }),
7 | );
8 |
9 | return (
10 |
11 | {listTreeChildren}
12 |
13 | );
14 | };
15 |
16 | ListTree.propTypes = {
17 | children: PropTypes.node.isRequired,
18 | };
19 |
20 | ListTree.displayName = 'ListTree';
21 |
22 | export default ListTree;
23 |
--------------------------------------------------------------------------------
/src/lib/components/ListTree/ListTree.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { storiesOf } from '@storybook/react';
3 | import { withInfo } from '@storybook/addon-info';
4 |
5 | import ListTree from './ListTree';
6 | import ListTreeGroup from './ListTreeGroup';
7 | import ListTreeItem from './ListTreeItem';
8 | import Link from '../Link/Link';
9 | import Image from '../Image/Image';
10 |
11 | storiesOf('List Tree', module)
12 | .add('Default',
13 | withInfo('The ListTree component can be used to show a directory style listing, such as a list of files and folders within a directory.')(() => (
14 |
15 |
16 | file
17 |
18 |
19 | charm-helpers-sync.yaml
20 |
21 |
22 | config.yaml
23 |
24 |
25 | default_rsync
26 | nagios_plugin.py
27 |
28 | check_mem.pl
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | ),
39 | ),
40 | );
41 |
--------------------------------------------------------------------------------
/src/lib/components/ListTree/ListTree.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactTestRenderer from 'react-test-renderer';
3 | import ListTree from './ListTree';
4 | import ListTreeGroup from './ListTreeGroup';
5 | import ListTreeItem from './ListTreeItem';
6 |
7 | describe('ListTree component', () => {
8 | it('should render a basic ListTree correctly', () => {
9 | const listTree = ReactTestRenderer.create(
10 |
11 |
12 | file
13 |
14 | );
15 | const json = listTree.toJSON();
16 | expect(json).toMatchSnapshot();
17 | });
18 |
19 | it('should render a nested ListTree correctly', () => {
20 | const listTree = ReactTestRenderer.create(
21 |
22 |
23 |
24 |
25 | file
26 |
27 |
28 |
29 | );
30 | const json = listTree.toJSON();
31 | expect(json).toMatchSnapshot();
32 | });
33 | });
34 |
--------------------------------------------------------------------------------
/src/lib/components/ListTree/ListTreeGroup.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | class ListTreeGroup extends React.Component {
5 | constructor() {
6 | super();
7 | this.onClick = this.onClick.bind(this);
8 | this.state = {
9 | open: false,
10 | };
11 | }
12 |
13 | onClick() {
14 | const { open } = this.state;
15 | this.setState({ open: !open });
16 | }
17 |
18 | render() {
19 | const { index } = this.props;
20 | const listTreeGroupChildren = React.Children.map(this.props.children, (child, key) =>
21 | React.cloneElement(child, { index: `${index}-${key}` }),
22 | );
23 |
24 | return (
25 |
26 |
34 | {this.props.name}
35 |
36 |
43 | {listTreeGroupChildren}
44 |
45 |
46 | );
47 | }
48 | }
49 |
50 | ListTreeGroup.defaultProps = {
51 | index: '0',
52 | };
53 |
54 | ListTreeGroup.propTypes = {
55 | children: PropTypes.node.isRequired,
56 | index: PropTypes.string,
57 | name: PropTypes.string.isRequired,
58 | };
59 |
60 | ListTreeGroup.displayName = 'ListTreeGroup';
61 |
62 | export default ListTreeGroup;
63 |
--------------------------------------------------------------------------------
/src/lib/components/ListTree/ListTreeItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const ListTreeItem = props => (
5 |
6 | {props.children}
7 |
8 | );
9 |
10 | ListTreeItem.defaultProps = {
11 | index: '0',
12 | };
13 |
14 | ListTreeItem.propTypes = {
15 | children: PropTypes.node.isRequired,
16 | index: PropTypes.string,
17 | };
18 |
19 | ListTreeItem.displayName = 'ListTreeItem';
20 |
21 | export default ListTreeItem;
22 |
--------------------------------------------------------------------------------
/src/lib/components/ListTree/__snapshots__/ListTree.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`ListTree component should render a basic ListTree correctly 1`] = `
4 |
9 |
12 |
20 | /group
21 |
22 |
35 |
36 |
37 | `;
38 |
39 | exports[`ListTree component should render a nested ListTree correctly 1`] = `
40 |
45 |
48 |
56 | /depth1
57 |
58 |
65 |
68 |
76 | /depth2
77 |
78 |
85 |
88 |
96 | /depth3
97 |
98 |
105 |
108 | file
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 | `;
118 |
--------------------------------------------------------------------------------
/src/lib/components/Matrix/Matrix.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import MatrixItem from './MatrixItem';
4 |
5 | class Matrix extends React.Component {
6 | constructor() {
7 | super();
8 | this.generateExtras = this.generateExtras.bind(this);
9 | }
10 |
11 | generateExtras() {
12 | const { children } = this.props;
13 | const emptyItemCount = (2 * React.Children.count(children)) % 3;
14 | const extraMatrixItems = [];
15 |
16 | for (let i = 0; i < emptyItemCount; i += 1) {
17 | extraMatrixItems.push( );
18 | }
19 |
20 | return extraMatrixItems;
21 | }
22 |
23 | render() {
24 | const { children } = this.props;
25 |
26 | // Generate extra MatrixItems to fill empty space in a row
27 | const extraMatrixItems = this.generateExtras();
28 |
29 | return (
30 |
31 | { children }
32 | { extraMatrixItems.length > 0 && extraMatrixItems }
33 |
34 | );
35 | }
36 | }
37 |
38 | Matrix.defaultProps = {
39 | children: null,
40 | };
41 |
42 | Matrix.propTypes = {
43 | children: (props, propName, componentName) => {
44 | const prop = props[propName];
45 | let error = null;
46 |
47 | React.Children.forEach(prop, (child) => {
48 | if (child.type !== MatrixItem) {
49 | error = new Error(`${componentName} children should be of type "MatrixItem".`);
50 | }
51 | });
52 |
53 | return error;
54 | },
55 | };
56 |
57 | Matrix.displayName = 'Matrix';
58 |
59 | export default Matrix;
60 |
--------------------------------------------------------------------------------
/src/lib/components/Matrix/Matrix.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { storiesOf } from '@storybook/react';
3 | import { text } from '@storybook/addon-knobs';
4 | import { withInfo } from '@storybook/addon-info';
5 |
6 | import Matrix from './Matrix';
7 | import MatrixItem from './MatrixItem';
8 |
9 | storiesOf('Matrix', module)
10 | .add('Default',
11 | withInfo('The Matrix component can be useful to display a selection of items in a format that is less linear than a normal list, using an image to describe each item. MatrixItem components will display in one column on small screens. At resolutions above $breakpoint-medium, the Matrix switches to three items per row.')(() => (
12 |
13 |
18 | {text('Text', 'Short description')}
19 |
20 |
21 | Short description
22 |
23 |
24 | Short description
25 |
26 |
27 | Short description
28 |
29 |
30 | Short description
31 |
32 |
33 | Short description
34 |
35 |
36 | Short description
37 |
38 |
39 | Short description
40 |
41 | ),
42 | ),
43 | );
44 |
--------------------------------------------------------------------------------
/src/lib/components/Matrix/Matrix.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactTestRenderer from 'react-test-renderer';
3 | import Matrix from './Matrix';
4 | import MatrixItem from './MatrixItem';
5 |
6 | describe('Matrix component', () => {
7 | it('should render a Matrix with one MatrixItem correctly', () => {
8 | const matrix = ReactTestRenderer.create(
9 |
10 |
11 | description
12 |
13 | ,
14 | );
15 | const json = matrix.toJSON();
16 | expect(json).toMatchSnapshot();
17 | });
18 |
19 | it('should render a Matrix with multiple MatrixItem correctly', () => {
20 | const matrix = ReactTestRenderer.create(
21 |
22 |
23 | description
24 |
25 |
26 | description
27 |
28 |
29 | description
30 |
31 | ,
32 | );
33 | const json = matrix.toJSON();
34 | expect(json).toMatchSnapshot();
35 | });
36 |
37 | it('should render a title when title prop provided', () => {
38 | const matrix = ReactTestRenderer.create(
39 |
40 |
41 | description
42 |
43 | ,
44 | );
45 | const json = matrix.toJSON();
46 | expect(json).toMatchSnapshot();
47 | });
48 |
49 | it('should convert title to link when href prop provided', () => {
50 | const matrix = ReactTestRenderer.create(
51 |
52 |
53 | description
54 |
55 | ,
56 | );
57 | const json = matrix.toJSON();
58 | expect(json).toMatchSnapshot();
59 | });
60 |
61 | it('should render an image when img prop provided', () => {
62 | const matrix = ReactTestRenderer.create(
63 |
64 |
65 | description
66 |
67 | ,
68 | );
69 | const json = matrix.toJSON();
70 | expect(json).toMatchSnapshot();
71 | });
72 |
73 | it('should auto-fill to have a multiple of three MatrixItem children', () => {
74 | const matrix = ReactTestRenderer.create(
75 |
76 |
77 | description
78 |
79 |
80 | description
81 |
82 |
83 | description
84 |
85 |
86 | description
87 |
88 | ,
89 | );
90 | const json = matrix.toJSON();
91 | expect(json).toMatchSnapshot();
92 | });
93 | });
94 |
--------------------------------------------------------------------------------
/src/lib/components/Matrix/MatrixItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import getClassName from '../../utils/getClassName';
4 |
5 | const MatrixItem = (props) => {
6 | const {
7 | children, className, href, img, title,
8 | } = props;
9 |
10 | const classNames = getClassName({
11 | [className]: className,
12 | 'p-matrix__item': true,
13 | }) || undefined;
14 |
15 | return (
16 |
17 | {img && }
18 |
19 | {title &&
20 |
21 | {href ? {title} : title}
22 | }
23 |
24 | { children }
25 |
26 |
27 |
28 | );
29 | };
30 |
31 | MatrixItem.defaultProps = {
32 | children: null,
33 | className: undefined,
34 | href: '',
35 | img: null,
36 | title: '',
37 | };
38 |
39 | MatrixItem.propTypes = {
40 | children: PropTypes.node,
41 | className: PropTypes.string,
42 | img: PropTypes.shape({
43 | src: PropTypes.string,
44 | alt: PropTypes.string,
45 | }),
46 | href: PropTypes.string,
47 | title: PropTypes.string,
48 | };
49 |
50 | MatrixItem.displayName = 'MatrixItem';
51 |
52 | export default MatrixItem;
53 |
--------------------------------------------------------------------------------
/src/lib/components/MediaObject/MediaObject.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import getClassName from '../../utils/getClassName';
4 |
5 | class MediaObject extends React.Component {
6 | constructor() {
7 | super();
8 |
9 | this.generateMetadata = this.generateMetadata.bind(this);
10 | }
11 |
12 | generateMetadata() {
13 | const { metadata } = this.props;
14 |
15 | const metadataItems = metadata.map((item, index) => {
16 | const { description, type } = item;
17 | const key = `${type}${index}`;
18 | const metadataClass = getClassName({
19 | 'p-media-object__meta-list-item': !type,
20 | 'p-media-object__meta-list-item--date': type === 'date',
21 | 'p-media-object__meta-list-item--location': type === 'location',
22 | 'p-media-object__meta-list-item--venue': type === 'venue',
23 | }) || undefined;
24 |
25 | return (
26 | {description}
27 | );
28 | });
29 |
30 | return (
31 |
32 | {metadataItems}
33 |
34 | );
35 | }
36 |
37 | render() {
38 | const {
39 | children, className, href, img, large, metadata, round, title,
40 | } = this.props;
41 |
42 | const containerClass = getClassName({
43 | [className]: className,
44 | 'p-media-object': !large,
45 | 'p-media-object--large': large,
46 | }) || undefined;
47 |
48 | const imgClass = getClassName({
49 | 'p-media-object__image': true,
50 | 'is-round': round,
51 | } || undefined);
52 |
53 | return (
54 |
55 | {img &&
56 |
57 | }
58 |
59 | {title &&
60 |
61 | {href ? {title} : title}
62 |
63 | }
64 | {children &&
65 |
66 | {children}
67 |
68 | }
69 | {metadata && this.generateMetadata()}
70 |
71 |
72 | );
73 | }
74 | }
75 |
76 | MediaObject.defaultProps = {
77 | children: undefined,
78 | className: undefined,
79 | href: undefined,
80 | img: undefined,
81 | large: false,
82 | metadata: undefined,
83 | round: false,
84 | title: undefined,
85 | };
86 |
87 | MediaObject.propTypes = {
88 | children: PropTypes.node,
89 | className: PropTypes.string,
90 | href: PropTypes.string,
91 | img: PropTypes.shape({
92 | alt: PropTypes.string,
93 | src: PropTypes.string,
94 | }),
95 | large: PropTypes.bool,
96 | metadata: PropTypes.arrayOf(
97 | PropTypes.shape({
98 | description: PropTypes.string,
99 | type: PropTypes.string,
100 | }),
101 | ),
102 | round: PropTypes.bool,
103 | title: PropTypes.string,
104 | };
105 |
106 | MediaObject.displayName = 'MediaObject';
107 |
108 | export default MediaObject;
109 |
--------------------------------------------------------------------------------
/src/lib/components/MediaObject/MediaObject.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { storiesOf } from '@storybook/react';
3 | import { boolean, select, text } from '@storybook/addon-knobs';
4 | import { withInfo } from '@storybook/addon-info';
5 |
6 | import MediaObject from './MediaObject';
7 |
8 | const metadataOptions = [undefined, 'date', 'location', 'venue'];
9 |
10 | storiesOf('Media Object', module)
11 | .add('Default',
12 | withInfo('The MediaObject component should be used to display events or articles.')(() => (
13 |
35 | {text('Description', 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.')}
36 | ),
37 | ),
38 | )
39 |
40 | .add('Round',
41 | withInfo('You can add the "round" prop to the MediaObject component to create a circular image style, which we recommend to be used for head shots of people.')(() => (
42 |
55 | {text('Description', 'Lorem ipsum dolor sit amet')}
56 | ),
57 | ),
58 | )
59 |
60 | .add('Large',
61 | withInfo('Add the "large" prop to display details of a single object on a page.')(() => (
62 |
69 | {text('Description', 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.')}
70 | ),
71 | ),
72 | );
73 |
--------------------------------------------------------------------------------
/src/lib/components/Modal/Modal.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const Modal = (props) => {
5 | const {
6 | children, onClick, show, title,
7 | } = props;
8 |
9 | return (
10 |
14 |
20 |
21 | { title }
22 |
27 | Close
28 |
29 |
30 | { children }
31 |
32 |
33 | );
34 | };
35 |
36 | Modal.defaultProps = {
37 | onClick: () => 1,
38 | show: false,
39 | title: null,
40 | };
41 |
42 | Modal.propTypes = {
43 | children: PropTypes.node.isRequired,
44 | onClick: PropTypes.func,
45 | show: PropTypes.bool,
46 | title: PropTypes.string,
47 | };
48 |
49 | Modal.displayName = 'Modal';
50 |
51 | export default Modal;
52 |
--------------------------------------------------------------------------------
/src/lib/components/Modal/Modal.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { storiesOf } from '@storybook/react';
3 | import { boolean, text } from '@storybook/addon-knobs';
4 | import { withInfo } from '@storybook/addon-info';
5 |
6 | import Modal from './Modal';
7 | import Button from '../Button/Button';
8 | import Image from '../Image/Image';
9 |
10 | class ModalExample extends React.Component {
11 | constructor() {
12 | super();
13 | this.state = { modal: false };
14 | this.toggleModal = this.toggleModal.bind(this);
15 | }
16 |
17 | toggleModal() {
18 | this.setState({ modal: !this.state.modal });
19 | }
20 |
21 | render() {
22 | return (
23 |
24 |
25 |
26 | {text('Text', 'This is a Modal with a picture of a cat. Check it out!')}
27 |
33 |
34 |
35 | );
36 | }
37 | }
38 |
39 | storiesOf('Modal', module)
40 | .add('Default',
41 | withInfo('The Modal component can be useful to overlay an area of the screen which can contain a prompt, dialog or interaction. Note that opening and closing the Modal requires props from a parent component, for example by adjusting it in the Knobs panel in Storybook.')(() => (
42 |
43 |
44 | {text('Description', 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Dolores sed, distinctio, alias accusantium veritatis magni magnam sapiente amet possimus in quis nisi autem iste, ullam facilis, deleniti voluptas enim. Perspiciatis vero eius debitis deserunt beatae doloribus tenetur accusantium consectetur nesciunt, minus, magni pariatur. Autem ipsa excepturi quod ducimus debitis voluptas!')}
45 |
46 |
),
47 | ),
48 | )
49 |
50 | .add('Example',
51 | withInfo('This is an example of how a Modal might be used in an application, in which the parent component (ModalExample) controls the \'show\' prop.')(() => (
52 | ),
53 | ),
54 | );
55 |
--------------------------------------------------------------------------------
/src/lib/components/Modal/Modal.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactTestRenderer from 'react-test-renderer';
3 | import Modal from './Modal';
4 |
5 | describe('Modal component', () => {
6 | it('should be hidden if show prop is false', () => {
7 | const modal = ReactTestRenderer.create(
8 |
9 | Modal text
10 | );
11 | const json = modal.toJSON();
12 | expect(json).toMatchSnapshot();
13 | });
14 |
15 | it('should show if show prop is true', () => {
16 | const modal = ReactTestRenderer.create(
17 |
18 | Modal text
19 | );
20 | const json = modal.toJSON();
21 | expect(json).toMatchSnapshot();
22 | });
23 |
24 | it('should display a title if prop is provided', () => {
25 | const modal = ReactTestRenderer.create(
26 |
27 | Modal text
28 | );
29 | const json = modal.toJSON();
30 | expect(json).toMatchSnapshot();
31 | });
32 | });
33 |
--------------------------------------------------------------------------------
/src/lib/components/Modal/__snapshots__/Modal.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Modal component should be hidden if show prop is false 1`] = `
4 |
12 |
18 |
21 |
24 |
29 | Close
30 |
31 |
32 |
33 | Modal text
34 |
35 |
36 |
37 | `;
38 |
39 | exports[`Modal component should display a title if prop is provided 1`] = `
40 |
48 |
54 |
57 |
60 | Modal title
61 |
62 |
67 | Close
68 |
69 |
70 |
71 | Modal text
72 |
73 |
74 |
75 | `;
76 |
77 | exports[`Modal component should show if show prop is true 1`] = `
78 |
86 |
92 |
95 |
98 |
103 | Close
104 |
105 |
106 |
107 | Modal text
108 |
109 |
110 |
111 | `;
112 |
--------------------------------------------------------------------------------
/src/lib/components/MutedHeading/MutedHeading.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const MutedHeading = props => (
5 |
6 | {props.children}
7 |
8 | );
9 |
10 | MutedHeading.defaultProps = {
11 | children: '',
12 | };
13 |
14 | MutedHeading.propTypes = {
15 | children: PropTypes.node,
16 | };
17 |
18 | MutedHeading.displayName = 'MutedHeading';
19 |
20 | export default MutedHeading;
21 |
--------------------------------------------------------------------------------
/src/lib/components/MutedHeading/MutedHeading.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { storiesOf } from '@storybook/react';
3 | import { text } from '@storybook/addon-knobs';
4 | import { withInfo } from '@storybook/addon-info';
5 |
6 | import MutedHeading from './MutedHeading';
7 |
8 | storiesOf('Muted Heading', module)
9 | .add('Default',
10 | withInfo('The MutedHeading component can be used to introduce a collection of icons or images.')(() => (
11 | {text('Text', 'Muted heading')} ),
12 | ),
13 | );
14 |
--------------------------------------------------------------------------------
/src/lib/components/MutedHeading/MutedHeading.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactTestRenderer from 'react-test-renderer';
3 | import MutedHeading from './MutedHeading';
4 |
5 | describe('MutedHeading component', () => {
6 | it('should render correctly', () => {
7 | const mutedHeading = ReactTestRenderer.create(
8 | Muted heading );
9 | const json = mutedHeading.toJSON();
10 | expect(json).toMatchSnapshot();
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/src/lib/components/MutedHeading/__snapshots__/MutedHeading.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`MutedHeading component should render correctly 1`] = `
4 |
7 | Muted heading
8 |
9 | `;
10 |
--------------------------------------------------------------------------------
/src/lib/components/Navigation/Navigation.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import NavigationBanner from './NavigationBanner';
4 | import NavigationLink from './NavigationLink';
5 |
6 | class Navigation extends React.Component {
7 | render() {
8 | const { children } = this.props;
9 | const links = [];
10 | let banner;
11 |
12 | React.Children.forEach(children, (child) => {
13 | if (child.type === NavigationBanner) {
14 | banner = child;
15 | } else if (child.type === NavigationLink) {
16 | links.push(child);
17 | }
18 | });
19 |
20 | return (
21 |
22 | { banner }
23 | { links.length > 0 &&
24 |
25 |
28 |
29 | }
30 |
31 | );
32 | }
33 | }
34 |
35 | Navigation.defaultProps = {
36 | children: null,
37 | };
38 |
39 | Navigation.propTypes = {
40 | children: (props, propName, componentName) => {
41 | const prop = props[propName];
42 | let error = null;
43 | let count = 0;
44 |
45 | React.Children.forEach(prop, (child) => {
46 | if (child.type === NavigationBanner) {
47 | count += 1;
48 | }
49 | });
50 |
51 | if (count !== 1) {
52 | error = new Error(`${componentName} should have exactly one child of type "NavigationBanner".`);
53 | }
54 |
55 | return error;
56 | },
57 | };
58 |
59 | Navigation.displayName = 'Navigation';
60 |
61 | export default Navigation;
62 |
--------------------------------------------------------------------------------
/src/lib/components/Navigation/Navigation.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { storiesOf } from '@storybook/react';
3 | import { boolean, text } from '@storybook/addon-knobs';
4 | import { withInfo } from '@storybook/addon-info';
5 |
6 | import Navigation from './Navigation';
7 | import NavigationBanner from './NavigationBanner';
8 | import NavigationLink from './NavigationLink';
9 |
10 | storiesOf('Navigation', module)
11 | .add('Text Banner',
12 | withInfo('The Navigation component can be added to the top of your sites. NavigationLink components are collapsed behind a "Menu" link in small screens and displayed horizontally on larger screens.')(() => (
13 |
14 |
18 |
23 |
28 |
33 | ),
34 | ),
35 | )
36 |
37 | .add('Logo Banner',
38 | withInfo('A logo object prop can also be passed to NavigationBanner, which will replace a simple text banner.')(() => (
39 |
40 |
47 |
52 |
57 |
62 | ),
63 | ),
64 | );
65 |
--------------------------------------------------------------------------------
/src/lib/components/Navigation/Navigation.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactTestRenderer from 'react-test-renderer';
3 | import Navigation from './Navigation';
4 | import NavigationBanner from './NavigationBanner';
5 | import NavigationLink from './NavigationLink';
6 |
7 | describe('', () => {
8 | it('renders with a text-based NavigationBanner correctly', () => {
9 | const navigation = ReactTestRenderer.create(
10 |
11 |
12 | ,
13 | );
14 | const json = navigation.toJSON();
15 | expect(json).toMatchSnapshot();
16 | });
17 |
18 | it('renders with a logo-based NavigationBanner correctly', () => {
19 | const navigation = ReactTestRenderer.create(
20 |
21 |
25 | ,
26 | );
27 | const json = navigation.toJSON();
28 | expect(json).toMatchSnapshot();
29 | });
30 |
31 | it('renders with a single NavigationLink correctly', () => {
32 | const navigation = ReactTestRenderer.create(
33 |
34 |
35 |
36 | ,
37 | );
38 | const json = navigation.toJSON();
39 | expect(json).toMatchSnapshot();
40 | });
41 |
42 | it('renders with multiple NavigationLinks correctly', () => {
43 | const navigation = ReactTestRenderer.create(
44 |
45 |
46 |
47 |
48 |
49 | ,
50 | );
51 | const json = navigation.toJSON();
52 | expect(json).toMatchSnapshot();
53 | });
54 | });
55 |
--------------------------------------------------------------------------------
/src/lib/components/Navigation/NavigationBanner.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | class NavigationBanner extends React.Component {
5 | render() {
6 | const {
7 | href, logo, menuText, title,
8 | } = this.props;
9 |
10 | const Tag = href ? 'a' : 'div';
11 |
12 | return (
13 |
28 | );
29 | }
30 | }
31 |
32 | NavigationBanner.defaultProps = {
33 | href: null,
34 | logo: { src: null, alt: '' },
35 | menuText: { open: 'Menu', close: 'Close menu' },
36 | title: null,
37 | };
38 |
39 | NavigationBanner.propTypes = {
40 | href: PropTypes.string,
41 | logo: PropTypes.shape({ src: PropTypes.string, alt: PropTypes.string }),
42 | menuText: PropTypes.shape({ open: PropTypes.string, close: PropTypes.string }),
43 | title: PropTypes.string,
44 | };
45 |
46 | NavigationBanner.displayName = 'NavigationBanner';
47 |
48 | export default NavigationBanner;
49 |
--------------------------------------------------------------------------------
/src/lib/components/Navigation/NavigationLink.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import getClassName from '../../utils/getClassName';
4 |
5 | const NavigationLink = (props) => {
6 | const { href, label, selected } = props;
7 |
8 | const className = getClassName({
9 | 'p-navigation__link': true,
10 | 'is-selected': selected,
11 | });
12 |
13 | return (
14 |
15 |
16 | {label}
17 |
18 |
19 | );
20 | };
21 |
22 | NavigationLink.defaultProps = {
23 | selected: false,
24 | };
25 |
26 | NavigationLink.propTypes = {
27 | label: PropTypes.string.isRequired,
28 | href: PropTypes.string.isRequired,
29 | selected: PropTypes.bool,
30 | };
31 |
32 | NavigationLink.displayName = 'NavigationLink';
33 |
34 | export default NavigationLink;
35 |
--------------------------------------------------------------------------------
/src/lib/components/Notification/Notification.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import getClassName from '../../utils/getClassName';
4 |
5 | class Notification extends React.Component {
6 | constructor(props) {
7 | super(props);
8 | this.handleClick = this.handleClick.bind(this);
9 | this.state = {
10 | visible: props.visible,
11 | };
12 | }
13 |
14 | componentWillReceiveProps(nextProps) {
15 | if (nextProps.visible !== this.props.visible) {
16 | this.setState({ visible: nextProps.visible });
17 | }
18 | }
19 |
20 | handleClick(event) {
21 | const { onClick } = this.props;
22 | const { visible } = this.state;
23 |
24 | if (onClick) {
25 | onClick(event);
26 | } else {
27 | this.setState({ visible: !visible });
28 | }
29 | }
30 |
31 | render() {
32 | const {
33 | action, children, className, dismissable, status, caution, information, negative, positive,
34 | onClick,
35 | } = this.props;
36 | const { visible } = this.state;
37 |
38 | // XXX Can remove when bug is fixed in Vanilla Sass
39 | // https://github.com/vanilla-framework/vanilla-framework/issues/1928
40 | const styleFix = {
41 | backgroundColor: 'transparent',
42 | backgroundSize: '1rem',
43 | border: '0',
44 | margin: '1.1875rem 1rem auto auto',
45 | padding: '.5rem',
46 | };
47 |
48 | const classNames = getClassName({
49 | [className]: className,
50 | 'p-notification--information': information,
51 | 'p-notification--positive': positive,
52 | 'p-notification--caution': caution,
53 | 'p-notification--negative': negative,
54 | }) || 'p-notification';
55 |
56 | return (
57 |
58 |
59 | {status && {status} }
60 | {children}
61 | {action && {action.value} }
62 |
63 | {(dismissable || onClick) &&
64 |
70 | Close
71 |
72 | }
73 |
74 | );
75 | }
76 | }
77 |
78 | Notification.defaultProps = {
79 | action: null,
80 | caution: false,
81 | className: undefined,
82 | dismissable: true,
83 | information: false,
84 | negative: false,
85 | onClick: undefined,
86 | positive: false,
87 | status: null,
88 | visible: true,
89 | };
90 |
91 | Notification.propTypes = {
92 | action: PropTypes.shape({
93 | value: PropTypes.string,
94 | href: PropTypes.string,
95 | }),
96 | caution: PropTypes.bool,
97 | children: PropTypes.node.isRequired,
98 | className: PropTypes.string,
99 | dismissable: PropTypes.bool,
100 | information: PropTypes.bool,
101 | negative: PropTypes.bool,
102 | onClick: PropTypes.func,
103 | positive: PropTypes.bool,
104 | status: PropTypes.string,
105 | visible: PropTypes.bool,
106 | };
107 |
108 | Notification.displayName = 'Notification';
109 |
110 | export default Notification;
111 |
--------------------------------------------------------------------------------
/src/lib/components/Notification/Notification.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactTestRenderer from 'react-test-renderer';
3 | import Notification from './Notification';
4 |
5 | describe('Notification component', () => {
6 | it('should render a default Notification', () => {
7 | const notification = ReactTestRenderer.create(
8 |
9 | This is a default Notification
10 | );
11 | const json = notification.toJSON();
12 | expect(json).toMatchSnapshot();
13 | });
14 |
15 | it('should render a status if prop is provided', () => {
16 | const notification = ReactTestRenderer.create(
17 |
18 | This is a Notification with a status
19 | );
20 | const json = notification.toJSON();
21 | expect(json).toMatchSnapshot();
22 | });
23 |
24 | it('should render an action if prop is provided', () => {
25 | const notification = ReactTestRenderer.create(
26 |
27 | This is a Notification with an action
28 | );
29 | const json = notification.toJSON();
30 | expect(json).toMatchSnapshot();
31 | });
32 |
33 | it('should render an information Notification', () => {
34 | const notification = ReactTestRenderer.create(
35 |
36 | This is an information Notification
37 | );
38 | const json = notification.toJSON();
39 | expect(json).toMatchSnapshot();
40 | });
41 |
42 | it('should render a positive Notification', () => {
43 | const notification = ReactTestRenderer.create(
44 |
45 | This is a positive Notification
46 | );
47 | const json = notification.toJSON();
48 | expect(json).toMatchSnapshot();
49 | });
50 |
51 | it('should render a caution Notification', () => {
52 | const notification = ReactTestRenderer.create(
53 |
54 | This is a caution Notification
55 | );
56 | const json = notification.toJSON();
57 | expect(json).toMatchSnapshot();
58 | });
59 |
60 | it('should render a negative Notification', () => {
61 | const notification = ReactTestRenderer.create(
62 |
63 | This is a negative Notification
64 | );
65 | const json = notification.toJSON();
66 | expect(json).toMatchSnapshot();
67 | });
68 | });
69 |
70 |
--------------------------------------------------------------------------------
/src/lib/components/Pagination/Pagination.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import PaginationItem from './PaginationItem';
4 |
5 | const Pagination = props => (
6 |
7 | { props.children }
8 |
9 | );
10 |
11 | Pagination.defaultProps = {
12 | children: null,
13 | };
14 |
15 | Pagination.propTypes = {
16 | children: (props, propName, componentName) => {
17 | const prop = props[propName];
18 | let error = null;
19 |
20 | React.Children.forEach(prop, (child) => {
21 | if (child.type !== PaginationItem) {
22 | error = new Error(`${componentName} children should be of type "PaginationItem".`);
23 | }
24 | });
25 |
26 | return error;
27 | },
28 | };
29 |
30 | Pagination.displayName = 'Pagination';
31 |
32 | export default Pagination;
33 |
--------------------------------------------------------------------------------
/src/lib/components/Pagination/Pagination.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { storiesOf } from '@storybook/react';
3 | import { boolean, text } from '@storybook/addon-knobs';
4 | import { withInfo } from '@storybook/addon-info';
5 |
6 | import Pagination from './Pagination';
7 | import PaginationItem from './PaginationItem';
8 |
9 | storiesOf('Pagination', module)
10 | .add('Next',
11 | withInfo('The Pagination component should be used to navigate from one article to the next or previous in a chronological fashion.')(() => (
12 |
13 |
20 | ),
21 | ),
22 | )
23 |
24 | .add('Previous',
25 | withInfo('info')(() => (
26 |
27 |
34 | ),
35 | ),
36 | )
37 |
38 | .add('Both',
39 | withInfo('info')(() => (
40 |
41 |
47 |
53 | ),
54 | ),
55 | );
56 |
--------------------------------------------------------------------------------
/src/lib/components/Pagination/Pagination.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactTestRenderer from 'react-test-renderer';
3 |
4 | import Pagination from './Pagination';
5 | import PaginationItem from './PaginationItem';
6 |
7 | describe('', () => {
8 | it('renders default Pagination with PaginationItem correctly', () => {
9 | const pagination = ReactTestRenderer.create(
10 |
11 |
12 | ,
13 | );
14 | const json = pagination.toJSON();
15 | expect(json).toMatchSnapshot();
16 | });
17 |
18 | it('renders PaginationItem with next prop correctly', () => {
19 | const pagination = ReactTestRenderer.create(
20 |
21 |
22 | ,
23 | );
24 | const json = pagination.toJSON();
25 | expect(json).toMatchSnapshot();
26 | });
27 |
28 | it('renders PaginationItem with previous prop correctly', () => {
29 | const pagination = ReactTestRenderer.create(
30 |
31 |
32 | ,
33 | );
34 | const json = pagination.toJSON();
35 | expect(json).toMatchSnapshot();
36 | });
37 | });
38 |
--------------------------------------------------------------------------------
/src/lib/components/Pagination/PaginationItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const PaginationItem = (props) => {
5 | const {
6 | href, label, next, previous, title,
7 | } = props;
8 |
9 | let className = 'p-pagination__link';
10 |
11 | if (next) {
12 | className += '--next';
13 | } else if (previous) {
14 | className += '--previous';
15 | }
16 |
17 | return (
18 |
19 | {label}
20 | {title}
21 |
22 | );
23 | };
24 |
25 | PaginationItem.defaultProps = {
26 | href: null,
27 | label: '',
28 | next: false,
29 | previous: false,
30 | title: '',
31 | };
32 |
33 | PaginationItem.propTypes = {
34 | href: PropTypes.string,
35 | label: PropTypes.string,
36 | next: PropTypes.bool,
37 | previous: PropTypes.bool,
38 | title: PropTypes.string,
39 | };
40 |
41 | PaginationItem.displayName = 'PaginationItem';
42 |
43 | export default PaginationItem;
44 |
--------------------------------------------------------------------------------
/src/lib/components/Pagination/__snapshots__/Pagination.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[` renders PaginationItem with next prop correctly 1`] = `
4 |
23 | `;
24 |
25 | exports[` renders PaginationItem with previous prop correctly 1`] = `
26 |
45 | `;
46 |
47 | exports[` renders default Pagination with PaginationItem correctly 1`] = `
48 |
67 | `;
68 |
--------------------------------------------------------------------------------
/src/lib/components/SideNav/SideNav.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import SideNavBanner from './SideNavBanner';
4 |
5 | class SideNav extends React.Component {
6 | constructor() {
7 | super();
8 | this.handleMenuClick = this.handleMenuClick.bind(this);
9 |
10 | this.state = { menuOpen: false };
11 | }
12 |
13 | handleMenuClick() {
14 | const { menuOpen } = this.state;
15 | this.setState({ menuOpen: !menuOpen });
16 | }
17 |
18 | render() {
19 | const { children } = this.props;
20 | const { menuOpen } = this.state;
21 | const content = [];
22 | let banner;
23 |
24 | React.Children.forEach(children, (child) => {
25 | if (child.type === SideNavBanner) {
26 | banner = React.cloneElement(child, {
27 | ...this.props,
28 | onClick: this.handleMenuClick,
29 | open: this.state.menuOpen,
30 | });
31 | } else {
32 | content.push(child);
33 | }
34 | });
35 |
36 | return (
37 |
38 | { banner }
39 |
44 |
45 | );
46 | }
47 | }
48 |
49 | SideNav.defaultProps = {
50 | children: null,
51 | };
52 |
53 | SideNav.propTypes = {
54 | children: (props, propName, componentName) => {
55 | const prop = props[propName];
56 | let error = null;
57 | let count = 0;
58 |
59 | React.Children.forEach(prop, (child) => {
60 | if (child.type === SideNavBanner) {
61 | count += 1;
62 | }
63 | });
64 |
65 | if (count !== 1) {
66 | error = new Error(`${componentName} should have exactly one child of type "SideNavBanner".`);
67 | }
68 |
69 | return error;
70 | },
71 | };
72 |
73 | SideNav.displayName = 'SideNav';
74 |
75 | export default SideNav;
76 |
--------------------------------------------------------------------------------
/src/lib/components/SideNav/SideNav.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactTestRenderer from 'react-test-renderer';
3 |
4 | import SideNav from './SideNav';
5 | import SideNavBanner from './SideNavBanner';
6 | import SideNavGroup from './SideNavGroup';
7 | import SideNavLink from './SideNavLink';
8 |
9 | describe('', () => {
10 | it('renders with a text-based SideNavBanner correctly', () => {
11 | const sidenav = ReactTestRenderer.create(
12 |
13 |
14 | ,
15 | );
16 | const json = sidenav.toJSON();
17 | expect(json).toMatchSnapshot();
18 | });
19 |
20 | it('renders with a logo-based SideNavBanner correctly', () => {
21 | const sidenav = ReactTestRenderer.create(
22 |
23 |
27 | ,
28 | );
29 | const json = sidenav.toJSON();
30 | expect(json).toMatchSnapshot();
31 | });
32 |
33 | it('renders with a single SideNavLink correctly', () => {
34 | const sidenav = ReactTestRenderer.create(
35 |
36 |
37 |
38 | ,
39 | );
40 | const json = sidenav.toJSON();
41 | expect(json).toMatchSnapshot();
42 | });
43 |
44 | it('renders with multiple SideNavLinks correctly', () => {
45 | const sidenav = ReactTestRenderer.create(
46 |
47 |
48 |
49 |
50 |
51 | ,
52 | );
53 | const json = sidenav.toJSON();
54 | expect(json).toMatchSnapshot();
55 | });
56 |
57 | it('renders with a SideNavGroup correctly', () => {
58 | const sidenav = ReactTestRenderer.create(
59 |
60 |
61 |
62 |
63 |
64 | ,
65 | );
66 | const json = sidenav.toJSON();
67 | expect(json).toMatchSnapshot();
68 | });
69 |
70 | it('renders with multiple SideNavGroups correctly', () => {
71 | const sidenav = ReactTestRenderer.create(
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 | ,
84 | );
85 | const json = sidenav.toJSON();
86 | expect(json).toMatchSnapshot();
87 | });
88 | });
89 |
--------------------------------------------------------------------------------
/src/lib/components/SideNav/SideNavBanner.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | class SideNavBanner extends React.Component {
5 | render() {
6 | const {
7 | href, logo, onClick, open, tagline, title,
8 | } = this.props;
9 |
10 | const Tag = href ? 'a' : 'div';
11 |
12 | return (
13 |
14 |
15 |
16 |
17 |
18 |
19 | {(logo.src ?
20 | : title
21 | )}
22 |
23 |
24 | {tagline &&
{tagline} }
25 |
26 |
27 |
28 |
29 | key === 'Enter' && onClick}
33 | aria-hidden={open ? 'true' : 'false'}
34 | role="button"
35 | tabIndex={0}
36 | />
37 | key === 'Enter' && onClick}
41 | aria-hidden={open ? 'false' : 'true'}
42 | role="button"
43 | tabIndex={0}
44 | />
45 |
46 |
47 |
48 |
49 |
50 |
51 | );
52 | }
53 | }
54 |
55 | SideNavBanner.defaultProps = {
56 | href: null,
57 | logo: { src: null, alt: '' },
58 | onClick: () => 1,
59 | open: false,
60 | tagline: '',
61 | title: null,
62 | };
63 |
64 | SideNavBanner.propTypes = {
65 | href: PropTypes.string,
66 | logo: PropTypes.shape({ src: PropTypes.string, alt: PropTypes.string }),
67 | onClick: PropTypes.func,
68 | open: PropTypes.bool,
69 | tagline: PropTypes.string,
70 | title: PropTypes.string,
71 | };
72 |
73 | SideNavBanner.displayName = 'SideNavBanner';
74 |
75 | export default SideNavBanner;
76 |
--------------------------------------------------------------------------------
/src/lib/components/SideNav/SideNavGroup.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import getClassName from '../../utils/getClassName';
4 |
5 | class SideNavGroup extends React.Component {
6 | constructor(props) {
7 | super(props);
8 | this.onClick = this.onClick.bind(this);
9 |
10 | this.state = { selected: props.selected };
11 | }
12 |
13 | onClick(e) {
14 | e.preventDefault();
15 | const { selected } = this.state;
16 | this.setState({ selected: !selected });
17 | }
18 |
19 | render() {
20 | const { children, href, label } = this.props;
21 | const { selected } = this.state;
22 |
23 | const className = getClassName({
24 | sidebar__link: true,
25 | 'is-selected': selected,
26 | });
27 |
28 | return (
29 |
30 |
31 | { label }
32 |
33 |
34 |
35 |
38 |
39 | );
40 | }
41 | }
42 |
43 | SideNavGroup.defaultProps = {
44 | children: null,
45 | href: '#',
46 | selected: false,
47 | };
48 |
49 | SideNavGroup.propTypes = {
50 | children: PropTypes.node,
51 | href: PropTypes.string,
52 | label: PropTypes.string.isRequired,
53 | selected: PropTypes.bool,
54 | };
55 |
56 | SideNavGroup.displayName = 'SideNavGroup';
57 |
58 | export default SideNavGroup;
59 |
--------------------------------------------------------------------------------
/src/lib/components/SideNav/SideNavLink.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import getClassName from '../../utils/getClassName';
4 |
5 | const SideNavLink = (props) => {
6 | const {
7 | children, href, label, selected,
8 | } = props;
9 |
10 | const className = getClassName({
11 | sidebar__link: true,
12 | 'is-selected': selected,
13 | });
14 |
15 | return (
16 |
17 |
18 | {label}
19 | {children}
20 |
21 |
22 | );
23 | };
24 |
25 | SideNavLink.defaultProps = {
26 | children: null,
27 | selected: false,
28 | };
29 |
30 | SideNavLink.propTypes = {
31 | children: PropTypes.node,
32 | label: PropTypes.string.isRequired,
33 | href: PropTypes.string.isRequired,
34 | selected: PropTypes.bool,
35 | };
36 |
37 | SideNavLink.displayName = 'SideNavLink';
38 |
39 | export default SideNavLink;
40 |
--------------------------------------------------------------------------------
/src/lib/components/Slider/Slider.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import ensureValueInRange from '../../utils/ensureValueInRange';
4 | import getClassName from '../../utils/getClassName';
5 | import SliderInput from './SliderInput';
6 |
7 | class Slider extends React.Component {
8 | constructor(props) {
9 | super(props);
10 | this.handleChange = this.handleChange.bind(this);
11 |
12 | const value = ensureValueInRange(props.value, props.max, props.min);
13 | this.state = { value };
14 | }
15 |
16 | componentWillReceiveProps(nextProps) {
17 | const { value, max, min } = nextProps;
18 | const newValue = ensureValueInRange(value, max, min);
19 |
20 | this.setState({ value: newValue });
21 | }
22 |
23 | handleChange(e) {
24 | const { max, min } = this.props;
25 | const targetValue = Number(e.target.value);
26 | const newValue = ensureValueInRange(targetValue, max, min);
27 |
28 | this.setState({ value: newValue });
29 | }
30 |
31 | // Fix for Chrome and Safari as they don't support CSS slider progress
32 | handleStyle() {
33 | const { value } = this.state;
34 | const { max, min } = this.props;
35 |
36 | const ratio = (value - min) / (max - min);
37 | return { backgroundImage: `-webkit-gradient(linear, left top, right top, color-stop(${ratio}, #335280), color-stop(${ratio}, #fff))` };
38 | }
39 |
40 | render() {
41 | const {
42 | className, disabled, max, min, showValue, step,
43 | } = this.props;
44 | const { value } = this.state;
45 |
46 | let style;
47 | if (/Chrome/i.test(navigator.userAgent) || /Safari/i.test(navigator.userAgent)) {
48 | style = this.handleStyle();
49 | }
50 |
51 | const classNames = getClassName({
52 | [className]: className,
53 | 'p-slider': true,
54 | }) || undefined;
55 |
56 | return (
57 |
58 |
69 | {showValue &&
70 | }
75 |
76 | );
77 | }
78 | }
79 |
80 | Slider.defaultProps = {
81 | className: undefined,
82 | disabled: false,
83 | max: 100,
84 | min: 0,
85 | showValue: false,
86 | step: 1,
87 | value: undefined,
88 | };
89 |
90 | Slider.propTypes = {
91 | className: PropTypes.string,
92 | disabled: PropTypes.bool,
93 | max: PropTypes.number,
94 | min: PropTypes.number,
95 | showValue: PropTypes.bool,
96 | step: PropTypes.number,
97 | value: PropTypes.number,
98 | };
99 |
100 | Slider.displayName = 'Slider';
101 |
102 | export default Slider;
103 |
--------------------------------------------------------------------------------
/src/lib/components/Slider/Slider.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { storiesOf } from '@storybook/react';
3 | import { boolean, number } from '@storybook/addon-knobs';
4 | import { withInfo } from '@storybook/addon-info';
5 |
6 | import Slider from './Slider';
7 |
8 | storiesOf('Slider', module)
9 | .add('Default',
10 | withInfo('The component allows a user to select from a specified range of values, where the precise value is not considered important.')(() => (
11 | ),
19 | ),
20 | )
21 |
22 | .add('With Value',
23 | withInfo('When the "showValue" prop is added to an editable input will display, showing a numeric representation of the slider value.')(() => (
24 | ),
31 | ),
32 | );
33 |
--------------------------------------------------------------------------------
/src/lib/components/Slider/Slider.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactTestRenderer from 'react-test-renderer';
3 | import { shallow } from 'enzyme';
4 | import Slider from './Slider';
5 |
6 | describe('', () => {
7 | it('should match the default snapshot', () => {
8 | const slider = ReactTestRenderer.create(
9 | );
10 | const json = slider.toJSON();
11 | expect(json).toMatchSnapshot();
12 | });
13 |
14 | it('should match the snapshot with showValue prop', () => {
15 | const slider = ReactTestRenderer.create(
16 | );
17 | const json = slider.toJSON();
18 | expect(json).toMatchSnapshot();
19 | });
20 |
21 | it('should match the snapshot with disabled prop', () => {
22 | const slider = ReactTestRenderer.create(
23 | );
24 | const json = slider.toJSON();
25 | expect(json).toMatchSnapshot();
26 | });
27 |
28 | it('should render additional classes', () => {
29 | const slider = shallow( );
30 |
31 | expect(slider.find('div').at(0).children('input').hasClass('class')).toBe(true);
32 | });
33 |
34 | it('should keep value state below max prop', () => {
35 | const slider = shallow( );
36 |
37 | slider.setProps({ value: 2 });
38 | expect(slider.state().value).toEqual(2);
39 |
40 | slider.setProps({ value: 3 });
41 | expect(slider.state().value).toEqual(2);
42 |
43 | slider.setProps({ max: 3 });
44 | expect(slider.state().value).toEqual(3);
45 | });
46 |
47 | it('should keep value state above min prop', () => {
48 | const slider = shallow( );
49 |
50 | slider.setProps({ value: 1 });
51 | expect(slider.state().value).toEqual(1);
52 |
53 | slider.setProps({ value: 0 });
54 | expect(slider.state().value).toEqual(1);
55 |
56 | slider.setProps({ min: 0 });
57 | expect(slider.state().value).toEqual(0);
58 | });
59 | });
60 |
--------------------------------------------------------------------------------
/src/lib/components/Slider/SliderInput.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import getClassName from '../../utils/getClassName';
4 |
5 | const SliderInput = (props) => {
6 | const {
7 | className, value, ...otherProps
8 | } = props;
9 |
10 | const classNames = getClassName({
11 | [className]: className,
12 | 'p-slider__input': true,
13 | }) || undefined;
14 |
15 | return (
16 |
22 | );
23 | };
24 |
25 | SliderInput.defaultProps = {
26 | className: undefined,
27 | maxLength: 3,
28 | value: undefined,
29 | };
30 |
31 | SliderInput.propTypes = {
32 | className: PropTypes.string,
33 | maxLength: PropTypes.number,
34 | value: PropTypes.number,
35 | };
36 |
37 | SliderInput.displayName = 'SliderInput';
38 |
39 | export default SliderInput;
40 |
--------------------------------------------------------------------------------
/src/lib/components/Slider/__snapshots__/Slider.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[` should match the default snapshot 1`] = `
4 |
7 |
18 |
19 | `;
20 |
21 | exports[` should match the snapshot with disabled prop 1`] = `
22 |
25 |
36 |
37 | `;
38 |
39 | exports[` should match the snapshot with showValue prop 1`] = `
40 |
43 |
54 |
62 |
63 | `;
64 |
--------------------------------------------------------------------------------
/src/lib/components/SteppedList/SteppedList.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const SteppedList = (props) => {
5 | if (!props.children) {
6 | return null;
7 | }
8 |
9 | const steppedListItems = React.Children.map(props.children,
10 | (child, index) => React.cloneElement(child, {
11 | index,
12 | detailed: props.detailed,
13 | }),
14 | );
15 |
16 | return (
17 |
18 | {steppedListItems}
19 |
20 | );
21 | };
22 |
23 | SteppedList.defaultProps = {
24 | children: '',
25 | detailed: false,
26 | };
27 |
28 | SteppedList.propTypes = {
29 | children: PropTypes.node,
30 | detailed: PropTypes.bool,
31 | };
32 |
33 | SteppedList.displayName = 'SteppedList';
34 |
35 | export default SteppedList;
36 |
--------------------------------------------------------------------------------
/src/lib/components/SteppedList/SteppedListItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const SteppedListItem = props => (
5 |
6 |
7 | {props.bullet ? props.bullet : props.index + 1}
8 | {props.title}
9 |
10 |
11 | {props.children}
12 |
13 |
14 | );
15 |
16 | SteppedListItem.defaultProps = {
17 | bullet: '',
18 | children: '',
19 | detailed: false,
20 | index: 0,
21 | };
22 |
23 | SteppedListItem.propTypes = {
24 | bullet: PropTypes.string,
25 | children: PropTypes.node,
26 | detailed: PropTypes.bool,
27 | index: PropTypes.number,
28 | title: PropTypes.string.isRequired,
29 | };
30 |
31 | SteppedListItem.displayName = 'SteppedListItem';
32 |
33 | export default SteppedListItem;
34 |
--------------------------------------------------------------------------------
/src/lib/components/Strip/Strip.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const Strip = (props) => {
5 | let classArray = [];
6 | let style = Object.assign({}, props.style);
7 | const {
8 | colour, image, bordered, padding, children,
9 | } = props;
10 |
11 | if (colour) {
12 | classArray = [...classArray, `p-strip--${props.colour}`];
13 | }
14 |
15 | if (image.src) {
16 | const imageColour = `is-${image.colour}`;
17 | classArray = [...classArray, 'p-strip--image', imageColour];
18 | style = Object.assign(
19 | style, { backgroundImage: `url(${image.src})` },
20 | );
21 | }
22 |
23 | if (bordered) {
24 | classArray = [...classArray, 'is-bordered'];
25 | }
26 |
27 | if (padding) {
28 | classArray = [...classArray, `is-${padding}`];
29 | }
30 |
31 | const classString = classArray.join(' ');
32 |
33 | return (
34 |
40 | );
41 | };
42 |
43 | Strip.defaultProps = {
44 | colour: 'light',
45 | bordered: false,
46 | padding: null,
47 | image: {
48 | src: '',
49 | colour: 'dark',
50 | },
51 | style: {},
52 | };
53 |
54 | Strip.propTypes = {
55 | children: PropTypes.node.isRequired,
56 | colour: PropTypes.oneOf(['light', 'dark', 'accent']),
57 | image: PropTypes.shape({
58 | src: PropTypes.string,
59 | colour: PropTypes.oneOf(['light', 'dark']),
60 | }),
61 | bordered: PropTypes.bool,
62 | padding: PropTypes.oneOf(['shallow', 'deep']),
63 | style: PropTypes.object,
64 | };
65 |
66 | Strip.displayName = 'Strip';
67 |
68 | export default Strip;
69 |
--------------------------------------------------------------------------------
/src/lib/components/Strip/StripColumn.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const StripColumn = (props) => {
5 | if (props.size < 1 || props.size > 12) {
6 | return new Error('StripColumn component must have size between 1 and 12');
7 | }
8 | return (
9 |
10 | {props.children}
11 |
12 | );
13 | };
14 |
15 | StripColumn.defaultProps = {
16 | style: {},
17 | size: 12,
18 | };
19 |
20 | StripColumn.propTypes = {
21 | children: PropTypes.node.isRequired,
22 | size: PropTypes.number,
23 | style: PropTypes.object,
24 | };
25 |
26 | StripColumn.displayName = 'StripColumn';
27 |
28 | export default StripColumn;
29 |
--------------------------------------------------------------------------------
/src/lib/components/Strip/StripRow.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const StripRow = props => (
5 |
6 | {props.children}
7 |
8 | );
9 |
10 | StripRow.defaultProps = {
11 | style: {},
12 | };
13 |
14 | StripRow.propTypes = {
15 | children: PropTypes.node.isRequired,
16 | style: PropTypes.object,
17 | };
18 |
19 | StripRow.displayName = 'StripRow';
20 |
21 | export default StripRow;
22 |
--------------------------------------------------------------------------------
/src/lib/components/Switch/Switch.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const Switch = (props) => {
5 | const { checked, id, label } = props;
6 |
7 | return (
8 |
9 | {label}
10 |
17 |
18 |
19 | );
20 | };
21 |
22 | Switch.defaultProps = {
23 | checked: false,
24 | label: undefined,
25 | };
26 |
27 | Switch.propTypes = {
28 | checked: PropTypes.bool,
29 | id: PropTypes.string.isRequired,
30 | label: PropTypes.string,
31 | };
32 |
33 | Switch.displayName = 'Switch';
34 |
35 | export default Switch;
36 |
--------------------------------------------------------------------------------
/src/lib/components/Switch/Switch.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { storiesOf } from '@storybook/react';
3 | import { withInfo } from '@storybook/addon-info';
4 | import { boolean, text } from '@storybook/addon-knobs';
5 |
6 | import Switch from './Switch';
7 |
8 | storiesOf('Switch', module)
9 | .add('Default',
10 | withInfo('You can use this switch pattern to display on and off content, such as for settings or simple controls. By changing the aria-checked attribute from true or false will animate the switch on/off.')(() =>
11 | ( ),
16 | ),
17 | );
18 |
--------------------------------------------------------------------------------
/src/lib/components/Switch/Switch.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactTestRenderer from 'react-test-renderer';
3 | import Switch from './Switch';
4 |
5 | describe('Switch component', () => {
6 | it('should compare with a snapshot', () => {
7 | const switchComponent = ReactTestRenderer.create(
8 | ,
9 | );
10 | const json = switchComponent.toJSON();
11 | expect(json).toMatchSnapshot();
12 | });
13 | });
14 |
15 |
--------------------------------------------------------------------------------
/src/lib/components/Switch/__snapshots__/Switch.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Switch component should compare with a snapshot 1`] = `
4 |
7 | Turn On/Off
8 |
15 |
18 |
19 | `;
20 |
--------------------------------------------------------------------------------
/src/lib/components/Table/TableCell.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import getClassName from '../../utils/getClassName';
4 |
5 | const TableCell = (props) => {
6 | const {
7 | align, ariaSort, children, className, columnHeader, role, rowHeader, ...otherProps
8 | } = props;
9 |
10 | const classNames = getClassName({
11 | [className]: className,
12 | [`u-align--${align}`]: align,
13 | }) || undefined;
14 |
15 | if (columnHeader) {
16 | return (
17 |
24 | {children}
25 | );
26 | }
27 |
28 | if (rowHeader) {
29 | return (
30 |
36 | {children}
37 | );
38 | }
39 |
40 | return (
41 |
46 | {children}
47 |
48 | );
49 | };
50 |
51 | TableCell.defaultProps = {
52 | align: null,
53 | ariaSort: 'none',
54 | children: null,
55 | className: undefined,
56 | columnHeader: false,
57 | role: 'gridcell',
58 | rowHeader: false,
59 | };
60 |
61 | TableCell.propTypes = {
62 | align: PropTypes.string,
63 | ariaSort: PropTypes.string,
64 | children: PropTypes.node,
65 | className: PropTypes.string,
66 | columnHeader: PropTypes.bool,
67 | role: PropTypes.string,
68 | rowHeader: PropTypes.bool,
69 | };
70 |
71 | TableCell.displayName = 'TableCell';
72 |
73 | export default TableCell;
74 |
--------------------------------------------------------------------------------
/src/lib/components/Table/TableRow.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const TableRow = (props) => {
5 | const { children, ...otherProps } = props;
6 |
7 | return (
8 |
9 | {children}
10 |
11 | );
12 | };
13 |
14 | TableRow.propTypes = {
15 | children: PropTypes.node.isRequired,
16 | };
17 |
18 | TableRow.displayName = 'TableRow';
19 |
20 | export default TableRow;
21 |
--------------------------------------------------------------------------------
/src/lib/components/Tabs/Tabs.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import TabsItem from './TabsItem';
5 |
6 | class Tabs extends React.Component {
7 | constructor(props) {
8 | super(props);
9 | this.toggleSelected = this.toggleSelected.bind(this);
10 | this.state = {
11 | selected: props.selected,
12 | };
13 | }
14 |
15 | componentWillReceiveProps(nextProps) {
16 | this.setState({ selected: nextProps.selected });
17 | }
18 |
19 | toggleSelected(index) {
20 | if (this.state.selected !== index) {
21 | this.setState({ selected: index });
22 | }
23 | }
24 |
25 | render() {
26 | const { selected } = this.state;
27 | const { children } = this.props;
28 |
29 | const tabItems = React.Children.map(children,
30 | (child, index) => React.cloneElement(child, {
31 | index,
32 | isSelected: selected === index,
33 | onClick: this.toggleSelected,
34 | }),
35 | );
36 |
37 | return (
38 |
39 |
42 |
43 | );
44 | }
45 | }
46 |
47 | Tabs.defaultProps = {
48 | children: null,
49 | selected: 0,
50 | };
51 |
52 | Tabs.propTypes = {
53 | children: (props, propName, componentName) => {
54 | const prop = props[propName];
55 | let error = null;
56 |
57 | React.Children.forEach(prop, (child) => {
58 | if (child.type !== TabsItem) {
59 | error = new Error(`${componentName} children should be of type "TabsItem".`);
60 | }
61 | });
62 |
63 | return error;
64 | },
65 | selected: PropTypes.number,
66 | };
67 |
68 | Tabs.displayName = 'Tabs';
69 |
70 | export default Tabs;
71 |
--------------------------------------------------------------------------------
/src/lib/components/Tabs/Tabs.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { storiesOf } from '@storybook/react';
3 | import { text, number } from '@storybook/addon-knobs';
4 | import { withInfo } from '@storybook/addon-info';
5 |
6 | import Tabs from './Tabs';
7 | import TabsItem from './TabsItem';
8 |
9 | const options = {
10 | range: true,
11 | min: 0,
12 | max: 2,
13 | step: 1,
14 | };
15 |
16 | storiesOf('Tabs', module)
17 | .add('Default',
18 | withInfo('Use the Tabs component when there are multiple categories/views/panes of content, but there is the need to only show one pane at a time. Don’t use Tabs for pagination of content or cases involve viewing content, not navigating between groups of content.')(() => (
19 |
22 |
23 | { text('Tab1 text', 'Section 1') }
24 |
25 |
26 | { text('Tab2 text', 'Section 2') }
27 |
28 |
29 | { text('Tab3 text', 'Section 3') }
30 |
31 | ),
32 | ),
33 | )
34 |
35 | .add('Pre-selected',
36 | withInfo('You can pre-select a tab using the selected prop. Note that it\'s 0-indexed')(() => (
37 |
40 |
41 | { text('Tab1 text', 'Section 1') }
42 |
43 |
44 | { text('Tab2 text', 'Section 2') }
45 |
46 |
47 | { text('Tab3 text', 'Section 3') }
48 |
49 | ),
50 | ),
51 | );
52 |
--------------------------------------------------------------------------------
/src/lib/components/Tabs/Tabs.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactTestRenderer from 'react-test-renderer';
3 | import Tabs from './Tabs';
4 | import TabsItem from './TabsItem';
5 |
6 | describe('Tabs component', () => {
7 | it('should render a Tabs component with one TabsItem and autoselect it', () => {
8 | const tabs = ReactTestRenderer.create(
9 |
10 | Tab 1
11 | ,
12 | );
13 | const json = tabs.toJSON();
14 | expect(json).toMatchSnapshot();
15 | });
16 |
17 | it('should render a Tabs component with more than one TabsItem and autoselect the first tab', () => {
18 | const tabs = ReactTestRenderer.create(
19 |
20 | Tab 1
21 | Tab 2
22 | Tab 3
23 | ,
24 | );
25 | const json = tabs.toJSON();
26 | expect(json).toMatchSnapshot();
27 | });
28 |
29 | it('should render a Tabs component with a different TabItem selected if prop is provided', () => {
30 | const tabs = ReactTestRenderer.create(
31 |
32 | Tab 1
33 | Tab 2
34 | Tab 3
35 | ,
36 | );
37 | const json = tabs.toJSON();
38 | expect(json).toMatchSnapshot();
39 | });
40 |
41 | it('should render anchor tags if href prop is provided', () => {
42 | const tabs = ReactTestRenderer.create(
43 |
44 | Tab 1
45 | Tab 2
46 | Tab 3
47 | ,
48 | );
49 | const json = tabs.toJSON();
50 | expect(json).toMatchSnapshot();
51 | });
52 | });
53 |
54 |
--------------------------------------------------------------------------------
/src/lib/components/Tabs/TabsItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | class TabsItem extends React.Component {
5 | constructor() {
6 | super();
7 | this.onClick = this.onClick.bind(this);
8 | }
9 |
10 | onClick() {
11 | const { index, onClick } = this.props;
12 | onClick(index);
13 | }
14 |
15 | render() {
16 | const {
17 | children, href, index, isSelected, onClick,
18 | } = this.props;
19 |
20 | return (
21 |
22 | event.key === 'Enter' && onClick(index)}
31 | >
32 | { children }
33 |
34 |
35 | );
36 | }
37 | }
38 |
39 | TabsItem.defaultProps = {
40 | href: null,
41 | index: 0,
42 | isSelected: false,
43 | onClick: () => 1,
44 | };
45 |
46 | TabsItem.propTypes = {
47 | children: PropTypes.node.isRequired,
48 | href: PropTypes.string,
49 | index: PropTypes.number,
50 | isSelected: PropTypes.bool,
51 | onClick: PropTypes.func,
52 | };
53 |
54 | TabsItem.displayName = 'TabsItem';
55 |
56 | export default TabsItem;
57 |
--------------------------------------------------------------------------------
/src/lib/components/ToolTip/ToolTip.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const ToolTip = ({
5 | id, message, position, children,
6 | }) => {
7 | const modifierClassPrefix = 'p-tooltip--';
8 |
9 | let className = 'p-tooltip';
10 |
11 | if (position) {
12 | className += ` ${modifierClassPrefix}${position}`;
13 | }
14 |
15 | return (
16 |
17 | {children}
18 |
19 | {message}
20 |
21 |
22 | );
23 | };
24 |
25 | ToolTip.defaultProps = {
26 | position: 'btm-right',
27 | };
28 |
29 | ToolTip.propTypes = {
30 | children: PropTypes.node.isRequired,
31 | position: PropTypes.oneOf([
32 | 'left',
33 | 'right',
34 | 'top-left',
35 | 'btm-left',
36 | 'top-right',
37 | 'btm-right',
38 | 'top-center',
39 | 'btm-center',
40 | ]),
41 | id: PropTypes.string.isRequired,
42 | message: PropTypes.string.isRequired,
43 | };
44 |
45 | ToolTip.displayName = 'ToolTip';
46 |
47 | export default ToolTip;
48 |
--------------------------------------------------------------------------------
/src/lib/components/ToolTip/ToolTip.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { storiesOf } from '@storybook/react';
3 | import { select } from '@storybook/addon-knobs';
4 | import { withInfo } from '@storybook/addon-info';
5 |
6 | import ToolTip from './ToolTip';
7 |
8 | const label = 'Positions';
9 | const options = [
10 | 'left',
11 | 'right',
12 | 'top-left',
13 | 'top-right',
14 | 'btm-left',
15 | 'btm-right',
16 | 'top-center',
17 | 'btm-center',
18 | ];
19 | const defaultValue = 'btm-left';
20 |
21 | storiesOf('ToolTip', module)
22 | .add('Default ToolTip',
23 | withInfo('You can set the position of the tooltip by passing a position prop')(() => (
24 |
29 | Example ToolTip
30 | ),
31 | ),
32 | );
33 |
--------------------------------------------------------------------------------
/src/lib/components/ToolTip/ToolTip.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactTestRenderer from 'react-test-renderer';
3 | import ToolTip from './ToolTip';
4 |
5 | describe('ToolTip component', () => {
6 | it('should render a tooltip component', () => {
7 | const toolTip = ReactTestRenderer.create(
8 |
9 | Bottom left tooltip
10 | ,
11 | );
12 | const json = toolTip.toJSON();
13 | expect(json).toMatchSnapshot();
14 | });
15 |
16 | it('should render a tooltip component with a bottom center tooltip', () => {
17 | const toolTip = ReactTestRenderer.create(
18 |
19 | Bottom center tooltip
20 | ,
21 | );
22 | const json = toolTip.toJSON();
23 | expect(json).toMatchSnapshot();
24 | });
25 |
26 | it('should render a tooltip component with a bottomright tooltip', () => {
27 | const toolTip = ReactTestRenderer.create(
28 |
29 | Bottom right tooltip
30 | ,
31 | );
32 | const json = toolTip.toJSON();
33 | expect(json).toMatchSnapshot();
34 | });
35 |
36 | it('should render a tooltip component with a left tooltip', () => {
37 | const toolTip = ReactTestRenderer.create(
38 |
39 | Left tooltip
40 | ,
41 | );
42 | const json = toolTip.toJSON();
43 | expect(json).toMatchSnapshot();
44 | });
45 |
46 | it('should render a tooltip component with a right tooltip', () => {
47 | const toolTip = ReactTestRenderer.create(
48 |
49 | Right tooltip
50 | ,
51 | );
52 | const json = toolTip.toJSON();
53 | expect(json).toMatchSnapshot();
54 | });
55 |
56 | it('should render a tooltip component with a top left tooltip', () => {
57 | const toolTip = ReactTestRenderer.create(
58 |
59 | Top left tooltip
60 | ,
61 | );
62 | const json = toolTip.toJSON();
63 | expect(json).toMatchSnapshot();
64 | });
65 |
66 | it('should render a tooltip component with a top center tooltip', () => {
67 | const toolTip = ReactTestRenderer.create(
68 |
69 | Top center tooltip
70 | ,
71 | );
72 | const json = toolTip.toJSON();
73 | expect(json).toMatchSnapshot();
74 | });
75 |
76 | it('should render a tooltip component with a top right tooltip', () => {
77 | const toolTip = ReactTestRenderer.create(
78 |
79 | Top right tooltip
80 | ,
81 | );
82 | const json = toolTip.toJSON();
83 | expect(json).toMatchSnapshot();
84 | });
85 | });
86 |
--------------------------------------------------------------------------------
/src/lib/components/ToolTip/__snapshots__/ToolTip.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`ToolTip component should render a tooltip component 1`] = `
4 |
8 | Bottom left tooltip
9 |
14 | Lorem ipsum dolor sit amet
15 |
16 |
17 | `;
18 |
19 | exports[`ToolTip component should render a tooltip component with a bottom center tooltip 1`] = `
20 |
24 | Bottom center tooltip
25 |
30 | Lorem ipsum dolor sit amet
31 |
32 |
33 | `;
34 |
35 | exports[`ToolTip component should render a tooltip component with a bottomright tooltip 1`] = `
36 |
40 | Bottom right tooltip
41 |
46 | Lorem ipsum dolor sit amet
47 |
48 |
49 | `;
50 |
51 | exports[`ToolTip component should render a tooltip component with a left tooltip 1`] = `
52 |
56 | Left tooltip
57 |
62 | Lorem ipsum dolor sit amet
63 |
64 |
65 | `;
66 |
67 | exports[`ToolTip component should render a tooltip component with a right tooltip 1`] = `
68 |
72 | Right tooltip
73 |
78 | Lorem ipsum dolor sit amet
79 |
80 |
81 | `;
82 |
83 | exports[`ToolTip component should render a tooltip component with a top center tooltip 1`] = `
84 |
88 | Top center tooltip
89 |
94 | Lorem ipsum dolor sit amet
95 |
96 |
97 | `;
98 |
99 | exports[`ToolTip component should render a tooltip component with a top left tooltip 1`] = `
100 |
104 | Top left tooltip
105 |
110 | Lorem ipsum dolor sit amet
111 |
112 |
113 | `;
114 |
115 | exports[`ToolTip component should render a tooltip component with a top right tooltip 1`] = `
116 |
120 | Top right tooltip
121 |
126 | Lorem ipsum dolor sit amet
127 |
128 |
129 | `;
130 |
--------------------------------------------------------------------------------
/src/lib/utils/ensureValueInRange.js:
--------------------------------------------------------------------------------
1 | export default function ensureValueInRange(val, max, min) {
2 | if (val) {
3 | if (val <= min) {
4 | return min;
5 | }
6 | if (val >= max) {
7 | return max;
8 | }
9 | } else {
10 | return min;
11 | }
12 | return val;
13 | }
14 |
--------------------------------------------------------------------------------
/src/lib/utils/getClassName.js:
--------------------------------------------------------------------------------
1 | export default function getClassName(classesObj) {
2 | return Object
3 | .keys(classesObj)
4 | .filter(key => classesObj[key])
5 | .join(' ');
6 | }
7 |
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | import raf from './tempPolyfills';
3 | import Enzyme from 'enzyme';
4 | import Adapter from 'enzyme-adapter-react-16';
5 |
6 | Enzyme.configure({ adapter: new Adapter() });
7 |
--------------------------------------------------------------------------------
/src/tempPolyfills.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | const raf = global.requestAnimationFrame = (cb) => {
3 | setTimeout(cb, 0);
4 | }
5 |
6 | export default raf;
--------------------------------------------------------------------------------