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
@@ -36,41 +36,21 @@ module Shadcn
36
36
  @modal = modal
37
37
  end
38
38
 
39
- def call
40
- content_tag(:div, popover_structure, popover_attributes)
41
- end
42
-
43
39
  private
44
40
 
45
- def popover_structure
46
- safe_join([
47
- trigger_wrapper,
48
- body
49
- ].compact)
50
- end
51
-
52
- def trigger_wrapper
53
- return unless trigger
54
-
55
- content_tag(:div, trigger, {
56
- "data-shadcn--popover-target": "trigger",
57
- "data-action": "click->shadcn--popover#toggle"
58
- })
41
+ def popover_classes
42
+ cn("relative inline-block", class_name)
59
43
  end
60
44
 
61
- def popover_attributes
62
- attrs = {
63
- class: cn("relative inline-block", class_name),
64
- "data-controller": "shadcn--popover",
65
- "data-shadcn--popover-open-value": @open.to_s,
66
- "data-shadcn--popover-side-value": @side.to_s,
67
- "data-shadcn--popover-align-value": @align.to_s,
68
- "data-shadcn--popover-modal-value": @modal.to_s,
69
- "data-action": "keydown.escape->shadcn--popover#close"
45
+ def popover_data_attrs
46
+ {
47
+ controller: "shadcn--popover",
48
+ "shadcn--popover-open-value": @open.to_s,
49
+ "shadcn--popover-side-value": @side.to_s,
50
+ "shadcn--popover-align-value": @align.to_s,
51
+ "shadcn--popover-modal-value": @modal.to_s,
52
+ action: "keydown.escape->shadcn--popover#close"
70
53
  }
71
- attrs.merge!(html_options)
72
- attrs.merge!(build_data)
73
- attrs.compact
74
54
  end
75
55
  end
76
56
  end
@@ -0,0 +1,13 @@
1
+ <div class="<%= progress_classes %>"
2
+ role="progressbar"
3
+ aria-valuemin="0"
4
+ aria-valuemax="<%= @max %>"<% unless @indeterminate %>
5
+ aria-valuenow="<%= @value %>"<% end %>
6
+ data-state="<%= data_state %>"<% if @value %>
7
+ data-value="<%= @value %>"<% end %>
8
+ data-max="<%= @max %>"
9
+ <%= tag_attributes %>>
10
+ <div class="<%= indicator_classes %>"<% if indicator_style %>
11
+ style="<%= indicator_style %>"<% end %>
12
+ data-state="<%= data_state %>"></div>
13
+ </div>
@@ -27,14 +27,10 @@ module Shadcn
27
27
  @indeterminate = indeterminate
28
28
  end
29
29
 
30
- def call
31
- content_tag(:div, progress_indicator, progress_attributes)
32
- end
33
-
34
30
  private
35
31
 
36
- def progress_indicator
37
- content_tag(:div, "", indicator_attributes)
32
+ def progress_classes
33
+ merge_classes(BASE_CLASSES)
38
34
  end
39
35
 
40
36
  def progress_percentage
@@ -50,28 +46,12 @@ module Shadcn
50
46
  end
51
47
  end
52
48
 
53
- def progress_attributes
54
- attrs = {
55
- class: merge_classes(BASE_CLASSES),
56
- role: "progressbar",
57
- "aria-valuemin": 0,
58
- "aria-valuemax": @max,
59
- "aria-valuenow": @indeterminate ? nil : @value,
60
- "data-state": @indeterminate ? "indeterminate" : "determinate",
61
- "data-value": @value,
62
- "data-max": @max
63
- }
64
- attrs.merge!(html_options)
65
- attrs.merge!(build_data)
66
- attrs.compact
49
+ def indicator_classes
50
+ cn(INDICATOR_CLASSES, @indeterminate ? "animate-progress-indeterminate" : "")
67
51
  end
68
52
 
69
- def indicator_attributes
70
- {
71
- class: cn(INDICATOR_CLASSES, @indeterminate ? "animate-progress-indeterminate" : ""),
72
- style: indicator_style,
73
- "data-state": @indeterminate ? "indeterminate" : "determinate"
74
- }
53
+ def data_state
54
+ @indeterminate ? "indeterminate" : "determinate"
75
55
  end
76
56
  end
77
57
  end
@@ -0,0 +1,8 @@
1
+ <div role="radiogroup"
2
+ class="<%= group_classes %>"
3
+ <%= "aria-required=true" if @required %>
4
+ <%= "aria-disabled=true" if @disabled %>
5
+ <%= tag_attributes %>>
6
+ <%= render_data_items %>
7
+ <%= render_slot_items %>
8
+ </div>
@@ -70,13 +70,20 @@ module Shadcn
70
70
  @orientation = orientation
71
71
  end
72
72
 
73
- def call
74
- content_tag(:div, group_attributes) do
75
- safe_join([render_data_items, render_slot_items].compact)
76
- end
73
+ private
74
+
75
+ def group_classes
76
+ cn(BASE_CLASSES, orientation_classes, class_name)
77
77
  end
78
78
 
79
- private
79
+ def orientation_classes
80
+ case @orientation
81
+ when :horizontal
82
+ "flex flex-row gap-4"
83
+ else
84
+ "grid gap-3"
85
+ end
86
+ end
80
87
 
81
88
  # Render items from the items: data array
82
89
  def render_data_items
@@ -104,26 +111,5 @@ module Shadcn
104
111
  def generate_item_id(value)
105
112
  "#{@name}-#{value}".parameterize
106
113
  end
107
-
108
- def group_attributes
109
- attrs = {
110
- role: "radiogroup",
111
- class: cn(BASE_CLASSES, orientation_classes, class_name),
112
- "aria-required": @required ? "true" : nil,
113
- "aria-disabled": @disabled ? "true" : nil
114
- }
115
- attrs.merge!(html_options)
116
- attrs.merge!(build_data)
117
- attrs.compact
118
- end
119
-
120
- def orientation_classes
121
- case @orientation
122
- when :horizontal
123
- "flex flex-row gap-4"
124
- else
125
- "grid gap-3"
126
- end
127
- end
128
114
  end
129
115
  end
@@ -8,6 +8,9 @@ module Shadcn
8
8
  # @example With label parameter (Tier 2 API)
9
9
  # <%= group.with_item(value: "free", label: "Free") %>
10
10
  #
11
+ # @example With label and description
12
+ # <%= group.with_item(value: "pro", label: "Pro", description: "For professional developers") %>
13
+ #
11
14
  # @example With block content (backward compatible)
12
15
  # <%= group.with_item(value: "free") { "Free" } %>
13
16
  #
@@ -36,10 +39,12 @@ module Shadcn
36
39
  ].join(" ")
