shadcn_phlexcomponents 0.1.9 → 0.1.11

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 (246) hide show
  1. checksums.yaml +4 -4
  2. data/app/javascript/controllers/accordion_controller.ts +133 -0
  3. data/app/javascript/controllers/{avatar_controller.js → avatar_controller.ts} +4 -0
  4. data/app/javascript/controllers/checkbox_controller.ts +34 -0
  5. data/app/javascript/controllers/collapsible_controller.ts +45 -0
  6. data/app/javascript/controllers/combobox_controller.ts +145 -0
  7. data/app/javascript/controllers/command_controller.ts +129 -0
  8. data/app/javascript/controllers/command_root_controller.ts +355 -0
  9. data/app/javascript/controllers/date_picker_controller.ts +274 -0
  10. data/app/javascript/controllers/date_range_picker_controller.ts +243 -0
  11. data/app/javascript/controllers/dialog_controller.ts +113 -0
  12. data/app/javascript/controllers/dropdown_menu_controller.ts +133 -0
  13. data/app/javascript/controllers/dropdown_menu_root_controller.ts +234 -0
  14. data/app/javascript/controllers/dropdown_menu_sub_controller.ts +150 -0
  15. data/app/javascript/controllers/{form_field_controller.js → form_field_controller.ts} +4 -6
  16. data/app/javascript/controllers/hover_card_controller.ts +93 -0
  17. data/app/javascript/controllers/{loading_button_controller.js → loading_button_controller.ts} +2 -2
  18. data/app/javascript/controllers/popover_controller.ts +141 -0
  19. data/app/javascript/controllers/progress_controller.ts +17 -0
  20. data/app/javascript/controllers/radio_group_controller.ts +106 -0
  21. data/app/javascript/controllers/select_controller.ts +200 -0
  22. data/app/javascript/controllers/{sidebar_controller.js → sidebar_controller.ts} +6 -2
  23. data/app/javascript/controllers/sidebar_trigger_controller.ts +21 -0
  24. data/app/javascript/controllers/slider_controller.ts +107 -0
  25. data/app/javascript/controllers/switch_controller.ts +30 -0
  26. data/app/javascript/controllers/tabs_controller.ts +79 -0
  27. data/app/javascript/controllers/{theme_switcher_controller.js → theme_switcher_controller.ts} +12 -9
  28. data/app/javascript/controllers/toast_container_controller.ts +62 -0
  29. data/app/javascript/controllers/toast_controller.ts +28 -0
  30. data/app/javascript/controllers/tooltip_controller.ts +98 -0
  31. data/app/javascript/{shadcn_phlexcomponents.js → shadcn_phlexcomponents.ts} +6 -2
  32. data/app/javascript/utils.ts +437 -0
  33. data/app/stylesheets/date_picker.css +16 -154
  34. data/app/stylesheets/nouislider.css +173 -0
  35. data/app/stylesheets/tw-animate.css +486 -0
  36. data/lib/install/install_shadcn_phlexcomponents.rb +16 -3
  37. data/lib/shadcn_phlexcomponents/alias.rb +3 -1
  38. data/lib/shadcn_phlexcomponents/components/accordion.rb +129 -0
  39. data/lib/shadcn_phlexcomponents/components/alert.rb +59 -0
  40. data/lib/shadcn_phlexcomponents/components/alert_dialog.rb +276 -0
  41. data/lib/shadcn_phlexcomponents/components/{aspect_ratio/aspect_ratio.rb → aspect_ratio.rb} +2 -2
  42. data/lib/shadcn_phlexcomponents/components/avatar.rb +63 -0
  43. data/lib/shadcn_phlexcomponents/components/badge.rb +35 -0
  44. data/lib/shadcn_phlexcomponents/components/base.rb +34 -7
  45. data/lib/shadcn_phlexcomponents/components/breadcrumb.rb +150 -0
  46. data/lib/shadcn_phlexcomponents/components/button.rb +49 -0
  47. data/lib/shadcn_phlexcomponents/components/card.rb +88 -0
  48. data/lib/shadcn_phlexcomponents/components/{checkbox/checkbox.rb → checkbox.rb} +18 -14
  49. data/lib/shadcn_phlexcomponents/components/{checkbox_group/checkbox_group.rb → checkbox_group.rb} +7 -8
  50. data/lib/shadcn_phlexcomponents/components/collapsible.rb +91 -0
  51. data/lib/shadcn_phlexcomponents/components/combobox.rb +398 -0
  52. data/lib/shadcn_phlexcomponents/components/command.rb +351 -0
  53. data/lib/shadcn_phlexcomponents/components/date_picker.rb +264 -0
  54. data/lib/shadcn_phlexcomponents/components/date_range_picker.rb +126 -0
  55. data/lib/shadcn_phlexcomponents/components/dialog.rb +234 -0
  56. data/lib/shadcn_phlexcomponents/components/dropdown_menu.rb +282 -0
  57. data/lib/shadcn_phlexcomponents/components/dropdown_menu_sub.rb +135 -0
  58. data/lib/shadcn_phlexcomponents/components/form/form_checkbox.rb +6 -7
  59. data/lib/shadcn_phlexcomponents/components/form/form_checkbox_group.rb +2 -2
  60. data/lib/shadcn_phlexcomponents/components/form/form_date_picker.rb +3 -4
  61. data/lib/shadcn_phlexcomponents/components/form/form_date_range_picker.rb +27 -41
  62. data/lib/shadcn_phlexcomponents/components/form/form_error.rb +1 -1
  63. data/lib/shadcn_phlexcomponents/components/form/form_helpers.rb +43 -8
  64. data/lib/shadcn_phlexcomponents/components/form/form_hint.rb +1 -1
  65. data/lib/shadcn_phlexcomponents/components/form/form_input.rb +3 -4
  66. data/lib/shadcn_phlexcomponents/components/form/form_radio_group.rb +4 -5
  67. data/lib/shadcn_phlexcomponents/components/form/form_select.rb +3 -4
  68. data/lib/shadcn_phlexcomponents/components/form/form_slider.rb +91 -0
  69. data/lib/shadcn_phlexcomponents/components/form/form_switch.rb +7 -6
  70. data/lib/shadcn_phlexcomponents/components/form/form_textarea.rb +3 -4
  71. data/lib/shadcn_phlexcomponents/components/{form/form.rb → form.rb} +22 -4
  72. data/lib/shadcn_phlexcomponents/components/hover_card.rb +110 -0
  73. data/lib/shadcn_phlexcomponents/components/input.rb +31 -0
  74. data/lib/shadcn_phlexcomponents/components/label.rb +16 -0
  75. data/lib/shadcn_phlexcomponents/components/{link/link.rb → link.rb} +10 -3
  76. data/lib/shadcn_phlexcomponents/components/{loading_button/loading_button.rb → loading_button.rb} +9 -2
  77. data/lib/shadcn_phlexcomponents/components/pagination.rb +166 -0
  78. data/lib/shadcn_phlexcomponents/components/popover.rb +116 -0
  79. data/lib/shadcn_phlexcomponents/components/{progress/progress.rb → progress.rb} +4 -4
  80. data/lib/shadcn_phlexcomponents/components/radio_group.rb +155 -0
  81. data/lib/shadcn_phlexcomponents/components/select.rb +421 -0
  82. data/lib/shadcn_phlexcomponents/components/{separator/separator.rb → separator.rb} +9 -8
  83. data/lib/shadcn_phlexcomponents/components/sheet.rb +239 -0
  84. data/lib/shadcn_phlexcomponents/components/{skeleton/skeleton.rb → skeleton.rb} +1 -1
  85. data/lib/shadcn_phlexcomponents/components/slider.rb +72 -0
  86. data/lib/shadcn_phlexcomponents/components/switch.rb +75 -0
  87. data/lib/shadcn_phlexcomponents/components/table.rb +140 -0
  88. data/lib/shadcn_phlexcomponents/components/tabs.rb +135 -0
  89. data/lib/shadcn_phlexcomponents/components/textarea.rb +24 -0
  90. data/lib/shadcn_phlexcomponents/components/toast.rb +153 -0
  91. data/lib/shadcn_phlexcomponents/components/{toast/toast_container.rb → toast_container.rb} +23 -4
  92. data/lib/shadcn_phlexcomponents/components/tooltip.rb +131 -0
  93. data/lib/shadcn_phlexcomponents/initializers/shadcn_phlexcomponents.rb +25 -0
  94. data/lib/shadcn_phlexcomponents/version.rb +1 -1
  95. data/lib/tasks/install.rake +1 -1
  96. metadata +78 -167
  97. data/app/javascript/controllers/accordion_controller.js +0 -124
  98. data/app/javascript/controllers/alert_dialog_controller.js +0 -21
  99. data/app/javascript/controllers/checkbox_controller.js +0 -28
  100. data/app/javascript/controllers/collapsible_controller.js +0 -35
  101. data/app/javascript/controllers/combobox_controller.js +0 -54
  102. data/app/javascript/controllers/date_picker_controller.js +0 -253
  103. data/app/javascript/controllers/date_range_picker_controller.js +0 -344
  104. data/app/javascript/controllers/dialog_controller.js +0 -114
  105. data/app/javascript/controllers/dropdown_menu_controller.js +0 -171
  106. data/app/javascript/controllers/hover_card_controller.js +0 -21
  107. data/app/javascript/controllers/popover_controller.js +0 -113
  108. data/app/javascript/controllers/progress_controller.js +0 -14
  109. data/app/javascript/controllers/radio_group_controller.js +0 -90
  110. data/app/javascript/controllers/select_controller.js +0 -274
  111. data/app/javascript/controllers/sidebar_trigger_controller.js +0 -15
  112. data/app/javascript/controllers/switch_controller.js +0 -24
  113. data/app/javascript/controllers/tabs_controller.js +0 -73
  114. data/app/javascript/controllers/toast_container_controller.js +0 -22
  115. data/app/javascript/controllers/toast_controller.js +0 -45
  116. data/app/javascript/controllers/tooltip_controller.js +0 -40
  117. data/app/javascript/utils.js +0 -184
  118. data/app/stylesheets/choices.css +0 -324
  119. data/app/stylesheets/tailwindcss-animate.css +0 -318
  120. data/lib/shadcn_phlexcomponents/components/accordion/accordion.rb +0 -38
  121. data/lib/shadcn_phlexcomponents/components/accordion/accordion_content.rb +0 -30
  122. data/lib/shadcn_phlexcomponents/components/accordion/accordion_item.rb +0 -26
  123. data/lib/shadcn_phlexcomponents/components/accordion/accordion_trigger.rb +0 -46
  124. data/lib/shadcn_phlexcomponents/components/alert/alert.rb +0 -40
  125. data/lib/shadcn_phlexcomponents/components/alert/alert_description.rb +0 -11
  126. data/lib/shadcn_phlexcomponents/components/alert/alert_title.rb +0 -11
  127. data/lib/shadcn_phlexcomponents/components/alert_dialog/alert_dialog.rb +0 -60
  128. data/lib/shadcn_phlexcomponents/components/alert_dialog/alert_dialog_action.rb +0 -22
  129. data/lib/shadcn_phlexcomponents/components/alert_dialog/alert_dialog_action_to.rb +0 -40
  130. data/lib/shadcn_phlexcomponents/components/alert_dialog/alert_dialog_cancel.rb +0 -22
  131. data/lib/shadcn_phlexcomponents/components/alert_dialog/alert_dialog_content.rb +0 -40
  132. data/lib/shadcn_phlexcomponents/components/alert_dialog/alert_dialog_description.rb +0 -22
  133. data/lib/shadcn_phlexcomponents/components/alert_dialog/alert_dialog_footer.rb +0 -11
  134. data/lib/shadcn_phlexcomponents/components/alert_dialog/alert_dialog_header.rb +0 -11
  135. data/lib/shadcn_phlexcomponents/components/alert_dialog/alert_dialog_title.rb +0 -22
  136. data/lib/shadcn_phlexcomponents/components/alert_dialog/alert_dialog_trigger.rb +0 -50
  137. data/lib/shadcn_phlexcomponents/components/avatar/avatar.rb +0 -31
  138. data/lib/shadcn_phlexcomponents/components/avatar/avatar_fallback.rb +0 -21
  139. data/lib/shadcn_phlexcomponents/components/avatar/avatar_image.rb +0 -19
  140. data/lib/shadcn_phlexcomponents/components/badge/badge.rb +0 -30
  141. data/lib/shadcn_phlexcomponents/components/breadcrumb/breadcrumb.rb +0 -53
  142. data/lib/shadcn_phlexcomponents/components/breadcrumb/breadcrumb_ellipsis.rb +0 -23
  143. data/lib/shadcn_phlexcomponents/components/breadcrumb/breadcrumb_item.rb +0 -11
  144. data/lib/shadcn_phlexcomponents/components/breadcrumb/breadcrumb_link.rb +0 -7
  145. data/lib/shadcn_phlexcomponents/components/breadcrumb/breadcrumb_page.rb +0 -21
  146. data/lib/shadcn_phlexcomponents/components/breadcrumb/breadcrumb_separator.rb +0 -26
  147. data/lib/shadcn_phlexcomponents/components/button/button.rb +0 -53
  148. data/lib/shadcn_phlexcomponents/components/card/card.rb +0 -31
  149. data/lib/shadcn_phlexcomponents/components/card/card_content.rb +0 -11
  150. data/lib/shadcn_phlexcomponents/components/card/card_description.rb +0 -11
  151. data/lib/shadcn_phlexcomponents/components/card/card_footer.rb +0 -11
  152. data/lib/shadcn_phlexcomponents/components/card/card_header.rb +0 -11
  153. data/lib/shadcn_phlexcomponents/components/card/card_title.rb +0 -11
  154. data/lib/shadcn_phlexcomponents/components/collapsible/collapsible.rb +0 -31
  155. data/lib/shadcn_phlexcomponents/components/collapsible/collapsible_content.rb +0 -24
  156. data/lib/shadcn_phlexcomponents/components/collapsible/collapsible_trigger.rb +0 -50
  157. data/lib/shadcn_phlexcomponents/components/date_picker/date_picker.rb +0 -87
  158. data/lib/shadcn_phlexcomponents/components/date_picker/date_picker_content.rb +0 -45
  159. data/lib/shadcn_phlexcomponents/components/date_picker/date_picker_trigger.rb +0 -64
  160. data/lib/shadcn_phlexcomponents/components/date_range_picker/date_range_picker.rb +0 -105
  161. data/lib/shadcn_phlexcomponents/components/date_range_picker/date_range_picker_content.rb +0 -9
  162. data/lib/shadcn_phlexcomponents/components/date_range_picker/date_range_picker_trigger.rb +0 -9
  163. data/lib/shadcn_phlexcomponents/components/dialog/dialog.rb +0 -52
  164. data/lib/shadcn_phlexcomponents/components/dialog/dialog_close.rb +0 -42
  165. data/lib/shadcn_phlexcomponents/components/dialog/dialog_content.rb +0 -54
  166. data/lib/shadcn_phlexcomponents/components/dialog/dialog_description.rb +0 -22
  167. data/lib/shadcn_phlexcomponents/components/dialog/dialog_footer.rb +0 -11
  168. data/lib/shadcn_phlexcomponents/components/dialog/dialog_header.rb +0 -11
  169. data/lib/shadcn_phlexcomponents/components/dialog/dialog_title.rb +0 -22
  170. data/lib/shadcn_phlexcomponents/components/dialog/dialog_trigger.rb +0 -50
  171. data/lib/shadcn_phlexcomponents/components/dropdown_menu/dropdown_menu.rb +0 -50
  172. data/lib/shadcn_phlexcomponents/components/dropdown_menu/dropdown_menu_content.rb +0 -52
  173. data/lib/shadcn_phlexcomponents/components/dropdown_menu/dropdown_menu_item.rb +0 -56
  174. data/lib/shadcn_phlexcomponents/components/dropdown_menu/dropdown_menu_item_to.rb +0 -28
  175. data/lib/shadcn_phlexcomponents/components/dropdown_menu/dropdown_menu_label.rb +0 -11
  176. data/lib/shadcn_phlexcomponents/components/dropdown_menu/dropdown_menu_separator.rb +0 -20
  177. data/lib/shadcn_phlexcomponents/components/dropdown_menu/dropdown_menu_trigger.rb +0 -57
  178. data/lib/shadcn_phlexcomponents/components/hover_card/hover_card.rb +0 -33
  179. data/lib/shadcn_phlexcomponents/components/hover_card/hover_card_content.rb +0 -32
  180. data/lib/shadcn_phlexcomponents/components/hover_card/hover_card_trigger.rb +0 -44
  181. data/lib/shadcn_phlexcomponents/components/input/input.rb +0 -32
  182. data/lib/shadcn_phlexcomponents/components/label/label.rb +0 -14
  183. data/lib/shadcn_phlexcomponents/components/pagination/pagination.rb +0 -38
  184. data/lib/shadcn_phlexcomponents/components/pagination/pagination_ellipsis.rb +0 -24
  185. data/lib/shadcn_phlexcomponents/components/pagination/pagination_link.rb +0 -34
  186. data/lib/shadcn_phlexcomponents/components/pagination/pagination_next.rb +0 -32
  187. data/lib/shadcn_phlexcomponents/components/pagination/pagination_previous.rb +0 -32
  188. data/lib/shadcn_phlexcomponents/components/popover/popover.rb +0 -34
  189. data/lib/shadcn_phlexcomponents/components/popover/popover_content.rb +0 -40
  190. data/lib/shadcn_phlexcomponents/components/popover/popover_trigger.rb +0 -50
  191. data/lib/shadcn_phlexcomponents/components/radio_group/radio_group.rb +0 -88
  192. data/lib/shadcn_phlexcomponents/components/radio_group/radio_group_item.rb +0 -66
  193. data/lib/shadcn_phlexcomponents/components/select/select.rb +0 -194
  194. data/lib/shadcn_phlexcomponents/components/select/select_content.rb +0 -64
  195. data/lib/shadcn_phlexcomponents/components/select/select_group.rb +0 -23
  196. data/lib/shadcn_phlexcomponents/components/select/select_item.rb +0 -59
  197. data/lib/shadcn_phlexcomponents/components/select/select_label.rb +0 -24
  198. data/lib/shadcn_phlexcomponents/components/select/select_trigger.rb +0 -56
  199. data/lib/shadcn_phlexcomponents/components/sheet/sheet.rb +0 -53
  200. data/lib/shadcn_phlexcomponents/components/sheet/sheet_close.rb +0 -42
  201. data/lib/shadcn_phlexcomponents/components/sheet/sheet_content.rb +0 -65
  202. data/lib/shadcn_phlexcomponents/components/sheet/sheet_description.rb +0 -22
  203. data/lib/shadcn_phlexcomponents/components/sheet/sheet_footer.rb +0 -11
  204. data/lib/shadcn_phlexcomponents/components/sheet/sheet_header.rb +0 -11
  205. data/lib/shadcn_phlexcomponents/components/sheet/sheet_title.rb +0 -22
  206. data/lib/shadcn_phlexcomponents/components/sheet/sheet_trigger.rb +0 -50
  207. data/lib/shadcn_phlexcomponents/components/sidebar/sidebar.rb +0 -108
  208. data/lib/shadcn_phlexcomponents/components/sidebar/sidebar_container.rb +0 -11
  209. data/lib/shadcn_phlexcomponents/components/sidebar/sidebar_content.rb +0 -11
  210. data/lib/shadcn_phlexcomponents/components/sidebar/sidebar_footer.rb +0 -11
  211. data/lib/shadcn_phlexcomponents/components/sidebar/sidebar_group.rb +0 -11
  212. data/lib/shadcn_phlexcomponents/components/sidebar/sidebar_group_content.rb +0 -11
  213. data/lib/shadcn_phlexcomponents/components/sidebar/sidebar_group_label.rb +0 -16
  214. data/lib/shadcn_phlexcomponents/components/sidebar/sidebar_header.rb +0 -11
  215. data/lib/shadcn_phlexcomponents/components/sidebar/sidebar_inset.rb +0 -15
  216. data/lib/shadcn_phlexcomponents/components/sidebar/sidebar_menu.rb +0 -11
  217. data/lib/shadcn_phlexcomponents/components/sidebar/sidebar_menu_button.rb +0 -61
  218. data/lib/shadcn_phlexcomponents/components/sidebar/sidebar_menu_item.rb +0 -9
  219. data/lib/shadcn_phlexcomponents/components/sidebar/sidebar_menu_sub.rb +0 -14
  220. data/lib/shadcn_phlexcomponents/components/sidebar/sidebar_menu_sub_button.rb +0 -48
  221. data/lib/shadcn_phlexcomponents/components/sidebar/sidebar_menu_sub_item.rb +0 -9
  222. data/lib/shadcn_phlexcomponents/components/sidebar/sidebar_trigger.rb +0 -40
  223. data/lib/shadcn_phlexcomponents/components/switch/switch.rb +0 -66
  224. data/lib/shadcn_phlexcomponents/components/table/table.rb +0 -75
  225. data/lib/shadcn_phlexcomponents/components/table/table_body.rb +0 -11
  226. data/lib/shadcn_phlexcomponents/components/table/table_caption.rb +0 -11
  227. data/lib/shadcn_phlexcomponents/components/table/table_cell.rb +0 -11
  228. data/lib/shadcn_phlexcomponents/components/table/table_footer.rb +0 -11
  229. data/lib/shadcn_phlexcomponents/components/table/table_head.rb +0 -14
  230. data/lib/shadcn_phlexcomponents/components/table/table_header.rb +0 -11
  231. data/lib/shadcn_phlexcomponents/components/table/table_row.rb +0 -11
  232. data/lib/shadcn_phlexcomponents/components/tabs/tabs.rb +0 -38
  233. data/lib/shadcn_phlexcomponents/components/tabs/tabs_content.rb +0 -35
  234. data/lib/shadcn_phlexcomponents/components/tabs/tabs_list.rb +0 -23
  235. data/lib/shadcn_phlexcomponents/components/tabs/tabs_trigger.rb +0 -45
  236. data/lib/shadcn_phlexcomponents/components/textarea/textarea.rb +0 -29
  237. data/lib/shadcn_phlexcomponents/components/toast/toast.rb +0 -101
  238. data/lib/shadcn_phlexcomponents/components/toast/toast_action.rb +0 -39
  239. data/lib/shadcn_phlexcomponents/components/toast/toast_action_to.rb +0 -28
  240. data/lib/shadcn_phlexcomponents/components/toast/toast_content.rb +0 -11
  241. data/lib/shadcn_phlexcomponents/components/toast/toast_description.rb +0 -11
  242. data/lib/shadcn_phlexcomponents/components/toast/toast_title.rb +0 -11
  243. data/lib/shadcn_phlexcomponents/components/tooltip/tooltip.rb +0 -34
  244. data/lib/shadcn_phlexcomponents/components/tooltip/tooltip_content.rb +0 -39
  245. data/lib/shadcn_phlexcomponents/components/tooltip/tooltip_trigger.rb +0 -48
  246. /data/lib/shadcn_phlexcomponents/components/{theme_switcher/theme_switcher.rb → theme_switcher.rb} +0 -0
