poetry-ui 0.1.4 → 0.1.6

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 (192) hide show
  1. checksums.yaml +4 -4
  2. data/.mcp.json +8 -0
  3. data/.yard-lint.yml +25 -0
  4. data/.yard_coverage_all +1 -0
  5. data/Archspec.rb +22 -0
  6. data/CHANGELOG.md +18 -0
  7. data/app/components/poetry/ui/accordion/component.html.erb +1 -1
  8. data/app/components/poetry/ui/accordion/component.rb +7 -7
  9. data/app/components/poetry/ui/alert/component.html.erb +1 -1
  10. data/app/components/poetry/ui/alert/component.rb +3 -6
  11. data/app/components/poetry/ui/alert_dialog/component.html.erb +2 -2
  12. data/app/components/poetry/ui/alert_dialog/component.rb +9 -3
  13. data/app/components/poetry/ui/aspect_ratio/component.rb +5 -7
  14. data/app/components/poetry/ui/attachment/component.html.erb +1 -1
  15. data/app/components/poetry/ui/attachment/component.rb +5 -5
  16. data/app/components/poetry/ui/autocomplete/component.html.erb +1 -1
  17. data/app/components/poetry/ui/autocomplete/component.rb +8 -7
  18. data/app/components/poetry/ui/avatar/component.rb +9 -7
  19. data/app/components/poetry/ui/badge/component.rb +5 -6
  20. data/app/components/poetry/ui/breadcrumb/component.html.erb +1 -1
  21. data/app/components/poetry/ui/breadcrumb/component.rb +4 -6
  22. data/app/components/poetry/ui/bubble/component.html.erb +1 -1
  23. data/app/components/poetry/ui/bubble/component.rb +4 -6
  24. data/app/components/poetry/ui/button/component.html.erb +1 -1
  25. data/app/components/poetry/ui/button/component.rb +7 -5
  26. data/app/components/poetry/ui/button_group/component.rb +6 -8
  27. data/app/components/poetry/ui/calendar/component.html.erb +1 -1
  28. data/app/components/poetry/ui/calendar/component.rb +24 -27
  29. data/app/components/poetry/ui/card/component.html.erb +1 -1
  30. data/app/components/poetry/ui/card/component.rb +3 -9
  31. data/app/components/poetry/ui/carousel/component.html.erb +1 -1
  32. data/app/components/poetry/ui/carousel/component.rb +4 -5
  33. data/app/components/poetry/ui/checkbox/component.html.erb +1 -1
  34. data/app/components/poetry/ui/checkbox/component.rb +4 -6
  35. data/app/components/poetry/ui/clipboard_text/component.html.erb +4 -4
  36. data/app/components/poetry/ui/clipboard_text/component.rb +11 -12
  37. data/app/components/poetry/ui/code_block/component.html.erb +2 -2
  38. data/app/components/poetry/ui/code_block/component.rb +9 -15
  39. data/app/components/poetry/ui/collapsible/component.html.erb +2 -2
  40. data/app/components/poetry/ui/collapsible/component.rb +5 -8
  41. data/app/components/poetry/ui/combobox/component.html.erb +6 -6
  42. data/app/components/poetry/ui/combobox/component.rb +63 -47
  43. data/app/components/poetry/ui/command/component.html.erb +3 -3
  44. data/app/components/poetry/ui/command/component.rb +36 -26
  45. data/app/components/poetry/ui/command/dialog_component.html.erb +2 -2
  46. data/app/components/poetry/ui/command/dialog_component.rb +5 -9
  47. data/app/components/poetry/ui/context_menu/component.html.erb +2 -2
  48. data/app/components/poetry/ui/context_menu/component.rb +8 -8
  49. data/app/components/poetry/ui/data_table/component.html.erb +3 -3
  50. data/app/components/poetry/ui/data_table/component.rb +10 -15
  51. data/app/components/poetry/ui/date_field/component.html.erb +3 -3
  52. data/app/components/poetry/ui/date_field/component.rb +15 -9
  53. data/app/components/poetry/ui/date_parsing.rb +36 -0
  54. data/app/components/poetry/ui/date_picker/component.html.erb +1 -1
  55. data/app/components/poetry/ui/date_picker/component.rb +8 -33
  56. data/app/components/poetry/ui/date_time_field/component.rb +4 -0
  57. data/app/components/poetry/ui/deferred/component.html.erb +1 -1
  58. data/app/components/poetry/ui/deferred/component.rb +3 -6
  59. data/app/components/poetry/ui/dialog/component.html.erb +2 -2
  60. data/app/components/poetry/ui/dialog/component.rb +3 -2
  61. data/app/components/poetry/ui/drawer/component.html.erb +2 -2
  62. data/app/components/poetry/ui/drawer/component.rb +4 -2
  63. data/app/components/poetry/ui/dropdown_menu/component.html.erb +2 -2
  64. data/app/components/poetry/ui/dropdown_menu/component.rb +7 -8
  65. data/app/components/poetry/ui/empty/component.html.erb +1 -1
  66. data/app/components/poetry/ui/empty/component.rb +1 -7
  67. data/app/components/poetry/ui/family_identity.rb +8 -11
  68. data/app/components/poetry/ui/field/component.html.erb +1 -1
  69. data/app/components/poetry/ui/field/component.rb +8 -10
  70. data/app/components/poetry/ui/field_group/component.rb +1 -9
  71. data/app/components/poetry/ui/field_separator/component.rb +5 -7
  72. data/app/components/poetry/ui/fieldset/component.rb +4 -7
  73. data/app/components/poetry/ui/file_input/component.html.erb +1 -1
  74. data/app/components/poetry/ui/file_input/component.rb +2 -6
  75. data/app/components/poetry/ui/hover_card/component.html.erb +2 -2
  76. data/app/components/poetry/ui/hover_card/component.rb +4 -2
  77. data/app/components/poetry/ui/icon/component.rb +6 -0
  78. data/app/components/poetry/ui/input/component.rb +4 -6
  79. data/app/components/poetry/ui/input_group/component.rb +2 -7
  80. data/app/components/poetry/ui/input_group_field.rb +4 -3
  81. data/app/components/poetry/ui/input_otp/component.html.erb +2 -2
  82. data/app/components/poetry/ui/input_otp/component.rb +9 -4
  83. data/app/components/poetry/ui/item/component.html.erb +1 -1
  84. data/app/components/poetry/ui/item/component.rb +4 -5
  85. data/app/components/poetry/ui/kbd/component.rb +1 -9
  86. data/app/components/poetry/ui/label/component.rb +5 -6
  87. data/app/components/poetry/ui/link/component.rb +6 -6
  88. data/app/components/poetry/ui/marker/component.html.erb +1 -1
  89. data/app/components/poetry/ui/marker/component.rb +4 -5
  90. data/app/components/poetry/ui/menubar/component.html.erb +1 -1
  91. data/app/components/poetry/ui/menubar/component.rb +28 -15
  92. data/app/components/poetry/ui/menus.rb +23 -0
  93. data/app/components/poetry/ui/message/component.html.erb +1 -1
  94. data/app/components/poetry/ui/message/component.rb +2 -6
  95. data/app/components/poetry/ui/message_scroller/component.html.erb +1 -1
  96. data/app/components/poetry/ui/message_scroller/component.rb +7 -8
  97. data/app/components/poetry/ui/metadata_list/component.rb +6 -7
  98. data/app/components/poetry/ui/meter/component.rb +11 -6
  99. data/app/components/poetry/ui/native_select/component.rb +5 -0
  100. data/app/components/poetry/ui/navigation_menu/component.html.erb +1 -1
  101. data/app/components/poetry/ui/navigation_menu/component.rb +10 -6
  102. data/app/components/poetry/ui/number_field/component.html.erb +6 -6
  103. data/app/components/poetry/ui/number_field/component.rb +19 -13
  104. data/app/components/poetry/ui/pagination/component.html.erb +1 -1
  105. data/app/components/poetry/ui/pagination/component.rb +10 -6
  106. data/app/components/poetry/ui/popover/component.html.erb +2 -2
  107. data/app/components/poetry/ui/popover/component.rb +7 -2
  108. data/app/components/poetry/ui/progress/component.rb +12 -6
  109. data/app/components/poetry/ui/questionnaire/component.html.erb +5 -22
  110. data/app/components/poetry/ui/questionnaire/component.rb +81 -29
  111. data/app/components/poetry/ui/radio_group/component.html.erb +1 -1
  112. data/app/components/poetry/ui/radio_group/component.rb +13 -6
  113. data/app/components/poetry/ui/resizable/component.html.erb +1 -1
  114. data/app/components/poetry/ui/resizable/component.rb +9 -4
  115. data/app/components/poetry/ui/scroll_area/component.rb +5 -9
  116. data/app/components/poetry/ui/search_field/component.html.erb +5 -5
  117. data/app/components/poetry/ui/search_field/component.rb +8 -9
  118. data/app/components/poetry/ui/select/component.html.erb +2 -2
  119. data/app/components/poetry/ui/select/component.rb +39 -14
  120. data/app/components/poetry/ui/sensitive_input/component.html.erb +6 -6
  121. data/app/components/poetry/ui/sensitive_input/component.rb +21 -23
  122. data/app/components/poetry/ui/separator/component.rb +5 -6
  123. data/app/components/poetry/ui/sheet/component.html.erb +2 -2
  124. data/app/components/poetry/ui/sheet/component.rb +1 -0
  125. data/app/components/poetry/ui/sidebar/component.html.erb +1 -1
  126. data/app/components/poetry/ui/sidebar/component.rb +11 -4
  127. data/app/components/poetry/ui/skeleton/component.rb +2 -10
  128. data/app/components/poetry/ui/slider/component.html.erb +1 -1
  129. data/app/components/poetry/ui/slider/component.rb +25 -6
  130. data/app/components/poetry/ui/spinner/component.rb +7 -7
  131. data/app/components/poetry/ui/stat/component.html.erb +1 -1
  132. data/app/components/poetry/ui/stat/component.rb +5 -8
  133. data/app/components/poetry/ui/switch/component.html.erb +1 -1
  134. data/app/components/poetry/ui/switch/component.rb +9 -6
  135. data/app/components/poetry/ui/table/component.html.erb +1 -1
  136. data/app/components/poetry/ui/table/component.rb +3 -8
  137. data/app/components/poetry/ui/tabs/component.html.erb +1 -1
  138. data/app/components/poetry/ui/tabs/component.rb +28 -19
  139. data/app/components/poetry/ui/tag_group/component.html.erb +2 -2
  140. data/app/components/poetry/ui/tag_group/component.rb +10 -8
  141. data/app/components/poetry/ui/textarea/component.rb +5 -6
  142. data/app/components/poetry/ui/time_field/component.rb +4 -0
  143. data/app/components/poetry/ui/timeline/component.rb +7 -8
  144. data/app/components/poetry/ui/toast/component.html.erb +1 -1
  145. data/app/components/poetry/ui/toast/component.rb +9 -5
  146. data/app/components/poetry/ui/toast_trigger/component.rb +1 -0
  147. data/app/components/poetry/ui/toaster/component.html.erb +1 -1
  148. data/app/components/poetry/ui/toaster/component.rb +4 -7
  149. data/app/components/poetry/ui/toggle/component.rb +6 -9
  150. data/app/components/poetry/ui/toggle_group/component.html.erb +1 -1
  151. data/app/components/poetry/ui/toggle_group/component.rb +9 -6
  152. data/app/components/poetry/ui/toolbar/component.rb +8 -7
  153. data/app/components/poetry/ui/tooltip/component.html.erb +2 -2
  154. data/app/components/poetry/ui/tooltip/component.rb +5 -2
  155. data/app/components/poetry/ui/tree/component.html.erb +3 -3
  156. data/app/components/poetry/ui/tree/component.rb +23 -12
  157. data/app/components/poetry/ui/typeset/component.rb +4 -9
  158. data/app/controllers/poetry/ui/llms_controller.rb +1 -0
  159. data/app/helpers/poetry/ui/form_builder/type_inference.rb +3 -0
  160. data/app/helpers/poetry/ui/form_builder.rb +37 -55
  161. data/app/helpers/poetry/ui/optimistic_form_builder.rb +1 -0
  162. data/app/helpers/poetry/ui/toast_stream_actions.rb +1 -0
  163. data/config/component_registry.yml +46 -0
  164. data/lib/generators/poetry/add/add_generator.rb +14 -0
  165. data/lib/generators/poetry/agent_rules/agent_rules_generator.rb +2 -0
  166. data/lib/generators/poetry/block/block_generator.rb +2 -0
  167. data/lib/generators/poetry/diff/diff_generator.rb +5 -0
  168. data/lib/generators/poetry/editor/editor_generator.rb +5 -0
  169. data/lib/generators/poetry/install/install_generator.rb +6 -0
  170. data/lib/generators/poetry/pagination/pagination_generator.rb +1 -0
  171. data/lib/generators/poetry/pagination/templates/poetry_pagy_helper.rb +4 -0
  172. data/lib/generators/poetry/skills_section.rb +1 -0
  173. data/lib/poetry/ui/chat.rb +82 -33
  174. data/lib/poetry/ui/code_block_highlighter.rb +4 -0
  175. data/lib/poetry/ui/fidelity.rb +28 -0
  176. data/lib/poetry/ui/recipes.rb +4 -0
  177. data/lib/poetry/ui/reset_floor.rb +15 -2
  178. data/lib/poetry/ui/testing/combobox.rb +12 -0
  179. data/lib/poetry/ui/testing/dialog.rb +2 -0
  180. data/lib/poetry/ui/testing/menu.rb +7 -0
  181. data/lib/poetry/ui/testing/select.rb +7 -0
  182. data/lib/poetry/ui/testing/tester.rb +11 -0
  183. data/lib/poetry/ui/testing.rb +1 -0
  184. data/lib/poetry/ui/themes.rb +3 -0
  185. data/lib/poetry/ui/version.rb +1 -1
  186. data/lib/poetry/ui/webmcp.rb +1 -0
  187. data/lib/poetry/ui.rb +17 -1
  188. data/rubydex_linter/rules/registry_class.rb +44 -0
  189. data/rubydex_linter/rules/style_sidecar.rb +38 -0
  190. metadata +18 -12
  191. data/app/components/poetry/ui/date_time_field/component.html.erb +0 -6
  192. data/app/components/poetry/ui/time_field/component.html.erb +0 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1dc3d057d2a1fd8016bbf07932226f34cc1cb43ae92fab30787e8111790d7737
