From af7c31b61316495352ee8bdb7ba4dcf00bae527a Mon Sep 17 00:00:00 2001 From: Jeremy Thomas Date: Mon, 10 Jun 2024 23:20:16 +0100 Subject: [PATCH 1/7] Init shop --- docs/_includes/docs/hero.html | 8 +- docs/_layouts/default.html | 7 +- docs/assets/css/main.css | 12 - docs/assets/javascript/shop.js | 1053 ++++++++++++++++++++++++++++++++ docs/shop.html | 396 ++++++++++++ 5 files changed, 1459 insertions(+), 17 deletions(-) create mode 100644 docs/assets/javascript/shop.js create mode 100644 docs/shop.html diff --git a/docs/_includes/docs/hero.html b/docs/_includes/docs/hero.html index 5a417b1f..9331c382 100644 --- a/docs/_includes/docs/hero.html +++ b/docs/_includes/docs/hero.html @@ -24,7 +24,9 @@ {% endif %} -
- {% include website/carbon.html %} -
+ {% unless include.hide_carbon == true %} +
+ {% include website/carbon.html %} +
+ {% endunless %} diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html index c5cd131f..1dab741f 100644 --- a/docs/_layouts/default.html +++ b/docs/_layouts/default.html @@ -12,8 +12,11 @@ {% endfor %} {% endif %} - {% include global/support.html %} - {% include global/native.html %} + {% unless page.hide_footer %} + {% include global/support.html %} + {% include global/native.html %} + {% endunless %} + {% include global/about.html %} diff --git a/docs/assets/css/main.css b/docs/assets/css/main.css index f3e28544..ae1bb9ca 100644 --- a/docs/assets/css/main.css +++ b/docs/assets/css/main.css @@ -1,15 +1,3 @@ -:root { - --zlog-h: 221deg; - --zlog-s: 40%; - --zlog-l: 30%; - --zlog: hsl(var(--zlog-h), var(--zlog-s), var(--zlog-l)); -} - -.zlog { - color: var(--zlog); - display: none; -} - body { align-content: flex-start; /* display: grid; */ diff --git a/docs/assets/javascript/shop.js b/docs/assets/javascript/shop.js new file mode 100644 index 00000000..91aa6a85 --- /dev/null +++ b/docs/assets/javascript/shop.js @@ -0,0 +1,1053 @@ +document.addEventListener("DOMContentLoaded", () => { + // Utils + const isObject = (obj) => { + return obj !== null && typeof obj === "object" && !Array.isArray(obj); + }; + + const isEmpty = (obj) => { + return Object.keys(obj).length === 0; + }; + + const humanizeGraphQLResponse = (input) => { + if (!input) return null; + const output = {}; + + Object.keys(input).forEach((key) => { + if (input[key] && input[key].edges) { + output[key] = input[key].edges.map((edge) => + humanizeGraphQLResponse(edge.node), + ); + } else if (isObject(input[key])) { + output[key] = humanizeGraphQLResponse(input[key]); + } else { + output[key] = input[key]; + } + }); + + return output; + }; + + const formatPrice = (price) => { + const { amount, currencyCode } = price; + return `${CURRENCIES[currencyCode]}${Math.trunc(amount)}`; + }; + + const getId = (id) => { + const parts = id.split("/"); + return parts[parts.length - 1]; + }; + + const getProductFromVariant = (variantId) => { + let product = {}; + let variant = {}; + + state.products.forEach((p) => { + const foundVariant = p.variants.find((v) => { + return v.id === variantId; + }); + + if (foundVariant) { + product = p; + variant = foundVariant; + } + }); + + return { + product, + variant, + }; + }; + + const STORAGE_CART_ID = "bulma-shop-cart-id"; + + const CURRENCIES = { + AED: "د.إ", + AFN: "؋", + ALL: "L", + AMD: "֏", + ANG: "ƒ", + AOA: "Kz", + ARS: "$", + AUD: "$", + AWG: "ƒ", + AZN: "₼", + BAM: "KM", + BBD: "$", + BDT: "৳", + BGN: "лв", + BHD: ".د.ب", + BIF: "FBu", + BMD: "$", + BND: "$", + BOB: "$b", + BOV: "BOV", + BRL: "R$", + BSD: "$", + BTC: "₿", + BTN: "Nu.", + BWP: "P", + BYN: "Br", + BYR: "Br", + BZD: "BZ$", + CAD: "$", + CDF: "FC", + CHE: "CHE", + CHF: "CHF", + CHW: "CHW", + CLF: "CLF", + CLP: "$", + CNH: "¥", + CNY: "¥", + COP: "$", + COU: "COU", + CRC: "₡", + CUC: "$", + CUP: "₱", + CVE: "$", + CZK: "Kč", + DJF: "Fdj", + DKK: "kr", + DOP: "RD$", + DZD: "دج", + EEK: "kr", + EGP: "£", + ERN: "Nfk", + ETB: "Br", + ETH: "Ξ", + EUR: "€", + FJD: "$", + FKP: "£", + GBP: "£", + GEL: "₾", + GGP: "£", + GHC: "₵", + GHS: "GH₵", + GIP: "£", + GMD: "D", + GNF: "FG", + GTQ: "Q", + GYD: "$", + HKD: "$", + HNL: "L", + HRK: "kn", + HTG: "G", + HUF: "Ft", + IDR: "Rp", + ILS: "₪", + IMP: "£", + INR: "₹", + IQD: "ع.د", + IRR: "﷼", + ISK: "kr", + JEP: "£", + JMD: "J$", + JOD: "JD", + JPY: "¥", + KES: "KSh", + KGS: "лв", + KHR: "៛", + KMF: "CF", + KPW: "₩", + KRW: "₩", + KWD: "KD", + KYD: "$", + KZT: "₸", + LAK: "₭", + LBP: "£", + LKR: "₨", + LRD: "$", + LSL: "M", + LTC: "Ł", + LTL: "Lt", + LVL: "Ls", + LYD: "LD", + MAD: "MAD", + MDL: "lei", + MGA: "Ar", + MKD: "ден", + MMK: "K", + MNT: "₮", + MOP: "MOP$", + MRO: "UM", + MRU: "UM", + MUR: "₨", + MVR: "Rf", + MWK: "MK", + MXN: "$", + MXV: "MXV", + MYR: "RM", + MZN: "MT", + NAD: "$", + NGN: "₦", + NIO: "C$", + NOK: "kr", + NPR: "₨", + NZD: "$", + OMR: "﷼", + PAB: "B/.", + PEN: "S/.", + PGK: "K", + PHP: "₱", + PKR: "₨", + PLN: "zł", + PYG: "Gs", + QAR: "﷼", + RMB: "¥", + RON: "lei", + RSD: "Дин.", + RUB: "₽", + RWF: "R₣", + SAR: "﷼", + SBD: "$", + SCR: "₨", + SDG: "ج.س.", + SEK: "kr", + SGD: "S$", + SHP: "£", + SLL: "Le", + SOS: "S", + SRD: "$", + SSP: "£", + STD: "Db", + STN: "Db", + SVC: "$", + SYP: "£", + SZL: "E", + THB: "฿", + TJS: "SM", + TMT: "T", + TND: "د.ت", + TOP: "T$", + TRL: "₤", + TRY: "₺", + TTD: "TT$", + TVD: "$", + TWD: "NT$", + TZS: "TSh", + UAH: "₴", + UGX: "USh", + USD: "$", + UYI: "UYI", + UYU: "$U", + UYW: "UYW", + UZS: "лв", + VEF: "Bs", + VES: "Bs.S", + VND: "₫", + VUV: "VT", + WST: "WS$", + XAF: "FCFA", + XBT: "Ƀ", + XCD: "$", + XOF: "CFA", + XPF: "₣", + XSU: "Sucre", + XUA: "XUA", + YER: "﷼", + ZAR: "R", + ZMW: "ZK", + ZWD: "Z$", + ZWL: "$", + }; + + const CART_QL = ` + id + createdAt + updatedAt + checkoutUrl + buyerIdentity { + countryCode + } + cost { + totalAmount { + amount + currencyCode + } + } + lines(first: 20) { + edges { + node { + id + quantity + cost { + subtotalAmount { + amount + currencyCode + } + totalAmount { + amount + currencyCode + } + } + merchandise { + ... on ProductVariant { + id + } + } + } + } + } + `; + + const COST_QL = ` + cost { + totalAmount { + amount + currencyCode + } + # The estimated amount, before taxes and discounts, for the customer to pay at checkout. + subtotalAmount { + amount + currencyCode + } + # The estimated tax amount for the customer to pay at checkout. + totalTaxAmount { + amount + currencyCode + } + # The estimated duty amount for the customer to pay at checkout. + totalDutyAmount { + amount + currencyCode + } + } + `; + + // State + const state = { + cart: {}, + products: [], + isLoading: false, + hasFetchedProducts: false, + countryCode: null, + }; + + // UI + const $cart = document.getElementById("cart"); + const $cartClose = document.querySelectorAll(".shop-cart-close"); + const $openCart = document.getElementById("open-cart"); + const $emptyCart = document.getElementById("empty-cart"); + const $fullCart = document.getElementById("full-cart"); + const $cartItems = document.getElementById("cart-items"); + const $products = document.getElementById("products"); + const $modal = document.getElementById("shop-modal"); + const $modalClose = document.querySelectorAll(".shop-modal-close"); + + $cartClose.forEach((el) => { + el.addEventListener("click", (event) => { + event.preventDefault(); + $cart.classList.remove("is-active"); + }); + }); + + $openCart.addEventListener("click", (event) => { + event.preventDefault(); + $cart.classList.add("is-active"); + }); + + $modalClose.forEach((el) => { + el.addEventListener("click", (event) => { + event.preventDefault(); + closeModal(); + }); + }); + + document.addEventListener("keydown", (event) => { + if (event.key === "Escape") { + $cart.classList.remove("is-active"); + closeModal(); + } + }); + + const closeModal = () => { + $modal.classList.remove("is-active"); + }; + + const openModal = (product) => { + $title = $modal.querySelector(".modal-title"); + $body = $modal.querySelector(".modal-body"); + $buttons = $modal.querySelector(".modal .buttons"); + $close = $modal.querySelector(".modal .buttons .button.is-close"); + + $title.replaceChildren(); + buildHeading($title, product); + + $body.replaceChildren(); + $body.className = `modal-body block shop-product shop-product-${getId(product.id)}`; + + buildDescription($body, product, false); + buildOptions($body, product); + + $buttons.replaceChildren(); + buildAddButton($buttons, product); + + $modal.classList.add("is-active"); + + update(); + }; + + const buildHeading = (el, product) => { + const { priceRange, title } = product; + const { minVariantPrice: min } = priceRange; + + const $heading = El("shop-product-heading"); + + const $h3 = El("shop-product-title", "h3"); + $h3.innerText = title; + $heading.appendChild($h3); + + const $price = El("shop-product-price"); + $price.appendChild(Price(min)); + $heading.appendChild($price); + + el.appendChild($heading); + }; + + const buildSizeGuide = (desc) => { + if (!desc) { + return; + } + + const parts = desc.split('

'); + const first = parts[0]; + + if (parts.length === 1) { + return first; + } + + const items = parts[1].split("Size guide

"); + + if (parts.length === 1) { + return items[0]; + } + + return ` + ${first} +
+ Size Guide + ${items[1]} +
+ `; + }; + + const buildDescription = (el, product) => { + const { descriptionHtml } = product; + + const $description = El("shop-product-description"); + const $tagline = El("shop-product-tagline"); + const $rest = El("shop-product-rest content"); + + const { first, rest } = truncateDescription(descriptionHtml); + + $tagline.innerHTML = first; + $rest.innerHTML = buildSizeGuide(rest); + + $description.appendChild($tagline); + $description.appendChild($rest); + el.appendChild($description); + }; + + const buildOptions = (el, product) => { + const { variants } = product; + + const $options = El("buttons has-addons are-small variants"); + $options.className += variants.length > 1 ? " multiple" : " single"; + + if (variants.length > 1) { + variants.forEach((variant) => { + const { id, title } = variant; + + const $option = El("button", "button"); + $option.dataset.id = id; + $option.innerText = title; + + $option.addEventListener("click", (event) => { + event.preventDefault(); + product.selectedVariant = id; + update(); + }); + + $options.appendChild($option); + }); + + el.appendChild($options); + } + }; + + const buildAddButton = (el, product) => { + const $buy = El("button is-primary is-medium", "button"); + $buy.innerText = "Add to cart"; + + $buy.addEventListener("click", async (event) => { + event.preventDefault(); + await addToCart(product.selectedVariant); + closeModal(); + }); + + el.appendChild($buy); + }; + + // Update Cycle + const updateProducts = () => { + if (state.hasFetchedProducts) { + $products.classList.add("has-loaded"); + } + + if ($products.childElementCount > 4) { + return; + } + + state.products.forEach((product) => { + const { id, availableForSale, featuredImage } = product; + + if (!availableForSale) { + return; + } + + const el = El(`shop-product shop-product-${getId(product.id)}`); + el.dataset.id = id; + + const $figure = El("shop-product-image image is-square", "figure"); + const $img = document.createElement("img"); + $img.src = featuredImage.url; + $figure.appendChild($img); + el.appendChild($figure); + + $figure.addEventListener("click", async (event) => { + event.preventDefault(); + openModal(product); + }); + + buildHeading(el, product); + buildDescription(el, product); + buildOptions(el, product); + + const $buttons = El("shop-product-buttons buttons"); + + buildAddButton($buttons, product); + + const $more = El("button is-text", "button"); + $more.innerText = "Learn more"; + $buttons.appendChild($more); + + $more.addEventListener("click", async (event) => { + event.preventDefault(); + openModal(product); + }); + + el.appendChild($buttons); + + $products.appendChild(el); + }); + }; + + const updateCart = () => { + if (isEmpty(state.cart)) { + return; + } + + const { checkoutUrl, cost, lines } = state.cart; + + if (lines.length > 0) { + $openCart.classList.add("is-primary"); + $cartItems.replaceChildren(); + + $emptyCart.style.display = "none"; + $fullCart.style.display = "block"; + + lines.forEach((line) => { + const variantId = line.merchandise.id; + const { product, variant } = getProductFromVariant(variantId); + + const $item = El("media shop-item"); + $item.dataset.id = line.id; + + const $left = El("media-left"); + const $image = El("shop-item-image image is-64x64"); + const $img = El("", "img"); + + if (product.featuredImage) { + $img.src = product.featuredImage.url; + } + + $image.appendChild($img); + $left.appendChild($image); + + const $right = El("media-content"); + + const $cost = El("shop-item-price"); + $cost.innerText = formatPrice(line.cost.totalAmount); + $right.appendChild($cost); + + const $title = El("shop-item-title"); + $title.innerText = `${product.title}`; + $right.appendChild($title); + + if (variant.title !== "Default Title") { + const $tag = El( + "shop-item-variant button is-primary is-small is-outlined", + "span", + ); + $tag.innerText = `${variant.title}`; + $right.appendChild($tag); + } + + const $quantity = El("shop-item-quantity button is-static", "span"); + $quantity.innerText = `${line.quantity}`; + $right.appendChild($quantity); + + const $buttons = El("shop-item-actions"); + + const $remove = El("button shop-item-remove is-small", "button"); + const $icon = Icon("fa-solid fa-trash-can"); + $remove.appendChild($icon); + $remove.addEventListener("click", async (event) => { + event.preventDefault(); + + if ( + window.confirm( + `Are you sure you want to remove this item from your cart?`, + ) + ) { + await removeFromCart(line.id); + } + }); + $buttons.appendChild($remove); + + const $addons = El("shop-item-buttons buttons are-small has-addons"); + + const $plus = El("button", "button"); + const $plusIcon = Icon("fa-solid fa-plus"); + $plus.appendChild($plusIcon); + $plus.addEventListener("click", async (event) => { + event.preventDefault(); + await addToCart(variant.id); + }); + + const $minus = El("button", "button"); + const $minusIcon = Icon("fa-solid fa-minus"); + $minus.appendChild($minusIcon); + $minus.addEventListener("click", async (event) => { + event.preventDefault(); + + if (line.quantity === 1) { + if ( + window.confirm( + `Are you sure you want to remove this item from your cart?`, + ) + ) { + await removeFromCart(line.id); + } + } else { + await decreaseFromCart(line.id, line.quantity - 1); + } + }); + + $addons.appendChild($minus); + $addons.appendChild($quantity); + $addons.appendChild($plus); + + $buttons.appendChild($addons); + $right.appendChild($buttons); + + $item.appendChild($left); + $item.appendChild($right); + + $cartItems.appendChild($item); + }); + + const $total = El("shop-total"); + const $totalLeft = El("shop-total-left"); + const $totalLabel = El("shop-total-label"); + $totalLabel.innerText = "Total"; + const $disclaimer = El("shop-total-disclaimer"); + $disclaimer.innerText = + "Tax included and shipping and discounts calculated at checkout"; + const $totalRight = El("shop-total-amount"); + $totalRight.innerText = formatPrice(cost.totalAmount); + $totalLeft.appendChild($totalLabel); + $totalLeft.appendChild($disclaimer); + $total.appendChild($totalLeft); + $total.appendChild($totalRight); + $cartItems.appendChild($total); + + const $checkout = El("button is-primary is-fullwidth", "a"); + $checkout.innerText = "Checkout"; + $checkout.href = checkoutUrl; + $cartItems.appendChild($checkout); + } else { + $openCart.classList.remove("is-primary"); + $emptyCart.style.display = "block"; + $fullCart.style.display = "none"; + } + }; + + const updateButtons = () => { + const $buttons = document.querySelectorAll( + "#open-cart, #shop button.button, #shop-modal button.button", + ); + + $buttons.forEach((button) => { + if (state.isLoading) { + button.setAttribute("disabled", ""); + } else { + button.removeAttribute("disabled"); + } + }); + }; + + const updateVariants = () => { + state.products.forEach((product) => { + const $blocs = document.querySelectorAll( + `.shop-product-${getId(product.id)}`, + ); + + $blocs.forEach(($bloc) => { + const $variants = $bloc.querySelectorAll(`.variants .button`); + + $variants.forEach(($el) => { + if ($el.dataset.id === product.selectedVariant) { + $el.classList.add("is-primary"); + } else { + $el.classList.remove("is-primary"); + } + }); + }); + }); + }; + + const update = () => { + updateProducts(); + updateCart(); + updateButtons(); + updateVariants(); + }; + + // HTML Elements + const El = (className = "", tag = "div") => { + const el = document.createElement(tag); + el.className = className; + return el; + }; + + const Icon = (icon) => { + const el = document.createElement("span"); + el.className = "icon"; + const i = document.createElement("i"); + i.className = icon; + el.appendChild(i); + return el; + }; + + const Price = (price) => { + const { amount, currencyCode } = price; + const el = El("shop-price", "span"); + el.innerText = `${CURRENCIES[currencyCode]}${Math.trunc(amount)}`; + return el; + }; + + const truncateDescription = (desc) => { + const parts = desc.split("
\n
\n"); + + return { + first: parts[0], + rest: parts.slice(1).join(" "), + }; + }; + + // API calls + const client = window.ShopifyStorefrontAPIClient.createStorefrontApiClient({ + storeDomain: "8df2f8-d5.myshopify.com", + apiVersion: "2024-04", + publicAccessToken: "e3764a4be9897a2d0531c4b5c2699c9f", + }); + + async function retrieveProducts() { + let context = ""; + + if (state.countryCode) { + context = `@inContext(country: ${state.countryCode})`; + } + + const query = ` + query allProducts ${context} { + products(first: 10) { + edges { + node { + id + availableForSale + description + descriptionHtml + featuredImage { + height + url + width + } + handle + images(first: 10) { + edges { + node { + height + url + width + } + } + } + priceRange { + maxVariantPrice { + amount + currencyCode + } + minVariantPrice { + amount + currencyCode + } + } + title + variants(first: 10) { + edges { + node { + availableForSale + id + price { + amount + currencyCode + } + title + } + } + } + } + } + } + } + `; + + state.isLoading = true; + update(); + + try { + const { data, errors } = await client.request(query); + + if (errors) { + return console.error(errors); + } + + const clean = humanizeGraphQLResponse(data); + state.products = clean.products.map((product) => { + return { + ...product, + selectedVariant: product.variants[0].id, + }; + }); + state.hasFetchedProducts = true; + } catch (error) { + console.error("Error fetching products:", error); + } + + state.isLoading = false; + update(); + } + + async function createCart() { + const query = ` + mutation cartCreate { + cartCreate ( + input: {} + ) { + cart { + ${CART_QL} + ${COST_QL} + } + } + } + `; + + state.isLoading = true; + update(); + + try { + const { data, errors } = await client.request(query); + + if (errors) { + return console.error(errors); + } + + const clean = humanizeGraphQLResponse(data); + state.cart = clean.cartCreate.cart; + + localStorage.setItem(STORAGE_CART_ID, clean.cartCreate.cart.id); + } catch (error) { + console.error("Error fetching products:", error); + } + + state.isLoading = false; + update(); + } + + async function retrieveCart(cartId) { + const query = ` + { + cart ( + id: "${cartId}" + ) { + ${CART_QL} + ${COST_QL} + } + } + `; + + state.isLoading = true; + update(); + + try { + const { data, errors } = await client.request(query); + + if (errors) { + createCart(); + return console.error(errors); + } + + const clean = humanizeGraphQLResponse(data); + state.cart = clean.cart; + + if (clean.cart.buyerIdentity.countryCode) { + state.countryCode = clean.cart.buyerIdentity.countryCode; + } + } catch (error) { + console.error("Error fetching products:", error); + createCart(); + } + + state.isLoading = false; + update(); + } + + async function addToCart(productId) { + const query = ` + mutation cartAdd { + cartLinesAdd ( + cartId: "${state.cart.id}" + lines: { + merchandiseId: "${productId}" + quantity: 1 + } + ) { + cart { + ${CART_QL} + ${COST_QL} + } + } + } + `; + + state.isLoading = true; + update(); + + try { + const { data, errors } = await client.request(query); + + if (errors) { + return console.error(errors); + } + + const clean = humanizeGraphQLResponse(data); + state.cart = clean.cartLinesAdd.cart; + + $cart.classList.add("is-active"); + } catch (error) { + console.error("Error fetching products:", error); + } + + state.isLoading = false; + update(); + } + + async function removeFromCart(lineId) { + const query = ` + mutation cartAdd { + cartLinesRemove ( + cartId: "${state.cart.id}" + lineIds: ["${lineId}"] + ) { + cart { + ${CART_QL} + ${COST_QL} + } + } + } + `; + + state.isLoading = true; + update(); + + try { + const { data, errors } = await client.request(query); + + if (errors) { + return console.error(errors); + } + + const clean = humanizeGraphQLResponse(data); + state.cart = clean.cartLinesRemove.cart; + } catch (error) { + console.error("Error fetching products:", error); + } + + state.isLoading = false; + update(); + } + + async function decreaseFromCart(lineId, quantity) { + const query = ` + mutation cartAdd { + cartLinesUpdate ( + cartId: "${state.cart.id}" + lines: { + id: "${lineId}" + quantity: ${quantity} + } + ) { + cart { + ${CART_QL} + ${COST_QL} + } + } + } + `; + + state.isLoading = true; + update(); + + try { + const { data, errors } = await client.request(query); + + if (errors) { + return console.error(errors); + } + + const clean = humanizeGraphQLResponse(data); + state.cart = clean.cartLinesUpdate.cart; + } catch (error) { + console.error("Error fetching products:", error); + } + + state.isLoading = false; + update(); + } + + // Init + const init = async () => { + const storedCart = localStorage.getItem(STORAGE_CART_ID); + + if (storedCart) { + await retrieveCart(storedCart); + } else { + await createCart(); + } + + await retrieveProducts(); + }; + + init(); +}); diff --git a/docs/shop.html b/docs/shop.html new file mode 100644 index 00000000..fedce3d0 --- /dev/null +++ b/docs/shop.html @@ -0,0 +1,396 @@ +--- +title: "The Bulma Shop" +layout: default +theme: primary +route: shop +hide_footer: true +breadcrumb: + - home + - shop +--- + + + +{% include global/header.html %} + +{% capture shop_placeholder %} +
+
+ +
+ +
+

The Bulma T-Shirt

+
+ $15 +
+
+
+
+ Show your CSS skills and add a little extra motivation with the + official Bulma sticker. A perfect reminder that design can be easy + with your favorite framework. +
+
+
+ +
+
+{% endcapture %} + +
+
+
+

+ The Bulma Shop +

+ +
+ +

+ Get yourself some Bulma swag. +

+ + +
+
+ +
+
+
+ +
+

Cart

+ +
+
+

Your cart is empty!

+ +
+
+ +
+
+
+
+
+ +
+ {{ shop_placeholder }} + {{ shop_placeholder }} + {{ shop_placeholder }} + {{ shop_placeholder }} +
+
+
+ + + + + From 83fcfdcf5ece12dbfd2d578f99d1b3a4e54b3609 Mon Sep 17 00:00:00 2001 From: Jeremy Thomas Date: Tue, 11 Jun 2024 01:20:25 +0100 Subject: [PATCH 2/7] Use cards for shop --- docs/_data/links.json | 9 +- docs/_includes/global/nav-item.html | 3 + docs/_sass/global/nav.scss | 3 +- docs/assets/css/website.css | 9 +- docs/assets/javascript/shop.js | 49 +++++++---- docs/shop.html | 122 +++++++++++++++++++--------- docs/website.scss | 1 + 7 files changed, 136 insertions(+), 60 deletions(-) diff --git a/docs/_data/links.json b/docs/_data/links.json index 16180833..fa7cc276 100644 --- a/docs/_data/links.json +++ b/docs/_data/links.json @@ -64,6 +64,13 @@ "icon": "fa-brands fa-patreon", "path": "/become-a-bulma-sponsor/" }, + "shop": { + "name": "Shop", + "subtitle": "The Official Bulma Shop", + "color": "success", + "icon": "fa-solid fa-store", + "path": "/shop/" + }, "brand": { "name": "Bulma Brand", "subtitle": "The official Bulma logos", @@ -750,7 +757,7 @@ "path": "/documentation/helpers/other-helpers/" } }, - "navbar": ["docs", "expo", "love", "sponsor"], + "navbar": ["docs", "expo", "love", "sponsor", "shop"], "navbar_icons": ["github", "twitter"], "navbar_more": ["made-with-bulma", "backers", "brand", "extensions"], "category_ids": [ diff --git a/docs/_includes/global/nav-item.html b/docs/_includes/global/nav-item.html index eceff97c..3c708a87 100644 --- a/docs/_includes/global/nav-item.html +++ b/docs/_includes/global/nav-item.html @@ -36,6 +36,9 @@ {% else %} {{ link.name }} {% endif %} + {% if include.link_id == "shop" %} + New! + {% endif %} {% endunless %} diff --git a/docs/_sass/global/nav.scss b/docs/_sass/global/nav.scss index d6932fbe..2f3f946e 100644 --- a/docs/_sass/global/nav.scss +++ b/docs/_sass/global/nav.scss @@ -54,7 +54,8 @@ } &.is-system, - &.is-docs { + &.is-docs, + &.is-shop { --h: #{cv.getVar("success-h")}; --s: #{cv.getVar("success-s")}; --l: #{cv.getVar("success-l")}; diff --git a/docs/assets/css/website.css b/docs/assets/css/website.css index 26e1515b..116d0392 100644 --- a/docs/assets/css/website.css +++ b/docs/assets/css/website.css @@ -34259,7 +34259,7 @@ has-background-moon.is-hoverable:active { --s: var(--bulma-info-s); --l: var(--bulma-info-l); } -.bd-nav-item.is-system, .bd-nav-item.is-docs { +.bd-nav-item.is-system, .bd-nav-item.is-docs, .bd-nav-item.is-shop { --h: var(--bulma-success-h); --s: var(--bulma-success-s); --l: var(--bulma-success-l); @@ -38852,6 +38852,13 @@ has-background-moon.is-hoverable:active { --theme-color: hsl(var(--theme-h), var(--theme-s), var(--theme-l)); } +.bd-theme-shop { + --theme-h: var(--bulma-success-h); + --theme-s: var(--bulma-success-s); + --theme-l: var(--bulma-success-l); + --theme-color: hsl(var(--theme-h), var(--theme-s), var(--theme-l)); +} + .bd-theme-features { --theme-h: var(--bulma-danger-h); --theme-s: var(--bulma-danger-s); diff --git a/docs/assets/javascript/shop.js b/docs/assets/javascript/shop.js index 91aa6a85..4ffce379 100644 --- a/docs/assets/javascript/shop.js +++ b/docs/assets/javascript/shop.js @@ -58,6 +58,7 @@ document.addEventListener("DOMContentLoaded", () => { }; }; + const THEME_COLOR = "success"; const STORAGE_CART_ID = "bulma-shop-cart-id"; const CURRENCIES = { @@ -264,7 +265,7 @@ document.addEventListener("DOMContentLoaded", () => { currencyCode } } - lines(first: 20) { + lines(first: 24) { edges { node { id @@ -326,6 +327,7 @@ document.addEventListener("DOMContentLoaded", () => { const $cart = document.getElementById("cart"); const $cartClose = document.querySelectorAll(".shop-cart-close"); const $openCart = document.getElementById("open-cart"); + const $cartCount = document.getElementById("cart-count"); const $emptyCart = document.getElementById("empty-cart"); const $fullCart = document.getElementById("full-cart"); const $cartItems = document.getElementById("cart-items"); @@ -450,7 +452,9 @@ document.addEventListener("DOMContentLoaded", () => { const buildOptions = (el, product) => { const { variants } = product; - const $options = El("buttons has-addons are-small variants"); + const $options = El( + "shop-product-variants buttons has-addons are-small variants", + ); $options.className += variants.length > 1 ? " multiple" : " single"; if (variants.length > 1) { @@ -475,7 +479,7 @@ document.addEventListener("DOMContentLoaded", () => { }; const buildAddButton = (el, product) => { - const $buy = El("button is-primary is-medium", "button"); + const $buy = El(`button is-${THEME_COLOR}`, "button"); $buy.innerText = "Add to cart"; $buy.addEventListener("click", async (event) => { @@ -493,7 +497,7 @@ document.addEventListener("DOMContentLoaded", () => { $products.classList.add("has-loaded"); } - if ($products.childElementCount > 4) { + if ($products.childElementCount > Number($products.dataset.count)) { return; } @@ -504,14 +508,16 @@ document.addEventListener("DOMContentLoaded", () => { return; } - const el = El(`shop-product shop-product-${getId(product.id)}`); - el.dataset.id = id; + const $card = El(`card shop-product shop-product-${getId(product.id)}`); + $card.dataset.id = id; + + const el = El("card-content"); const $figure = El("shop-product-image image is-square", "figure"); const $img = document.createElement("img"); $img.src = featuredImage.url; $figure.appendChild($img); - el.appendChild($figure); + $card.appendChild($figure); $figure.addEventListener("click", async (event) => { event.preventDefault(); @@ -536,8 +542,8 @@ document.addEventListener("DOMContentLoaded", () => { }); el.appendChild($buttons); - - $products.appendChild(el); + $card.appendChild(el); + $products.appendChild($card); }); }; @@ -549,11 +555,14 @@ document.addEventListener("DOMContentLoaded", () => { const { checkoutUrl, cost, lines } = state.cart; if (lines.length > 0) { - $openCart.classList.add("is-primary"); + $openCart.classList.add(`is-${THEME_COLOR}`); $cartItems.replaceChildren(); $emptyCart.style.display = "none"; $fullCart.style.display = "block"; + $cartCount.style.display = "inline-flex"; + + let totalCount = 0; lines.forEach((line) => { const variantId = line.merchandise.id; @@ -585,7 +594,7 @@ document.addEventListener("DOMContentLoaded", () => { if (variant.title !== "Default Title") { const $tag = El( - "shop-item-variant button is-primary is-small is-outlined", + `shop-item-variant button is-${THEME_COLOR} is-small is-outlined`, "span", ); $tag.innerText = `${variant.title}`; @@ -595,6 +604,7 @@ document.addEventListener("DOMContentLoaded", () => { const $quantity = El("shop-item-quantity button is-static", "span"); $quantity.innerText = `${line.quantity}`; $right.appendChild($quantity); + totalCount += line.quantity; const $buttons = El("shop-item-actions"); @@ -671,14 +681,17 @@ document.addEventListener("DOMContentLoaded", () => { $total.appendChild($totalRight); $cartItems.appendChild($total); - const $checkout = El("button is-primary is-fullwidth", "a"); + const $checkout = El(`button is-${THEME_COLOR} is-fullwidth`, "a"); $checkout.innerText = "Checkout"; $checkout.href = checkoutUrl; $cartItems.appendChild($checkout); + + $cartCount.innerText = totalCount; } else { - $openCart.classList.remove("is-primary"); + $openCart.classList.remove(`is-${THEME_COLOR}`); $emptyCart.style.display = "block"; $fullCart.style.display = "none"; + $cartCount.style.display = "none"; } }; @@ -707,9 +720,9 @@ document.addEventListener("DOMContentLoaded", () => { $variants.forEach(($el) => { if ($el.dataset.id === product.selectedVariant) { - $el.classList.add("is-primary"); + $el.classList.add(`is-${THEME_COLOR}`); } else { - $el.classList.remove("is-primary"); + $el.classList.remove(`is-${THEME_COLOR}`); } }); }); @@ -771,7 +784,7 @@ document.addEventListener("DOMContentLoaded", () => { const query = ` query allProducts ${context} { - products(first: 10) { + products(first: 12) { edges { node { id @@ -784,7 +797,7 @@ document.addEventListener("DOMContentLoaded", () => { width } handle - images(first: 10) { + images(first: 6) { edges { node { height @@ -804,7 +817,7 @@ document.addEventListener("DOMContentLoaded", () => { } } title - variants(first: 10) { + variants(first: 12) { edges { node { availableForSale diff --git a/docs/shop.html b/docs/shop.html index fedce3d0..c44dfaef 100644 --- a/docs/shop.html +++ b/docs/shop.html @@ -1,7 +1,7 @@ --- -title: "The Bulma Shop" +title: "The Official Bulma Shop" layout: default -theme: primary +theme: shop route: shop hide_footer: true breadcrumb: @@ -14,36 +14,47 @@ breadcrumb: --shop-duration: 500ms; } - .shop-open-cart { - margin-top: 1.5rem; + .shop-hero { + min-height: calc(100vh - 6.5rem); } - @media screen and (min-width: 800px) { + .shop-hero .bd-hero { + position: relative; + } + + .shop-hero .bd-hero-body { + position: relative; + margin: 0 auto; + max-width: 110rem; + width: 100%; + } + + .shop-open-cart { + margin-top: 1.5rem; + min-width: 10em; + } + + .shop-open-cart .tag { + margin: 0 -0.625em 0 0.625em; + padding: 0 0.5em; + min-width: 2em; + background-color: transparent; + color: currentColor; + border: 1px solid currentColor; + } + + @media screen and (min-width: 1000px) { .shop-open-cart { margin-top: 0; - position: absolute; - right: 3rem; - top: calc(50% - 1rem); + position: fixed; + bottom: 1.5rem; + right: 1.5rem; + z-index: 30; } } - .shop-product-heading { - align-items: center; - gap: 1em; - justify-content: space-between; - display: flex; - font-size: 1.25em; - margin-bottom: 0.25em; - } - - .shop-product-title { - color: var(--bulma-text-strong); - font-weight: 700; - } - - .shop-product-price { - color: var(--bulma-text-strong); - font-size: 0.875em; + .shop-cart-count { + background-color: var(--bulma-primary-50); } .shop-cart, @@ -59,7 +70,7 @@ breadcrumb: .shop-cart { opacity: 0; position: fixed; - z-index: 10; + z-index: 40; pointer-events: none; } @@ -77,7 +88,7 @@ breadcrumb: max-width: 22rem; padding: 2rem; position: absolute; - z-index: 20; + z-index: 50; transform: translateX(100%); transition-duration: var(--shop-duration); transition-property: transform; @@ -167,6 +178,8 @@ breadcrumb: display: grid; grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr)); gap: 3rem; + max-width: 110rem; + margin: 0 auto; } .shop-products.has-loaded .shop-product.is-placeholder { @@ -176,9 +189,39 @@ breadcrumb: .shop-product { display: flex; flex-direction: column; + margin-bottom: 0 !important; + overflow: hidden; } - .shop-product .variants { + .shop-product .card-content { + display: flex; + flex-direction: column; + flex-grow: 1; + } + + .shop-product-heading { + align-items: center; + gap: 1em; + justify-content: space-between; + display: flex; + font-size: 1.25em; + margin-top: 0.25em; + margin-bottom: 0.5em; + } + + .shop-product-title { + color: var(--bulma-text-strong); + font-weight: 700; + line-height: 1.25; + } + + .shop-product-price { + color: var(--bulma-text-strong); + font-size: 0.875em; + } + + .shop-product-variants { + margin-bottom: 0 !important; margin-top: 1em; } @@ -189,9 +232,7 @@ breadcrumb: } .shop-product-image { - border-radius: 0.5rem; cursor: pointer; - margin-bottom: 0.5em; overflow: hidden; } @@ -240,6 +281,7 @@ breadcrumb: .shop-product-buttons { justify-content: space-between; + margin-top: 1em; } .shop-modal { @@ -262,6 +304,7 @@ breadcrumb: .shop-modal-content .shop-product-tagline { color: var(--bulma-text-strong); + margin-bottom: 1em; } .shop-modal-content .shop-product-rest { @@ -270,6 +313,7 @@ breadcrumb: .shop-modal-buttons { justify-content: space-between; + margin-top: 1em; } .shop-modal-close { @@ -322,11 +366,11 @@ breadcrumb: {% endcapture %} -
-
+
+

- The Bulma Shop + The Official Bulma Shop


@@ -337,9 +381,10 @@ breadcrumb:
@@ -366,11 +411,10 @@ breadcrumb:
-
- {{ shop_placeholder }} - {{ shop_placeholder }} - {{ shop_placeholder }} - {{ shop_placeholder }} +
+ {% for i in (1..8) %} + {{ shop_placeholder }} + {% endfor %}
diff --git a/docs/website.scss b/docs/website.scss index be8f0418..e14fe314 100644 --- a/docs/website.scss +++ b/docs/website.scss @@ -231,6 +231,7 @@ $themes: ( "customize": "info", "docs": "success", "expo": "warning", + "shop": "success", "features": "danger", "helpers": "link", "html": "html", From 1a6164b836a5e3de50d4f7af45d7a81f305bdeb1 Mon Sep 17 00:00:00 2001 From: Jeremy Thomas Date: Tue, 11 Jun 2024 01:30:26 +0100 Subject: [PATCH 3/7] Add shop select --- docs/assets/javascript/shop.js | 47 +++++++++++++++++++--------------- docs/shop.html | 4 +++ 2 files changed, 30 insertions(+), 21 deletions(-) diff --git a/docs/assets/javascript/shop.js b/docs/assets/javascript/shop.js index 4ffce379..71002ef7 100644 --- a/docs/assets/javascript/shop.js +++ b/docs/assets/javascript/shop.js @@ -452,29 +452,34 @@ document.addEventListener("DOMContentLoaded", () => { const buildOptions = (el, product) => { const { variants } = product; - const $options = El( - "shop-product-variants buttons has-addons are-small variants", - ); - $options.className += variants.length > 1 ? " multiple" : " single"; + const $select = El("shop-product-variants select is-success"); + const $options = El("shop-product-select", "select"); if (variants.length > 1) { variants.forEach((variant) => { const { id, title } = variant; - const $option = El("button", "button"); + const $option = El("", "option"); $option.dataset.id = id; $option.innerText = title; - $option.addEventListener("click", (event) => { - event.preventDefault(); - product.selectedVariant = id; - update(); - }); + // $option.addEventListener("click", (event) => { + // event.preventDefault(); + // product.selectedVariant = id; + // update(); + // }); $options.appendChild($option); }); - el.appendChild($options); + $select.addEventListener("change", (event) => { + event.preventDefault(); + product.selectedVariant = id; + update(); + }); + + $select.appendChild($options); + el.appendChild($select); } }; @@ -715,17 +720,17 @@ document.addEventListener("DOMContentLoaded", () => { `.shop-product-${getId(product.id)}`, ); - $blocs.forEach(($bloc) => { - const $variants = $bloc.querySelectorAll(`.variants .button`); + // $blocs.forEach(($bloc) => { + // const $variants = $bloc.querySelectorAll(`.variants .button`); - $variants.forEach(($el) => { - if ($el.dataset.id === product.selectedVariant) { - $el.classList.add(`is-${THEME_COLOR}`); - } else { - $el.classList.remove(`is-${THEME_COLOR}`); - } - }); - }); + // $variants.forEach(($el) => { + // if ($el.dataset.id === product.selectedVariant) { + // $el.classList.add(`is-${THEME_COLOR}`); + // } else { + // $el.classList.remove(`is-${THEME_COLOR}`); + // } + // }); + // }); }); }; diff --git a/docs/shop.html b/docs/shop.html index c44dfaef..67ccfe57 100644 --- a/docs/shop.html +++ b/docs/shop.html @@ -225,6 +225,10 @@ breadcrumb: margin-top: 1em; } + .shop-product-select { + width: 100%; + } + .shop-product-description { display: flex; flex-grow: 1; From 6cebea9ce20d2a3fda38008eb4eca96d1121b707 Mon Sep 17 00:00:00 2001 From: Jeremy Thomas Date: Tue, 11 Jun 2024 02:39:35 +0100 Subject: [PATCH 4/7] Add carousel --- docs/assets/javascript/shop.js | 135 +++++++++++++++++++++++++-------- docs/shop.html | 87 +++++++++++++++++---- 2 files changed, 174 insertions(+), 48 deletions(-) diff --git a/docs/assets/javascript/shop.js b/docs/assets/javascript/shop.js index 71002ef7..bcd08fd2 100644 --- a/docs/assets/javascript/shop.js +++ b/docs/assets/javascript/shop.js @@ -58,6 +58,22 @@ document.addEventListener("DOMContentLoaded", () => { }; }; + const getPreviousIndex = (list, current) => { + if (current === 0) { + return list.length - 1; + } + + return current - 1; + } + + const getNextIndex = (list, current) => { + if (current === list.length - 1) { + return 0; + } + + return current + 1; + } + const THEME_COLOR = "success"; const STORAGE_CART_ID = "bulma-shop-cart-id"; @@ -366,10 +382,9 @@ document.addEventListener("DOMContentLoaded", () => { }; const openModal = (product) => { - $title = $modal.querySelector(".modal-title"); - $body = $modal.querySelector(".modal-body"); - $buttons = $modal.querySelector(".modal .buttons"); - $close = $modal.querySelector(".modal .buttons .button.is-close"); + const $title = $modal.querySelector(".modal-title"); + const $body = $modal.querySelector(".modal-body"); + const $buttons = $modal.querySelector(".modal .buttons"); $title.replaceChildren(); buildHeading($title, product); @@ -462,19 +477,14 @@ document.addEventListener("DOMContentLoaded", () => { const $option = El("", "option"); $option.dataset.id = id; $option.innerText = title; - - // $option.addEventListener("click", (event) => { - // event.preventDefault(); - // product.selectedVariant = id; - // update(); - // }); + $option.value = id; $options.appendChild($option); }); $select.addEventListener("change", (event) => { event.preventDefault(); - product.selectedVariant = id; + product.selectedVariant = event.target.value; update(); }); @@ -507,7 +517,7 @@ document.addEventListener("DOMContentLoaded", () => { } state.products.forEach((product) => { - const { id, availableForSale, featuredImage } = product; + const { id, availableForSale, images } = product; if (!availableForSale) { return; @@ -518,16 +528,51 @@ document.addEventListener("DOMContentLoaded", () => { const el = El("card-content"); - const $figure = El("shop-product-image image is-square", "figure"); - const $img = document.createElement("img"); - $img.src = featuredImage.url; - $figure.appendChild($img); - $card.appendChild($figure); + const $images = El("shop-product-images image is-square"); + const $carousel = El("shop-product-carousel image is-square"); - $figure.addEventListener("click", async (event) => { - event.preventDefault(); - openModal(product); - }); + if (images) { + images.forEach(img => { + const $figure = El("shop-product-image image is-square", "figure"); + const $img = document.createElement("img"); + $img.src = img.url; + $figure.appendChild($img); + $carousel.appendChild($figure); + }); + + $images.appendChild($carousel); + + if (images.length > 1) { + const $prev = El("shop-product-arrow shop-product-prev", "button"); + const $prevArrow = El("icon", "button"); + const $prevIcon = El("fa-solid fa-arrow-left", "i"); + $prevArrow.appendChild($prevIcon); + $prev.appendChild($prevArrow); + + $prev.addEventListener("click", event => { + event.preventDefault(); + product.selectedImage = getPreviousIndex(images, product.selectedImage); + update(); + }); + + const $next = El("shop-product-arrow shop-product-next", "button"); + const $nextArrow = El("icon", "button"); + const $nextIcon = El("fa-solid fa-arrow-right", "i"); + $nextArrow.appendChild($nextIcon); + $next.appendChild($nextArrow); + + $next.addEventListener("click", event => { + event.preventDefault(); + product.selectedImage = getNextIndex(images, product.selectedImage); + update(); + }); + + $images.appendChild($prev); + $images.appendChild($next); + } + } + + $card.appendChild($images); buildHeading(el, product); buildDescription(el, product); @@ -552,6 +597,20 @@ document.addEventListener("DOMContentLoaded", () => { }); }; + const updateImages = () => { + state.products.forEach((product) => { + const $blocs = document.querySelectorAll( + `.shop-product-${getId(product.id)}`, + ); + + $blocs.forEach(($bloc) => { + const $carousel = $bloc.querySelector(`.shop-product-carousel`); + const offset = product.selectedImage * 100; + $carousel.style.transform = `translateX(-${offset}%`; + }); + }); + } + const updateCart = () => { if (isEmpty(state.cart)) { return; @@ -577,13 +636,23 @@ document.addEventListener("DOMContentLoaded", () => { $item.dataset.id = line.id; const $left = El("media-left"); + // const $images = El("shop-item-images"); const $image = El("shop-item-image image is-64x64"); const $img = El("", "img"); - if (product.featuredImage) { $img.src = product.featuredImage.url; } + // if (product.images) { + // product.images.forEach(img => { + // const $image = El("shop-item-image image is-64x64"); + // const $img = El("", "img"); + // $img.src = img.url; + // $image.appendChild($img); + // $images.appendChild($image); + // }); + // } + $image.appendChild($img); $left.appendChild($image); @@ -720,17 +789,17 @@ document.addEventListener("DOMContentLoaded", () => { `.shop-product-${getId(product.id)}`, ); - // $blocs.forEach(($bloc) => { - // const $variants = $bloc.querySelectorAll(`.variants .button`); + $blocs.forEach(($bloc) => { + const $variants = $bloc.querySelectorAll(`.shop-product-select option`); - // $variants.forEach(($el) => { - // if ($el.dataset.id === product.selectedVariant) { - // $el.classList.add(`is-${THEME_COLOR}`); - // } else { - // $el.classList.remove(`is-${THEME_COLOR}`); - // } - // }); - // }); + $variants.forEach(($el) => { + if ($el.dataset.id === product.selectedVariant) { + $el.setAttribute("selected", ""); + } else { + $el.removeAttribute("selected"); + } + }); + }); }); }; @@ -739,6 +808,7 @@ document.addEventListener("DOMContentLoaded", () => { updateCart(); updateButtons(); updateVariants(); + updateImages(); }; // HTML Elements @@ -855,6 +925,7 @@ document.addEventListener("DOMContentLoaded", () => { state.products = clean.products.map((product) => { return { ...product, + selectedImage: 0, selectedVariant: product.variants[0].id, }; }); diff --git a/docs/shop.html b/docs/shop.html index 67ccfe57..1bce695c 100644 --- a/docs/shop.html +++ b/docs/shop.html @@ -235,7 +235,58 @@ breadcrumb: flex-direction: column; } + .shop-product-images { + position: relative; + } + + .shop-product-images:hover .shop-product-arrow { + opacity: 1; + } + + .shop-product-arrow { + position: absolute; + opacity: 0; + top: 0; + bottom: 0; + display: flex; + align-items: center; + justify-content: center; + padding: 1em; + } + + .shop-product-arrow .icon { + background-color: rgb(0 0 0 / 10%); + border-radius: 9999px; + transition-duration: var(--shop-duration); + transition-property: background-color, color, opacity; + } + + .shop-product-arrow:hover .icon { + background-color: rgb(0 0 0 / 50%); + color: white; + } + + .shop-product-arrow:active .icon { + background-color: rgb(0 0 0 / 70%); + color: white; + } + + .shop-product-prev { + left: 0; + } + + .shop-product-next { + right: 0; + } + + .shop-product-carousel { + display: flex; + transition-duration: 300ms; + transition-property: transform; + } + .shop-product-image { + flex-shrink: 0; cursor: pointer; overflow: hidden; } @@ -246,7 +297,7 @@ breadcrumb: transform-origin: center; } - .shop-product-image:hover img { + .shop-product-images:hover img { transform: scale(1.1); } @@ -343,29 +394,33 @@ breadcrumb: {% include global/header.html %} {% capture shop_placeholder %} -
+
-
-

The Bulma T-Shirt

-
- $15 +
+
+

The Bulma T-Shirt

+
+ $15 +
-
-
-
- Show your CSS skills and add a little extra motivation with the - official Bulma sticker. A perfect reminder that design can be easy - with your favorite framework. + +
+
+ Show your CSS skills and add a little extra motivation with the + official Bulma sticker. A perfect reminder that design can be easy + with your favorite framework. +
+
+ +
+
-
-
-
{% endcapture %} From b84d16890f466ecaa031ba379120f576335ad196 Mon Sep 17 00:00:00 2001 From: Jeremy Thomas Date: Tue, 11 Jun 2024 03:03:31 +0100 Subject: [PATCH 5/7] Add photoswipe --- docs/assets/javascript/shop.js | 13 ++++++++++++- docs/shop.html | 9 +++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/docs/assets/javascript/shop.js b/docs/assets/javascript/shop.js index bcd08fd2..2c5f2082 100644 --- a/docs/assets/javascript/shop.js +++ b/docs/assets/javascript/shop.js @@ -533,11 +533,22 @@ document.addEventListener("DOMContentLoaded", () => { if (images) { images.forEach(img => { - const $figure = El("shop-product-image image is-square", "figure"); + const $figure = El("shop-product-image image is-square", "a"); + $figure.href = img.url; + $figure.dataset.pswpHeight = img.height; + $figure.dataset.pswpWidth = img.width; + $figure.target = "_blank"; const $img = document.createElement("img"); $img.src = img.url; $figure.appendChild($img); $carousel.appendChild($figure); + + const lightbox = new window.PhotoSwipeLightbox({ + gallery: '.shop-product-image', + pswpModule: window.PhotoSwipe + }); + + lightbox.init(); }); $images.appendChild($carousel); diff --git a/docs/shop.html b/docs/shop.html index 1bce695c..5babbbbc 100644 --- a/docs/shop.html +++ b/docs/shop.html @@ -9,6 +9,8 @@ breadcrumb: - shop --- + +