Repository: react-materialize/react-materialize
Pull request #557 (2018-06-15 15:21:54)

Extend component classNames with prop classNames
# Description

See title.

## Type of change

Please delete options that are not relevant.

- [ ] Bug fix (non-breaking change which fixes an issue)
- [X] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [X] This change requires a documentation update

# How Has This Been Tested?

With JEST checking snapshots.

# Checklist:

- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] My changes generate no new warnings
- [ ] I have not generated a new package version. (the maintainers will handle that)



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ad8e76c..92f4938 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
 # Changelog
 
+### 2.3.0
+
+- All components now accept `className` prop
+
 ### 2.2.1
 
 - Col component now supports xl (CloudPower97 #541)
diff --git a/package-lock.json b/package-lock.json
index fc1cc25..bc4a8a1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
 {
   "name": "react-materialize",
-  "version": "2.2.1",
+  "version": "2.3.0",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
diff --git a/package.json b/package.json
index fa987ae..c8086d4 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "react-materialize",
-  "version": "2.2.1",
+  "version": "2.3.0",
   "description": "Material design components for react",
   "main": "./lib/index.js",
   "files": [
diff --git a/src/Breadcrumb.js b/src/Breadcrumb.js
index 3010211..61cbadc 100644
--- a/src/Breadcrumb.js
+++ b/src/Breadcrumb.js
@@ -1,9 +1,10 @@
 import React from 'react';
 import PropTypes from 'prop-types';
+import cx from 'classnames';
 import Col from './Col';
 
-const Breadcrumb = ({ cols, children }) => (
-  <nav className="row">
+const Breadcrumb = ({ cols, children, className }) => (
+  <nav className={cx('row', className)}>
     <div className="nav-wrapper">
       <Col s={cols}>
         {React.Children.map(children, item =>
@@ -16,7 +17,8 @@ const Breadcrumb = ({ cols, children }) => (
 
 Breadcrumb.propTypes = {
   children: PropTypes.node,
-  cols: PropTypes.number
+  cols: PropTypes.number,
+  className: PropTypes.string
 };
 
 Breadcrumb.defaultProps = {
diff --git a/src/CardTitle.js b/src/CardTitle.js
index 8457f2d..0a3956f 100644
--- a/src/CardTitle.js
+++ b/src/CardTitle.js
@@ -7,22 +7,17 @@ import constants from './constants';
 class CardTitle extends Component {
   render() {
     let { image, reveal, waves, children, ...props } = this.props;
-    let classes = {
-      'card-image': true
-    };
-    if (waves) {
-      classes['waves-effect'] = true;
-      classes['waves-' + waves] = true;
-      classes['waves-block'] = true;
-    }
-    let imgClasses = { activator: reveal };
-    let titleClasses = {
-      'card-title': true
-    };
+    const classes = cx({
+      'card-image': true,
+      'waves-effect': waves,
+      'waves-block': waves,
+      [`waves-${waves}`]: waves
+    });
+
     return (
       <div className={cx(classes)} {...props}>
-        <img className={cx(imgClasses)} src={image} />
-        <span className={cx(titleClasses)}>{children}</span>
+        <img className={cx({ activator: reveal })} src={image} />
+        <span className={cx('card-title')}>{children}</span>
       </div>
     );
   }
diff --git a/src/Chip.js b/src/Chip.js
index 3e0d0e1..c4d0e3c 100644
--- a/src/Chip.js
+++ b/src/Chip.js
@@ -1,9 +1,10 @@
 import React from 'react';
 import PropTypes from 'prop-types';
+import cx from 'classnames';
 
-const Chip = ({ children, close }) => {
+const Chip = ({ children, close, className, ...props }) => {
   return (
-    <div className="chip">
+    <div className={cx('chip', className)} {...props}>
       {children}
       {close ? <i className="close material-icons">close</i> : null}
     </div>
@@ -11,6 +12,7 @@ const Chip = ({ children, close }) => {
 };
 
 Chip.propTypes = {
+  className: PropTypes.string,
   children: PropTypes.node,
   /**
    * Shows a close icon
diff --git a/src/Collection.js b/src/Collection.js
index 5be8105..b7dbcd5 100644
--- a/src/Collection.js
+++ b/src/Collection.js
@@ -9,7 +9,7 @@ class Collection extends Component {
   }
 
   render() {
-    const { children, header } = this.props;
+    const { children, header, className } = this.props;
 
     let classes = {
       collection: true,
@@ -22,7 +22,7 @@ class Collection extends Component {
       }
     });
     return (
-      <C className={cx(classes)}>
+      <C className={cx('collection', { 'with-header': !!header }, className)}>
         {header ? this.renderHeader() : null}
         {children}
       </C>
@@ -44,7 +44,8 @@ class Collection extends Component {
 
 Collection.propTypes = {
   children: PropTypes.node,
-  header: PropTypes.node
+  header: PropTypes.node,
+  className: PropTypes.string
 };
 
 export default Collection;
diff --git a/src/Modal.js b/src/Modal.js
index 977ded8..16f48cd 100644
--- a/src/Modal.js
+++ b/src/Modal.js
@@ -51,17 +51,21 @@ class Modal extends Component {
       children,
       fixedFooter,
       header,
+      className,
       ...other
     } = this.props;
 
     delete other.modalOptions;
     delete other.trigger;
 
-    const classes = cx({
-      modal: true,
-      'modal-fixed-footer': fixedFooter,
-      'bottom-sheet': bottomSheet
-    });
+    const classes = cx(
+      'modal',
+      {
+        'modal-fixed-footer': fixedFooter,
+        'bottom-sheet': bottomSheet
+      },
+      className
+    );
 
     return this.modalRoot
       ? ReactDOM.createPortal(
@@ -142,6 +146,10 @@ Modal.propTypes = {
      */
     complete: PropTypes.func
   }),
+  /**
+   * Extra class to added to the Modal
+   */
+  className: PropTypes.string,
   /**
    * Modal is opened on mount
    */
diff --git a/src/Preloader.js b/src/Preloader.js
index 079faed..4257956 100644
--- a/src/Preloader.js
+++ b/src/Preloader.js
@@ -7,25 +7,19 @@ let colors = ['blue', 'red', 'yellow', 'green'];
 
 class Preloader extends Component {
   render() {
-    let classes = {
-      'preloader-wrapper': true,
-      active: this.props.active
-    };
+    const { active, size, flashing, className } = this.props;
 
-    if (this.props.size) {
-      classes[this.props.size] = true;
-    }
+    let classes = cx('preloader-wrapper', { active: active, size: size });
 
     let spinners;
-    if (this.props.flashing) {
-      spinners = [];
-      colors.map(color => {
-        spinners.push(<Spinner color={color} only={false} key={color} />);
-      });
+    if (flashing) {
+      spinners = colors.map(color => (
+        <Spinner color={color} only={false} key={color} />
+      ));
     } else {
-      spinners = <Spinner color={this.props.color} />;
+      spinners = <Spinner color={color} />;
     }
-    return <div className={cx(this.props.className, classes)}>{spinners}</div>;
+    return <div className={cx(className, classes)}>{spinners}</div>;
   }
 }
 
diff --git a/src/Spinner.js b/src/Spinner.js
index ac14aed..e3ac79d 100644
--- a/src/Spinner.js
+++ b/src/Spinner.js
@@ -2,17 +2,14 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import cx from 'classnames';
 
-const Spinner = ({ color, only }) => {
-  let spinnerClasses = {
-    'spinner-layer': true
-  };
-  if (only) {
-    spinnerClasses['spinner-' + color + '-only'] = true;
-  } else {
-    spinnerClasses['spinner-' + color] = true;
-  }
+const Spinner = ({ color, only, className }) => {
+  const spinnerClasses = cx('spinner-layer', {
+    [`spinner-${color}-only`]: only,
+    [`spinner-${color}`]: !only
+  });
+
   return (
-    <div className={cx(spinnerClasses)}>
+    <div className={cx(spinnerClasses, className)}>
       <div className="circle-clipper left">
         <div className="circle" />
       </div>
@@ -31,6 +28,7 @@ Spinner.defaultProps = {
 };
 
 Spinner.propTypes = {
+  className: PropTypes.string,
   color: PropTypes.string,
   only: PropTypes.bool
 };
diff --git a/src/Toast.js b/src/Toast.js
index 08630bd..ba80af6 100644
--- a/src/Toast.js
+++ b/src/Toast.js
@@ -11,11 +11,11 @@ class Toast extends Component {
   }
 
   render() {
-    const { children, rounded } = this.props;
+    const { children, rounded, className } = this.props;
     const classes = { toast: true, rounded: rounded };
 
     return (
-      <Button onClick={this.onClick} className={cx(classes)}>
+      <Button onClick={this.onClick} className={cx(classes, className)}>
         {children}
       </Button>
     );
@@ -27,6 +27,7 @@ class Toast extends Component {
 }
 
 Toast.propTypes = {
+  className: PropTypes.string,
   toast: PropTypes.string.isRequired,
   children: PropTypes.node,
   rounded: PropTypes.bool


Test patch:
diff --git a/test/Breadcrumb.spec.js b/test/Breadcrumb.spec.js
index cd321be..c2381cf 100644
--- a/test/Breadcrumb.spec.js
+++ b/test/Breadcrumb.spec.js
@@ -4,6 +4,12 @@ import Breadcrumb from '../src/Breadcrumb';
 
 describe('<Breadcrumb />', () => {
   test('renders', () => {
-    expect(shallow(<Breadcrumb />)).toMatchSnapshot();
+    const wrapper = (
+      <Breadcrumb cols={6} className="blue">
+        <a href="#">Home</a>
+        <a href="#">Login</a>
+      </Breadcrumb>
+    );
+    expect(shallow(wrapper)).toMatchSnapshot();
   });
 });
diff --git a/test/CardTitle.spec.js b/test/CardTitle.spec.js
index 634b64b..85180a6 100644
--- a/test/CardTitle.spec.js
+++ b/test/CardTitle.spec.js
@@ -2,20 +2,41 @@ import React from 'react';
 import { shallow } from 'enzyme';
 import CardTitle from '../src/CardTitle';
 
-let wrapper = shallow(<CardTitle image="">I am a very simple card</CardTitle>);
-
 describe('<CardTitle />', () => {
+  let wrapper;
+
   test('renders', () => {
+    wrapper = shallow(<CardTitle image="">I am a very simple card</CardTitle>);
     expect(wrapper).toMatchSnapshot();
   });
 
-  wrapper = shallow(
-    <CardTitle image="" reveal>
-      I am a very simple card
-    </CardTitle>
-  );
-
   test('requires an image prop', () => {
-    expect(wrapper.find('img.activator')).toHaveLength(1);
+    wrapper = shallow(
+      <CardTitle image="url.jpg" reveal>
+        I am a very simple card
+      </CardTitle>
+    );
+
+    expect(wrapper.find('img').hasClass('activator')).toBeTruthy();
+  });
+
+  test('can have waves', () => {
+    wrapper = shallow(
+      <CardTitle image="url.jpg" waves="green">
+        I am a very simple card
+      </CardTitle>
+    );
+
+    expect(wrapper.hasClass('waves-green')).toBeTruthy();
+  });
+
+  test('accepts classnames', () => {
+    wrapper = shallow(
+      <CardTitle image="url.jpg" waves="green" className="find">
+        I am a very simple card
+      </CardTitle>
+    );
+
+    expect(wrapper.hasClass('find')).toBeTruthy();
   });
 });
diff --git a/test/Chip.spec.js b/test/Chip.spec.js
index 0dca883..dd6e1aa 100644
--- a/test/Chip.spec.js
+++ b/test/Chip.spec.js
@@ -20,4 +20,9 @@ describe('<Chip />', () => {
     wrapper = shallow(<Chip close>{child}</Chip>);
     expect(wrapper).toMatchSnapshot();
   });
+
+  test('accepts className', () => {
+    wrapper = shallow(<Chip className="find" />);
+    expect(wrapper.hasClass('find')).toBeTruthy();
+  });
 });
diff --git a/test/Collection.spec.js b/test/Collection.spec.js
index acd5056..08c5d9c 100644
--- a/test/Collection.spec.js
+++ b/test/Collection.spec.js
@@ -44,6 +44,16 @@ describe('<Collection />', () => {
       expect(wrapper.find('h2').text()).toEqual('ages');
     });
 
+    test('passes classname', () => {
+      wrapper = shallow(
+        <Collection className="find">
+          <CollectionItem>Alvin</CollectionItem>
+        </Collection>
+      );
+
+      expect(wrapper.hasClass('find')).toBeTruthy();
+    });
+
     test('should render anchors if present', () => {
       wrapper = mount(
         <Collection>
diff --git a/test/Preloader.spec.js b/test/Preloader.spec.js
new file mode 100644
index 0000000..9fda36c
--- /dev/null
+++ b/test/Preloader.spec.js
@@ -0,0 +1,19 @@
+import React from 'react';
+import { shallow } from 'enzyme';
+import Preloader from '../src/Preloader';
+
+describe('<Preloader />', () => {
+  test('should render a Preloader', () => {
+    const wrapper = shallow(
+      <Preloader
+        className="find"
+        size="big"
+        active={false}
+        color="red"
+        flashing
+      />
+    );
+
+    expect(wrapper).toMatchSnapshot();
+  });
+});
diff --git a/test/__snapshots__/Breadcrumb.spec.js.snap b/test/__snapshots__/Breadcrumb.spec.js.snap
index c4ace0a..22db4c4 100644
--- a/test/__snapshots__/Breadcrumb.spec.js.snap
+++ b/test/__snapshots__/Breadcrumb.spec.js.snap
@@ -2,14 +2,29 @@
 
 exports[`<Breadcrumb /> renders 1`] = `
 <nav
-  className="row"
+  className="row blue"
 >
   <div
     className="nav-wrapper"
   >
     <Col
-      s={12}
-    />
+      s={6}
+    >
+      <a
+        className="breadcrumb"
+        href="#"
+        key=".0"
+      >
+        Home
+      </a>
+      <a
+        className="breadcrumb"
+        href="#"
+        key=".1"
+      >
+        Login
+      </a>
+    </Col>
   </div>
 </nav>
 `;
diff --git a/test/__snapshots__/CardTitle.spec.js.snap b/test/__snapshots__/CardTitle.spec.js.snap
index 882c3f5..7e9a6c9 100644
--- a/test/__snapshots__/CardTitle.spec.js.snap
+++ b/test/__snapshots__/CardTitle.spec.js.snap
@@ -5,7 +5,7 @@ exports[`<CardTitle /> renders 1`] = `
   className="card-image"
 >
   <img
-    className="activator"
+    className=""
     src=""
   />
   <span
diff --git a/test/__snapshots__/Preloader.spec.js.snap b/test/__snapshots__/Preloader.spec.js.snap
new file mode 100644
index 0000000..38f5a54
--- /dev/null
+++ b/test/__snapshots__/Preloader.spec.js.snap
@@ -0,0 +1,28 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`<Preloader /> should render a Preloader 1`] = `
+<div
+  className="find preloader-wrapper size"
+>
+  <Spinner
+    color="blue"
+    key="blue"
+    only={false}
+  />
+  <Spinner
+    color="red"
+    key="red"
+    only={false}
+  />
+  <Spinner
+    color="yellow"
+    key="yellow"
+    only={false}
+  />
+  <Spinner
+    color="green"
+    key="green"
+    only={false}
+  />
+</div>
+`;