Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cart drawer #1544

Merged
merged 59 commits into from May 6, 2022
Merged
Show file tree
Hide file tree
Changes from 58 commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
ff4d096
Cart drawer
tyleralsbury Nov 17, 2021
17ea609
Added cart behaviours
tyleralsbury Nov 25, 2021
81dc58b
in progress - animations
tyleralsbury Nov 25, 2021
af20a9e
temp
tyleralsbury Nov 26, 2021
578619f
Behaviour improvements
tyleralsbury Mar 7, 2022
6ebb15f
temp
tyleralsbury Mar 17, 2022
6fe5bf4
Empty state
tyleralsbury Mar 17, 2022
9066c52
Merge branch 'main' into cart-drawer
ludoboludo Mar 29, 2022
74a3548
add new general settings and move old ones, also start updating the e…
ludoboludo Mar 29, 2022
fbfac65
add cart note to drawer and add conditionals to enable either notific…
ludoboludo Mar 30, 2022
815045e
change the DOM structure
ludoboludo Mar 31, 2022
d9c02af
fix layout
ludoboludo Mar 31, 2022
0aef289
adjust styling to match the cart page structure
ludoboludo Apr 1, 2022
cec56ce
tweaks
ludoboludo Apr 1, 2022
2f6e16d
fix cart bubble not updating quantity when adding/removing
ludoboludo Apr 5, 2022
8ee6791
remove comment
ludoboludo Apr 5, 2022
940ae0c
Fix animation on cart when adding to cart. Adjust font size on empty …
ludoboludo Apr 7, 2022
4f84c99
add dynamic checkout buttons and proper styling for them
ludoboludo Apr 7, 2022
133bd60
deal with dynamic checkout buttons in drawer and moving CSS into its …
ludoboludo Apr 7, 2022
2d73b1c
a11y fixes, load js asset only once, fix spacing in drawer
ludoboludo Apr 8, 2022
7fddfbf
fix asset loading when drawer is disabled and show dynamic checkout b…
ludoboludo Apr 8, 2022
6e4ca26
white space and console log removal
ludoboludo Apr 11, 2022
7a1fe97
fixes duplicate ID and close button position as well as focus when cl…
ludoboludo Apr 11, 2022
2f4d1eb
remove dynamic checkout setting and leave them on by default
ludoboludo Apr 11, 2022
8844b6a
address some UX feedback, more to come
ludoboludo Apr 12, 2022
b1aba03
tweak button classes and fix trapFocus target
ludoboludo Apr 12, 2022
b1050bb
some small a11y fixes
ludoboludo Apr 12, 2022
a17f2aa
Visual tweaks, spacing and a11y fix for the cart note and duplicate IDs
ludoboludo Apr 13, 2022
72aca93
fix overflow on chrome
ludoboludo Apr 13, 2022
c5e3c65
UX fixes and cart note class fix
ludoboludo Apr 19, 2022
e2d8347
add a11y fixes for cart table
ludoboludo Apr 19, 2022
a4ea68e
trying to fix focus trap
ludoboludo Apr 20, 2022
dce7ff9
fix focus trap for good. Hopefully.
ludoboludo Apr 21, 2022
801043b
keep cart icon in header as link
ludoboludo Apr 21, 2022
c45b0a9
add aria-haspopup on submit button only via JS
ludoboludo Apr 21, 2022
e0d6897
remove dynamic checkout from drawer
ludoboludo Apr 21, 2022
4e0774e
fix last a11y stuff
ludoboludo Apr 22, 2022
e65b016
fix Olivia's feedback
ludoboludo Apr 25, 2022
552e46c
a11y tweaks
ludoboludo Apr 25, 2022
bf2271d
Update 2 translation files
translation-platform[bot] Apr 26, 2022
ca5df79
Update 17 translation files
translation-platform[bot] Apr 26, 2022
751f1bc
Update 13 translation files
translation-platform[bot] Apr 26, 2022
4990c0d
change elements used for price totals
ludoboludo Apr 26, 2022
8933b67
fix spacing for discounted prices
ludoboludo Apr 26, 2022
fa0792d
address some of Lucas' comments
ludoboludo Apr 26, 2022
cd00038
fix language key
ludoboludo Apr 26, 2022
19f52de
Update 6 translation files
translation-platform[bot] Apr 27, 2022
07a9cda
move asset loading in header
ludoboludo Apr 27, 2022
9387bf9
styling edit
ludoboludo Apr 28, 2022
df12fcd
Update 1 translation file
translation-platform[bot] Apr 28, 2022
e293d24
Edit IDs and remove repeated JS
ludoboludo Apr 29, 2022
be5058a
move a11y string to theme.liquid
ludoboludo Apr 29, 2022
451e021
move cart drawer item JS
ludoboludo May 2, 2022
cd72d83
set back top value to 0 and remove console log
ludoboludo May 3, 2022
7fff7d5
Cart type = page (#1638)
ludoboludo May 3, 2022
26cdf47
Update 11 translation files
translation-platform[bot] May 4, 2022
2754934
adjust close button color for iOS
ludoboludo May 4, 2022
0ad8202
fix focus on quick add when closing drawer
ludoboludo May 4, 2022
12ee9d1
fix table header layout on mobile
ludoboludo May 5, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions assets/base.css
Expand Up @@ -2352,6 +2352,7 @@ input[type='checkbox'] {
.header__icon--cart .icon {
height: 4.4rem;
width: 4.4rem;
padding: 0;
}

.header__icon--cart {
Expand Down
126 changes: 126 additions & 0 deletions assets/cart-drawer.js
@@ -0,0 +1,126 @@
class CartDrawer extends HTMLElement {
constructor() {
super();

this.addEventListener('keyup', (evt) => evt.code === 'Escape' && this.close());
this.querySelector('#CartDrawer-Overlay').addEventListener('click', this.close.bind(this));
this.setHeaderCartIconAccessibility();
}

setHeaderCartIconAccessibility() {
const cartLink = document.querySelector('#cart-icon-bubble');
cartLink.setAttribute('role', 'button');
cartLink.setAttribute('aria-haspopup', 'dialog');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just flagging that this is related to #1612. We'll need to replace it:

Suggested change
cartLink.setAttribute('aria-haspopup', 'dialog');
cartLink.setAttribute('aria-describedby', 'a11y-modal-window-message');

once that PR is merged.

No need to change anything yet

cartLink.addEventListener('click', (event) => {
event.preventDefault();
this.open(cartLink)
});
cartLink.addEventListener('keydown', (event) => {
ludoboludo marked this conversation as resolved.
Show resolved Hide resolved
if (event.code.toUpperCase() === 'SPACE') {
event.preventDefault();
this.open(cartLink);
}
});
}

open(triggeredBy) {
if (triggeredBy) this.setActiveElement(triggeredBy);
const cartDrawerNote = this.querySelector('[id^="Details-"] summary');
if (cartDrawerNote && !cartDrawerNote.hasAttribute('role')) this.setSummaryAccessibility(cartDrawerNote);
// here the animation doesn't seem to always get triggered. A timeout seem to help
setTimeout(() => {this.classList.add('animate', 'active')});

this.addEventListener('transitionend', () => {
const containerToTrapFocusOn = this.classList.contains('is-empty') ? this.querySelector('.drawer__inner-empty') : document.getElementById('CartDrawer');
const focusElement = this.querySelector('.drawer__inner') || this.querySelector('.drawer__close');
trapFocus(containerToTrapFocusOn, focusElement);
}, { once: true });

document.body.classList.add('overflow-hidden');
}

close() {
this.classList.remove('active');
removeTrapFocus(this.activeElement);
document.body.classList.remove('overflow-hidden');
}

setSummaryAccessibility(cartDrawerNote) {
cartDrawerNote.setAttribute('role', 'button');
cartDrawerNote.setAttribute('aria-expanded', 'false');

if(cartDrawerNote.nextElementSibling.getAttribute('id')) {
ludoboludo marked this conversation as resolved.
Show resolved Hide resolved
cartDrawerNote.setAttribute('aria-controls', cartDrawerNote.nextElementSibling.id);
}

cartDrawerNote.addEventListener('click', (event) => {
event.currentTarget.setAttribute('aria-expanded', !event.currentTarget.closest('details').hasAttribute('open'));
});

cartDrawerNote.parentElement.addEventListener('keyup', onKeyUpEscape);
}

renderContents(parsedState) {
this.querySelector('.drawer__inner').classList.contains('is-empty') && this.querySelector('.drawer__inner').classList.remove('is-empty');
this.productId = parsedState.id;
this.getSectionsToRender().forEach((section => {
const sectionElement = section.selector ? document.querySelector(section.selector) : document.getElementById(section.id);
sectionElement.innerHTML =
this.getSectionInnerHTML(parsedState.sections[section.id], section.selector);
}));

setTimeout(() => {
this.querySelector('#CartDrawer-Overlay').addEventListener('click', this.close.bind(this));
this.open();
});
}

getSectionInnerHTML(html, selector = '.shopify-section') {
return new DOMParser()
.parseFromString(html, 'text/html')
.querySelector(selector).innerHTML;
}

getSectionsToRender() {
return [
{
id: 'cart-drawer',
selector: '#CartDrawer'
},
{
id: 'cart-icon-bubble'
}
];
}

getSectionDOM(html, selector = '.shopify-section') {
return new DOMParser()
.parseFromString(html, 'text/html')
.querySelector(selector);
}

setActiveElement(element) {
this.activeElement = element;
}
}

customElements.define('cart-drawer', CartDrawer);

class CartDrawerItems extends CartItems {
getSectionsToRender() {
return [
{
id: 'CartDrawer',
section: 'cart-drawer',
selector: '.drawer__inner'
},
{
id: 'cart-icon-bubble',
section: 'cart-icon-bubble',
selector: '.shopify-section'
}
];
}
}

customElements.define('cart-drawer-items', CartDrawerItems);
2 changes: 1 addition & 1 deletion assets/cart-notification.js
Expand Up @@ -5,7 +5,7 @@ class CartNotification extends HTMLElement {
this.notification = document.getElementById('cart-notification');
this.header = document.querySelector('sticky-header');
this.onBodyClick = this.handleBodyClick.bind(this);

this.notification.addEventListener('keyup', (evt) => evt.code === 'Escape' && this.close());
this.querySelectorAll('button[type="button"]').forEach((closeButton) =>
closeButton.addEventListener('click', this.close.bind(this))
Expand Down
56 changes: 44 additions & 12 deletions assets/cart.js
Expand Up @@ -3,7 +3,8 @@ class CartRemoveButton extends HTMLElement {
super();
this.addEventListener('click', (event) => {
event.preventDefault();
this.closest('cart-items').updateQuantity(this.dataset.index, 0);
const cartItems = this.closest('cart-items') || this.closest('cart-drawer-items');
cartItems.updateQuantity(this.dataset.index, 0);
});
}
}
Expand All @@ -14,7 +15,7 @@ class CartItems extends HTMLElement {
constructor() {
super();

this.lineItemStatusElement = document.getElementById('shopping-cart-line-item-status');
this.lineItemStatusElement = document.getElementById('shopping-cart-line-item-status') || document.getElementById('CartDrawer-LineItemStatus');

this.currentItemCount = Array.from(this.querySelectorAll('[name="updates[]"]'))
.reduce((total, quantityInput) => total + parseInt(quantityInput.value), 0);
Expand Down Expand Up @@ -72,43 +73,54 @@ class CartItems extends HTMLElement {
.then((state) => {
const parsedState = JSON.parse(state);
this.classList.toggle('is-empty', parsedState.item_count === 0);
const cartDrawerWrapper = document.querySelector('cart-drawer');
const cartFooter = document.getElementById('main-cart-footer');

if (cartFooter) cartFooter.classList.toggle('is-empty', parsedState.item_count === 0);
if (cartDrawerWrapper) cartDrawerWrapper.classList.toggle('is-empty', parsedState.item_count === 0);

this.getSectionsToRender().forEach((section => {
const elementToReplace =
document.getElementById(section.id).querySelector(section.selector) || document.getElementById(section.id);

elementToReplace.innerHTML =
this.getSectionInnerHTML(parsedState.sections[section.section], section.selector);
}));

this.updateLiveRegions(line, parsedState.item_count);
const lineItem = document.getElementById(`CartItem-${line}`);
if (lineItem && lineItem.querySelector(`[name="${name}"]`)) lineItem.querySelector(`[name="${name}"]`).focus();
const lineItem = document.getElementById(`CartItem-${line}`) || document.getElementById(`CartDrawer-Item-${line}`);
if (lineItem && lineItem.querySelector(`[name="${name}"]`)) {
cartDrawerWrapper ? trapFocus(cartDrawerWrapper, lineItem.querySelector(`[name="${name}"]`)) : lineItem.querySelector(`[name="${name}"]`).focus();
} else if (parsedState.item_count === 0 && cartDrawerWrapper) {
trapFocus(cartDrawerWrapper.querySelector('.drawer__inner-empty'), cartDrawerWrapper.querySelector('a'))
} else if (document.querySelector('.cart-item') && cartDrawerWrapper) {
trapFocus(cartDrawerWrapper, document.querySelector('.cart-item__name'))
}
this.disableLoading();
}).catch(() => {
this.querySelectorAll('.loading-overlay').forEach((overlay) => overlay.classList.add('hidden'));
document.getElementById('cart-errors').textContent = window.cartStrings.error;
const errors = document.getElementById('cart-errors') || document.getElementById('CartDrawer-CartErrors');
errors.textContent = window.cartStrings.error;
this.disableLoading();
});
}

updateLiveRegions(line, itemCount) {
if (this.currentItemCount === itemCount) {
document.getElementById(`Line-item-error-${line}`)
const lineItemError = document.getElementById(`Line-item-error-${line}`) || document.getElementById(`CartDrawer-LineItemError-${line}`);
const quantityElement = document.getElementById(`Quantity-${line}`) || document.getElementById(`Drawer-quantity-${line}`);

lineItemError
.querySelector('.cart-item__error-text')
.innerHTML = window.cartStrings.quantityError.replace(
'[quantity]',
document.getElementById(`Quantity-${line}`).value
quantityElement.value
);
}

this.currentItemCount = itemCount;
this.lineItemStatusElement.setAttribute('aria-hidden', true);

const cartStatus = document.getElementById('cart-live-region-text');
const cartStatus = document.getElementById('cart-live-region-text') || document.getElementById('CartDrawer-LiveRegionText');
cartStatus.setAttribute('aria-hidden', false);

setTimeout(() => {
Expand All @@ -123,15 +135,35 @@ class CartItems extends HTMLElement {
}

enableLoading(line) {
document.getElementById('main-cart-items').classList.add('cart__items--disabled');
this.querySelectorAll(`#CartItem-${line} .loading-overlay`).forEach((overlay) => overlay.classList.remove('hidden'));
const mainCartItems = document.getElementById('main-cart-items') || document.getElementById('CartDrawer-CartItems');
mainCartItems.classList.add('cart__items--disabled');

const cartItemElements = this.querySelectorAll(`#CartItem-${line} .loading-overlay`);
const cartDrawerItemElements = this.querySelectorAll(`#CartDrawer-Item-${line} .loading-overlay`);

[...cartItemElements, ...cartDrawerItemElements].forEach((overlay) => overlay.classList.remove('hidden'));

document.activeElement.blur();
this.lineItemStatusElement.setAttribute('aria-hidden', false);
}

disableLoading() {
document.getElementById('main-cart-items').classList.remove('cart__items--disabled');
const mainCartItems = document.getElementById('main-cart-items') || document.getElementById('CartDrawer-CartItems');
mainCartItems.classList.remove('cart__items--disabled');
}
}

customElements.define('cart-items', CartItems);

if (!customElements.get('cart-note')) {
customElements.define('cart-note', class CartNote extends HTMLElement {
constructor() {
super();

this.addEventListener('change', debounce((event) => {
const body = JSON.stringify({ note: event.target.value });
fetch(`${routes.cart_update_url}`, {...fetchConfig(), ...{ body }});
}, 300))
}
});
};