Skip to content

Commit

Permalink
robust cart component, mobile + free shipping progress example
Browse files Browse the repository at this point in the history
  • Loading branch information
iamkevingreen committed Nov 28, 2020
1 parent c887965 commit 3aec24d
Show file tree
Hide file tree
Showing 6 changed files with 174 additions and 64 deletions.
27 changes: 2 additions & 25 deletions web/src/components/cart.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
import React, { useState, useEffect } from 'react'

import { PageLink } from 'src/components/link'
import { useCartTotals, useCartItems, useCheckout, useToggleCart } from 'src/context/siteContext'
import { useCartTotals, useCartItems } from 'src/context/siteContext'

import {
LineItem
} from 'src/components/cart/lineItem'

export const Cart = () => {
const lineItems = useCartItems()
const openCheckout = useCheckout()
const toggleCart = useToggleCart()
const { total } = useCartTotals()
return (
<div>
Expand All @@ -23,30 +20,10 @@ export const Cart = () => {
))}
</div>
) : (
<div className='ac'>
<div className='tc p3'>
Cart is Empty
</div>
)}
<div className='bcw p1'>
<div className='x jcb p05'>
<h5 className='ac x caps'>Shipping is Free</h5>
{lineItems.length > 0 && (
<div className='x f jcb aic'>
<span className='s24'>Subtotal</span>
<span className='s24'>{total}</span>
</div>
)}
</div>
{lineItems.length < 1 ? (
<div className='f jcc aic rel x'>
<PageLink className='s24 m05' onClick={() => toggleCart()} to='/'>Let's Go Shooooping</PageLink>
</div>
): (
<button onClick={() => openCheckout()} type='submit' className='button bcblue cw button--h-black x bcb p1'>
<span className='s24 block p1'>Checkout</span>
</button>
)}
</div>
</div>
)
}
57 changes: 34 additions & 23 deletions web/src/components/cart/lineItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,31 +41,42 @@ export const LineItem = ({ id, title, quantity, variant: { price, compareAtPrice
)

return (
<div className='f x aic rel cart__single'>
<div className='cart__single-image mr1'>
<img className='x' src={itemImage} alt={title} />
</div>
<div>
<span className='h4 ls1 caps m0 p0'>{title}</span>
<div className='f jcs aic product__form-qty-wrapper mt05 mxa'>
<button className='block rel qty__control no-style s24 f jcc aic founders cursor py05 aic' onClick={() => stateQuantity === 1 ? null : updateQuantity(stateQuantity - 1)}><Minus /></button>
<input type='number' value={stateQuantity} onChange={e => updateQuantity(parseInt(e.currentTarget.value, 10))} name='quantity' min='1' className='cb founders card-qty bn ac' />
<button className='qty__control no-style s1 block f jcc aic founders s24 cursor rel py05 jcc aic' onClick={() => updateQuantity(stateQuantity + 1)}><Plus /></button>
<div className='abs right cart__single-price s16 bottom p1'>
{compareAtPrice && (
<span className='strikethrough'>
${parseFloat(compareAtPrice) * stateQuantity} ({stateQuantity})
</span>
)}
<span>
${parseFloat(price) * stateQuantity} ({stateQuantity})
</span>
<>
<div className='x df pr mt1 line__item-single row gutter--none'>
<div className='line__item-image col c5'>
<img className='x db' src={itemImage} alt={title} />
</div>
<div className='col c11 df jcb fdc'>
<div className='df jcb y fdc pl1'>
<span className='s15 m0 p0'>{title}</span>
<div className='row'>
<div className='col c6'>
<div className='df aic jcb line__item-qty'>
<button aria-label='decrease quantity' className='block rel qty__control button--none bg--transparent f jcc aic pr cursor aic' onClick={() => stateQuantity === 1 ? null : updateQuantity(stateQuantity - 1)}><Minus /></button>
<span name='quantity' min='1' className=' tc card-qty'>{stateQuantity}</span>
<button aria-label='increase quantity' className='qty__control button--none bg--transparent s1 block f jcc aic pr cursor rel jcc aic' onClick={() => updateQuantity(stateQuantity + 1)}><Plus /></button>
</div>
</div>
<div className='col c2' />
<div className='col c8 tl '>
<div className='right line__item-price s15'>
{compareAtPrice && (
<span className='strikethrough'>
${parseFloat(compareAtPrice) * stateQuantity}
</span>
)}
<span className='s15'>
${parseFloat(price) * stateQuantity}
</span>
</div>
</div>
</div>
</div>
<button type='reset' className='p05 pa button--none bg--transparent close right top cb' onClick={() => removeFromCart(id)}>
<Close className='block' />
</button>
</div>
<button type='reset' className='p05 abs no-style close right top cb' onClick={() => removeFromCart(id)}>
<Close className='block' />
</button>
</div>
</div>
</>
)
}
82 changes: 68 additions & 14 deletions web/src/components/cartDrawer.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,89 @@
import React from 'react'
import { Link } from 'gatsby'
import cx from 'classnames'

import { Cart } from 'src/components/cart'
import { useStore, useToggleCart} from 'src/context/siteContext'
import { useStore, useToggleCart, useCartTotals, useCartItems, useCheckout } from 'src/context/siteContext'

import {
Close
} from 'src/components/svgs'

/*
/ Cart Drawer: This module is a bit more robust then other parts of the theme,
/ this is because carts are relatively difficult to execute, scale to mobile etc,
/ feel free to keep more of this styling if you use this component, the structure
/ a bit confusing to make sure all elements are visible/scrollable on mobile
*/

export const CartDrawer = () => {
const lineItems = useCartItems()
const { cartIsOpen } = useStore()
const { total } = useCartTotals()
const toggleCart = useToggleCart()
const openCheckout = useCheckout()
const trap = cartIsOpen ? (
<div className='cart__drawer-inner'>
<div className='ac rel cart__drawer-header'>
<h4>Your Cart</h4>
<button type='reset' className='p05 abs no-style cart__drawer-close close right top cb' onClick={() => toggleCart()}>
<Close className='block' />
</button>
<React.Fragment>
<div className='cart__drawer-inner'>
<div className='ac rel cart__drawer-header df aic jcc'>
<button type='reset' className='pa button--none bg--transparent cart__drawer-close close left cb' onClick={() => toggleCart()}>
<Close className='db m1' />
</button>
<h4 className='s26 medium'>Cart</h4>
</div>
<div className='bg--off-white cart__drawer-shipping df jcc aic tc'>
<div className='x ac'>
<div className='cart__drawer-progress x pr'>
<span className='block pa bg--black left y' style={{ 'width': `${((parseFloat(total)).toFixed(2) / 40) * 100}%` }} />
</div>
{lineItems.length > 0 && (
<>
{parseInt(total, 0) <= 40 ? (
<div className='s12 p1'>You're <span className=''>${(40 - (parseFloat(total))).toFixed(2)}</span> away from free shipping!</div>
) : (
<div className='s12 p1'>Your order qualifies for <span className='bold'>Free Shipping!</span></div>
)}
</>
)}
</div>
</div>
<Cart />
</div>
<div className={cx('bg--white cart__drawer-buttons', parseInt(total, 0) >= 40 && 'free', lineItems.length >= 1 && 'visible')}>
<div className='f fw bcw left ac cart__drawer-bottom pa bottom x jcc aic rel x'>
<div className='x p2 jcb'>
{lineItems.length > 0 && (
<div className='py1 cart__drawer-sub medium'>
<div className='x df jcb aic py18 px1 bcw'>
<span className='s18'>Subtotal</span>
<span className='s18 mono'>${total}</span>
</div>
</div>
)}
</div>
{lineItems.length < 1 ? (
<Link className='button button--lg p3 bold s16 dib tc color--white bg--black medium x' onClick={() => toggleCart()} to='/'>Continue Shopping</Link>
): (
<button onClick={openCheckout} type='submit' className='button bn bt p2 medium bg--black color--black s16 x'>
<span className='color--white'>Checkout</span>
</button>
)}
</div>
</div>
<Cart />
</div>
</React.Fragment>
) : (
false
)
return (
<div
className={cx('cart__drawer bcw z10 right top x abs', cartIsOpen && 'is-open')}
id='container'
>
<>
<div
className={cx('cart__drawer bg--white aist z10 right top x pf', cartIsOpen && 'is-open')}
id='container'
>
{trap}
</div>
</div>
{/* Handles the overlay to click-close the cart */}
<div onClick={() => toggleCart()} className={cx('cart__drawer-bg bg--white z9 left top x y pf', cartIsOpen && 'is-open')} />
</>
)
}
4 changes: 2 additions & 2 deletions web/src/context/siteContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -185,10 +185,10 @@ function useCartTotals() {
} = useContext(StoreContext)

const tax = checkout.totalTaxV2
? `$${Number(checkout.totalTaxV2.amount).toFixed(2)}`
? `${Number(checkout.totalTaxV2.amount).toFixed(2)}`
: '-'
const total = checkout.totalPriceV2
? `$${Number(checkout.totalPriceV2.amount).toFixed(2)}`
? `${Number(checkout.totalPriceV2.amount).toFixed(2)}`
: '-'

return {
Expand Down
67 changes: 67 additions & 0 deletions web/src/styles/lib/_drawer.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
.cart {
&__drawer {
height: 100vh;
max-height: -webkit-fill-available;
max-height: stretch;
border-left: 1px solid currentColor;
transition: all 0.3s ease-in-out;
transform: translateX(100%);
display: flex;
justify-content: space;
flex-direction: column;
justify-content: space-between;
width: 100%;
align-items: stretch;
&-buttons {
&.visible {
border-top: 2px solid rgba(28, 31, 42, 0.1);
}
}
&-inner {
flex: 1;
overflow-y: scroll;
}
@include breakpoint(800) {
left: auto;
transform: translateX(375px);
max-width: 375px;
right: 0;
}
&-progress {
min-height: 6px;
> span {
max-width: 100%;
}
}
&-bg {
opacity: 0;
user-select: none;
visibility: hidden;
&.is-open {
user-select: visible;
visibility: visible;
opacity: 0.4;
}
}
&.is-open {
transform: translateX(0px);
}
&-header {
min-height: 60px;
}
&-close {
margin-left: 10px;
svg {
width: 10px;
height: auto;
}
}
&-buttons {
max-height: 120px;
flex: 1;
&.visible {
max-height: 164px;
}
}
}
}
1 change: 1 addition & 0 deletions web/src/styles/main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
@import './lib/config';
@import './lib/containers';
@import './lib/borders';
@import './lib/drawer';
// @import './lib/fonts';
// @import './lib/global';
// @import './lib/typography';
Expand Down

0 comments on commit 3aec24d

Please sign in to comment.