4
- data.tar.gz: dc55e865b56c2616c1b3bc071aebb6b6ef1d597159e468972be626b45242e65d
3
+ metadata.gz: 83490085dc76d9571592fc20f909ed21c4dc36625df781b284e552396ed39db9
4
+ data.tar.gz: 92251c4128e98d2d1d4ab452cd8723fe722c30ae4cd97d8f1aafb4b1968c8ef4
5
5
  SHA512:
6
- metadata.gz: '011951d31c612220f1b733879608a7c47fba2e713b293b651cd615fe240018000eae5118a391aeb87141c0c20132b9a55fa96a9fb205c8090e5896ca7f7b11a8'
7
- data.tar.gz: 43ffba5aa6ee261bfee9f0b7d6e8283069672f62fc38666c9572a76ce18720a6d9622f84a1db07d155f7e6bf427fe5594ae69b71d15170110442eec1465d57b1
6
+ metadata.gz: 321ecf105f65b599312096b0d77b54ee9bc829ed86ff44f96f71e660bc1de0b64e2432b21567a978fb05e2beab37a31a188c9b8c012ac85b628d39687880e401
7
+ data.tar.gz: 474c0a4c380e214e2b98452105f0859ddaac4385480170a647d1fd556b8a01d25cb40955666bfcadde26c568bed8cefafba746eda7468ab38d9c9f6d70ffdeda
data/.mcp.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "mcpServers": {
3
+ "rubydex": {
4
+ "command": "bundle",
5
+ "args": ["exec", "rdx", "mcp"]
6
+ }
7
+ }
8
+ }
data/.yard-lint.yml ADDED
@@ -0,0 +1,25 @@
1
+ # yard-lint: the documentation tier YARD's own gates miss - a documented
2
+ # method missing a @param, an @example that does not parse, option tags,
3
+ # tag order, invalid types. Runs as rake yard:lint (in the default task).
4
+ # UndocumentedObjects stays off: yard:coverage gates public objects at its
5
+ # recorded floor, and this validator counts the @api private internals the
6
+ # gems hide on purpose (--hide-api is not honoured here). MissingReturn is
7
+ # off until the @param backlog is cleared; revisit then.
8
+ AllValidators:
9
+ YardOptions:
10
+ - --no-private
11
+ Documentation/UndocumentedObjects:
12
+ Enabled: false
13
+ Documentation/MissingReturn:
14
+ Enabled: false
15
+ Tags/InvalidTypes:
16
+ Enabled: true
17
+ # A **options splat passed through to a control is documented as @param
18
+ # options [Hash], not one @option per key; and @example blocks here are
19
+ # ERB as often as Ruby, which this validator cannot parse.
20
+ Documentation/UndocumentedOptions:
21
+ Enabled: false
22
+ Tags/OptionTags:
23
+ Enabled: false
24
+ Tags/ExampleSyntax:
25
+ Enabled: false
@@ -0,0 +1 @@
1
+ 0
data/Archspec.rb ADDED
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The architecture the family enforces by review, as checks (rake arch:check).
4
+ # ui depends on core alone. lib reaches the components only through the
5
+ # registry; the test helpers under lib/poetry/ui/testing drive components
6
+ # by name and may name them; the generators are install tooling nothing
7
+ # in a component depends on.
8
+ root "."
9
+ source "app/**/*.rb", "lib/**/*.rb"
10
+
11
+ component :components, in: "app/components/**/*.rb"
12
+ component :lib, in: "lib/**/*.rb", except: ["lib/generators/**/*", "lib/poetry/ui/testing/**/*"]
13
+ component :testing, in: "lib/poetry/ui/testing/**/*.rb"
14
+ component :generators, in: "lib/generators/**/*.rb"
15
+
16
+ lib.cannot_use :components, because: "lib reaches the components through the registry"
17
+ components.cannot_use :generators, because: "a component never depends on the install tooling"
18
+ lib.cannot_reference_constants "Poetry::Charts", "Poetry::Agent", "ApplicationController",
19
+ because: "ui depends on core alone and never names the host"
20
+ components.cannot_reference_constants "Poetry::Charts", "Poetry::Agent", "ApplicationController",
21
+ because: "ui depends on core alone and never names the host"
22
+ no_cycles among: %i[lib components]
data/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.6] - 2026-09-20
4
+
5
+ ### Changed
6
+
7
+ - The gem is developed in the Poetry monorepo, roboruby/poetry, under `gems/poetry-ui`; the gemspec's source, changelog and issue links point there, and its file list no longer needs git on the machine (a path gem inside a slim image lists the same files).
8
+ - The gem describes Poetry as the AI-native frontend framework for Rails.
9
+
10
+ ## [0.1.5] - 2026-09-18
11
+
12
+ ### Changed
13
+
14
+ - The registry carries the gem's internal namespaces (`internals`), so `poetry check` can warn a host that names one.
15
+ - 36 methods the reference already hid with `@api private` are Ruby-private now: each was called only by its own class or template, so the runtime enforces what the tag only stated. A host that reached one gets a NoMethodError instead of an internal that may change without notice. The tag remains on the internals the family shares between its gems and on whole internal classes.
16
+ - Every class, module and method carries a one-sentence description, private helpers included: `rake yard:coverage:all` measures the whole tree (a tag-only docstring counts as blank) and the committed floor now stands at zero.
17
+ - DateTimeField and TimeField render through DateField's template (their verbatim copies are gone); Calendar and DatePicker share `DateParsing`; the four value-field form builder methods ride one `control_field` helper; the chat turn compiler builds its frames through `Chat::AssistantTurn::Timeline`. No rendered markup changed.
18
+ - Every component's root rides the core default: `root_attributes` is the public `Poetry::Core::Component` method, and a component's override passes only its own markup up (`super("data-variant" => variant)`); the slot name, `data-component` and the root's Stimulus wiring come from the default. Twelve overrides that added nothing are gone, and the shared `FamilyIdentity` override passes the family's slot prefix the same way. The rendered attributes are unchanged apart from their order on the element.
19
+ - The part builders (a popup's content, a field's group, a tree row, a dialog panel, and the rest) return `element_attributes(...)`, the part's attributes with its Stimulus wiring merged safely, named after their part so the slot, the dictionary classes and the wiring come stamped, and the templates splat the result (`tag.div(**content_attributes)`); `to_attributes` and the `Poetry::Core::HTML::Attributes.new(...)` wrap at every call site are gone.
20
+
3
21
  ## [0.1.4] - 2026-09-15
