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.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/ui.esm.js +3 -6
  3. data/app/assets/javascripts/ui.js +3 -6
  4. data/app/behaviors/ui/command_dialog_behavior.rb +0 -8
  5. data/app/behaviors/ui/empty_behavior.rb +6 -6
  6. data/app/behaviors/ui/input_behavior.rb +1 -1
  7. data/app/behaviors/ui/input_group_behavior.rb +3 -1
  8. data/app/behaviors/ui/input_group_button_behavior.rb +7 -6
  9. data/app/behaviors/ui/menubar_checkbox_item_behavior.rb +1 -1
  10. data/app/behaviors/ui/menubar_radio_item_behavior.rb +1 -1
  11. data/app/behaviors/ui/popover_behavior.rb +11 -3
  12. data/app/behaviors/ui/spinner_behavior.rb +1 -1
  13. data/app/behaviors/ui/switch_behavior.rb +49 -49
  14. data/app/behaviors/ui/toggle_group_behavior.rb +2 -2
  15. data/app/behaviors/ui/toggle_group_item_behavior.rb +7 -3
  16. data/app/behaviors/ui/tooltip_behavior.rb +12 -2
  17. data/app/components/ui/base.rb +8 -0
  18. data/app/components/ui/calendar.rb +51 -0
  19. data/app/components/ui/carousel_next.rb +2 -2
  20. data/app/components/ui/carousel_previous.rb +2 -2
  21. data/app/components/ui/date_picker.rb +1 -1
  22. data/app/components/ui/date_picker_trigger.rb +1 -1
  23. data/app/components/ui/dropdown_menu_trigger.rb +3 -2
  24. data/app/components/ui/input_group_button.rb +9 -2
  25. data/app/components/ui/navigation_menu_content.rb +1 -1
  26. data/app/components/ui/navigation_menu_item.rb +1 -1
  27. data/app/components/ui/navigation_menu_link.rb +1 -1
  28. data/app/components/ui/navigation_menu_list.rb +1 -1
  29. data/app/components/ui/navigation_menu_trigger.rb +1 -1
  30. data/app/components/ui/popover.rb +26 -1
  31. data/app/components/ui/select.rb +14 -2
  32. data/app/components/ui/sonner_toaster.rb +1 -1
  33. data/app/components/ui/table.rb +7 -7
  34. data/app/components/ui/table_body.rb +3 -3
  35. data/app/components/ui/table_footer.rb +3 -3
  36. data/app/components/ui/table_header.rb +3 -3
  37. data/app/components/ui/table_row.rb +2 -2
  38. data/app/components/ui/toggle_group.rb +8 -2
  39. data/app/components/ui/toggle_group_item.rb +4 -3
  40. data/app/components/ui/tooltip.rb +26 -2
  41. data/app/helpers/ui/popover_behavior.rb +5 -2
  42. data/app/javascript/ui/controllers/collapsible_controller.js +2 -0
  43. data/app/javascript/ui/controllers/dropdown_controller.js +8 -14
  44. data/app/view_components/ui/calendar_component.rb +62 -0
  45. data/app/view_components/ui/dropdown_menu_trigger_component.rb +14 -30
  46. data/app/view_components/ui/input_group_button_component.rb +16 -1
  47. data/app/view_components/ui/navigation_menu_content_component.rb +1 -1
  48. data/app/view_components/ui/navigation_menu_item_component.rb +1 -1
  49. data/app/view_components/ui/navigation_menu_link_component.rb +1 -1
  50. data/app/view_components/ui/navigation_menu_list_component.rb +1 -1
  51. data/app/view_components/ui/navigation_menu_trigger_component.rb +1 -1
  52. data/app/view_components/ui/popover_component.rb +25 -4
  53. data/app/view_components/ui/popover_trigger_component.rb +5 -0
  54. data/app/view_components/ui/select_component.rb +13 -2
  55. data/app/view_components/ui/table_body_component.rb +1 -1
  56. data/app/view_components/ui/table_component.rb +4 -4
  57. data/app/view_components/ui/table_footer_component.rb +1 -1
  58. data/app/view_components/ui/table_header_component.rb +1 -1
  59. data/app/view_components/ui/table_row_component.rb +2 -2
  60. data/app/view_components/ui/tooltip_component.rb +33 -2
  61. data/app/view_components/ui/tooltip_trigger_component.rb +24 -4
  62. data/app/views/ui/_avatar.html.erb +8 -4
  63. data/app/views/ui/_button.html.erb +16 -0
  64. data/app/views/ui/_date_picker.html.erb +4 -4
  65. data/app/views/ui/_popover.html.erb +30 -4
  66. data/app/views/ui/_select.html.erb +13 -2
  67. data/app/views/ui/_skeleton.html.erb +1 -1
  68. data/app/views/ui/_tooltip.html.erb +28 -2
  69. data/app/views/ui/dropdown_menu/_trigger.html.erb +3 -1
  70. data/app/views/ui/input_group/_button.html.erb +14 -3
  71. data/app/views/ui/popover/_trigger.html.erb +22 -2
  72. data/app/views/ui/tooltip/_trigger.html.erb +16 -21
  73. data/lib/ui/version.rb +1 -1
  74. metadata +2 -1
