nitro_kit 0.9.0 → 2.0.0.alpha.2

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 (219) hide show
  1. checksums.yaml +4 -4
  2. data/.agents/plugins/marketplace.json +20 -0
  3. data/CHANGELOG.md +172 -0
  4. data/LICENSE +28 -0
  5. data/README.md +22 -15
  6. data/STYLE_GUIDE.md +425 -0
  7. data/app/assets/stylesheets/nitro_kit-tailwind-v4.css +38 -0
  8. data/app/assets/stylesheets/nitro_kit.css +7239 -0
  9. data/app/components/nitro_kit/accordion.rb +125 -65
  10. data/app/components/nitro_kit/alert.rb +80 -43
  11. data/app/components/nitro_kit/app_navigation.rb +307 -0
  12. data/app/components/nitro_kit/app_shell.rb +227 -0
  13. data/app/components/nitro_kit/appearance_bootstrap.rb +146 -0
  14. data/app/components/nitro_kit/appearance_picker.rb +156 -0
  15. data/app/components/nitro_kit/auth_shell.rb +34 -0
  16. data/app/components/nitro_kit/avatar.rb +85 -29
  17. data/app/components/nitro_kit/avatar_stack.rb +155 -9
  18. data/app/components/nitro_kit/badge.rb +43 -81
  19. data/app/components/nitro_kit/button.rb +180 -103
  20. data/app/components/nitro_kit/button_group.rb +63 -11
  21. data/app/components/nitro_kit/button_to.rb +69 -0
  22. data/app/components/nitro_kit/card.rb +42 -45
  23. data/app/components/nitro_kit/checkbox.rb +115 -62
  24. data/app/components/nitro_kit/checkbox_group.rb +131 -22
  25. data/app/components/nitro_kit/choice.rb +55 -0
  26. data/app/components/nitro_kit/combobox.rb +292 -105
  27. data/app/components/nitro_kit/command_palette.rb +412 -0
  28. data/app/components/nitro_kit/component.rb +322 -46
  29. data/app/components/nitro_kit/container.rb +32 -0
  30. data/app/components/nitro_kit/control_group.rb +45 -0
  31. data/app/components/nitro_kit/danger_zone.rb +78 -0
  32. data/app/components/nitro_kit/data_section.rb +106 -0
  33. data/app/components/nitro_kit/details_table.rb +229 -0
  34. data/app/components/nitro_kit/dialog.rb +257 -83
  35. data/app/components/nitro_kit/dropdown.rb +335 -122
  36. data/app/components/nitro_kit/dropzone.rb +326 -0
  37. data/app/components/nitro_kit/empty_state.rb +99 -0
  38. data/app/components/nitro_kit/field.rb +461 -236
  39. data/app/components/nitro_kit/field_group.rb +15 -8
  40. data/app/components/nitro_kit/fieldset.rb +37 -39
  41. data/app/components/nitro_kit/flex.rb +49 -0
  42. data/app/components/nitro_kit/form_builder.rb +313 -72
  43. data/app/components/nitro_kit/form_section.rb +79 -0
  44. data/app/components/nitro_kit/grid.rb +34 -0
  45. data/app/components/nitro_kit/icon.rb +54 -25
  46. data/app/components/nitro_kit/input.rb +97 -10
  47. data/app/components/nitro_kit/label.rb +22 -6
  48. data/app/components/nitro_kit/layout_options.rb +7 -0
  49. data/app/components/nitro_kit/page_header.rb +76 -0
  50. data/app/components/nitro_kit/pagination.rb +355 -69
  51. data/app/components/nitro_kit/pagination_bar.rb +90 -0
  52. data/app/components/nitro_kit/progressive_image.rb +181 -0
  53. data/app/components/nitro_kit/radio_button.rb +77 -55
  54. data/app/components/nitro_kit/radio_button_group.rb +107 -36
  55. data/app/components/nitro_kit/responsive_value.rb +94 -0
  56. data/app/components/nitro_kit/rich_text_area.rb +31 -0
  57. data/app/components/nitro_kit/select.rb +129 -64
  58. data/app/components/nitro_kit/settings_layout.rb +152 -0
  59. data/app/components/nitro_kit/sheet.rb +202 -0
  60. data/app/components/nitro_kit/stat_grid.rb +75 -0
  61. data/app/components/nitro_kit/switch.rb +108 -51
  62. data/app/components/nitro_kit/table.rb +237 -45
  63. data/app/components/nitro_kit/tabs.rb +168 -63
  64. data/app/components/nitro_kit/textarea.rb +81 -10
  65. data/app/components/nitro_kit/toast.rb +211 -80
  66. data/app/components/nitro_kit/toolbar.rb +75 -0
  67. data/app/components/nitro_kit/tooltip.rb +235 -34
  68. data/app/components/nitro_kit/typeset.rb +28 -0
  69. data/app/javascript/controllers/nk/app_shell_controller.js +224 -0
  70. data/app/javascript/controllers/nk/appearance_controller.js +73 -0
  71. data/app/javascript/controllers/nk/avatar_controller.js +15 -0
  72. data/app/javascript/controllers/nk/button_controller.js +56 -0
  73. data/app/javascript/controllers/nk/checkable_controller.js +41 -0
  74. data/app/javascript/controllers/nk/combobox_controller.js +314 -82
  75. data/app/javascript/controllers/nk/command_palette_controller.js +249 -0
  76. data/app/javascript/controllers/nk/dialog_controller.js +14 -10
  77. data/app/javascript/controllers/nk/dropdown_controller.js +106 -58
  78. data/app/javascript/controllers/nk/dropzone/direct_upload.js +61 -0
  79. data/app/javascript/controllers/nk/dropzone/file_rules.js +77 -0
  80. data/app/javascript/controllers/nk/dropzone/form_submit_lock.js +30 -0
  81. data/app/javascript/controllers/nk/dropzone_controller.js +482 -0
  82. data/app/javascript/controllers/nk/overlay_position.js +37 -0
  83. data/app/javascript/controllers/nk/progressive_image_controller.js +116 -0
  84. data/app/javascript/controllers/nk/tabs_controller.js +135 -23
  85. data/app/javascript/controllers/nk/toast_controller.js +112 -45
  86. data/app/javascript/controllers/nk/tooltip_controller.js +12 -47
  87. data/config/importmap.rb +2 -0
  88. data/config/locales/en.yml +98 -0
  89. data/docs/agent_guide.md +139 -0
  90. data/docs/agent_native_spec.md +422 -0
  91. data/docs/component_contracts.md +263 -0
  92. data/docs/customization.md +595 -0
  93. data/docs/hotwire.md +94 -0
  94. data/docs/initialization_prompt.md +36 -0
  95. data/docs/migration_1_to_2.md +318 -0
  96. data/docs/new_app_strategy.md +22 -0
  97. data/docs/patterns/application_foundation.md +136 -0
  98. data/docs/patterns/crud_resource.md +144 -0
  99. data/docs/patterns/destructive_action.md +105 -0
  100. data/docs/patterns/flash_and_toast.md +57 -0
  101. data/docs/patterns/inline_edit.md +59 -0
  102. data/docs/patterns/queryable_collection.md +187 -0
  103. data/docs/patterns/resource_form.md +126 -0
  104. data/docs/rails_conventions.md +95 -0
  105. data/docs/rails_integration.md +514 -0
  106. data/lib/generators/nitro_kit/install_generator.rb +48 -0
  107. data/lib/generators/nitro_kit/upgrade_smoke_tests_generator.rb +67 -0
  108. data/lib/nitro_kit/engine.rb +20 -0
  109. data/lib/nitro_kit/installation.rb +671 -0
  110. data/lib/nitro_kit/migration_inventory.rb +345 -0
  111. data/lib/nitro_kit/upgrade_smoke_test.rb +403 -0
  112. data/lib/nitro_kit/version.rb +1 -1
  113. data/lib/nitro_kit.rb +5 -42
  114. data/lib/rails/commands/nitro_kit/nitro_kit_command.rb +42 -0
  115. data/lib/tasks/nitro_kit_tasks.rake +79 -4
  116. data/plugins/nitro-kit/.codex-plugin/plugin.json +28 -0
  117. data/plugins/nitro-kit/skills/nitro-kit-hotwire/SKILL.md +67 -0
  118. data/plugins/nitro-kit/skills/nitro-kit-hotwire/agents/openai.yaml +4 -0
  119. data/plugins/nitro-kit/skills/nitro-kit-rails/SKILL.md +60 -0
  120. data/plugins/nitro-kit/skills/nitro-kit-rails/agents/openai.yaml +4 -0
  121. data/plugins/nitro-kit/skills/nitro-kit-ui/SKILL.md +62 -0
  122. data/plugins/nitro-kit/skills/nitro-kit-ui/agents/openai.yaml +4 -0
  123. data/src/stylesheets/nitro_kit/components/accordion.css +119 -0
  124. data/src/stylesheets/nitro_kit/components/alert.css +82 -0
  125. data/src/stylesheets/nitro_kit/components/app_navigation.css +156 -0
  126. data/src/stylesheets/nitro_kit/components/app_shell.css +605 -0
  127. data/src/stylesheets/nitro_kit/components/appearance_picker.css +117 -0
  128. data/src/stylesheets/nitro_kit/components/auth_shell.css +8 -0
  129. data/src/stylesheets/nitro_kit/components/avatar.css +81 -0
  130. data/src/stylesheets/nitro_kit/components/avatar_stack.css +80 -0
  131. data/src/stylesheets/nitro_kit/components/badge.css +76 -0
  132. data/src/stylesheets/nitro_kit/components/button.css +283 -0
  133. data/src/stylesheets/nitro_kit/components/button_group.css +53 -0
  134. data/src/stylesheets/nitro_kit/components/button_to.css +6 -0
  135. data/src/stylesheets/nitro_kit/components/card.css +70 -0
  136. data/src/stylesheets/nitro_kit/components/checkbox.css +160 -0
  137. data/src/stylesheets/nitro_kit/components/checkbox_group.css +61 -0
  138. data/src/stylesheets/nitro_kit/components/combobox.css +148 -0
  139. data/src/stylesheets/nitro_kit/components/command_palette.css +255 -0
  140. data/src/stylesheets/nitro_kit/components/container.css +25 -0
  141. data/src/stylesheets/nitro_kit/components/control_group.css +149 -0
  142. data/src/stylesheets/nitro_kit/components/danger_zone.css +54 -0
  143. data/src/stylesheets/nitro_kit/components/data_section.css +40 -0
  144. data/src/stylesheets/nitro_kit/components/details_table.css +53 -0
  145. data/src/stylesheets/nitro_kit/components/dialog.css +80 -0
  146. data/src/stylesheets/nitro_kit/components/dropdown.css +152 -0
  147. data/src/stylesheets/nitro_kit/components/dropzone.css +198 -0
  148. data/src/stylesheets/nitro_kit/components/empty_state.css +53 -0
  149. data/src/stylesheets/nitro_kit/components/field.css +79 -0
  150. data/src/stylesheets/nitro_kit/components/field_group.css +6 -0
  151. data/src/stylesheets/nitro_kit/components/fieldset.css +28 -0
  152. data/src/stylesheets/nitro_kit/components/flex.css +455 -0
  153. data/src/stylesheets/nitro_kit/components/form_section.css +71 -0
  154. data/src/stylesheets/nitro_kit/components/grid.css +308 -0
  155. data/src/stylesheets/nitro_kit/components/icon.css +26 -0
  156. data/src/stylesheets/nitro_kit/components/input.css +80 -0
  157. data/src/stylesheets/nitro_kit/components/label.css +9 -0
  158. data/src/stylesheets/nitro_kit/components/layout.css +440 -0
  159. data/src/stylesheets/nitro_kit/components/page_header.css +61 -0
  160. data/src/stylesheets/nitro_kit/components/pagination.css +119 -0
  161. data/src/stylesheets/nitro_kit/components/pagination_bar.css +41 -0
  162. data/src/stylesheets/nitro_kit/components/palette.css +113 -0
  163. data/src/stylesheets/nitro_kit/components/progressive_image.css +139 -0
  164. data/src/stylesheets/nitro_kit/components/radio_button.css +127 -0
  165. data/src/stylesheets/nitro_kit/components/radio_button_group.css +143 -0
  166. data/src/stylesheets/nitro_kit/components/rich_text_area.css +21 -0
  167. data/src/stylesheets/nitro_kit/components/select.css +70 -0
  168. data/src/stylesheets/nitro_kit/components/settings_layout.css +100 -0
  169. data/src/stylesheets/nitro_kit/components/sheet.css +100 -0
  170. data/src/stylesheets/nitro_kit/components/stat_grid.css +51 -0
  171. data/src/stylesheets/nitro_kit/components/switch.css +152 -0
  172. data/src/stylesheets/nitro_kit/components/table.css +116 -0
  173. data/src/stylesheets/nitro_kit/components/tabs.css +138 -0
  174. data/src/stylesheets/nitro_kit/components/textarea.css +39 -0
  175. data/src/stylesheets/nitro_kit/components/toast.css +131 -0
  176. data/src/stylesheets/nitro_kit/components/toolbar.css +78 -0
  177. data/src/stylesheets/nitro_kit/components/tooltip.css +124 -0
  178. data/src/stylesheets/nitro_kit/components/typeset.css +191 -0
  179. data/src/stylesheets/nitro_kit/layers.css +7 -0
  180. data/src/stylesheets/nitro_kit/reset.css +192 -0
  181. data/src/stylesheets/nitro_kit/tokens.css +198 -0
  182. metadata +182 -51
  183. data/Rakefile +0 -8
  184. data/app/assets/tailwind/application.css +0 -49
  185. data/app/components/nitro_kit/datepicker.rb +0 -9
  186. data/app/helpers/nitro_kit/accordion_helper.rb +0 -9
  187. data/app/helpers/nitro_kit/alert_helper.rb +0 -11
  188. data/app/helpers/nitro_kit/avatar_helper.rb +0 -13
  189. data/app/helpers/nitro_kit/badge_helper.rb +0 -13
  190. data/app/helpers/nitro_kit/button_group_helper.rb +0 -9
  191. data/app/helpers/nitro_kit/button_helper.rb +0 -49
  192. data/app/helpers/nitro_kit/card_helper.rb +0 -9
  193. data/app/helpers/nitro_kit/checkbox_helper.rb +0 -50
  194. data/app/helpers/nitro_kit/combobox_helper.rb +0 -9
  195. data/app/helpers/nitro_kit/datepicker_helper.rb +0 -9
  196. data/app/helpers/nitro_kit/dialog_helper.rb +0 -9
  197. data/app/helpers/nitro_kit/dropdown_helper.rb +0 -9
  198. data/app/helpers/nitro_kit/field_group_helper.rb +0 -9
  199. data/app/helpers/nitro_kit/field_helper.rb +0 -9
  200. data/app/helpers/nitro_kit/fieldset_helper.rb +0 -9
  201. data/app/helpers/nitro_kit/form_helper.rb +0 -13
  202. data/app/helpers/nitro_kit/icon_helper.rb +0 -9
  203. data/app/helpers/nitro_kit/input_helper.rb +0 -37
  204. data/app/helpers/nitro_kit/label_helper.rb +0 -19
  205. data/app/helpers/nitro_kit/pagination_helper.rb +0 -46
  206. data/app/helpers/nitro_kit/radio_button_helper.rb +0 -23
  207. data/app/helpers/nitro_kit/select_helper.rb +0 -24
  208. data/app/helpers/nitro_kit/switch_helper.rb +0 -9
  209. data/app/helpers/nitro_kit/table_helper.rb +0 -9
  210. data/app/helpers/nitro_kit/tabs_helper.rb +0 -9
  211. data/app/helpers/nitro_kit/textarea_helper.rb +0 -9
  212. data/app/helpers/nitro_kit/toast_helper.rb +0 -25
  213. data/app/helpers/nitro_kit/tooltip_helper.rb +0 -9
  214. data/app/javascript/controllers/nk/accordion_controller.js +0 -18
  215. data/app/javascript/controllers/nk/datepicker_controller.js +0 -5
  216. data/app/javascript/controllers/nk/switch_controller.js +0 -32
  217. data/lib/generators/nitro_kit/component_generator.rb +0 -84
  218. data/lib/nitro_kit/schema_builder.rb +0 -129
  219. data/lib/nitro_kit/variants.rb +0 -21