4
22
 
5
23
  ### Fixed
@@ -1,3 +1,3 @@
1
- <%= content_tag(:div, **root_attributes.to_attributes) do %>
1
+ <%= content_tag(:div, **root_attributes) do %>
2
2
  <% items.each do |item| %><%= item %><% end %>
3
3
  <% end %>
@@ -132,21 +132,19 @@ module Poetry
132
132
  Array(open).map(&:to_s)
133
133
  end
134
134
 
135
- # @api private
135
+ # The root's attributes: this component's markup over the core default.
136
136
  def root_attributes
137
- html_attributes.merge_if_not_set(
138
- { "data-slot" => "accordion", "data-orientation" => "vertical" }
139
- .merge(stimulus_attributes_for(:root))
140
- .merge(component_data_attributes)
141
- )
137
+ super({ "data-orientation" => "vertical" })
142
138
  end
143
139
 
144
140
  private
145
141
 
142
+ # The server-stable id every item id derives from.
146
143
  def instance_id
147
144
  @instance_id ||= poetry_instance_id("poetry-accordion")
148
145
  end
149
146
 
147
+ # One item's header: the trigger button with its chevron, wired to the controller.
150
148
  def accordion_header(item_id, title, open_item, disabled: false)
151
149
  trigger_attrs = {
152
150
  type: "button", id: "#{item_id}-trigger", class: css(:trigger),
@@ -173,6 +171,7 @@ module Poetry
173
171
  end
174
172
  end
175
173
 
174
+ # One item's panel region, hidden when closed, labelled by its trigger.
176
175
  def accordion_panel(item_id, open_item, &)
177
176
  attrs = {
178
177
  id: "#{item_id}-panel", role: "region", class: css(:content),
@@ -185,12 +184,13 @@ module Poetry
185
184
  end
186
185
  end
187
186
 
187
+ # The trigger's chevron icon.
188
188
  def chevron
189
189
  render(Icon::Component.new(name: :"chevron-down", class: css(:indicator),
190
190
  data: { slot: "accordion-trigger-icon" }))
191
191
  end
192
192
 
193
- private :open_values, :root_attributes
193
+ private :open_values
194
194
  end
195
195
  end
196
196
  end
@@ -1,4 +1,4 @@
1
- <%= content_tag(:div, **root_attributes.to_attributes) do %>
1
+ <%= content_tag(:div, **root_attributes) do %>
2
2
  <%= icon %>
3
3
  <% if title? %><div data-slot="alert-title" class="<%= css(:title) %>"><%= title %></div><% end %>
4
4
  <div data-slot="alert-description" class="<%= css(:description) %>"><%= content %></div>
@@ -43,18 +43,15 @@ module Poetry
43
43
  "pinned to the top-right by the theme"
44
44
  part "alert-description", "The body copy - the content block renders here"
45
45
 
46
- # @api private
46
+ # The root's attributes: this component's markup over the core default.
47
47
  def root_attributes
48
- html_attributes.merge_if_not_set(
48
+ super(
49
49
  {
50
- "data-slot" => "alert",
51
50
  "data-variant" => variant,
52
51
  "role" => (variant == :destructive ? "alert" : "status")
53
- }.merge(component_data_attributes)
52
+ }
54
53
  )
55
54
  end
56
-
57
- private :root_attributes
58
55
  end
59
56
  end
60
57
  end
@@ -1,6 +1,6 @@
1
- <%= content_tag(:div, **root_attributes.to_attributes) do %>
1
+ <%= content_tag(:div, **root_attributes) do %>
2
2
  <%= trigger %>
3
- <%= content_tag(:dialog, **Poetry::Core::HTML::Attributes.new(dialog_attributes).to_attributes) do %>
3
+ <%= content_tag(:dialog, **dialog_attributes) do %>
4
4
  <div data-slot="alert-dialog-header" class="<%= header_classes %>">
5
5
  <% if media? %>
6
6
  <div data-slot="alert-dialog-media" class="<%= media_classes %>"><%= media %></div>
@@ -148,29 +148,32 @@ module Poetry
148
148
  raise ArgumentError, "AlertDialog requires with_cancel (the safe way out)" unless cancel?
149
149
  end
150
150
 
151
+ # The title element's id.
151
152
  # @api private
152
153
  def title_id
153
154
  "#{instance_id}-title"
154
155
  end
155
156
 
157
+ # The description element's id.
156
158
  # @api private
157
159
  def description_id
158
160
  "#{instance_id}-description"
159
161
  end
160
162
 
163
+ # The dialog element's attributes: the alertdialog role, size, closed state and wiring.
161
164
  # @api private
162
165
  def dialog_attributes
163
- {
166
+ attrs = {
164
167
  "class" => css(:content, class: content_class),
165
168
  # Explicit role: overrides the implicit dialog role (aria-modal
166
169
  # still comes from showModal).
167
170
  "role" => "alertdialog",
168
- "data-slot" => "alert-dialog-content",
169
171
  "data-size" => size,
170
172
  "data-closed" => "",
171
173
  "aria-labelledby" => title_id,
172
174
  "aria-describedby" => description_id
173
- }.merge(stimulus_attributes_for(:content))
175
+ }
176
+ element_attributes(:content, attrs)
174
177
  end
175
178
 
176
179
  # The media/size layout branches emitted as explicit server-side
@@ -185,16 +188,19 @@ module Poetry
185
188
  ].compact.join(" "))