@@ -5,6 +5,8 @@
5
5
  # Container for popover trigger and content.
6
6
  # Uses PopoverBehavior concern for shared styling logic.
7
7
  #
8
+ # Supports asChild pattern for composition without wrapper elements.
9
+ #
8
10
  # @example Basic usage
9
11
  # <%= render UI::PopoverComponent.new do %>
10
12
  # <%= render UI::TriggerComponent.new do %>
@@ -15,13 +17,19 @@
15
17
  # <% end %>
16
18
  # <% end %>
17
19
  #
18
- # @example With custom placement
19
- # <%= render UI::PopoverComponent.new(placement: "top", offset: 8) do %>
20
- # ...
20
+ # @example With asChild - pass attributes to custom element
21
+ # <%= render UI::PopoverComponent.new(as_child: true) do |popover| %>
22
+ # <%= render UI::InputGroupAddonComponent.new(**popover.popover_attrs) do %>
23
+ # <%= render UI::PopoverTriggerComponent.new(as_child: true) do |trigger| %>
24
+ # <%= render UI::InputGroupButtonComponent.new(**trigger.trigger_attrs) { "Info" } %>
25
+ # <% end %>
26
+ # <%= render UI::PopoverContentComponent.new { "Content" } %>
27
+ # <% end %>
21
28
  # <% end %>
22
29
  class UI::PopoverComponent < ViewComponent::Base
23
30
  include UI::PopoverBehavior
24
31
 
32
+ # @param as_child [Boolean] When true, yields self to block for attribute access
25
33
  # @param placement [String] Placement of the popover (e.g., "bottom", "top-start")
26
34
  # @param offset [Integer] Distance in pixels from the trigger
27
35
  # @param trigger [String] Trigger type ("click" or "hover")
@@ -29,6 +37,7 @@ class UI::PopoverComponent < ViewComponent::Base
29
37
  # @param classes [String] Additional CSS classes to merge
30
38
  # @param attributes [Hash] Additional HTML attributes
31
39
  def initialize(
40
+ as_child: false,
32
41
  placement: "bottom",
33
42
  offset: 4,
34
43
  trigger: "click",
@@ -38,6 +47,7 @@ class UI::PopoverComponent < ViewComponent::Base
38
47
  side_offset: nil,
39
48
  **attributes
40
49
  )
50
+ @as_child = as_child
41
51
  @placement = placement
42
52
  @offset = side_offset || offset
43
53
  @trigger = trigger
@@ -47,9 +57,20 @@ class UI::PopoverComponent < ViewComponent::Base
47
57
  @attributes = attributes
48
58
  end
49
59
 
60
+ # Returns popover attributes for as_child mode
61
+ def popover_attrs
62
+ popover_html_attributes.deep_merge(@attributes)
63
+ end
64
+
50
65
  def call
