Skip to content

Commit

Permalink
feat(react): add aspect ratio component (#7123)
Browse files Browse the repository at this point in the history
* feat(react): add aspect ratio component

* feat(react): add aspect ratio to entrypoint

* docs(react): add usage link for aspect ratio docs

Co-authored-by: Alessandra Davila <[email protected]>
Co-authored-by: TJ Egan <[email protected]>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
  • Loading branch information
4 people authored Oct 27, 2020
1 parent 8600958 commit e992909
Show file tree
Hide file tree
Showing 9 changed files with 369 additions and 0 deletions.
27 changes: 27 additions & 0 deletions packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,33 @@ Map {
},
},
},
"AspectRatio" => Object {
"propTypes": Object {
"as": Object {
"type": "elementType",
},
"children": Object {
"type": "node",
},
"className": Object {
"type": "string",
},
"ratio": Object {
"args": Array [
Array [
"16x9",
"9x16",
"2x1",
"1x2",
"4x3",
"3x4",
"1x1",
],
],
"type": "oneOf",
},
},
},
"Breadcrumb" => Object {
"propTypes": Object {
"aria-label": Object {
Expand Down
1 change: 1 addition & 0 deletions packages/react/src/__tests__/index-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ describe('Carbon Components React', () => {
"Accordion",
"AccordionItem",
"AccordionSkeleton",
"AspectRatio",
"Breadcrumb",
"BreadcrumbItem",
"BreadcrumbSkeleton",
Expand Down
75 changes: 75 additions & 0 deletions packages/react/src/components/AspectRatio/AspectRatio-story.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/**
* Copyright IBM Corp. 2016, 2018
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/

import './AspectRatio-story.scss';

import { withKnobs, select } from '@storybook/addon-knobs';
import React from 'react';
import { Grid, Row, Column } from '../Grid';
import { AspectRatio } from './';
import mdx from './AspectRatio.mdx';

export default {
title: 'AspectRatio',
component: AspectRatio,
decorators: [
withKnobs,
(story) => <div className="aspect-ratio-story">{story()}</div>,
],
parameters: {
docs: {
page: mdx,
},
},
};

export const aspectRatio = () => {
return (
<Grid>
<Row>
<Column>
<AspectRatio ratio="1x1">Content</AspectRatio>
</Column>
<Column>
<AspectRatio ratio="1x1">Content</AspectRatio>
</Column>
<Column>
<AspectRatio ratio="1x1">Content</AspectRatio>
</Column>
<Column>
<AspectRatio ratio="1x1">Content</AspectRatio>
</Column>
</Row>
</Grid>
);
};

export const playground = () => {
const ratio = select(
'ratio',
['16x9', '9x16', '2x1', '1x2', '4x3', '3x4', '1x1'],
'1x1'
);
return (
<Grid>
<Row>
<Column>
<AspectRatio ratio={ratio}>Content</AspectRatio>
</Column>
<Column>
<AspectRatio ratio={ratio}>Content</AspectRatio>
</Column>
<Column>
<AspectRatio ratio={ratio}>Content</AspectRatio>
</Column>
<Column>
<AspectRatio ratio={ratio}>Content</AspectRatio>
</Column>
</Row>
</Grid>
);
};
11 changes: 11 additions & 0 deletions packages/react/src/components/AspectRatio/AspectRatio-story.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//
// Copyright IBM Corp. 2016, 2018
//
// This source code is licensed under the Apache-2.0 license found in the
// LICENSE file in the root directory of this source tree.
//

.aspect-ratio-story .bx--aspect-ratio {
background: #f7f1ff;
padding: 1rem;
}
69 changes: 69 additions & 0 deletions packages/react/src/components/AspectRatio/AspectRatio.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/**
* Copyright IBM Corp. 2016, 2018
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/

import { settings } from 'carbon-components';
import cx from 'classnames';
import PropTypes from 'prop-types';
import React from 'react';

const { prefix } = settings;

/**
* The AspectRatio component provides a `ratio` prop that will be used to
* specify the aspect ratio that the children you provide will be displayed in.
* This is often useful alongside our grid components, or for media assets like
* images or videos.
*/
function AspectRatio({
as: BaseComponent = 'div',
className: containerClassName,
children,
ratio = '1x1',
...rest
}) {
const className = cx(
containerClassName,
`${prefix}--aspect-ratio`,
`${prefix}--aspect-ratio--${ratio}`
);
return (
<BaseComponent className={className} {...rest}>
{children}
</BaseComponent>
);
}

AspectRatio.propTypes = {
/**
* Provide a custom component or string to be rendered as the outermost node
* of the component. This is useful if you want to deviate from the default
* `div` tag, where you could specify `section` or `article` instead.
*
* ```jsx
* <AspectRatio as="article">My content</AspectRatio>
* ```
*/
as: PropTypes.elementType,

/**
* Specify the content that will be placed in the aspect ratio
*/
children: PropTypes.node,

/**
* Specify a class name for the outermost node of the component
*/
className: PropTypes.string,

/**
* Specify the ratio to be used by the aspect ratio container. This will
* determine what aspect ratio your content will be displayed in.
*/
ratio: PropTypes.oneOf(['16x9', '9x16', '2x1', '1x2', '4x3', '3x4', '1x1']),
};

export default AspectRatio;
76 changes: 76 additions & 0 deletions packages/react/src/components/AspectRatio/AspectRatio.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { Story, Props, Source, Preview } from '@storybook/addon-docs/blocks';
import { Grid, Row, Column } from '../Grid';
import { AspectRatio } from '../AspectRatio';

# AspectRatio

[Source code](https://github.com/carbon-design-system/carbon/tree/master/packages/react/src/components/AspectRatio)
&nbsp;|&nbsp;
[Usage guidelines](https://www.carbondesignsystem.com/guidelines/2x-grid/overview#aspect-ratio)

<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->

## Table of Contents

- [Overview](#overview)
- [Component API](#component-api)
- [Feedback](#feedback)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

## Overview

The `AspectRatio` component supports rendering your content in a specific aspect
ratio through the `ratio` prop. This prop will specify the proportion between
the width and the height of your content. The width will be determined by
spanning 100% of the space available in your layout, and the height will be
determined by the ratio that you specified.

<Preview>
<Story id="aspectratio--aspect-ratio" />
</Preview>

To see the full list of ratios supported by the `ratio` prop, check out the prop
table in the [Component API](#component-api) section below.

## Component API

<Props />

### AspectRatio as

You can use the `as` prop to support rendering the outermost node in the
component with a specific tag, or custom component, as opposed to the default
`<div>` that is used.

For example, to render an `article` you could use `as="article"`:

```jsx
<AspectRatio as="article" ratio="4x3">
Your content
</AspectRatio>
```

You can also provide custom components, for example:

```jsx
function Article({ children, ...rest }) {
return <article {...rest}>{children}</article>;
}

<AspectRatio as={Article} ratio="4x3">
Your content
</AspectRatio>;
```

### AspectRatio className

The `className` prop passed into `AspectRatio` will be forwarded to the
outermost node in the component.

## Feedback

Help us improve this component by providing feedback, asking questions on Slack,
or updating this file on
[GitHub](https://github.com/carbon-design-system/carbon/edit/master/packages/react/src/components/AspectRatio/AspectRatio.mdx).
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/**
* Copyright IBM Corp. 2016, 2018
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/

import React from 'react';
import ReactDOM from 'react-dom';
import { Simulate } from 'react-dom/test-utils';
import { AspectRatio } from '../';

describe('AspectRatio', () => {
it('should support rendering content in different aspect ratios', () => {
const ratios = ['16x9', '9x16', '2x1', '1x2', '4x3', '3x4', '1x1'];

for (const ratio of ratios) {
const mountNode = document.createElement('div');
ReactDOM.render(
<AspectRatio ratio={ratio}>{ratio}</AspectRatio>,
mountNode
);

// Verify the aspect ratio class exists on the container
const container = mountNode.firstChild;
const classes = Array.from(container.classList);
const ratioClass = classes.find((className) => {
return className.includes(ratio);
});
expect(ratioClass).not.toBe(null);
}
});

describe('Component API', () => {
let mountNode;

beforeEach(() => {
mountNode = document.createElement('div');
document.body.appendChild(mountNode);
});

afterEach(() => {
document.body.removeChild(mountNode);
});

it('should pass in a given className to the outermost node', () => {
ReactDOM.render(
<AspectRatio className="test" data-testid="test">
test
</AspectRatio>,
mountNode
);

const container = document.querySelector('[data-testid="test"]');
expect(container).not.toBe(null);
expect(container.classList.contains('test')).toBe(true);
});

it('should forward extra props to the outermost node', () => {
const onClick = jest.fn();
ReactDOM.render(
<AspectRatio data-testid="test" onClick={onClick}>
test
</AspectRatio>,
mountNode
);
const container = mountNode.firstChild;
expect(container).not.toBe(null);
expect(container.getAttribute('data-testid')).toBe('test');

Simulate.click(container);
expect(onClick).toHaveBeenCalledTimes(1);
});

it('should support rendering custom elements with the `as` prop', () => {
function Test() {
return (
<>
<AspectRatio as="section">test</AspectRatio>
<AspectRatio
className="test"
as={(props) => <article {...props} />}>
test as component
</AspectRatio>
</>
);
}

ReactDOM.render(<Test />, mountNode);

const section = document.querySelector('section');
expect(section).not.toBe(null);

const article = document.querySelector('article');
expect(article).not.toBe(null);

// Make sure props are forwarded to a custom base component
expect(article.classList.contains('test')).toBe(true);
});
});
});
8 changes: 8 additions & 0 deletions packages/react/src/components/AspectRatio/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* Copyright IBM Corp. 2016, 2018
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/

export { default as AspectRatio } from './AspectRatio';
1 change: 1 addition & 0 deletions packages/react/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

export Accordion from './components/Accordion';
export AccordionItem from './components/AccordionItem';
export { AspectRatio } from './components/AspectRatio';
export { Breadcrumb, BreadcrumbItem } from './components/Breadcrumb';
export Button from './components/Button';
export ButtonSet from './components/ButtonSet';
Expand Down

0 comments on commit e992909

Please sign in to comment.