186
189
  end
187
190
 
191
+ # The media slot's classes, sized for the default dialog.
188
192
  # @api private
189
193
  def media_classes
190
194
  css(:media, class: (css(:media_size_default) if size == :default))
191
195
  end
192
196
 
197
+ # The title's classes, placed beside the media in the default size.
193
198
  # @api private
194
199
  def title_classes
195
200
  css(:title, class: (css(:title_beside_media) if media? && size == :default))
196
201
  end
197
202
 
203
+ # The footer's classes, tightened in the small size.
198
204
  # @api private
199
205
  def footer_classes
200
206
  css(:footer, class: (css(:footer_size_sm) if size == :sm))
@@ -41,22 +41,20 @@ module Poetry
41
41
  "AspectRatio ratio: must be a CSS ratio ('16/9', '1', '1.5') - got #{ratio.inspect}"
42
42
  end
43
43
 
44
+ # Renders the ratio box around the content.
44
45
  # @api private
45
46
  def call
46
- content_tag(:div, content, **root_attributes.to_attributes)
47
+ content_tag(:div, content, **root_attributes)
47
48
  end
48
49
 
49
- # @api private
50
+ # The root's attributes: this component's markup over the core default.
50
51
  def root_attributes
51
- html_attributes.merge_if_not_set(
52
+ super(
52
53
  {
53
- "data-slot" => "aspect-ratio",
54
54
  "style" => "--ratio: #{ratio}"
55
- }.merge(component_data_attributes)
55
+ }
56
56
  )