51
- content_tag :div, **popover_html_attributes do
66
+ if @as_child
67
+ # asChild mode: yield self to block, child accesses popover_attrs
52
68
  content
69
+ else
70
+ # Default: render wrapper div with controller
71
+ content_tag :div, **popover_html_attributes do
72
+ content
73
+ end
53
74
  end
54
75
  end
55
76
  end
@@ -26,6 +26,11 @@ class UI::PopoverTriggerComponent < ViewComponent::Base
26
26
  @attributes = attributes
27
27
  end
28
28
 
29
+ # Returns trigger attributes for as_child mode
30
+ def trigger_attrs
31
+ popover_trigger_html_attributes.deep_merge(@attributes)
32
+ end
33
+
29
34
  def call
30
35
  trigger_attrs = popover_trigger_html_attributes.deep_merge(@attributes)
31
36
 
@@ -18,14 +18,25 @@ class UI::SelectComponent < ViewComponent::Base
18
18
 
19
19
  # @param value [String] Currently selected value
20
20
  # @param classes [String] Additional CSS classes to merge
21
+ # @param as_child [Boolean] If true, renders without wrapper div but preserves controller on inner element
21
22
  # @param attributes [Hash] Additional HTML attributes
22
- def initialize(value: nil, classes: "", **attributes)
23
+ def initialize(value: nil, classes: "", as_child: false, **attributes)
23
24
  @value = value
24
25
  @classes = classes
26
+ @as_child = as_child
25
27
  @attributes = attributes
26
28
  end
27
29
 
28
30
  def call
29
- content_tag :div, content, **select_html_attributes.deep_merge(@attributes)
31
+ attrs = select_html_attributes.deep_merge(@attributes)
32
+ if @as_child
33
+ # When as_child, we still need a wrapper for the Stimulus controller
34
+ # but we use a minimal inline wrapper that doesn't break flex layouts
35
+ # Override class to use 'contents' which makes the element invisible to layout
36
+ attrs[:class] = "contents"
37
+ content_tag :span, content, **attrs
38
+ else
39
+ content_tag :div, content, **attrs
40
+ end
30
41
  end
31
42
  end
@@ -3,7 +3,7 @@
3
3
  class UI::TableBodyComponent < ViewComponent::Base
4
4
  include UI::TableBodyBehavior
5
5
 
6
- renders_many :rows, RowComponent
6
+ renders_many :rows, UI::TableRowComponent
7
7
 
8
8
  # Alias amigável
9
9
  alias_method :row, :with_row
@@ -3,10 +3,10 @@
3
3
  class UI::TableComponent < ViewComponent::Base
4
4
  include UI::TableBehavior
5
5
 
6
- renders_one :table_header, HeaderComponent
7
- renders_one :table_body, BodyComponent
8
- renders_one :table_footer, FooterComponent
9
- renders_one :table_caption, CaptionComponent
6
+ renders_one :table_header, UI::TableHeaderComponent
7
+ renders_one :table_body, UI::TableBodyComponent
8
+ renders_one :table_footer, UI::TableFooterComponent
9
+ renders_one :table_caption, UI::TableCaptionComponent
10
10
 
11
11
  # Aliases amigáveis
12
12
  alias_method :header, :with_table_header
@@ -3,7 +3,7 @@
3
3
  class UI::TableFooterComponent < ViewComponent::Base
4
4
  include UI::TableFooterBehavior
5
5
 
6
- renders_many :rows, RowComponent
6
+ renders_many :rows, UI::TableRowComponent
7
7
 
8
8
  # Alias amigável
9
9
  alias_method :row, :with_row
@@ -3,7 +3,7 @@
3
3
  class UI::TableHeaderComponent < ViewComponent::Base
4
4
  include UI::TableHeaderBehavior
5
5
 
6
- renders_many :rows, RowComponent
6
+ renders_many :rows, UI::TableRowComponent
7
7
 
8
8
  # Alias amigável
9
9
  alias_method :row, :with_row