@@ -0,0 +1,412 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NitroKit
4
+ class CommandPalette < Component
5
+ Destination = ::Data.define(
6
+ :label,
7
+ :href,
8
+ :description,
9
+ :html,
10
+ :aria,
11
+ :data,
12
+ :css_class
13
+ )
14
+ private_constant :Destination
15
+
16
+ module Validation
17
+ private
18
+
19
+ def component_id(value)
20
+ return value if value.is_a?(String) && value.match?(/\A[A-Za-z0-9][A-Za-z0-9_-]*\z/)
21
+
22
+ raise ArgumentError, "CommandPalette id: must contain only letters, numbers, underscores, and hyphens"
23
+ end
24
+
25
+ def required_text(name, value)
26
+ return value if value.is_a?(String) && !value.strip.empty?
27
+
28
+ raise ArgumentError, "#{name} must be a non-blank String"
29
+ end
30
+ end
31
+ private_constant :Validation
32
+
33
+ include Validation
34
+
35
+ class Results < Component
36
+ include Validation
37
+
38
+ def initialize(
39
+ id:,
40
+ html: {},
41
+ aria: {},
42
+ data: {},
43
+ desperately_need_a_class: nil
44
+ )
45
+ @identifier = component_id(id)
46
+ @destinations = []
47
+
48
+ super(
49
+ component: :command_palette_results,
50
+ attributes: {
51
+ id: frame_id,
52
+ target: "_top",
53
+ data: {
54
+ nk__command_palette_target: "frame",
55
+ action: [
56
+ "turbo:before-fetch-request->nk--command-palette#loading",
57
+ "turbo:frame-load->nk--command-palette#loaded"
58
+ ].join(" ")
59
+ }
60
+ },
61
+ html:,
62
+ aria:,
63
+ data:,
64
+ desperately_need_a_class:
65
+ )
66
+ end
67
+
68
+ def destination(
69
+ label,
70
+ href:,
71
+ description: nil,
72
+ html: {},
73
+ aria: {},
74
+ data: {},
75
+ desperately_need_a_class: nil
76
+ )
77
+ ensure_collecting!
78
+
79
+ @destinations << Destination.new(
80
+ label: required_text(:label, label),
81
+ href: required_text(:href, href),
82
+ description: validate_optional_text!(:description, description),
83
+ html:,
84
+ aria:,
85
+ data:,
86
+ css_class: desperately_need_a_class
87
+ )
88
+ nil
89
+ end
90
+
91
+ def view_template(&block)
92
+ @collecting = true
93
+ yield(self) if block
94
+
95
+ tag(:"turbo-frame", **root_attributes) do
96
+ nav(
97
+ id: results_id,
98
+ aria: { labelledby: title_id },
99
+ data: { slot: "command-palette-results" }
100
+ ) do
101
+ @destinations.each_with_index { |destination, index| render_destination(destination, index) }
102
+ end
103
+ end
104
+ ensure
105
+ @collecting = false
106
+ end
107
+
108
+ private
109
+
110
+ def render_destination(destination, index)
111
+ a(
112
+ **slot_attributes(
113
+ :destination,
114
+ attributes: {
115
+ id: destination_id(index),
116
+ href: destination.href,
117
+ data: {
118
+ nk__command_palette_target: "destination",
119
+ action: "click->nk--command-palette#select keydown->nk--command-palette#navigateDestination"
120
+ }
121
+ },
122
+ html: destination.html,
123
+ aria: destination.aria,
124
+ data: destination.data,
125
+ desperately_need_a_class: destination.css_class
126
+ )
127
+ ) do
128
+ span(**slot_attributes(:destination_label)) { destination.label }
129
+ if destination.description
130
+ span(**slot_attributes(:destination_description)) { destination.description }
131
+ end
132
+ end
133
+ end
134
+
135
+ def frame_id = "#{@identifier}-results-frame"
136
+ def results_id = "#{@identifier}-results"
137
+ def title_id = "#{@identifier}-title"
138
+ def destination_id(index) = "#{@identifier}-destination-#{index + 1}"
139
+
140
+ def ensure_collecting!
141
+ return if @collecting
142
+
143
+ raise ArgumentError, "CommandPalette::Results destinations must be declared inside the render block"
144
+ end
145
+
146
+ def qualified_slot(slot)
147
+ slot_name = normalize_identity(slot, name: "slot")
148
+ slot_name.start_with?("command-palette-") ? slot_name : "command-palette-#{slot_name}"
149
+ end
150
+ end
151
+
152
+ def initialize(
153
+ id:,
154
+ label: I18n.t("nitro_kit.command_palette.label"),
155
+ placeholder: I18n.t("nitro_kit.command_palette.placeholder"),
156
+ empty_text: I18n.t("nitro_kit.command_palette.empty"),
157
+ close_label: I18n.t("nitro_kit.command_palette.close"),
158
+ shortcut: true,
159
+ shortcut_label: I18n.t("nitro_kit.command_palette.shortcut"),
160
+ search_url: nil,
161
+ html: {},
162
+ aria: {},
163
+ data: {},
164
+ desperately_need_a_class: nil
165
+ )
166
+ @identifier = component_id(id)
167
+ @label = required_text(:label, label)
168
+ @placeholder = required_text(:placeholder, placeholder)
169
+ @empty_text = required_text(:empty_text, empty_text)
170
+ @close_label = required_text(:close_label, close_label)
171
+ @shortcut = validate_boolean!(:shortcut, shortcut)
172
+ @shortcut_label = required_text(:shortcut_label, shortcut_label)
173
+ @search_url = validate_optional_text!(:search_url, search_url)
174
+ @destinations = []
175
+
176
+ super(
177
+ component: :command_palette,
178
+ attributes: {
179
+ id: @identifier,
180
+ data: {
181
+ controller: "nk--dialog nk--command-palette",
182
+ action: root_actions,
183
+ nk__dialog_dismissible_value: true,
184
+ nk__command_palette_empty_value: @empty_text,
185
+ nk__command_palette_results_one_value: I18n.t("nitro_kit.command_palette.results.one"),
186
+ nk__command_palette_results_other_value: I18n.t("nitro_kit.command_palette.results.other")
187
+ }
188
+ },
189
+ html:,
190
+ aria:,
191
+ data:,
192
+ desperately_need_a_class:
193
+ )
194
+ end
195
+
196
+ attr_reader :identifier
197
+
198
+ def view_template(&block)
199
+ collect_destinations(&block)
200
+
201
+ div(**root_attributes) do
202
+ render_trigger
203
+ render_panel
204
+ end
205
+ end
206
+
207
+ def destination(
208
+ label,
209
+ href:,
210
+ description: nil,
211
+ html: {},
212
+ aria: {},
213
+ data: {},
214
+ desperately_need_a_class: nil
215
+ )
216
+ ensure_collecting!
217
+
218
+ @destinations << Destination.new(
219
+ label: required_text(:label, label),
220
+ href: required_text(:href, href),
221
+ description: validate_optional_text!(:description, description),
222
+ html:,
223
+ aria:,
224
+ data:,
225
+ css_class: desperately_need_a_class
226
+ )
227
+ nil
228
+ end
229
+
230
+ private
231
+
232
+ def collect_destinations
233
+ raise ArgumentError, "CommandPalette requires a declaration block" unless block_given?
234
+
235
+ @collecting = true
236
+ yield(self)
237
+ raise ArgumentError, "CommandPalette requires at least one destination" if @destinations.empty?
238
+ ensure
239
+ @collecting = false
240
+ end
241
+
242
+ def render_trigger
243
+ render_in_slot(
244
+ Button.new(
245
+ icon: :search,
246
+ html: { command: "show-modal", commandfor: panel_id },
247
+ aria: {
248
+ haspopup: "dialog",
249
+ keyshortcuts: @shortcut ? "Meta+K Control+K" : nil
250
+ }.compact,
251
+ data: {
252
+ nk__command_palette_target: "trigger",
253
+ action: "click->nk--command-palette#open"
254
+ }
255
+ ),
256
+ :trigger
257
+ ) do
258
+ span(**slot_attributes(:trigger_label)) { @label }
259
+ if @shortcut
260
+ kbd(**slot_attributes(:shortcut, attributes: { aria: { hidden: true } })) do
261
+ @shortcut_label
262
+ end
263
+ end
264
+ end
265
+ end
266
+
267
+ def render_panel
268
+ dialog(
269
+ **slot_attributes(
270
+ :panel,
271
+ attributes: {
272
+ id: panel_id,
273
+ closedby: "any",
274
+ aria: { labelledby: title_id },
275
+ data: {
276
+ nk__dialog_target: "panel",
277
+ nk__command_palette_target: "panel",
278
+ action: [
279
+ "click->nk--dialog#dismiss",
280
+ "cancel->nk--dialog#cancel",
281
+ "close->nk--command-palette#closed"
282
+ ].join(" ")
283
+ }
284
+ }
285
+ )
286
+ ) do
287
+ h2(**slot_attributes(:title, attributes: { id: title_id })) { @label }
288
+ render_close
289
+ render_search
290
+ render_destinations
291
+ render_empty
292
+ render_status
293
+ end
294
+ end
295
+
296
+ def render_search
297
+ attributes = slot_attributes(
298
+ :search,
299
+ attributes: {
300
+ hidden: true,
301
+ action: @search_url,
302
+ method: @search_url ? "get" : nil,
303
+ data: {
304
+ nk__command_palette_target: @search_url ? "form search" : "search",
305
+ turbo_frame: @search_url ? frame_id : nil
306
+ }.compact
307
+ }.compact
308
+ )
309
+
310
+ if @search_url
311
+ form(**attributes) { render_search_field }
312
+ else
313
+ div(**attributes) { render_search_field }
314
+ end
315
+ end
316
+
317
+ def render_search_field
318
+ render_in_slot(Icon.new(:search, size: :sm), :search_icon)
319
+ render_in_slot(
320
+ Input.new(
321
+ type: :search,
322
+ name: @search_url ? "query" : nil,
323
+ placeholder: @placeholder,
324
+ autocomplete: "off",
325
+ aria: { label: @label, controls: results_id },
326
+ data: {
327
+ nk__command_palette_target: "input",
328
+ action: [
329
+ "input->nk--command-palette##{@search_url ? "search" : "filter"}",
330
+ "keydown->nk--command-palette#navigateInput"
331
+ ].join(" ")
332
+ }
333
+ ),
334
+ :input
335
+ )
336
+ end
337
+
338
+ def render_close
339
+ render_in_slot(
340
+ Button.new(
341
+ icon: :x,
342
+ variant: :ghost,
343
+ size: :md,
344
+ html: { command: "close", commandfor: panel_id },
345
+ aria: { label: @close_label }
346
+ ),
347
+ :close
348
+ )
349
+ end
350
+
351
+ def render_destinations
352
+ render Results.new(id: identifier) do |results|
353
+ @destinations.each do |destination|
354
+ results.destination(
355
+ destination.label,
356
+ href: destination.href,
357
+ description: destination.description,
358
+ html: destination.html,
359
+ aria: destination.aria,
360
+ data: destination.data,
361
+ desperately_need_a_class: destination.css_class
362
+ )
363
+ end
364
+ end
365
+ end
366
+
367
+ def render_empty
368
+ p(
369
+ **slot_attributes(
370
+ :empty,
371
+ attributes: {
372
+ hidden: true,
373
+ data: { nk__command_palette_target: "empty" }
374
+ }
375
+ )
376
+ ) { @empty_text }
377
+ end
378
+
379
+ def render_status
380
+ p(
381
+ **slot_attributes(
382
+ :status,
383
+ attributes: {
384
+ role: "status",
385
+ aria: { live: "polite", atomic: "true" },
386
+ data: { nk__command_palette_target: "status" }
387
+ }
388
+ )
389
+ )
390
+ end
391
+
392
+ def root_actions
393
+ actions = [
394
+ "turbo:before-visit@document->nk--command-palette#closeForVisit",
395
+ "turbo:before-cache@document->nk--command-palette#closeForVisit"
396
+ ]
397
+ actions.prepend("keydown@document->nk--command-palette#shortcut") if @shortcut
398
+ actions.join(" ")
399
+ end
400
+
401
+ def panel_id = "#{identifier}-panel"
402
+ def title_id = "#{identifier}-title"
403
+ def results_id = "#{identifier}-results"
404
+ def frame_id = "#{identifier}-results-frame"
405
+
406
+ def ensure_collecting!
407
+ return if @collecting
408
+
409
+ raise ArgumentError, "CommandPalette destinations must be declared inside the render block"
410
+ end
411
+ end
412
+ end