57
57
  end
58
-
59
- private :root_attributes
60
58
  end
61
59
  end
62
60
  end
@@ -1,4 +1,4 @@
1
- <%= content_tag(:div, **root_attributes.to_attributes) do %>
1
+ <%= content_tag(:div, **root_attributes) do %>
2
2
  <%= trigger %>
3
3
  <%= media %>
4
4
  <% if title? || description? %>
@@ -122,17 +122,17 @@ module Poetry
122
122
  ANNOUNCED_STATES.include?(state)
123
123
  end
124
124
 
125
- # @api private
125
+ # The root's attributes: this component's markup over the core default.
126
126
  def root_attributes
127
- html_attributes.merge_if_not_set(
127
+ super(
128
128
  {
129
- "data-slot" => "attachment", "data-upload-state" => state,
129
+ "data-upload-state" => state,
130
130
  "data-size" => size, "data-orientation" => orientation
131
- }.merge(component_data_attributes)
131
+ }
132
132
  )
133
133
  end
134
134
 
135
- private :announced?, :root_attributes
135
+ private :announced?
136
136
  end
137
137
  end
138
138
  end
@@ -1,4 +1,4 @@
1
- <%= content_tag(:div, **root_attributes.to_attributes) do %>
1
+ <%= content_tag(:div, **root_attributes) do %>
2
2
  <%= tag.input(**input_attributes, class: Poetry::Ui::Input::Style.css) %>
