Repository: reactioncommerce/reaction-component-library
Pull request #90 (2018-06-20 18:08:39)

feat: Cart Empty Message component
This PR adds a `CartEmptyMessage` component.

It will render a Cart Empty message, with a button to `Continue Shopping`, when a user is viewing their cart with nothing in it.

Issue in the starterkit: https://github.com/reactioncommerce/reaction-next-starterkit/issues/138


Patch:
diff --git a/package/src/components/CartEmptyMessage/v1/CartEmptyMessage.js b/package/src/components/CartEmptyMessage/v1/CartEmptyMessage.js
new file mode 100644
index 00000000..f127f64f
--- /dev/null
+++ b/package/src/components/CartEmptyMessage/v1/CartEmptyMessage.js
@@ -0,0 +1,66 @@
+import React, { Component, Fragment } from "react";
+import PropTypes from "prop-types";
+import styled from "styled-components";
+import { applyTheme } from "../../../utils";
+
+const EmptyButton = styled.div`
+  display: flex;
+  justify-content: center;
+`;
+
+const EmptyMessage = styled.div`
+  color: ${applyTheme("color_coolGrey500")};
+  display: flex;
+  font-size: ${applyTheme("cartEmptyMessageFontSize")};
+  justify-content: center;
+  letter-spacing: ${applyTheme("cartEmptyMessageLetterSpacing")};
+  margin-bottom: ${applyTheme("cartEmptyMessageMarginBottom")};
+`;
+
+class CartEmptyMessage extends Component {
+  static propTypes = {
+    /**
+     * Text to display inside the button
+     */
+    buttonText: PropTypes.string,
+    /**
+     * On object of component children to pass into this component
+     */
+    components: PropTypes.shape({
+      ContinueShoppingButton: PropTypes.oneOfType([PropTypes.string, PropTypes.func])
+    }),
+    /**
+     * Text to display inside the message area
+     */
+    messageText: PropTypes.string,
+    /**
+     * Onclick function to pass to the Button component. Not handled internally, directly passed
+     */
+    onClick: PropTypes.func.isRequired
+  }
+
+  static defaultProps = {
+    buttonText: "Continue shopping",
+    messageText: "Your shopping cart is empty."
+  };
+
+  handleOnClick = () => {
+    this.props.onClick();
+  }
+
+  render() {
+    const { buttonText, messageText } = this.props;
+    const { ContinueShoppingButton } = this.props.components;
+
+    return (
+      <Fragment>
+        <EmptyMessage>{messageText}</EmptyMessage>
+        <EmptyButton>
+          <ContinueShoppingButton actionType="important" onClick={this.handleOnClick}>{buttonText}</ContinueShoppingButton>
+        </EmptyButton>
+      </Fragment>
+    );
+  }
+}
+
+export default CartEmptyMessage;
diff --git a/package/src/components/CartEmptyMessage/v1/CartEmptyMessage.md b/package/src/components/CartEmptyMessage/v1/CartEmptyMessage.md
new file mode 100644
index 00000000..15ca58b4
--- /dev/null
+++ b/package/src/components/CartEmptyMessage/v1/CartEmptyMessage.md
@@ -0,0 +1,28 @@
+### CartEmptyMessage
+
+#### Overview
+The `CartEmptyMessage` displays when viewing an empty shopping cart.
+
+#### A cart empty message will be rendered when there are no items in your cart
+```jsx
+const Button = require("../../Button/v1").default;
+const onClick = () => {};
+
+  <CartEmptyMessage
+    components={{ContinueShoppingButton: Button}}
+    onClick={onClick}
+  />
+```
+
+#### It's possible to customize message and button text
+```jsx
+const Button = require("../../Button/v1").default;
+const onClick = () => {};
+
+  <CartEmptyMessage
+    buttonText="Custom button text"
+    components={{ContinueShoppingButton: Button}}
+    messageText="Custom message text"
+    onClick={onClick}
+  />
+```
diff --git a/package/src/components/CartEmptyMessage/v1/index.js b/package/src/components/CartEmptyMessage/v1/index.js
new file mode 100644
index 00000000..40455802
--- /dev/null
+++ b/package/src/components/CartEmptyMessage/v1/index.js
@@ -0,0 +1,1 @@
+export { default } from "./CartEmptyMessage";
diff --git a/package/src/defaultComponentTheme.js b/package/src/defaultComponentTheme.js
index d3623892..8da1be88 100644
--- a/package/src/defaultComponentTheme.js
+++ b/package/src/defaultComponentTheme.js
@@ -323,10 +323,17 @@ const helpTextStyles = {
 const errorsBlockStyles = {
   rui_errorsBlockColor: red,
   rui_errorsBlockFontSize: fontSize14,
-  rui_errorsBlockMargin: `${baseUnit(1)} 0 0`,
+  rui_errorsBlockMargin: `${baseUnit(1)} 0`,
   rui_errorsBlockIconMargin: `0 ${baseUnit(0.5)} 0 0`
 };
 
+// cartEmptyMessage
+const cartEmptyMessage = {
+  rui_cartEmptyMessageFontSize: baseFontSize,
+  rui_cartEmptyMessageLetterSpacing: `${baseUnit(0.03)}`,
+  rui_cartEmptyMessageMarginBottom: `${baseUnit(5.4)}`
+};
+
 export default {
   ...defaultStyles,
   ...buttonStyles,
@@ -336,5 +343,6 @@ export default {
   ...fieldStyles,
   ...labelStyles,
   ...helpTextStyles,
-  ...errorsBlockStyles
+  ...errorsBlockStyles,
+  ...cartEmptyMessage
 };
diff --git a/styleguide.config.js b/styleguide.config.js
index 6bb1809d..209b0c8a 100644
--- a/styleguide.config.js
+++ b/styleguide.config.js
@@ -98,7 +98,7 @@ module.exports = {
           name: "Product"
         }),
         generateSection({
-          componentNames: ["CartSummary"],
+          componentNames: ["CartEmptyMessage", "CartSummary"],
           content: "styleguide/src/sections/Cart.md",
           name: "Cart"
         }),
diff --git a/styleguide/src/sections/Cart.md b/styleguide/src/sections/Cart.md
index da2c2f8a..56bde7d2 100644
--- a/styleguide/src/sections/Cart.md
+++ b/styleguide/src/sections/Cart.md
@@ -1,1 +1,1 @@
-TODO Write introduction to Cart
+TODO: Add information related to the Cart


Test patch:
diff --git a/package/src/components/CartEmptyMessage/v1/CartEmptyMessage.test.js b/package/src/components/CartEmptyMessage/v1/CartEmptyMessage.test.js
new file mode 100644
index 00000000..c9c51667
--- /dev/null
+++ b/package/src/components/CartEmptyMessage/v1/CartEmptyMessage.test.js
@@ -0,0 +1,27 @@
+import React from "react";
+import renderer from "react-test-renderer";
+import Button from "../../Button/v1";
+import CartEmptyMessage from "./CartEmptyMessage";
+
+test("default cart empty button", () => {
+  const onClick = () => {};
+  const component = renderer.create(<CartEmptyMessage components={{ ContinueShoppingButton: Button }} onClick={onClick} />);
+
+  const tree = component.toJSON();
+  expect(tree).toMatchSnapshot();
+});
+
+test("custom cart empty button", () => {
+  const onClick = () => {};
+  const component = renderer.create((
+    <CartEmptyMessage
+      buttonText={"Custom button text"}
+      components={{ ContinueShoppingButton: Button }}
+      messageText={"Custom message text"}
+      onClick={onClick}
+    />
+  ));
+
+  const tree = component.toJSON();
+  expect(tree).toMatchSnapshot();
+});
diff --git a/package/src/components/CartEmptyMessage/v1/__snapshots__/CartEmptyMessage.test.js.snap b/package/src/components/CartEmptyMessage/v1/__snapshots__/CartEmptyMessage.test.js.snap
new file mode 100644
index 00000000..f5fa52da
--- /dev/null
+++ b/package/src/components/CartEmptyMessage/v1/__snapshots__/CartEmptyMessage.test.js.snap
@@ -0,0 +1,325 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`custom cart empty button 1`] = `
+Array [
+  .c0 {
+  color: #3c3c3c;
+  display: -webkit-box;
+  display: -webkit-flex;
+  display: -ms-flexbox;
+  display: flex;
+  font-size: 16;
+  -webkit-box-pack: center;
+  -webkit-justify-content: center;
+  -ms-flex-pack: center;
+  justify-content: center;
+  -webkit-letter-spacing: 0.01875rem;
+  -moz-letter-spacing: 0.01875rem;
+  -ms-letter-spacing: 0.01875rem;
+  letter-spacing: 0.01875rem;
+  margin-bottom: 3.375rem;
+}
+
+<div
+    className="c0"
+  >
+    Custom message text
+  </div>,
+  .c3 {
+  width: 1.125em;
+  height: 1.125em;
+}
+
+.c1 {
+  -webkit-font-smoothing: antialiased;
+  -webkit-align-items: center;
+  -webkit-box-align: center;
+  -ms-flex-align: center;
+  align-items: center;
+  background-color: #1999dd;
+  border-color: #1999dd;
+  border-style: solid;
+  border-width: 1px;
+  border-radius: 2px;
+  box-sizing: border-box;
+  color: #ffffff;
+  cursor: pointer;
+  display: -webkit-inline-box;
+  display: -webkit-inline-flex;
+  display: -ms-inline-flexbox;
+  display: inline-flex;
+  font-family: "Source Sans Pro","Helvetica Neue",Helvetica,sans-serif;
+  -webkit-box-pack: center;
+  -webkit-justify-content: center;
+  -ms-flex-pack: center;
+  justify-content: center;
+  margin: 0;
+  min-width: 100px;
+  outline: none;
+  padding-left: 20px;
+  padding-right: 20px;
+  padding-top: 10px;
+  padding-bottom: 10px;
+  position: relative;
+  text-align: center;
+}
+
+.c1:hover {
+  background-color: #25b0f9;
+  border-color: #25b0f9;
+  color: #ffffff;
+}
+
+.c1:active {
+  background-color: #057dbe;
+  border-color: #057dbe;
+  color: #ffffff;
+}
+
+.c2 {
+  display: -webkit-box;
+  display: -webkit-flex;
+  display: -ms-flexbox;
+  display: flex;
+  overflow: hidden;
+  padding-left: 20px;
+  -webkit-transition: width 0.2s ease-out 0s,padding-left 0.2s ease-out 0s,opacity 0.2s ease-out 0.2s;
+  transition: width 0.2s ease-out 0s,padding-left 0.2s ease-out 0s,opacity 0.2s ease-out 0.2s;
+}
+
+.c2 svg path,
+.c2 svg rect {
+  fill: #ffffff;
+}
+
+.c0 {
+  display: -webkit-box;
+  display: -webkit-flex;
+  display: -ms-flexbox;
+  display: flex;
+  -webkit-box-pack: center;
+  -webkit-justify-content: center;
+  -ms-flex-pack: center;
+  justify-content: center;
+}
+
+<div
+    className="c0"
+  >
+    <div
+      className="c1"
+      onClick={[Function]}
+      onKeyPress={[Function]}
+      role="button"
+      tabIndex={0}
+      title={undefined}
+    >
+      <div>
+        Custom button text
+      </div>
+      <div
+        className="c2"
+        style={
+          Object {
+            "opacity": 0,
+            "paddingLeft": 0,
+            "width": 0,
+          }
+        }
+      >
+        <svg
+          className="c3"
+          height="40px"
+          style={
+            Object {
+              "enableBackground": "new 0 0 50 50",
+            }
+          }
+          version="1.1"
+          viewBox="0 0 50 50"
+          width="40px"
+          x="0px"
+          y="0px"
+        >
+          <path
+            d="M43.935,25.145c0-10.318-8.364-18.683-18.683-18.683c-10.318,0-18.683,8.365-18.683,18.683h4.068c0-8.071,6.543-14.615,14.615-14.615c8.072,0,14.615,6.543,14.615,14.615H43.935z"
+            fill="#000"
+          >
+            <animateTransform
+              attributeName="transform"
+              attributeType="xml"
+              dur="1s"
+              from="0 25 25"
+              repeatCount="indefinite"
+              to="360 25 25"
+              type="rotate"
+            />
+          </path>
+        </svg>
+      </div>
+    </div>
+  </div>,
+]
+`;
+
+exports[`default cart empty button 1`] = `
+Array [
+  .c0 {
+  color: #3c3c3c;
+  display: -webkit-box;
+  display: -webkit-flex;
+  display: -ms-flexbox;
+  display: flex;
+  font-size: 16;
+  -webkit-box-pack: center;
+  -webkit-justify-content: center;
+  -ms-flex-pack: center;
+  justify-content: center;
+  -webkit-letter-spacing: 0.01875rem;
+  -moz-letter-spacing: 0.01875rem;
+  -ms-letter-spacing: 0.01875rem;
+  letter-spacing: 0.01875rem;
+  margin-bottom: 3.375rem;
+}
+
+<div
+    className="c0"
+  >
+    Your shopping cart is empty.
+  </div>,
+  .c3 {
+  width: 1.125em;
+  height: 1.125em;
+}
+
+.c1 {
+  -webkit-font-smoothing: antialiased;
+  -webkit-align-items: center;
+  -webkit-box-align: center;
+  -ms-flex-align: center;
+  align-items: center;
+  background-color: #1999dd;
+  border-color: #1999dd;
+  border-style: solid;
+  border-width: 1px;
+  border-radius: 2px;
+  box-sizing: border-box;
+  color: #ffffff;
+  cursor: pointer;
+  display: -webkit-inline-box;
+  display: -webkit-inline-flex;
+  display: -ms-inline-flexbox;
+  display: inline-flex;
+  font-family: "Source Sans Pro","Helvetica Neue",Helvetica,sans-serif;
+  -webkit-box-pack: center;
+  -webkit-justify-content: center;
+  -ms-flex-pack: center;
+  justify-content: center;
+  margin: 0;
+  min-width: 100px;
+  outline: none;
+  padding-left: 20px;
+  padding-right: 20px;
+  padding-top: 10px;
+  padding-bottom: 10px;
+  position: relative;
+  text-align: center;
+}
+
+.c1:hover {
+  background-color: #25b0f9;
+  border-color: #25b0f9;
+  color: #ffffff;
+}
+
+.c1:active {
+  background-color: #057dbe;
+  border-color: #057dbe;
+  color: #ffffff;
+}
+
+.c2 {
+  display: -webkit-box;
+  display: -webkit-flex;
+  display: -ms-flexbox;
+  display: flex;
+  overflow: hidden;
+  padding-left: 20px;
+  -webkit-transition: width 0.2s ease-out 0s,padding-left 0.2s ease-out 0s,opacity 0.2s ease-out 0.2s;
+  transition: width 0.2s ease-out 0s,padding-left 0.2s ease-out 0s,opacity 0.2s ease-out 0.2s;
+}
+
+.c2 svg path,
+.c2 svg rect {
+  fill: #ffffff;
+}
+
+.c0 {
+  display: -webkit-box;
+  display: -webkit-flex;
+  display: -ms-flexbox;
+  display: flex;
+  -webkit-box-pack: center;
+  -webkit-justify-content: center;
+  -ms-flex-pack: center;
+  justify-content: center;
+}
+
+<div
+    className="c0"
+  >
+    <div
+      className="c1"
+      onClick={[Function]}
+      onKeyPress={[Function]}
+      role="button"
+      tabIndex={0}
+      title={undefined}
+    >
+      <div>
+        Continue shopping
+      </div>
+      <div
+        className="c2"
+        style={
+          Object {
+            "opacity": 0,
+            "paddingLeft": 0,
+            "width": 0,
+          }
+        }
+      >
+        <svg
+          className="c3"
+          height="40px"
+          style={
+            Object {
+              "enableBackground": "new 0 0 50 50",
+            }
+          }
+          version="1.1"
+          viewBox="0 0 50 50"
+          width="40px"
+          x="0px"
+          y="0px"
+        >
+          <path
+            d="M43.935,25.145c0-10.318-8.364-18.683-18.683-18.683c-10.318,0-18.683,8.365-18.683,18.683h4.068c0-8.071,6.543-14.615,14.615-14.615c8.072,0,14.615,6.543,14.615,14.615H43.935z"
+            fill="#000"
+          >
+            <animateTransform
+              attributeName="transform"
+              attributeType="xml"
+              dur="1s"
+              from="0 25 25"
+              repeatCount="indefinite"
+              to="360 25 25"
+              type="rotate"
+            />
+          </path>
+        </svg>
+      </div>
+    </div>
+  </div>,
+]
+`;
diff --git a/package/src/components/ErrorsBlock/v1/__snapshots__/ErrorsBlock.test.js.snap b/package/src/components/ErrorsBlock/v1/__snapshots__/ErrorsBlock.test.js.snap
index 16b035af..41b76032 100644
--- a/package/src/components/ErrorsBlock/v1/__snapshots__/ErrorsBlock.test.js.snap
+++ b/package/src/components/ErrorsBlock/v1/__snapshots__/ErrorsBlock.test.js.snap
@@ -2,7 +2,7 @@
 
 exports[`renders all error messages 1`] = `
 .c0 {
-  margin: 0.625rem 0 0;
+  margin: 0.625rem 0;
 }
 
 .c1 {