ariadne_view_components 0.0.58 → 0.0.64

Sign up to get free protection for your applications and to get access to all the features.
Files changed (264) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +70 -0
  3. data/LICENSE.txt +661 -49
  4. data/README.md +52 -4
  5. data/app/assets/javascripts/ariadne_view_components.js +98 -7
  6. data/app/assets/javascripts/ariadne_view_components.js.map +1 -1
  7. data/app/assets/stylesheets/ariadne_view_components.css +1 -7
  8. data/app/components/ariadne/base_component.rb +79 -27
  9. data/app/components/ariadne/behaviors/tooltipable.rb +120 -0
  10. data/app/components/ariadne/conditional_wrapper.rb +21 -0
  11. data/app/components/ariadne/form/base_component.rb +74 -0
  12. data/app/components/ariadne/form/base_input_component.rb +60 -0
  13. data/app/components/ariadne/form/caption/component.html.erb +10 -0
  14. data/app/components/ariadne/form/caption/component.rb +29 -0
  15. data/app/components/ariadne/form/form_control/component.html.erb +19 -0
  16. data/app/components/ariadne/form/form_control/component.rb +27 -0
  17. data/app/components/ariadne/form/form_reference/component.html.erb +1 -0
  18. data/app/components/ariadne/form/form_reference/component.rb +18 -0
  19. data/app/components/ariadne/form/group/component.html.erb +5 -0
  20. data/app/components/ariadne/form/group/component.rb +27 -0
  21. data/app/components/ariadne/form/hidden_field/component.html.erb +1 -0
  22. data/app/components/ariadne/form/hidden_field/component.rb +15 -0
  23. data/app/components/ariadne/form/separator/component.html.erb +1 -0
  24. data/app/components/ariadne/form/separator/component.rb +8 -0
  25. data/app/components/ariadne/form/spacing_wrapper/component.html.erb +3 -0
  26. data/app/components/ariadne/form/spacing_wrapper/component.rb +8 -0
  27. data/app/components/ariadne/form/text_field/component.html.erb +25 -0
  28. data/app/components/ariadne/form/text_field/component.rb +132 -0
  29. data/app/components/ariadne/form/validation_message/component.html.erb +5 -0
  30. data/app/components/ariadne/form/validation_message/component.rb +14 -0
  31. data/app/components/ariadne/layout/narrow/component.html.erb +10 -0
  32. data/app/components/ariadne/layout/narrow/component.rb +24 -0
  33. data/app/components/ariadne/layout/nav_bar/component.css +0 -0
  34. data/app/components/ariadne/layout/nav_bar/component.html.erb +123 -0
  35. data/app/components/ariadne/layout/nav_bar/component.rb +77 -0
  36. data/app/components/ariadne/ui/button/component.html.erb +5 -0
  37. data/app/components/ariadne/ui/button/component.rb +184 -0
  38. data/app/components/ariadne/ui/clipboard_copy/component.html.erb +8 -0
  39. data/app/components/ariadne/ui/clipboard_copy/component.rb +102 -0
  40. data/app/components/ariadne/ui/clipboard_copy/component.ts +54 -0
  41. data/app/components/ariadne/ui/combobox/component.html.erb +32 -0
  42. data/app/components/ariadne/ui/combobox/component.rb +83 -0
  43. data/app/components/ariadne/ui/combobox/component.ts +119 -0
  44. data/app/components/ariadne/ui/combobox/menu_item/component.html.erb +9 -0
  45. data/app/components/ariadne/ui/combobox/menu_item/component.rb +53 -0
  46. data/app/components/ariadne/ui/combobox/option/component.html.erb +11 -0
  47. data/app/components/ariadne/ui/combobox/option/component.rb +45 -0
  48. data/app/components/ariadne/ui/heroicon/component.html.erb +3 -0
  49. data/app/components/ariadne/ui/heroicon/component.rb +141 -0
  50. data/app/components/ariadne/ui/image/component.rb +69 -0
  51. data/app/components/ariadne/ui/link/component.html.erb +3 -0
  52. data/app/components/ariadne/ui/link/component.rb +56 -0
  53. data/app/components/ariadne/ui/typography/component.html.erb +3 -0
  54. data/app/components/ariadne/ui/typography/component.rb +41 -0
  55. data/app/lib/ariadne/attributes_helper.rb +119 -0
  56. data/app/lib/ariadne/fetch_or_fallback_helper.rb +1 -1
  57. data/app/lib/ariadne/form.rb +16 -0
  58. data/app/lib/ariadne/view_helper.rb +2 -5
  59. data/app/lib/view_components_contrib/html_attrs.rb +64 -0
  60. data/app/lib/view_components_contrib/style_variants.rb +14 -0
  61. data/lib/ariadne/forms/acts_as_component.rb +125 -0
  62. data/lib/ariadne/forms/base.html.erb +8 -0
  63. data/lib/ariadne/forms/base.rb +132 -0
  64. data/lib/ariadne/forms/buffer_rewriter.rb +51 -0
  65. data/lib/ariadne/forms/builder.rb +88 -0
  66. data/lib/ariadne/forms/dsl/button_input.rb +33 -0
  67. data/lib/ariadne/forms/dsl/form_object.rb +26 -0
  68. data/lib/ariadne/forms/dsl/input.rb +322 -0
  69. data/lib/ariadne/forms/dsl/input_group.rb +34 -0
  70. data/lib/ariadne/forms/dsl/input_methods.rb +157 -0
  71. data/lib/ariadne/forms/dsl/submit_button_input.rb +36 -0
  72. data/lib/ariadne/forms/dsl/text_field_input.rb +73 -0
  73. data/lib/ariadne/forms/utils.rb +34 -0
  74. data/lib/ariadne/generate.rb +11 -0
  75. data/lib/ariadne/view_components/engine.rb +24 -7
  76. data/lib/ariadne/view_components/version.rb +1 -1
  77. data/lib/ariadne/view_components.rb +1 -1
  78. data/lib/ariadne/yard/backend.rb +24 -0
  79. data/lib/ariadne/yard/component_manifest.rb +148 -0
  80. data/lib/ariadne/yard/component_ref.rb +49 -0
  81. data/lib/ariadne/yard/docs_helper.rb +98 -0
  82. data/lib/ariadne/yard/info_arch_docs_helper.rb +31 -0
  83. data/lib/ariadne/yard/lookbook_docs_helper.rb +32 -0
  84. data/lib/ariadne/yard/lookbook_pages_backend.rb +235 -0
  85. data/lib/ariadne/yard/registry.rb +136 -0
  86. data/lib/ariadne/yard/renders_many_handler.rb +23 -0
  87. data/lib/ariadne/yard/renders_one_handler.rb +23 -0
  88. data/lib/ariadne/yard.rb +19 -0
  89. data/static/arguments.yml +141 -48
  90. data/static/audited_at.json +0 -9
  91. data/static/classes.yml +210 -209
  92. data/static/constants.json +2 -209
  93. data/static/statuses.json +0 -9
  94. metadata +125 -210
  95. data/app/assets/builds/ariadne_view_components.css +0 -2202
  96. data/app/assets/javascripts/components/ariadne/accumulator_controller/accumulator_controller.d.ts +0 -22
  97. data/app/assets/javascripts/components/ariadne/ariadne-form.d.ts +0 -22
  98. data/app/assets/javascripts/components/ariadne/ariadne.d.ts +0 -2
  99. data/app/assets/javascripts/components/ariadne/clipboard_copy_component/clipboard-copy-component.d.ts +0 -4
  100. data/app/assets/javascripts/components/ariadne/dropdown/menu_component.d.ts +0 -1
  101. data/app/assets/javascripts/components/ariadne/events_controller/events_controller.d.ts +0 -4
  102. data/app/assets/javascripts/components/ariadne/options_controller/options_controller.d.ts +0 -39
  103. data/app/assets/javascripts/components/ariadne/outlet_manager_controller/outlet_manager_controller.d.ts +0 -42
  104. data/app/assets/javascripts/components/ariadne/slideover_component/slideover-component.d.ts +0 -9
  105. data/app/assets/javascripts/components/ariadne/string_match_controller/string_match_controller.d.ts +0 -27
  106. data/app/assets/javascripts/components/ariadne/synced_boolean_attributes_controller/synced_boolean_attributes_controller.d.ts +0 -48
  107. data/app/assets/javascripts/components/ariadne/tab_container_component/tab-container-component.d.ts +0 -1
  108. data/app/assets/javascripts/components/ariadne/tab_nav_component/tab-nav-component.d.ts +0 -9
  109. data/app/assets/javascripts/components/ariadne/time_ago_component/time-ago-component.d.ts +0 -1
  110. data/app/assets/javascripts/components/ariadne/toggleable_controller/toggleable_controller.d.ts +0 -34
  111. data/app/assets/javascripts/components/ariadne/tooltip_component/tooltip-component.d.ts +0 -24
  112. data/app/assets/stylesheets/dropdown.css +0 -46
  113. data/app/assets/stylesheets/prosemirror.css +0 -323
  114. data/app/assets/stylesheets/tooltip-component.css +0 -37
  115. data/app/components/ariadne/accumulator_controller/accumulator_controller.d.ts +0 -22
  116. data/app/components/ariadne/accumulator_controller/accumulator_controller.js +0 -39
  117. data/app/components/ariadne/accumulator_controller/accumulator_controller.ts +0 -48
  118. data/app/components/ariadne/action_card_component.html.erb +0 -13
  119. data/app/components/ariadne/action_card_component.rb +0 -88
  120. data/app/components/ariadne/ariadne-form.d.ts +0 -22
  121. data/app/components/ariadne/ariadne-form.js +0 -85
  122. data/app/components/ariadne/ariadne.d.ts +0 -2
  123. data/app/components/ariadne/ariadne.js +0 -24
  124. data/app/components/ariadne/ariadne.ts +0 -29
  125. data/app/components/ariadne/avatar_component.rb +0 -81
  126. data/app/components/ariadne/avatar_stack_component/avatar_stack_component.html.erb +0 -12
  127. data/app/components/ariadne/avatar_stack_component.rb +0 -75
  128. data/app/components/ariadne/base_button.rb +0 -70
  129. data/app/components/ariadne/blankslate_component/blankslate_component.html.erb +0 -26
  130. data/app/components/ariadne/blankslate_component.rb +0 -148
  131. data/app/components/ariadne/body_component.rb +0 -30
  132. data/app/components/ariadne/bottom_tab_component.html.erb +0 -4
  133. data/app/components/ariadne/bottom_tab_component.rb +0 -44
  134. data/app/components/ariadne/bottom_tab_nav_component.html.erb +0 -5
  135. data/app/components/ariadne/bottom_tab_nav_component.rb +0 -33
  136. data/app/components/ariadne/breadcrumbs_component.html.erb +0 -13
  137. data/app/components/ariadne/breadcrumbs_component.rb +0 -31
  138. data/app/components/ariadne/button_component/button_component.html.erb +0 -4
  139. data/app/components/ariadne/button_component.rb +0 -165
  140. data/app/components/ariadne/checkbox_component.html.erb +0 -5
  141. data/app/components/ariadne/checkbox_component.rb +0 -43
  142. data/app/components/ariadne/clipboard_copy_component/clipboard-copy-component.d.ts +0 -4
  143. data/app/components/ariadne/clipboard_copy_component/clipboard-copy-component.js +0 -18
  144. data/app/components/ariadne/clipboard_copy_component/clipboard-copy-component.ts +0 -19
  145. data/app/components/ariadne/clipboard_copy_component/clipboard_copy_component.html.erb +0 -9
  146. data/app/components/ariadne/clipboard_copy_component.rb +0 -90
  147. data/app/components/ariadne/close_button_component.html.erb +0 -4
  148. data/app/components/ariadne/close_button_component.rb +0 -33
  149. data/app/components/ariadne/combobox_component.html.erb +0 -14
  150. data/app/components/ariadne/combobox_component.rb +0 -76
  151. data/app/components/ariadne/component.rb +0 -127
  152. data/app/components/ariadne/container_component/container_component.html.erb +0 -3
  153. data/app/components/ariadne/container_component.rb +0 -25
  154. data/app/components/ariadne/content.rb +0 -12
  155. data/app/components/ariadne/counter_component.rb +0 -100
  156. data/app/components/ariadne/details_component/details_component.html.erb +0 -4
  157. data/app/components/ariadne/details_component.rb +0 -81
  158. data/app/components/ariadne/dropdown/menu_component.d.ts +0 -1
  159. data/app/components/ariadne/dropdown/menu_component.html.erb +0 -20
  160. data/app/components/ariadne/dropdown/menu_component.js +0 -1
  161. data/app/components/ariadne/dropdown/menu_component.rb +0 -101
  162. data/app/components/ariadne/dropdown/menu_component.ts +0 -1
  163. data/app/components/ariadne/dropdown_component/dropdown_component.html.erb +0 -8
  164. data/app/components/ariadne/dropdown_component.rb +0 -172
  165. data/app/components/ariadne/events_controller/events_controller.d.ts +0 -4
  166. data/app/components/ariadne/events_controller/events_controller.js +0 -6
  167. data/app/components/ariadne/events_controller/events_controller.ts +0 -7
  168. data/app/components/ariadne/flash_component/flash_component.html.erb +0 -31
  169. data/app/components/ariadne/flash_component.rb +0 -128
  170. data/app/components/ariadne/flex_component/flex_component.html.erb +0 -5
  171. data/app/components/ariadne/flex_component.rb +0 -56
  172. data/app/components/ariadne/footer_component/footer_component.html.erb +0 -7
  173. data/app/components/ariadne/footer_component.rb +0 -23
  174. data/app/components/ariadne/grid_component/grid_component.html.erb +0 -26
  175. data/app/components/ariadne/grid_component.rb +0 -67
  176. data/app/components/ariadne/header_component/header_component.html.erb +0 -29
  177. data/app/components/ariadne/header_component.rb +0 -111
  178. data/app/components/ariadne/heading_component.rb +0 -49
  179. data/app/components/ariadne/heroicon_component/heroicon_component.html.erb +0 -4
  180. data/app/components/ariadne/heroicon_component.rb +0 -166
  181. data/app/components/ariadne/image_component.rb +0 -53
  182. data/app/components/ariadne/inline_flex_component/inline_flex_component.html.erb +0 -6
  183. data/app/components/ariadne/inline_flex_component.rb +0 -72
  184. data/app/components/ariadne/layout_component.html.erb +0 -21
  185. data/app/components/ariadne/layout_component.rb +0 -69
  186. data/app/components/ariadne/link_component.rb +0 -65
  187. data/app/components/ariadne/list_component/list_component.html.erb +0 -3
  188. data/app/components/ariadne/list_component.rb +0 -70
  189. data/app/components/ariadne/modal_component.html.erb +0 -11
  190. data/app/components/ariadne/modal_component.rb +0 -88
  191. data/app/components/ariadne/narrow_container_component/narrow_container_component.html.erb +0 -3
  192. data/app/components/ariadne/narrow_container_component.rb +0 -30
  193. data/app/components/ariadne/options_controller/options_controller.d.ts +0 -39
  194. data/app/components/ariadne/options_controller/options_controller.js +0 -89
  195. data/app/components/ariadne/options_controller/options_controller.ts +0 -122
  196. data/app/components/ariadne/outlet_manager_controller/outlet_manager_controller.d.ts +0 -42
  197. data/app/components/ariadne/outlet_manager_controller/outlet_manager_controller.js +0 -237
  198. data/app/components/ariadne/outlet_manager_controller/outlet_manager_controller.ts +0 -278
  199. data/app/components/ariadne/panel_bar_component/panel_bar_component.html.erb +0 -20
  200. data/app/components/ariadne/panel_bar_component.rb +0 -80
  201. data/app/components/ariadne/pill_component/pill_component.html.erb +0 -3
  202. data/app/components/ariadne/pill_component.rb +0 -44
  203. data/app/components/ariadne/popover_component.html.erb +0 -10
  204. data/app/components/ariadne/popover_component.rb +0 -81
  205. data/app/components/ariadne/progress_bar_component.html.erb +0 -5
  206. data/app/components/ariadne/progress_bar_component.rb +0 -63
  207. data/app/components/ariadne/relative_time_component.html.erb +0 -3
  208. data/app/components/ariadne/relative_time_component.rb +0 -61
  209. data/app/components/ariadne/show_more_button_component.html.erb +0 -11
  210. data/app/components/ariadne/show_more_button_component.rb +0 -47
  211. data/app/components/ariadne/slideover_component/slideover-component.d.ts +0 -9
  212. data/app/components/ariadne/slideover_component/slideover-component.js +0 -11
  213. data/app/components/ariadne/slideover_component/slideover-component.ts +0 -17
  214. data/app/components/ariadne/slideover_component/slideover_component.html.erb +0 -9
  215. data/app/components/ariadne/slideover_component.rb +0 -66
  216. data/app/components/ariadne/spinner_component.html.erb +0 -16
  217. data/app/components/ariadne/spinner_component.rb +0 -45
  218. data/app/components/ariadne/string_match_controller/string_match_controller.d.ts +0 -27
  219. data/app/components/ariadne/string_match_controller/string_match_controller.js +0 -51
  220. data/app/components/ariadne/string_match_controller/string_match_controller.ts +0 -65
  221. data/app/components/ariadne/subheader_component.html.erb +0 -11
  222. data/app/components/ariadne/subheader_component.rb +0 -65
  223. data/app/components/ariadne/synced_boolean_attributes_controller/synced_boolean_attributes_controller.d.ts +0 -48
  224. data/app/components/ariadne/synced_boolean_attributes_controller/synced_boolean_attributes_controller.js +0 -207
  225. data/app/components/ariadne/synced_boolean_attributes_controller/synced_boolean_attributes_controller.ts +0 -256
  226. data/app/components/ariadne/tab_component/tab_component.html.erb +0 -3
  227. data/app/components/ariadne/tab_component.rb +0 -98
  228. data/app/components/ariadne/tab_container_component/tab-container-component.d.ts +0 -1
  229. data/app/components/ariadne/tab_container_component/tab-container-component.js +0 -23
  230. data/app/components/ariadne/tab_container_component/tab-container-component.ts +0 -24
  231. data/app/components/ariadne/tab_container_component.erb +0 -10
  232. data/app/components/ariadne/tab_container_component.rb +0 -68
  233. data/app/components/ariadne/tab_nav_component/tab-nav-component.d.ts +0 -9
  234. data/app/components/ariadne/tab_nav_component/tab-nav-component.js +0 -33
  235. data/app/components/ariadne/tab_nav_component/tab-nav-component.ts +0 -34
  236. data/app/components/ariadne/tab_nav_component/tab_nav_component.html.erb +0 -7
  237. data/app/components/ariadne/tab_nav_component.rb +0 -72
  238. data/app/components/ariadne/table_nav_component/table_nav_component.html.erb +0 -52
  239. data/app/components/ariadne/table_nav_component.rb +0 -338
  240. data/app/components/ariadne/text.rb +0 -25
  241. data/app/components/ariadne/time_ago_component/time-ago-component.d.ts +0 -1
  242. data/app/components/ariadne/time_ago_component/time-ago-component.js +0 -1
  243. data/app/components/ariadne/time_ago_component/time-ago-component.ts +0 -1
  244. data/app/components/ariadne/time_ago_component.rb +0 -56
  245. data/app/components/ariadne/timeline_component/timeline_component.html.erb +0 -19
  246. data/app/components/ariadne/timeline_component.rb +0 -34
  247. data/app/components/ariadne/toggle_component/toggle_component.html.erb +0 -15
  248. data/app/components/ariadne/toggle_component.rb +0 -95
  249. data/app/components/ariadne/toggleable_controller/toggleable_controller.d.ts +0 -34
  250. data/app/components/ariadne/toggleable_controller/toggleable_controller.js +0 -54
  251. data/app/components/ariadne/toggleable_controller/toggleable_controller.ts +0 -77
  252. data/app/components/ariadne/tooltip_component/tooltip-component.d.ts +0 -24
  253. data/app/components/ariadne/tooltip_component/tooltip-component.js +0 -43
  254. data/app/components/ariadne/tooltip_component/tooltip-component.ts +0 -57
  255. data/app/components/ariadne/tooltip_component/tooltip_component.html.erb +0 -4
  256. data/app/components/ariadne/tooltip_component.rb +0 -108
  257. data/app/lib/ariadne/action_view_extensions/form_helper.rb +0 -30
  258. data/app/lib/ariadne/audited/dsl.rb +0 -32
  259. data/app/lib/ariadne/form_builder.rb +0 -80
  260. data/app/lib/ariadne/status/dsl.rb +0 -41
  261. data/config/importmap.rb +0 -3
  262. data/exe/tailwindcss +0 -21
  263. data/lib/rubocop/cop/ariadne/base_cop.rb +0 -26
  264. data/tailwind.config.js +0 -70