@@ -3,8 +3,8 @@
3
3
  class UI::TableRowComponent < ViewComponent::Base
4
4
  include UI::TableRowBehavior
5
5
 
6
- renders_many :heads, HeadComponent
7
- renders_many :cells, CellComponent
6
+ renders_many :heads, UI::TableHeadComponent
7
+ renders_many :cells, UI::TableCellComponent
8
8
 
9
9
  # Aliases amigáveis
10
10
  alias_method :head, :with_head
@@ -3,17 +3,48 @@
3
3
  # TooltipComponent - ViewComponent implementation
4
4
  #
5
5
  # Root container for tooltip. Manages tooltip state via Stimulus controller.
6
+ #
7
+ # Supports asChild pattern for composition without wrapper elements.
8
+ #
9
+ # @example Basic usage
10
+ # <%= render UI::TooltipComponent.new do %>
11
+ # <%= render UI::TooltipTriggerComponent.new { "Hover me" } %>
12
+ # <%= render UI::TooltipContentComponent.new { "Tooltip text" } %>
13
+ # <% end %>
14
+ #
15
+ # @example With asChild - pass attributes to custom element
16
+ # <%= render UI::TooltipComponent.new(as_child: true) do |tooltip| %>
17
+ # <%= render UI::InputGroupAddonComponent.new(**tooltip.tooltip_attrs) do %>
18
+ # <%= render UI::TooltipTriggerComponent.new(as_child: true) do |trigger| %>
19
+ # <%= render UI::InputGroupButtonComponent.new(**trigger.trigger_attrs) { "Info" } %>
20
+ # <% end %>
21
+ # <%= render UI::TooltipContentComponent.new { "Content" } %>
22
+ # <% end %>
23
+ # <% end %>
6
24
  class UI::TooltipComponent < ViewComponent::Base
7
25
  include UI::TooltipBehavior
8
26
 
27
+ # @param as_child [Boolean] When true, yields self to block for attribute access
9
28
  # @param attributes [Hash] Additional HTML attributes
10
- def initialize(**attributes)
29
+ def initialize(as_child: false, **attributes)
30
+ @as_child = as_child
11
31
  @attributes = attributes
12
32
  end
13
33
 
34
+ # Returns tooltip attributes for as_child mode
35
+ def tooltip_attrs
36
+ tooltip_html_attributes.deep_merge(@attributes)
37
+ end
38
+
14
39
  def call
15
- content_tag :div, **tooltip_html_attributes.merge(@attributes.except(:data)) do
40
+ if @as_child
41
+ # asChild mode: yield self to block, child accesses tooltip_attrs
16
42
  content
43
+ else
44
+ # Default: render wrapper div with controller
45
+ content_tag :div, **tooltip_html_attributes.merge(@attributes.except(:data)) do
46
+ content
47
+ end
17
48
  end
18
49
  end
19
50
  end
@@ -4,6 +4,18 @@
4
4
  #
5
5
  # The interactive element that shows/hides the tooltip on hover/focus.
6
6
  # Supports asChild pattern for composition with other components.
7
+ #
8
+ # @example Basic usage
9
+ # <%= render UI::TooltipTriggerComponent.new do %>
10
+ # Hover me
11
+ # <% end %>
12
+ #
13
+ # @example As child (yields attributes to block)
14
+ # <%= render UI::TooltipTriggerComponent.new(as_child: true) do |trigger| %>
15
+ # <%= render UI::InputGroupButtonComponent.new(**trigger.trigger_attrs) do %>
16
+ # Info
17
+ # <% end %>
18
+ # <% end %>
7
19
  class UI::TooltipTriggerComponent < ViewComponent::Base
8
20
  include UI::TooltipTriggerBehavior
9
21
 
@@ -14,12 +26,20 @@ class UI::TooltipTriggerComponent < ViewComponent::Base
14
26
  @attributes = attributes
15
27
  end
16
28
 
