fernandes-ui 0.1.2 → 0.1.4
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/app/assets/javascripts/ui.esm.js +3 -6
- data/app/assets/javascripts/ui.js +3 -6
- data/app/behaviors/ui/command_dialog_behavior.rb +0 -8
- data/app/behaviors/ui/empty_behavior.rb +6 -6
- data/app/behaviors/ui/input_behavior.rb +1 -1
- data/app/behaviors/ui/input_group_behavior.rb +3 -1
- data/app/behaviors/ui/input_group_button_behavior.rb +7 -6
- data/app/behaviors/ui/menubar_checkbox_item_behavior.rb +1 -1
- data/app/behaviors/ui/menubar_radio_item_behavior.rb +1 -1
- data/app/behaviors/ui/popover_behavior.rb +11 -3
- data/app/behaviors/ui/spinner_behavior.rb +1 -1
- data/app/behaviors/ui/switch_behavior.rb +49 -49
- data/app/behaviors/ui/toggle_group_behavior.rb +2 -2
- data/app/behaviors/ui/toggle_group_item_behavior.rb +7 -3
- data/app/behaviors/ui/tooltip_behavior.rb +12 -2
- data/app/components/ui/base.rb +8 -0
- data/app/components/ui/calendar.rb +51 -0
- data/app/components/ui/carousel_next.rb +2 -2
- data/app/components/ui/carousel_previous.rb +2 -2
- data/app/components/ui/date_picker.rb +1 -1
- data/app/components/ui/date_picker_trigger.rb +1 -1
- data/app/components/ui/dropdown_menu_trigger.rb +3 -2
- data/app/components/ui/input_group_button.rb +9 -2
- data/app/components/ui/navigation_menu_content.rb +1 -1
- data/app/components/ui/navigation_menu_item.rb +1 -1
- data/app/components/ui/navigation_menu_link.rb +1 -1
- data/app/components/ui/navigation_menu_list.rb +1 -1
- data/app/components/ui/navigation_menu_trigger.rb +1 -1
- data/app/components/ui/popover.rb +26 -1
- data/app/components/ui/select.rb +14 -2
- data/app/components/ui/sonner_toaster.rb +1 -1
- data/app/components/ui/table.rb +7 -7
- data/app/components/ui/table_body.rb +3 -3
- data/app/components/ui/table_footer.rb +3 -3
- data/app/components/ui/table_header.rb +3 -3
- data/app/components/ui/table_row.rb +2 -2
- data/app/components/ui/toggle_group.rb +8 -2
- data/app/components/ui/toggle_group_item.rb +4 -3
- data/app/components/ui/tooltip.rb +26 -2
- data/app/helpers/ui/popover_behavior.rb +5 -2
- data/app/javascript/ui/controllers/collapsible_controller.js +2 -0
- data/app/javascript/ui/controllers/dropdown_controller.js +8 -14
- data/app/view_components/ui/calendar_component.rb +62 -0
- data/app/view_components/ui/dropdown_menu_trigger_component.rb +14 -30
- data/app/view_components/ui/input_group_button_component.rb +16 -1
- data/app/view_components/ui/navigation_menu_content_component.rb +1 -1
- data/app/view_components/ui/navigation_menu_item_component.rb +1 -1
- data/app/view_components/ui/navigation_menu_link_component.rb +1 -1
- data/app/view_components/ui/navigation_menu_list_component.rb +1 -1
- data/app/view_components/ui/navigation_menu_trigger_component.rb +1 -1
- data/app/view_components/ui/popover_component.rb +25 -4
- data/app/view_components/ui/popover_trigger_component.rb +5 -0
- data/app/view_components/ui/select_component.rb +13 -2
- data/app/view_components/ui/table_body_component.rb +1 -1
- data/app/view_components/ui/table_component.rb +4 -4
- data/app/view_components/ui/table_footer_component.rb +1 -1
- data/app/view_components/ui/table_header_component.rb +1 -1
- data/app/view_components/ui/table_row_component.rb +2 -2
- data/app/view_components/ui/tooltip_component.rb +33 -2
- data/app/view_components/ui/tooltip_trigger_component.rb +24 -4
- data/app/views/ui/_avatar.html.erb +8 -4
- data/app/views/ui/_button.html.erb +16 -0
- data/app/views/ui/_date_picker.html.erb +4 -4
- data/app/views/ui/_popover.html.erb +30 -4
- data/app/views/ui/_select.html.erb +13 -2
- data/app/views/ui/_skeleton.html.erb +1 -1
- data/app/views/ui/_tooltip.html.erb +28 -2
- data/app/views/ui/dropdown_menu/_trigger.html.erb +3 -1
- data/app/views/ui/input_group/_button.html.erb +14 -3
- data/app/views/ui/popover/_trigger.html.erb +22 -2
- data/app/views/ui/tooltip/_trigger.html.erb +16 -21
- data/lib/ui/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a5e204a6fcf76008dccc34ec8487f8b2bd80ba924a43aeecd59a44802d744ade
|
|
4
|
+
data.tar.gz: f470c5ab397812019513fdbb08d6c77166613f06be3e5f22ec801905b32860c7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dfce0d35ccdb4fcc6a09108aef9eb7e197046a9c2bed89545cc5a979f7d7e870f593ba545f632cdf3021557a726f4c1d7d726a0cf08a31f51a9896c376a35fa8
|
|
7
|
+
data.tar.gz: 22dee7451abe1a42880c98ec161c33e5baf43b264ded6399e8d3c5a24c6f55d9a0bf7355ff684e5c90a4121924babcc6f39dac98a5c48788e00624c413125ca8
|
|
@@ -1703,11 +1703,9 @@ class DropdownController extends Controller {
|
|
|
1703
1703
|
openSubmenuHandler(event) {
|
|
1704
1704
|
const trigger = event.currentTarget;
|
|
1705
1705
|
const submenu = trigger.nextElementSibling;
|
|
1706
|
-
if (document.activeElement && document.activeElement.hasAttribute("role") && document.activeElement.getAttribute("role") === "menuitem") {
|
|
1707
|
-
document.activeElement.blur();
|
|
1708
|
-
}
|
|
1709
1706
|
clearAllTabindexes(this.element);
|
|
1710
1707
|
trigger.setAttribute("tabindex", "0");
|
|
1708
|
+
trigger.focus();
|
|
1711
1709
|
this.lastHoveredItem = trigger;
|
|
1712
1710
|
if (this.closeSubmenuTimeouts.has(trigger)) {
|
|
1713
1711
|
clearTimeout(this.closeSubmenuTimeouts.get(trigger));
|
|
@@ -1740,11 +1738,9 @@ class DropdownController extends Controller {
|
|
|
1740
1738
|
}
|
|
1741
1739
|
trackHoveredItem(event) {
|
|
1742
1740
|
const item = event.currentTarget;
|
|
1743
|
-
if (document.activeElement && document.activeElement.hasAttribute("role") && document.activeElement.getAttribute("role") === "menuitem") {
|
|
1744
|
-
document.activeElement.blur();
|
|
1745
|
-
}
|
|
1746
1741
|
clearAllTabindexes(this.element);
|
|
1747
1742
|
item.setAttribute("tabindex", "0");
|
|
1743
|
+
item.focus();
|
|
1748
1744
|
this.lastHoveredItem = item;
|
|
1749
1745
|
}
|
|
1750
1746
|
toggleCheckbox(event) {
|
|
@@ -2802,6 +2798,7 @@ class CollapsibleController extends Controller {
|
|
|
2802
2798
|
content.dataset.state = state;
|
|
2803
2799
|
if (isOpen) {
|
|
2804
2800
|
content.removeAttribute("hidden");
|
|
2801
|
+
void content.offsetHeight;
|
|
2805
2802
|
content.style.height = `${content.scrollHeight}px`;
|
|
2806
2803
|
} else {
|
|
2807
2804
|
if (animate) {
|
|
@@ -1588,11 +1588,9 @@
|
|
|
1588
1588
|
openSubmenuHandler(event) {
|
|
1589
1589
|
const trigger = event.currentTarget;
|
|
1590
1590
|
const submenu = trigger.nextElementSibling;
|
|
1591
|
-
if (document.activeElement && document.activeElement.hasAttribute("role") && document.activeElement.getAttribute("role") === "menuitem") {
|
|
1592
|
-
document.activeElement.blur();
|
|
1593
|
-
}
|
|
1594
1591
|
clearAllTabindexes(this.element);
|
|
1595
1592
|
trigger.setAttribute("tabindex", "0");
|
|
1593
|
+
trigger.focus();
|
|
1596
1594
|
this.lastHoveredItem = trigger;
|
|
1597
1595
|
if (this.closeSubmenuTimeouts.has(trigger)) {
|
|
1598
1596
|
clearTimeout(this.closeSubmenuTimeouts.get(trigger));
|
|
@@ -1625,11 +1623,9 @@
|
|
|
1625
1623
|
}
|
|
1626
1624
|
trackHoveredItem(event) {
|
|
1627
1625
|
const item = event.currentTarget;
|
|
1628
|
-
if (document.activeElement && document.activeElement.hasAttribute("role") && document.activeElement.getAttribute("role") === "menuitem") {
|
|
1629
|
-
document.activeElement.blur();
|
|
1630
|
-
}
|
|
1631
1626
|
clearAllTabindexes(this.element);
|
|
1632
1627
|
item.setAttribute("tabindex", "0");
|
|
1628
|
+
item.focus();
|
|
1633
1629
|
this.lastHoveredItem = item;
|
|
1634
1630
|
}
|
|
1635
1631
|
toggleCheckbox(event) {
|
|
@@ -2661,6 +2657,7 @@
|
|
|
2661
2657
|
content.dataset.state = state;
|
|
2662
2658
|
if (isOpen) {
|
|
2663
2659
|
content.removeAttribute("hidden");
|
|
2660
|
+
void content.offsetHeight;
|
|
2664
2661
|
content.style.height = `${content.scrollHeight}px`;
|
|
2665
2662
|
} else {
|
|
2666
2663
|
if (animate) {
|
|
@@ -1,20 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
3
|
# UI::CommandDialogBehavior
|
|
5
4
|
|
|
6
|
-
#
|
|
7
|
-
|
|
8
5
|
# @ui_component Command Dialog
|
|
9
6
|
|
|
10
7
|
# @ui_category other
|
|
11
8
|
|
|
12
|
-
#
|
|
13
|
-
|
|
14
9
|
# @ui_anatomy Command Dialog - Root container with state management (required)
|
|
15
10
|
|
|
16
|
-
#
|
|
17
|
-
|
|
18
11
|
# @ui_feature Keyboard navigation
|
|
19
12
|
|
|
20
13
|
# @ui_feature Custom styling with Tailwind classes
|
|
@@ -23,7 +16,6 @@
|
|
|
23
16
|
|
|
24
17
|
# @ui_feature Animation support
|
|
25
18
|
|
|
26
|
-
#
|
|
27
19
|
module UI::CommandDialogBehavior
|
|
28
20
|
def command_dialog_base_classes
|
|
29
21
|
""
|
|
@@ -18,7 +18,7 @@ module UI::EmptyBehavior
|
|
|
18
18
|
def empty_html_attributes
|
|
19
19
|
{
|
|
20
20
|
class: empty_classes,
|
|
21
|
-
data: {
|
|
21
|
+
data: {slot: "empty"}
|
|
22
22
|
}
|
|
23
23
|
end
|
|
24
24
|
|
|
@@ -36,7 +36,7 @@ module UI::EmptyHeaderBehavior
|
|
|
36
36
|
def empty_header_html_attributes
|
|
37
37
|
{
|
|
38
38
|
class: empty_header_classes,
|
|
39
|
-
data: {
|
|
39
|
+
data: {slot: "empty-header"}
|
|
40
40
|
}
|
|
41
41
|
end
|
|
42
42
|
|
|
@@ -54,7 +54,7 @@ module UI::EmptyMediaBehavior
|
|
|
54
54
|
def empty_media_html_attributes
|
|
55
55
|
{
|
|
56
56
|
class: empty_media_classes,
|
|
57
|
-
data: {
|
|
57
|
+
data: {slot: "empty-media"}
|
|
58
58
|
}
|
|
59
59
|
end
|
|
60
60
|
|
|
@@ -86,7 +86,7 @@ module UI::EmptyTitleBehavior
|
|
|
86
86
|
def empty_title_html_attributes
|
|
87
87
|
{
|
|
88
88
|
class: empty_title_classes,
|
|
89
|
-
data: {
|
|
89
|
+
data: {slot: "empty-title"}
|
|
90
90
|
}
|
|
91
91
|
end
|
|
92
92
|
|
|
@@ -104,7 +104,7 @@ module UI::EmptyDescriptionBehavior
|
|
|
104
104
|
def empty_description_html_attributes
|
|
105
105
|
{
|
|
106
106
|
class: empty_description_classes,
|
|
107
|
-
data: {
|
|
107
|
+
data: {slot: "empty-description"}
|
|
108
108
|
}
|
|
109
109
|
end
|
|
110
110
|
|
|
@@ -122,7 +122,7 @@ module UI::EmptyContentBehavior
|
|
|
122
122
|
def empty_content_html_attributes
|
|
123
123
|
{
|
|
124
124
|
class: empty_content_classes,
|
|
125
|
-
data: {
|
|
125
|
+
data: {slot: "empty-content"}
|
|
126
126
|
}
|
|
127
127
|
end
|
|
128
128
|
|
|
@@ -32,8 +32,9 @@ module UI::InputGroupBehavior
|
|
|
32
32
|
private
|
|
33
33
|
|
|
34
34
|
# Base classes - exact match from shadcn/ui
|
|
35
|
+
# Uses CSS variable --radius for customizable border radius (defaults to 0.375rem / rounded-md)
|
|
35
36
|
def input_group_base_classes
|
|
36
|
-
"group/input-group border-input dark:bg-input/30 relative flex w-full items-center rounded-
|
|
37
|
+
"group/input-group border-input dark:bg-input/30 relative flex w-full items-center rounded-[var(--radius,0.375rem)] border shadow-xs transition-[color,box-shadow] outline-none"
|
|
37
38
|
end
|
|
38
39
|
|
|
39
40
|
# Height classes
|
|
@@ -52,6 +53,7 @@ module UI::InputGroupBehavior
|
|
|
52
53
|
end
|
|
53
54
|
|
|
54
55
|
# Focus state classes
|
|
56
|
+
# Uses box-shadow for focus ring which automatically follows border-radius
|
|
55
57
|
def input_group_focus_classes
|
|
56
58
|
"has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot=input-group-control]:focus-visible]:ring-[3px]"
|
|
57
59
|
end
|
|
@@ -25,7 +25,7 @@ module UI::InputGroupButtonBehavior
|
|
|
25
25
|
variant: @variant,
|
|
26
26
|
classes: input_group_button_classes,
|
|
27
27
|
"data-size": @size
|
|
28
|
-
}.
|
|
28
|
+
}.deep_merge(attributes_value).compact
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
# Returns combined CSS classes for the button
|
|
@@ -72,18 +72,19 @@ module UI::InputGroupButtonBehavior
|
|
|
72
72
|
end
|
|
73
73
|
|
|
74
74
|
# Size-specific classes
|
|
75
|
+
# Uses calc(var(--radius, 0.375rem) - 5px) to create slightly smaller rounded corners for nested buttons
|
|
75
76
|
def input_group_button_size_classes
|
|
76
77
|
case @size.to_s
|
|
77
78
|
when "xs"
|
|
78
|
-
"h-6 gap-1 px-2 rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-3.5 has-[>svg]:px-2"
|
|
79
|
+
"h-6 gap-1 px-2 rounded-[calc(var(--radius,0.375rem)-5px)] [&>svg:not([class*='size-'])]:size-3.5 has-[>svg]:px-2"
|
|
79
80
|
when "sm"
|
|
80
|
-
"h-8 px-2.5 gap-1.5 rounded-
|
|
81
|
+
"h-8 px-2.5 gap-1.5 rounded-[calc(var(--radius,0.375rem)-5px)] has-[>svg]:px-2.5"
|
|
81
82
|
when "icon-xs"
|
|
82
|
-
"size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0"
|
|
83
|
+
"size-6 rounded-[calc(var(--radius,0.375rem)-5px)] p-0 has-[>svg]:p-0"
|
|
83
84
|
when "icon-sm"
|
|
84
|
-
"size-8 p-0 has-[>svg]:p-0"
|
|
85
|
+
"size-8 rounded-[calc(var(--radius,0.375rem)-5px)] p-0 has-[>svg]:p-0"
|
|
85
86
|
else
|
|
86
|
-
"h-6 gap-1 px-2 rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-3.5 has-[>svg]:px-2"
|
|
87
|
+
"h-6 gap-1 px-2 rounded-[calc(var(--radius,0.375rem)-5px)] [&>svg:not([class*='size-'])]:size-3.5 has-[>svg]:px-2"
|
|
87
88
|
end
|
|
88
89
|
end
|
|
89
90
|
end
|
|
@@ -39,7 +39,7 @@ module UI::MenubarCheckboxItemBehavior
|
|
|
39
39
|
attributes_value = respond_to?(:attributes, true) ? attributes : @attributes
|
|
40
40
|
(attributes_value&.fetch(:data, {}) || {}).merge({
|
|
41
41
|
"ui--menubar-target": "item",
|
|
42
|
-
action: "click->ui--menubar#
|
|
42
|
+
action: "click->ui--menubar#selectItem mouseenter->ui--menubar#trackHoveredItem",
|
|
43
43
|
state: checked? ? "checked" : "unchecked"
|
|
44
44
|
})
|
|
45
45
|
end
|
|
@@ -39,7 +39,7 @@ module UI::MenubarRadioItemBehavior
|
|
|
39
39
|
attributes_value = respond_to?(:attributes, true) ? attributes : @attributes
|
|
40
40
|
base_data = {
|
|
41
41
|
"ui--menubar-target": "item",
|
|
42
|
-
action: "click->ui--menubar#
|
|
42
|
+
action: "click->ui--menubar#selectItem mouseenter->ui--menubar#trackHoveredItem",
|
|
43
43
|
state: checked? ? "checked" : "unchecked"
|
|
44
44
|
}
|
|
45
45
|
|
|
@@ -25,12 +25,20 @@
|
|
|
25
25
|
#
|
|
26
26
|
module UI::PopoverBehavior
|
|
27
27
|
# Returns HTML attributes for the popover container element
|
|
28
|
+
# When used with asChild, only data attributes are returned (no classes)
|
|
28
29
|
def popover_html_attributes
|
|
29
30
|
attributes_value = respond_to?(:attributes, true) ? attributes : @attributes
|
|
30
|
-
{
|
|
31
|
-
class: popover_classes,
|
|
31
|
+
attrs = {
|
|
32
32
|
data: popover_data_attributes
|
|
33
|
-
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
# Only add container classes if not using asChild
|
|
36
|
+
# When asChild is true, the child component handles its own styling
|
|
37
|
+
unless instance_variable_defined?(:@as_child) && @as_child
|
|
38
|
+
attrs[:class] = popover_classes
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
attrs.merge(attributes_value)
|
|
34
42
|
end
|
|
35
43
|
|
|
36
44
|
# Returns combined CSS classes for the popover
|
|
@@ -28,62 +28,62 @@
|
|
|
28
28
|
# @ui_related toggle
|
|
29
29
|
#
|
|
30
30
|
module UI::SwitchBehavior
|
|
31
|
-
# Returns HTML attributes for the switch button element
|
|
32
|
-
def switch_html_attributes
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
31
|
+
# Returns HTML attributes for the switch button element
|
|
32
|
+
def switch_html_attributes
|
|
33
|
+
{
|
|
34
|
+
class: switch_classes,
|
|
35
|
+
role: "switch",
|
|
36
|
+
type: "button",
|
|
37
|
+
tabindex: @disabled ? -1 : 0,
|
|
38
|
+
"aria-checked": switch_aria_checked,
|
|
39
|
+
"data-state": switch_state,
|
|
40
|
+
"data-slot": "switch",
|
|
41
|
+
data: switch_data_attributes
|
|
42
|
+
}.tap do |attrs|
|
|
43
|
+
if @disabled
|
|
44
|
+
attrs[:disabled] = true
|
|
45
|
+
attrs["aria-disabled"] = "true"
|
|
46
|
+
end
|
|
46
47
|
end
|
|
47
48
|
end
|
|
48
|
-
end
|
|
49
49
|
|
|
50
|
-
# Returns combined CSS classes for the switch
|
|
51
|
-
def switch_classes
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
end
|
|
50
|
+
# Returns combined CSS classes for the switch
|
|
51
|
+
def switch_classes
|
|
52
|
+
classes_value = respond_to?(:classes, true) ? classes : @classes
|
|
53
|
+
TailwindMerge::Merger.new.merge([
|
|
54
|
+
switch_base_classes,
|
|
55
|
+
classes_value
|
|
56
|
+
].compact.join(" "))
|
|
57
|
+
end
|
|
58
58
|
|
|
59
|
-
# Returns CSS classes for the thumb element
|
|
60
|
-
def switch_thumb_classes
|
|
61
|
-
|
|
62
|
-
end
|
|
59
|
+
# Returns CSS classes for the thumb element
|
|
60
|
+
def switch_thumb_classes
|
|
61
|
+
"bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0"
|
|
62
|
+
end
|
|
63
63
|
|
|
64
|
-
private
|
|
64
|
+
private
|
|
65
65
|
|
|
66
|
-
# Base classes applied to all switches
|
|
67
|
-
def switch_base_classes
|
|
68
|
-
|
|
69
|
-
end
|
|
66
|
+
# Base classes applied to all switches
|
|
67
|
+
def switch_base_classes
|
|
68
|
+
"peer data-[state=checked]:bg-primary data-[state=checked]:border-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50"
|
|
69
|
+
end
|
|
70
70
|
|
|
71
|
-
# Returns the switch state (checked or unchecked)
|
|
72
|
-
def switch_state
|
|
73
|
-
|
|
74
|
-
end
|
|
71
|
+
# Returns the switch state (checked or unchecked)
|
|
72
|
+
def switch_state
|
|
73
|
+
@checked ? "checked" : "unchecked"
|
|
74
|
+
end
|
|
75
75
|
|
|
76
|
-
# Returns aria-checked attribute value
|
|
77
|
-
def switch_aria_checked
|
|
78
|
-
|
|
79
|
-
end
|
|
76
|
+
# Returns aria-checked attribute value
|
|
77
|
+
def switch_aria_checked
|
|
78
|
+
@checked.to_s
|
|
79
|
+
end
|
|
80
80
|
|
|
81
|
-
# Returns data attributes for Stimulus controller
|
|
82
|
-
def switch_data_attributes
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
end
|
|
81
|
+
# Returns data attributes for Stimulus controller
|
|
82
|
+
def switch_data_attributes
|
|
83
|
+
{
|
|
84
|
+
controller: "ui--switch",
|
|
85
|
+
ui__switch_checked_value: @checked,
|
|
86
|
+
action: "click->ui--switch#toggle keydown->ui--switch#handleKeydown"
|
|
87
|
+
}
|
|
88
|
+
end
|
|
89
89
|
end
|
|
@@ -11,11 +11,11 @@ module UI::ToggleGroupBehavior
|
|
|
11
11
|
def toggle_group_html_attributes
|
|
12
12
|
attrs = {
|
|
13
13
|
class: toggle_group_classes,
|
|
14
|
-
role: @type == "single" ? "radiogroup" : "group",
|
|
14
|
+
role: (@type == "single") ? "radiogroup" : "group",
|
|
15
15
|
data: {
|
|
16
16
|
controller: "ui--toggle-group",
|
|
17
17
|
"ui--toggle-group-type-value": @type || "single",
|
|
18
|
-
"ui--toggle-group-value-value": @value&.to_json || (@type == "multiple" ? "[]" : "null")
|
|
18
|
+
"ui--toggle-group-value-value": @value&.to_json || ((@type == "multiple") ? "[]" : "null")
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
|
|
@@ -12,10 +12,14 @@ module UI::ToggleGroupItemBehavior
|
|
|
12
12
|
attrs = {
|
|
13
13
|
class: toggle_group_item_classes,
|
|
14
14
|
type: "button",
|
|
15
|
-
role: @group_type == "single" ? "radio" : "button",
|
|
15
|
+
role: (@group_type == "single") ? "radio" : "button",
|
|
16
16
|
disabled: @disabled ? true : nil,
|
|
17
|
-
"aria-pressed": @group_type == "multiple"
|
|
18
|
-
|
|
17
|
+
"aria-pressed": if @group_type == "multiple"
|
|
18
|
+
@pressed ? "true" : "false"
|
|
19
|
+
end,
|
|
20
|
+
"aria-checked": if @group_type == "single"
|
|
21
|
+
@pressed ? "true" : "false"
|
|
22
|
+
end,
|
|
19
23
|
data: {
|
|
20
24
|
"ui--toggle-group-target": "item",
|
|
21
25
|
action: "click->ui--toggle-group#toggle",
|
|
@@ -22,10 +22,20 @@ require "tailwind_merge"
|
|
|
22
22
|
#
|
|
23
23
|
module UI::TooltipBehavior
|
|
24
24
|
# Returns HTML attributes for the tooltip root element
|
|
25
|
+
# When as_child is true, don't include the "contents" class since
|
|
26
|
+
# the parent element will handle its own styling
|
|
25
27
|
def tooltip_html_attributes
|
|
26
|
-
{
|
|
28
|
+
attrs = {
|
|
27
29
|
data: tooltip_data_attributes
|
|
28
|
-
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# Only add "contents" class when not using asChild
|
|
33
|
+
# asChild mode passes data attributes to parent, which handles its own styling
|
|
34
|
+
unless instance_variable_defined?(:@as_child) && @as_child
|
|
35
|
+
attrs[:class] = "contents"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
attrs.compact
|
|
29
39
|
end
|
|
30
40
|
|
|
31
41
|
# Returns data attributes for the tooltip controller
|
|
@@ -138,6 +138,14 @@ class UI::Calendar < Phlex::HTML
|
|
|
138
138
|
end
|
|
139
139
|
|
|
140
140
|
def render_dropdowns
|
|
141
|
+
if use_native_select?
|
|
142
|
+
render_native_dropdowns
|
|
143
|
+
else
|
|
144
|
+
render_ui_select_dropdowns
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def render_native_dropdowns
|
|
141
149
|
div(class: "flex items-center gap-1") do
|
|
142
150
|
select(
|
|
143
151
|
data: {ui__calendar_target: "monthSelect", action: "change->ui--calendar#goToMonth"},
|
|
@@ -160,6 +168,49 @@ class UI::Calendar < Phlex::HTML
|
|
|
160
168
|
end
|
|
161
169
|
end
|
|
162
170
|
|
|
171
|
+
def render_ui_select_dropdowns
|
|
172
|
+
div(class: "flex items-center gap-1 px-8 flex-1") do
|
|
173
|
+
# Month select
|
|
174
|
+
render UI::Select.new(classes: "flex-1", value: (@month.month - 1).to_s) do
|
|
175
|
+
render UI::SelectTrigger.new(classes: "h-9 w-full gap-1 px-2 text-sm font-medium border-0 shadow-none")
|
|
176
|
+
render UI::SelectContent.new(classes: "min-w-[8rem]") do
|
|
177
|
+
Date::MONTHNAMES.compact.each_with_index do |name, i|
|
|
178
|
+
render UI::SelectItem.new(value: i.to_s) { name }
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
input(
|
|
182
|
+
type: "hidden",
|
|
183
|
+
value: (@month.month - 1).to_s,
|
|
184
|
+
data: {
|
|
185
|
+
ui__select_target: "hiddenInput",
|
|
186
|
+
ui__calendar_target: "monthSelect",
|
|
187
|
+
action: "change->ui--calendar#goToMonth"
|
|
188
|
+
}
|
|
189
|
+
)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Year select
|
|
193
|
+
render UI::Select.new(value: @month.year.to_s) do
|
|
194
|
+
render UI::SelectTrigger.new(classes: "min-w-[75px] h-9 w-auto gap-1 px-2 text-sm font-medium border-0 shadow-none")
|
|
195
|
+
render UI::SelectContent.new(classes: "min-w-[5rem] max-h-[200px]") do
|
|
196
|
+
current_year = Date.today.year
|
|
197
|
+
((current_year - @year_range)..(current_year + 10)).each do |y|
|
|
198
|
+
render UI::SelectItem.new(value: y.to_s) { y.to_s }
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
input(
|
|
202
|
+
type: "hidden",
|
|
203
|
+
value: @month.year.to_s,
|
|
204
|
+
data: {
|
|
205
|
+
ui__select_target: "hiddenInput",
|
|
206
|
+
ui__calendar_target: "yearSelect",
|
|
207
|
+
action: "change->ui--calendar#goToYear"
|
|
208
|
+
}
|
|
209
|
+
)
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
163
214
|
def render_table
|
|
164
215
|
table(class: "w-full border-collapse space-y-1", role: "grid") do
|
|
165
216
|
thead { render_weekdays }
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
class UI::CarouselNext <
|
|
3
|
+
class UI::CarouselNext < UI::Base
|
|
4
4
|
def initialize(classes: nil, **attributes)
|
|
5
5
|
@classes = classes
|
|
6
6
|
@attributes = attributes
|
|
@@ -10,7 +10,7 @@ class UI::CarouselNext < Phlex::HTML
|
|
|
10
10
|
extend UI::CarouselNextBehavior
|
|
11
11
|
|
|
12
12
|
render UI::Button.new(**carousel_next_html_attributes, classes: carousel_next_classes, variant: :outline, size: :icon) do
|
|
13
|
-
|
|
13
|
+
lucide_icon("arrow-right", class: "size-4")
|
|
14
14
|
span(class: "sr-only") { "Next slide" }
|
|
15
15
|
end
|
|
16
16
|
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
class UI::CarouselPrevious <
|
|
3
|
+
class UI::CarouselPrevious < UI::Base
|
|
4
4
|
def initialize(classes: nil, **attributes)
|
|
5
5
|
@classes = classes
|
|
6
6
|
@attributes = attributes
|
|
@@ -10,7 +10,7 @@ class UI::CarouselPrevious < Phlex::HTML
|
|
|
10
10
|
extend UI::CarouselPreviousBehavior
|
|
11
11
|
|
|
12
12
|
render UI::Button.new(**carousel_previous_html_attributes, classes: carousel_previous_classes, variant: :outline, size: :icon) do
|
|
13
|
-
|
|
13
|
+
lucide_icon("arrow-left", class: "size-4")
|
|
14
14
|
span(class: "sr-only") { "Previous slide" }
|
|
15
15
|
end
|
|
16
16
|
end
|
|
@@ -116,7 +116,7 @@ class UI::DatePicker < Phlex::HTML
|
|
|
116
116
|
|
|
117
117
|
# Render the trigger button
|
|
118
118
|
def trigger(placeholder: @placeholder, selected: @selected, icon: :chevron, classes: "", **attributes, &block)
|
|
119
|
-
render
|
|
119
|
+
render UI::DatePickerTrigger.new(
|
|
120
120
|
placeholder: placeholder,
|
|
121
121
|
selected: selected,
|
|
122
122
|
icon: icon,
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
# render UI::Trigger.new(placeholder: "Pick a date")
|
|
8
8
|
#
|
|
9
9
|
class UI::DatePickerTrigger < Phlex::HTML
|
|
10
|
-
include DatePickerTriggerBehavior
|
|
10
|
+
include UI::DatePickerTriggerBehavior
|
|
11
11
|
|
|
12
12
|
# @param placeholder [String] Placeholder text when no date selected
|
|
13
13
|
# @param selected [Date, Range, Array] Currently selected date(s) for display
|
|
@@ -31,8 +31,9 @@ class UI::DropdownMenuTrigger < Phlex::HTML
|
|
|
31
31
|
trigger_attrs = dropdown_menu_trigger_html_attributes.deep_merge(@attributes)
|
|
32
32
|
|
|
33
33
|
if @as_child
|
|
34
|
-
#
|
|
35
|
-
|
|
34
|
+
# When as_child is true, only pass functional attributes (data, aria, tabindex, role)
|
|
35
|
+
# The child component handles its own styling (following shadcn pattern)
|
|
36
|
+
yield(trigger_attrs.except(:class)) if block_given?
|
|
36
37
|
else
|
|
37
38
|
# Default: render wrapper div with trigger behavior
|
|
38
39
|
div(**trigger_attrs) do
|
|
@@ -28,11 +28,18 @@ class UI::InputGroupButton < Phlex::HTML
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
def view_template(&block)
|
|
31
|
+
# Merge classes from @attributes[:class] with input_group_button_classes
|
|
32
|
+
# This is needed when as_child patterns pass classes through attributes
|
|
33
|
+
merged_classes = TailwindMerge::Merger.new.merge([
|
|
34
|
+
input_group_button_classes,
|
|
35
|
+
@attributes[:class]
|
|
36
|
+
].compact.join(" "))
|
|
37
|
+
|
|
31
38
|
# Use input_group_button_classes which includes button behavior + input group styling
|
|
32
39
|
button(
|
|
33
40
|
type: @type,
|
|
34
|
-
class:
|
|
35
|
-
**@attributes.except(:type, :variant, :size)
|
|
41
|
+
class: merged_classes,
|
|
42
|
+
**@attributes.except(:type, :variant, :size, :class)
|
|
36
43
|
) do
|
|
37
44
|
yield if block_given?
|
|
38
45
|
end
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
# end
|
|
15
15
|
# end
|
|
16
16
|
class UI::NavigationMenuContent < Phlex::HTML
|
|
17
|
-
include UI::
|
|
17
|
+
include UI::NavigationMenuContentBehavior
|
|
18
18
|
|
|
19
19
|
# @param viewport [Boolean] Whether content should be rendered in viewport (inherited from parent)
|
|
20
20
|
# @param classes [String] Additional CSS classes to merge
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
# render UI::Link.new(href: "/about") { "About" }
|
|
18
18
|
# end
|
|
19
19
|
class UI::NavigationMenuItem < Phlex::HTML
|
|
20
|
-
include UI::
|
|
20
|
+
include UI::NavigationMenuItemBehavior
|
|
21
21
|
|
|
22
22
|
# @param value [String] Optional value for controlled state
|
|
23
23
|
# @param classes [String] Additional CSS classes to merge
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
# link_to "About", about_path, **link_attrs
|
|
21
21
|
# end
|
|
22
22
|
class UI::NavigationMenuLink < Phlex::HTML
|
|
23
|
-
include UI::
|
|
23
|
+
include UI::NavigationMenuLinkBehavior
|
|
24
24
|
include UI::SharedAsChildBehavior
|
|
25
25
|
|
|
26
26
|
# @param href [String] URL for the link (ignored when as_child: true)
|