Skip to content

Commit

Permalink
Merge pull request #97 from reactioncommerce/feat-135-nnnnat-cart-item
Browse files Browse the repository at this point in the history
Add CartItem component
  • Loading branch information
willopez authored Jun 28, 2018
2 parents dcd3e3a + 2443ca1 commit c9568d7
Show file tree
Hide file tree
Showing 20 changed files with 1,800 additions and 23 deletions.
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,15 @@
"suiteName": "jest-tests"
},
"dependencies": {
"@material-ui/core": "^1.2.3",
"lodash.debounce": "^4.0.8",
"lodash.get": "^4.4.2",
"lodash.isempty": "^4.4.0",
"lodash.isequal": "^4.5.0",
"mdi-material-ui": "^5.1.1",
"polished": "^1.9.2",
"react": "16.2.0",
"react-dom": "16.2.0",
"react": "16.4.0",
"react-dom": "16.4.0",
"react-select": "2.0.0-alpha.8",
"reacto-form": "0.0.2",
"styled-components": "3.1.6"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ 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
Expand All @@ -16,7 +15,6 @@ const onClick = () => {};

#### It's possible to customize message and button text
```jsx
const Button = require("../../Button/v1").default;
const onClick = () => {};

<CartEmptyMessage
Expand Down
268 changes: 268 additions & 0 deletions package/src/components/CartItem/v1/CartItem.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
import React, { Component } from "react";
import PropTypes from "prop-types";
import styled from "styled-components";
import { applyTheme } from "../../../utils";

const Item = styled.div`
align-items: flex-start;
border-bottom: solid 1px ${applyTheme("color_black05")};
box-sizing: border-box;
display: flex;
padding: 1rem;
width: 100%;
> * {
box-sizing: border-box;
}
`;

const ItemContent = styled.div`
display: flex;
margin-left: 1rem;
position: relative;
width: 100%;
`;

const ItemContentDetail = styled.div`
display: flex;
flex: 1 1 auto;
flex-wrap: wrap;
`;

const ItemContentDetailInner = styled.div`
display: flex;
flex: 1 1 100%;
flex-wrap: wrap;
`;

const ItemContentDetailInfo = styled.div`
flex: 1 1 100%;
@media (min-width: 992px) {
flex: 1 1 auto;
}
`;

const ItemContentQuantityInput = styled.div`
margin: 1rem 0;
@media (min-width: 992px) {
margin: 0;
}
`;

const ItemContentPrice = styled.div`
bottom: 0;
flex: 0 1 auto;
position: absolute;
right: 0;
@media (min-width: 768px) {
margin-left: 1.5rem;
position: relative;
}
`;

const ItemRemoveButton = styled.button`
align-self: flex-start;
background-color: transparent;
border: none;
color: ${applyTheme("color_coolGrey400")};
cursor: pointer;
display: table;
flex: 0 1 auto;
font-family: ${applyTheme("font_family")};
font-size: ${applyTheme("font_size_small")};
font-weight: normal;
line-height: normal;
letter-spacing: normal;
margin: 0.5rem 0 0;
padding: 0;
width: auto;
&:focus,
&:hover {
color: ${applyTheme("color_coolGrey")};
}
`;

class CartItem extends Component {
static propTypes = {
/**
* Provided child components to display item data
*/
components: PropTypes.shape({
/**
* CartItemDetail component
*/
CartItemDetailComponent: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
/**
* Stock warning component
*/
CartItemStockWarningComponent: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
/**
* Price component
*/
CartItemPriceComponent: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
/**
* QuantityInput component
*/
CartItemQuantityInputComponent: PropTypes.oneOfType([PropTypes.string, PropTypes.func])
}),
/**
* CartItem data
*/
item: PropTypes.shape({
/**
* The cart item ID
*/
_id: PropTypes.string,
/**
* Array of additional attributes of the chosen item.
*/
attributes: PropTypes.arrayOf(PropTypes.shape({
/**
* Attribute label (i.e. "Color").
*/
label: PropTypes.string,
/**
* Attribute value (i.e. "Red").
*/
value: PropTypes.string
})),
/**
* Current stock quantity of item
*/
currentQuantity: PropTypes.number,
/**
* Image url of chosen item
*/
imageUrl: PropTypes.string,
/**
* Is the chosen item have a low quantity
*/
isLowInventoryQuantity: PropTypes.bool,
/**
* Price object of chosen item
*/
price: PropTypes.shape({
/**
* Chosen items compare at price
*/
compareAtPrice: PropTypes.string,
/**
* Chosen items display price
*/
displayPrice: PropTypes.string
}),
/**
* Chosen items slug
*/
productSlug: PropTypes.string,
/**
* Chosen items title
*/
title: PropTypes.string,
/**
* Quantity of chosen item in cart
*/
quantity: PropTypes.number
}).isRequired,
/**
* On cart item quantity change handler
*/
onChangeCartItemQuantity: PropTypes.func,
/**
* On remove item from cart handler
*/
onRemoveItemFromCart: PropTypes.func
};

static defaultProps = {
components: {
CartItemDetailComponent: "Cart Item Detail",
CartItemStockWarningComponent: "Cart Item Stock Warning",
CartItemPriceComponent: "Cart Item Price",
CartItemQuantityInputComponent: "Cart Item Quantity Input"
},
onChangeCartItemQuantity() {},
onRemoveItemFromCart() {}
};

state = {
isProcessing: false
};

handleChangeCartItemQuantity = (value) => {
const { onChangeCartItemQuantity } = this.props;
onChangeCartItemQuantity(value);
};

handleRemoveItemFromCart = () => {
const { onRemoveItemFromCart, item: { _id } } = this.props;
onRemoveItemFromCart(_id);
};

renderImage() {
const { item: { imageUrl, productSlug } } = this.props;
return (
<a href={productSlug}>
<picture>
<source srcSet={`${imageUrl}/150`} media="(min-width: 768px)" />
<img src={`${imageUrl}/100`} alt="" />
</picture>
</a>
);
}

render() {
const {
components: {
CartItemDetailComponent,
CartItemStockWarningComponent,
CartItemPriceComponent,
CartItemQuantityInputComponent
},
item: {
attributes,
currentQuantity,
productSlug,
title,
quantity,
isLowInventoryQuantity,
price: { displayPrice, compareAtPrice }
}
} = this.props;
return (
<Item>
{this.renderImage()}
<ItemContent>
<ItemContentDetail>
<ItemContentDetailInner>
<ItemContentDetailInfo>
<CartItemDetailComponent title={title} productSlug={productSlug} attributes={attributes} />

<CartItemStockWarningComponent
inventoryQuantity={currentQuantity}
isLowInventoryQuantity={isLowInventoryQuantity}
/>
</ItemContentDetailInfo>

<ItemContentQuantityInput>
<CartItemQuantityInputComponent value={quantity} onChange={this.handleChangeCartItemQuantity} />
</ItemContentQuantityInput>
</ItemContentDetailInner>

<ItemRemoveButton onClick={this.handleRemoveItemFromCart}>Remove</ItemRemoveButton>
</ItemContentDetail>

<ItemContentPrice>
<CartItemPriceComponent displayPrice={displayPrice} displayCompareAtPrice={compareAtPrice} />
</ItemContentPrice>
</ItemContent>
</Item>
);
}
}

export default CartItem;
94 changes: 94 additions & 0 deletions package/src/components/CartItem/v1/CartItem.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
### CartItem

#### Overview
This component will be used when there is a need to show an item that customer has added to their cart.
It could be used in the future to show items that are within a "Wish List", "Saved for Later", or other customer generated lists of un-purchased products. There's potential that with a few modifications could be used to show Order Items, though the type and amount of information shown within an order item may be different enough to warrant a separate component.

#### Basic Usage
```jsx
const item = {
_id: "123",
attributes: [{ label: "vendor", value: "Patagonia" }, { label: "Color", value: "Red" }, { label: "Size", value: "Medium" }],
currentQuantity: 3,
imageUrl: "http://placehold.it",
isLowInventoryQuantity: true,
price: {
displayPrice: "$20.00",
compareAtPrice: "$45.00"
},
productSlug: "/product-slug",
title: "A Great Product",
quantity: 2
};

<CartItem
components={{
CartItemDetailComponent: CartItemDetail,
CartItemStockWarningComponent: StockWarning,
CartItemPriceComponent: Price,
CartItemQuantityInputComponent: QuantityInput
}}
item={item}
onChangeCartItemQuantity={value => console.log("cart item quantity changed to", value)}
onRemoveItemFromCart={() => console.log("Item removed from cart")}
/>
```

#### Without Compare At Price
```jsx
const item = {
_id: "123",
attributes: [{ label: "vendor", value: "Patagonia" }, { label: "Color", value: "Red" }, { label: "Size", value: "Medium" }],
currentQuantity: 3,
imageUrl: "http://placehold.it",
isLowInventoryQuantity: true,
price: {
displayPrice: "$20.00"
},
productSlug: "/product-slug",
title: "A Great Product",
quantity: 2
};

<CartItem
components={{
CartItemDetailComponent: CartItemDetail,
CartItemStockWarningComponent: StockWarning,
CartItemPriceComponent: Price,
CartItemQuantityInputComponent: QuantityInput
}}
item={item}
onChangeCartItemQuantity={value => console.log("cart item quantity changed to", value)}
onRemoveItemFromCart={() => console.log("Item removed from cart")}
/>
```

#### Without Stock Warning
```jsx
const item = {
_id: "123",
attributes: [{ label: "vendor", value: "Patagonia" }, { label: "Color", value: "Red" }, { label: "Size", value: "Medium" }],
currentQuantity: 3,
imageUrl: "http://placehold.it",
isLowInventoryQuantity: false,
price: {
displayPrice: "$20.00",
compareAtPrice: "$45.00"
},
productSlug: "/product-slug",
title: "A Great Product",
quantity: 2
};

<CartItem
components={{
CartItemDetailComponent: CartItemDetail,
CartItemStockWarningComponent: StockWarning,
CartItemPriceComponent: Price,
CartItemQuantityInputComponent: QuantityInput
}}
item={item}
onChangeCartItemQuantity={value => console.log("cart item quantity changed to", value)}
onRemoveItemFromCart={() => console.log("Item removed from cart")}
/>
```
Loading

0 comments on commit c9568d7

Please sign in to comment.