17
- def call
18
- trigger_attrs = tooltip_trigger_html_attributes.merge(@attributes.except(:data))
29
+ # Returns trigger attributes for as_child mode
30
+ def trigger_attrs
31
+ tooltip_trigger_html_attributes.deep_merge(@attributes)
32
+ end
19
33
 
20
- # Default mode: render as button with proper styling
21
- content_tag :button, **trigger_attrs do
34
+ def call
35
+ if @as_child
36
+ # asChild mode: yield self so caller can access trigger_attrs
22
37
  content
38
+ else
39
+ # Default mode: render as button with proper styling
40
+ content_tag :button, **trigger_attrs do
41
+ content
42
+ end
23
43
  end
24
44
  end
25
45
  end
@@ -1,13 +1,17 @@
1
1
  <%
2
- classes ||= ""
2
+ # Avatar - ERB Partial
3
+ #
4
+ # Displays an image element with a fallback for representing users.
5
+ #
6
+ # @param classes [String] Additional CSS classes to merge
7
+
8
+ @classes = local_assigns.fetch(:classes, "")
3
9
 
4
10
  # Include behavior module
5
11
  extend UI::AvatarBehavior
6
12
 
7
13
  # Get HTML attributes from behavior
8
14
  attrs = avatar_html_attributes
9
- all_classes = attrs[:class]
10
- data_attrs = attrs[:data]
11
- %><span class="<%= all_classes %>" data-controller="<%= data_attrs[:controller] %>" data-slot="<%= data_attrs[:slot] %>">
15
+ %><span class="<%= attrs[:class] %>" data-controller="<%= attrs[:data][:controller] %>" data-slot="<%= attrs[:data][:slot] %>">
12
16
  <%= yield %>
13
17
  </span>
@@ -11,6 +11,9 @@
11
11
  # @param disabled [Boolean] Whether the button is disabled
12
12
  # @param classes [String] Additional CSS classes to merge
13
13
  # @param attributes [Hash] Additional HTML attributes
14
+ #
15
+ # Supports splatting attributes from parent components (e.g., **trigger_attrs)
16
+ # When attributes are splatted, data/class/id are merged from local_assigns
14
17
 
15
18
  @variant = local_assigns.fetch(:variant, "default")
16
19
  @size = local_assigns.fetch(:size, "default")
@@ -20,6 +23,19 @@
20
23
  @attributes = local_assigns.fetch(:attributes, {})
21
24
  content_param = local_assigns.fetch(:content, nil)
22
25
 
26
+ # Support for splatted attributes (e.g., **trigger_attrs)
27
+ # Merge data and class from local_assigns if present (from splatting)
28
+ splatted_data = local_assigns.fetch(:data, {})
29
+ splatted_class = local_assigns.fetch(:class, nil)
30
+
31
+ # Merge splatted attributes into @attributes
32
+ if splatted_data.present?
33
+ @attributes = @attributes.deep_merge(data: splatted_data)
34
+ end
35
+ if splatted_class.present?
36
+ @attributes[:class] = TailwindMerge::Merger.new.merge([@attributes[:class], splatted_class].compact.join(" "))
37
+ end
38
+
23
39
  extend UI::ButtonBehavior
24
40
  base_attrs = button_html_attributes
25
41
 
@@ -61,14 +61,14 @@
61
61
  -%>
62
62
  <%= content_tag :div, all_attributes do %>
63
63
  <% if @label %>
64
- <%= render "ui/label/label", content: @label, for: @label_for, classes: "px-1" %>
64
+ <%= render "ui/label", content: @label, for: @label_for, classes: "px-1" %>
65
65
  <% end %>
66
66
 
67
- <%= render "ui/popover/popover", trigger: "click", placement: "bottom-start", offset: 4 do %>
67
+ <%= render "ui/popover", trigger: "click", placement: "bottom-start", offset: 4 do %>
68
68
  <%= yield %>
69
69
 
70
- <%= render "ui/popover/popover_content", classes: "w-auto overflow-hidden p-0" do %>
71
- <%= render "ui/calendar/calendar",
70
+ <%= render "ui/popover/content", classes: "w-auto overflow-hidden p-0" do %>
71
+ <%= render "ui/calendar",
72
72
  mode: @mode,
