-
Notifications
You must be signed in to change notification settings - Fork 57
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #97 from reactioncommerce/feat-135-nnnnat-cart-item
Add CartItem component
- Loading branch information
Showing
20 changed files
with
1,800 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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")} | ||
/> | ||
``` |
Oops, something went wrong.