37
40
 
38
41
  LABEL_CLASSES = "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
42
+ DESCRIPTION_CLASSES = "text-sm text-muted-foreground"
39
43
 
40
44
  # @param value [String] The value for this radio option
41
45
  # @param id [String, nil] HTML id attribute
42
46
  # @param label [String, nil] Label text (alternative to block content)
47
+ # @param description [String, nil] Description text displayed below the label
43
48
  # @param disabled [Boolean] Whether this option is disabled
44
49
  # @param group_name [String, nil] The name attribute from parent group
45
50
  # @param selected [Boolean] Whether this option is selected
@@ -47,6 +52,7 @@ module Shadcn
47
52
  value:,
48
53
  id: nil,
49
54
  label: nil,
55
+ description: nil,
50
56
  disabled: false,
51
57
  group_name: nil,
52
58
  selected: false,
@@ -57,6 +63,7 @@ module Shadcn
57
63
  @value = value
58
64
  @id = id || "radio-#{value}"
59
65
  @label = label
66
+ @description = description
60
67
  @disabled = disabled
61
68
  @group_name = group_name
62
69
  @selected = selected
@@ -66,12 +73,17 @@ module Shadcn
66
73
  label_text = @label || content.presence
67
74
 
68
75
  if label_text.present?
69
- # Render with integrated label
70
- content_tag(:label, label_wrapper_attributes) do
71
- safe_join([
72
- radio_input,
73
- content_tag(:span, label_text, class: LABEL_CLASSES)
74
- ])
76
+ if @description.present?
77
+ # Render with label and description
78
+ render_with_description(label_text)
79
+ else
80
+ # Render with integrated label only
81
+ content_tag(:label, label_wrapper_attributes) do
82
+ safe_join([
83
+ radio_input,
84
+ content_tag(:span, label_text, class: LABEL_CLASSES)
85
+ ])
86
+ end
75
87
  end