73
73
  selected: @selected,
74
74
  locale: @locale,
@@ -1,19 +1,45 @@
1
1
  <%# Popover Component - Container for popover trigger and content %>
2
+ <%#
3
+ Supports asChild pattern for composition without wrapper elements.
4
+
5
+ @example Basic usage
6
+ render "ui/popover" do
7
+ render "ui/popover/trigger" { "Open" }
8
+ render "ui/popover/content" { "Content" }
9
+ end
10
+
11
+ @example With asChild (no wrapper div)
12
+ render "ui/popover", as_child: true do |popover_attrs|
13
+ render "ui/input_group/addon", attributes: popover_attrs do
14
+ render "ui/popover/trigger", as_child: true do |trigger_attrs|
15
+ ...
16
+ end
17
+ render "ui/popover/content" { "Content" }
18
+ end
19
+ end
20
+ %>
2
21
  <%
22
+ @as_child = local_assigns.fetch(:as_child, false)
3
23
  @trigger = local_assigns.fetch(:trigger, "click")
4
24
  @placement = local_assigns.fetch(:placement, "bottom")
5
- @offset = local_assigns.fetch(:offset, 8)
25
+ @offset = local_assigns.fetch(:offset, 4)
6
26
  @hover_delay = local_assigns.fetch(:hover_delay, 0)
7
27
  @align = local_assigns.fetch(:align, nil)
8
28
  @classes = local_assigns.fetch(:classes, "")
9
29
  @attributes = local_assigns.fetch(:attributes, {})
30
+ @data = local_assigns.fetch(:data, {})
10
31
 
11
32
  # Extend behavior for shared logic
12
33
  extend UI::PopoverBehavior
13
34
 
14
- # Get HTML attributes from behavior
35
+ # Get HTML attributes from behavior and deep merge with passed data and attributes
15
36
  all_attributes = popover_html_attributes.deep_merge(@attributes)
37
+ all_attributes[:data] = (all_attributes[:data] || {}).merge(@data) if @data.present?
16
38
  -%>
17
- <%= content_tag :div, all_attributes do %>
18
- <%= yield %>
39
+ <% if @as_child %>
40
+ <%= yield(all_attributes) %>
41
+ <% else %>
42
+ <%= content_tag :div, all_attributes do %>
43
+ <%= yield %>
44
+ <% end %>
19
45
  <% end %>
@@ -4,6 +4,7 @@
4
4
  @attributes = local_assigns.fetch(:attributes, {})
5
5
  @value = local_assigns.fetch(:value, nil)
6
6
  @id = local_assigns.fetch(:id, nil)
7
+ as_child = local_assigns.fetch(:as_child, false)
7
8
 
8
9
  # Extend behavior for shared logic
9
10
  extend ::UI::SelectBehavior
@@ -12,6 +13,16 @@
12
13
  all_attributes = select_html_attributes.deep_merge(@attributes)
13
14
  all_attributes[:id] = @id if @id.present?
14
15
  -%>
15
- <%= content_tag :div, all_attributes do %>
16
- <%= yield %>
16
+ <% if as_child %>
17
+ <%# When as_child, we still need a wrapper for the Stimulus controller
18
+ but we use a minimal inline wrapper that doesn't break flex layouts
19
+ Override class to use 'contents' which makes the element invisible to layout %>
20
+ <% all_attributes[:class] = "contents" %>
21
+ <%= content_tag :span, all_attributes do %>
22
+ <%= yield %>
23
+ <% end %>
24
+ <% else %>
25
+ <%= content_tag :div, all_attributes do %>
26
+ <%= yield %>
27
+ <% end %>
17
28
  <% end %>
@@ -13,7 +13,7 @@ html_class = local_assigns.fetch(:class, "")
13
13
  attributes = local_assigns.except(:class)
14
14
 
15
15
  default_classes = "bg-accent animate-pulse rounded-md"
