shadcn_phlexcomponents 0.1.9 → 0.1.14

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 (256) hide show
  1. checksums.yaml +4 -4
  2. data/app/javascript/controllers/accordion_controller.ts +136 -0
  3. data/app/javascript/controllers/alert_dialog_controller.ts +12 -0
  4. data/app/javascript/controllers/avatar_controller.ts +24 -0
  5. data/app/javascript/controllers/checkbox_controller.ts +41 -0
  6. data/app/javascript/controllers/collapsible_controller.ts +52 -0
  7. data/app/javascript/controllers/combobox_controller.ts +376 -0
  8. data/app/javascript/controllers/command_controller.ts +301 -0
  9. data/app/javascript/controllers/date_picker_controller.ts +334 -0
  10. data/app/javascript/controllers/date_range_picker_controller.ts +253 -0
  11. data/app/javascript/controllers/dialog_controller.ts +115 -0
  12. data/app/javascript/controllers/dropdown_menu_controller.ts +309 -0
  13. data/app/javascript/controllers/dropdown_menu_sub_controller.ts +152 -0
  14. data/app/javascript/controllers/form_field_controller.ts +27 -0
  15. data/app/javascript/controllers/hover_card_controller.ts +103 -0
  16. data/app/javascript/controllers/{loading_button_controller.js → loading_button_controller.ts} +7 -2
  17. data/app/javascript/controllers/popover_controller.ts +118 -0
  18. data/app/javascript/controllers/progress_controller.ts +23 -0
  19. data/app/javascript/controllers/radio_group_controller.ts +113 -0
  20. data/app/javascript/controllers/select_controller.ts +341 -0
  21. data/app/javascript/controllers/{sidebar_controller.js → sidebar_controller.ts} +6 -2
  22. data/app/javascript/controllers/sidebar_trigger_controller.ts +21 -0
  23. data/app/javascript/controllers/slider_controller.ts +114 -0
  24. data/app/javascript/controllers/switch_controller.ts +37 -0
  25. data/app/javascript/controllers/tabs_controller.ts +87 -0
  26. data/app/javascript/controllers/theme_switcher_controller.ts +40 -0
  27. data/app/javascript/controllers/toast_container_controller.ts +67 -0
  28. data/app/javascript/controllers/toast_controller.ts +34 -0
  29. data/app/javascript/controllers/toggle_controller.ts +28 -0
  30. data/app/javascript/controllers/toggle_group_controller.ts +28 -0
  31. data/app/javascript/controllers/tooltip_controller.ts +110 -0
  32. data/app/javascript/shadcn_phlexcomponents.ts +61 -0
  33. data/app/javascript/utils/command.ts +544 -0
  34. data/app/javascript/utils/floating_ui.ts +196 -0
  35. data/app/javascript/utils/index.ts +417 -0
  36. data/app/stylesheets/date_picker.css +81 -101
  37. data/app/stylesheets/nouislider.css +173 -0
  38. data/app/stylesheets/tw-animate.css +486 -0
  39. data/lib/install/install_shadcn_phlexcomponents.rb +16 -3
  40. data/lib/shadcn_phlexcomponents/alias.rb +6 -1
  41. data/lib/shadcn_phlexcomponents/components/accordion.rb +130 -0
  42. data/lib/shadcn_phlexcomponents/components/alert.rb +59 -0
  43. data/lib/shadcn_phlexcomponents/components/alert_dialog.rb +279 -0
  44. data/lib/shadcn_phlexcomponents/components/{aspect_ratio/aspect_ratio.rb → aspect_ratio.rb} +2 -2
  45. data/lib/shadcn_phlexcomponents/components/avatar.rb +63 -0
  46. data/lib/shadcn_phlexcomponents/components/badge.rb +35 -0
  47. data/lib/shadcn_phlexcomponents/components/base.rb +48 -7
  48. data/lib/shadcn_phlexcomponents/components/breadcrumb.rb +150 -0
  49. data/lib/shadcn_phlexcomponents/components/button.rb +49 -0
  50. data/lib/shadcn_phlexcomponents/components/card.rb +88 -0
  51. data/lib/shadcn_phlexcomponents/components/{checkbox/checkbox.rb → checkbox.rb} +18 -14
  52. data/lib/shadcn_phlexcomponents/components/{checkbox_group/checkbox_group.rb → checkbox_group.rb} +7 -8
  53. data/lib/shadcn_phlexcomponents/components/collapsible.rb +90 -0
  54. data/lib/shadcn_phlexcomponents/components/combobox.rb +428 -0
  55. data/lib/shadcn_phlexcomponents/components/command.rb +381 -0
  56. data/lib/shadcn_phlexcomponents/components/date_picker.rb +208 -0
  57. data/lib/shadcn_phlexcomponents/components/date_range_picker.rb +143 -0
  58. data/lib/shadcn_phlexcomponents/components/dialog.rb +236 -0
  59. data/lib/shadcn_phlexcomponents/components/dropdown_menu.rb +283 -0
  60. data/lib/shadcn_phlexcomponents/components/dropdown_menu_sub.rb +136 -0
  61. data/lib/shadcn_phlexcomponents/components/form/form_checkbox.rb +6 -7
  62. data/lib/shadcn_phlexcomponents/components/form/form_checkbox_group.rb +2 -2
  63. data/lib/shadcn_phlexcomponents/components/form/form_combobox.rb +64 -0
  64. data/lib/shadcn_phlexcomponents/components/form/form_date_picker.rb +3 -4
  65. data/lib/shadcn_phlexcomponents/components/form/form_date_range_picker.rb +27 -41
  66. data/lib/shadcn_phlexcomponents/components/form/form_error.rb +1 -1
  67. data/lib/shadcn_phlexcomponents/components/form/form_helpers.rb +43 -8
  68. data/lib/shadcn_phlexcomponents/components/form/form_hint.rb +1 -1
  69. data/lib/shadcn_phlexcomponents/components/form/form_input.rb +3 -4
  70. data/lib/shadcn_phlexcomponents/components/form/form_radio_group.rb +4 -5
  71. data/lib/shadcn_phlexcomponents/components/form/form_select.rb +3 -4
  72. data/lib/shadcn_phlexcomponents/components/form/form_slider.rb +91 -0
  73. data/lib/shadcn_phlexcomponents/components/form/form_switch.rb +7 -6
  74. data/lib/shadcn_phlexcomponents/components/form/form_textarea.rb +3 -4
  75. data/lib/shadcn_phlexcomponents/components/{form/form.rb → form.rb} +36 -4
  76. data/lib/shadcn_phlexcomponents/components/hover_card.rb +111 -0
  77. data/lib/shadcn_phlexcomponents/components/input.rb +31 -0
  78. data/lib/shadcn_phlexcomponents/components/label.rb +16 -0
  79. data/lib/shadcn_phlexcomponents/components/{link/link.rb → link.rb} +10 -3
  80. data/lib/shadcn_phlexcomponents/components/{loading_button/loading_button.rb → loading_button.rb} +9 -2
  81. data/lib/shadcn_phlexcomponents/components/pagination.rb +166 -0
  82. data/lib/shadcn_phlexcomponents/components/popover.rb +116 -0
  83. data/lib/shadcn_phlexcomponents/components/{progress/progress.rb → progress.rb} +4 -4
  84. data/lib/shadcn_phlexcomponents/components/radio_group.rb +155 -0
  85. data/lib/shadcn_phlexcomponents/components/select.rb +406 -0
  86. data/lib/shadcn_phlexcomponents/components/{separator/separator.rb → separator.rb} +9 -8
  87. data/lib/shadcn_phlexcomponents/components/sheet.rb +243 -0
  88. data/lib/shadcn_phlexcomponents/components/{skeleton/skeleton.rb → skeleton.rb} +1 -1
  89. data/lib/shadcn_phlexcomponents/components/slider.rb +72 -0
  90. data/lib/shadcn_phlexcomponents/components/switch.rb +75 -0
  91. data/lib/shadcn_phlexcomponents/components/table.rb +140 -0
  92. data/lib/shadcn_phlexcomponents/components/tabs.rb +135 -0
  93. data/lib/shadcn_phlexcomponents/components/textarea.rb +24 -0
  94. data/lib/shadcn_phlexcomponents/components/toast.rb +153 -0
  95. data/lib/shadcn_phlexcomponents/components/{toast/toast_container.rb → toast_container.rb} +23 -4
  96. data/lib/shadcn_phlexcomponents/components/toggle.rb +54 -0
  97. data/lib/shadcn_phlexcomponents/components/tooltip.rb +132 -0
  98. data/lib/shadcn_phlexcomponents/engine.rb +1 -5
  99. data/lib/shadcn_phlexcomponents/initializers/shadcn_phlexcomponents.rb +25 -0
  100. data/lib/shadcn_phlexcomponents/version.rb +1 -1
  101. data/lib/tasks/install.rake +1 -1
  102. metadata +83 -167
  103. data/app/javascript/controllers/accordion_controller.js +0 -124
  104. data/app/javascript/controllers/alert_dialog_controller.js +0 -21
  105. data/app/javascript/controllers/avatar_controller.js +0 -15
  106. data/app/javascript/controllers/checkbox_controller.js +0 -28
  107. data/app/javascript/controllers/collapsible_controller.js +0 -35
  108. data/app/javascript/controllers/combobox_controller.js +0 -54
  109. data/app/javascript/controllers/date_picker_controller.js +0 -253
  110. data/app/javascript/controllers/date_range_picker_controller.js +0 -344
  111. data/app/javascript/controllers/dialog_controller.js +0 -114
  112. data/app/javascript/controllers/dropdown_menu_controller.js +0 -171
  113. data/app/javascript/controllers/form_field_controller.js +0 -24
  114. data/app/javascript/controllers/hover_card_controller.js +0 -21
  115. data/app/javascript/controllers/popover_controller.js +0 -113
  116. data/app/javascript/controllers/progress_controller.js +0 -14
  117. data/app/javascript/controllers/radio_group_controller.js +0 -90
  118. data/app/javascript/controllers/select_controller.js +0 -274
  119. data/app/javascript/controllers/sidebar_trigger_controller.js +0 -15
  120. data/app/javascript/controllers/switch_controller.js +0 -24
  121. data/app/javascript/controllers/tabs_controller.js +0 -73
  122. data/app/javascript/controllers/theme_switcher_controller.js +0 -32
  123. data/app/javascript/controllers/toast_container_controller.js +0 -22
  124. data/app/javascript/controllers/toast_controller.js +0 -45
  125. data/app/javascript/controllers/tooltip_controller.js +0 -40
  126. data/app/javascript/shadcn_phlexcomponents.js +0 -53
  127. data/app/javascript/utils.js +0 -184
  128. data/app/stylesheets/choices.css +0 -324
  129. data/app/stylesheets/tailwindcss-animate.css +0 -318
  130. data/lib/shadcn_phlexcomponents/components/accordion/accordion.rb +0 -38
  131. data/lib/shadcn_phlexcomponents/components/accordion/accordion_content.rb +0 -30
  132. data/lib/shadcn_phlexcomponents/components/accordion/accordion_item.rb +0 -26
  133. data/lib/shadcn_phlexcomponents/components/accordion/accordion_trigger.rb +0 -46
  134. data/lib/shadcn_phlexcomponents/components/alert/alert.rb +0 -40
  135. data/lib/shadcn_phlexcomponents/components/alert/alert_description.rb +0 -11
  136. data/lib/shadcn_phlexcomponents/components/alert/alert_title.rb +0 -11
  137. data/lib/shadcn_phlexcomponents/components/alert_dialog/alert_dialog.rb +0 -60
  138. data/lib/shadcn_phlexcomponents/components/alert_dialog/alert_dialog_action.rb +0 -22
  139. data/lib/shadcn_phlexcomponents/components/alert_dialog/alert_dialog_action_to.rb +0 -40
  140. data/lib/shadcn_phlexcomponents/components/alert_dialog/alert_dialog_cancel.rb +0 -22
  141. data/lib/shadcn_phlexcomponents/components/alert_dialog/alert_dialog_content.rb +0 -40
  142. data/lib/shadcn_phlexcomponents/components/alert_dialog/alert_dialog_description.rb +0 -22
  143. data/lib/shadcn_phlexcomponents/components/alert_dialog/alert_dialog_footer.rb +0 -11
  144. data/lib/shadcn_phlexcomponents/components/alert_dialog/alert_dialog_header.rb +0 -11
  145. data/lib/shadcn_phlexcomponents/components/alert_dialog/alert_dialog_title.rb +0 -22
  146. data/lib/shadcn_phlexcomponents/components/alert_dialog/alert_dialog_trigger.rb +0 -50
  147. data/lib/shadcn_phlexcomponents/components/avatar/avatar.rb +0 -31
  148. data/lib/shadcn_phlexcomponents/components/avatar/avatar_fallback.rb +0 -21
  149. data/lib/shadcn_phlexcomponents/components/avatar/avatar_image.rb +0 -19
  150. data/lib/shadcn_phlexcomponents/components/badge/badge.rb +0 -30
  151. data/lib/shadcn_phlexcomponents/components/breadcrumb/breadcrumb.rb +0 -53
  152. data/lib/shadcn_phlexcomponents/components/breadcrumb/breadcrumb_ellipsis.rb +0 -23
  153. data/lib/shadcn_phlexcomponents/components/breadcrumb/breadcrumb_item.rb +0 -11
  154. data/lib/shadcn_phlexcomponents/components/breadcrumb/breadcrumb_link.rb +0 -7
  155. data/lib/shadcn_phlexcomponents/components/breadcrumb/breadcrumb_page.rb +0 -21
  156. data/lib/shadcn_phlexcomponents/components/breadcrumb/breadcrumb_separator.rb +0 -26
  157. data/lib/shadcn_phlexcomponents/components/button/button.rb +0 -53
  158. data/lib/shadcn_phlexcomponents/components/card/card.rb +0 -31
  159. data/lib/shadcn_phlexcomponents/components/card/card_content.rb +0 -11
  160. data/lib/shadcn_phlexcomponents/components/card/card_description.rb +0 -11
  161. data/lib/shadcn_phlexcomponents/components/card/card_footer.rb +0 -11
  162. data/lib/shadcn_phlexcomponents/components/card/card_header.rb +0 -11
  163. data/lib/shadcn_phlexcomponents/components/card/card_title.rb +0 -11
  164. data/lib/shadcn_phlexcomponents/components/collapsible/collapsible.rb +0 -31
  165. data/lib/shadcn_phlexcomponents/components/collapsible/collapsible_content.rb +0 -24
  166. data/lib/shadcn_phlexcomponents/components/collapsible/collapsible_trigger.rb +0 -50
  167. data/lib/shadcn_phlexcomponents/components/date_picker/date_picker.rb +0 -87
  168. data/lib/shadcn_phlexcomponents/components/date_picker/date_picker_content.rb +0 -45
  169. data/lib/shadcn_phlexcomponents/components/date_picker/date_picker_trigger.rb +0 -64
  170. data/lib/shadcn_phlexcomponents/components/date_range_picker/date_range_picker.rb +0 -105
  171. data/lib/shadcn_phlexcomponents/components/date_range_picker/date_range_picker_content.rb +0 -9
  172. data/lib/shadcn_phlexcomponents/components/date_range_picker/date_range_picker_trigger.rb +0 -9
  173. data/lib/shadcn_phlexcomponents/components/dialog/dialog.rb +0 -52
  174. data/lib/shadcn_phlexcomponents/components/dialog/dialog_close.rb +0 -42
  175. data/lib/shadcn_phlexcomponents/components/dialog/dialog_content.rb +0 -54
  176. data/lib/shadcn_phlexcomponents/components/dialog/dialog_description.rb +0 -22
  177. data/lib/shadcn_phlexcomponents/components/dialog/dialog_footer.rb +0 -11
  178. data/lib/shadcn_phlexcomponents/components/dialog/dialog_header.rb +0 -11
  179. data/lib/shadcn_phlexcomponents/components/dialog/dialog_title.rb +0 -22
  180. data/lib/shadcn_phlexcomponents/components/dialog/dialog_trigger.rb +0 -50
  181. data/lib/shadcn_phlexcomponents/components/dropdown_menu/dropdown_menu.rb +0 -50
  182. data/lib/shadcn_phlexcomponents/components/dropdown_menu/dropdown_menu_content.rb +0 -52
  183. data/lib/shadcn_phlexcomponents/components/dropdown_menu/dropdown_menu_item.rb +0 -56
  184. data/lib/shadcn_phlexcomponents/components/dropdown_menu/dropdown_menu_item_to.rb +0 -28
  185. data/lib/shadcn_phlexcomponents/components/dropdown_menu/dropdown_menu_label.rb +0 -11
  186. data/lib/shadcn_phlexcomponents/components/dropdown_menu/dropdown_menu_separator.rb +0 -20
  187. data/lib/shadcn_phlexcomponents/components/dropdown_menu/dropdown_menu_trigger.rb +0 -57
  188. data/lib/shadcn_phlexcomponents/components/hover_card/hover_card.rb +0 -33
  189. data/lib/shadcn_phlexcomponents/components/hover_card/hover_card_content.rb +0 -32
  190. data/lib/shadcn_phlexcomponents/components/hover_card/hover_card_trigger.rb +0 -44
  191. data/lib/shadcn_phlexcomponents/components/input/input.rb +0 -32
  192. data/lib/shadcn_phlexcomponents/components/label/label.rb +0 -14
  193. data/lib/shadcn_phlexcomponents/components/pagination/pagination.rb +0 -38
  194. data/lib/shadcn_phlexcomponents/components/pagination/pagination_ellipsis.rb +0 -24
  195. data/lib/shadcn_phlexcomponents/components/pagination/pagination_link.rb +0 -34
  196. data/lib/shadcn_phlexcomponents/components/pagination/pagination_next.rb +0 -32
  197. data/lib/shadcn_phlexcomponents/components/pagination/pagination_previous.rb +0 -32
  198. data/lib/shadcn_phlexcomponents/components/popover/popover.rb +0 -34
  199. data/lib/shadcn_phlexcomponents/components/popover/popover_content.rb +0 -40
  200. data/lib/shadcn_phlexcomponents/components/popover/popover_trigger.rb +0 -50
  201. data/lib/shadcn_phlexcomponents/components/radio_group/radio_group.rb +0 -88
  202. data/lib/shadcn_phlexcomponents/components/radio_group/radio_group_item.rb +0 -66
  203. data/lib/shadcn_phlexcomponents/components/select/select.rb +0 -194
  204. data/lib/shadcn_phlexcomponents/components/select/select_content.rb +0 -64
  205. data/lib/shadcn_phlexcomponents/components/select/select_group.rb +0 -23
  206. data/lib/shadcn_phlexcomponents/components/select/select_item.rb +0 -59
  207. data/lib/shadcn_phlexcomponents/components/select/select_label.rb +0 -24
  208. data/lib/shadcn_phlexcomponents/components/select/select_trigger.rb +0 -56
  209. data/lib/shadcn_phlexcomponents/components/sheet/sheet.rb +0 -53
  210. data/lib/shadcn_phlexcomponents/components/sheet/sheet_close.rb +0 -42
  211. data/lib/shadcn_phlexcomponents/components/sheet/sheet_content.rb +0 -65
  212. data/lib/shadcn_phlexcomponents/components/sheet/sheet_description.rb +0 -22
  213. data/lib/shadcn_phlexcomponents/components/sheet/sheet_footer.rb +0 -11
  214. data/lib/shadcn_phlexcomponents/components/sheet/sheet_header.rb +0 -11
  215. data/lib/shadcn_phlexcomponents/components/sheet/sheet_title.rb +0 -22
  216. data/lib/shadcn_phlexcomponents/components/sheet/sheet_trigger.rb +0 -50
  217. data/lib/shadcn_phlexcomponents/components/sidebar/sidebar.rb +0 -108
  218. data/lib/shadcn_phlexcomponents/components/sidebar/sidebar_container.rb +0 -11
  219. data/lib/shadcn_phlexcomponents/components/sidebar/sidebar_content.rb +0 -11
  220. data/lib/shadcn_phlexcomponents/components/sidebar/sidebar_footer.rb +0 -11
  221. data/lib/shadcn_phlexcomponents/components/sidebar/sidebar_group.rb +0 -11
  222. data/lib/shadcn_phlexcomponents/components/sidebar/sidebar_group_content.rb +0 -11
  223. data/lib/shadcn_phlexcomponents/components/sidebar/sidebar_group_label.rb +0 -16
  224. data/lib/shadcn_phlexcomponents/components/sidebar/sidebar_header.rb +0 -11
  225. data/lib/shadcn_phlexcomponents/components/sidebar/sidebar_inset.rb +0 -15
  226. data/lib/shadcn_phlexcomponents/components/sidebar/sidebar_menu.rb +0 -11
  227. data/lib/shadcn_phlexcomponents/components/sidebar/sidebar_menu_button.rb +0 -61
  228. data/lib/shadcn_phlexcomponents/components/sidebar/sidebar_menu_item.rb +0 -9
  229. data/lib/shadcn_phlexcomponents/components/sidebar/sidebar_menu_sub.rb +0 -14
  230. data/lib/shadcn_phlexcomponents/components/sidebar/sidebar_menu_sub_button.rb +0 -48
  231. data/lib/shadcn_phlexcomponents/components/sidebar/sidebar_menu_sub_item.rb +0 -9
  232. data/lib/shadcn_phlexcomponents/components/sidebar/sidebar_trigger.rb +0 -40
  233. data/lib/shadcn_phlexcomponents/components/switch/switch.rb +0 -66
  234. data/lib/shadcn_phlexcomponents/components/table/table.rb +0 -75
  235. data/lib/shadcn_phlexcomponents/components/table/table_body.rb +0 -11
  236. data/lib/shadcn_phlexcomponents/components/table/table_caption.rb +0 -11
  237. data/lib/shadcn_phlexcomponents/components/table/table_cell.rb +0 -11
  238. data/lib/shadcn_phlexcomponents/components/table/table_footer.rb +0 -11
  239. data/lib/shadcn_phlexcomponents/components/table/table_head.rb +0 -14
  240. data/lib/shadcn_phlexcomponents/components/table/table_header.rb +0 -11
  241. data/lib/shadcn_phlexcomponents/components/table/table_row.rb +0 -11
  242. data/lib/shadcn_phlexcomponents/components/tabs/tabs.rb +0 -38
  243. data/lib/shadcn_phlexcomponents/components/tabs/tabs_content.rb +0 -35
  244. data/lib/shadcn_phlexcomponents/components/tabs/tabs_list.rb +0 -23
  245. data/lib/shadcn_phlexcomponents/components/tabs/tabs_trigger.rb +0 -45
  246. data/lib/shadcn_phlexcomponents/components/textarea/textarea.rb +0 -29
  247. data/lib/shadcn_phlexcomponents/components/toast/toast.rb +0 -101
  248. data/lib/shadcn_phlexcomponents/components/toast/toast_action.rb +0 -39
  249. data/lib/shadcn_phlexcomponents/components/toast/toast_action_to.rb +0 -28
  250. data/lib/shadcn_phlexcomponents/components/toast/toast_content.rb +0 -11
  251. data/lib/shadcn_phlexcomponents/components/toast/toast_description.rb +0 -11
  252. data/lib/shadcn_phlexcomponents/components/toast/toast_title.rb +0 -11
  253. data/lib/shadcn_phlexcomponents/components/tooltip/tooltip.rb +0 -34
  254. data/lib/shadcn_phlexcomponents/components/tooltip/tooltip_content.rb +0 -39
  255. data/lib/shadcn_phlexcomponents/components/tooltip/tooltip_trigger.rb +0 -48
  256. /data/lib/shadcn_phlexcomponents/components/{theme_switcher/theme_switcher.rb → theme_switcher.rb} +0 -0
