From c78df9e5f12386229b16824276d692ced9fe7995 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?R=C3=A2u=20Cao?=
Date: Tue, 27 Jun 2023 17:23:43 +0200
Subject: [PATCH] Add QR code icon, button, modal for invites
Using https://excid3.github.io/tailwindcss-stimulus-components/
---
.../stylesheets/application.tailwind.css | 1 +
.../stylesheets/components/animations.css | 16 +++++++++++++
app/components/modal_component.html.erb | 15 ++++++++++++
app/components/modal_component.rb | 2 ++
.../qr_code_modal_component.html.erb | 6 +++++
app/components/qr_code_modal_component.rb | 24 +++++++++++++++++++
app/javascript/controllers/application.js | 3 +++
app/views/icons/_qr_code.html.erb | 11 +++++++++
app/views/invitations/index.html.erb | 15 ++++++++----
config/importmap.rb | 1 +
tailwind.config.js | 3 ++-
.../tailwindcss-stimulus-components.js | 2 ++
12 files changed, 93 insertions(+), 6 deletions(-)
create mode 100644 app/assets/stylesheets/components/animations.css
create mode 100644 app/components/modal_component.html.erb
create mode 100644 app/components/modal_component.rb
create mode 100644 app/components/qr_code_modal_component.html.erb
create mode 100644 app/components/qr_code_modal_component.rb
create mode 100644 app/views/icons/_qr_code.html.erb
create mode 100644 vendor/javascript/tailwindcss-stimulus-components.js
diff --git a/app/assets/stylesheets/application.tailwind.css b/app/assets/stylesheets/application.tailwind.css
index acf8aa9..e679da8 100644
--- a/app/assets/stylesheets/application.tailwind.css
+++ b/app/assets/stylesheets/application.tailwind.css
@@ -2,6 +2,7 @@
@import "tailwindcss/components";
@import "tailwindcss/utilities";
+@import "components/animations";
@import "components/base";
@import "components/buttons";
@import "components/dashboard_services";
diff --git a/app/assets/stylesheets/components/animations.css b/app/assets/stylesheets/components/animations.css
new file mode 100644
index 0000000..23b7664
--- /dev/null
+++ b/app/assets/stylesheets/components/animations.css
@@ -0,0 +1,16 @@
+@keyframes scaleIn {
+ from {
+ transform: scale(0.5);
+ opacity: 0;
+ }
+ to {
+ transform: scale(1);
+ opacity: 1;
+ }
+}
+
+.animate-scale-in {
+ animation-name: scaleIn;
+ animation-duration: 0.15s;
+ animation-timing-function: cubic-bezier(0.2, 0, 0.13, 1);
+}
diff --git a/app/components/modal_component.html.erb b/app/components/modal_component.html.erb
new file mode 100644
index 0000000..81702ab
--- /dev/null
+++ b/app/components/modal_component.html.erb
@@ -0,0 +1,15 @@
+
+
+
+
+ <%= content %>
+
+
+
+
+
+
+
diff --git a/app/components/modal_component.rb b/app/components/modal_component.rb
new file mode 100644
index 0000000..249ec37
--- /dev/null
+++ b/app/components/modal_component.rb
@@ -0,0 +1,2 @@
+class ModalComponent < ViewComponent::Base
+end
diff --git a/app/components/qr_code_modal_component.html.erb b/app/components/qr_code_modal_component.html.erb
new file mode 100644
index 0000000..7d02637
--- /dev/null
+++ b/app/components/qr_code_modal_component.html.erb
@@ -0,0 +1,6 @@
+<%= render ModalComponent.new do %>
+ <% if @descripton.present? %>
+ <%= @description %>
+ <% end %>
+ <%= raw @qr_code_svg %>
+<% end %>
diff --git a/app/components/qr_code_modal_component.rb b/app/components/qr_code_modal_component.rb
new file mode 100644
index 0000000..5b2f7b6
--- /dev/null
+++ b/app/components/qr_code_modal_component.rb
@@ -0,0 +1,24 @@
+require "rqrcode"
+
+class QrCodeModalComponent < ViewComponent::Base
+ def initialize(qr_content:, description: nil)
+ @description = description
+ @qr_code_svg = qr_code_svg(qr_content)
+ end
+
+ private
+
+ def qr_code_svg(content)
+ qr_code = RQRCode::QRCode.new(content)
+ qr_code.as_svg(
+ color: "000",
+ shape_rendering: "crispEdges",
+ module_size: 6,
+ standalone: true,
+ use_path: true,
+ svg_attributes: {
+ class: 'inline-block'
+ }
+ )
+ end
+end
diff --git a/app/javascript/controllers/application.js b/app/javascript/controllers/application.js
index 1213e85..97645a6 100644
--- a/app/javascript/controllers/application.js
+++ b/app/javascript/controllers/application.js
@@ -1,7 +1,10 @@
import { Application } from "@hotwired/stimulus"
+import { Modal } from "tailwindcss-stimulus-components"
const application = Application.start()
+application.register('modal', Modal)
+
// Configure Stimulus development experience
application.debug = false
window.Stimulus = application
diff --git a/app/views/icons/_qr_code.html.erb b/app/views/icons/_qr_code.html.erb
new file mode 100644
index 0000000..fba7fa5
--- /dev/null
+++ b/app/views/icons/_qr_code.html.erb
@@ -0,0 +1,11 @@
+
diff --git a/app/views/invitations/index.html.erb b/app/views/invitations/index.html.erb
index b2c5a22..e54ee68 100644
--- a/app/views/invitations/index.html.erb
+++ b/app/views/invitations/index.html.erb
@@ -8,20 +8,25 @@
diff --git a/config/importmap.rb b/config/importmap.rb
index 17d5cd6..605cf26 100644
--- a/config/importmap.rb
+++ b/config/importmap.rb
@@ -6,3 +6,4 @@ pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
pin_all_from "app/javascript/controllers", under: "controllers"
pin "bech32" # @2.0.0
+pin "tailwindcss-stimulus-components" # @3.0.4
diff --git a/tailwind.config.js b/tailwind.config.js
index a86d1b2..a3ba1c4 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -5,7 +5,8 @@ module.exports = {
'./app/components/**/*.rb',
'./app/views/**/*.html.erb',
'./app/helpers/**/*.rb',
- './app/javascript/**/*.js'
+ './app/javascript/**/*.js',
+ './vendor/javascript/tailwindcss-stimulus-components.js'
],
safelist: [
'bg-gray-100', 'text-gray-800',
diff --git a/vendor/javascript/tailwindcss-stimulus-components.js b/vendor/javascript/tailwindcss-stimulus-components.js
new file mode 100644
index 0000000..e877767
--- /dev/null
+++ b/vendor/javascript/tailwindcss-stimulus-components.js
@@ -0,0 +1,2 @@
+import{Controller as t}from"@hotwired/stimulus";class s extends t{initialize(){this.hide()}connect(){setTimeout((()=>{this.show()}),this.showDelayValue),this.hasDismissAfterValue&&setTimeout((()=>{this.close()}),this.dismissAfterValue)}close(){this.hide(),setTimeout((()=>{this.element.remove()}),this.removeDelayValue)}show(){this.element.classList.add(...this.showClasses),this.element.classList.remove(...this.hideClasses)}hide(){this.element.classList.add(...this.hideClasses),this.element.classList.remove(...this.showClasses)}}s.values={dismissAfter:Number,showDelay:{type:Number,default:200},removeDelay:{type:Number,default:1100}},s.classes=["show","hide"];class e extends t{connect(){this.timeout=null,this.duration=this.data.get("duration")||1e3}save(){clearTimeout(this.timeout),this.timeout=setTimeout((()=>{this.statusTarget.textContent="Saving...",Rails.fire(this.formTarget,"submit")}),this.duration)}success(){this.setStatus("Saved!")}error(){this.setStatus("Unable to save!")}setStatus(t){this.statusTarget.textContent=t,this.timeout=setTimeout((()=>{this.statusTarget.textContent=""}),2e3)}}e.targets=["form","status"];class i extends t{constructor(...t){super(...t),this._onMenuButtonKeydown=t=>{switch(t.keyCode){case 13:case 32:t.preventDefault(),this.toggle()}}}connect(){this.toggleClass=this.data.get("class")||"hidden",this.visibleClass=this.data.get("visibleClass")||null,this.invisibleClass=this.data.get("invisibleClass")||null,this.activeClass=this.data.get("activeClass")||null,this.enteringClass=this.data.get("enteringClass")||null,this.leavingClass=this.data.get("leavingClass")||null,this.hasButtonTarget&&this.buttonTarget.addEventListener("keydown",this._onMenuButtonKeydown),this.element.setAttribute("aria-haspopup","true")}disconnect(){this.hasButtonTarget&&this.buttonTarget.removeEventListener("keydown",this._onMenuButtonKeydown)}toggle(){this.openValue=!this.openValue}openValueChanged(){this.openValue?this._show():this._hide()}_show(t){setTimeout((()=>{this.menuTarget.classList.remove(this.toggleClass),this.element.setAttribute("aria-expanded","true"),this._enteringClassList[0].forEach((t=>{this.menuTarget.classList.add(t)}).bind(this)),this._activeClassList[0].forEach((t=>{this.activeTarget.classList.add(t)})),this._invisibleClassList[0].forEach((t=>this.menuTarget.classList.remove(t))),this._visibleClassList[0].forEach((t=>{this.menuTarget.classList.add(t)})),setTimeout((()=>{this._enteringClassList[0].forEach((t=>this.menuTarget.classList.remove(t)))}).bind(this),this.enterTimeout[0]),"function"==typeof t&&t()}).bind(this))}_hide(t){setTimeout((()=>{this.element.setAttribute("aria-expanded","false"),this._invisibleClassList[0].forEach((t=>this.menuTarget.classList.add(t))),this._visibleClassList[0].forEach((t=>this.menuTarget.classList.remove(t))),this._activeClassList[0].forEach((t=>this.activeTarget.classList.remove(t))),this._leavingClassList[0].forEach((t=>this.menuTarget.classList.add(t))),setTimeout((()=>{this._leavingClassList[0].forEach((t=>this.menuTarget.classList.remove(t))),"function"==typeof t&&t(),this.menuTarget.classList.add(this.toggleClass)}).bind(this),this.leaveTimeout[0])}).bind(this))}show(){this.openValue=!0}hide(t){!1===this.element.contains(t.target)&&this.openValue&&(this.openValue=!1)}get activeTarget(){return this.data.has("activeTarget")?document.querySelector(this.data.get("activeTarget")):this.element}get _activeClassList(){return this.activeClass?this.activeClass.split(",").map((t=>t.split(" "))):[[],[]]}get _visibleClassList(){return this.visibleClass?this.visibleClass.split(",").map((t=>t.split(" "))):[[],[]]}get _invisibleClassList(){return this.invisibleClass?this.invisibleClass.split(",").map((t=>t.split(" "))):[[],[]]}get _enteringClassList(){return this.enteringClass?this.enteringClass.split(",").map((t=>t.split(" "))):[[],[]]}get _leavingClassList(){return this.leavingClass?this.leavingClass.split(",").map((t=>t.split(" "))):[[],[]]}get enterTimeout(){return(this.data.get("enterTimeout")||"0,0").split(",").map((t=>parseInt(t)))}get leaveTimeout(){return(this.data.get("leaveTimeout")||"0,0").split(",").map((t=>parseInt(t)))}}i.targets=["menu","button"],i.values={open:Boolean};class a extends t{connect(){this.toggleClass=this.data.get("class")||"hidden",this.backgroundId=this.data.get("backgroundId")||"modal-background",this.backgroundHtml=this.data.get("backgroundHtml")||this._backgroundHTML(),this.allowBackgroundClose="true"===(this.data.get("allowBackgroundClose")||"true"),this.preventDefaultActionOpening="true"===(this.data.get("preventDefaultActionOpening")||"true"),this.preventDefaultActionClosing="true"===(this.data.get("preventDefaultActionClosing")||"true")}disconnect(){this.close()}open(t){this.preventDefaultActionOpening&&t.preventDefault(),t.target.blur&&t.target.blur(),this.lockScroll(),this.containerTarget.classList.remove(this.toggleClass),this.data.get("disable-backdrop")||(document.body.insertAdjacentHTML("beforeend",this.backgroundHtml),this.background=document.querySelector(`#${this.backgroundId}`))}close(t){t&&this.preventDefaultActionClosing&&t.preventDefault(),this.unlockScroll(),this.containerTarget.classList.add(this.toggleClass),this.background&&this.background.remove()}closeBackground(t){this.allowBackgroundClose&&t.target===this.containerTarget&&this.close(t)}closeWithKeyboard(t){27!==t.keyCode||this.containerTarget.classList.contains(this.toggleClass)||this.close(t)}_backgroundHTML(){return``}lockScroll(){const t=window.innerWidth-document.documentElement.clientWidth;document.body.style.paddingRight=`${t}px`,this.saveScrollPosition(),document.body.classList.add("fixed","inset-x-0","overflow-hidden"),document.body.style.top=`-${this.scrollPosition}px`}unlockScroll(){document.body.style.paddingRight=null,document.body.classList.remove("fixed","inset-x-0","overflow-hidden"),this.restoreScrollValue&&this.restoreScrollPosition(),document.body.style.top=null}saveScrollPosition(){this.scrollPosition=window.pageYOffset||document.body.scrollTop}restoreScrollPosition(){void 0!==this.scrollPosition&&(document.documentElement.scrollTop=this.scrollPosition)}}a.targets=["container"],a.values={backdropColor:{type:String,default:"rgba(0, 0, 0, 0.8)"},restoreScroll:{type:Boolean,default:!0}};class l extends t{connect(){this.activeTabClasses=(this.data.get("activeTab")||"active").split(" "),this.inactiveTabClasses=(this.data.get("inactiveTab")||"inactive").split(" "),this.anchor&&(this.index=this.tabTargets.findIndex((t=>t.id===this.anchor))),this.showTab()}change(t){t.preventDefault(),this.index=t.currentTarget.dataset.index?t.currentTarget.dataset.index:t.currentTarget.dataset.id?this.tabTargets.findIndex((d=>d.id==t.currentTarget.dataset.id)):this.tabTargets.indexOf(t.currentTarget),window.dispatchEvent(new CustomEvent("tsc:tab-change"))}showTab(){this.tabTargets.forEach(((t,d)=>{const c=this.panelTargets[d];d===this.index?(c.classList.remove("hidden"),t.classList.remove(...this.inactiveTabClasses),t.classList.add(...this.activeTabClasses),t.id&&(location.hash=t.id)):(c.classList.add("hidden"),t.classList.remove(...this.activeTabClasses),t.classList.add(...this.inactiveTabClasses))}))}get index(){return parseInt(this.data.get("index")||0)}set index(t){this.data.set("index",t>=0?t:0),this.showTab()}get anchor(){return document.URL.split("#").length>1?document.URL.split("#")[1]:null}}l.targets=["tab","panel"];class o extends t{connect(){this.toggleClass=this.data.get("class")||"hidden"}toggle(t){t.preventDefault(),this.openValue=!this.openValue}hide(t){t.preventDefault(),this.openValue=!1}show(t){t.preventDefault(),this.openValue=!0}openValueChanged(){this.toggleClass&&this.toggleableTargets.forEach((t=>{t.classList.toggle(this.toggleClass)}))}}o.targets=["toggleable"],o.values={open:Boolean};class n extends t{initialize(){this.contentTarget.setAttribute("style",`transform:translate(${this.data.get("translateX")}, ${this.data.get("translateY")});`)}mouseOver(){this.contentTarget.classList.remove("hidden")}mouseOut(){this.contentTarget.classList.add("hidden")}toggle(){this.contentTarget.classList.contains("hidden")?this.contentTarget.classList.remove("hidden"):this.contentTarget.classList.add("hidden")}}n.targets=["content"];class r extends i{_show(){this.overlayTarget.classList.remove(this.toggleClass),super._show((()=>{this._activeClassList[1].forEach((t=>this.overlayTarget.classList.add(t))),this._invisibleClassList[1].forEach((t=>this.overlayTarget.classList.remove(t))),this._visibleClassList[1].forEach((t=>this.overlayTarget.classList.add(t))),setTimeout((()=>{this._enteringClassList[1].forEach((t=>this.overlayTarget.classList.remove(t)))}).bind(this),this.enterTimeout[1])}).bind(this))}_hide(){this._leavingClassList[1].forEach((t=>this.overlayTarget.classList.add(t))),super._hide((()=>{setTimeout((()=>{this._visibleClassList[1].forEach((t=>this.overlayTarget.classList.remove(t))),this._invisibleClassList[1].forEach((t=>this.overlayTarget.classList.add(t))),this._activeClassList[1].forEach((t=>this.overlayTarget.classList.remove(t))),this._leavingClassList[1].forEach((t=>this.overlayTarget.classList.remove(t))),this.overlayTarget.classList.add(this.toggleClass)}).bind(this),this.leaveTimeout[1])}).bind(this))}}r.targets=["menu","overlay"];class h extends t{connect(){this.styleProperty=this.data.get("style")||"backgroundColor"}update(){this.preview=this.color}set preview(t){this.previewTarget.style[this.styleProperty]=t;const d=this._getContrastYIQ(t);"color"===this.styleProperty?this.previewTarget.style.backgroundColor=d:this.previewTarget.style.color=d}get color(){return this.colorTarget.value}_getContrastYIQ(t){return t=t.replace("#",""),(299*parseInt(t.substr(0,2),16)+587*parseInt(t.substr(2,2),16)+114*parseInt(t.substr(4,2),16))/1e3>=128?"#000":"#fff"}}h.targets=["preview","color"];export{s as Alert,e as Autosave,h as ColorPreview,i as Dropdown,a as Modal,n as Popover,r as Slideover,l as Tabs,o as Toggle};
+