16
- merged_classes = [default_classes, html_class].compact_blank.join(" ")
16
+ merged_classes = TailwindMerge::Merger.new.merge([default_classes, html_class].compact_blank.join(" "))
17
17
  %>
18
18
 
19
19
  <div data-slot="skeleton" class="<%= merged_classes %>" <%= attributes.map { |k, v| "#{k}=\"#{v}\"" }.join(" ").html_safe %>></div>
@@ -1,5 +1,27 @@
1
1
  <%# Tooltip Component - Root container for tooltip %>
2
+ <%#
3
+ Supports asChild pattern for composition without wrapper elements.
4
+
5
+ @example Basic usage
6
+ render "ui/tooltip" do
7
+ render "ui/tooltip/trigger" { "Hover me" }
8
+ render "ui/tooltip/content" { "Tooltip text" }
9
+ end
10
+
11
+ @example With asChild (no wrapper div)
12
+ render "ui/tooltip", as_child: true do |tooltip_attrs|
13
+ render "ui/input_group/addon", attributes: tooltip_attrs do
14
+ render "ui/tooltip/trigger", as_child: true do |trigger_attrs|
15
+ render "ui/input_group/button", attributes: trigger_attrs do
16
+ "Info"
17
+ end
18
+ end
19
+ render "ui/tooltip/content" { "Content" }
20
+ end
21
+ end
22
+ %>
2
23
  <%
24
+ @as_child = local_assigns.fetch(:as_child, false)
3
25
  @attributes = local_assigns.fetch(:attributes, {})
4
26
 
5
27
  # Extend behavior for shared logic
@@ -8,6 +30,10 @@
8
30
  # Get HTML attributes from behavior
9
31
  all_attributes = tooltip_html_attributes.deep_merge(@attributes)
10
32
  -%>
11
- <%= content_tag :div, all_attributes do %>
12
- <%= yield %>
33
+ <% if @as_child %>
34
+ <%= yield(all_attributes) %>
35
+ <% else %>
36
+ <%= content_tag :div, all_attributes do %>
37
+ <%= yield %>
38
+ <% end %>
13
39
  <% end %>
@@ -12,7 +12,9 @@
12
12
  all_attributes = dropdown_menu_trigger_html_attributes.deep_merge(@attributes)
13
13
  -%>
14
14
  <% if as_child %>
15
- <%= yield(all_attributes) %>
15
+ <%# When as_child is true, only pass functional attributes (data, aria, tabindex, role)
16
+ The child component handles its own styling (following shadcn pattern) %>
17
+ <%= yield(all_attributes.except(:class)) %>
16
18
  <% else %>
17
19
  <%= content_tag :div, all_attributes do %>
18
20
  <%= content || yield %>
@@ -10,9 +10,20 @@
10
10
  # Extend behavior for shared logic
11
11
  extend UI::InputGroupButtonBehavior
12
12
 
13
- # Get HTML attributes from behavior
14
- all_attributes = input_group_button_html_attributes.deep_merge(@attributes)
13
+ # Get base attributes from behavior (type, variant, classes)
14
+ base_attrs = input_group_button_attributes
15
+
16
+ # Extract button-specific params that ui/button accepts as direct arguments
17
+ button_params = {
18
+ type: base_attrs[:type],
19
+ variant: base_attrs[:variant],
20
+ classes: base_attrs[:classes]
21
+ }
22
+
23
+ # Merge additional HTML attributes (data, role, aria, tabindex, etc.) into attributes hash
24
+ html_attrs = @attributes.except(:type, :variant, :classes)
25
+ button_params[:attributes] = html_attrs unless html_attrs.empty?
15
26
  -%>
16
- <%= render "ui/button", **all_attributes do %>
27
+ <%= render "ui/button", **button_params do %>
17
28
  <%= content || yield %>
18
29
  <% end %>
@@ -1,6 +1,22 @@
1
1
  <%# Popover Trigger - Button or element that triggers the popover %>