@@ -0,0 +1,102 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ module Ariadne
5
+ module UI
6
+ module ClipboardCopy
7
+ # Use `ClipboardCopy` to copy element text content or input values to the clipboard.
8
+ #
9
+ # @example Default
10
+ # <%= render(Ariadne::UI::ClipboardCopy::Component.new(value: "Text to copy", aria_label: "Copy text to the system clipboard" )) %>
11
+ #
12
+ # @example With text instead of icons
13
+ # <%= render(Ariadne::UI::ClipboardCopy::Component.new(value: "Text to copy", aria_label: "Copy text to the system clipboard" )) do %>
14
+ # Click to copy!
15
+ # <% end %>
16
+ #
17
+ # @example Copying from an element
18
+ # <%= render(Ariadne::UI::ClipboardCopy::Component.new(for_id: "blob-path", aria_label: "Copy text to the system clipboard" )) %>
19
+ # <div id="blob-path">src/index.js</div>
20
+ #
21
+ # @accessibility
22
+ # Always set an accessible label to help the user interact with the component.
23
+ class Component < Ariadne::UI::Button::Component
24
+ # @param value [Strinulus_g] Text to copy when the component is clicked.
25
+ option :value, optional: true
26
+ # @param for [String] If `value` is not provided, the element with this id will be copied.
27
+ option :for, optional: true
28
+
29
+ def initialize(**options)
30
+ super
31
+ validate!
32
+ end
33
+
34
+ accepts_html_attributes do |html_attrs|
35
+ prepend_controller(html_attrs)
36
+ prepend_action(html_attrs, "clipboard-copy->ariadne-ui-clipboard-copy#copy")
37
+ html_attrs[:value] = @value if @value.present?
38
+ html_attrs[:for] = @for if @for.present?
39
+ end
40
+
41
+ def before_render
42
+ validate_aria_label!(html_attrs) if content.blank?
43
+ end
44
+
45
+ private def validate!
46
+ raise ArgumentError, "Must provide either `value` or `for`" if @value.blank? && @for.blank?
47
+ raise ArgumentError, "Must provide only `value` or `for`, not both" if @value.present? && @for.present?
48
+ end
49
+
50
+ def classes
51
+ style(:button, size:) + style(:size)
52
+ end
53
+
54
+ style do
55
+ base do
56
+ [
57
+ "ariadne-inline-flex",
58
+ "ariadne-items-center",
59
+ "ariadne-justify-center",
60
+ ]
61
+ end
62
+
63
+ variants do
64
+ size do
65
+ xs do
66
+ [
67
+ "ariadne-gap-0.5",
68
+ "ariadne-text-xs-md",
69
+ "ariadne-rounded",
70
+ "[&>svg]:ariadne-size-3",
71
+ ]
72
+ end
73
+ sm do
74
+ [
75
+ "ariadne-gap-0.5",
76
+ "ariadne-px-1",
77
+ "ariadne-text-sm-md",
78
+ "ariadne-rounded",
79
+ "[&>svg]:ariadne-size-4",
80
+ ]
81
+ end
82
+ base do
83
+ ["ariadne-gap-1", "ariadne-text-base", "ariadne-rounded-md"]
84
+ end
85
+ md do
86
+ [
87
+ "ariadne-gap-1",
88
+ "ariadne-text-base",
89
+ "ariadne-rounded-lg",
90
+ "[&>svg]:ariadne-size-5",
91
+ ]
92
+ end
93
+ lg do
94
+ ["ariadne-gap-1", "ariadne-text-lg", "ariadne-rounded-lg"]
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,54 @@
1
+ import {controllerFactory} from '@utils/createController'
2
+
3
+ import '@github/clipboard-copy-element'
4
+
5
+ const CLIPBOARD_COPY_TIMER_DURATION = 2000
6
+
7
+ export default class ClipboardCopyController extends controllerFactory<HTMLDetailsElement>()({
8
+ targets: {
9
+ initial: HTMLElement,
10
+ confirmed: null,
11
+ },
12
+ }) {
13
+ declare clipboardCopyElementTimers: WeakMap<HTMLElement, number>
14
+
15
+ connect() {
16
+ this.clipboardCopyElementTimers = new WeakMap()
17
+ }
18
+
19
+ copy(event: Event) {
20
+ const target = event.target as HTMLElement
21
+ const currentTimeout = this.clipboardCopyElementTimers.get(target)
22
+
23
+ if (currentTimeout) {
24
+ clearTimeout(currentTimeout)
25
+ this.clipboardCopyElementTimers.delete(target)
26
+ } else {
27
+ this.showConfirm()
28
+ }
29
+
30
+ this.clipboardCopyElementTimers.set(
31
+ target,
32
+ window.setTimeout(() => {
33
+ this.showInitial()
34
+ this.clipboardCopyElementTimers.delete(target)
35
+ }, CLIPBOARD_COPY_TIMER_DURATION),
36
+ )
37
+ }
38
+
39
+ showConfirm() {
40
+ if (this.hasConfirmedTarget) {
41
+ this.confirmedTarget.classList.remove('hidden')
42
+ this.confirmedTarget.classList.add('inline-block')
43
+ this.initialTarget.classList.add('hidden')
44
+ }
45
+ }
46
+
47
+ showInitial() {
48
+ this.initialTarget.classList.remove('hidden')
49
+ this.initialTarget.classList.add('inline-block')
50
+ if (this.hasConfirmedTarget) {
51
+ this.confirmedTarget.classList.add('hidden')
52
+ }
53
+ }
54
+ }
@@ -0,0 +1,32 @@
1
+ <details class="inline" <%= html_attrs.to_html %>>
2
+ <summary class="list-none rounded" data-combobox-target="anchor">
3
+ <%= content %>
4
+ </summary>
5
+ <details-menu
6
+ class='<%= style(:popover, size:) %>'
7
+ role="menu"
8
+ data-combobox-target="popover"
9
+ data-action="details-menu-selected->combobox#close">
10
+ <% if show_search == :yes %>
11
+ <div class="p-1 pb-0">
12
+ <%= render UI::Input::Component.new(
13
+ theme: :soft,
14
+ width: :full,
15
+ html_attrs: input_attrs
16
+ ) %>
17
+ </div>
18
+ <% end %>
19
+ <div class="<%= style(:options) %>"
20
+ data-combobox-target="options">
21
+ <%= options %>
22
+ <div class="text-center" data-input-filter-target="emptyRoot">
23
+ <% if any_options? %>
24
+ <%= "no_options" %>
25
+ <% else %>
26
+ <span class="py-3 text-sm text-zinc-600 dark:text-zinc-400"><%= translate(:no_options) %></span>
27
+ <% end %>
28
+ </div>
29
+ </div>
30
+ </details-menu>
31
+ </details>
32
+ <%= selector %>
@@ -0,0 +1,83 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ module Ariadne
5
+ module UI
6
+ module Combobox
7
+ class Component < BaseComponent
8
+ option :placement, default: proc { "bottom" }
9
+ option :show_search, default: proc { :yes }
10
+ option :size, default: proc { :md }
11
+
12
+ option :clamped, default: proc { false }
13
+
14
+ option :changed_action, optional: true
15
+
16
+ option :filter_placeholder, default: proc { "Filter…" }
17
+
18
+ renders_many :options, Ariadne::UI::Combobox::MenuItem::Component
19
+ renders_one :selector, ->(size:, icon:) do
20
+ render(Ariadne::UI::Button::Component.new(theme: :ghost, size: size, icon_only: :yes)) do
21
+ render(Ariadne::UI::Heroicon::Component.new(icon: icon, variant: :solid, size: size))
22
+ end
23
+ end
24
+
25
+ def any_options?
26
+ options.any?
27
+ end
28
+
29
+ accepts_html_attributes do |html_attrs|
30
+ html_attrs[:data] = {
31
+ controller: "input-filter #{stimulus_name}",
32
+ "#{stimulus_name}-placement-value": placement,
33
+ "#{stimulus_name}-clamped-value": clamped,
34
+ }
35
+ if changed_action
36
+ html_attrs[:data][:action] = "#{stimulus_name}:changed->#{changed_action}"
37
+ end
38
+ end
39
+
40
+ accepts_html_attributes_for :input,
41
+ autocomplete: "off",
42
+ autofocus: true,
43
+ type: "search",
44
+ placeholder: proc { @filter_placeholder },
45
+ data: {
46
+ "input-filter-target": "input",
47
+ "#{stimulus_name}-target": "searchInput",
48
+ "action": "input-filter#handleInput",
49
+ }
50
+
51
+ style :popover do
52
+ base do
53
+ [
54
+ "fixed",
55
+ "max-w-[90vw]",
56
+ "z-20",
57
+ "scroll",
58
+ "scrollbar-trigger",
59
+ "rounded-lg",
60
+ "shadow-lg",
61
+ "bg-white",
62
+ "dark:bg-zinc-900",
63
+ ]
64
+ end
65
+
66
+ variants do
67
+ size do
68
+ sm { "w-36" }
69
+ md { "w-52" }
70
+ lg { "w-96" }
71
+ end
72
+ end
73
+ end
74
+
75
+ style :options do
76
+ base do
77
+ ["overflow-auto", "p-2"]
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,119 @@
1
+ import {type Placement, autoUpdate, computePosition, flip, offset, shift, size} from '@floating-ui/dom'
2
+ import {controllerFactory} from '@utils/createController'
3
+ import {useClickOutside, useMutation} from 'stimulus-use'
4
+
5
+ export default class ComboboxController extends controllerFactory<HTMLDetailsElement>()({
6
+ targets: {
7
+ anchor: null,
8
+ options: null,
9
+ popover: null,
10
+ searchInput: HTMLInputElement,
11
+ },
12
+ values: {
13
+ clamped: Boolean,
14
+ placement: String,
15
+ },
16
+ }) {
17
+ private changedIds = new Set<string>()
18
+ private clickHandlers: Array<() => void> = []
19
+ labels: Array<{el: HTMLLabelElement; searchString: string}>
20
+ unsubAutoUpdate: (() => void) | undefined
21
+
22
+ private setupClickHandlers() {
23
+ const cb = () => this.toggle()
24
+
25
+ for (const fn of this.clickHandlers) {
26
+ fn()
27
+ }
28
+ this.clickHandlers = []
29
+
30
+ for (const el of this.anchorTarget.querySelectorAll('button, [tabindex]:not([tabindex="-1"])')) {
31
+ el.addEventListener('click', cb)
32
+ this.clickHandlers.push(() => el.removeEventListener('click', cb))
33
+ }
34
+ }
35
+
36
+ checkboxClicked(e: Event) {
37
+ const target = e.target as HTMLInputElement
38
+ const value = target.value
39
+ if (this.changedIds.has(value)) {
40
+ this.changedIds.delete(value)
41
+ } else {
42
+ this.changedIds.add(value)
43
+ }
44
+ this.dispatch('clicked', {detail: value})
45
+ }
46
+
47
+ clickOutside() {
48
+ this.element.open = false
49
+ this.setupAutoUpdate()
50
+ this.close()
51
+ }
52
+
53
+ close() {
54
+ if (this.hasSearchInputTarget) this.searchInputTarget.value = ''
55
+ if (this.changedIds.size > 0) {
56
+ this.dispatch('changed')
57
+ this.changedIds.clear()
58
+ }
59
+ }
60
+
61
+ connect() {
62
+ useClickOutside(this)
63
+ useMutation(this, {childList: true, subtree: true})
64
+ this.setupAutoUpdate()
65
+ this.setupClickHandlers()
66
+ }
67
+
68
+ disconnect() {
69
+ this.unsubAutoUpdate?.()
70
+ }
71
+
72
+ setupAutoUpdate(): void {
73
+ if (!this.element.open) {
74
+ this.unsubAutoUpdate?.()
75
+ return
76
+ }
77
+
78
+ const updatePopoverPosition = (): void => {
79
+ const options = this.optionsTarget
80
+ const searchInput = this.hasSearchInputTarget ? this.searchInputTarget : null
81
+ const shouldClamp = this.clampedValue
82
+
83
+ void computePosition(this.anchorTarget, this.popoverTarget, {
84
+ middleware: [
85
+ offset(6),
86
+ flip(),
87
+ shift({padding: 6}),
88
+ size({
89
+ apply({availableHeight}) {
90
+ const inputHeight = searchInput ? searchInput.getBoundingClientRect().height : 0
91
+
92
+ let maxHeight = availableHeight - inputHeight - 6
93
+ if (shouldClamp && maxHeight > 400) maxHeight = 400
94
+
95
+ Object.assign(options.style, {
96
+ maxHeight: `${maxHeight}px`,
97
+ })
98
+ },
99
+ }),
100
+ ],
101
+ placement: this.placementValue as Placement,
102
+ strategy: 'fixed',
103
+ }).then(({x, y}) => {
104
+ Object.assign(this.popoverTarget.style, {
105
+ left: `${x}px`,
106
+ top: `${y}px`,
107
+ })
108
+ })
109
+ }
110
+
111
+ updatePopoverPosition()
112
+ this.unsubAutoUpdate = autoUpdate(this.anchorTarget, this.popoverTarget, updatePopoverPosition)
113
+ }
114
+
115
+ toggle(): void {
116
+ this.element.open = !this.element.open
117
+ this.setupAutoUpdate()
118
+ }
119
+ }
@@ -0,0 +1,9 @@
1
+ <% slot = capture do %>
2
+ <span class="truncate"><%= label %></span>
3
+ <% end %>
4
+
5
+ <%= content_tag(
6
+ link? ? :a : :button,
7
+ slot,
8
+ html_attrs,
9
+ ) %>
@@ -0,0 +1,53 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ module Ariadne
5
+ module UI
6
+ module Combobox
7
+ module MenuItem
8
+ class Component < BaseComponent
9
+ option :label
10
+ option :as, default: proc { :link } # :button
11
+
12
+ accepts_html_attributes do |html_attrs|
13
+ html_attrs[:class] = style(as:)
14
+ html_attrs["data-input-filter-target"] = "searchString"
15
+ if as == :link && !html_attrs[:target]
16
+ html_attrs[:target] = "_top"
17
+ end
18
+ end
19
+
20
+ style do
21
+ base do
22
+ [
23
+ "flex",
24
+ "gap-0.5",
25
+ "items-center",
26
+ "ps-2",
27
+ "pe-1",
28
+ "text-ui-base",
29
+ "rounded",
30
+ "!ring-0",
31
+ "hover:bg-zinc-100",
32
+ "hover:dark:bg-zinc-800",
33
+ "focus-within:bg-zinc-100",
34
+ "focus-within:dark:bg-zinc-800",
35
+ ]
36
+ end
37
+ variants do
38
+ as do
39
+ button do
40
+ "appearance-none inline-flex w-full"
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ def link?
47
+ as == :link
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,11 @@
1
+ <label
2
+ tabindex="0"
3
+ role="menuitemcheckbox"
4
+ class="<%= style %>">
5
+ <span class="px-1"><%= option_component %></span>
6
+ <span
7
+ class="truncate <%= text_content ? 'px-1' : 'inline-flex items-center' %>"
8
+ data-input-filter-target="searchString">
9
+ <%= content %>
10
+ </span>
11
+ </label>
@@ -0,0 +1,45 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ module Ariadne
5
+ module UI
6
+ module Combobox
7
+ module Option
8
+ class Component < BaseComponent
9
+ option :as, default: proc { :link } # :button
10
+
11
+ option :type, default: proc { :multiple }
12
+ option :text_content, default: proc { true }
13
+
14
+ accepts_html_attributes disabled: false,
15
+ tabindex: -1,
16
+ data: proc {
17
+ { action: "#{UI::Combobox::Component.stimulus_name}#checkboxClicked" }
18
+ }
19
+
20
+ def option_component
21
+ cmp = type == :multiple ? Checkbox : Radio
22
+ render(cmp::Component.new(html_attrs:))
23
+ end
24
+
25
+ style do
26
+ base do
27
+ [
28
+ "flex",
29
+ "gap-0.5",
30
+ "items-center",
31
+ "text-ui-base",
32
+ "rounded",
33
+ "!ring-0",
34
+ "hover:bg-zinc-100",
35
+ "hover:dark:bg-zinc-800",
36
+ "focus-within:bg-zinc-100",
37
+ "focus-within:dark:bg-zinc-800",
38
+ ]
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,3 @@
1
+ <svg class="<%= html_attrs[:class] %>" <%= svg_attributes %>>
2
+ <%= inner %>
3
+ </svg>
@@ -0,0 +1,141 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ require "heroicons_helper"
5
+
6
+ module Ariadne
7
+ module UI
8
+ module Heroicon
9
+ # @example Default
10
+ # <%= render(Ariadne::UI::Heroicon::Component.new(icon: :check, variant: HeroiconsHelper::Icon::VARIANT_OUTLINE)) %>
11
+ # <%= render(Ariadne::UI::Heroicon::Component.new(icon: :check, variant: HeroiconsHelper::Icon::VARIANT_SOLID)) %>
12
+ #
13
+ # @example Medium
14
+ # <%= render(Ariadne::UI::Heroicon::Component.new(icon: :"user-group", variant: HeroiconsHelper::Icon::VARIANT_OUTLINE, size: :md)) %>
15
+ #
16
+ # @example Helper
17
+ # <%= ariadne_heroicon(icon: :check, variant: HeroiconsHelper::Icon::VARIANT_OUTLINE) %>
18
+ #
19
+ # @param tag [Symbol, String] The rendered tag name
20
+ # @param classes [String] <%= link_to_classes_docs %>
21
+ # @param icon [Symbol, String] Name of <%= link_to_heroicons %> to use.
22
+ # @param variant [String] <%= one_of(HeroiconsHelper::Icon::VALID_VARIANTS, sort: false) %>
23
+ # @param size [Symbol] <%= one_of(Ariadne::UI::Heroicon::Component::SIZE_MAPPINGS, sort: false) %>
24
+ class Component < Ariadne::BaseComponent
25
+ option :icon
26
+ option :variant
27
+ option :size, default: -> { :md }
28
+
29
+ include IconHelper
30
+ include HeroiconsHelper
31
+
32
+ PRELOADED_ICONS = [
33
+ {
34
+ name: "bell",
35
+ variant: HeroiconsHelper::Icon::VARIANT_OUTLINE,
36
+ },
37
+ {
38
+ name: "check",
39
+ variant: HeroiconsHelper::Icon::VARIANT_OUTLINE,
40
+ },
41
+ {
42
+ name: "chevron-down",
43
+ variant: HeroiconsHelper::Icon::VARIANT_OUTLINE,
44
+ },
45
+ {
46
+ name: "clipboard",
47
+ variant: HeroiconsHelper::Icon::VARIANT_OUTLINE,
48
+ },
49
+ {
50
+ name: "clock",
51
+ variant: HeroiconsHelper::Icon::VARIANT_OUTLINE,
52
+ },
53
+ {
54
+ name: "information-circle",
55
+ variant: HeroiconsHelper::Icon::VARIANT_OUTLINE,
56
+ },
57
+ {
58
+ name: "dots-horizontal",
59
+ variant: HeroiconsHelper::Icon::VARIANT_OUTLINE,
60
+ },
61
+ {
62
+ name: "link",
63
+ variant: HeroiconsHelper::Icon::VARIANT_OUTLINE,
64
+ },
65
+ {
66
+ name: "lock-closed",
67
+ variant: HeroiconsHelper::Icon::VARIANT_OUTLINE,
68
+ },
69
+ {
70
+ name: "mail",
71
+ variant: HeroiconsHelper::Icon::VARIANT_OUTLINE,
72
+ },
73
+ {
74
+ name: "menu",
75
+ variant: HeroiconsHelper::Icon::VARIANT_OUTLINE,
76
+ },
77
+ {
78
+ name: "pencil",
79
+ variant: HeroiconsHelper::Icon::VARIANT_OUTLINE,
80
+ },
81
+ {
82
+ name: "plus-sm",
83
+ variant: HeroiconsHelper::Icon::VARIANT_OUTLINE,
84
+ },
85
+ {
86
+ name: "question-mark-circle",
87
+ variant: HeroiconsHelper::Icon::VARIANT_OUTLINE,
88
+ },
89
+ {
90
+ name: "search",
91
+ variant: HeroiconsHelper::Icon::VARIANT_OUTLINE,
92
+ },
93
+ {
94
+ name: "search",
95
+ variant: HeroiconsHelper::Icon::VARIANT_OUTLINE,
96
+ },
97
+ {
98
+ name: "trash",
99
+ variant: HeroiconsHelper::Icon::VARIANT_OUTLINE,
100
+ },
101
+ {
102
+ name: "x-mark",
103
+ variant: HeroiconsHelper::Icon::VARIANT_OUTLINE,
104
+ },
105
+ ].freeze
106
+
107
+ accepts_html_attributes do |html_attrs|
108
+ html_attrs[:class] = Ariadne::ViewComponents.tailwind_merger.merge([@heroicon.attributes["class"], style(size: @size), html_attrs[:class]].join(" "))
109
+ end
110
+
111
+ def initialize(**options)
112
+ super
113
+
114
+ check_icon_presence!(icon, variant)
115
+
116
+ @heroicon = heroicon(@icon, variant: @variant)
117
+ end
118
+
119
+ def svg_attributes
120
+ tag.attributes(html_attrs.except(:class).merge(@heroicon.attributes.except("class", "aria-hidden")))
121
+ end
122
+
123
+ def inner
124
+ ActiveSupport::SafeBuffer.new(@heroicon.path)
125
+ end
126
+
127
+ style do
128
+ variants do
129
+ size do
130
+ xs { ["ariadne-w-4", "ariadne-h-4"] }
131
+ sm { ["ariadne-w-5", "ariadne-h-5"] }
132
+ md { ["ariadne-w-6", "ariadne-h-6"] }
133
+ lg { ["ariadne-w-10", "ariadne-h-10"] }
134
+ xl { ["ariadne-w-12", "ariadne-h-12"] }
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end