Skip to content

Commit

Permalink
feat(media): create media object component (#1383)
Browse files Browse the repository at this point in the history
* feat(media): create media object component

* feat(media): add menu elements

* chore(media): rename media.img to media.figure

* docs(media): add layout examples

* docs(media): add form elements to media object example

* docs(media): add titles to examples

* feat(media): add aria-label prop to media.menu

* style(media): change to arrow fns from function keyword

* style(media): use defaultProps for everything

* fix(media): adjust composition of body + figure content

* refactor(media): remove menu wrapper div, pass props to button

* fix(media): remove unneeded css attr

* refactor(media): scss shorthand declaration

* fix(media): ie11 proofing

* refactor(media): use flex shorthand

* fix(media): revert change to dropdownmenu, add dropdownProps instead
  • Loading branch information
alexkrolick authored Jul 17, 2019
1 parent 099a09e commit 5140c13
Show file tree
Hide file tree
Showing 15 changed files with 524 additions and 0 deletions.
13 changes: 13 additions & 0 deletions scripts/styleguide.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,19 @@ const allSections = [
description: 'Box UI Elements components implement the reusable building blocks of the Box Design Language',
sectionDepth: 2,
usageMode: 'expand',
sections: [
{
name: 'Media',
components: [
'../src/components/media/Media.js',
'../src/components/media/MediaFigure.js',
'../src/components/media/MediaBody.js',
'../src/components/media/MediaMenu.js',
],
description: 'Implements the "media object" layout',
usageMode: 'expand',
},
],
},
{
name: 'Icons',
Expand Down
35 changes: 35 additions & 0 deletions src/components/media/Media.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// @flow
import * as React from 'react';
import classnames from 'classnames';
import MediaFigure from './MediaFigure';
import MediaBody from './MediaBody';
import MediaMenu from './MediaMenu';
import './Media.scss';

type Props = {
/** Component to use as outermost element, e.g., 'li' */
as: React.ElementType,
/** Child elements */
children: React.ChildrenArray<React.Element<typeof MediaFigure | typeof MediaBody | typeof MediaMenu>>,
/** Additional class names */
className?: string,
};

const Media = ({ as: Wrapper, children, className, ...rest }: Props) => (
<Wrapper className={classnames('bdl-Media', className)} {...rest}>
{children}
</Wrapper>
);

// Use this instead of default value because of param destructuring bug in Flow
// that affects union types
// https://github.com/facebook/flow/issues/5461
Media.defaultProps = {
as: 'div',
};

Media.Body = MediaBody;
Media.Menu = MediaMenu;
Media.Figure = MediaFigure;

export default Media;
148 changes: 148 additions & 0 deletions src/components/media/Media.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
> **Note**
>
> `Media` is a compound component, the inner elements are not meant to be used outside of the Media container
Implements the "media object" element

```plaintext
import Media from 'box-ui-elements/es/components/media'
```

```js
const { MenuItem } = require('../menu');

<Media style={{ width: 300 }}>
<Media.Figure>
<Avatar size="large" />
</Media.Figure>

<Media.Body>
<Media.Menu label="Options">
<MenuItem>Edit</MenuItem>
<MenuItem>Delete</MenuItem>
</Media.Menu>
<div>
<b>Yo Yo Ma</b> commented on this file
</div>
<div>
Please review the notes
<br />a b c d e f g h i j k l m n o p q r s t u v w x y z 0 1 2 3 4 5 6 7 8
9 0 1 2 3 4 5 6 7 8 9
</div>
</Media.Body>
</Media>;
```

```js
const { MenuItem } = require('../menu');

<>
<code>
<span style={{ color: 'green' }}>Media</span>{' '}
<span style={{ color: 'purple' }}>Media.Figure</span>{' '}
<span style={{ color: 'orange' }}>Media.Body</span>{' '}
<span style={{ color: 'red' }}>Media.Menu</span>
</code>
<br />
<br />
<Media style={{ width: 300, boxShadow: '0 0 2px 3px green', padding: 5 }}>
<Media.Figure style={{ boxShadow: '0 0 2px 3px purple' }}>
<Avatar size="large" />
</Media.Figure>

<Media.Body style={{ boxShadow: '0 0 2px 3px orange', padding: 3 }}>
<Media.Menu
style={{ boxShadow: '0 0 2px 3px red', margin: 3, padding: 3 }}
>
<MenuItem>Edit</MenuItem>
<MenuItem>Delete</MenuItem>
</Media.Menu>
<div>
<b>Yo Yo Ma</b> commented on this file
</div>
<div>
Please review the notes
<br />a b c d e f g h i j k l m n o p q r s t u v w x y z 0 1 2 3 4 5 6 7
8 9 0 1 2 3 4 5 6 7 8 9
</div>
</Media.Body>
</Media>
</>;
```

## Nesting Media Components

```js
const { MenuItem } = require('../menu');

<Media style={{ width: 300 }}>
<Media.Figure>
<Avatar />
</Media.Figure>

<Media.Body>
<Media.Menu>
<MenuItem>Edit</MenuItem>
<MenuItem>Delete</MenuItem>
</Media.Menu>
<div>
<b>Yo Yo Ma</b> commented on this file
</div>
<div>This is a nested media object</div>
<ul style={{ margin: 0, padding: 0 }}>
<Media as="li" style={{ marginTop: 10 }}>
<Media.Figure>
<Avatar />
</Media.Figure>

<Media.Body>
<div>
<b>Bjork</b> replied
</div>
<div>I must agree!</div>
<Media as="li" style={{ marginTop: 10 }}>
<Media.Figure>
<Avatar />
</Media.Figure>

<Media.Body>
<div>
<b>Bono</b> replied
</div>
<div>Me too!</div>
</Media.Body>
</Media>
</Media.Body>
</Media>
</ul>
</Media.Body>
</Media>;
```

## With Form Elements

```js
const { MenuItem } = require('../menu');

<Media style={{ width: 300 }}>
<Media.Figure>
<Avatar size="large" />
</Media.Figure>

<Media.Body>
<Media.Menu>
<MenuItem>Edit</MenuItem>
<MenuItem>Delete</MenuItem>
</Media.Menu>
<div>
<b>W.A. Mozart</b> commented on this file
</div>
<div>Everyone get ready to perform the symphony tonight!</div>
<div>
<Button>Reply</Button>
<Button>Cancel</Button>
<TextArea label="Response" />
</div>
</Media.Body>
</Media>;
```
42 changes: 42 additions & 0 deletions src/components/media/Media.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
@import '../../styles/variables';

// the outermost element
.bdl-Media {
align-items: flex-start;
display: flex;
}

// the media content, ie, an avatar
.bdl-Media-figure {
align-self: flex-start;
flex: 0 0 auto;
justify-self: flex-start;
margin: 0;
padding: 0;
}

// the main content, e.g. title and message
.bdl-Media-body {
flex: 1 1 100%;
margin-left: 10px;
// handle wrapping long words in text content without breaking
// float position of menu
overflow-wrap: break-word;
word-break: break-word; // CJK scripts
word-wrap: break-word; // old IE
}

// the "..." menu button, meant to be used inside Media-body
.bdl-Media-menu {
// float is used to reflow content instead of pushing it to the left
float: right;

// overrides for .btn-plain's margin resets on focus states
&,
&:active,
&:hover,
&:focus {
margin-bottom: 5px;
margin-left: 10px;
}
}
19 changes: 19 additions & 0 deletions src/components/media/MediaBody.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// @flow
import * as React from 'react';
import classnames from 'classnames';
import './Media.scss';

type Props = {
/** Child elements */
children: React.Node,
/** Additional class names */
className?: string,
};

const MediaBody = ({ className, children, ...rest }: Props) => (
<div className={classnames('bdl-Media-body', className)} {...rest}>
{children}
</div>
);

export default MediaBody;
30 changes: 30 additions & 0 deletions src/components/media/MediaBody.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
> **Note**
>
> `Media` is a compound component, the inner elements are not meant to be used outside of the Media container
```js
const { MenuItem } = require('../menu');

<Media style={{ width: 300 }}>
<Media.Figure>
<Avatar size="large" />
</Media.Figure>

<Media.Body style={{ boxShadow: '0 0 2px 3px red' }}>
<Media.Menu>
<MenuItem>Edit</MenuItem>
<MenuItem>Delete</MenuItem>
</Media.Menu>
<div>
<b>Yo Yo Ma</b> commented on this file
</div>
<div>
Long strings without spaces wrap:
<br />
Thistextdoesnothaveanyspacesanditshouldbewrappedinsidethecontaineralignedwiththerightedge
<br />a b c d e f g h i j k l m n o p q r s t u v w x y z 0 1 2 3 4 5 6 7 8
9 0 1 2 3 4 5 6 7 8 9
</div>
</Media.Body>
</Media>;
```
28 changes: 28 additions & 0 deletions src/components/media/MediaFigure.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// @flow
import * as React from 'react';
import classnames from 'classnames';
import './Media.scss';

type Props = {
/** Component to use as outermost element, e.g., 'div' */
as: React.ElementType,
/** Child elements */
children: React.Node,
/** Additional class names */
className?: string,
};

const MediaFigure = ({ as: Wrapper, className, children, ...rest }: Props) => (
<Wrapper className={classnames('bdl-Media-figure', className)} {...rest}>
{children}
</Wrapper>
);

// Use this instead of default value because of param destructuring bug in Flow
// that affects union types
// https://github.com/facebook/flow/issues/5461
MediaFigure.defaultProps = {
as: 'figure',
};

export default MediaFigure;
28 changes: 28 additions & 0 deletions src/components/media/MediaFigure.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
> **Note**
>
> `Media` is a compound component, the inner elements are not meant to be used outside of the Media container
```js
const { MenuItem } = require('../menu');

<Media style={{ width: 300 }}>
<Media.Figure style={{ boxShadow: '0 0 2px 3px red' }}>
<Avatar size="large" />
</Media.Figure>

<Media.Body>
<Media.Menu>
<MenuItem>Edit</MenuItem>
<MenuItem>Delete</MenuItem>
</Media.Menu>
<div>
<b>Yo Yo Ma</b> commented on this file
</div>
<div>
Please review the notes
<br />a b c d e f g h i j k l m n o p q r s t u v w x y z 0 1 2 3 4 5 6 7 8
9 0 1 2 3 4 5 6 7 8 9
</div>
</Media.Body>
</Media>;
```
41 changes: 41 additions & 0 deletions src/components/media/MediaMenu.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// @flow
import * as React from 'react';
import classnames from 'classnames';
import IconEllipsis from '../../icons/general/IconEllipsis';
import PlainButton from '../plain-button';
import DropdownMenu from '../dropdown-menu';
import { Menu } from '../menu';
import { bdlGray62 } from '../../styles/variables';
import './Media.scss';

type Props = {
/** Child elements */
children: React.Node,
/** Additional class names for the menu button */
className?: string,
/** Additional props for the DropdownMenu */
dropdownProps?: {},
/** is the dropdown menu button disabled */
isDisabled: boolean,
};

const MediaMenu = ({ className, children, isDisabled, dropdownProps, ...rest }: Props) => (
<DropdownMenu constrainToScrollParent isRightAligned {...dropdownProps}>
<PlainButton
isDisabled={isDisabled}
className={classnames('bdl-Media-menu', className)}
type="button"
{...rest}
>
<IconEllipsis color={bdlGray62} height={16} width={16} />
</PlainButton>
<Menu>{children}</Menu>
</DropdownMenu>
);

MediaMenu.defaultProps = {
dropdownProps: {},
isDisabled: false,
};

export default MediaMenu;
Loading

0 comments on commit 5140c13

Please sign in to comment.