@@ -0,0 +1,243 @@
1
+ import { Calendar, Options } from 'vanilla-calendar-pro'
2
+ import DatePickerController from './date_picker_controller'
3
+ import dayjs from 'dayjs'
4
+ import customParseFormat from 'dayjs/plugin/customParseFormat'
5
+ import utc from 'dayjs/plugin/utc'
6
+ dayjs.extend(customParseFormat)
7
+ dayjs.extend(utc)
8
+
9
+ const DELIMITER = ' - '
10
+ const DAYJS_FORMAT = 'YYYY-MM-DD'
11
+
12
+ export default class extends DatePickerController {
13
+ static targets = [
14
+ 'trigger',
15
+ 'triggerText',
16
+ 'contentWrapper',
17
+ 'content',
18
+ 'input',
19
+ 'hiddenInput',
20
+ 'endHiddenInput',
21
+ 'inputContainer',
22
+ 'calendar',
23
+ ]
24
+
25
+ static values = {
26
+ isOpen: Boolean,
27
+ date: String,
28
+ endDate: String,
29
+ }
30
+
31
+ declare readonly endHiddenInputTarget: HTMLInputElement
32
+ declare endDateValue: string
33
+
34
+ inputBlur() {
35
+ const dates = this.calendar.context.selectedDates
36
+ const startDate = dates[0]
37
+ const endDate = dates[1]
38
+ let datesDisplay = ''
39
+
40
+ if (startDate) {
41
+ datesDisplay = `${dayjs(startDate).format(this.format)}${DELIMITER}`
42
+ }
43
+
44
+ if (endDate) {
45
+ datesDisplay = `${datesDisplay}${dayjs(endDate).format(this.format)}`
46
+ }
47
+
48
+ this.inputTarget.value = datesDisplay
49
+ this.inputContainerTarget.dataset.focus = 'false'
50
+ }
51
+
52
+ inputDate(event: KeyboardEvent) {
53
+ const value = (event.target as HTMLInputElement).value
54
+ const dates = value.split(DELIMITER).filter((d) => d.length > 0)
55
+
56
+ if (dates.length > 0) {
57
+ const startDate = dates[0]
58
+ const endDate = dates[1]
59
+ let selectedDates: string[] = this.calendar.context.selectedDates
60
+
61
+ if (dayjs(startDate, this.format, true).isValid()) {
62
+ const dayjsDate = dayjs(value, this.format).format(DAYJS_FORMAT)
63
+ selectedDates[0] = dayjsDate
64
+ }
65
+
66
+ if (dayjs(endDate, this.format, true).isValid()) {
67
+ const dayjsDate = dayjs(endDate, this.format).format(DAYJS_FORMAT)
68
+ selectedDates[1] = dayjsDate
69
+ }
70
+
71
+ selectedDates = selectedDates.filter((d) => !!d)
72
+
73
+ this.calendar.set({
74
+ selectedDates: selectedDates,
75
+ })
76
+ if (selectedDates[0]) {
77
+ this.dateValue = selectedDates[0]
78
+ }
79
+ if (selectedDates[1]) {
80
+ this.endDateValue = selectedDates[1]
81
+ }
82
+ } else {
83
+ this.calendar.set({
84
+ selectedDates: [],
85
+ })
86
+ this.dateValue = ''
87
+ this.endDateValue = ''
88
+ }
89
+ }
90
+
91
+ getOptions() {
92
+ let options = {
93
+ type: 'multiple',
94
+ selectionDatesMode: 'multiple-ranged',
95
+ displayMonthsCount: 2,
96
+ monthsToSwitch: 1,
97
+ displayDatesOutside: false,
98
+ enableJumpToSelectedDate: true,
99
+ onClickDate: this.onClickDateListener,
100
+ } as Options
101
+
102
+ const selectedDates = []
103
+
104
+ const startDate = this.element.dataset.value
105
+ const endDate = this.element.dataset.endValue
106
+
107
+ if (startDate && dayjs(startDate).isValid()) {
108
+ const date = dayjs(startDate).format(DAYJS_FORMAT)
109
+ selectedDates.push(date)
110
+ }
111
+
112
+ if (endDate && dayjs(endDate).isValid()) {
113
+ const date = dayjs(endDate).format(DAYJS_FORMAT)
114
+ selectedDates.push(date)
115
+ }
116
+
117
+ options.selectedDates = selectedDates
118
+
119
+ try {
120
+ options = {
121
+ ...options,
122
+ ...JSON.parse(this.element.dataset.options || ''),
123
+ }
124
+ } catch {
125
+ options = options
126
+ }
127
+
128
+ if (options.selectedDates && options.selectedDates.length > 0) {
129
+ this.dateValue = `${options.selectedDates[0]}`
130
+ this.endDateValue = `${options.selectedDates[1]}`
131
+ }
132
+
133
+ return options
134
+ }
135
+
136
+ onClickDate(self: Calendar) {
137
+ const dates = self.context.selectedDates
138
+
139
+ if (dates.length > 0) {
140
+ const startDate = dates[0]
141
+ const endDate = dates[1]
142
+
143
+ this.dateValue = startDate
144
+
145
+ if (endDate) {
146
+ this.endDateValue = endDate
147
+ this.close()
148
+ } else {
149
+ this.endDateValue = ''
150
+ }
151
+ } else {
152
+ this.dateValue = ''
153
+ this.endDateValue = ''
154
+ }
155
+ }
156
+
157
+ setupInputMask() {
158
+ const pattern = this.format.replace(/[^\/]/g, '9')
159
+ const im = new Inputmask(`${pattern}${DELIMITER}${pattern}`, {
160
+ showMaskOnHover: false,
161
+ })
162
+ im.mask(this.inputTarget)
163
+ }
164
+
165
+ dateValueChanged(value: string) {
166
+ this.onClickDateListener = this.onClickDate.bind(this)
167
+
168
+ const endDate = this.endDateValue
169
+ let datesDisplay = ''
170
+
171
+ if (value && value.length > 0) {
172
+ const dayjsDate = dayjs(value)
173
+ const formattedDate = dayjsDate.format(this.format)
174
+ this.hiddenInputTarget.value = dayjsDate.utc().format()
175
+
176
+ if (endDate) {
177
+ datesDisplay = `${formattedDate}${DELIMITER}${dayjs(endDate).format(
178
+ this.format,
179
+ )}`
180
+ } else {
181
+ datesDisplay = `${formattedDate}${DELIMITER}`
182
+ }
183
+ } else {
184
+ this.hiddenInputTarget.value = ''
185
+
186
+ if (endDate) {
187
+ datesDisplay = `${DELIMITER}${dayjs(endDate).format(this.format)}`
188
+ }
189
+ }
190
+
191
+ if (this.hasInputTarget) this.inputTarget.value = datesDisplay
192
+ if (this.hasTriggerTextTarget) {
193
+ const hasValue = (!!value && value.length > 0) || !!endDate
194
+
195
+ this.triggerTarget.dataset.hasValue = `${hasValue}`
196
+
197
+ if (this.triggerTarget.dataset.placeholder && !hasValue) {
198
+ this.triggerTextTarget.textContent =
199
+ this.triggerTarget.dataset.placeholder
200
+ } else {
201
+ this.triggerTextTarget.textContent = datesDisplay
202
+ }
203
+ }
204
+ }
205
+
206
+ endDateValueChanged(value: string) {
207
+ const startDate = this.dateValue
208
+ let datesDisplay = ''
209
+
210
+ if (value && value.length > 0) {
211
+ const dayjsDate = dayjs(value)
212
+ const formattedDate = dayjsDate.format(this.format)
213
+ this.endHiddenInputTarget.value = dayjsDate.utc().format()
214
+
215
+ if (startDate) {
216
+ datesDisplay = `${dayjs(startDate).format(
217
+ this.format,
218
+ )}${DELIMITER}${formattedDate}`
219
+ } else {
220
+ datesDisplay = `${DELIMITER}${formattedDate}`
221
+ }
222
+ } else {
223
+ this.endHiddenInputTarget.value = ''
224
+
225
+ if (startDate) {
226
+ datesDisplay = `${dayjs(startDate).format(this.format)}${DELIMITER}`
227
+ }
228
+ }
229
+
230
+ if (this.hasInputTarget) this.inputTarget.value = datesDisplay
231
+ if (this.hasTriggerTextTarget) {
232
+ const hasValue = (!!value && value.length > 0) || !!startDate
233
+ this.triggerTarget.dataset.hasValue = `${hasValue}`
234
+
235
+ if (this.triggerTarget.dataset.placeholder && !hasValue) {
236
+ this.triggerTextTarget.textContent =
237
+ this.triggerTarget.dataset.placeholder
238
+ } else {
239
+ this.triggerTextTarget.textContent = datesDisplay
240
+ }
241
+ }
242
+ }
243
+ }
@@ -0,0 +1,113 @@
1
+ import { Controller } from '@hotwired/stimulus'
2
+ import { useClickOutside } from 'stimulus-use'
3
+ import {
4
+ ON_OPEN_FOCUS_DELAY,
5
+ openWithOverlay,
6
+ closeWithOverlay,
7
+ focusTrigger,
8
+ showContent,
9
+ hideContent,
10
+ getFocusableElements,
11
+ } from '../utils'
12
+
13
+ export default class extends Controller<HTMLElement> {
14
+ static targets = ['trigger', 'content']
15
+ static values = {
16
+ isOpen: Boolean,
17
+ }
18
+
19
+ declare readonly triggerTarget: HTMLElement
20
+ declare readonly contentTarget: HTMLElement
21
+ declare readonly hasContentTarget: boolean
22
+ declare isOpenValue: boolean
23
+ declare DOMKeydownListener: (event: KeyboardEvent) => void
24
+
25
+ connect() {
26
+ this.DOMKeydownListener = this.onDOMKeydown.bind(this)
27
+ useClickOutside(this, { element: this.contentTarget })
28
+ }
29
+
30
+ open() {
31
+ this.isOpenValue = true
32
+
33
+ setTimeout(() => {
34
+ this.onOpenFocusedElement().focus()
35
+ }, ON_OPEN_FOCUS_DELAY)
36
+ }
37
+
38
+ onOpenFocusedElement() {
39
+ const focusableElements = getFocusableElements(this.contentTarget)
40
+ return focusableElements[0]
41
+ }
42
+
43
+ close() {
44
+ this.isOpenValue = false
45
+ }
46
+
47
+ onDOMKeydown(event: KeyboardEvent) {
48
+ if (!this.isOpenValue) return
49
+
50
+ const key = event.key
51
+
52
+ if (key === 'Escape') {
53
+ this.close()
54
+ } else if (key === 'Tab') {
55
+ const focusableElements = getFocusableElements(this.contentTarget)
56
+
57
+ const firstElement = focusableElements[0]
58
+ const lastElement = focusableElements[focusableElements.length - 1]
59
+
60
+ // If Shift + Tab pressed on first element, go to last element
61
+ if (event.shiftKey && document.activeElement === firstElement) {
62
+ event.preventDefault()
63
+ lastElement.focus()
64
+ }
65
+ // If Tab pressed on last element, go to first element
66
+ else if (!event.shiftKey && document.activeElement === lastElement) {
67
+ event.preventDefault()
68
+ firstElement.focus()
69
+ }
70
+ }
71
+ }
72
+
73
+ isOpenValueChanged(isOpen: boolean, previousIsOpen: boolean) {
74
+ if (isOpen) {
75
+ openWithOverlay(this.contentTarget.id)
76
+
77
+ showContent({
78
+ trigger: this.triggerTarget,
79
+ content: this.contentTarget,
80
+ contentContainer: this.contentTarget,
81
+ })
82
+
83
+ this.setupEventListeners()
84
+ } else {
85
+ closeWithOverlay(this.contentTarget.id)
86
+
87
+ hideContent({
88
+ trigger: this.triggerTarget,
89
+ content: this.contentTarget,
90
+ contentContainer: this.contentTarget,
91
+ })
92
+
93
+ this.cleanupEventListeners()
94
+
95
+ // Only focus trigger when is previously opened
96
+ if (previousIsOpen) {
97
+ focusTrigger(this.triggerTarget)
98
+ }
99
+ }
100
+ }
101
+
102
+ setupEventListeners() {
103
+ document.addEventListener('keydown', this.DOMKeydownListener)
104
+ }
105
+
106
+ cleanupEventListeners() {
107
+ document.removeEventListener('keydown', this.DOMKeydownListener)
108
+ }
109
+
110
+ disconnect() {
111
+ this.cleanupEventListeners()
112
+ }
113
+ }
@@ -0,0 +1,133 @@
1
+ import DropdownMenuSubController from './dropdown_menu_sub_controller'
2
+ import DropdownMenuRootController from './dropdown_menu_root_controller'
3
+
4
+ export default class extends DropdownMenuRootController {
5
+ static values = {
6
+ isOpen: Boolean,
7
+ setEqualWidth: { type: Boolean, default: false },
8
+ closestContentSelector: {
9
+ type: String,
10
+ default:
11
+ '[data-dropdown-menu-target="content"], [data-dropdown-menu-sub-target="content"]',
12
+ },
13
+ }
14
+
15
+ declare DOMKeydownListener: (event: KeyboardEvent) => void
16
+ declare subMenuControllers: DropdownMenuSubController[]
17
+
18
+ connect() {
19
+ super.connect()
20
+ }
21
+
22
+ onOpen(_event: MouseEvent | KeyboardEvent) {
23
+ // Sub menus are not connected to the DOM yet when dropdown menu is connected.
24
+ // So we initialize them here instead of in connect().
25
+ if (this.subMenuControllers === undefined) {
26
+ let subMenuControllers = [] as DropdownMenuSubController[]
27
+
28
+ const subMenus = Array.from(
29
+ this.contentTarget.querySelectorAll(
30
+ '[data-shadcn-phlexcomponents="dropdown-menu-sub"]',
31
+ ),
32
+ )
33
+
34
+ subMenus.forEach((subMenu) => {
35
+ const subMenuController =
36
+ window.Stimulus.getControllerForElementAndIdentifier(
37
+ subMenu,
38
+ 'dropdown-menu-sub',
39
+ ) as DropdownMenuSubController
40
+
41
+ if (subMenuController) {
42
+ subMenuControllers.push(subMenuController)
43
+ }
44
+ })
45
+
46
+ this.subMenuControllers = subMenuControllers
47
+ }
48
+ }
49
+
50
+ focusItem(event: MouseEvent | KeyboardEvent) {
51
+ const item = event.currentTarget as HTMLElement
52
+ let items = [] as HTMLElement[]
53
+ const content = item.closest(
54
+ this.closestContentSelectorValue,
55
+ ) as HTMLElement
56
+
57
+ const isSubMenu =
58
+ content.dataset.shadcnPhlexcomponents === 'dropdown-menu-sub-content'
59
+
60
+ if (isSubMenu) {
61
+ const subMenu = content.closest(
62
+ '[data-shadcn-phlexcomponents="dropdown-menu-sub"]',
63
+ )
64
+ const subMenuController = this.subMenuControllers.find(
65
+ (subMenuController) => subMenuController.element == subMenu,
66
+ )
67
+ if (subMenuController) {
68
+ items = subMenuController.items
69
+ }
70
+ } else {
71
+ items = this.items
72
+ }
73
+
74
+ let index = items.indexOf(item)
75
+
76
+ if (event instanceof KeyboardEvent) {
77
+ const key = event.key
78
+ let newIndex = 0
79
+
80
+ if (key === 'ArrowUp') {
81
+ newIndex = index - 1
82
+ if (newIndex < 0) {
83
+ newIndex = 0
84
+ }
85
+ } else {
86
+ newIndex = index + 1
87
+ if (newIndex > items.length - 1) {
88
+ newIndex = items.length - 1
89
+ }
90
+ }
91
+
92
+ items[newIndex].focus()
93
+ } else {
94
+ // item mouseover event
95
+ items[index].focus()
96
+ }
97
+
98
+ // Close submenus on the same level
99
+ const subMenusInContent = Array.from(
100
+ content.querySelectorAll(
101
+ '[data-shadcn-phlexcomponents="dropdown-menu-sub"]',
102
+ ),
103
+ ) as HTMLElement[]
104
+
105
+ subMenusInContent.forEach((subMenu) => {
106
+ const subMenuController = this.subMenuControllers.find(
107
+ (subMenuController) => subMenuController.element == subMenu,
108
+ )
109
+
110
+ if (subMenuController) {
111
+ subMenuController.closeImmediately()
112
+ }
113
+ })
114
+ }
115
+
116
+ onClose() {
117
+ this.subMenuControllers.forEach((subMenuController) => {
118
+ subMenuController.closeImmediately()
119
+ })
120
+ }
121
+
122
+ onSelect(event: MouseEvent | KeyboardEvent) {
123
+ if (event instanceof KeyboardEvent) {
124
+ const key = event.key
125
+ const item = (event.currentTarget || event.target) as HTMLElement
126
+
127
+ // For rails button_to
128
+ if (item && (key === 'Enter' || key === ' ')) {
129
+ item.click()
130
+ }
131
+ }
132
+ }
133
+ }
@@ -0,0 +1,234 @@
1
+ import { Controller } from '@hotwired/stimulus'
2
+ import { useClickOutside } from 'stimulus-use'
3
+ import {
4
+ getSameLevelItems,
5
+ ON_OPEN_FOCUS_DELAY,
6
+ lockScroll,
7
+ unlockScroll,
8
+ showContent,
9
+ hideContent,
10
+ initFloatingUi,
11
+ focusTrigger,
12
+ } from '../utils'
13
+
14
+ export default class extends Controller<HTMLElement> {
15
+ static targets = ['trigger', 'contentContainer', 'content', 'item']
16
+ static values = {
17
+ isOpen: Boolean,
18
+ setEqualWidth: { type: Boolean, default: false },
19
+ closestContentSelector: { type: String, default: '' },
20
+ }
21
+
22
+ declare isOpenValue: boolean
23
+ declare setEqualWidthValue: boolean
24
+ declare closestContentSelectorValue: string
25
+ declare readonly triggerTarget: HTMLElement
26
+ declare readonly contentContainerTarget: HTMLElement
27
+ declare readonly contentTarget: HTMLElement
28
+ declare readonly itemTargets: HTMLElement[]
29
+ declare items: HTMLElement[]
30
+ declare DOMKeydownListener: (event: KeyboardEvent) => void
31
+ declare cleanup: () => void
32
+
33
+ connect() {
34
+ this.DOMKeydownListener = this.onDOMKeydown.bind(this)
35
+ this.items = getSameLevelItems({
36
+ content: this.contentTarget,
37
+ items: this.itemTargets,
38
+ closestContentSelector: this.closestContentSelectorValue,
39
+ })
40
+ useClickOutside(this, { element: this.contentTarget, dispatchEvent: false })
41
+ }
42
+
43
+ toggle(event: MouseEvent) {
44
+ if (this.isOpenValue) {
45
+ this.close()
46
+ } else {
47
+ this.open(event)
48
+ }
49
+ }
50
+
51
+ open(event: MouseEvent | KeyboardEvent) {
52
+ this.isOpenValue = true
53
+ this.onOpen(event)
54
+
55
+ setTimeout(() => {
56
+ this.onOpenFocusedElement(event).focus()
57
+ }, ON_OPEN_FOCUS_DELAY)
58
+ }
59
+
60
+ onOpen(_event: MouseEvent | KeyboardEvent) {}
61
+
62
+ onOpenFocusedElement(event: MouseEvent | KeyboardEvent) {
63
+ let itemIndex = null as number | null
64
+
65
+ if (event instanceof KeyboardEvent) {
66
+ const key = event.key
67
+
68
+ if (['ArrowDown', 'Enter', ' '].includes(key)) {
69
+ itemIndex = 0
70
+ }
71
+ }
72
+
73
+ if (itemIndex !== null) {
74
+ return this.items[itemIndex]
75
+ } else {
76
+ return this.contentTarget
77
+ }
78
+ }
79
+
80
+ close() {
81
+ this.isOpenValue = false
82
+ this.onClose()
83
+ }
84
+
85
+ onClose() {}
86
+
87
+ onItemFocus(event: FocusEvent) {
88
+ const item = event.currentTarget as HTMLElement
89
+ item.tabIndex = 0
90
+ }
91
+
92
+ onItemBlur(event: FocusEvent) {
93
+ const item = event.currentTarget as HTMLElement
94
+ item.tabIndex = -1
95
+ }
96
+
97
+ focusItemByIndex(
98
+ event: KeyboardEvent | null = null,
99
+ index: number | null = null,
100
+ ) {
101
+ if (event !== null) {
102
+ const key = event.key
103
+
104
+ if (key === 'ArrowUp') {
105
+ this.items[this.items.length - 1].focus()
106
+ } else {
107
+ this.items[0].focus()
108
+ }
109
+ } else if (index !== null) {
110
+ this.items[index].focus()
111
+ }
112
+ }
113
+
114
+ focusItem(event: MouseEvent | KeyboardEvent) {
115
+ let item = event.currentTarget as HTMLElement
116
+ const index = this.items.indexOf(item)
117
+
118
+ if (event instanceof KeyboardEvent) {
119
+ const key = event.key
120
+ let newIndex = 0
121
+
122
+ if (key === 'ArrowUp') {
123
+ newIndex = index - 1
124
+
125
+ if (newIndex < 0) {
126
+ newIndex = 0
127
+ }
128
+ } else {
129
+ newIndex = index + 1
130
+
131
+ if (newIndex > this.items.length - 1) {
132
+ newIndex = this.items.length - 1
133
+ }
134
+ }
135
+
136
+ this.items[newIndex].focus()
137
+ } else {
138
+ // item mouseover event
139
+ this.items[index].focus()
140
+ }
141
+ }
142
+
143
+ focusContent(event: MouseEvent) {
144
+ const item = event.currentTarget as HTMLElement
145
+ const content = item.closest(
146
+ this.closestContentSelectorValue,
147
+ ) as HTMLElement
148
+ content.focus()
149
+ }
150
+
151
+ select(event: MouseEvent | KeyboardEvent) {
152
+ if (!this.isOpenValue) return
153
+
154
+ this.onSelect(event)
155
+ this.close()
156
+ }
157
+
158
+ onSelect(_event: MouseEvent | KeyboardEvent) {}
159
+
160
+ isOpenValueChanged(isOpen: boolean, previousIsOpen: boolean) {
161
+ if (isOpen) {
162
+ lockScroll()
163
+
164
+ showContent({
165
+ trigger: this.triggerTarget,
166
+ content: this.contentTarget,
167
+ contentContainer: this.contentContainerTarget,
168
+ setEqualWidth: this.setEqualWidthValue,
169
+ })
170
+
171
+ this.cleanup = initFloatingUi({
172
+ referenceElement: this.triggerTarget,
173
+ floatingElement: this.contentContainerTarget,
174
+ side: this.contentTarget.dataset.side,
175
+ align: this.contentTarget.dataset.align,
176
+ sideOffset: 4,
177
+ })
178
+
179
+ this.setupEventListeners()
180
+ } else {
181
+ unlockScroll()
182
+
183
+ hideContent({
184
+ trigger: this.triggerTarget,
185
+ content: this.contentTarget,
186
+ contentContainer: this.contentContainerTarget,
187
+ })
188
+
189
+ this.cleanupEventListeners()
190
+
191
+ // Only focus trigger when is previously opened
192
+ if (previousIsOpen) {
193
+ focusTrigger(this.triggerTarget)
194
+ }
195
+ }
196
+ }
197
+
198
+ clickOutside(event: MouseEvent) {
199
+ const target = event.target
200
+ // Let #toggle to handle state when clicked on trigger
201
+ if (target === this.triggerTarget) return
202
+
203
+ this.close()
204
+ }
205
+
206
+ onDOMKeydown(event: KeyboardEvent) {
207
+ if (!this.isOpenValue) return
208
+
209
+ const key = event.key
210
+
211
+ if (['Tab', 'Enter', ' '].includes(key)) event.preventDefault()
212
+
213
+ if (key === 'Home') {
214
+ this.focusItemByIndex(null, 0)
215
+ } else if (key === 'End') {
216
+ this.focusItemByIndex(null, this.items.length - 1)
217
+ } else if (key === 'Escape') {
218
+ this.close()
219
+ }
220
+ }
221
+
222
+ setupEventListeners() {
223
+ document.addEventListener('keydown', this.DOMKeydownListener)
224
+ }
225
+
226
+ cleanupEventListeners() {
227
+ if (this.cleanup) this.cleanup()
228
+ document.removeEventListener('keydown', this.DOMKeydownListener)
229
+ }
230
+
231
+ disconnect() {
232
+ this.cleanupEventListeners()
233
+ }
234
+ }