;
/**
@@ -19,6 +16,14 @@ export interface ImageListTypeMap {
classes?: {
/** Styles applied to the root element. */
root?: string;
+ /** Styles applied to the root element if `variant="masonry"`. */
+ masonry?: string;
+ /** Styles applied to the root element if `variant="quilted"`. */
+ quilted?: string;
+ /** Styles applied to the root element if `variant="standard"`. */
+ standard?: string;
+ /** Styles applied to the root element if `variant="woven"`. */
+ woven?: string;
};
/**
* Number of columns.
@@ -26,10 +31,20 @@ export interface ImageListTypeMap
{
*/
cols?: number;
/**
- * Number of px for the spacing between tiles.
+ * The gap between items in px.
* @default 4
*/
- spacing?: number;
+ gap?: number;
+ /**
+ * The height of one row in px.
+ * @default 'auto'
+ */
+ rowHeight?: number | 'auto';
+ /**
+ * The variant to use.
+ * @default 'standard'
+ */
+ variant?: OverridableStringUnion;
};
defaultComponent: D;
}
diff --git a/packages/material-ui/src/ImageList/ImageList.js b/packages/material-ui/src/ImageList/ImageList.js
index b6f4dc91bd3749..c7ad11cb9f11ce 100644
--- a/packages/material-ui/src/ImageList/ImageList.js
+++ b/packages/material-ui/src/ImageList/ImageList.js
@@ -1,69 +1,77 @@
import * as React from 'react';
-import { isFragment } from 'react-is';
import PropTypes from 'prop-types';
import clsx from 'clsx';
import withStyles from '../styles/withStyles';
+import ImageListContext from './ImageListContext';
export const styles = {
/* Styles applied to the root element. */
root: {
- display: 'flex',
- flexWrap: 'wrap',
+ display: 'grid',
overflowY: 'auto',
listStyle: 'none',
padding: 0,
WebkitOverflowScrolling: 'touch', // Add iOS momentum scrolling.
},
+ /* Styles applied to the root element if `variant="masonry"`. */
+ masonry: {
+ display: 'block',
+ },
+ /* Styles applied to the root element if `variant="quilted"`. */
+ quilted: {},
+ /* Styles applied to the root element if `variant="standard"`. */
+ standard: {},
+ /* Styles applied to the root element if `variant="woven"`. */
+ woven: {},
};
const ImageList = React.forwardRef(function ImageList(props, ref) {
const {
- cellHeight = 180,
children,
classes,
className,
cols = 2,
component: Component = 'ul',
- spacing = 4,
- style,
+ rowHeight = 'auto',
+ gap = 4,
+ style: styleProp,
+ variant = 'standard',
...other
} = props;
+ const contextValue = React.useMemo(() => ({ rowHeight, gap, variant }), [
+ rowHeight,
+ gap,
+ variant,
+ ]);
+
+ React.useEffect(() => {
+ if (process.env.NODE_ENV !== 'production') {
+ // Detect Internet Explorer 8+
+ if (document !== undefined && 'objectFit' in document.documentElement.style === false) {
+ console.error(
+ [
+ 'Material-UI: ImageList v5+ no longer natively supports Internet Explorer.',
+ 'Use v4 of this component instead, or polyfill CSS object-fit.',
+ ].join('\n'),
+ );
+ }
+ }
+ }, []);
+
+ const style =
+ variant === 'masonry'
+ ? { columnCount: cols, columnGap: gap, ...styleProp }
+ : { gridTemplateColumns: `repeat(${cols}, 1fr)`, gap, ...styleProp };
+
return (
- {React.Children.map(children, (child) => {
- if (!React.isValidElement(child)) {
- return null;
- }
-
- if (process.env.NODE_ENV !== 'production') {
- if (isFragment(child)) {
- console.error(
- [
- "Material-UI: The ImageList component doesn't accept a Fragment as a child.",
- 'Consider providing an array instead.',
- ].join('\n'),
- );
- }
- }
-
- const childCols = child.props.cols || 1;
- const childRows = child.props.rows || 1;
-
- return React.cloneElement(child, {
- style: {
- width: `${(100 / cols) * childCols}%`,
- height: cellHeight === 'auto' ? 'auto' : cellHeight * childRows + spacing,
- padding: spacing / 2,
- ...child.props.style,
- },
- });
- })}
+ {children}
);
});
@@ -74,13 +82,7 @@ ImageList.propTypes = {
// | To update them edit the d.ts file and run "yarn proptypes" |
// ----------------------------------------------------------------------
/**
- * Number of px for one cell height.
- * You can set `'auto'` if you want to let the children determine the height.
- * @default 180
- */
- cellHeight: PropTypes.oneOfType([PropTypes.oneOf(['auto']), PropTypes.number]),
- /**
- * Image Tiles that will be in Image List.
+ * Items that will be in the image list.
*/
children: PropTypes /* @typescript-to-proptypes-ignore */.node.isRequired,
/**
@@ -102,14 +104,27 @@ ImageList.propTypes = {
*/
component: PropTypes.elementType,
/**
- * Number of px for the spacing between tiles.
+ * The gap between items in px.
* @default 4
*/
- spacing: PropTypes.number,
+ gap: PropTypes.number,
+ /**
+ * The height of one row in px.
+ * @default 'auto'
+ */
+ rowHeight: PropTypes.oneOfType([PropTypes.oneOf(['auto']), PropTypes.number]),
/**
* @ignore
*/
style: PropTypes.object,
+ /**
+ * The variant to use.
+ * @default 'standard'
+ */
+ variant: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([
+ PropTypes.oneOf(['masonry', 'quilted', 'standard', 'woven']),
+ PropTypes.string,
+ ]),
};
export default withStyles(styles, { name: 'MuiImageList' })(ImageList);
diff --git a/packages/material-ui/src/ImageList/ImageList.test.js b/packages/material-ui/src/ImageList/ImageList.test.js
index 6a1d405ef647bf..fc52e4c72e533e 100644
--- a/packages/material-ui/src/ImageList/ImageList.test.js
+++ b/packages/material-ui/src/ImageList/ImageList.test.js
@@ -1,9 +1,9 @@
import * as React from 'react';
import { expect } from 'chai';
-import { createShallow, getClasses, createMount, describeConformance } from 'test/utils';
+import { createClientRender, getClasses, createMount, describeConformance } from 'test/utils';
import ImageList from './ImageList';
-const tilesData = [
+const itemsData = [
{
img: 'images/image-list/00-52-29-429_640.jpg',
title: 'Breakfast',
@@ -19,11 +19,10 @@ const tilesData = [
describe('', () => {
let classes;
const mount = createMount();
- let shallow;
+ const render = createClientRender();
before(() => {
classes = getClasses();
- shallow = createShallow({ dive: true });
});
describeConformance(
@@ -39,128 +38,180 @@ describe('', () => {
}),
);
- it('should render children and change cellHeight', () => {
- const cellHeight = 250;
- const wrapper = shallow(
-
- {tilesData.map((tile) => (
- by: {tile.author}}
- >
-
-
- ))}
- ,
- );
-
- expect(wrapper.find('.grid-tile').length).to.equal(2);
- expect(wrapper.children().at(0).props().style.height).to.equal(cellHeight + 4);
+ const children = itemsData.map((item) => (
+ by: {item.author}}
+ data-testid="test-children"
+ >
+
+
+ ));
+
+ it('should render children by default', () => {
+ const { getAllByTestId } = render({children});
+
+ expect(getAllByTestId('test-children').length).to.equal(2);
});
- it('renders children by default', () => {
- const wrapper = shallow(
-
- {tilesData.map((tile) => (
- by: {tile.author}}
- >
-
-
- ))}
- {false && this is a null child}
- ,
- );
-
- expect(wrapper.find('.grid-tile').length).to.equal(2);
- });
+ describe('classes:', () => {
+ it('should render with the root and standard classes by default', () => {
+ const { getByTestId } = render({children});
- it('renders children and change cols', () => {
- const wrapper = shallow(
-
- {tilesData.map((tile) => (
- by: {tile.author}}
- >
-
-
- ))}
- ,
- );
-
- expect(wrapper.find('.grid-tile').length).to.equal(2);
- expect(wrapper.children().at(0).props().style.width).to.equal('25%');
- });
+ expect(getByTestId('test-root')).to.have.class(classes.root);
+ expect(getByTestId('test-root')).to.have.class(classes.standard);
+ });
- it('renders children and change spacing', () => {
- const spacing = 10;
- const wrapper = shallow(
-
- {tilesData.map((tile) => (
- by: {tile.author}}
- >
-
-
- ))}
- ,
- );
-
- expect(wrapper.find('.grid-tile').length).to.equal(2);
- expect(wrapper.children().at(0).props().style.padding).to.equal(spacing / 2);
- });
+ it('should render with the masonry class', () => {
+ const { getByTestId } = render(
+
+ {children}
+ ,
+ );
- it('should render children and overwrite style', () => {
- const style = { backgroundColor: 'red' };
- const wrapper = shallow(
-
- {tilesData.map((tile) => (
- by: {tile.author}}
- >
-
-
- ))}
- ,
- );
-
- expect(wrapper.find('.grid-tile').length).to.equal(2);
- expect(wrapper.props().style.backgroundColor).to.equal(style.backgroundColor);
- });
+ expect(getByTestId('test-root')).to.have.class(classes.root);
+ expect(getByTestId('test-root')).to.have.class(classes.masonry);
+ });
- describe('prop: cellHeight', () => {
- it('should accept auto as a property', () => {
- const wrapper = shallow(
-
-
+ it('should render with the quilted class', () => {
+ const { getByTestId } = render(
+
+ {children}
,
);
- expect(wrapper.children().at(0).props().style.height).to.equal('auto');
+ expect(getByTestId('test-root')).to.have.class(classes.root);
+ expect(getByTestId('test-root')).to.have.class(classes.woven);
+ });
+
+ it('should render with the woven class', () => {
+ const { getByTestId } = render(
+
+ {children}
+ ,
+ );
+
+ expect(getByTestId('test-root')).to.have.class(classes.root);
+ expect(getByTestId('test-root')).to.have.class(classes.woven);
});
});
- it('warns a Fragment is passed as a child', () => {
- expect(() => {
- mount(
-
-
+ describe('style attribute:', () => {
+ it('should render with default grid-template-columns and gap styles', function test() {
+ if (!/jsdom/.test(window.navigator.userAgent)) {
+ this.skip();
+ }
+
+ const { getByTestId } = render({children});
+
+ expect(getByTestId('test-root').style['grid-template-columns']).to.equal('repeat(2, 1fr)');
+ expect(getByTestId('test-root').style.gap).to.equal('4px');
+ });
+
+ it('should overwrite style', () => {
+ const style = { backgroundColor: 'red' };
+ const { getByTestId } = render(
+
+ {children}
,
);
- }).toErrorDev("Material-UI: The ImageList component doesn't accept a Fragment as a child.");
+
+ expect(getByTestId('test-root').style).to.have.property('backgroundColor', 'red');
+ });
+ });
+
+ describe('props:', () => {
+ describe('prop: component', () => {
+ it('should render a ul by default', () => {
+ const { container } = render({children});
+ expect(container.firstChild).to.have.property('nodeName', 'UL');
+ });
+
+ it('should render a different component', () => {
+ const { container } = render({children});
+ expect(container.firstChild).to.have.property('nodeName', 'DIV');
+ });
+ });
+
+ describe('prop: className', () => {
+ it('should append the className to the root element', () => {
+ const { container } = render({children});
+ expect(container.firstChild).to.have.class('foo');
+ });
+ });
+
+ describe('prop: variant', () => {
+ it('should render with column-count and column-gap styles', function test() {
+ if (!/jsdom/.test(window.navigator.userAgent)) {
+ this.skip();
+ }
+
+ const { getByTestId } = render(
+
+ {children}
+ ,
+ );
+
+ expect(getByTestId('test-root').style['column-count']).to.equal('2');
+ expect(getByTestId('test-root').style['column-gap']).to.equal('4px');
+ });
+ });
+
+ describe('prop: cols', () => {
+ it('should render with modified grid-template-columns style', function test() {
+ if (!/jsdom/.test(window.navigator.userAgent)) {
+ this.skip();
+ }
+
+ const { getByTestId } = render(
+
+ {children}
+ ,
+ );
+
+ expect(getByTestId('test-root').style['grid-template-columns']).to.equal('repeat(4, 1fr)');
+ });
+
+ it('should render with modified column-count style', function test() {
+ if (!/jsdom/.test(window.navigator.userAgent)) {
+ this.skip();
+ }
+
+ const { getByTestId } = render(
+
+ {children}
+ ,
+ );
+
+ expect(getByTestId('test-root').style['column-count']).to.equal('4');
+ });
+ });
+
+ describe('prop: gap', () => {
+ it('should render with modified grid-template-columns style', () => {
+ const { getByTestId } = render(
+
+ {children}
+ ,
+ );
+
+ expect(getByTestId('test-root').style.gap).to.equal('8px');
+ });
+
+ it('should render with modified column-gap style', function test() {
+ if (!/jsdom/.test(window.navigator.userAgent)) {
+ this.skip();
+ }
+
+ const { getByTestId } = render(
+
+ {children}
+ ,
+ );
+
+ expect(getByTestId('test-root').style['column-gap']).to.equal('8px');
+ });
+ });
});
});
diff --git a/packages/material-ui/src/ImageList/ImageListContext.js b/packages/material-ui/src/ImageList/ImageListContext.js
new file mode 100644
index 00000000000000..b745a7c3041cc4
--- /dev/null
+++ b/packages/material-ui/src/ImageList/ImageListContext.js
@@ -0,0 +1,13 @@
+import * as React from 'react';
+
+/**
+ * @ignore - internal component.
+ * @type {React.Context<{} | {expanded: boolean, disabled: boolean, toggle: () => void}>}
+ */
+const ImageListContext = React.createContext({});
+
+if (process.env.NODE_ENV !== 'production') {
+ ImageListContext.displayName = 'ImageListContext';
+}
+
+export default ImageListContext;
diff --git a/packages/material-ui/src/ImageListItem/ImageListItem.d.ts b/packages/material-ui/src/ImageListItem/ImageListItem.d.ts
index 63bef9b7ffac74..c040468c4651f6 100644
--- a/packages/material-ui/src/ImageListItem/ImageListItem.d.ts
+++ b/packages/material-ui/src/ImageListItem/ImageListItem.d.ts
@@ -4,18 +4,16 @@ import { OverridableComponent, OverrideProps } from '../OverridableComponent';
export interface ImageListItemTypeMap {
props: P & {
/**
- * Theoretically you can pass any node as children, but the main use case is to pass an img,
- * in which case ImageListItem takes care of making the image "cover" available space
- * (similar to `background-size: cover` or to `object-fit: cover`).
+ * While you can pass any node as children, the main use case is for an img.
*/
children?: React.ReactNode;
/**
- * Width of the tile in number of grid cells.
+ * Width of the item in number of grid columns.
* @default 1
*/
cols?: number;
/**
- * Height of the tile in number of grid cells.
+ * Height of the item in number of grid rows.
* @default 1
*/
rows?: number;
@@ -35,7 +33,7 @@ export interface ImageListItemTypeMap
;
-export type ImageListItemClassKey = 'root' | 'tile' | 'imgFullHeight' | 'imgFullWidth';
+export type ImageListItemClassKey = keyof NonNullable;
export type ImageListItemProps<
D extends React.ElementType = ImageListItemTypeMap['defaultComponent'],
diff --git a/packages/material-ui/src/ImageListItem/ImageListItem.js b/packages/material-ui/src/ImageListItem/ImageListItem.js
index 9c3569029663dc..8e4f47b6f562d9 100644
--- a/packages/material-ui/src/ImageListItem/ImageListItem.js
+++ b/packages/material-ui/src/ImageListItem/ImageListItem.js
@@ -1,128 +1,103 @@
import * as React from 'react';
+import { isFragment } from 'react-is';
import PropTypes from 'prop-types';
import clsx from 'clsx';
-import debounce from '../utils/debounce';
import withStyles from '../styles/withStyles';
import isMuiElement from '../utils/isMuiElement';
-import { ownerWindow } from '../utils';
+import ImageListContext from '../ImageList/ImageListContext';
export const styles = {
/* Styles applied to the root element. */
root: {
- boxSizing: 'border-box',
- flexShrink: 0,
- },
- /* Styles applied to the `div` element that wraps the children. */
- tile: {
+ display: 'inline-block',
position: 'relative',
- display: 'block', // In case it's not rendered with a div.
- height: '100%',
- overflow: 'hidden',
+ lineHeight: 0, // 🤷🏻♂️Fixes masonry item gap
},
- /* Styles applied to an `img` element child, if needed to ensure it covers the tile. */
- imgFullHeight: {
+ /* Styles applied to an `img` element to ensure it covers the item. */
+ img: {
+ objectFit: 'cover',
+ width: '100%',
height: '100%',
- transform: 'translateX(-50%)',
- position: 'relative',
- left: '50%',
},
- /* Styles applied to an `img` element child, if needed to ensure it covers the tile. */
- imgFullWidth: {
- width: '100%',
- position: 'relative',
- transform: 'translateY(-50%)',
- top: '50%',
+ /* Styles applied to the root element if `variant="standard"`. */
+ standard: {
+ // For titlebar under list item
+ display: 'flex',
+ flexDirection: 'column',
+ '& $img': {
+ height: 'auto',
+ flexGrow: 1,
+ },
+ },
+ /* Styles applied to the root element if `variant="woven"`. */
+ woven: {
+ height: '100%',
+ alignSelf: 'center',
+ '&:nth-child(even)': {
+ height: '70%',
+ },
},
-};
-
-const fit = (imgEl, classes) => {
- if (!imgEl || !imgEl.complete) {
- return;
- }
-
- if (
- imgEl.width / imgEl.height >
- imgEl.parentElement.offsetWidth / imgEl.parentElement.offsetHeight
- ) {
- imgEl.classList.remove(...classes.imgFullWidth.split(' '));
- imgEl.classList.add(...classes.imgFullHeight.split(' '));
- } else {
- imgEl.classList.remove(...classes.imgFullHeight.split(' '));
- imgEl.classList.add(...classes.imgFullWidth.split(' '));
- }
};
const ImageListItem = React.forwardRef(function ImageListItem(props, ref) {
- // cols rows default values are for docs only
+ // TODO: - Use jsdoc @default?: "cols rows default values are for docs only"
const {
children,
classes,
className,
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
cols = 1,
component: Component = 'li',
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
rows = 1,
+ style,
...other
} = props;
- const imgRef = React.useRef(null);
-
- React.useEffect(() => {
- const img = imgRef.current;
-
- if (!img) {
- return undefined;
- }
-
- let listener;
-
- if (img.complete) {
- fit(img, classes);
- } else {
- listener = () => {
- fit(img, classes);
- };
- img.addEventListener('load', listener);
- }
-
- return () => {
- if (listener) {
- img.removeEventListener('load', listener);
- }
- };
- });
+ const { rowHeight = 'auto', gap, variant } = React.useContext(ImageListContext);
- React.useEffect(() => {
- const handleResize = debounce(() => {
- fit(imgRef.current, classes);
- });
-
- const containerWindow = ownerWindow(imgRef.current);
- containerWindow.addEventListener('resize', handleResize);
- return () => {
- handleResize.clear();
- containerWindow.removeEventListener('resize', handleResize);
- };
- }, [classes]);
+ let height = 'auto';
+ if (variant === 'woven') {
+ height = undefined;
+ } else if (rowHeight !== 'auto') {
+ height = rowHeight * rows + gap * (rows - 1);
+ }
return (
-
-
- {React.Children.map(children, (child) => {
- if (!React.isValidElement(child)) {
- return null;
+
+ {React.Children.map(children, (child) => {
+ if (!React.isValidElement(child)) {
+ return null;
+ }
+
+ if (process.env.NODE_ENV !== 'production') {
+ if (isFragment(child)) {
+ console.error(
+ [
+ "Material-UI: The ImageListItem component doesn't accept a Fragment as a child.",
+ 'Consider providing an array instead.',
+ ].join('\n'),
+ );
}
+ }
- if (child.type === 'img' || isMuiElement(child, ['Image'])) {
- return React.cloneElement(child, {
- ref: imgRef,
- });
- }
+ if (child.type === 'img' || isMuiElement(child, ['Image'])) {
+ return React.cloneElement(child, {
+ className: clsx(classes.img, child.props.className),
+ });
+ }
- return child;
- })}
-
+ return child;
+ })}
);
});
@@ -133,9 +108,7 @@ ImageListItem.propTypes = {
// | To update them edit the d.ts file and run "yarn proptypes" |
// ----------------------------------------------------------------------
/**
- * Theoretically you can pass any node as children, but the main use case is to pass an img,
- * in which case ImageListItem takes care of making the image "cover" available space
- * (similar to `background-size: cover` or to `object-fit: cover`).
+ * While you can pass any node as children, the main use case is for an img.
*/
children: PropTypes.node,
/**
@@ -147,7 +120,7 @@ ImageListItem.propTypes = {
*/
className: PropTypes.string,
/**
- * Width of the tile in number of grid cells.
+ * Width of the item in number of grid columns.
* @default 1
*/
cols: PropTypes.number,
@@ -157,10 +130,14 @@ ImageListItem.propTypes = {
*/
component: PropTypes.elementType,
/**
- * Height of the tile in number of grid cells.
+ * Height of the item in number of grid rows.
* @default 1
*/
rows: PropTypes.number,
+ /**
+ * @ignore
+ */
+ style: PropTypes.object,
};
export default withStyles(styles, { name: 'MuiImageListItem' })(ImageListItem);
diff --git a/packages/material-ui/src/ImageListItem/ImageListItem.test.js b/packages/material-ui/src/ImageListItem/ImageListItem.test.js
index bb1d3bf90a200b..3bb842e1fc5010 100644
--- a/packages/material-ui/src/ImageListItem/ImageListItem.test.js
+++ b/packages/material-ui/src/ImageListItem/ImageListItem.test.js
@@ -1,12 +1,13 @@
import * as React from 'react';
import { expect } from 'chai';
-import { spy, useFakeTimers } from 'sinon';
-import { getClasses, createMount, describeConformance } from 'test/utils';
+import { createClientRender, getClasses, createMount, describeConformance } from 'test/utils';
+import ImageList from '../ImageList';
import ImageListItem from './ImageListItem';
describe('', () => {
- const mount = createMount();
let classes;
+ const mount = createMount();
+ const render = createClientRender();
before(() => {
classes = getClasses();
@@ -20,27 +21,12 @@ describe('', () => {
testComponentPropWith: 'div',
}));
- const tileData = {
- img: 'images/image-list/00-52-29-429_640.jpg',
+ const itemData = {
+ img: 'images/image-list/breakfast.jpg',
title: 'Breakfast',
author: 'jill111',
};
- describe('prop: children', () => {
- it('should render children by default', () => {
- const children = ;
- const wrapper = mount({children});
-
- expect(wrapper.containsMatchingElement(children)).to.equal(true);
- });
-
- it('should not change non image child', () => {
- const children = ;
- const wrapper = mount({children});
- expect(wrapper.containsMatchingElement(children)).to.equal(true);
- });
- });
-
function mountMockImage(imgEl) {
const Image = React.forwardRef((props, ref) => {
React.useImperativeHandle(ref, () => imgEl, []);
@@ -61,75 +47,66 @@ describe('', () => {
it('should handle missing image', () => {
mountMockImage(null);
});
+ });
+
+ const children = ;
- it('should fit the height', () => {
- const imgEl = {
- complete: true,
- width: 16,
- height: 9,
- parentElement: { offsetWidth: 4, offsetHeight: 3 },
- classList: { remove: spy(), add: spy() },
- removeEventListener: () => {},
- };
- mountMockImage(imgEl);
- expect(imgEl.classList.remove.callCount).to.equal(1);
- expect(imgEl.classList.remove.args[0][0]).to.equal(classes.imgFullWidth);
- expect(imgEl.classList.add.callCount).to.equal(1);
- expect(imgEl.classList.add.args[0][0]).to.equal(classes.imgFullHeight);
+ describe('props:', () => {
+ describe('prop: children', () => {
+ it('should render children by default', () => {
+ const { getByTestId } = render({children});
+
+ expect(getByTestId('test-children')).not.to.equal(null);
+ });
});
- it('should fit the width', () => {
- const imgEl = {
- complete: true,
- width: 4,
- height: 3,
- parentElement: { offsetWidth: 16, offsetHeight: 9 },
- classList: { remove: spy(), add: spy() },
- removeEventListener: () => {},
- };
- mountMockImage(imgEl);
- expect(imgEl.classList.remove.callCount).to.equal(1);
- expect(imgEl.classList.remove.args[0][0]).to.equal(classes.imgFullHeight);
- expect(imgEl.classList.add.callCount).to.equal(1);
- expect(imgEl.classList.add.args[0][0]).to.equal(classes.imgFullWidth);
+ describe('prop: component', () => {
+ it('should render a different component', () => {
+ const { container } = render({children});
+ expect(container.firstChild).to.have.property('nodeName', 'DIV');
+ });
+ });
+
+ describe('prop: variant', () => {
+ it('should render with the woven class', () => {
+ const { getByTestId } = render(
+
+
+ ,
+ );
+
+ expect(getByTestId('test-children')).to.have.class(classes.root);
+ expect(getByTestId('test-children')).to.have.class(classes.woven);
+ });
});
});
- describe('resize', () => {
- let clock;
+ describe('classes:', () => {
+ it('should render with the root and standard classes by default', () => {
+ const { getByTestId } = render(
+
+
+ ,
+ );
- beforeEach(() => {
- clock = useFakeTimers();
+ expect(getByTestId('test-children')).to.have.class(classes.root);
+ expect(getByTestId('test-children')).to.have.class(classes.standard);
});
- afterEach(() => {
- clock.restore();
+ it('should render img with the img class', () => {
+ const { getByTestId } = render({children});
+
+ expect(getByTestId('test-children')).to.have.class(classes.img);
});
- it('should handle the resize event', () => {
- const imgEl = {
- complete: true,
- width: 4,
- height: 3,
- parentElement: { offsetWidth: 16, offsetHeight: 9 },
- classList: { remove: spy(), add: spy() },
- removeEventListener: () => {},
- };
- mountMockImage(imgEl);
- expect(imgEl.classList.remove.callCount).to.equal(1);
- expect(imgEl.classList.remove.args[0][0]).to.equal(classes.imgFullHeight);
- expect(imgEl.classList.add.callCount).to.equal(1);
- expect(imgEl.classList.add.args[0][0]).to.equal(classes.imgFullWidth);
-
- window.dispatchEvent(new window.Event('resize', {}));
- expect(imgEl.classList.remove.callCount).to.equal(1);
- clock.tick(166);
-
- expect(imgEl.classList.remove.callCount).to.equal(2);
- expect(imgEl.classList.remove.callCount).to.equal(2);
- expect(imgEl.classList.remove.args[1][0]).to.equal(classes.imgFullHeight);
- expect(imgEl.classList.add.callCount).to.equal(2);
- expect(imgEl.classList.add.args[1][0]).to.equal(classes.imgFullWidth);
+ it('should not render a non-img with the img class', () => {
+ const { getByTestId } = render(
+
+
+ ,
+ );
+
+ expect(getByTestId('test-children')).to.not.have.class(classes.img);
});
});
});
diff --git a/packages/material-ui/src/ImageListItemBar/ImageListItemBar.d.ts b/packages/material-ui/src/ImageListItemBar/ImageListItemBar.d.ts
index 3f69c864e433ca..7f049a27025b18 100644
--- a/packages/material-ui/src/ImageListItemBar/ImageListItemBar.d.ts
+++ b/packages/material-ui/src/ImageListItemBar/ImageListItemBar.d.ts
@@ -5,7 +5,7 @@ export interface ImageListItemBarProps
extends StandardProps, 'title'> {
/**
* An IconButton element to be used as secondary action target
- * (primary action target is the tile itself).
+ * (primary action target is the item itself).
*/
actionIcon?: React.ReactNode;
/**
@@ -19,14 +19,16 @@ export interface ImageListItemBarProps
classes?: {
/** Styles applied to the root element. */
root?: string;
- /** Styles applied to the root element if `titlePosition="bottom"`. */
- titlePositionBottom?: string;
- /** Styles applied to the root element if `titlePosition="top"`. */
- titlePositionTop?: string;
- /** Styles applied to the root element if a `subtitle` is provided. */
- rootSubtitle?: string;
+ /** Styles applied to the root element if `position="bottom"`. */
+ positionBottom?: string;
+ /** Styles applied to the root element if `position="top"`. */
+ positionTop?: string;
+ /** Styles applied to the root element if `position="below"`. */
+ positionBelow?: string;
/** Styles applied to the title and subtitle container element. */
titleWrap?: string;
+ /** Styles applied to the title and subtitle container element if `position="below"`. */
+ titleWrapBelow?: string;
/** Styles applied to the container element if `actionPosition="left"`. */
titleWrapActionPosLeft?: string;
/** Styles applied to the container element if `actionPosition="right"`. */
@@ -40,19 +42,19 @@ export interface ImageListItemBarProps
/** Styles applied to the actionIcon if `actionPosition="left"`. */
actionIconActionPosLeft?: string;
};
+ /**
+ * Position of the title bar.
+ * @default 'bottom'
+ */
+ position?: 'below' | 'top' | 'bottom';
/**
* String or element serving as subtitle (support text).
*/
subtitle?: React.ReactNode;
/**
- * Title to be displayed on tile.
+ * Title to be displayed.
*/
title?: React.ReactNode;
- /**
- * Position of the title bar.
- * @default 'bottom'
- */
- titlePosition?: 'top' | 'bottom';
}
export type ImageListItemBarClassKey = keyof NonNullable;
diff --git a/packages/material-ui/src/ImageListItemBar/ImageListItemBar.js b/packages/material-ui/src/ImageListItemBar/ImageListItemBar.js
index 3a2d81265a0480..60f9165d4f69ce 100644
--- a/packages/material-ui/src/ImageListItemBar/ImageListItemBar.js
+++ b/packages/material-ui/src/ImageListItemBar/ImageListItemBar.js
@@ -9,39 +9,44 @@ export const styles = (theme) => ({
position: 'absolute',
left: 0,
right: 0,
- height: 48,
background: 'rgba(0, 0, 0, 0.5)',
display: 'flex',
alignItems: 'center',
fontFamily: theme.typography.fontFamily,
},
- /* Styles applied to the root element if `titlePosition="bottom"`. */
- titlePositionBottom: {
+ /* Styles applied to the root element if `position="bottom"`. */
+ positionBottom: {
bottom: 0,
},
- /* Styles applied to the root element if `titlePosition="top"`. */
- titlePositionTop: {
+ /* Styles applied to the root element if `position="top"`. */
+ positionTop: {
top: 0,
},
- /* Styles applied to the root element if a `subtitle` is provided. */
- rootSubtitle: {
- height: 68,
+ /* Styles applied to the root element if `position="below"`. */
+ positionBelow: {
+ position: 'relative',
+ background: 'transparent',
+ alignItems: 'normal',
},
/* Styles applied to the title and subtitle container element. */
titleWrap: {
flexGrow: 1,
- marginLeft: 16,
- marginRight: 16,
+ padding: '12px 16px',
color: theme.palette.common.white,
overflow: 'hidden',
},
+ /* Styles applied to the title and subtitle container element if `position="below"`. */
+ titleWrapBelow: {
+ padding: '6px 0 12px',
+ color: 'inherit',
+ },
/* Styles applied to the container element if `actionPosition="left"`. */
titleWrapActionPosLeft: {
- marginLeft: 0,
+ paddingLeft: 0,
},
/* Styles applied to the container element if `actionPosition="right"`. */
titleWrapActionPosRight: {
- marginRight: 0,
+ paddingRight: 0,
},
/* Styles applied to the title container element. */
title: {
@@ -75,7 +80,7 @@ const ImageListItemBar = React.forwardRef(function ImageListItemBar(props, ref)
className,
subtitle,
title,
- titlePosition = 'bottom',
+ position = 'bottom',
...other
} = props;
@@ -86,9 +91,9 @@ const ImageListItemBar = React.forwardRef(function ImageListItemBar(props, ref)
className={clsx(
classes.root,
{
- [classes.titlePositionBottom]: titlePosition === 'bottom',
- [classes.titlePositionTop]: titlePosition === 'top',
- [classes.rootSubtitle]: subtitle,
+ [classes.positionBelow]: position === 'below',
+ [classes.positionBottom]: position === 'bottom',
+ [classes.positionTop]: position === 'top',
},
className,
)}
@@ -97,6 +102,7 @@ const ImageListItemBar = React.forwardRef(function ImageListItemBar(props, ref)
>
', () => {
let classes;
const mount = createMount();
- let shallow;
+ const render = createClientRender();
before(() => {
classes = getClasses();
- shallow = createShallow({ dive: true });
});
describeConformance(, () => ({
@@ -21,17 +20,76 @@ describe('', () => {
skip: ['componentProp'],
}));
- const tileData = {
+ const itemData = {
img: 'images/image-list/00-52-29-429_640.jpg',
title: 'Breakfast',
author: 'jill111',
};
- describe('prop: title', () => {
- it('should renders title', () => {
- const wrapper = shallow();
+ describe('props:', () => {
+ describe('prop: title', () => {
+ it('should render a title', () => {
+ const { container } = render();
- expect(wrapper.children('div').text()).to.equal(tileData.title);
+ expect(container.querySelector('div')).to.have.text(itemData.title);
+ });
+ });
+
+ describe('prop: subtitle', () => {
+ it('should render a subtitle', () => {
+ const { container } = render();
+
+ expect(container.querySelector('div')).to.have.text(itemData.author);
+ });
+ });
+
+ describe('prop: position', () => {
+ describe('position="top"', () => {
+ it('should render the positionTop class', () => {
+ const { container } = render();
+
+ expect(container.querySelector('div')).to.have.class(classes.root);
+ expect(container.querySelector('div')).to.have.class(classes.positionTop);
+ });
+ });
+
+ describe('position="below"', () => {
+ it('should render the positionBelow class', () => {
+ const { container } = render();
+
+ expect(container.querySelector('div')).to.have.class(classes.root);
+ expect(container.querySelector('div')).to.have.class(classes.positionBelow);
+ });
+
+ it('should render a child div with the titleWrapBelow class', () => {
+ const { container } = render();
+
+ expect(container.firstChild.querySelector('div')).to.have.class(classes.titleWrap);
+ expect(container.firstChild.querySelector('div')).to.have.class(classes.titleWrapBelow);
+ });
+ });
+ });
+
+ describe('prop: actionPosition', () => {
+ it('should render a child div with the titleWrapActionPosLeft class', () => {
+ const { container } = render(
+ } actionPosition="left" />,
+ );
+
+ expect(container.firstChild.querySelector('div')).to.have.class(classes.titleWrap);
+ expect(container.firstChild.querySelector('div')).to.have.class(
+ classes.titleWrapActionPosLeft,
+ );
+ });
+ });
+ });
+
+ describe('classes:', () => {
+ it('should render with the root and positionBottom classes by default', () => {
+ const { container } = render();
+
+ expect(container.querySelector('div')).to.have.class(classes.root);
+ expect(container.querySelector('div')).to.have.class(classes.positionBottom);
});
});
});
diff --git a/packages/material-ui/src/Modal/Modal.test.js b/packages/material-ui/src/Modal/Modal.test.js
index 85e0fd50d70c50..ef0a2e81911668 100644
--- a/packages/material-ui/src/Modal/Modal.test.js
+++ b/packages/material-ui/src/Modal/Modal.test.js
@@ -47,7 +47,7 @@ describe('', () => {
}),
);
- describe('props', () => {
+ describe('props:', () => {
let container;
before(() => {
diff --git a/packages/material-ui/src/styles/props.d.ts b/packages/material-ui/src/styles/props.d.ts
index 839c77f80634e1..ee42fc215fc661 100644
--- a/packages/material-ui/src/styles/props.d.ts
+++ b/packages/material-ui/src/styles/props.d.ts
@@ -38,12 +38,12 @@ import { FormControlProps } from '../FormControl';
import { FormGroupProps } from '../FormGroup';
import { FormHelperTextProps } from '../FormHelperText';
import { FormLabelProps } from '../FormLabel';
-import { ImageListProps } from '../ImageList';
-import { ImageListItemBarProps } from '../ImageListItemBar';
-import { ImageListItemProps } from '../ImageListItem';
import { GridProps } from '../Grid';
import { IconButtonProps } from '../IconButton';
import { IconProps } from '../Icon';
+import { ImageListProps } from '../ImageList';
+import { ImageListItemBarProps } from '../ImageListItemBar';
+import { ImageListItemProps } from '../ImageListItem';
import { InputAdornmentProps } from '../InputAdornment';
import { InputBaseProps } from '../InputBase';
import { InputLabelProps } from '../InputLabel';
diff --git a/packages/material-ui/test/typescript/components.spec.tsx b/packages/material-ui/test/typescript/components.spec.tsx
index d59d941acb28b6..7edf260e2b4ba5 100644
--- a/packages/material-ui/test/typescript/components.spec.tsx
+++ b/packages/material-ui/test/typescript/components.spec.tsx
@@ -530,7 +530,7 @@ const GridTest = () => (
);
const ImageListTest = () => (
- log(e)}>
+ log(e)}>
log(e)}>