shadcn-rails 0.1.0 → 0.2.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.
Files changed (169) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +69 -2
  3. data/README.md +102 -1398
  4. data/__mocks__/@floating-ui/dom.js +67 -0
  5. data/app/assets/javascripts/shadcn/controllers/base_menu_controller.js +266 -0
  6. data/app/assets/javascripts/shadcn/controllers/combobox_controller.js +34 -8
  7. data/app/assets/javascripts/shadcn/controllers/command_controller.js +5 -1
  8. data/app/assets/javascripts/shadcn/controllers/context_menu_controller.js +64 -135
  9. data/app/assets/javascripts/shadcn/controllers/dropdown_controller.js +56 -186
  10. data/app/assets/javascripts/shadcn/controllers/hover_card_controller.js +29 -55
  11. data/app/assets/javascripts/shadcn/controllers/menubar_controller.js +10 -7
  12. data/app/assets/javascripts/shadcn/controllers/navigation_menu_controller.js +10 -6
  13. data/app/assets/javascripts/shadcn/controllers/popover_controller.js +35 -60
  14. data/app/assets/javascripts/shadcn/controllers/select_controller.js +37 -17
  15. data/app/assets/javascripts/shadcn/controllers/sidebar_controller.js +24 -14
  16. data/app/assets/javascripts/shadcn/controllers/tooltip_controller.js +28 -59
  17. data/app/assets/javascripts/shadcn/index.js +9 -1
  18. data/app/assets/javascripts/shadcn/utils/floating.js +179 -0
  19. data/app/assets/stylesheets/shadcn/base.css +32 -0
  20. data/app/assets/stylesheets/shadcn/components.css +12 -0
  21. data/app/components/shadcn/accordion_component.html.erb +8 -0
  22. data/app/components/shadcn/accordion_component.rb +6 -15
  23. data/app/components/shadcn/alert_component.html.erb +6 -0
  24. data/app/components/shadcn/alert_component.rb +0 -18
  25. data/app/components/shadcn/alert_dialog_component.html.erb +12 -0
  26. data/app/components/shadcn/alert_dialog_component.rb +7 -27
  27. data/app/components/shadcn/aspect_ratio_component.html.erb +7 -0
  28. data/app/components/shadcn/aspect_ratio_component.rb +4 -19
  29. data/app/components/shadcn/avatar_component.html.erb +20 -0
  30. data/app/components/shadcn/avatar_component.rb +8 -36
  31. data/app/components/shadcn/badge_component.html.erb +1 -0
  32. data/app/components/shadcn/badge_component.rb +0 -11
  33. data/app/components/shadcn/base_component.rb +15 -2
  34. data/app/components/shadcn/breadcrumb_component.html.erb +5 -0
  35. data/app/components/shadcn/breadcrumb_component.rb +6 -16
  36. data/app/components/shadcn/button_component.html.erb +18 -0
  37. data/app/components/shadcn/button_component.rb +1 -41
  38. data/app/components/shadcn/card_component.html.erb +8 -0
  39. data/app/components/shadcn/card_component.rb +2 -6
  40. data/app/components/shadcn/checkbox_component.html.erb +32 -0
  41. data/app/components/shadcn/checkbox_component.rb +4 -43
  42. data/app/components/shadcn/collapsible_component.html.erb +8 -0
  43. data/app/components/shadcn/collapsible_component.rb +6 -15
  44. data/app/components/shadcn/command_list_component.rb +29 -14
  45. data/app/components/shadcn/context_menu_checkbox_item_component.rb +76 -0
  46. data/app/components/shadcn/context_menu_component.html.erb +11 -0
  47. data/app/components/shadcn/context_menu_component.rb +6 -26
  48. data/app/components/shadcn/context_menu_content_component.rb +37 -14
  49. data/app/components/shadcn/context_menu_item_component.rb +3 -2
  50. data/app/components/shadcn/context_menu_radio_group_component.rb +42 -0
  51. data/app/components/shadcn/context_menu_radio_item_component.rb +76 -0
  52. data/app/components/shadcn/dialog_component.html.erb +14 -0
  53. data/app/components/shadcn/dialog_component.rb +8 -29
  54. data/app/components/shadcn/drawer_component.html.erb +12 -0
  55. data/app/components/shadcn/drawer_component.rb +7 -27
  56. data/app/components/shadcn/dropdown_menu_checkbox_item_component.rb +76 -0
  57. data/app/components/shadcn/dropdown_menu_component.html.erb +14 -0
  58. data/app/components/shadcn/dropdown_menu_component.rb +9 -29
  59. data/app/components/shadcn/dropdown_menu_content_component.rb +45 -16
  60. data/app/components/shadcn/dropdown_menu_radio_group_component.rb +42 -0
  61. data/app/components/shadcn/dropdown_menu_radio_item_component.rb +76 -0
  62. data/app/components/shadcn/field_component.rb +7 -8
  63. data/app/components/shadcn/hover_card_component.html.erb +12 -0
  64. data/app/components/shadcn/hover_card_component.rb +7 -26
  65. data/app/components/shadcn/input_component.html.erb +18 -0
  66. data/app/components/shadcn/input_component.rb +2 -27
  67. data/app/components/shadcn/input_otp_component.rb +3 -3
  68. data/app/components/shadcn/kbd_component.html.erb +1 -0
  69. data/app/components/shadcn/kbd_component.rb +3 -10
  70. data/app/components/shadcn/label_component.html.erb +3 -0
  71. data/app/components/shadcn/label_component.rb +2 -18
  72. data/app/components/shadcn/menubar_component.html.erb +6 -0
  73. data/app/components/shadcn/menubar_component.rb +4 -15
  74. data/app/components/shadcn/menubar_content_component.rb +45 -20
  75. data/app/components/shadcn/menubar_sub_content_component.rb +21 -8
  76. data/app/components/shadcn/native_select_component.html.erb +22 -0
  77. data/app/components/shadcn/native_select_component.rb +9 -39
  78. data/app/components/shadcn/navigation_menu_component.html.erb +6 -0
  79. data/app/components/shadcn/navigation_menu_component.rb +4 -15
  80. data/app/components/shadcn/pagination_component.html.erb +5 -0
  81. data/app/components/shadcn/pagination_component.rb +11 -15
  82. data/app/components/shadcn/popover_component.html.erb +15 -0
  83. data/app/components/shadcn/popover_component.rb +10 -30
  84. data/app/components/shadcn/progress_component.html.erb +13 -0
  85. data/app/components/shadcn/progress_component.rb +6 -26
  86. data/app/components/shadcn/radio_group_component.html.erb +8 -0
  87. data/app/components/shadcn/radio_group_component.rb +12 -26
  88. data/app/components/shadcn/radio_group_item_component.rb +32 -6
  89. data/app/components/shadcn/resizable_panel_group_component.rb +27 -16
  90. data/app/components/shadcn/scroll_area_component.html.erb +7 -0
  91. data/app/components/shadcn/scroll_area_component.rb +4 -16
  92. data/app/components/shadcn/select_component.html.erb +46 -0
  93. data/app/components/shadcn/select_component.rb +29 -86
  94. data/app/components/shadcn/separator_component.html.erb +5 -0
  95. data/app/components/shadcn/separator_component.rb +6 -14
  96. data/app/components/shadcn/sheet_component.html.erb +12 -0
  97. data/app/components/shadcn/sheet_component.rb +7 -27
  98. data/app/components/shadcn/sidebar_component.rb +2 -2
  99. data/app/components/shadcn/skeleton_component.html.erb +1 -0
  100. data/app/components/shadcn/skeleton_component.rb +4 -2
  101. data/app/components/shadcn/slider_component.html.erb +12 -0
  102. data/app/components/shadcn/slider_component.rb +2 -21
  103. data/app/components/shadcn/spinner_component.html.erb +18 -0
  104. data/app/components/shadcn/spinner_component.rb +2 -30
  105. data/app/components/shadcn/switch_component.html.erb +72 -0
  106. data/app/components/shadcn/switch_component.rb +4 -82
  107. data/app/components/shadcn/table_component.html.erb +9 -0
  108. data/app/components/shadcn/table_component.rb +2 -10
  109. data/app/components/shadcn/tabs_component.html.erb +8 -0
  110. data/app/components/shadcn/tabs_component.rb +4 -17
  111. data/app/components/shadcn/textarea_component.html.erb +13 -0
  112. data/app/components/shadcn/textarea_component.rb +6 -22
  113. data/app/components/shadcn/toast_component.html.erb +36 -0
  114. data/app/components/shadcn/toast_component.rb +6 -54
  115. data/app/components/shadcn/toggle_component.html.erb +12 -0
  116. data/app/components/shadcn/toggle_component.rb +6 -21
  117. data/app/components/shadcn/toggle_group_component.html.erb +14 -0
  118. data/app/components/shadcn/toggle_group_component.rb +6 -29
  119. data/app/components/shadcn/tooltip_component.html.erb +20 -0
  120. data/app/components/shadcn/tooltip_component.rb +13 -38
  121. data/lib/generators/shadcn/add/USAGE +24 -0
  122. data/lib/generators/shadcn/add/add_generator.rb +279 -0
  123. data/lib/generators/shadcn/install/USAGE +22 -0
  124. data/lib/generators/shadcn/install/install_generator.rb +8 -3
  125. data/lib/generators/shadcn/install/templates/initializer.rb.tt +7 -27
  126. data/lib/generators/shadcn/install/templates/shadcn.yml.tt +15 -31
  127. data/lib/shadcn/rails/version.rb +1 -1
  128. metadata +54 -42
  129. data/.dockerignore +0 -40
  130. data/CLAUDE.md +0 -463
  131. data/PROGRESS.md +0 -485
  132. data/Rakefile +0 -29
  133. data/__tests__/controllers/__snapshots__/calendar_controller.test.js.snap +0 -13
  134. data/__tests__/controllers/__snapshots__/popover_controller.test.js.snap +0 -46
  135. data/__tests__/controllers/__snapshots__/sheet_controller.test.js.snap +0 -111
  136. data/__tests__/controllers/__snapshots__/tabs_controller.test.js.snap +0 -27
  137. data/__tests__/controllers/accordion_controller.test.js +0 -904
  138. data/__tests__/controllers/calendar_controller.test.js +0 -1370
  139. data/__tests__/controllers/carousel_controller.test.js +0 -912
  140. data/__tests__/controllers/checkbox_controller.test.js +0 -454
  141. data/__tests__/controllers/collapsible_controller.test.js +0 -407
  142. data/__tests__/controllers/combobox_controller.test.js +0 -966
  143. data/__tests__/controllers/context_menu_controller.test.js +0 -627
  144. data/__tests__/controllers/date_picker_controller.test.js +0 -636
  145. data/__tests__/controllers/dialog_controller.test.js +0 -878
  146. data/__tests__/controllers/drawer_controller.test.js +0 -995
  147. data/__tests__/controllers/menubar_controller.test.js +0 -736
  148. data/__tests__/controllers/navigation_menu_controller.test.js +0 -598
  149. data/__tests__/controllers/popover_controller.test.js +0 -1007
  150. data/__tests__/controllers/radio_group_controller.test.js +0 -640
  151. data/__tests__/controllers/resizable_controller.test.js +0 -680
  152. data/__tests__/controllers/select_controller.test.js +0 -674
  153. data/__tests__/controllers/sheet_controller.test.js +0 -986
  154. data/__tests__/controllers/slider_controller.test.js +0 -1036
  155. data/__tests__/controllers/switch_controller.test.js +0 -424
  156. data/__tests__/controllers/tabs_controller.test.js +0 -907
  157. data/__tests__/controllers/toggle_group_controller.test.js +0 -839
  158. data/__tests__/controllers/tooltip_controller.test.js +0 -808
  159. data/__tests__/helpers/stimulus-test-helper.js +0 -203
  160. data/babel.config.cjs +0 -5
  161. data/bin/console +0 -11
  162. data/bin/setup +0 -8
  163. data/jest.config.js +0 -19
  164. data/jest.setup.js +0 -8
  165. data/lib/generators/shadcn/component/component_generator.rb +0 -188
  166. data/lib/generators/shadcn/theme/theme_generator.rb +0 -128
  167. data/package-lock.json +0 -7415
  168. data/package.json +0 -68
  169. data/rollup.config.js +0 -29
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shadcn
4
+ # Dropdown Menu Radio Item component
5
+ # A radio button within a radio group
6
+ class DropdownMenuRadioItemComponent < BaseComponent
7
+ BASE_CLASSES = "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50"
8
+
9
+ renders_one :shortcut, lambda { |**options|
10
+ DropdownMenuShortcutComponent.new(**options)
11
+ }
12
+
13
+ # @param value [String] Value of this radio item
14
+ # @param checked [Boolean] Whether item is selected
15
+ # @param disabled [Boolean] Whether item is disabled
16
+ def initialize(value: nil, checked: false, disabled: false, **options, &block)
17
+ super(**options, &block)
18
+ @value = value
19
+ @checked = checked
20
+ @disabled = disabled
21
+ end
22
+
23
+ def call
24
+ content_tag(:div, item_content, item_attributes)
25
+ end
26
+
27
+ private
28
+
29
+ def item_content
30
+ safe_join([
31
+ radio_indicator,
32
+ content,
33
+ shortcut
34
+ ].compact)
35
+ end
36
+
37
+ def radio_indicator
38
+ content_tag(:span, radio_icon, class: "absolute left-2 flex h-3.5 w-3.5 items-center justify-center")
39
+ end
40
+
41
+ def radio_icon
42
+ return "" unless @checked
43
+
44
+ content_tag(:svg, circle_svg, {
45
+ xmlns: "http://www.w3.org/2000/svg",
46
+ width: "16",
47
+ height: "16",
48
+ viewBox: "0 0 24 24",
49
+ fill: "currentColor",
50
+ stroke: "none",
51
+ class: "h-4 w-4"
52
+ })
53
+ end
54
+
55
+ def circle_svg
56
+ content_tag(:circle, "", cx: "12", cy: "12", r: "6")
57
+ end
58
+
59
+ def item_attributes
60
+ attrs = {
61
+ class: cn(BASE_CLASSES, class_name),
62
+ role: "menuitemradio",
63
+ "aria-checked": @checked.to_s,
64
+ tabindex: @disabled ? nil : "-1",
65
+ "data-disabled": @disabled ? "" : nil,
66
+ "data-state": @checked ? "checked" : "unchecked",
67
+ "data-value": @value,
68
+ "data-shadcn--dropdown-target": "item",
69
+ "data-action": "click->shadcn--dropdown#selectRadio"
70
+ }
71
+ attrs.merge!(html_options)
72
+ attrs.merge!(build_data)
73
+ attrs.compact
74
+ end
75
+ end
76
+ end
@@ -43,10 +43,15 @@ module Shadcn
43
43
  }
