diff --git a/components/index.js b/components/index.js index d9c1c22734..668bb81c7b 100644 --- a/components/index.js +++ b/components/index.js @@ -237,6 +237,9 @@ export SplitViewListbox from './split-view/listbox'; export SLDSTextarea from './textarea'; export Textarea from './textarea'; +export SLDSTiles from './tiles'; +export Tiles from './tiles'; + export SLDSTimepicker from './time-picker'; export Timepicker from './time-picker'; diff --git a/components/site-stories.js b/components/site-stories.js index cbf39c0b1a..7025785f44 100644 --- a/components/site-stories.js +++ b/components/site-stories.js @@ -26,6 +26,7 @@ const documentationSiteLiveExamples = { illustration: require('@salesforce/design-system-react/components/illustration/__docs__/site-stories.js'), input: require('@salesforce/design-system-react/components/input/__docs__/site-stories.js'), textarea: require('@salesforce/design-system-react/components/textarea/__docs__/site-stories.js'), + tiles: require('@salesforce/design-system-react/components/tiles/__docs__/site-stories.js'), 'global-header': require('@salesforce/design-system-react/components/global-header/__docs__/site-stories.js'), 'global-navigation-bar': require('@salesforce/design-system-react/components/global-navigation-bar/__docs__/site-stories.js'), 'icon-settings': require('@salesforce/design-system-react/components/icon-settings/__docs__/site-stories.js'), diff --git a/components/storybook-stories.js b/components/storybook-stories.js index e44eb197d3..75004ad8cb 100644 --- a/components/storybook-stories.js +++ b/components/storybook-stories.js @@ -31,6 +31,7 @@ export Dropdown from '../components/menu-dropdown/__docs__/storybook-stories'; export IconSettings from '../components/icon-settings/__docs__/storybook-stories'; export Input from '../components/input/__docs__/storybook-stories'; export Teaxtarea from '../components/textarea/__docs__/storybook-stories'; +export Tiles from '../components/tiles/__docs__/storybook-stories'; export InlineInput from '../components/forms/input/__docs__/inline/storybook-stories'; export Search from '../components/input/__docs__/search/storybook-stories'; export GlobalHeader from '../components/global-header/__docs__/storybook-stories'; diff --git a/components/tiles/__docs__/site-stories.js b/components/tiles/__docs__/site-stories.js new file mode 100644 index 0000000000..5211e93580 --- /dev/null +++ b/components/tiles/__docs__/site-stories.js @@ -0,0 +1,16 @@ +// This object is imported into the documentation site. An example for the documentation site should be part of the pull request for the component. The object key is the kabob case of the "URL folder". In the case of `http://localhost:8080/components/app-launcher/`, `app-launcher` is the `key`. The folder name is created by `components.component` value in `package.json`. The following uses webpack's raw-loader plugin to get "text files" that will be eval()'d by CodeMirror within the documentation site on page load. + +/* eslint-env node */ +/* eslint-disable global-require */ + +const siteStories = [ + require('raw-loader!@salesforce/design-system-react/components/tiles/__examples__/default.jsx'), + require('raw-loader!@salesforce/design-system-react/components/tiles/__examples__/defaultWithActions.jsx'), + require('raw-loader!@salesforce/design-system-react/components/tiles/__examples__/icon.jsx'), + require('raw-loader!@salesforce/design-system-react/components/tiles/__examples__/avatar.jsx'), + require('raw-loader!@salesforce/design-system-react/components/tiles/__examples__/task.jsx'), + require('raw-loader!@salesforce/design-system-react/components/tiles/__examples__/article.jsx'), + require('raw-loader!@salesforce/design-system-react/components/tiles/__examples__/board.jsx'), +]; + +module.exports = siteStories; diff --git a/components/tiles/__docs__/storybook-stories.jsx b/components/tiles/__docs__/storybook-stories.jsx new file mode 100644 index 0000000000..74cdb1cb35 --- /dev/null +++ b/components/tiles/__docs__/storybook-stories.jsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { storiesOf } from '@storybook/react'; +import { TILES } from '../../../utilities/constants'; +import IconSettings from '../../icon-settings'; + +import Default from '../__examples__/default'; +import DefaultWithActions from '../__examples__/defaultWithActions'; +import Icon from '../__examples__/icon'; +import Avatar from '../__examples__/avatar'; +import Task from '../__examples__/task'; +import Article from '../__examples__/article'; +import Board from '../__examples__/board'; + +storiesOf(TILES, module) + .addDecorator((getStory) => ( +
+ {getStory()} +
+ )) + .add('Default', () => ) + .add('DefaultWithActions', () => ) + .add('Icon', () => ) + .add('Avatar', () => ) + .add('Task', () => ) + .add('Article', () =>
) + .add('Board', () => ); diff --git a/components/tiles/__examples__/article.jsx b/components/tiles/__examples__/article.jsx new file mode 100644 index 0000000000..eb577090ea --- /dev/null +++ b/components/tiles/__examples__/article.jsx @@ -0,0 +1,27 @@ +import React from 'react'; +import IconSettings from '~/components/icon-settings'; +import Tiles from '~/components/tiles'; +import TileDetails from '../details'; + +class Example extends React.Component { + render() { + return ( + +
+ + +

by Steve Author

+
    +
  • Breaking News
  • +
  • 1 day ago
  • +
+
+
+
+
+ ); + } +} + +Example.displayName = 'ArticleExample'; // export is replaced with `ReactDOM.render(, mountNode);` at runtime +export default Example; diff --git a/components/tiles/__examples__/avatar.jsx b/components/tiles/__examples__/avatar.jsx new file mode 100644 index 0000000000..ebc6ec58dd --- /dev/null +++ b/components/tiles/__examples__/avatar.jsx @@ -0,0 +1,62 @@ +import React from 'react'; +import IconSettings from '~/components/icon-settings'; +import Tiles from '~/components/tiles'; +import Avatar from '~/components/avatar'; // `~` is replaced with design-system-react at runtime +import TileDetails from '../details'; +import TileMediaFigure from '../mediaFigure'; +import TileMediaBody from '../mediaBody'; + +class Example extends React.Component { + render() { + return ( + +
+ + + } + /> + + +
+
+ First Label: +
+
+ Description for first label +
+
+ Second Label: +
+
+ Description for second label +
+
+
+
+
+
+
+ ); + } +} + +Example.displayName = 'AvatarExample'; // export is replaced with `ReactDOM.render(, mountNode);` at runtime +export default Example; diff --git a/components/tiles/__examples__/board.jsx b/components/tiles/__examples__/board.jsx new file mode 100644 index 0000000000..b3b1763eed --- /dev/null +++ b/components/tiles/__examples__/board.jsx @@ -0,0 +1,46 @@ +import React from 'react'; +import IconSettings from '~/components/icon-settings'; +import Tiles from '~/components/tiles'; +import TileDetails from '../details'; + +class Example extends React.Component { + render() { + return ( + +
+
    +
  • + + +

    $500,000

    +

    + Company One +

    +

    + Closing 9/30/2015 +

    +
    +
    +
  • +
  • + + +

    $35,000

    +

    + Company Three +

    +

    + Closing 10/12/2015 +

    +
    +
    +
  • +