@@ -0,0 +1,341 @@
1
+ import { useClickOutside } from 'stimulus-use'
2
+ import { onKeydown, focusItemByIndex } from './dropdown_menu_controller'
3
+ import { initFloatingUi } from '../utils/floating_ui'
4
+ import {
5
+ getSameLevelItems,
6
+ focusTrigger,
7
+ hideContent,
8
+ showContent,
9
+ lockScroll,
10
+ unlockScroll,
11
+ onClickOutside,
12
+ setGroupLabelsId,
13
+ getNextEnabledIndex,
14
+ getPreviousEnabledIndex,
15
+ focusElement,
16
+ } from '../utils'
17
+ import { Controller } from '@hotwired/stimulus'
18
+
19
+ const SelectController = class extends Controller<HTMLElement> {
20
+ // targets
21
+ static targets = [
22
+ 'trigger',
23
+ 'contentContainer',
24
+ 'content',
25
+ 'item',
26
+ 'triggerText',
27
+ 'group',
28
+ 'select',
29
+ ]
30
+ declare readonly triggerTarget: HTMLElement
31
+ declare readonly contentContainerTarget: HTMLElement
32
+ declare readonly contentTarget: HTMLElement
33
+ declare readonly itemTargets: HTMLElement[]
34
+ declare triggerTextTarget: HTMLElement
35
+ declare groupTargets: HTMLElement[]
36
+ declare selectTarget: HTMLSelectElement
37
+
38
+ // values
39
+ static values = {
40
+ isOpen: Boolean,
41
+ selected: String,
42
+ }
43
+ declare isOpenValue: boolean
44
+ declare selectedValue: string
45
+
46
+ // custom properties
47
+ declare searchString: string
48
+ declare searchTimeout: number
49
+ declare itemsInnerText: string[]
50
+ declare items: HTMLElement[]
51
+ declare DOMKeydownListener: (event: KeyboardEvent) => void
52
+ declare cleanup: () => void
53
+
54
+ connect() {
55
+ this.items = getSameLevelItems({
56
+ content: this.contentTarget,
57
+ items: this.itemTargets,
58
+ closestContentSelector: '[data-select-target="content"]',
59
+ })
60
+ this.itemsInnerText = this.items.map((i) => i.innerText.trim())
61
+ this.searchString = ''
62
+ useClickOutside(this, { element: this.contentTarget, dispatchEvent: false })
63
+ this.DOMKeydownListener = this.onDOMKeydown.bind(this)
64
+ setGroupLabelsId(this)
65
+ }
66
+
67
+ toggle(event: MouseEvent) {
68
+ if (this.isOpenValue) {
69
+ this.close()
70
+ } else {
71
+ this.open(event)
72
+ }
73
+ }
74
+
75
+ open(event: MouseEvent | KeyboardEvent) {
76
+ this.isOpenValue = true
77
+
78
+ let elementToFocus = null as HTMLElement | null
79
+
80
+ if (this.selectedValue) {
81
+ const item = this.itemTargets.find(
82
+ (i) => i.dataset.value === this.selectedValue,
83
+ )
84
+
85
+ if (item && !item.dataset.disabled) {
86
+ elementToFocus = item
87
+ }
88
+ }
89
+
90
+ if (!elementToFocus) {
91
+ if (event instanceof KeyboardEvent) {
92
+ const key = event.key
93
+
94
+ if (['ArrowDown', 'Enter', ' '].includes(key)) {
95
+ elementToFocus = this.items[0]
96
+ }
97
+ } else {
98
+ elementToFocus = this.contentTarget
99
+ }
100
+ }
101
+
102
+ focusElement(elementToFocus)
103
+ }
104
+
105
+ close() {
106
+ this.isOpenValue = false
107
+ }
108
+
109
+ onItemFocus(event: FocusEvent) {
110
+ const item = event.currentTarget as HTMLElement
111
+ item.tabIndex = 0
112
+ }
113
+
114
+ onItemBlur(event: FocusEvent) {
115
+ const item = event.currentTarget as HTMLElement
116
+ item.tabIndex = -1
117
+ }
118
+
119
+ focusItemByIndex(
120
+ event: KeyboardEvent | null = null,
121
+ index: number | null = null,
122
+ ) {
123
+ focusItemByIndex(this, event, index)
124
+ }
125
+
126
+ focusItem(event: MouseEvent | KeyboardEvent) {
127
+ const item = event.currentTarget as HTMLElement
128
+ const index = this.items.indexOf(item)
129
+
130
+ if (event instanceof KeyboardEvent) {
131
+ const key = event.key
132
+ let newIndex = 0
133
+
134
+ if (key === 'ArrowUp') {
135
+ newIndex = getPreviousEnabledIndex({
136
+ items: this.items,
137
+ currentIndex: index,
138
+ wrapAround: false,
139
+ })
140
+ } else {
141
+ newIndex = getNextEnabledIndex({
142
+ items: this.items,
143
+ currentIndex: index,
144
+ wrapAround: false,
145
+ })
146
+ }
147
+
148
+ this.items[newIndex].focus()
149
+ } else {
150
+ // item mouseover event
151
+ this.items[index].focus()
152
+ }
153
+ }
154
+
155
+ focusContent() {
156
+ this.contentTarget.focus()
157
+ }
158
+
159
+ select(event: MouseEvent | KeyboardEvent) {
160
+ const item = event.currentTarget as HTMLElement
161
+ const value = item.dataset.value as string
162
+ this.selectedValue = value
163
+ this.close()
164
+ }
165
+
166
+ clickOutside(event: MouseEvent) {
167
+ onClickOutside(this, event)
168
+ }
169
+
170
+ isOpenValueChanged(isOpen: boolean, previousIsOpen: boolean) {
171
+ if (isOpen) {
172
+ lockScroll(this.contentTarget.id)
173
+
174
+ showContent({
175
+ trigger: this.triggerTarget,
176
+ content: this.contentTarget,
177
+ contentContainer: this.contentContainerTarget,
178
+ setEqualWidth: true,
179
+ })
180
+
181
+ this.cleanup = initFloatingUi({
182
+ referenceElement: this.triggerTarget,
183
+ floatingElement: this.contentContainerTarget,
184
+ side: this.contentTarget.dataset.side,
185
+ align: this.contentTarget.dataset.align,
186
+ sideOffset: 4,
187
+ })
188
+
189
+ this.setupEventListeners()
190
+ } else {
191
+ unlockScroll(this.contentTarget.id)
192
+
193
+ hideContent({
194
+ trigger: this.triggerTarget,
195
+ content: this.contentTarget,
196
+ contentContainer: this.contentContainerTarget,
197
+ })
198
+
199
+ if (previousIsOpen) {
200
+ focusTrigger(this.triggerTarget)
201
+ }
202
+
203
+ this.cleanupEventListeners()
204
+ }
205
+ }
206
+
207
+ selectedValueChanged(value: string) {
208
+ const item = this.itemTargets.find((i) => i.dataset.value === value)
209
+
210
+ if (item) {
211
+ this.triggerTextTarget.textContent = item.textContent
212
+
213
+ this.itemTargets.forEach((i) => {
214
+ if (i.dataset.value === value) {
215
+ i.setAttribute('aria-selected', 'true')
216
+ } else {
217
+ i.setAttribute('aria-selected', 'false')
218
+ }
219
+ })
220
+
221
+ this.selectTarget.value = value
222
+ }
223
+
224
+ this.triggerTarget.dataset.hasValue = `${!!value && value.length > 0}`
225
+
226
+ const placeholder = this.triggerTarget.dataset.placeholder
227
+
228
+ if (placeholder && this.triggerTarget.dataset.hasValue === 'false') {
229
+ this.triggerTextTarget.textContent = placeholder
230
+ }
231
+ }
232
+
233
+ disconnect() {
234
+ this.cleanupEventListeners()
235
+ }
236
+
237
+ protected onDOMKeydown(event: KeyboardEvent) {
238
+ if (!this.isOpenValue) return
239
+
240
+ onKeydown(this, event)
241
+
242
+ const { key, altKey, ctrlKey, metaKey } = event
243
+
244
+ if (
245
+ key === 'Backspace' ||
246
+ key === 'Clear' ||
247
+ (key.length === 1 && key !== ' ' && !altKey && !ctrlKey && !metaKey)
248
+ ) {
249
+ this.handleSearch(key)
250
+ }
251
+ }
252
+
253
+ protected setupEventListeners() {
254
+ document.addEventListener('keydown', this.DOMKeydownListener)
255
+ }
256
+
257
+ protected cleanupEventListeners() {
258
+ if (this.cleanup) this.cleanup()
259
+ document.removeEventListener('keydown', this.DOMKeydownListener)
260
+ }
261
+
262
+ // https://www.w3.org/WAI/ARIA/apg/patterns/combobox/examples/combobox-select-only/
263
+ protected handleSearch(char: string) {
264
+ const searchString = this.getSearchString(char)
265
+ const focusedItem = this.items.find(
266
+ (item) => document.activeElement === item,
267
+ )
268
+ const focusedIndex = focusedItem ? this.items.indexOf(focusedItem) : 0
269
+ const searchIndex = this.getIndexByLetter(searchString, focusedIndex + 1)
270
+
271
+ // if a match was found, go to it
272
+ if (searchIndex >= 0) {
273
+ this.focusItemByIndex(null, searchIndex)
274
+ }
275
+ // if no matches, clear the timeout and search string
276
+ else {
277
+ window.clearTimeout(this.searchTimeout)
278
+ this.searchString = ''
279
+ }
280
+ }
281
+
282
+ protected filterItemsInnerText(items: string[], filter: string) {
283
+ return items.filter((item) => {
284
+ const matches = item.toLowerCase().indexOf(filter.toLowerCase()) === 0
285
+ return matches
286
+ })
287
+ }
288
+
289
+ protected getSearchString(char: string) {
290
+ // reset typing timeout and start new timeout
291
+ // this allows us to make multiple-letter matches, like a native select
292
+ if (typeof this.searchTimeout === 'number') {
293
+ window.clearTimeout(this.searchTimeout)
294
+ }
295
+
296
+ this.searchTimeout = window.setTimeout(() => {
297
+ this.searchString = ''
298
+ }, 500)
299
+
300
+ // add most recent letter to saved search string
301
+ this.searchString += char
302
+ return this.searchString
303
+ }
304
+
305
+ // return the index of an option from an array of options, based on a search string
306
+ // if the filter is multiple iterations of the same letter (e.g "aaa"), then cycle through first-letter matches
307
+ protected getIndexByLetter(filter: string, startIndex: number) {
308
+ const orderedItems = [
309
+ ...this.itemsInnerText.slice(startIndex),
310
+ ...this.itemsInnerText.slice(0, startIndex),
311
+ ]
312
+
313
+ const firstMatch = this.filterItemsInnerText(orderedItems, filter)[0]
314
+
315
+ const allSameLetter = (array: string[]) =>
316
+ array.every((letter) => letter === array[0])
317
+
318
+ // first check if there is an exact match for the typed string
319
+ if (firstMatch) {
320
+ const index = this.itemsInnerText.indexOf(firstMatch)
321
+ return index
322
+ }
323
+
324
+ // if the same letter is being repeated, cycle through first-letter matches
325
+ else if (allSameLetter(filter.split(''))) {
326
+ const matches = this.filterItemsInnerText(orderedItems, filter[0])
327
+ const index = this.itemsInnerText.indexOf(matches[0])
328
+ return index
329
+ }
330
+
331
+ // if no matches, return -1
332
+ else {
333
+ return -1
334
+ }
335
+ }
336
+ }
337
+
338
+ type Select = InstanceType<typeof SelectController>
339
+
340
+ export { SelectController }
341
+ export type { Select }
@@ -1,8 +1,12 @@
1
1
  import { Controller } from '@hotwired/stimulus'