3
3
  <%= tag.div(**content_attributes) do %>
4
4
  <div data-slot="autocomplete-list" role="listbox" id="<%= list_id %>"
@@ -125,6 +125,7 @@ module Poetry
125
125
  # @api private
126
126
  def item_models = (@item_models ||= [])
127
127
 
128
+ # The root id: the given id as a token, else a stable instance id.
128
129
  # @api private
129
130
  def autocomplete_id
130
131
  @autocomplete_id ||= if (token = dom_id_token(id))
@@ -134,18 +135,16 @@ module Poetry
134
135
  end
135
136
  end
136
137
 
138
+ # The listbox's id.
137
139
  # @api private
138
140
  def list_id = "#{autocomplete_id}-list"
139
141
 
140
- # @api private
142
+ # The root's attributes: this component's markup over the core default.
141
143
  def root_attributes
142
- html_attributes.merge_if_not_set(
143
- { "class" => css, "data-slot" => "autocomplete", "id" => autocomplete_id }
144
- .merge(stimulus_attributes_for(:root))
145
- .merge(component_data_attributes)
146
- )
144
+ super({ "class" => css, "id" => autocomplete_id })
147
145
  end
148
146
 
147
+ # The text input's attributes: the combobox role, its list and open state, and the wiring.
149
148
  # @api private
150
149
  def input_attributes