76
88
  else
77
89
  # Render just the radio input (for use with external labels)
@@ -81,6 +93,20 @@ module Shadcn
81
93
 
82
94
  private
83
95
 
96
+ def render_with_description(label_text)
97
+ content_tag(:div, class: "flex items-start space-x-3") do
98
+ safe_join([
99
+ content_tag(:div, class: "mt-0.5") { radio_input },
100
+ content_tag(:div, class: "grid gap-1.5 leading-none") do
101
+ safe_join([
102
+ content_tag(:label, label_text, class: cn(LABEL_CLASSES, "cursor-pointer"), for: @id),
103
+ content_tag(:p, @description, class: DESCRIPTION_CLASSES)
104
+ ])
105
+ end
106
+ ])
107
+ end
108
+ end
109
+
84
110
  def radio_input
85
111
  tag(:input, input_attributes)
86
112
  end
@@ -32,22 +32,30 @@ module Shadcn
32
32
  # <% end %>
33
33
  #
34
34
  class ResizablePanelGroupComponent < BaseComponent
35
- renders_many :panels, lambda { |default_size: nil, min_size: nil, max_size: nil, **options|
36
- ResizablePanelComponent.new(
37
- default_size: default_size,
38
- min_size: min_size,
39
- max_size: max_size,
40
- direction: @direction,
41
- **options
42
- )
43
- }
44
-
45
- renders_many :handles, lambda { |with_handle: false, **options|
46
- ResizableHandleComponent.new(
47
- with_handle: with_handle,
48
- direction: @direction,
49
- **options
50
- )
35
+ # Use polymorphic slots to preserve the order of panels and handles
36
+ renders_many :items, types: {
37
+ panel: {
38
+ renders: lambda { |default_size: nil, min_size: nil, max_size: nil, **options|
39
+ ResizablePanelComponent.new(
40
+ default_size: default_size,
41
+ min_size: min_size,
42
+ max_size: max_size,
43
+ direction: @direction,
44
+ **options
45
+ )
46
+ },
47
+ as: :panel
48
+ },
49
+ handle: {
50
+ renders: lambda { |with_handle: false, **options|
51
+ ResizableHandleComponent.new(
52
+ with_handle: with_handle,
53
+ direction: @direction,
54
+ **options
55
+ )
56
+ },
57
+ as: :handle
58
+ }
51
59
  }
52
60
 
53
61
  DIRECTIONS = {
@@ -70,7 +78,10 @@ module Shadcn
70
78
  private
71
79
 
72
80
  def group_content
81
+ # Trigger slot evaluation first
73
82
  content
83
+ # Render all items in the order they were added
84
+ safe_join(items)
74
85
  end
75
86
 
76
87
  def group_attributes
@@ -0,0 +1,7 @@
1
+ <div class="<%= scroll_classes %>"
2
+ data-controller="shadcn--scroll-area"
3
+ data-shadcn--scroll-area-orientation-value="<%= @orientation %>"
4
+ data-shadcn--scroll-area-type-value="<%= @type %>"
5
+ <%= tag_attributes %>>
6
+ <%= scroll_structure %>
7
+ </div>
@@ -33,12 +33,12 @@ module Shadcn
33
33
  @type = type.to_sym
34
34
  end
35
35
 
36
- def call
37
- content_tag(:div, scroll_structure, scroll_attributes)
38
- end
39
-
40
36
  private
41
37
 
38
+ def scroll_classes
39
+ merge_classes(BASE_CLASSES)
40
+ end
41
+
42
42
  def scroll_structure
43
43
  safe_join([
44
44
  viewport,
@@ -94,17 +94,5 @@ module Shadcn
94
94
 
95
95
  content_tag(:div, "", class: "absolute right-0 bottom-0 h-2.5 w-2.5 bg-transparent")
96
96
  end
97
-
98
- def scroll_attributes
99
- attrs = {
100
- class: merge_classes(BASE_CLASSES),
101
- "data-controller": "shadcn--scroll-area",
102
- "data-shadcn--scroll-area-orientation-value": @orientation.to_s,
103
- "data-shadcn--scroll-area-type-value": @type.to_s
104
- }
105
- attrs.merge!(html_options)
106
- attrs.merge!(build_data)
107
- attrs.compact
108
- end
109
97
  end
110
98
  end
@@ -0,0 +1,46 @@
1
+ <div class="<%= wrapper_classes %>"
2
+ data-controller="shadcn--select"
3
+ data-shadcn--select-value-value="<%= @value %>"
4
+ data-action="keydown.escape->shadcn--select#close"
5
+ <%= tag_attributes %>>
6
+ <input type="hidden"
7
+ <%= "name=\"#{ERB::Util.html_escape_once(@name)}\"".html_safe if @name %>
8
+ <%= "id=\"#{ERB::Util.html_escape_once(@id)}\"".html_safe if @id %>
9
+ <%= "value=\"#{ERB::Util.html_escape_once(@value)}\"".html_safe if @value %>
10
+ <%= "required" if @required %>
11
+ data-shadcn--select-target="input" />
12
+
13
+ <button type="button"
14
+ class="<%= trigger_classes %>"
15
+ role="combobox"
16
+ <%= "disabled" if @disabled %>
17
+ aria-expanded="false"
18
+ aria-haspopup="listbox"
19
+ data-shadcn--select-target="trigger"
20
+ data-action="click->shadcn--select#toggle keydown->shadcn--select#handleKeydown"
21
+ data-placeholder="<%= @placeholder %>">
22
+ <span data-shadcn--select-target="display"><%= display_text %></span>
23
+ <svg xmlns="http://www.w3.org/2000/svg"
24
+ width="16"
25
+ height="16"
26
+ viewBox="0 0 24 24"
27
+ fill="none"
28
+ class="h-4 w-4 opacity-50">
29
+ <path d="m6 9 6 6 6-6"
30
+ stroke="currentColor"
31
+ stroke-width="2"
32
+ stroke-linecap="round"
33
+ stroke-linejoin="round" />
34
+ </svg>
35
+ </button>
36
+
37
+ <div class="<%= CONTENT_CLASSES %>"
38
+ role="listbox"
39
+ data-shadcn--select-target="content"
40
+ data-state="closed"
41
+ hidden>
42
+ <div class="<%= VIEWPORT_CLASSES %>">
43
+ <%= items_content %>
44
+ </div>
45
+ </div>
46
+ </div>
@@ -25,11 +25,20 @@ module Shadcn
25
25
  CONTENT_CLASSES = "absolute left-0 top-full z-50 mt-1 max-h-96 min-w-[var(--radix-select-trigger-width)] w-max overflow-hidden rounded-md border bg-popover 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"
26
26
  VIEWPORT_CLASSES = "p-1"
27
27
 
28
- renders_many :items, lambda { |value:, **options, &block|
29
- SelectItemComponent.new(value: value, **options, &block)
30
- }
31
- renders_many :groups, lambda { |label: nil, **options, &block|
32
- SelectGroupComponent.new(label: label, **options, &block)
28
+ # Use polymorphic slots to preserve the order of items and groups
29
+ renders_many :select_items, types: {
30
+ item: {
31
+ renders: lambda { |value:, **options, &block|
32
+ SelectItemComponent.new(value: value, **options, &block)
33
+ },
34
+ as: :item
35
+ },
36
+ group: {
37
+ renders: lambda { |label: nil, **options, &block|
38
+ SelectGroupComponent.new(label: label, **options, &block)
39
+ },
40
+ as: :group
41
+ }
33
42
  }
34
43
 
35
44
  # @param name [String, nil] Form field name
@@ -56,96 +65,30 @@ module Shadcn
56
65
  @required = required
57
66
  end
58
67
 
59
- def call
60
- content_tag(:div, select_structure, select_attributes)
61
- end
62
-
63
68
  private
64
69
 
65
- def select_structure
66
- safe_join([
67
- hidden_input,
68
- trigger,
69
- content_wrapper
70
- ])
71
- end
72
-
73
- def hidden_input
74
- tag(:input,
75
- type: "hidden",
76
- name: @name,
77
- id: @id,
78
- value: @value,
79
- required: @required || nil,
80
- "data-shadcn--select-target": "input"
81
- )
82
- end
83
-
84
- def trigger
85
- content_tag(:button, trigger_content, trigger_attributes)
86
- end
87
-
88
- def trigger_content
89
- safe_join([
90
- content_tag(:span, @value.presence || @placeholder, "data-shadcn--select-target": "display"),
91
- chevron_icon
92
- ])
70
+ def wrapper_classes
71
+ cn("relative inline-block", class_name)
93
72
  end
94
73
 
95
- def chevron_icon
96
- content_tag(:svg,
97
- content_tag(:path, nil, d: "m6 9 6 6 6-6", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round"),
98
- xmlns: "http://www.w3.org/2000/svg",
99
- width: "16",
100
- height: "16",
101
- viewBox: "0 0 24 24",
102
- fill: "none",
103
- class: "h-4 w-4 opacity-50"
104
- )
74
+ def trigger_classes
75
+ cn(TRIGGER_CLASSES, class_name)
105
76
  end
106
77
 
107
- def trigger_attributes
108
- {
109
- type: "button",
110
- class: cn(TRIGGER_CLASSES, class_name),
111
- role: "combobox",
112
- disabled: @disabled || nil,
113
- "aria-expanded": "false",
114
- "aria-haspopup": "listbox",
115
- "data-shadcn--select-target": "trigger",
116
- "data-action": "click->shadcn--select#toggle keydown->shadcn--select#handleKeydown",
117
- "data-placeholder": @placeholder
118
- }
119
- end
120
-
121
- def content_wrapper
122
- content_tag(:div, viewport, {
123
- class: CONTENT_CLASSES,
124
- role: "listbox",
125
- "data-shadcn--select-target": "content",
126
- "data-state": "closed",
127
- hidden: true
128
- })
129
- end
130
-
131
- def viewport
132
- content_tag(:div, items_content, class: VIEWPORT_CLASSES)
78
+ def display_text
79
+ @value.presence || @placeholder
133
80
  end
134
81
 
135
82
  def items_content
136
- safe_join([items, groups, content].compact.flatten)
137
- end
138
-
139
- def select_attributes
140
- attrs = {
141
- class: cn("relative inline-block", class_name),
142
- "data-controller": "shadcn--select",
143
- "data-shadcn--select-value-value": @value,
144
- "data-action": "keydown.escape->shadcn--select#close"
145
- }
146
- attrs.merge!(html_options.except(:class))
147
- attrs.merge!(build_data)
148
- attrs.compact
83
+ # Trigger slot evaluation first by accessing content
84
+ raw_content = content
85
+ # If polymorphic slots were used, render them in order
86
+ if select_items.any?
87
+ safe_join(select_items)
88
+ else
89
+ # Otherwise render the raw block content (for backwards compatibility)
90
+ raw_content
91
+ end
149
92
  end
150
93
  end
151
94
  end
@@ -0,0 +1,5 @@
1
+ <div class="<%= separator_classes %>"
2
+ role="<%= separator_role %>"<% if aria_orientation %>
3
+ aria-orientation="<%= aria_orientation %>"<% end %>
4
+ data-orientation="<%= @orientation %>"
5
+ <%= tag_attributes %>></div>
@@ -29,26 +29,18 @@ module Shadcn
29
29
  @decorative = decorative
30
30
  end
31
31
 
32
- def call
33
- tag.div(**separator_attributes)
34
- end
35
-
36
32
  private
37
33
 
38
34
  def separator_classes
39
35
  cn(BASE_CLASSES, ORIENTATIONS[@orientation], class_name)
40
36
  end
41
37
 
42
- def separator_attributes
43
- attrs = {
44
- class: separator_classes,
45
- role: @decorative ? "none" : "separator",
46
- "aria-orientation": @decorative ? nil : @orientation.to_s,
47
- "data-orientation": @orientation.to_s
48
- }
49
- attrs.merge!(html_options)
50
- attrs.merge!(build_data)
51
- attrs.compact
38
+ def separator_role
39
+ @decorative ? "none" : "separator"
40
+ end
41
+
42
+ def aria_orientation
43
+ @decorative ? nil : @orientation.to_s
52
44
  end
53
45
  end
54
46
  end
@@ -0,0 +1,12 @@
1
+ <div class="<%= sheet_classes %>"
2
+ data-controller="<%= sheet_data_attrs[:controller] %>"
3
+ data-shadcn--sheet-open-value="<%= sheet_data_attrs[:"shadcn--sheet-open-value"] %>"
4
+ data-shadcn--sheet-side-value="<%= sheet_data_attrs[:"shadcn--sheet-side-value"] %>"
5
+ <%= tag_attributes %>>
6
+ <% if trigger? %>
7
+ <div data-shadcn--sheet-target="trigger" data-action="click->shadcn--sheet#open">
8
+ <%= trigger %>
9
+ </div>
10
+ <% end %>
11
+ <%= body if body? %>
12
+ </div>
@@ -45,38 +45,18 @@ module Shadcn
45
45
  @open = open
46
46
  end
47
47
 
48
- def call
49
- content_tag(:div, sheet_structure, sheet_attributes)
50
- end
51
-
52
48
  private
53
49
 
54
- def sheet_structure
55
- safe_join([
56
- trigger_wrapper,
57
- body
58
- ].compact)
59
- end
60
-
61
- def trigger_wrapper
62
- return unless trigger
63
-
64
- content_tag(:div, trigger, {
65
- "data-shadcn--sheet-target": "trigger",
66
- "data-action": "click->shadcn--sheet#open"
67
- })
50
+ def sheet_classes
51
+ class_name
68
52
  end
69
53
 
70
- def sheet_attributes
71
- attrs = {
72
- class: class_name,
73
- "data-controller": "shadcn--sheet",
74
- "data-shadcn--sheet-open-value": @open.to_s,
75
- "data-shadcn--sheet-side-value": @side.to_s
54
+ def sheet_data_attrs
55
+ {
56
+ controller: "shadcn--sheet",
57
+ "shadcn--sheet-open-value": @open.to_s,
58
+ "shadcn--sheet-side-value": @side.to_s
76
59
  }
77
- attrs.merge!(html_options)
78
- attrs.merge!(build_data)
79
- attrs.compact
80
60
  end
81
61
  end
82
62
  end
@@ -86,12 +86,12 @@ module Shadcn
86
86
  end
87
87
 
88
88
  def call
89
- content_tag(:aside, sidebar_content, sidebar_attributes)
89
+ content_tag(:aside, sidebar_wrapper, sidebar_attributes)
90
90
  end
91
91
 
92
92
  private
93
93
 
94
- def sidebar_content
94
+ def sidebar_wrapper
95
95
  safe_join([
96
96
  sidebar_inner
97
97
  ].compact)
@@ -0,0 +1 @@
1
+ <div class="<%= skeleton_classes %>" <%= tag_attributes %>><%= content %></div>