2
2
 
3
- export default class extends Controller {
3
+ export default class extends Controller<HTMLElement> {
4
4
  static targets = ['panel', 'panelOffset']
5
5
 
6
+ declare readonly panelTarget: HTMLElement
7
+ declare readonly panelOffsetTarget: HTMLElement
8
+ declare width: number
9
+
6
10
  connect() {
7
11
  this.width = this.element.offsetWidth
8
12
  }
@@ -26,7 +30,7 @@ export default class extends Controller {
26
30
  this.element.dataset.state = 'collapsed'
27
31
  this.element.dataset.collapsible = 'offcanvas'
28
32
  this.panelTarget.style.left = `-${this.width}px`
29
- this.panelOffsetTarget.style.width = 0
33
+ this.panelOffsetTarget.style.width = `${0}`
30
34
  }
31
35
 
32
36
  isOpen() {
@@ -0,0 +1,21 @@
1
+ import { Controller } from '@hotwired/stimulus'
2
+ import SidebarController from './sidebar_controller'
3
+
4
+ export default class extends Controller<HTMLElement> {
5
+ declare sidebarId: string | undefined
6
+
7
+ connect() {
8
+ this.sidebarId = this.element.dataset.sidebarId
9
+ }
10
+
11
+ toggle() {
12
+ const sidebar = this.application.getControllerForElementAndIdentifier(
13
+ document.querySelector(`#${this.sidebarId}`) as HTMLElement,
14
+ 'sidebar',
15
+ ) as SidebarController
16
+
17
+ if (sidebar) {
18
+ sidebar.toggle()
19
+ }
20
+ }
21
+ }
@@ -0,0 +1,114 @@
1
+ import { Controller } from '@hotwired/stimulus'
2
+ import noUiSlider, { API, Options } from 'nouislider'
3
+
4
+ const SliderController = class extends Controller<HTMLElement> {
5
+ // targets
6
+ static targets = ['slider', 'hiddenInput', 'endHiddenInput']
7
+ declare readonly sliderTarget: HTMLInputElement
8
+ declare readonly hiddenInputTarget: HTMLInputElement
9
+ declare readonly endHiddenInputTarget: HTMLInputElement
10
+ declare readonly hasEndHiddenInputTarget: boolean
11
+
12
+ // custom properties
13
+ declare range: boolean
14
+ declare slider: API
15
+ declare onUpdateValuesListener: (values: (string | number)[]) => void
16
+ declare DOMClickListener: (event: MouseEvent) => void
17
+
18
+ connect() {
19
+ this.range = this.element.dataset.range === 'true'
20
+ this.DOMClickListener = this.onDOMClick.bind(this)
21
+ this.onUpdateValuesListener = this.onUpdateValues.bind(this)
22
+ const options = this.getOptions()
23
+ this.slider = noUiSlider.create(this.sliderTarget, options)
24
+
25
+ if (this.element.dataset.disabled === 'true') {
26
+ this.slider.disable()
27
+ }
28
+
29
+ if (this.element.dataset.id) {
30
+ const lowerHandle = this.slider.target.querySelector('.noUi-handle-lower')
31
+ if (lowerHandle) {
32
+ lowerHandle.id = this.element.dataset.id
33
+ }
34
+ }
35
+ this.slider.on('update', this.onUpdateValuesListener)
36
+
37
+ document.addEventListener('click', this.DOMClickListener)
38
+ }
39
+
40
+ disconnect() {
41
+ document.removeEventListener('click', this.DOMClickListener)
42
+ }
43
+
44
+ protected getOptions() {
45
+ const defaultOptions = {
46
+ connect: this.range ? true : 'lower',
47
+ tooltips: true,
48
+ } as Options
49
+
50
+ defaultOptions.range = {
51
+ min: [parseFloat(this.element.dataset.min || '0')],
52
+ max: [parseFloat(this.element.dataset.max || '100')],
53
+ }
54
+
55
+ defaultOptions.step = parseFloat(this.element.dataset.step || '1')
56
+
57
+ if (this.range) {
58
+ defaultOptions.start = [
59
+ parseFloat(this.element.dataset.value || '0'),
60
+ parseFloat(this.element.dataset.endValue || '0'),
61
+ ]
62
+
63
+ defaultOptions.handleAttributes = [
64
+ { 'aria-label': 'lower' },
65
+ { 'aria-label': 'upper' },
66
+ ]
67
+ } else {
68
+ defaultOptions.start = [parseFloat(this.element.dataset.value || '0')]
69
+ defaultOptions.handleAttributes = [{ 'aria-label': 'lower' }]
70
+ }
71
+
72
+ defaultOptions.orientation = (this.element.dataset.orientation ||
73
+ 'horizontal') as Options['orientation']
74
+
75
+ try {
76
+ return {
77
+ ...defaultOptions,
78
+ ...JSON.parse(this.element.dataset.options || ''),
79
+ }
80
+ } catch {
81
+ return defaultOptions
82
+ }
83
+ }
84
+
85
+ protected onUpdateValues(values: (string | number)[]) {
86
+ this.hiddenInputTarget.value = `${values[0]}`
87
+
88
+ if (this.range && this.hasEndHiddenInputTarget) {
89
+ this.endHiddenInputTarget.value = `${values[1]}`
90
+ }
91
+ }
92
+
93
+ protected onDOMClick(event: MouseEvent) {
94
+ const target = event.target
95
+
96
+ // Focus handle of slider when label is clicked.
97
+ if (target instanceof HTMLLabelElement) {
98
+ const id = target.getAttribute('for')
99
+
100
+ if (id === this.element.dataset.id) {
101
+ const handle = document.querySelector(`#${id}`) as HTMLElement
102
+
103
+ if (handle) {
104
+ handle.focus()
105
+ }
106
+ }
107
+ }
108
+ }
109
+ }
110
+
111
+ type Slider = InstanceType<typeof SliderController>
112
+
113
+ export { SliderController }
114
+ export type { Slider }
@@ -0,0 +1,37 @@
1
+ import { Controller } from '@hotwired/stimulus'
2
+
3
+ const SwitchController = class extends Controller<HTMLElement> {
4
+ // targets
5
+ static targets = ['input', 'thumb']
6
+ declare readonly inputTarget: HTMLInputElement
7
+ declare readonly thumbTarget: HTMLElement
8
+
9
+ // values
10
+ static values = {
11
+ isChecked: Boolean,
12
+ }
13
+ declare isCheckedValue: boolean
14
+
15
+ toggle() {
16
+ this.isCheckedValue = !this.isCheckedValue
17
+ }
18
+
19
+ isCheckedValueChanged(value: boolean) {
20
+ if (value) {
21
+ this.element.ariaChecked = 'true'
22
+ this.element.dataset.state = 'checked'
23
+ this.thumbTarget.dataset.state = 'checked'
24
+ this.inputTarget.checked = true
25
+ } else {
26
+ this.element.ariaChecked = 'false'
27
+ this.element.dataset.state = 'unchecked'
28
+ this.thumbTarget.dataset.state = 'unchecked'
29
+ this.inputTarget.checked = false
30
+ }
31
+ }
32
+ }
33
+
34
+ type Switch = InstanceType<typeof SwitchController>
35
+
36
+ export { SwitchController }
37
+ export type { Switch }
@@ -0,0 +1,87 @@
1
+ import { Controller } from '@hotwired/stimulus'
2
+ import { getNextEnabledIndex, getPreviousEnabledIndex } from '../utils'
3
+
4
+ const TabsController = class extends Controller {
5
+ // targets
6
+ static targets = ['trigger', 'content']
7
+ declare readonly triggerTargets: HTMLButtonElement[]
8
+ declare readonly contentTargets: HTMLElement[]
9
+
10
+ // values
11
+ static values = {
12
+ active: String,
13
+ }
14
+ declare activeValue: string
15
+
16
+ connect() {
17
+ if (!this.activeValue) {
18
+ this.activeValue = this.triggerTargets[0].dataset.value as string
19
+ }
20
+ }
21
+
22
+ setActiveTab(event: MouseEvent | KeyboardEvent) {
23
+ const target = event.currentTarget as HTMLButtonElement
24
+
25
+ if (event instanceof MouseEvent) {
26
+ this.activeValue = target.dataset.value as string
27
+ } else {
28
+ const key = event.key
29
+
30
+ const focusableTriggers = this.triggerTargets.filter(
31
+ (t) => !t.disabled,
32
+ ) as HTMLButtonElement[]
33
+
34
+ const index = focusableTriggers.indexOf(target)
35
+ let newIndex = 0
36
+
37
+ if (key === 'ArrowLeft') {
38
+ newIndex = getPreviousEnabledIndex({
39
+ items: focusableTriggers,
40
+ currentIndex: index,
41
+ wrapAround: true,
42
+ })
43
+ } else {
44
+ newIndex = getNextEnabledIndex({
45
+ items: focusableTriggers,
46
+ currentIndex: index,
47
+ wrapAround: true,
48
+ })
49
+ }
50
+
51
+ this.activeValue = focusableTriggers[newIndex].dataset.value as string
52
+ focusableTriggers[newIndex].focus()
53
+ }
54
+ }
55
+
56
+ activeValueChanged(value: string) {
57
+ this.triggerTargets.forEach((trigger) => {
58
+ const triggerValue = trigger.dataset.value
59
+ const content = this.contentTargets.find((c) => {
60
+ return c.dataset.value === triggerValue
61
+ })
62
+
63
+ if (!content) {
64
+ throw new Error(
65
+ `Could not find TabsContent with value "${triggerValue}"`,
66
+ )
67
+ }
68
+
69
+ if (triggerValue === value) {
70
+ trigger.ariaSelected = 'true'
71
+ trigger.tabIndex = 0
72
+ trigger.dataset.state = 'active'
73
+ content.classList.remove('hidden')
74
+ } else {
75
+ trigger.ariaSelected = 'false'
76
+ trigger.tabIndex = -1
77
+ trigger.dataset.state = 'inactive'
78
+ content.classList.add('hidden')
79
+ }
80
+ })
81
+ }
82
+ }
83
+
84
+ type Tabs = InstanceType<typeof TabsController>
85
+
86
+ export { TabsController }
87
+ export type { Tabs }
@@ -0,0 +1,40 @@
1
+ import { Controller } from '@hotwired/stimulus'
2
+
3
+ const ThemeSwitcherController = class extends Controller {
4
+ initialize() {
5
+ if (
6
+ localStorage.theme === 'dark' ||
7
+ (!('theme' in localStorage) &&
8
+ window.matchMedia('(prefers-color-scheme: dark)').matches)
9
+ ) {
10
+ this.setDarkMode()
11
+ } else {
12
+ this.setLightMode()
13
+ }
14
+ }
15
+
16
+ toggle() {
17
+ if (document.documentElement.classList.contains('dark')) {
18
+ this.setLightMode()
19
+ } else {
20
+ this.setDarkMode()
21
+ }
22
+ }
23
+
24
+ setLightMode() {
25
+ localStorage.theme = 'light'
26
+ document.documentElement.classList.remove('dark')
27
+ document.documentElement.style.colorScheme = ''
28
+ }
29
+
30
+ setDarkMode() {
31
+ localStorage.theme = 'dark'
32
+ document.documentElement.classList.add('dark')
33
+ document.documentElement.style.colorScheme = 'dark'
34
+ }
35
+ }
36
+
37
+ type ThemeSwitcher = InstanceType<typeof ThemeSwitcherController>
38
+
39
+ export { ThemeSwitcherController }
40
+ export type { ThemeSwitcher }