44
44
 
45
45
  # Input slot - renders an Input component
46
- renders_one :input, lambda { |**options|
46
+ # @param error [Boolean] Whether to show error styles on the input
47
+ # Note: If using with_error slot, call it BEFORE with_input for automatic error detection,
48
+ # or pass error: true explicitly
49
+ renders_one :input, lambda { |error: nil, **options|
47
50
  options[:id] ||= @input_id
48
51
  options[:name] ||= @name
49
- if @has_error
52
+ # Use explicit error param if provided, otherwise check error slot
53
+ has_error = error.nil? ? error? : error
54
+ if has_error
50
55
  options[:class_name] = cn("border-destructive focus-visible:ring-destructive", options[:class_name])
51
56
  end
52
57
  Shadcn::InputComponent.new(**options)
@@ -67,12 +72,6 @@ module Shadcn
67
72
  super(**options)
68
73
  @name = name
69
74
  @input_id = id || generate_id
70
- @has_error = false
71
- end
72
-
73
- def before_render
74
- # Track if error is present for styling
75
- @has_error = error.present?
76
75
  end
77
76
 
78
77
  def call
@@ -0,0 +1,12 @@
1
+ <div class="<%= hover_card_classes %>"
2
+ data-controller="<%= hover_card_data_attrs[:controller] %>"
3
+ data-shadcn--hover-card-open-delay-value="<%= hover_card_data_attrs[:"shadcn--hover-card-open-delay-value"] %>"
4
+ data-shadcn--hover-card-close-delay-value="<%= hover_card_data_attrs[:"shadcn--hover-card-close-delay-value"] %>"
5
+ <%= tag_attributes %>>
6
+ <% if trigger? %>
7
+ <div data-shadcn--hover-card-target="trigger">
8
+ <%= trigger %>
9
+ </div>
10
+ <% end %>
11
+ <%= card_content if card_content? %>
12
+ </div>
@@ -28,37 +28,18 @@ module Shadcn
28
28
  @close_delay = close_delay
29
29
  end
30
30
 
31
- def call
32
- content_tag(:div, build_card_content, card_attributes)
33
- end
34
-
35
31
  private
36
32
 
37
- def build_card_content
38
- safe_join([
39
- trigger_wrapper,
40
- card_content
41
- ].compact)
42
- end
43
-
44
- def trigger_wrapper
45
- return unless trigger
46
-
47
- content_tag(:div, trigger, {
48
- "data-shadcn--hover-card-target": "trigger"
49
- })
33
+ def hover_card_classes
34
+ cn("relative inline-block", class_name)
50
35
  end
51
36
 
52
- def card_attributes
53
- attrs = {
54
- class: cn("relative inline-block", class_name),
55
- "data-controller": "shadcn--hover-card",
56
- "data-shadcn--hover-card-open-delay-value": @open_delay,
57
- "data-shadcn--hover-card-close-delay-value": @close_delay
37
+ def hover_card_data_attrs
38
+ {
39
+ controller: "shadcn--hover-card",
40
+ "shadcn--hover-card-open-delay-value": @open_delay,
41
+ "shadcn--hover-card-close-delay-value": @close_delay
58
42
  }
59
- attrs.merge!(html_options)
60
- attrs.merge!(build_data)
61
- attrs.compact
62
43
  end
63
44
  end
64
45
  end
@@ -0,0 +1,18 @@
1
+ <input type="<%= @type %>"
2
+ class="<%= input_classes %>"<% if @name %>
3
+ name="<%= ERB::Util.html_escape_once(@name) %>"<% end %><% if @id %>
4
+ id="<%= ERB::Util.html_escape_once(@id) %>"<% end %><% if @value %>
5
+ value="<%= ERB::Util.html_escape_once(@value) %>"<% end %><% if @placeholder %>
6
+ placeholder="<%= ERB::Util.html_escape_once(@placeholder) %>"<% end %><% if @disabled %>
7
+ disabled<% end %><% if @required %>
8
+ required<% end %><% if @readonly %>
9
+ readonly<% end %><% if @autofocus %>
10
+ autofocus<% end %><% if @autocomplete %>
11
+ autocomplete="<%= ERB::Util.html_escape_once(@autocomplete) %>"<% end %><% if @pattern %>
12
+ pattern="<%= ERB::Util.html_escape_once(@pattern) %>"<% end %><% if @min %>
13
+ min="<%= ERB::Util.html_escape_once(@min) %>"<% end %><% if @max %>
14
+ max="<%= ERB::Util.html_escape_once(@max) %>"<% end %><% if @step %>
15
+ step="<%= ERB::Util.html_escape_once(@step) %>"<% end %><% if @minlength %>
16
+ minlength="<%= @minlength %>"<% end %><% if @maxlength %>
17
+ maxlength="<%= @maxlength %>"<% end %>
18
+ <%= tag_attributes %>>
@@ -74,35 +74,10 @@ module Shadcn
74
74
  @maxlength = maxlength
75
75
  end
76
76
 
77
- def call
78
- tag(:input, input_attributes)
79
- end
80
-
81
77
  private
82
78
 
83
- def input_attributes
84
- attrs = {
85
- type: @type,
86
- name: @name,
87
- id: @id,
88
- value: @value,
89
- placeholder: @placeholder,
90
- disabled: @disabled || nil,
91
- required: @required || nil,
92
- readonly: @readonly || nil,
93
- autofocus: @autofocus || nil,
94
- autocomplete: @autocomplete,
95
- pattern: @pattern,
96
- min: @min,
97
- max: @max,
98
- step: @step,
99
- minlength: @minlength,
100
- maxlength: @maxlength,
101
- class: merge_classes(BASE_CLASSES)
102
- }
103
- attrs.merge!(html_options)
104
- attrs.merge!(build_data)
105
- attrs.compact
79
+ def input_classes
80
+ merge_classes(BASE_CLASSES)
106
81
  end
107
82
  end
108
83
  end
@@ -31,7 +31,7 @@ module Shadcn
31
31
  renders_many :groups, "GroupComponent"
32
32
 
33
33
  # Separators between groups
34
- renders_many :separators, "SeparatorComponent"
34
+ renders_many :separators, "OtpSeparatorComponent"
35
35
 
36
36
  # @param length [Integer] Number of OTP digits
37
37
  # @param name [String] Input name for form submission
@@ -169,8 +169,8 @@ module Shadcn
169
169
  end
170
170
  end
171
171
 
172
- # Separator subcomponent
173
- class SeparatorComponent < BaseComponent
172
+ # Separator subcomponent (named OtpSeparatorComponent to avoid conflict with standalone SeparatorComponent)
173
+ class OtpSeparatorComponent < BaseComponent
174
174
  BASE_CLASSES = "flex items-center justify-center px-2"
175
175
 
176
176
  def call
@@ -0,0 +1 @@
1
+ <kbd class="<%= kbd_classes %>" <%= tag_attributes %>><%= content %></kbd>
@@ -18,19 +18,12 @@ module Shadcn
18
18
  # </span>
19
19
  #
20
20
  class KbdComponent < BaseComponent
21
- BASE_CLASSES = "pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground opacity-100"
22
-
23
- def call
24
- tag.kbd(content, **kbd_attributes)
25
- end
21
+ BASE_CLASSES = 'pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground opacity-100'
26
22
 
27
23
  private
28
24
 
29
- def kbd_attributes
30
- attrs = { class: merge_classes(BASE_CLASSES) }
31
- attrs.merge!(html_options)
32
- attrs.merge!(build_data)
33
- attrs.compact
25
+ def kbd_classes
26
+ merge_classes(BASE_CLASSES)
34
27
  end
35
28
  end
36
29
  end
@@ -0,0 +1,3 @@
1
+ <label <% if @for %>for="<%= @for %>"<% end %> class="<%= label_classes %>" <%= tag_attributes %>>
2
+ <%= content %><% if @required %><span class="text-destructive" aria-hidden="true"> *</span><% end %>
3
+ </label>
@@ -24,26 +24,10 @@ module Shadcn
24
24
  @required = required
25
25
  end
26
26
 
27
- def call
28
- label_text = content
29
- label_text = safe_join([label_text, required_indicator]) if @required
30
- tag.label(label_text, **label_attributes)
31
- end
32
-
33
27
  private
34
28
 
35
- def required_indicator
36
- content_tag(:span, " *", class: "text-destructive", "aria-hidden": "true")
37
- end
38
-
39
- def label_attributes
40
- attrs = {
41
- for: @for,
42
- class: merge_classes(BASE_CLASSES)
43
- }
44
- attrs.merge!(html_options)
45
- attrs.merge!(build_data)
46
- attrs.compact
29
+ def label_classes
30
+ cn(BASE_CLASSES, class_name)
47
31
  end
48
32
  end
49
33
  end
@@ -0,0 +1,6 @@
1
+ <div class="<%= menubar_classes %>"
2
+ role="menubar"
3
+ data-controller="shadcn--menubar"
4
+ <%= tag_attributes %>>
5
+ <%= menubar_content %>
6
+ </div>
@@ -32,25 +32,14 @@ module Shadcn
32
32
  MenubarMenuComponent.new(**options)
33
33
  }
34
34
 
35
- def call
36
- content_tag(:div, menubar_content, menubar_attributes)
37
- end
38
-
39
35
  private
40
36
 
41
- def menubar_content
42
- safe_join(menus)
37
+ def menubar_classes
38
+ cn(BASE_CLASSES, class_name)
43
39
  end
44
40
 
45
- def menubar_attributes
46
- attrs = {
47
- class: cn(BASE_CLASSES, class_name),
48
- role: "menubar",
49
- "data-controller": "shadcn--menubar"
50
- }
51
- attrs.merge!(html_options)
52
- attrs.merge!(build_data)
53
- attrs.compact
41
+ def menubar_content
42
+ safe_join(menus)
54
43
  end
55
44
  end
56
45
  end
@@ -6,23 +6,44 @@ module Shadcn
6
6
  class MenubarContentComponent < BaseComponent
7
7
  BASE_CLASSES = "z-50 min-w-[12rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95"
8
8
 
9
- renders_many :items, lambda { |**options, &block|
10
- MenubarItemComponent.new(**options, &block)
11
- }
12
- renders_many :labels, lambda { |**options, &block|
13
- MenubarLabelComponent.new(**options, &block)
14
- }
15
- renders_many :separators, lambda { |**options|
16
- MenubarSeparatorComponent.new(**options)
17
- }
18
- renders_many :checkbox_items, lambda { |**options, &block|
19
- MenubarCheckboxItemComponent.new(**options, &block)
20
- }
21
- renders_many :radio_groups, lambda { |**options, &block|
22
- MenubarRadioGroupComponent.new(**options, &block)
23
- }
24
- renders_many :sub_menus, lambda { |**options, &block|
25
- MenubarSubComponent.new(**options, &block)
9
+ # Use polymorphic slots to preserve the order of items, labels, separators, etc.
10
+ renders_many :menu_items, types: {
11
+ item: {
12
+ renders: lambda { |**options, &block|
13
+ MenubarItemComponent.new(**options, &block)
14
+ },
15
+ as: :item
16
+ },
17
+ label: {
18
+ renders: lambda { |**options, &block|
19
+ MenubarLabelComponent.new(**options, &block)
20
+ },
21
+ as: :label
22
+ },
23
+ separator: {
24
+ renders: lambda { |**options|
25
+ MenubarSeparatorComponent.new(**options)
26
+ },
27
+ as: :separator
28
+ },
29
+ checkbox_item: {
30
+ renders: lambda { |**options, &block|
31
+ MenubarCheckboxItemComponent.new(**options, &block)
32
+ },
33
+ as: :checkbox_item
34
+ },
35
+ radio_group: {
36
+ renders: lambda { |**options, &block|
37
+ MenubarRadioGroupComponent.new(**options, &block)
38
+ },
39
+ as: :radio_group
40
+ },
41
+ sub_menu: {
42
+ renders: lambda { |**options, &block|
43
+ MenubarSubComponent.new(**options, &block)
44
+ },
45
+ as: :sub_menu
46
+ }
26
47
  }
27
48
 
28
49
  # @param align [Symbol] Content alignment (:start, :center, :end)
@@ -40,10 +61,14 @@ module Shadcn
40
61
  private
41
62
 
42
63
  def menu_content
43
- if items.any? || labels.any? || separators.any? || checkbox_items.any? || radio_groups.any? || sub_menus.any?
44
- safe_join([labels, items, separators, checkbox_items, radio_groups, sub_menus, content].flatten.compact)
64
+ # Trigger slot evaluation first by accessing content
65
+ raw_content = content
66
+ # If polymorphic slots were used, render them in order
67
+ if menu_items.any?
68
+ safe_join(menu_items)
45
69
  else
46
- content
70
+ # Otherwise render the raw block content (for backwards compatibility)
71
+ raw_content
47
72
  end
48
73
  end
49
74
 
@@ -6,11 +6,20 @@ module Shadcn
6
6
  class MenubarSubContentComponent < BaseComponent
7
7
  BASE_CLASSES = "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95"
8
8
 
9
- renders_many :items, lambda { |**options, &block|
10
- MenubarItemComponent.new(**options, &block)
11
- }
12
- renders_many :separators, lambda { |**options|
13
- MenubarSeparatorComponent.new(**options)
9
+ # Use polymorphic slots to preserve the order of items and separators
10
+ renders_many :menu_items, types: {
11
+ item: {
12
+ renders: lambda { |**options, &block|
13
+ MenubarItemComponent.new(**options, &block)
14
+ },
15
+ as: :item
16
+ },
17
+ separator: {
18
+ renders: lambda { |**options|
19
+ MenubarSeparatorComponent.new(**options)
20
+ },
21
+ as: :separator
22
+ }
14
23
  }
15
24
 
16
25
  def call
@@ -20,10 +29,14 @@ module Shadcn
20
29
  private
21
30
 
22
31
  def sub_content
23
- if items.any? || separators.any?
24
- safe_join([items, separators, content].flatten.compact)
32
+ # Trigger slot evaluation first by accessing content
33
+ raw_content = content
34
+ # If polymorphic slots were used, render them in order
35
+ if menu_items.any?
36
+ safe_join(menu_items)
25
37
  else
26
- content
38
+ # Otherwise render the raw block content (for backwards compatibility)
39
+ raw_content
27
40
  end
28
41
  end
29
42
 
@@ -0,0 +1,22 @@
1
+ <div class="<%= WRAPPER_CLASSES %>">
2
+ <select <%= "name=\"#{ERB::Util.html_escape_once(@name)}\"".html_safe if @name %>
3
+ <%= "id=\"#{ERB::Util.html_escape_once(@id)}\"".html_safe if @id %>
4
+ <%= "disabled" if @disabled %>
5
+ <%= "required" if @required %>
6
+ class="<%= select_classes %>"
7
+ <%= tag_attributes %>>
8
+ <%= select_content %>
9
+ </select>
10
+ <svg xmlns="http://www.w3.org/2000/svg"
11
+ width="16"
12
+ height="16"
13
+ viewBox="0 0 24 24"
14
+ fill="none"
15
+ stroke="currentColor"
16
+ stroke-width="2"
17
+ stroke-linecap="round"
18
+ stroke-linejoin="round"
19
+ class="<%= CHEVRON_CLASSES %>">
20
+ <path d="m6 9 6 6 6-6" />
21
+ </svg>
22
+ </div>
@@ -57,48 +57,18 @@ module Shadcn
57
57
  @required = required
58
58
  end
59
59
 
60
- def call
61
- tag.div(class: WRAPPER_CLASSES) do
62
- safe_join([
63
- tag.select(**select_attributes) do
64
- if optgroups.any?
65
- safe_join(optgroups)
66
- else
67
- safe_join(options)
68
- end
69
- end,
70
- chevron_icon
71
- ])
72
- end
73
- end
60
+ private
74
61
 
75
- def chevron_icon
76
- tag.svg(
77
- xmlns: "http://www.w3.org/2000/svg",
78
- width: "16",
79
- height: "16",
80
- viewBox: "0 0 24 24",
81
- fill: "none",
82
- stroke: "currentColor",
83
- stroke_width: "2",
84
- stroke_linecap: "round",
85
- stroke_linejoin: "round",
86
- class: CHEVRON_CLASSES
87
- ) do
88
- tag.path(d: "m6 9 6 6 6-6")
89
- end
62
+ def select_classes
63
+ merge_classes(SELECT_CLASSES)
90
64
  end
91
65
 
92
- private
93
-
94
- def select_attributes
95
- {
96
- name: @name,
97
- id: @id,
98
- disabled: @disabled || nil,
99
- required: @required || nil,
100
- class: merge_classes(SELECT_CLASSES)
101
- }.merge(html_options).merge(build_data).compact
66
+ def select_content
67
+ if optgroups.any?
68
+ safe_join(optgroups)
69
+ else
70
+ safe_join(options)
71
+ end
102
72
  end
103
73
 
104
74
  # Option subcomponent
@@ -0,0 +1,6 @@
1
+ <nav class="<%= navigation_classes %>"
2
+ data-controller="shadcn--navigation-menu"
3
+ aria-label="Main"
4
+ <%= tag_attributes %>>
5
+ <%= navigation_content %>
6
+ </nav>
@@ -31,12 +31,12 @@ module Shadcn
31
31
  NavigationMenuListComponent.new(**options)
32
32
  }
33
33
 
34
- def call
35
- content_tag(:nav, navigation_content, navigation_attributes)
36
- end
37
-
38
34
  private
39
35
 
36
+ def navigation_classes
37
+ cn(BASE_CLASSES, class_name)
38
+ end
39
+
40
40
  def navigation_content
41
41
  safe_join([list, viewport].compact)
42
42
  end
@@ -61,16 +61,5 @@ module Shadcn
61
61
  hidden: true
62
62
  }
63
63
  end
64
-
65
- def navigation_attributes
66
- attrs = {
67
- class: cn(BASE_CLASSES, class_name),
68
- "data-controller": "shadcn--navigation-menu",
69
- "aria-label": "Main"
70
- }
71
- attrs.merge!(html_options)
72
- attrs.merge!(build_data)
73
- attrs.compact
74
- end
75
64
  end
76
65
  end
@@ -0,0 +1,5 @@
1
+ <% if @should_render %>
2
+ <nav role="navigation" aria-label="pagination" class="<%= pagination_classes %>" <%= tag_attributes %>>
3
+ <%= build_pagination_content %>
4
+ </nav>
5
+ <% end %>
@@ -43,13 +43,12 @@ module Shadcn
43
43
  @window = window
44
44
  end
45
45
 
46
- def call
46
+ def before_render
47
+ # Pre-compute auto-generated content if needed
47
48
  if auto_generate?
48
- return "" if total_pages <= 1
49
-
50
- content_tag(:nav, auto_generated_content, pagination_attributes)
49
+ @should_render = total_pages > 1
51
50
  else
52
- content_tag(:nav, build_pagination_content, pagination_attributes)
51
+ @should_render = true
53
52
  end
54
53
  end
55
54
 
@@ -178,18 +177,15 @@ module Shadcn
178
177
  end
179
178
 
180
179
  def build_pagination_content
181
- pagination_content || ""
180
+ if auto_generate?
181
+ auto_generated_content
182
+ else
183
+ pagination_content || ""
184
+ end
182
185
  end
183
186
 
184
- def pagination_attributes
185
- attrs = {
186
- role: "navigation",
187
- "aria-label": "pagination",
188
- class: merge_classes(BASE_CLASSES)
189
- }
190
- attrs.merge!(html_options)
191
- attrs.merge!(build_data)
192
- attrs.compact
187
+ def pagination_classes
188
+ merge_classes(BASE_CLASSES)
193
189
  end
194
190
  end
195
191
  end
@@ -0,0 +1,15 @@
1
+ <div class="<%= popover_classes %>"
2
+ data-controller="<%= popover_data_attrs[:controller] %>"
3
+ data-shadcn--popover-open-value="<%= popover_data_attrs[:"shadcn--popover-open-value"] %>"
4
+ data-shadcn--popover-side-value="<%= popover_data_attrs[:"shadcn--popover-side-value"] %>"
5
+ data-shadcn--popover-align-value="<%= popover_data_attrs[:"shadcn--popover-align-value"] %>"
6
+ data-shadcn--popover-modal-value="<%= popover_data_attrs[:"shadcn--popover-modal-value"] %>"
7
+ data-action="<%= popover_data_attrs[:action] %>"
8
+ <%= tag_attributes %>>
9
+ <% if trigger? %>
10
+ <div data-shadcn--popover-target="trigger" data-action="click->shadcn--popover#toggle">
11
+ <%= trigger %>
12
+ </div>
13
+ <% end %>
14
+ <%= body if body? %>
15
+ </div>