maquina-components 0.4.4 → 0.5.1
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.
- checksums.yaml +4 -4
- data/README.md +19 -1
- data/app/assets/stylesheets/breadcrumbs.css +21 -0
- data/app/assets/stylesheets/combobox.css +36 -1
- data/app/assets/stylesheets/drawer.css +135 -0
- data/app/assets/stylesheets/menu_button.css +155 -0
- data/app/assets/stylesheets/separator.css +20 -0
- data/app/assets/stylesheets/sidebar.css +12 -0
- data/app/assets/stylesheets/stats.css +101 -0
- data/app/assets/tailwind/maquina_components_engine/engine.css +4 -0
- data/app/helpers/maquina_components/combobox_helper.rb +2 -1
- data/app/helpers/maquina_components/components_helper.rb +33 -0
- data/app/helpers/maquina_components/drawer_helper.rb +19 -0
- data/app/helpers/maquina_components/icons_helper.rb +68 -1
- data/app/javascript/controllers/breadcrumb_controller.js +39 -30
- data/app/javascript/controllers/combobox_controller.js +9 -0
- data/app/javascript/controllers/drawer_controller.js +287 -0
- data/app/javascript/controllers/drawer_trigger_controller.js +58 -0
- data/app/javascript/controllers/menu_button_controller.js +107 -29
- data/app/javascript/controllers/sidebar_controller.js +6 -8
- data/app/javascript/controllers/sidebar_trigger_controller.js +33 -4
- data/app/javascript/controllers/toaster_controller.js +12 -0
- data/app/views/components/_alert.html.erb +3 -1
- data/app/views/components/_badge.html.erb +7 -1
- data/app/views/components/_breadcrumbs.html.erb +1 -1
- data/app/views/components/_calendar.html.erb +1 -1
- data/app/views/components/_card.html.erb +1 -1
- data/app/views/components/_combobox.html.erb +3 -2
- data/app/views/components/_date_picker.html.erb +4 -3
- data/app/views/components/_drawer.html.erb +37 -0
- data/app/views/components/_dropdown.html.erb +21 -24
- data/app/views/components/_dropdown_menu.html.erb +1 -1
- data/app/views/components/_empty.html.erb +1 -1
- data/app/views/components/_header.html.erb +1 -1
- data/app/views/components/_menu_button.html.erb +29 -34
- data/app/views/components/_pagination.html.erb +1 -1
- data/app/views/components/_separator.html.erb +9 -10
- data/app/views/components/_sidebar.html.erb +1 -1
- data/app/views/components/_simple_table.html.erb +3 -1
- data/app/views/components/_table.html.erb +1 -1
- data/app/views/components/_toast.html.erb +6 -1
- data/app/views/components/_toaster.html.erb +1 -1
- data/app/views/components/_toggle_group.html.erb +1 -1
- data/app/views/components/alert/_description.html.erb +1 -1
- data/app/views/components/alert/_title.html.erb +1 -1
- data/app/views/components/breadcrumbs/_ellipsis.html.erb +1 -1
- data/app/views/components/breadcrumbs/_item.html.erb +1 -1
- data/app/views/components/breadcrumbs/_link.html.erb +1 -1
- data/app/views/components/breadcrumbs/_list.html.erb +1 -1
- data/app/views/components/breadcrumbs/_page.html.erb +1 -1
- data/app/views/components/breadcrumbs/_separator.html.erb +1 -1
- data/app/views/components/calendar/_header.html.erb +1 -1
- data/app/views/components/calendar/_week.html.erb +1 -1
- data/app/views/components/card/_action.html.erb +1 -1
- data/app/views/components/card/_content.html.erb +1 -1
- data/app/views/components/card/_description.html.erb +1 -1
- data/app/views/components/card/_footer.html.erb +1 -1
- data/app/views/components/card/_header.html.erb +1 -1
- data/app/views/components/card/_title.html.erb +1 -1
- data/app/views/components/combobox/_content.html.erb +1 -1
- data/app/views/components/combobox/_empty.html.erb +3 -3
- data/app/views/components/combobox/_group.html.erb +1 -1
- data/app/views/components/combobox/_input.html.erb +1 -1
- data/app/views/components/combobox/_label.html.erb +1 -1
- data/app/views/components/combobox/_list.html.erb +1 -1
- data/app/views/components/combobox/_option.html.erb +1 -1
- data/app/views/components/combobox/_separator.html.erb +1 -1
- data/app/views/components/combobox/_trigger.html.erb +1 -1
- data/app/views/components/drawer/_close.html.erb +14 -0
- data/app/views/components/drawer/_content.html.erb +7 -0
- data/app/views/components/drawer/_footer.html.erb +7 -0
- data/app/views/components/drawer/_header.html.erb +12 -0
- data/app/views/components/drawer/_provider.html.erb +14 -0
- data/app/views/components/drawer/_trigger.html.erb +14 -0
- data/app/views/components/dropdown_menu/_content.html.erb +1 -1
- data/app/views/components/dropdown_menu/_group.html.erb +1 -1
- data/app/views/components/dropdown_menu/_item.html.erb +1 -1
- data/app/views/components/dropdown_menu/_label.html.erb +3 -7
- data/app/views/components/dropdown_menu/_separator.html.erb +1 -1
- data/app/views/components/dropdown_menu/_shortcut.html.erb +3 -7
- data/app/views/components/dropdown_menu/_trigger.html.erb +1 -1
- data/app/views/components/empty/_content.html.erb +1 -1
- data/app/views/components/empty/_description.html.erb +3 -7
- data/app/views/components/empty/_header.html.erb +1 -1
- data/app/views/components/empty/_media.html.erb +5 -3
- data/app/views/components/empty/_title.html.erb +3 -7
- data/app/views/components/pagination/_content.html.erb +1 -1
- data/app/views/components/pagination/_item.html.erb +1 -1
- data/app/views/components/pagination/_link.html.erb +1 -1
- data/app/views/components/pagination/_next.html.erb +1 -1
- data/app/views/components/pagination/_previous.html.erb +1 -1
- data/app/views/components/sidebar/_content.html.erb +1 -1
- data/app/views/components/sidebar/_footer.html.erb +1 -1
- data/app/views/components/sidebar/_group.html.erb +1 -1
- data/app/views/components/sidebar/_header.html.erb +1 -1
- data/app/views/components/sidebar/_inset.html.erb +1 -1
- data/app/views/components/sidebar/_menu.html.erb +1 -1
- data/app/views/components/sidebar/_menu_button.html.erb +1 -1
- data/app/views/components/sidebar/_menu_item.html.erb +1 -1
- data/app/views/components/sidebar/_menu_link.html.erb +4 -7
- data/app/views/components/sidebar/_provider.html.erb +1 -1
- data/app/views/components/sidebar/_trigger.html.erb +1 -1
- data/app/views/components/stats/_stats_card.html.erb +32 -92
- data/app/views/components/stats/_stats_grid.html.erb +18 -24
- data/app/views/components/table/_body.html.erb +1 -1
- data/app/views/components/table/_caption.html.erb +3 -3
- data/app/views/components/table/_cell.html.erb +3 -3
- data/app/views/components/table/_footer.html.erb +1 -1
- data/app/views/components/table/_head.html.erb +3 -3
- data/app/views/components/table/_header.html.erb +1 -1
- data/app/views/components/table/_row.html.erb +1 -1
- data/app/views/components/toast/_action.html.erb +1 -1
- data/app/views/components/toast/_description.html.erb +1 -1
- data/app/views/components/toast/_title.html.erb +1 -1
- data/app/views/components/toggle_group/_item.html.erb +1 -1
- data/lib/generators/maquina_components/install/templates/maquina_components_helper.rb.tt +8 -0
- data/lib/generators/maquina_components/scaffold_templates/scaffold_templates_generator.rb +40 -0
- data/lib/generators/maquina_components/scaffold_templates/templates/_form.html.erb.tt +49 -0
- data/lib/generators/maquina_components/scaffold_templates/templates/edit.html.erb.tt +29 -0
- data/lib/generators/maquina_components/scaffold_templates/templates/index.html.erb.tt +58 -0
- data/lib/generators/maquina_components/scaffold_templates/templates/new.html.erb.tt +26 -0
- data/lib/generators/maquina_components/scaffold_templates/templates/partial.html.erb.tt +17 -0
- data/lib/generators/maquina_components/scaffold_templates/templates/show.html.erb.tt +53 -0
- data/lib/maquina-components.rb +5 -0
- data/lib/maquina_components/engine.rb +7 -3
- data/lib/maquina_components/version.rb +1 -1
- metadata +52 -3
- data/app/assets/stylesheets/maquina_components.css +0 -178
|
@@ -1,59 +1,137 @@
|
|
|
1
1
|
import { Controller } from "@hotwired/stimulus"
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* MenuButton Controller
|
|
5
|
+
*
|
|
6
|
+
* Sidebar-flavored button that toggles a dropdown panel with:
|
|
7
|
+
* - Click to toggle
|
|
8
|
+
* - Click outside to close
|
|
9
|
+
* - Escape key to close
|
|
10
|
+
* - aria-expanded state on the trigger
|
|
11
|
+
* - Animation states via data-state
|
|
12
|
+
*/
|
|
3
13
|
export default class extends Controller {
|
|
4
14
|
static targets = ["button", "content"]
|
|
5
15
|
|
|
16
|
+
static values = {
|
|
17
|
+
open: { type: Boolean, default: false }
|
|
18
|
+
}
|
|
19
|
+
|
|
6
20
|
connect() {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
21
|
+
this.handleClickOutside = this.handleClickOutside.bind(this)
|
|
22
|
+
this.handleKeydown = this.handleKeydown.bind(this)
|
|
23
|
+
this.boundTeardown = this.teardown.bind(this)
|
|
10
24
|
|
|
11
|
-
|
|
12
|
-
this.isOpen = this.buttonTarget.dataset.state === "open"
|
|
25
|
+
document.addEventListener("turbo:before-cache", this.boundTeardown)
|
|
13
26
|
|
|
14
|
-
if (this.
|
|
15
|
-
this.
|
|
27
|
+
if (this.hasContentTarget) {
|
|
28
|
+
this.contentTarget.hidden = !this.openValue
|
|
16
29
|
}
|
|
17
30
|
}
|
|
18
31
|
|
|
19
32
|
disconnect() {
|
|
20
|
-
this.
|
|
33
|
+
this.removeEventListeners()
|
|
34
|
+
document.removeEventListener("turbo:before-cache", this.boundTeardown)
|
|
21
35
|
}
|
|
22
36
|
|
|
23
|
-
toggle() {
|
|
24
|
-
|
|
25
|
-
|
|
37
|
+
toggle(event) {
|
|
38
|
+
event?.preventDefault()
|
|
39
|
+
|
|
40
|
+
if (this.openValue) {
|
|
41
|
+
this.close()
|
|
42
|
+
} else {
|
|
43
|
+
this.open()
|
|
26
44
|
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
open() {
|
|
48
|
+
if (this.openValue || !this.hasContentTarget) return
|
|
27
49
|
|
|
50
|
+
this.openValue = true
|
|
51
|
+
this.buttonTarget.dataset.state = "open"
|
|
52
|
+
this.buttonTarget.setAttribute("aria-expanded", "true")
|
|
53
|
+
this.contentTarget.dataset.state = "open"
|
|
28
54
|
this.contentTarget.classList.remove("hidden")
|
|
55
|
+
this.contentTarget.hidden = false
|
|
29
56
|
|
|
30
|
-
this.
|
|
31
|
-
|
|
57
|
+
this.addEventListeners()
|
|
58
|
+
}
|
|
32
59
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
60
|
+
close() {
|
|
61
|
+
if (!this.openValue || !this.hasContentTarget) return
|
|
62
|
+
|
|
63
|
+
// Start closing animation, then hide once it finishes
|
|
64
|
+
this.contentTarget.dataset.state = "closing"
|
|
65
|
+
|
|
66
|
+
const animationDuration = 100 // matches CSS animation duration
|
|
67
|
+
|
|
68
|
+
this._closeTimeout = setTimeout(() => {
|
|
69
|
+
this._closeTimeout = null
|
|
70
|
+
this.openValue = false
|
|
71
|
+
this.buttonTarget.dataset.state = "closed"
|
|
72
|
+
this.buttonTarget.setAttribute("aria-expanded", "false")
|
|
73
|
+
this.contentTarget.dataset.state = "closed"
|
|
74
|
+
this.contentTarget.hidden = true
|
|
75
|
+
|
|
76
|
+
this.removeEventListeners()
|
|
77
|
+
this.buttonTarget.focus()
|
|
78
|
+
}, animationDuration)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Turbo Cache Teardown
|
|
82
|
+
|
|
83
|
+
teardown() {
|
|
84
|
+
if (this._closeTimeout) {
|
|
85
|
+
clearTimeout(this._closeTimeout)
|
|
86
|
+
this._closeTimeout = null
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
this.openValue = false
|
|
90
|
+
|
|
91
|
+
if (this.hasButtonTarget) {
|
|
92
|
+
this.buttonTarget.dataset.state = "closed"
|
|
93
|
+
this.buttonTarget.setAttribute("aria-expanded", "false")
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (this.hasContentTarget) {
|
|
97
|
+
this.contentTarget.dataset.state = "closed"
|
|
98
|
+
this.contentTarget.hidden = true
|
|
40
99
|
}
|
|
100
|
+
|
|
101
|
+
this.removeEventListeners()
|
|
41
102
|
}
|
|
42
103
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
104
|
+
// Event Handlers
|
|
105
|
+
|
|
106
|
+
handleClickOutside(event) {
|
|
107
|
+
if (!this.openValue) return
|
|
108
|
+
if (this.element.contains(event.target)) return
|
|
109
|
+
|
|
110
|
+
this.close()
|
|
111
|
+
}
|
|
46
112
|
|
|
47
|
-
|
|
48
|
-
|
|
113
|
+
handleKeydown(event) {
|
|
114
|
+
if (!this.openValue) return
|
|
115
|
+
|
|
116
|
+
if (event.key === "Escape") {
|
|
117
|
+
event.preventDefault()
|
|
118
|
+
this.close()
|
|
49
119
|
}
|
|
50
120
|
}
|
|
51
121
|
|
|
52
|
-
|
|
53
|
-
|
|
122
|
+
// Event Listener Management
|
|
123
|
+
|
|
124
|
+
addEventListeners() {
|
|
125
|
+
// Delay adding click outside listener to prevent immediate close
|
|
126
|
+
setTimeout(() => {
|
|
127
|
+
document.addEventListener("click", this.handleClickOutside)
|
|
128
|
+
}, 0)
|
|
129
|
+
|
|
130
|
+
document.addEventListener("keydown", this.handleKeydown)
|
|
54
131
|
}
|
|
55
132
|
|
|
56
|
-
|
|
57
|
-
document.removeEventListener(
|
|
133
|
+
removeEventListeners() {
|
|
134
|
+
document.removeEventListener("click", this.handleClickOutside)
|
|
135
|
+
document.removeEventListener("keydown", this.handleKeydown)
|
|
58
136
|
}
|
|
59
137
|
}
|
|
@@ -318,19 +318,17 @@ export default class extends Controller {
|
|
|
318
318
|
// Scroll Lock (for mobile overlay)
|
|
319
319
|
// ============================================================================
|
|
320
320
|
|
|
321
|
+
// Styling lives in drawer.css (body[data-maquina-scroll-locked]); the
|
|
322
|
+
// controller only records the scroll offset and flips the attribute
|
|
321
323
|
lockScroll() {
|
|
322
324
|
this.scrollPosition = window.pageYOffset
|
|
323
|
-
document.body.style.
|
|
324
|
-
document.body.
|
|
325
|
-
document.body.style.top = `-${this.scrollPosition}px`
|
|
326
|
-
document.body.style.width = "100%"
|
|
325
|
+
document.body.style.setProperty("--maquina-scroll-lock-top", `-${this.scrollPosition}px`)
|
|
326
|
+
document.body.setAttribute("data-maquina-scroll-locked", "")
|
|
327
327
|
}
|
|
328
328
|
|
|
329
329
|
unlockScroll() {
|
|
330
|
-
document.body.
|
|
331
|
-
document.body.style.
|
|
332
|
-
document.body.style.top = ""
|
|
333
|
-
document.body.style.width = ""
|
|
330
|
+
document.body.removeAttribute("data-maquina-scroll-locked")
|
|
331
|
+
document.body.style.removeProperty("--maquina-scroll-lock-top")
|
|
334
332
|
if (this.scrollPosition !== undefined) {
|
|
335
333
|
window.scrollTo(0, this.scrollPosition)
|
|
336
334
|
}
|
|
@@ -2,12 +2,13 @@ import { Controller } from "@hotwired/stimulus"
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Sidebar Trigger Controller
|
|
5
|
-
*
|
|
6
|
-
* Triggers sidebar toggle via Stimulus outlets
|
|
5
|
+
*
|
|
6
|
+
* Triggers sidebar toggle via Stimulus outlets and mirrors the sidebar's
|
|
7
|
+
* state on the trigger (aria-expanded, aria-controls).
|
|
7
8
|
* Can be placed anywhere on the page - finds sidebar via outlet selector.
|
|
8
|
-
*
|
|
9
|
+
*
|
|
9
10
|
* @example
|
|
10
|
-
* <button
|
|
11
|
+
* <button
|
|
11
12
|
* data-controller="sidebar-trigger"
|
|
12
13
|
* data-action="click->sidebar-trigger#triggerClick"
|
|
13
14
|
* data-sidebar-trigger-sidebar-outlet="[data-outlet='sidebar']"
|
|
@@ -18,6 +19,23 @@ import { Controller } from "@hotwired/stimulus"
|
|
|
18
19
|
export default class extends Controller {
|
|
19
20
|
static outlets = ["sidebar"]
|
|
20
21
|
|
|
22
|
+
connect() {
|
|
23
|
+
this.handleStateChanged = this.handleStateChanged.bind(this)
|
|
24
|
+
window.addEventListener("sidebar:stateChanged", this.handleStateChanged)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
disconnect() {
|
|
28
|
+
window.removeEventListener("sidebar:stateChanged", this.handleStateChanged)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
sidebarOutletConnected(outlet) {
|
|
32
|
+
this.syncExpanded(outlet.openValue)
|
|
33
|
+
|
|
34
|
+
if (outlet.hasSidebarTarget && outlet.sidebarTarget.id) {
|
|
35
|
+
this.element.setAttribute("aria-controls", outlet.sidebarTarget.id)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
21
39
|
/**
|
|
22
40
|
* Toggle sidebar when trigger is clicked
|
|
23
41
|
* Works with multiple sidebars if multiple outlets are connected
|
|
@@ -29,4 +47,15 @@ export default class extends Controller {
|
|
|
29
47
|
})
|
|
30
48
|
}
|
|
31
49
|
}
|
|
50
|
+
|
|
51
|
+
handleStateChanged(event) {
|
|
52
|
+
if (!this.hasSidebarOutlet) return
|
|
53
|
+
if (!this.sidebarOutletElements.includes(event.target)) return
|
|
54
|
+
|
|
55
|
+
this.syncExpanded(event.detail.open)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
syncExpanded(open) {
|
|
59
|
+
this.element.setAttribute("aria-expanded", open ? "true" : "false")
|
|
60
|
+
}
|
|
32
61
|
}
|
|
@@ -60,6 +60,10 @@ export default class extends Controller {
|
|
|
60
60
|
return controller.createToast({ title, variant: "error", ...options })
|
|
61
61
|
},
|
|
62
62
|
|
|
63
|
+
destructive(title, options = {}) {
|
|
64
|
+
return controller.createToast({ title, variant: "error", ...options })
|
|
65
|
+
},
|
|
66
|
+
|
|
63
67
|
dismiss(toastId) {
|
|
64
68
|
controller.dismissToast(toastId)
|
|
65
69
|
},
|
|
@@ -115,6 +119,14 @@ export default class extends Controller {
|
|
|
115
119
|
}
|
|
116
120
|
|
|
117
121
|
buildToastHtml({ id, title, description, variant, duration, dismissible, action }) {
|
|
122
|
+
// Titles/descriptions are escaped below; variant and duration are
|
|
123
|
+
// interpolated into attributes, so constrain them to safe values.
|
|
124
|
+
// :destructive is the library-wide alias for toast's error variant.
|
|
125
|
+
if (variant === "destructive") variant = "error"
|
|
126
|
+
const knownVariants = ["default", "success", "info", "warning", "error"]
|
|
127
|
+
variant = knownVariants.includes(variant) ? variant : "default"
|
|
128
|
+
duration = Number(duration) || 0
|
|
129
|
+
|
|
118
130
|
const icons = {
|
|
119
131
|
default: "",
|
|
120
132
|
success: this.getIconSvg("circle_check"),
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
<%# locals: (variant: :default, icon: nil, css_classes: "", **html_options) %>
|
|
2
|
-
|
|
2
|
+
<%# :error is accepted as an alias of the canonical :destructive variant %>
|
|
3
|
+
<% variant = :destructive if variant.to_s == "error" %>
|
|
4
|
+
<% merged_data = merge_component_data(html_options,
|
|
3
5
|
component: :alert,
|
|
4
6
|
variant: variant,
|
|
5
7
|
has_icon: icon.present? || nil
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
<%# locals: (variant: :default, size: :md, css_classes: "", **html_options) %>
|
|
2
|
-
<%
|
|
2
|
+
<%
|
|
3
|
+
# Library-wide vocabulary aliases: :default is the canonical middle
|
|
4
|
+
# size (badge's own is :md), :destructive the canonical danger variant
|
|
5
|
+
size = :md if size.to_s == "default"
|
|
6
|
+
variant = :destructive if variant.to_s == "error"
|
|
7
|
+
%>
|
|
8
|
+
<% merged_data = merge_component_data(html_options,
|
|
3
9
|
component: :badge,
|
|
4
10
|
variant: variant,
|
|
5
11
|
size: size
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<%# locals: (css_classes: "", **html_options) %>
|
|
2
|
-
<% merged_data = (html_options
|
|
2
|
+
<% merged_data = merge_component_data(html_options, component: :card) %>
|
|
3
3
|
|
|
4
4
|
<%= content_tag :div, class: css_classes.presence, data: merged_data, **html_options do %>
|
|
5
5
|
<%= yield %>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<%# locals: (id: nil, name: nil, value: nil, placeholder: "Select...", css_classes: "", **html_options) %>
|
|
2
|
-
|
|
3
|
-
<%
|
|
2
|
+
<%# Deterministic fallback id: random ids break idiomorph matching across Turbo morphs %>
|
|
3
|
+
<% combobox_id = id || "combobox-#{name.to_s.parameterize.presence || "default"}" %>
|
|
4
|
+
<% merged_data = merge_component_data(html_options,
|
|
4
5
|
component: :combobox,
|
|
5
6
|
controller: "combobox",
|
|
6
7
|
combobox_value_value: value,
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
<%# locals: (selected: nil, selected_end: nil, mode: :single, min_date: nil, max_date: nil, disabled_dates: [], show_outside_days: true, week_starts_on: :sunday, placeholder: nil, input_name: nil, input_name_end: nil, id: nil, disabled: false, required: false, css_classes: "", **html_options) %>
|
|
2
2
|
<%
|
|
3
|
-
#
|
|
4
|
-
|
|
3
|
+
# Deterministic ID for popover targeting: random ids break idiomorph
|
|
4
|
+
# matching across Turbo morphs
|
|
5
|
+
component_id = id || "date-picker-#{input_name.to_s.parameterize.presence || "default"}"
|
|
5
6
|
popover_id = "#{component_id}-popover"
|
|
6
7
|
|
|
7
8
|
# Parse selected dates for display
|
|
@@ -28,7 +29,7 @@
|
|
|
28
29
|
|
|
29
30
|
default_placeholder = mode == :range ? "Select date range" : "Select date"
|
|
30
31
|
|
|
31
|
-
merged_data = (html_options
|
|
32
|
+
merged_data = merge_component_data(html_options,
|
|
32
33
|
controller: "date-picker",
|
|
33
34
|
component: "date-picker",
|
|
34
35
|
"date-picker-mode-value": mode,
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<%# locals: (id: nil, state: :closed, side: :right, aria_label: "Drawer", css_classes: "", **html_options) %>
|
|
2
|
+
<% stable_id = id || "drawer-#{side}"
|
|
3
|
+
merged_data = merge_component_data(html_options,
|
|
4
|
+
drawer_part: :root,
|
|
5
|
+
drawer_target: "drawer",
|
|
6
|
+
state: state,
|
|
7
|
+
side: side
|
|
8
|
+
) %>
|
|
9
|
+
<aside
|
|
10
|
+
id="<%= stable_id %>"
|
|
11
|
+
class="group peer drawer-loading <%= css_classes %>"
|
|
12
|
+
<%= tag.attributes(data: merged_data, **html_options) %>
|
|
13
|
+
>
|
|
14
|
+
<%# Backdrop overlay %>
|
|
15
|
+
<div
|
|
16
|
+
id="<%= stable_id %>-backdrop"
|
|
17
|
+
data-drawer-part="backdrop"
|
|
18
|
+
data-drawer-target="backdrop"
|
|
19
|
+
data-action="click->drawer#backdropClick"
|
|
20
|
+
></div>
|
|
21
|
+
<%# Drawer container (fixed positioned) %>
|
|
22
|
+
<div
|
|
23
|
+
id="<%= stable_id %>-panel"
|
|
24
|
+
data-drawer-part="panel"
|
|
25
|
+
data-drawer-target="panel"
|
|
26
|
+
role="dialog"
|
|
27
|
+
aria-modal="true"
|
|
28
|
+
aria-label="<%= aria_label %>"
|
|
29
|
+
tabindex="-1"
|
|
30
|
+
<%= tag.attributes(aria: {hidden: true}, inert: true) if state.to_s == "closed" %>
|
|
31
|
+
>
|
|
32
|
+
<%# Inner drawer content wrapper %>
|
|
33
|
+
<div id="<%= stable_id %>-inner" data-drawer-part="inner">
|
|
34
|
+
<%= yield %>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
</aside>
|
|
@@ -1,25 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
1
|
+
<%# locals: (id: nil, side: :top, align: :start, css_classes: "", **html_options) %>
|
|
2
|
+
<% merged_data = merge_component_data(html_options,
|
|
3
|
+
"menu-button-part": "content",
|
|
4
|
+
"menu-button-target": "content",
|
|
5
|
+
side: side,
|
|
6
|
+
align: align,
|
|
7
|
+
state: "closed"
|
|
8
|
+
) %>
|
|
9
|
+
<% aria_attrs = { orientation: "vertical" } %>
|
|
10
|
+
<% aria_attrs[:labelledby] = "#{id}-trigger" if id.present? %>
|
|
11
|
+
|
|
12
|
+
<%= content_tag :div,
|
|
13
|
+
id: (id.present? ? "#{id}-content" : nil),
|
|
14
|
+
role: "menu",
|
|
15
|
+
tabindex: "-1",
|
|
16
|
+
aria: aria_attrs,
|
|
17
|
+
class: css_classes.presence,
|
|
18
|
+
data: merged_data,
|
|
19
|
+
hidden: true,
|
|
20
|
+
**html_options do %>
|
|
21
21
|
<%= yield %>
|
|
22
|
-
|
|
23
|
-
<!--
|
|
24
|
-
<div role="separator" aria-orientation="horizontal" class="separator"></div>
|
|
25
|
-
-->
|
|
22
|
+
<% end %>
|
|
@@ -1,44 +1,39 @@
|
|
|
1
|
-
<%# locals: (title: "", subtitle: nil, icon: nil, text_icon: nil, icon_classes: "", submenu: false) %>
|
|
1
|
+
<%# locals: (title: "", subtitle: nil, icon: nil, text_icon: nil, icon_classes: "", submenu: false, id: nil, css_classes: "", **html_options) %>
|
|
2
|
+
<% menu_button_id = id || "menu-button-#{title.to_s.parameterize.presence || "menu"}" %>
|
|
3
|
+
<% merged_data = merge_component_data(html_options,
|
|
4
|
+
component: "menu-button"
|
|
5
|
+
) %>
|
|
2
6
|
|
|
3
|
-
|
|
4
|
-
<li
|
|
7
|
+
<%= content_tag :ul, class: css_classes.presence, data: merged_data, **html_options do %>
|
|
8
|
+
<li data-menu-button-part="item" data-controller="menu-button">
|
|
5
9
|
<button
|
|
10
|
+
type="button"
|
|
11
|
+
id="<%= menu_button_id %>-trigger"
|
|
6
12
|
data-state="closed"
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50
|
|
13
|
-
data-[state=open]:bg-sidebar-accent
|
|
14
|
-
data-[state=open]text-sidebar-accent-foreground
|
|
15
|
-
group-data-[collapsible=icon]:size-8! [&>span:last-child]:truncate
|
|
16
|
-
[&>svg]:size-4 [&>svg]:shrink-0 hover:bg-sidebar-accent
|
|
17
|
-
hover:text-sidebar-accent-foreground h-12 text-sm
|
|
18
|
-
group-data-[collapsible=icon]:p-0!
|
|
19
|
-
", data-action="menu-button#toggle", data-menu-button-target="button" }
|
|
13
|
+
data-menu-button-part="trigger"
|
|
14
|
+
data-menu-button-target="button"
|
|
15
|
+
data-action="menu-button#toggle"
|
|
16
|
+
aria-haspopup="menu"
|
|
17
|
+
aria-expanded="false"
|
|
20
18
|
>
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
</div>
|
|
32
|
-
<% end %>
|
|
33
|
-
<div class="grid flex-1 text-left text-sm leading-tight">
|
|
34
|
-
<span class="truncate font-semibold"><%= title %></span>
|
|
19
|
+
<% if icon.present? %>
|
|
20
|
+
<%= image_tag icon, alt: title.presence || "", class: icon_classes.presence %>
|
|
21
|
+
<% elsif text_icon.present? %>
|
|
22
|
+
<span data-menu-button-part="avatar">
|
|
23
|
+
<span class="<%= icon_classes %>"><%= text_icon %></span>
|
|
24
|
+
</span>
|
|
25
|
+
<% end %>
|
|
26
|
+
|
|
27
|
+
<span data-menu-button-part="text">
|
|
28
|
+
<span data-menu-button-part="title"><%= title %></span>
|
|
35
29
|
<% if subtitle.present? %>
|
|
36
|
-
<span
|
|
30
|
+
<span data-menu-button-part="subtitle"><%= subtitle %></span>
|
|
37
31
|
<% end %>
|
|
38
|
-
</
|
|
32
|
+
</span>
|
|
33
|
+
|
|
39
34
|
<%= builtin_icon_for(:chevron_up_down, class: "ml-auto") if submenu %>
|
|
40
35
|
</button>
|
|
41
36
|
|
|
42
|
-
<%= yield %>
|
|
37
|
+
<%= yield menu_button_id %>
|
|
43
38
|
</li>
|
|
44
|
-
|
|
39
|
+
<% end %>
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
<%# locals: (orientation: :horizontal)
|
|
1
|
+
<%# locals: (orientation: :horizontal, css_classes: "", **html_options) %>
|
|
2
|
+
<% merged_data = merge_component_data(html_options,
|
|
3
|
+
component: :separator,
|
|
4
|
+
orientation: orientation
|
|
5
|
+
) %>
|
|
2
6
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
bg-border shrink-0 data-[orientation=horizontal]:h-px
|
|
8
|
-
data-[orientation=horizontal]:w-full data-[orientation=vertical]:w-px mx-2
|
|
9
|
-
data-[orientation=vertical]:h-4
|
|
10
|
-
"
|
|
11
|
-
></div>
|
|
7
|
+
<%= tag.div role: "none",
|
|
8
|
+
class: css_classes.presence,
|
|
9
|
+
data: merged_data,
|
|
10
|
+
**html_options %>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<%# locals: (id: nil, state: :collapsed, collapsible: :offcanvas, variant: :inset, side: :left, css_classes: "", **html_options) %>
|
|
2
2
|
<% stable_id = id || "sidebar-#{side}"
|
|
3
3
|
|
|
4
|
-
merged_data = (html_options
|
|
4
|
+
merged_data = merge_component_data(html_options,
|
|
5
5
|
sidebar_part: :root,
|
|
6
6
|
sidebar_target: "sidebar",
|
|
7
7
|
state: state,
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
<%# locals: (collection:, columns:, caption: nil, variant: nil, table_variant: nil, empty_message: "No data available", row_id: nil, html_options: {}) %>
|
|
1
|
+
<%# locals: (collection:, columns:, caption: nil, variant: nil, table_variant: nil, empty_message: "No data available", row_id: nil, html_options: {}, **extra_html_options) %>
|
|
2
|
+
<%# html_options: {} is the legacy call style; bare keyword passthrough is the standard one %>
|
|
3
|
+
<% html_options = html_options.merge(extra_html_options) %>
|
|
2
4
|
<%= render "components/table", variant: variant, table_variant: table_variant, **html_options do %>
|
|
3
5
|
<% if caption.present? %>
|
|
4
6
|
<%= render "components/table/caption" do %><%= caption %><% end %>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<%# locals: (css_classes: "", container: true, variant: nil, table_variant: nil, **html_options) %>
|
|
2
2
|
<%
|
|
3
3
|
# Table data attributes - merge user data with component defaults
|
|
4
|
-
table_data = (html_options
|
|
4
|
+
table_data = merge_component_data(html_options, component: :table)
|
|
5
5
|
table_data[:variant] = table_variant if table_variant
|
|
6
6
|
|
|
7
7
|
# Container data attributes
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
<%# locals: (variant: :default, title: nil, description: nil, icon: nil, duration: 5000, dismissible: true, content: nil, css_classes: "", **html_options) %>
|
|
2
2
|
<%
|
|
3
|
+
# :destructive is the library-wide danger variant; toast renders it
|
|
4
|
+
# with its existing error styling
|
|
5
|
+
variant = variant.to_s.to_sym
|
|
6
|
+
variant = :error if variant == :destructive
|
|
7
|
+
|
|
3
8
|
# Auto-select icon based on variant if not provided
|
|
4
9
|
default_icons = {
|
|
5
10
|
default: nil,
|
|
@@ -10,7 +15,7 @@
|
|
|
10
15
|
}
|
|
11
16
|
toast_icon = icon || default_icons[variant]
|
|
12
17
|
%>
|
|
13
|
-
<% merged_data = (html_options
|
|
18
|
+
<% merged_data = merge_component_data(html_options,
|
|
14
19
|
component: :toast,
|
|
15
20
|
controller: "toast",
|
|
16
21
|
variant: variant,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<%# locals: (position: :bottom_right, content: nil, css_classes: "", **html_options) %>
|
|
2
|
-
<% merged_data = (html_options
|
|
2
|
+
<% merged_data = merge_component_data(html_options,
|
|
3
3
|
component: :toaster,
|
|
4
4
|
controller: "toaster",
|
|
5
5
|
position: position.to_s.dasherize,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<%# locals: (text: nil, content: nil, css_classes: "", **html_options) %>
|
|
2
|
-
<% merged_data = (html_options
|
|
2
|
+
<% merged_data = merge_component_data(html_options, alert_part: :description) %>
|
|
3
3
|
|
|
4
4
|
<%= content_tag :div, class: css_classes.presence, data: merged_data, **html_options do %>
|
|
5
5
|
<%= text || content %>
|