151
150
  attrs = {
@@ -158,6 +157,7 @@ module Poetry
158
157
  attrs.merge(stimulus_attributes_for(:input).transform_keys(&:to_sym))
159
158
  end
160
159
 
160
+ # The popup's attributes with its open or closed state.
161
161
  # @api private
162
162
  def content_attributes
163
163
  attrs = {
@@ -172,6 +172,7 @@ module Poetry
172
172
  attrs.merge(stimulus_attributes_for(:content))
173
173
  end
174
174
 
175
+ # One option's attributes: its id, value, label and highlighted state.
175
176
  # @api private
176
177
  def item_attributes(item, index)
177
178
  attrs = {
@@ -190,7 +191,7 @@ module Poetry
190
191
  # @api private
191
192
  Item = Struct.new(:label, :value, :disabled, :highlighted, keyword_init: true)
192
193
 
193
- private :item_models, :autocomplete_id, :list_id, :root_attributes, :input_attributes, :content_attributes
194
+ private :item_models, :autocomplete_id, :list_id, :input_attributes, :content_attributes
194
195
  private :item_attributes
195
196
  end
196
197
  end
@@ -57,30 +57,33 @@ module Poetry
57
57
  ensure_content!
58
58
  end
59
59
 
60
+ # Renders the avatar: the fallback, the image and the badge.
60
61
  # @api private
61
62
  def call
62
- content_tag(:span, root_attributes.to_attributes) do
63
+ content_tag(:span, root_attributes) do
63
64
  safe_join([fallback, image, badge_part].compact)
64
65
  end
65
66
  end
66
67
 
67
- # @api private
68
+ # The root's attributes: this component's markup over the core default.
68
69
  def root_attributes
69
- html_attributes.merge_if_not_set(
70
+ super(
70
71
  {
71
- "data-slot" => "avatar", "data-size" => size,
72
+ "data-size" => size,
72
73
  "role" => "img", "aria-label" => label
73
- }.merge(component_data_attributes)
74
+ }
74
75
  )
75
76
  end
76
77
 
77
78
  private
78
79
 
80
+ # The initials fallback span.
79
81
  def fallback
80
82
  content_tag(:span, content, "data-slot" => "avatar-fallback", "aria-hidden" => "true",
81
83
  class: css(:fallback))
82
84
  end
83
85
 
86
+ # The image, or nil without a source.
84
87
  def image
85
88
  return if src.blank?
86
89
 
@@ -90,13 +93,12 @@ module Poetry
90
93
  tag.img(src: src, alt: "", "data-slot": "avatar-image", class: css(:image))
91
94
  end
92
95
 
96
+ # The badge span, or nil without a badge.
93
97
  def badge_part
94
98
  return unless badge?
95
99
 
96
100
  content_tag(:span, badge, "data-slot" => "avatar-badge", "aria-hidden" => "true", class: css(:badge))
97
101
  end
98
-
99
- private :root_attributes
100
102
  end
101
103
  end
102
104
  end
@@ -54,19 +54,18 @@ module Poetry
54
54
  ensure_content!
55
55
  end
56
56
 
57
+ # Renders the badge as a link when it has an href, else as a span.
57
58
  # @api private
58
59
  def call
59
- content_tag(href.present? ? :a : :span, content, **root_attributes.to_attributes)
60
+ content_tag(href.present? ? :a : :span, content, **root_attributes)
60
61
  end
61
62
 
62
- # @api private
63
+ # The root's attributes: this component's markup over the core default.
63
64
  def root_attributes
64
- attrs = { "data-slot" => "badge", "data-variant" => variant }
65
+ attrs = { "data-variant" => variant }
65
66
  attrs["href"] = href if href.present?
66
- html_attributes.merge_if_not_set(attrs.merge(component_data_attributes))
67
+ super(attrs)
67
68
  end
68
-
69
- private :root_attributes
70
69
  end
71
70
  end
72
71
  end
@@ -1,4 +1,4 @@
1
- <%= content_tag(:nav, **root_attributes.to_attributes) do %>
1
+ <%= content_tag(:nav, **root_attributes) do %>
2
2
  <ol data-slot="breadcrumb-list" class="<%= css(:list) %>">
3
3
  <% entries.each_with_index do |entry, index| %>
4
4
  <li data-slot="breadcrumb-item" class="<%= css(:item) %>">
@@ -18,7 +18,6 @@ module Poetry
18
18
  # <% end %>
19
19
  class Component < Poetry::Core::Component
20
20
  # The caller's separator block, captured by with_separator.
21
- # @api private
22
21
  attr_reader :separator_block
23
22
 
24
23
  # Projected into the registry, llms.txt, and the agent surface.
@@ -84,18 +83,17 @@ module Poetry
84
83
  @entries ||= []
85
84
  end
86
85
 
87
- # @api private
86
+ # The root's attributes: this component's markup over the core default.
88
87
  def root_attributes
89
- html_attributes.merge_if_not_set(
90
- { "aria-label" => "breadcrumb", "data-slot" => "breadcrumb" }.merge(component_data_attributes)
91
- )
88
+ super({ "aria-label" => "breadcrumb" })
92
89
  end
93
90
 
94
91
  # One declared crumb.
95
92
  # @api private
96
93
  Entry = Data.define(:label, :href, :ellipsis, :block)
97
94
 
98
- private :entries, :root_attributes
95
+ private :entries
96
+ private :separator_block
99
97
  end
100
98
  end
101
99
  end
@@ -1,4 +1,4 @@
1
- <%= content_tag(:div, **root_attributes.to_attributes) do %>
1
+ <%= content_tag(:div, **root_attributes) do %>
2
2
  <%= content_tag(content_tag_name, content, **content_attributes) %>
3
3
  <%= reactions %>
4
4
  <% end %>
@@ -76,12 +76,9 @@ module Poetry
76
76
  ensure_content!
77
77
  end
78
78
 
79
- # @api private
79
+ # The root's attributes: this component's markup over the core default.
80
80
  def root_attributes
81
- html_attributes.merge_if_not_set(
82
- { "data-slot" => "bubble", "data-variant" => variant, "data-align" => align }
83
- .merge(component_data_attributes)
84
- )
81
+ super({ "data-variant" => variant, "data-align" => align })
85
82
  end
86
83
 
87
84
  # href: implies the anchor - an href on the default :div would
@@ -91,6 +88,7 @@ module Poetry
91
88
  href.present? ? :a : tag
92
89
  end
93
90
 
91
+ # The content element's attributes, with the href or button type its tag needs.
94
92
  # @api private
95
93
  def content_attributes
96
94
  attrs = { class: css(:content), "data-slot" => "bubble-content" }
@@ -99,7 +97,7 @@ module Poetry
99
97
  attrs
100
98
  end
101
99
 
102
- private :root_attributes, :content_tag_name, :content_attributes
100
+ private :content_tag_name, :content_attributes
103
101
  end
104
102
  end
105
103
  end
@@ -1,4 +1,4 @@
1
- <%= content_tag(root_tag, **root_attributes.to_attributes) do %>
1
+ <%= content_tag(root_tag, **root_attributes) do %>
2
2
  <% if loading %>
3
3
  <span data-slot="spinner" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" class="animate-spin"><path d="M21 12a9 9 0 1 1-6.219-8.56" /></svg></span>
4
4
  <% end %>
@@ -113,18 +113,20 @@ module Poetry
113
113
  tag == :a || href.present?
114
114
  end
115
115
 
116
+ # The root tag: an anchor for a link, else a button.
116
117
  # @api private
117
118
  def root_tag
118
119
  link_tag? ? :a : :button
119
120
  end
120
121
 
121
- # @api private
122
+ # The root's attributes: the base and the tag's own, over the caller's.
122
123
  def root_attributes
123
- html_attributes.merge_if_not_set(base_attributes.merge(tag_attributes))
124
+ html_attributes.merge_if_not_set(base_attributes.merge(tag_attributes)).to_attributes
124
125
  end
125
126
 
126
127
  private
127
128
 
129
+ # The attributes every button root carries: identity, variant, size, label and loading state.
128
130
  def base_attributes
129
131
  attrs = { "data-slot" => "button", "data-variant" => variant, "data-size" => size }
130
132
  .merge(component_data_attributes)
@@ -143,6 +145,7 @@ module Poetry
143
145
  link_tag? ? link_attributes : button_attributes
144
146
  end
145
147
 
148
+ # The anchor's attributes: the button role, and the href or the disabled state.
146
149
  def link_attributes
147
150
  attrs = { "role" => "button" }
148
151
  if disabled || loading
@@ -153,15 +156,14 @@ module Poetry
153
156
  attrs
154
157
  end
155
158
 
159
+ # The button's attributes: its type and disabled state.
156
160
  def button_attributes
157
161
  attrs = { "type" => type }
158
162
  attrs["disabled"] = true if disabled || loading
159
163
  attrs
160
164
  end
161
165
 
162
- private :icon_only?, :link_tag?, :root_tag, :root_attributes
163
-
164
- private :icon_only?, :link_tag?, :root_tag, :root_attributes
166
+ private :icon_only?, :link_tag?, :root_tag
165
167
  end
166
168
  end
167
169
  end
@@ -48,22 +48,20 @@ module Poetry
48
48
  ensure_content!
49
49
  end
50
50
 
51
+ # Renders the group around its content.
51
52
  # @api private
52
53
  def call
53
- content_tag(:div, content, **root_attributes.to_attributes)
54
+ content_tag(:div, content, **root_attributes)
54
55
  end
55
56
 
56
- # @api private
57
+ # The root's attributes: this component's markup over the core default.
57
58
  def root_attributes
58
- html_attributes.merge_if_not_set(
59
+ super(
59
60
  {
60
- "role" => "group", "data-slot" => "button-group",
61
- "data-orientation" => orientation
62
- }.merge(component_data_attributes)
61
+ "role" => "group", "data-orientation" => orientation
62
+ }
63
63
  )
64
64
  end
65
-
66
- private :root_attributes
67
65
  end
68
66
  end
69
67
  end
@@ -1,4 +1,4 @@
1
- <%= content_tag(:div, **root_attributes.to_attributes) do %>
1
+ <%= content_tag(:div, **root_attributes) do %>
2
2
  <div data-slot="calendar-nav" class="<%= css(:nav) %>">
3
3
  <%= render Poetry::Ui::Button::Component.new(**previous_options) do |button| %>
4
4
  <% button.with_leading { render Poetry::Ui::Icon::Component.new(name: :"chevron-left") } %>