@skutally/ui-library 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,40 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static values = {
5
+ variant: { type: String, default: "default" },
6
+ dot: { type: Boolean, default: false },
7
+ }
8
+
9
+ connect() {
10
+ this.applyVariant()
11
+ if (this.dotValue) this.prependDot()
12
+ }
13
+
14
+ applyVariant() {
15
+ const base = "inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-semibold transition-colors"
16
+ const map = {
17
+ default: "bg-primary text-primary-foreground",
18
+ secondary: "bg-muted text-muted-foreground",
19
+ success: "bg-emerald-100 text-emerald-700 dark:bg-emerald-900 dark:text-emerald-300",
20
+ warning: "bg-amber-100 text-amber-700 dark:bg-amber-900 dark:text-amber-300",
21
+ danger: "bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-300",
22
+ info: "bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300",
23
+ outline: "bg-transparent border border-border text-foreground",
24
+ }
25
+ this.element.className = `${base} ${map[this.variantValue] ?? map.default}`
26
+ }
27
+
28
+ prependDot() {
29
+ if (this.element.querySelector("[data-badge-dot]")) return
30
+ const dot = document.createElement("span")
31
+ dot.setAttribute("data-badge-dot", "")
32
+ const colors = {
33
+ default: "bg-primary-foreground", secondary: "bg-muted-foreground",
34
+ success: "bg-emerald-500", warning: "bg-amber-500",
35
+ danger: "bg-red-500", info: "bg-blue-500", outline: "bg-foreground",
36
+ }
37
+ dot.className = `w-1.5 h-1.5 rounded-full ${colors[this.variantValue] ?? colors.default}`
38
+ this.element.prepend(dot)
39
+ }
40
+ }
@@ -0,0 +1,79 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static values = {
5
+ variant: { type: String, default: "default" },
6
+ size: { type: String, default: "default" },
7
+ loading: { type: Boolean, default: false },
8
+ loadingText: { type: String, default: "Loading..." },
9
+ clickable: { type: Boolean, default: false },
10
+ }
11
+
12
+ connect() {
13
+ this._originalHTML = this.element.innerHTML
14
+ this.applyBase()
15
+ this.applyVariant()
16
+ this.applySize()
17
+ if (this.loadingValue) this.setLoading(true)
18
+ if (this.clickableValue) {
19
+ this.element.addEventListener("click", () => this.handleClick())
20
+ }
21
+ }
22
+
23
+ applyBase() {
24
+ this.element.classList.add(
25
+ "inline-flex", "items-center", "justify-center", "gap-2",
26
+ "whitespace-nowrap", "rounded-md", "text-sm", "font-medium",
27
+ "transition-colors", "cursor-pointer", "focus-visible:outline-none",
28
+ "focus-visible:ring-2", "focus-visible:ring-ring", "focus-visible:ring-offset-2",
29
+ "disabled:pointer-events-none", "disabled:opacity-50"
30
+ )
31
+ }
32
+
33
+ applyVariant() {
34
+ const map = {
35
+ default: ["bg-primary", "text-primary-foreground", "hover:bg-primary/90"],
36
+ destructive: ["bg-red-600", "text-white", "hover:bg-red-700", "dark:bg-red-700", "dark:hover:bg-red-800"],
37
+ outline: ["border", "border-border", "bg-background", "hover:bg-accent", "hover:text-accent-foreground"],
38
+ secondary: ["bg-muted", "text-foreground", "hover:bg-muted/80"],
39
+ ghost: ["hover:bg-accent", "hover:text-accent-foreground"],
40
+ link: ["text-primary", "underline-offset-4", "hover:underline"],
41
+ success: ["bg-emerald-600", "text-white", "hover:bg-emerald-700"],
42
+ warning: ["bg-amber-500", "text-white", "hover:bg-amber-600"],
43
+ }
44
+ this.element.classList.add(...(map[this.variantValue] ?? map.default))
45
+ }
46
+
47
+ applySize() {
48
+ const map = {
49
+ xs: ["h-7", "px-2.5", "text-xs", "rounded"],
50
+ sm: ["h-8", "px-3", "text-xs", "rounded-md"],
51
+ default: ["h-9", "px-4", "py-2"],
52
+ lg: ["h-11", "px-6", "text-base"],
53
+ xl: ["h-12", "px-8", "text-lg", "rounded-lg"],
54
+ icon: ["h-9", "w-9", "p-0"],
55
+ }
56
+ this.element.classList.add(...(map[this.sizeValue] ?? map.default))
57
+ }
58
+
59
+ setLoading(on) {
60
+ if (on) {
61
+ this.element.disabled = true
62
+ this.element.innerHTML = `
63
+ <svg class="animate-spin h-4 w-4 shrink-0" viewBox="0 0 24 24" fill="none">
64
+ <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" class="opacity-25"/>
65
+ <path d="M4 12a8 8 0 018-8V0C5.3 0 0 5.3 0 12h4z" fill="currentColor" class="opacity-75"/>
66
+ </svg>
67
+ ${this.loadingTextValue}`
68
+ } else {
69
+ this.element.disabled = false
70
+ this.element.innerHTML = this._originalHTML
71
+ }
72
+ }
73
+
74
+ handleClick() {
75
+ if (this.element.disabled) return
76
+ this.setLoading(true)
77
+ setTimeout(() => this.setLoading(false), 2000)
78
+ }
79
+ }
@@ -0,0 +1,10 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ connect() {
5
+ this.element.classList.add(
6
+ "rounded-lg", "border", "border-border", "bg-card",
7
+ "text-card-foreground", "shadow-sm"
8
+ )
9
+ }
10
+ }
@@ -0,0 +1,33 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["icon"]
5
+ static values = {
6
+ checked: { type: Boolean, default: false },
7
+ }
8
+
9
+ connect() {
10
+ this.render()
11
+ }
12
+
13
+ toggle() {
14
+ this.checkedValue = !this.checkedValue
15
+ this.render()
16
+ this.dispatch("change", { detail: { checked: this.checkedValue } })
17
+ }
18
+
19
+ render() {
20
+ const on = this.checkedValue
21
+ const box = this.element.querySelector("[role=checkbox]")
22
+ if (box) {
23
+ box.setAttribute("aria-checked", on)
24
+ box.classList.toggle("bg-primary", on)
25
+ box.classList.toggle("text-primary-foreground", on)
26
+ box.classList.toggle("border-primary", on)
27
+ box.classList.toggle("border-border", !on)
28
+ }
29
+ if (this.hasIconTarget) {
30
+ this.iconTarget.classList.toggle("hidden", !on)
31
+ }
32
+ }
33
+ }
@@ -0,0 +1,35 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["menu"]
5
+
6
+ connect() {
7
+ this._outside = (e) => { if (!this.element.contains(e.target)) this.close() }
8
+ this._escape = (e) => { if (e.key === "Escape") this.close() }
9
+ document.addEventListener("click", this._outside)
10
+ document.addEventListener("keydown", this._escape)
11
+ }
12
+
13
+ disconnect() {
14
+ document.removeEventListener("click", this._outside)
15
+ document.removeEventListener("keydown", this._escape)
16
+ }
17
+
18
+ toggle() {
19
+ this.menuTarget.classList.contains("hidden") ? this.open() : this.close()
20
+ }
21
+
22
+ open() {
23
+ this.menuTarget.classList.remove("hidden")
24
+ requestAnimationFrame(() => {
25
+ this.menuTarget.classList.remove("opacity-0", "scale-95")
26
+ this.menuTarget.classList.add("opacity-100", "scale-100")
27
+ })
28
+ }
29
+
30
+ close() {
31
+ this.menuTarget.classList.add("opacity-0", "scale-95")
32
+ this.menuTarget.classList.remove("opacity-100", "scale-100")
33
+ setTimeout(() => this.menuTarget.classList.add("hidden"), 100)
34
+ }
35
+ }
@@ -0,0 +1,24 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static values = {
5
+ variant: { type: String, default: "default" },
6
+ icon: { type: Boolean, default: false },
7
+ }
8
+
9
+ connect() {
10
+ this.applyVariant()
11
+ }
12
+
13
+ applyVariant() {
14
+ const base = "flex h-10 w-full rounded-md border px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
15
+ const map = {
16
+ default: "border-border bg-background",
17
+ error: "border-red-400 bg-red-50 text-red-900 focus-visible:ring-red-300 dark:bg-red-950 dark:text-red-200 dark:border-red-800",
18
+ success: "border-emerald-400 bg-emerald-50 text-emerald-900 focus-visible:ring-emerald-300 dark:bg-emerald-950 dark:text-emerald-200 dark:border-emerald-800",
19
+ }
20
+ const variant = map[this.variantValue] ?? map.default
21
+ const iconPad = this.iconValue ? "pl-9" : ""
22
+ this.element.className = `${base} ${variant} ${iconPad}`.trim()
23
+ }
24
+ }
@@ -0,0 +1,39 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["overlay", "box"]
5
+
6
+ connect() {
7
+ this._key = (e) => { if (e.key === "Escape") this.close() }
8
+ document.addEventListener("keydown", this._key)
9
+ }
10
+
11
+ disconnect() {
12
+ document.removeEventListener("keydown", this._key)
13
+ }
14
+
15
+ open() {
16
+ this.overlayTarget.classList.remove("hidden")
17
+ document.body.style.overflow = "hidden"
18
+ requestAnimationFrame(() => {
19
+ this.overlayTarget.classList.remove("opacity-0")
20
+ this.boxTarget.classList.remove("scale-95", "opacity-0")
21
+ this.boxTarget.classList.add("scale-100", "opacity-100")
22
+ this.boxTarget.querySelector("button, input")?.focus()
23
+ })
24
+ }
25
+
26
+ close() {
27
+ this.overlayTarget.classList.add("opacity-0")
28
+ this.boxTarget.classList.add("scale-95", "opacity-0")
29
+ this.boxTarget.classList.remove("scale-100", "opacity-100")
30
+ setTimeout(() => {
31
+ this.overlayTarget.classList.add("hidden")
32
+ document.body.style.overflow = ""
33
+ }, 200)
34
+ }
35
+
36
+ backdrop(e) {
37
+ if (e.target === this.overlayTarget) this.close()
38
+ }
39
+ }
@@ -0,0 +1,35 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["content"]
5
+
6
+ connect() {
7
+ this._outside = (e) => { if (!this.element.contains(e.target)) this.close() }
8
+ this._escape = (e) => { if (e.key === "Escape") this.close() }
9
+ document.addEventListener("click", this._outside)
10
+ document.addEventListener("keydown", this._escape)
11
+ }
12
+
13
+ disconnect() {
14
+ document.removeEventListener("click", this._outside)
15
+ document.removeEventListener("keydown", this._escape)
16
+ }
17
+
18
+ toggle() {
19
+ this.contentTarget.classList.contains("hidden") ? this.open() : this.close()
20
+ }
21
+
22
+ open() {
23
+ this.contentTarget.classList.remove("hidden")
24
+ requestAnimationFrame(() => {
25
+ this.contentTarget.classList.remove("opacity-0", "scale-95")
26
+ this.contentTarget.classList.add("opacity-100", "scale-100")
27
+ })
28
+ }
29
+
30
+ close() {
31
+ this.contentTarget.classList.add("opacity-0", "scale-95")
32
+ this.contentTarget.classList.remove("opacity-100", "scale-100")
33
+ setTimeout(() => this.contentTarget.classList.add("hidden"), 100)
34
+ }
35
+ }
@@ -0,0 +1,22 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["bar"]
5
+ static values = {
6
+ value: { type: Number, default: 0 },
7
+ }
8
+
9
+ connect() {
10
+ this.render()
11
+ }
12
+
13
+ render() {
14
+ const pct = Math.min(100, Math.max(0, this.valueValue))
15
+ this.barTarget.style.width = `${pct}%`
16
+ this.element.setAttribute("aria-valuenow", pct)
17
+ }
18
+
19
+ valueValueChanged() {
20
+ this.render()
21
+ }
22
+ }
@@ -0,0 +1,30 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["option", "dot"]
5
+ static values = {
6
+ selected: { type: Number, default: -1 },
7
+ }
8
+
9
+ connect() {
10
+ if (this.selectedValue >= 0) this.render()
11
+ }
12
+
13
+ select(e) {
14
+ this.selectedValue = this.optionTargets.indexOf(e.currentTarget)
15
+ this.render()
16
+ this.dispatch("change", { detail: { selected: this.selectedValue } })
17
+ }
18
+
19
+ render() {
20
+ this.optionTargets.forEach((opt, i) => {
21
+ const active = i === this.selectedValue
22
+ const ring = opt.querySelector("[data-radio-target='dot']")
23
+ if (ring) {
24
+ ring.classList.toggle("border-primary", active)
25
+ const inner = ring.querySelector("span")
26
+ if (inner) inner.classList.toggle("hidden", !active)
27
+ }
28
+ })
29
+ }
30
+ }
@@ -0,0 +1,37 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["overlay", "panel"]
5
+ static values = {
6
+ side: { type: String, default: "right" },
7
+ }
8
+
9
+ open() {
10
+ this.overlayTarget.classList.remove("hidden")
11
+ document.body.style.overflow = "hidden"
12
+ requestAnimationFrame(() => {
13
+ this.overlayTarget.classList.remove("opacity-0")
14
+ this.panelTarget.classList.remove(this._hiddenTransform())
15
+ this.panelTarget.classList.add("translate-x-0", "translate-y-0")
16
+ })
17
+ }
18
+
19
+ close() {
20
+ this.overlayTarget.classList.add("opacity-0")
21
+ this.panelTarget.classList.remove("translate-x-0", "translate-y-0")
22
+ this.panelTarget.classList.add(this._hiddenTransform())
23
+ setTimeout(() => {
24
+ this.overlayTarget.classList.add("hidden")
25
+ document.body.style.overflow = ""
26
+ }, 300)
27
+ }
28
+
29
+ backdrop(e) {
30
+ if (e.target === this.overlayTarget) this.close()
31
+ }
32
+
33
+ _hiddenTransform() {
34
+ const map = { right: "translate-x-full", left: "-translate-x-full", top: "-translate-y-full", bottom: "translate-y-full" }
35
+ return map[this.sideValue] || "translate-x-full"
36
+ }
37
+ }
@@ -0,0 +1,57 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["track", "fill", "thumb", "output"]
5
+ static values = {
6
+ min: { type: Number, default: 0 },
7
+ max: { type: Number, default: 100 },
8
+ val: { type: Number, default: 50 },
9
+ step: { type: Number, default: 1 },
10
+ }
11
+
12
+ connect() {
13
+ this._dragging = false
14
+ this._onMove = (e) => this._handleMove(e)
15
+ this._onUp = () => this._handleUp()
16
+ this.render()
17
+ }
18
+
19
+ startDrag(e) {
20
+ e.preventDefault()
21
+ this._dragging = true
22
+ document.addEventListener("mousemove", this._onMove)
23
+ document.addEventListener("mouseup", this._onUp)
24
+ document.addEventListener("touchmove", this._onMove)
25
+ document.addEventListener("touchend", this._onUp)
26
+ this._handleMove(e)
27
+ }
28
+
29
+ _handleMove(e) {
30
+ if (!this._dragging) return
31
+ const rect = this.trackTarget.getBoundingClientRect()
32
+ const clientX = e.touches ? e.touches[0].clientX : e.clientX
33
+ let pct = (clientX - rect.left) / rect.width
34
+ pct = Math.max(0, Math.min(1, pct))
35
+ const range = this.maxValue - this.minValue
36
+ let val = this.minValue + pct * range
37
+ val = Math.round(val / this.stepValue) * this.stepValue
38
+ this.valValue = Math.max(this.minValue, Math.min(this.maxValue, val))
39
+ this.render()
40
+ this.dispatch("change", { detail: { value: this.valValue } })
41
+ }
42
+
43
+ _handleUp() {
44
+ this._dragging = false
45
+ document.removeEventListener("mousemove", this._onMove)
46
+ document.removeEventListener("mouseup", this._onUp)
47
+ document.removeEventListener("touchmove", this._onMove)
48
+ document.removeEventListener("touchend", this._onUp)
49
+ }
50
+
51
+ render() {
52
+ const pct = ((this.valValue - this.minValue) / (this.maxValue - this.minValue)) * 100
53
+ if (this.hasFillTarget) this.fillTarget.style.width = `${pct}%`
54
+ if (this.hasThumbTarget) this.thumbTarget.style.left = `${pct}%`
55
+ if (this.hasOutputTarget) this.outputTarget.textContent = this.valValue
56
+ }
57
+ }
@@ -0,0 +1,31 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["header", "body"]
5
+
6
+ sort(e) {
7
+ const th = e.currentTarget
8
+ const col = parseInt(th.dataset.col)
9
+ const dir = th.dataset.dir === "asc" ? "desc" : "asc"
10
+
11
+ // Reset all headers
12
+ this.headerTargets.forEach(h => { h.dataset.dir = ""; h.querySelector("[data-sort-icon]")?.classList.add("opacity-0") })
13
+ th.dataset.dir = dir
14
+ const icon = th.querySelector("[data-sort-icon]")
15
+ if (icon) {
16
+ icon.classList.remove("opacity-0")
17
+ icon.style.transform = dir === "desc" ? "rotate(180deg)" : ""
18
+ }
19
+
20
+ const rows = Array.from(this.bodyTarget.querySelectorAll("tr"))
21
+ rows.sort((a, b) => {
22
+ const aVal = a.children[col]?.textContent.trim() ?? ""
23
+ const bVal = b.children[col]?.textContent.trim() ?? ""
24
+ const aNum = parseFloat(aVal.replace(/[^0-9.-]/g, ""))
25
+ const bNum = parseFloat(bVal.replace(/[^0-9.-]/g, ""))
26
+ if (!isNaN(aNum) && !isNaN(bNum)) return dir === "asc" ? aNum - bNum : bNum - aNum
27
+ return dir === "asc" ? aVal.localeCompare(bVal) : bVal.localeCompare(aVal)
28
+ })
29
+ rows.forEach(r => this.bodyTarget.appendChild(r))
30
+ }
31
+ }
@@ -0,0 +1,29 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["tab", "panel"]
5
+
6
+ connect() {
7
+ this.idx = 0
8
+ this.render()
9
+ }
10
+
11
+ switch(e) {
12
+ this.idx = this.tabTargets.indexOf(e.currentTarget)
13
+ this.render()
14
+ }
15
+
16
+ render() {
17
+ this.tabTargets.forEach((t, i) => {
18
+ const active = i === this.idx
19
+ t.classList.toggle("border-foreground", active)
20
+ t.classList.toggle("text-foreground", active)
21
+ t.classList.toggle("font-semibold", active)
22
+ t.classList.toggle("bg-background", active)
23
+ t.classList.toggle("border-transparent", !active)
24
+ t.classList.toggle("text-muted-foreground", !active)
25
+ t.classList.toggle("font-medium", !active)
26
+ })
27
+ this.panelTargets.forEach((p, i) => p.classList.toggle("hidden", i !== this.idx))
28
+ }
29
+ }
@@ -0,0 +1,41 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["container"]
5
+
6
+ show({ params: { type = "info", msg = "Notification" } }) {
7
+ const map = {
8
+ info: { ring: "bg-background border-border text-foreground", dot: "bg-blue-500" },
9
+ success: { ring: "bg-background border-border text-foreground", dot: "bg-emerald-500" },
10
+ warning: { ring: "bg-background border-border text-foreground", dot: "bg-amber-500" },
11
+ danger: { ring: "bg-background border-border text-foreground", dot: "bg-red-500" },
12
+ }
13
+ const cfg = map[type] ?? map.info
14
+ const el = document.createElement("div")
15
+ el.className = `flex items-center gap-3 px-4 py-3 rounded-lg border text-sm shadow-lg ${cfg.ring} transition-all duration-300 opacity-0 translate-y-2`
16
+ el.innerHTML = `
17
+ <span class="w-2 h-2 rounded-full shrink-0 ${cfg.dot}"></span>
18
+ <span class="flex-1">${this._escape(msg)}</span>
19
+ <button class="text-lg leading-none opacity-40 hover:opacity-80 ml-1 cursor-pointer">\u2715</button>`
20
+ el.querySelector("button").addEventListener("click", () => this._dismiss(el))
21
+ this.containerTarget.appendChild(el)
22
+ requestAnimationFrame(() => {
23
+ el.classList.replace("opacity-0", "opacity-100")
24
+ el.classList.replace("translate-y-2", "translate-y-0")
25
+ })
26
+ setTimeout(() => this._dismiss(el), 4000)
27
+ }
28
+
29
+ _dismiss(el) {
30
+ if (!el.parentNode) return
31
+ el.classList.replace("opacity-100", "opacity-0")
32
+ el.classList.add("-translate-y-1")
33
+ setTimeout(() => el.remove(), 300)
34
+ }
35
+
36
+ _escape(str) {
37
+ const div = document.createElement("div")
38
+ div.textContent = str
39
+ return div.innerHTML
40
+ }
41
+ }
@@ -0,0 +1,27 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["track", "thumb"]
5
+ static values = {
6
+ checked: { type: Boolean, default: false },
7
+ }
8
+
9
+ connect() {
10
+ this.render()
11
+ }
12
+
13
+ flip() {
14
+ this.checkedValue = !this.checkedValue
15
+ this.render()
16
+ this.dispatch("change", { detail: { checked: this.checkedValue } })
17
+ }
18
+
19
+ render() {
20
+ const on = this.checkedValue
21
+ const btn = this.element.querySelector("[role=switch]")
22
+ if (btn) btn.setAttribute("aria-checked", on)
23
+ this.trackTarget.classList.toggle("bg-primary", on)
24
+ this.trackTarget.classList.toggle("bg-muted", !on)
25
+ this.thumbTarget.style.transform = on ? "translateX(20px)" : "translateX(0)"
26
+ }
27
+ }
@@ -0,0 +1,19 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["content"]
5
+ static values = {
6
+ position: { type: String, default: "top" },
7
+ }
8
+
9
+ show() {
10
+ this.contentTarget.classList.remove("hidden", "opacity-0")
11
+ this.contentTarget.classList.add("opacity-100")
12
+ }
13
+
14
+ hide() {
15
+ this.contentTarget.classList.remove("opacity-100")
16
+ this.contentTarget.classList.add("opacity-0")
17
+ setTimeout(() => this.contentTarget.classList.add("hidden"), 150)
18
+ }
19
+ }