+
+
+ ); + } +} + +Example.displayName = 'ArticleExample'; // export is replaced with `ReactDOM.render(, mountNode);` at runtime +export default Example; diff --git a/components/tiles/__examples__/default.jsx b/components/tiles/__examples__/default.jsx new file mode 100644 index 0000000000..439ca7ed7e --- /dev/null +++ b/components/tiles/__examples__/default.jsx @@ -0,0 +1,48 @@ +import React from 'react'; +import IconSettings from '~/components/icon-settings'; +import Tiles from '~/components/tiles'; +import TileDetails from '../details'; + +class Example extends React.Component { + render() { + return ( + +
+ + +
+
+ First Label: +
+
+ Description for first label +
+
+ Second Label: +
+
+ Description for second label +
+
+
+
+
+
+ ); + } +} + +Example.displayName = 'DefaultExample'; // export is replaced with `ReactDOM.render(, mountNode);` at runtime +export default Example; diff --git a/components/tiles/__examples__/defaultWithActions.jsx b/components/tiles/__examples__/defaultWithActions.jsx new file mode 100644 index 0000000000..debf887828 --- /dev/null +++ b/components/tiles/__examples__/defaultWithActions.jsx @@ -0,0 +1,70 @@ +import React from 'react'; +import IconSettings from '~/components/icon-settings'; +import Tiles from '~/components/tiles'; +import Dropdown from '~/components/menu-dropdown'; +import TileDetails from '../details'; + +class Example extends React.Component { + render() { + return ( + +
+ { + console.log('selected: ', value); + }} + options={[ + { label: 'Menu Item One', value: 'A0' }, + { label: 'Menu Item Two', value: 'B0' }, + { label: 'Menu Item Three', value: 'C0' }, + { type: 'divider' }, + { label: 'Menu Item Four', value: 'D0' }, + ]} + /> + } + > + +
+
+ First Label: +
+
+ Description for first label +
+
+ Second Label: +
+
+ Description for second label +
+
+
+
+
+
+ ); + } +} + +Example.displayName = 'DefaultWithActionsExample'; // export is replaced with `ReactDOM.render(, mountNode);` at runtime +export default Example; diff --git a/components/tiles/__examples__/icon.jsx b/components/tiles/__examples__/icon.jsx new file mode 100644 index 0000000000..25610c3dd5 --- /dev/null +++ b/components/tiles/__examples__/icon.jsx @@ -0,0 +1,63 @@ +import React from 'react'; +import IconSettings from '~/components/icon-settings'; +import Tiles from '~/components/tiles'; +import Icon from '~/components/icon'; // `~` is replaced with design-system-react at runtime +import TileDetails from '../details'; +import TileMediaFigure from '../mediaFigure'; +import TileMediaBody from '../mediaBody'; + +class Example extends React.Component { + render() { + return ( + +
+ + + } + /> + + +
+
+ First Label: +
+
+ Description for first label +
+
+ Second Label: +
+
+ Description for second label +
+
+
+
+
+
+
+ ); + } +} + +Example.displayName = 'IconExample'; // export is replaced with `ReactDOM.render(, mountNode);` at runtime +export default Example; diff --git a/components/tiles/__examples__/task.jsx b/components/tiles/__examples__/task.jsx new file mode 100644 index 0000000000..7925f72eb4 --- /dev/null +++ b/components/tiles/__examples__/task.jsx @@ -0,0 +1,47 @@ +import React from 'react'; +import IconSettings from '~/components/icon-settings'; +import Tiles from '~/components/tiles'; +import TileDetails from '../details'; +import TileMediaFigure from '../mediaFigure'; +import TileMediaBody from '../mediaBody'; + +class Example extends React.Component { + render() { + return ( + +
+ + + + +
+ } + /> + + +