2
+ <%#
3
+ Supports asChild pattern for composition without wrapper elements.
4
+
5
+ @example Basic usage
6
+ render "ui/popover/trigger" do
7
+ button { "Click me" }
8
+ end
9
+
10
+ @example With asChild
11
+ render "ui/popover/trigger", as_child: true do |trigger_attrs|
12
+ render "ui/input_group/button", attributes: trigger_attrs do
13
+ "Info"
14
+ end
15
+ end
16
+ %>
2
17
  <%
3
18
  content ||= local_assigns.fetch(:content, nil)
19
+ as_child = local_assigns.fetch(:as_child, false)
4
20
  @classes = local_assigns.fetch(:classes, "")
5
21
  @attributes = local_assigns.fetch(:attributes, {})
6
22
 
@@ -10,6 +26,10 @@
10
26
  # Get HTML attributes from behavior
11
27
  all_attributes = popover_trigger_html_attributes.deep_merge(@attributes)
12
28
  -%>
13
- <%= content_tag :div, all_attributes do %>
14
- <%= content || yield %>
29
+ <% if as_child %>
30
+ <%= yield(all_attributes) %>
31
+ <% else %>
32
+ <%= content_tag :div, all_attributes do %>
33
+ <%= content || yield %>
34
+ <% end %>
15
35
  <% end %>
@@ -1,4 +1,19 @@
1
1
  <%# Tooltip Trigger - Interactive element that shows/hides tooltip %>
2
+ <%#
3
+ Supports asChild pattern for composition without wrapper elements.
4
+
5
+ @example Basic usage
6
+ render "ui/tooltip/trigger" do
7
+ "Hover me"
8
+ end
9
+
10
+ @example With asChild
11
+ render "ui/tooltip/trigger", as_child: true do |trigger_attrs|
12
+ render "ui/input_group/button", attributes: trigger_attrs do
13
+ "Info"
14
+ end
15
+ end
16
+ %>
2
17
  <%
3
18
  @as_child = local_assigns.fetch(:as_child, false)
4
19
  @attributes = local_assigns.fetch(:attributes, {})
@@ -10,27 +25,7 @@
10
25
  trigger_attrs = tooltip_trigger_html_attributes.deep_merge(@attributes)
11
26
  -%>
12
27
  <% if @as_child %>
13
- <%# asChild mode: capture block and inject attributes into first element %>
14
- <% captured_content = capture { yield } %>
15
- <%= raw captured_content.sub(/(<[a-z][a-z0-9]*)([\s>])/i) do |match|
16
- tag_start, tag_end = $1, $2
17
-
18
- # Build attributes string manually
19
- attrs_array = trigger_attrs.map do |key, value|
20
- if key == :data && value.is_a?(Hash)
21
- # Handle data attributes
22
- value.map { |k, v| "data-#{k.to_s.dasherize}=\"#{ERB::Util.html_escape(v)}\"" }.join(" ")
23
- elsif value.is_a?(TrueClass)
24
- key.to_s.dasherize
25
- elsif value.is_a?(FalseClass) || value.nil?
26
- nil
27
- else
28
- "#{key.to_s.dasherize}=\"#{ERB::Util.html_escape(value)}\""
29
- end
30
- end.compact.join(" ")
31
-
32
- "#{tag_start} #{attrs_array}#{tag_end}"
33
- end %>
28
+ <%= yield(trigger_attrs) %>
34
29
  <% else %>
35
30
  <%# Default mode: render as button %>
36
31
  <%= content_tag :button, trigger_attrs do %>
data/lib/ui/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module UI
2
- VERSION = "0.1.2"
2
+ VERSION = "0.1.4"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fernandes-ui
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Celso Fernandes
@@ -379,6 +379,7 @@ files:
379
379
  - app/components/ui/avatar_fallback.rb
380
380
  - app/components/ui/avatar_image.rb
381
381
  - app/components/ui/badge.rb
382
+ - app/components/ui/base.rb
382
383
  - app/components/ui/blockquote.rb
383
384
  - app/components/ui/breadcrumb.rb
384
385
  - app/components/ui/breadcrumb_ellipsis.rb