Last active
May 7, 2025 02:23
-
-
Save zacariec/57507c9fcf18f5226be650d915e7e6b4 to your computer and use it in GitHub Desktop.
Brand Switcher for Shopify
This file contains hidden or 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
<style> | |
#shopify-section-{{ section.id }} ul { | |
display: flex; | |
flex-direction: row; | |
list-style: none; | |
margin: unset; | |
padding: unset; | |
} | |
#shopify-section-{{ section.id }} brand-button a { | |
display: flex; | |
position: relative; | |
height: fit-content; | |
width: fit-content; | |
align-items: center; | |
padding: 20px 10px; | |
} | |
#shopify-section-{{ section.id }} brand-button a img { | |
transition: all 0.3s ease-in-out; | |
} | |
#shopify-section-{{ section.id }} brand-button a:visited { | |
color: unset; | |
} | |
#shopify-section-{{ section.id }} li:not(:last-child) brand-button a:after { | |
content: ""; | |
height: 25px; | |
right: 0; | |
border-left: 1px solid #e5e5e5; | |
position: absolute; | |
} | |
{% for block in section.blocks %} | |
#{{ block.id }} img { | |
filter: opacity(0.5) drop-shadow(0 0 0 {{ block.settings.color }}) saturate(1000%); | |
} | |
#{{ block.id }} [data-active] img { | |
filter: opacity(0.5) drop-shadow(0 0 0 {{ block.settings.active_color }}) saturate(1000%); | |
} | |
#{{ block.id }} a:hover img, | |
#{{ block.id }} a:focus img { | |
filter: opacity(0.5) drop-shadow(0 0 0 {{ block.settings.active_color }}) saturate(1000%); | |
} | |
{% endfor %} | |
</style> | |
{% if section.blocks.size != 0 %} | |
<nav> | |
<ul> | |
{% for block in section.blocks %} | |
{% case block.type %} | |
{% when 'brand' %} | |
{% unless block.settings.page == null %} | |
<li id="{{ block.id }}" {{ block.shopify_attributes }}> | |
<brand-button data-brand="{{ block.settings.brand }}"> | |
{% assign search_parameter = block.settings.brand | url_encode | prepend: '?context=' %} | |
{% assign url = block.settings.page.url | append: search_parameter %} | |
<a href="{{ url }}"> | |
{% if block.settings.logo != null %} | |
{{ block.settings.logo | image_url: height: 25 | image_tag: alt: block.settings.brand }} | |
{% else %} | |
{{ block.settings.brand }} | |
{% endif %} | |
</a> | |
</brand-button> | |
</li> | |
{% endunless %} | |
{% endcase %} | |
{% endfor %} | |
</ul> | |
</nav> | |
{% endif %} | |
<script type="application/javascript"> | |
/** | |
* BrandContext custom element that manages brand context across the application. | |
* This element wraps every HTMLElement that requires context of the brand from a JS perspective. | |
* It listens to "subscribe:store_context" events on the window to add subscribers to context changes. | |
* When context is changed, it triggers a "context_changed" event to all subscribers. | |
* | |
* Setting context on the brand-context element will trigger updates: element.context = "Brand"; | |
* @extends {HTMLElement} | |
*/ | |
class BrandContext extends HTMLElement { | |
/** | |
* Initialize the BrandContext element | |
*/ | |
constructor() { | |
super(); | |
/** @type {URL} The current page URL */ | |
this.url = new URL(window.location.href); | |
/** @type {string|null} The context parameter from the URL search params */ | |
this.searchParamContext = this.url.searchParams.get('context'); | |
/** @type {Array<HTMLElement>} Array of elements subscribed to context changes */ | |
this.subscribers = []; | |
} | |
/** | |
* Set the brand context value in session storage and notify subscribers | |
* @param {string} value - The brand context value to set | |
* @returns {string} The current context value from session storage | |
*/ | |
set context(value) { | |
window.sessionStorage.setItem('store_context', value); | |
if (window.sessionStorage.getItem('store_context') !== value) { | |
console.error('Error setting store context, ', value); | |
return window.sessionStorage.getItem('store_context'); | |
} | |
this.triggerContextChange(value); | |
return window.sessionStorage.getItem('store_context'); | |
} | |
/** | |
* Get the current brand context value from session storage | |
* @returns {string|null} The current context value | |
*/ | |
get context() { | |
return window.sessionStorage.getItem('store_context'); | |
} | |
/** | |
* Notify all subscribers of a context change | |
* @param {string} value - The new context value | |
*/ | |
triggerContextChange(value) { | |
for (const subscriber of this.subscribers) { | |
subscriber.dispatchEvent( | |
new CustomEvent('context_changed', { | |
detail: { | |
/** @type {BrandContext} The source of the context change */ | |
source: this, | |
/** @type {string} The new brand value */ | |
brand: value, | |
}, | |
}), | |
); | |
} | |
} | |
/** | |
* Add a subscriber to context changes | |
* @param {CustomEvent} event - The event containing the subscriber element | |
* @returns {void} | |
*/ | |
addSubscriber(event) { | |
if (!event?.detail) { | |
return console.error('No CustomEvent was passed.'); | |
} | |
if (event.detail instanceof HTMLElement === false) { | |
return console.error('No valid element was passed to the event.'); | |
} | |
if (!event.detail?.uuid) { | |
return console.error("Element doesn't have a generated UUID"); | |
} | |
/** @type {HTMLElement|undefined} */ | |
const isSubscribed = this.subscribers.find((element) => element.uuid === event.detail.uuid); | |
if (isSubscribed) { | |
return console.warn('Element already subscribed, not subscribing.'); | |
} | |
this.subscribers.push(event.detail); | |
} | |
/** | |
* Add event listeners for subscriber registration | |
*/ | |
addListeners() { | |
window.addEventListener('subscribe:store_context', this.addSubscriber.bind(this)); | |
} | |
/** | |
* Initialize the component - called from connectedCallback | |
*/ | |
initialize() { | |
this.addListeners(); | |
// Set context from URL parameter if available | |
if (this.searchParamContext) { | |
/** @type {string} The decoded context from the URL */ | |
const decodedContext = decodeURIComponent(this.searchParamContext); | |
window.sessionStorage.setItem('store_context', decodedContext); | |
return; | |
} | |
} | |
/** | |
* Lifecycle callback when element is added to the DOM | |
*/ | |
connectedCallback() { | |
this.initialize(); | |
} | |
} | |
window.customElements.get('brand-context') || window.customElements.define('brand-context', BrandContext); | |
/** | |
* BrandButton custom element that represents a selectable brand option | |
* @extends {HTMLElement} | |
*/ | |
class BrandButton extends HTMLElement { | |
/** | |
* Initialize the BrandButton element | |
*/ | |
constructor() { | |
super(); | |
/** @type {HTMLAnchorElement|null} The anchor element within this button */ | |
this.anchor = this.querySelector('a'); | |
/** @type {BrandContext|null} Reference to the brand context element */ | |
this.context = document.querySelector('brand-context'); | |
/** @type {string} The brand name associated with this button */ | |
this.brand = this.dataset.brand; | |
/** @type {string} Unique identifier for this element */ | |
this.uuid = window.crypto.randomUUID(); | |
} | |
/** | |
* Handle context change events | |
* @param {CustomEvent} event - The context changed event | |
*/ | |
update(event) { | |
// do whatever you would want here. | |
console.log(event); | |
} | |
/** | |
* Subscribe to the brand context | |
*/ | |
subscribeToContext() { | |
/** @type {number} Interval ID for checking if context is mounted */ | |
const checkInterval = setInterval(() => { | |
if (window.customElements.get('brand-context')) { | |
clearInterval(checkInterval); | |
window.dispatchEvent(new CustomEvent('subscribe:store_context', { detail: this })); | |
this.addEventListener('context_changed', this.update.bind(this)); | |
} | |
}, 100); | |
} | |
/** | |
* Lifecycle callback when element is added to the DOM | |
*/ | |
connectedCallback() { | |
this.subscribeToContext(); | |
// if context exists and value of context is equal to this brand, it's active. | |
if (this.context && this.context.context == this.brand) { | |
this.dataset.active = true; | |
} | |
} | |
} | |
window.customElements.get('brand-button') || window.customElements.define('brand-button', BrandButton); | |
</script> | |
{% schema %} | |
{ | |
"name": "Brand Switcher", | |
"tag": "section", | |
"settings": [ | |
{ | |
"type": "color", | |
"label": "Background", | |
"id": "background" | |
} | |
], | |
"blocks": [ | |
{ | |
"type": "brand", | |
"name": "Brand", | |
"limit": 5, | |
"settings": [ | |
{ | |
"type": "text", | |
"label": "Brand Name", | |
"id": "brand" | |
}, | |
{ | |
"type": "page", | |
"label": "Page", | |
"id": "page" | |
}, | |
{ | |
"type": "image_picker", | |
"label": "Logo", | |
"id": "logo" | |
}, | |
{ | |
"type": "color", | |
"label": "Non-Active color", | |
"id": "color" | |
}, | |
{ | |
"type": "color", | |
"label": "Active color", | |
"id": "active_color" | |
} | |
] | |
} | |
], | |
"presets": [ | |
{ | |
"name": "Brand Switcher" | |
} | |
] | |
} | |
{% endschema %} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment