Last active
August 13, 2023 22:00
-
-
Save thorpj/9f7164b1afa93d7e46f88895a5c7c5cc to your computer and use it in GitHub Desktop.
StimulusJS Controller for popups using tippy.js.
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
import ApplicationController from './application_controller'; | |
import tippy from 'tippy.js'; | |
import 'tippy.js/dist/tippy.css'; | |
// Inspired by https://chrislabarge.com/posts/stimulus-popup/ | |
// NOTE: ApplicationController must have `this.element[this.identifier] = this;` in the connect() method. | |
/* | |
// Manually set content and trigger | |
// 'data-popup-trigger': :manual is optional. When no content is given, the controller isn't going to show a popup on mouseenter or click | |
%div.some-popup{ data: { controller: :popup, popup: { target: :trigger }}} | |
JS> document.querySelector('.some-popup').popup.show('Hello there') | |
// You could also manually trigger the below examples, without changing the content | |
JS> document.querySelector('.some-popup').popup.show() | |
// Text content | |
%btn.btn.btn-primary{ data: { 'popup-target': :trigger, action: 'click->popup#show', content: 'Text content'}} Click me | |
// HTML content | |
%btn.avatar.btn.btn-primary{ data: { 'popup-target': :trigger, action: 'click->popup#showExistingContent' } } | |
Click me | |
.popup{ 'data-popup-target': :content } | |
%span Hello | |
*/ | |
export default class extends ApplicationController { | |
static targets = ['trigger', 'content']; | |
manualTrigger = 'manual'; | |
defaultTimeout = 8000; | |
defaultPopupTrigger = 'mouseenter focus'; | |
initialize() { | |
this.trigger = this.getTrigger(); | |
this.content = this.getContent(); | |
this.initPopup(); | |
if (this.hasContentTarget) { | |
this.contentTarget.style.display = 'none'; | |
} | |
} | |
showExistingContent(event) { | |
this.show(); | |
} | |
show(content = null, timeout = this.defaultTimeout) { | |
if (content) { | |
this.setContent(content); | |
} | |
this.popup.show(); | |
this.setTimeout(timeout); | |
} | |
hide() { | |
this.popup.hide(); | |
} | |
setContent(content) { | |
this.popup.setContent(content); | |
} | |
setTimeout(timeout) { | |
clearTimeout(this.timeout); // Clear timeout from previous show action | |
if (timeout !== 0) { | |
this.timeout = setTimeout(() => { | |
this.popup.hide(); | |
}, timeout); | |
} | |
} | |
initPopup() { | |
this.popup = tippy(this.trigger, { | |
content: this.content, | |
allowHTML: true, | |
trigger: this.getTriggerMethod(), | |
}); | |
} | |
getTriggerMethod() { | |
if (this.trigger.dataset.popupTrigger) { | |
// E.g. 'data-popup-trigger': 'click' | |
return this.trigger.dataset.popupTrigger; | |
} else if (!this.content) { | |
// No content given, setContent and manually trigger only | |
return this.manualTrigger; | |
} else { | |
// Use default trigger(s) | |
return this.defaultPopupTrigger; | |
} | |
} | |
getContent() { | |
if (this.hasContentTarget) { | |
return this.contentTarget.innerHTML; | |
} else if (this.trigger.dataset.content) { | |
return this.trigger.dataset.content; | |
} | |
} | |
getTrigger() { | |
if (this.hasTriggerTarget) { | |
return this.triggerTarget; | |
} else { | |
return this.element; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Can be passed text content through a data attribute, can have HTML content nested under it using the 'data-popup-target': :content, or manually triggered using JS