@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.
- package/README.md +514 -0
- package/bin/cli.js +272 -0
- package/package.json +36 -0
- package/registry/registry.json +207 -0
- package/src/base.css +82 -0
- package/src/controllers/accordion.js +27 -0
- package/src/controllers/alert.js +39 -0
- package/src/controllers/badge.js +40 -0
- package/src/controllers/button.js +79 -0
- package/src/controllers/card.js +10 -0
- package/src/controllers/checkbox.js +33 -0
- package/src/controllers/dropdown.js +35 -0
- package/src/controllers/input.js +24 -0
- package/src/controllers/modal.js +39 -0
- package/src/controllers/popover.js +35 -0
- package/src/controllers/progress.js +22 -0
- package/src/controllers/radio.js +30 -0
- package/src/controllers/sheet.js +37 -0
- package/src/controllers/slider.js +57 -0
- package/src/controllers/table.js +31 -0
- package/src/controllers/tabs.js +29 -0
- package/src/controllers/toast.js +41 -0
- package/src/controllers/toggle.js +27 -0
- package/src/controllers/tooltip.js +19 -0
- package/src/controllers.js +836 -0
- package/templates/components.json +13 -0
|
@@ -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,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
|
+
}
|