+ Assignee +

+
+
+ + +
+ ); + } +} + +Example.displayName = 'TaskExample'; // export is replaced with `ReactDOM.render(, mountNode);` at runtime +export default Example; diff --git a/components/tiles/details.jsx b/components/tiles/details.jsx new file mode 100644 index 0000000000..7375ae61cf --- /dev/null +++ b/components/tiles/details.jsx @@ -0,0 +1,23 @@ +/* Copyright (c) 2015-present, salesforce.com, inc. All rights reserved */ +/* Licensed under BSD 3-Clause - see LICENSE.txt or git.io/sfdc-license */ + +import React from 'react'; +import PropTypes from 'prop-types'; + +const propTypes = { + /** + * The content for the Tile component. _Tested with Mocha framework and snapshot testing._ + */ + children: PropTypes.node.isRequired, +}; + +/** + * The content for the Tile component. + */ +const TileDetails = ({ children }) => ( +
{children}
+); + +export default TileDetails; + +TileDetails.propTypes = propTypes; diff --git a/components/tiles/index.jsx b/components/tiles/index.jsx new file mode 100644 index 0000000000..3a7caaad22 --- /dev/null +++ b/components/tiles/index.jsx @@ -0,0 +1,68 @@ +/* Copyright (c) 2015-present, salesforce.com, inc. All rights reserved */ +/* Licensed under BSD 3-Clause - see LICENSE.txt or git.io/sfdc-license */ + +// # Tiles Component + +// Implements the [Tiles design pattern](https://www.lightningdesignsystem.com/components/tiles/) in React. + +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from '../../utilities/class-names'; + +const propTypes = { + /** + * HTML title attribute. _Tested with snapshot testing._ + */ + title: PropTypes.string, + /** + * Action overflow menu dropdown for the tile. + */ + dropdownButton: PropTypes.node, + /** + * Different types of tile formats. + */ + variant: PropTypes.oneOf(['media', 'board']), + /** + * The content for the Tile component. + */ + children: PropTypes.node.isRequired, +}; + +class Tiles extends React.Component { + render() { + return ( +
+ {this.props.dropdownButton && this.props.variant !== 'media' ? ( +
+

+ {this.props.title} +

+
{this.props.dropdownButton}
+
+ ) : ( +

+ {this.props.title} +

+ )} + {this.props.children} +
+ ); + } +} + +Tiles.propTypes = propTypes; + +export default Tiles; diff --git a/components/tiles/mediaBody.jsx b/components/tiles/mediaBody.jsx new file mode 100644 index 0000000000..7c00cc1888 --- /dev/null +++ b/components/tiles/mediaBody.jsx @@ -0,0 +1,32 @@ +/* Copyright (c) 2015-present, salesforce.com, inc. All rights reserved */ +/* Licensed under BSD 3-Clause - see LICENSE.txt or git.io/sfdc-license */ + +import React from 'react'; +import PropTypes from 'prop-types'; + +const propTypes = { + /** + * HTML title attribute. _Tested with snapshot testing._ + */ + title: PropTypes.string.isRequired, + /** + * The children for the TileMediaBody component. _Tested with Mocha framework and snapshot testing._ + */ + children: PropTypes.node.isRequired, +}; + +/** + * The media body for the Tile component. + */ +const TileMediaBody = ({ title, children }) => ( +
+

+ {title} +

+ {children} +
+); + +export default TileMediaBody; + +TileMediaBody.propTypes = propTypes; diff --git a/components/tiles/mediaFigure.jsx b/components/tiles/mediaFigure.jsx new file mode 100644 index 0000000000..b27032ed13 --- /dev/null +++ b/components/tiles/mediaFigure.jsx @@ -0,0 +1,23 @@ +/* Copyright (c) 2015-present, salesforce.com, inc. All rights reserved */ +/* Licensed under BSD 3-Clause - see LICENSE.txt or git.io/sfdc-license */ + +import React from 'react'; +import PropTypes from 'prop-types'; + +const propTypes = { + /** + * The media figure for this tile. It can be an icon, avatar or a task. + */ + mediaFigure: PropTypes.node.isRequired, +}; + +/** + * The media figure for the Tile component. + */ +const TileMediaFigure = ({ mediaFigure }) => ( +
{mediaFigure}
+); + +export default TileMediaFigure; + +TileMediaFigure.propTypes = propTypes; diff --git a/utilities/constants.js b/utilities/constants.js index 4ed1442ea0..219892189d 100644 --- a/utilities/constants.js +++ b/utilities/constants.js @@ -40,6 +40,7 @@ export const CHECKBOX = 'SLDSCheckbox'; export const FORMS_INLINE_EDIT = 'SLDSInlineEdit'; export const INPUT = 'SLDSInput'; export const TEXTAREA = 'SLDSTextarea'; +export const TILES = 'SLDSTiles'; export const SEARCH = 'SLDSSearch'; export const GLOBAL_HEADER = 'SLDSGlobalHeader'; export const GLOBAL_HEADER_PROFILE = 'SLDSGlobalHeaderProfile';