shadcn_phlexcomponents 0.1.0

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 (168) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +39 -0
  3. data/Rakefile +12 -0
  4. data/app/assets/tailwind/tailwindcss-animate.css +318 -0
  5. data/app/assets/tailwind/vanilla-calendar-pro.css +461 -0
  6. data/app/javascript/controllers/accordion_controller.js +133 -0
  7. data/app/javascript/controllers/alert_dialog_controller.js +157 -0
  8. data/app/javascript/controllers/avatar_controller.js +15 -0
  9. data/app/javascript/controllers/checkbox_controller.js +28 -0
  10. data/app/javascript/controllers/collapsible_controller.js +35 -0
  11. data/app/javascript/controllers/combobox_controller.js +291 -0
  12. data/app/javascript/controllers/datepicker_controller.js +47 -0
  13. data/app/javascript/controllers/dialog_controller.js +159 -0
  14. data/app/javascript/controllers/dropdown_menu_controller.js +193 -0
  15. data/app/javascript/controllers/hover_card_controller.js +135 -0
  16. data/app/javascript/controllers/loading_button_controller.js +15 -0
  17. data/app/javascript/controllers/popover_controller.js +124 -0
  18. data/app/javascript/controllers/progress_controller.js +14 -0
  19. data/app/javascript/controllers/radio_group_controller.js +90 -0
  20. data/app/javascript/controllers/select_controller.js +294 -0
  21. data/app/javascript/controllers/sheet_controller.js +159 -0
  22. data/app/javascript/controllers/sidebar_controller.js +36 -0
  23. data/app/javascript/controllers/sidebar_trigger_controller.js +15 -0
  24. data/app/javascript/controllers/switch_controller.js +24 -0
  25. data/app/javascript/controllers/tabs_controller.js +73 -0
  26. data/app/javascript/controllers/theme_switcher_controller.js +32 -0
  27. data/app/javascript/controllers/toast_container_controller.js +22 -0
  28. data/app/javascript/controllers/toast_controller.js +45 -0
  29. data/app/javascript/controllers/tooltip_controller.js +135 -0
  30. data/lib/components/accordion.rb +38 -0
  31. data/lib/components/accordion_content.rb +28 -0
  32. data/lib/components/accordion_item.rb +26 -0
  33. data/lib/components/accordion_trigger.rb +45 -0
  34. data/lib/components/alert.rb +40 -0
  35. data/lib/components/alert_description.rb +11 -0
  36. data/lib/components/alert_dialog.rb +60 -0
  37. data/lib/components/alert_dialog_action.rb +22 -0
  38. data/lib/components/alert_dialog_action_to.rb +37 -0
  39. data/lib/components/alert_dialog_cancel.rb +22 -0
  40. data/lib/components/alert_dialog_content.rb +40 -0
  41. data/lib/components/alert_dialog_description.rb +22 -0
  42. data/lib/components/alert_dialog_footer.rb +11 -0
  43. data/lib/components/alert_dialog_header.rb +11 -0
  44. data/lib/components/alert_dialog_title.rb +22 -0
  45. data/lib/components/alert_dialog_trigger.rb +50 -0
  46. data/lib/components/alert_title.rb +11 -0
  47. data/lib/components/aspect_ratio.rb +19 -0
  48. data/lib/components/avatar.rb +31 -0
  49. data/lib/components/avatar_fallback.rb +21 -0
  50. data/lib/components/avatar_image.rb +20 -0
  51. data/lib/components/badge.rb +36 -0
  52. data/lib/components/base.rb +108 -0
  53. data/lib/components/breadcrumb.rb +51 -0
  54. data/lib/components/breadcrumb_ellipsis.rb +23 -0
  55. data/lib/components/breadcrumb_item.rb +11 -0
  56. data/lib/components/breadcrumb_link.rb +7 -0
  57. data/lib/components/breadcrumb_page.rb +21 -0
  58. data/lib/components/breadcrumb_separator.rb +26 -0
  59. data/lib/components/button.rb +53 -0
  60. data/lib/components/card.rb +31 -0
  61. data/lib/components/card_content.rb +11 -0
  62. data/lib/components/card_description.rb +11 -0
  63. data/lib/components/card_footer.rb +11 -0
  64. data/lib/components/card_header.rb +11 -0
  65. data/lib/components/card_title.rb +11 -0
  66. data/lib/components/checkbox.rb +65 -0
  67. data/lib/components/checkbox_group.rb +48 -0
  68. data/lib/components/collapsible.rb +32 -0
  69. data/lib/components/collapsible_content.rb +25 -0
  70. data/lib/components/collapsible_trigger.rb +50 -0
  71. data/lib/components/datepicker.rb +38 -0
  72. data/lib/components/dialog.rb +52 -0
  73. data/lib/components/dialog_close.rb +42 -0
  74. data/lib/components/dialog_content.rb +54 -0
  75. data/lib/components/dialog_description.rb +22 -0
  76. data/lib/components/dialog_footer.rb +11 -0
  77. data/lib/components/dialog_header.rb +11 -0
  78. data/lib/components/dialog_title.rb +22 -0
  79. data/lib/components/dialog_trigger.rb +50 -0
  80. data/lib/components/dropdown_menu.rb +50 -0
  81. data/lib/components/dropdown_menu_content.rb +49 -0
  82. data/lib/components/dropdown_menu_item.rb +57 -0
  83. data/lib/components/dropdown_menu_item_to.rb +25 -0
  84. data/lib/components/dropdown_menu_label.rb +12 -0
  85. data/lib/components/dropdown_menu_separator.rb +20 -0
  86. data/lib/components/dropdown_menu_trigger.rb +58 -0
  87. data/lib/components/hover_card.rb +33 -0
  88. data/lib/components/hover_card_content.rb +36 -0
  89. data/lib/components/hover_card_trigger.rb +50 -0
  90. data/lib/components/input.rb +32 -0
  91. data/lib/components/label.rb +15 -0
  92. data/lib/components/link.rb +26 -0
  93. data/lib/components/loading_button.rb +21 -0
  94. data/lib/components/pagination.rb +38 -0
  95. data/lib/components/pagination_ellipsis.rb +24 -0
  96. data/lib/components/pagination_link.rb +34 -0
  97. data/lib/components/pagination_next.rb +32 -0
  98. data/lib/components/pagination_previous.rb +32 -0
  99. data/lib/components/popover.rb +35 -0
  100. data/lib/components/popover_content.rb +37 -0
  101. data/lib/components/popover_trigger.rb +52 -0
  102. data/lib/components/progress.rb +37 -0
  103. data/lib/components/radio_group.rb +62 -0
  104. data/lib/components/radio_group_item.rb +66 -0
  105. data/lib/components/select.rb +189 -0
  106. data/lib/components/select_content.rb +59 -0
  107. data/lib/components/select_group.rb +23 -0
  108. data/lib/components/select_item.rb +58 -0
  109. data/lib/components/select_label.rb +23 -0
  110. data/lib/components/select_trigger.rb +54 -0
  111. data/lib/components/separator.rb +29 -0
  112. data/lib/components/sheet.rb +53 -0
  113. data/lib/components/sheet_close.rb +42 -0
  114. data/lib/components/sheet_content.rb +67 -0
  115. data/lib/components/sheet_description.rb +22 -0
  116. data/lib/components/sheet_footer.rb +11 -0
  117. data/lib/components/sheet_header.rb +11 -0
  118. data/lib/components/sheet_title.rb +22 -0
  119. data/lib/components/sheet_trigger.rb +50 -0
  120. data/lib/components/sidebar.rb +103 -0
  121. data/lib/components/sidebar_container.rb +11 -0
  122. data/lib/components/sidebar_content.rb +11 -0
  123. data/lib/components/sidebar_footer.rb +11 -0
  124. data/lib/components/sidebar_group.rb +11 -0
  125. data/lib/components/sidebar_group_content.rb +11 -0
  126. data/lib/components/sidebar_group_label.rb +16 -0
  127. data/lib/components/sidebar_header.rb +11 -0
  128. data/lib/components/sidebar_inset.rb +15 -0
  129. data/lib/components/sidebar_menu.rb +11 -0
  130. data/lib/components/sidebar_menu_button.rb +61 -0
  131. data/lib/components/sidebar_menu_item.rb +9 -0
  132. data/lib/components/sidebar_menu_sub.rb +14 -0
  133. data/lib/components/sidebar_menu_sub_button.rb +48 -0
  134. data/lib/components/sidebar_menu_sub_item.rb +9 -0
  135. data/lib/components/sidebar_trigger.rb +40 -0
  136. data/lib/components/skeleton.rb +11 -0
  137. data/lib/components/switch.rb +65 -0
  138. data/lib/components/table.rb +73 -0
  139. data/lib/components/table_body.rb +11 -0
  140. data/lib/components/table_caption.rb +11 -0
  141. data/lib/components/table_cell.rb +11 -0
  142. data/lib/components/table_footer.rb +11 -0
  143. data/lib/components/table_head.rb +14 -0
  144. data/lib/components/table_header.rb +11 -0
  145. data/lib/components/table_row.rb +11 -0
  146. data/lib/components/tabs.rb +38 -0
  147. data/lib/components/tabs_content.rb +35 -0
  148. data/lib/components/tabs_list.rb +23 -0
  149. data/lib/components/tabs_trigger.rb +45 -0
  150. data/lib/components/textarea.rb +28 -0
  151. data/lib/components/theme_switcher.rb +21 -0
  152. data/lib/components/toast.rb +100 -0
  153. data/lib/components/toast_action.rb +38 -0
  154. data/lib/components/toast_action_to.rb +25 -0
  155. data/lib/components/toast_container.rb +44 -0
  156. data/lib/components/toast_content.rb +11 -0
  157. data/lib/components/toast_description.rb +11 -0
  158. data/lib/components/toast_title.rb +11 -0
  159. data/lib/components/tooltip.rb +34 -0
  160. data/lib/components/tooltip_content.rb +42 -0
  161. data/lib/components/tooltip_trigger.rb +50 -0
  162. data/lib/install/install_shadcn_phlexcomponents.rb +12 -0
  163. data/lib/shadcn_phlexcomponents/alias.rb +132 -0
  164. data/lib/shadcn_phlexcomponents/engine.rb +11 -0
  165. data/lib/shadcn_phlexcomponents/version.rb +5 -0
  166. data/lib/shadcn_phlexcomponents.rb +9 -0
  167. data/lib/tasks/install.rake +10 -0
  168. metadata +264 -0
@@ -0,0 +1,157 @@
1
+ import { Controller } from '@hotwired/stimulus'
2
+
3
+ export default class extends Controller {
4
+ static targets = ['trigger', 'content']
5
+
6
+ connect() {
7
+ this.DOMClickListener = this.onDOMClick.bind(this)
8
+ this.DOMKeydownListener = this.onDOMKeydown.bind(this)
9
+
10
+ this.focusableElements = this.contentTarget.querySelectorAll(
11
+ 'button, [href], input:not([type="hidden"]), select, textarea, [tabindex]:not([tabindex="-1"])',
12
+ )
13
+
14
+ this.firstElement = this.focusableElements[0]
15
+ this.lastElement = this.focusableElements[this.focusableElements.length - 1]
16
+ this.contentElement = document.querySelector(`#${this.contentTarget.id}`)
17
+ }
18
+
19
+ open() {
20
+ this.showOverlay()
21
+ this.contentElement.classList.remove('hidden')
22
+ this.contentElement.dataset.state = 'open'
23
+ this.triggerTarget.ariaExpanded = true
24
+ this.setupEventListeners()
25
+ this.addInert()
26
+
27
+ if (window.innerHeight < document.documentElement.scrollHeight) {
28
+ document.body.dataset.scrollLocked = 1
29
+ }
30
+ document.body.appendChild(this.contentElement)
31
+ this.firstElement.focus() // must be after appendChild
32
+ }
33
+
34
+ close() {
35
+ this.contentElement.dataset.state = 'closed'
36
+ this.triggerTarget.ariaExpanded = false
37
+ this.cleanupEventListeners()
38
+ this.removeInert()
39
+ this.element.appendChild(this.contentElement)
40
+
41
+ setTimeout(() => {
42
+ this.contentElement.classList.add('hidden')
43
+ }, 100)
44
+
45
+ setTimeout(() => {
46
+ delete document.body.dataset.scrollLocked
47
+ this.removeOverlay()
48
+ }, 150)
49
+
50
+ this.focusTrigger()
51
+ }
52
+
53
+ focusTrigger() {
54
+ if (this.triggerTarget.dataset.asChild === 'false') {
55
+ this.triggerTarget.firstElementChild.focus()
56
+ } else {
57
+ this.triggerTarget.focus()
58
+ }
59
+ }
60
+
61
+ isOpen() {
62
+ return this.triggerTarget.ariaExpanded === 'true'
63
+ }
64
+
65
+ showOverlay() {
66
+ const elem = document.createElement('div')
67
+ elem.classList.add(
68
+ 'fixed',
69
+ 'inset-0',
70
+ 'z-50',
71
+ 'bg-black/80',
72
+ 'data-[state=open]:animate-in',
73
+ 'data-[state=closed]:animate-out',
74
+ 'data-[state=closed]:fade-out-0',
75
+ 'data-[state=open]:fade-in-0',
76
+ 'pointer-events-auto',
77
+ )
78
+ elem.dataset.state = 'open'
79
+ elem.ariaHidden = true
80
+ elem.dataset.overlay = true
81
+ document.body.appendChild(elem)
82
+ }
83
+
84
+ removeOverlay() {
85
+ if (document.querySelector('[data-overlay]')) {
86
+ document.querySelector('[data-overlay]').remove()
87
+ }
88
+ }
89
+
90
+ // Global listeners
91
+ onDOMClick(event) {
92
+ if (!this.isOpen()) return
93
+
94
+ const target = event.target
95
+ const trigger = event.target.closest(
96
+ '[data-shadcn-phlexcomponents--alert-dialog-target="trigger"]',
97
+ )
98
+
99
+ if (trigger) return
100
+
101
+ const close = target.closest(
102
+ '[data-action*="shadcn-phlexcomponents--alert-dialog#close"]',
103
+ )
104
+
105
+ if (
106
+ close ||
107
+ (target.dataset.action &&
108
+ target.dataset.action.includes(
109
+ 'shadcn-phlexcomponents--alert-dialog#close',
110
+ ))
111
+ )
112
+ this.close()
113
+ }
114
+
115
+ onDOMKeydown(event) {
116
+ if (!this.isOpen()) return
117
+
118
+ const key = event.key
119
+
120
+ if (key === 'Escape') {
121
+ this.close()
122
+ } else if (key === 'Tab') {
123
+ // If Shift + Tab pressed on first element, go to last element
124
+ if (event.shiftKey && document.activeElement === this.firstElement) {
125
+ event.preventDefault()
126
+ this.lastElement.focus()
127
+ }
128
+ // If Tab pressed on last element, go to first element
129
+ else if (!event.shiftKey && document.activeElement === this.lastElement) {
130
+ event.preventDefault()
131
+ this.firstElement.focus()
132
+ }
133
+ }
134
+ }
135
+
136
+ setupEventListeners() {
137
+ document.addEventListener('click', this.DOMClickListener)
138
+ document.addEventListener('keydown', this.DOMKeydownListener)
139
+ }
140
+
141
+ cleanupEventListeners() {
142
+ document.removeEventListener('click', this.DOMClickListener)
143
+ document.removeEventListener('keydown', this.DOMKeydownListener)
144
+ }
145
+
146
+ addInert() {
147
+ Array.from(document.body.children)
148
+ .filter((el) => el !== this.contentElement)
149
+ .forEach((el) => el.setAttribute('inert', ''))
150
+ }
151
+
152
+ removeInert() {
153
+ Array.from(document.body.children)
154
+ .filter((el) => el.hasAttribute('inert'))
155
+ .forEach((el) => el.removeAttribute('inert'))
156
+ }
157
+ }
@@ -0,0 +1,15 @@
1
+ import { Controller } from '@hotwired/stimulus'
2
+
3
+ export default class extends Controller {
4
+ static targets = ['image', 'fallback']
5
+
6
+ connect() {
7
+ this.imageTarget.onerror = () => {
8
+ if (this.hasFallbackTarget) {
9
+ this.fallbackTarget.classList.remove('hidden')
10
+ }
11
+
12
+ this.imageTarget.classList.add('hidden')
13
+ }
14
+ }
15
+ }
@@ -0,0 +1,28 @@
1
+ import { Controller } from '@hotwired/stimulus'
2
+
3
+ export default class extends Controller {
4
+ static targets = ['input']
5
+ static values = {
6
+ checked: Boolean,
7
+ }
8
+
9
+ toggle() {
10
+ this.checkedValue = !this.checkedValue
11
+ }
12
+
13
+ preventDefault(event) {
14
+ event.preventDefault()
15
+ }
16
+
17
+ checkedValueChanged(value) {
18
+ if (value === true) {
19
+ this.element.ariaChecked = true
20
+ this.element.dataset.checked = true
21
+ this.inputTarget.checked = true
22
+ } else {
23
+ this.element.ariaChecked = false
24
+ this.element.dataset.checked = false
25
+ this.inputTarget.checked = false
26
+ }
27
+ }
28
+ }
@@ -0,0 +1,35 @@
1
+ import { Controller } from '@hotwired/stimulus'
2
+
3
+ export default class extends Controller {
4
+ static targets = ['trigger', 'content', 'item']
5
+
6
+ connect() {
7
+ if (this.isOpen()) {
8
+ this.open()
9
+ }
10
+ }
11
+
12
+ toggle() {
13
+ if (this.isOpen()) {
14
+ this.close()
15
+ } else {
16
+ this.open()
17
+ }
18
+ }
19
+
20
+ isOpen() {
21
+ return this.triggerTarget.ariaExpanded === 'true'
22
+ }
23
+
24
+ open() {
25
+ this.triggerTarget.ariaExpanded = true
26
+ this.triggerTarget.dataset.state = 'open'
27
+ this.contentTarget.classList.remove('hidden')
28
+ }
29
+
30
+ close() {
31
+ this.triggerTarget.ariaExpanded = false
32
+ this.triggerTarget.dataset.state = 'closed'
33
+ this.contentTarget.classList.add('hidden')
34
+ }
35
+ }
@@ -0,0 +1,291 @@
1
+ import { Controller } from '@hotwired/stimulus'
2
+ import {
3
+ computePosition,
4
+ flip,
5
+ shift,
6
+ offset,
7
+ autoUpdate,
8
+ } from '@floating-ui/dom'
9
+
10
+ export default class extends Controller {
11
+ static targets = [
12
+ 'trigger',
13
+ 'contentWrapper',
14
+ 'content',
15
+ 'item',
16
+ 'triggerText',
17
+ 'itemCheckIcon',
18
+ 'select',
19
+ 'searchInput',
20
+ ]
21
+
22
+ static values = {
23
+ selected: String,
24
+ }
25
+
26
+ connect() {
27
+ this.contentTarget.dataset.state = this.isOpen() ? 'open' : 'closed'
28
+ this.clickOutsideListener = this.onClickOutside.bind(this)
29
+ this.comboboxKeydownListener = this.onComboboxKeydown.bind(this)
30
+ this.searchInputKeydownListener = this.debounce(
31
+ this.onSearchInputKeydown.bind(this),
32
+ 100,
33
+ )
34
+
35
+ this.items = [
36
+ ...this.element.querySelectorAll(
37
+ '[data-shadcn-phlexcomponents--combobox-target="item"]:not([data-disabled])',
38
+ ),
39
+ ]
40
+ }
41
+
42
+ // Methods
43
+ toggle() {
44
+ if (this.isOpen()) {
45
+ this.close()
46
+ } else {
47
+ this.open()
48
+ }
49
+ }
50
+
51
+ open() {
52
+ const triggerWidth = this.triggerTarget.offsetWidth
53
+ this.contentWrapperTarget.classList.remove('hidden')
54
+
55
+ const contentWrapperWidth = this.contentWrapperTarget.offsetWidth
56
+
57
+ if (contentWrapperWidth < triggerWidth) {
58
+ this.contentWrapperTarget.style.width = `${triggerWidth}px`
59
+ }
60
+
61
+ this.searchInputTarget.focus()
62
+
63
+ if (this.selectedValue) {
64
+ const item = this.itemTargets.find(
65
+ (i) => i.dataset.value === this.selectedValue,
66
+ )
67
+ const index = this.items.indexOf(item)
68
+ if (index > -1) {
69
+ this.highlightItem(index)
70
+ }
71
+ } else {
72
+ this.highlightItem(0)
73
+ }
74
+
75
+ this.triggerTarget.ariaExpanded = true
76
+ this.contentTarget.dataset.state = 'open'
77
+
78
+ if (window.innerHeight < document.documentElement.scrollHeight) {
79
+ document.body.dataset.scrollLocked = 1
80
+ }
81
+ this.setupEventListeners()
82
+
83
+ this.cleanup = autoUpdate(
84
+ this.triggerTarget,
85
+ this.contentWrapperTarget,
86
+ () => {
87
+ computePosition(this.triggerTarget, this.contentWrapperTarget, {
88
+ placement: 'bottom',
89
+ strategy: 'fixed',
90
+ middleware: [flip(), shift(), offset(4)],
91
+ }).then(({ x, y }) => {
92
+ Object.assign(this.contentWrapperTarget.style, {
93
+ left: `${x}px`,
94
+ top: `${y}px`,
95
+ })
96
+ })
97
+ },
98
+ )
99
+ }
100
+
101
+ close() {
102
+ this.contentTarget.dataset.state = 'closed'
103
+ this.triggerTarget.ariaExpanded = false
104
+ this.cleanup()
105
+ this.cleanupEventListeners()
106
+ delete document.body.dataset.scrollLocked
107
+
108
+ setTimeout(() => {
109
+ this.contentWrapperTarget.classList.add('hidden')
110
+ }, 100)
111
+
112
+ if (this.triggerTarget.nodeName === 'DIV') {
113
+ this.triggerTarget.firstElementChild.focus()
114
+ } else {
115
+ this.triggerTarget.focus()
116
+ }
117
+ }
118
+
119
+ isOpen() {
120
+ return this.triggerTarget.ariaExpanded === 'true'
121
+ }
122
+
123
+ highlightItem(selectedIndex) {
124
+ const item = this.items[selectedIndex]
125
+ console.log('this.highlightItem', item)
126
+ item.dataset.selected = true
127
+
128
+ this.items.forEach((item, index) => {
129
+ if (index !== selectedIndex) item.dataset.selected = false
130
+ })
131
+ }
132
+
133
+ currentHighlightedIndex() {
134
+ console.log('item')
135
+ const item = this.items.find((i) => i.dataset.selected === 'true')
136
+ return this.items.indexOf(item)
137
+ console.log('item', item)
138
+
139
+ // if (item) {
140
+ // return this.items.indexOf(item)
141
+ // }
142
+
143
+ // return 0
144
+ }
145
+
146
+ debounce(callback, wait) {
147
+ let timeoutId = null
148
+ return (...args) => {
149
+ window.clearTimeout(timeoutId)
150
+ timeoutId = window.setTimeout(() => {
151
+ callback.apply(null, args)
152
+ }, wait)
153
+ }
154
+ }
155
+
156
+ onSearchInputKeydown(event) {
157
+ // console.log('search ecet', event)
158
+ this.filterItems()
159
+ }
160
+
161
+ onArrowUpKeydown(event) {
162
+ const index = this.currentHighlightedIndex()
163
+ console.log('onArrowUpKeydown index', index)
164
+
165
+ if (index > 0) {
166
+ this.highlightItem(index - 1)
167
+ }
168
+
169
+ event.preventDefault()
170
+ }
171
+
172
+ onArrowDownKeydown(event) {
173
+ const index = this.currentHighlightedIndex()
174
+
175
+ console.log('onArrowDownKeydown index', index)
176
+
177
+ console.log('index + 1 < this.items.length', index + 1 < this.items.length)
178
+ if (index + 1 < this.items.length) {
179
+ this.highlightItem(index + 1)
180
+ }
181
+
182
+ event.preventDefault()
183
+ }
184
+
185
+ selectItem(event) {
186
+ const index = this.currentHighlightedIndex()
187
+ const item = this.items[index]
188
+ item.click()
189
+ event.preventDefault()
190
+ }
191
+
192
+ filterItems = () => {
193
+ const search = this.searchInputTarget.value
194
+ const filteredItems = this.items.filter((i) =>
195
+ i.innerText.toLowerCase().includes(search),
196
+ )
197
+
198
+ this.items.forEach((i) => {
199
+ if (filteredItems.includes(i)) {
200
+ i.classList.remove('hidden')
201
+ } else {
202
+ i.classList.add('hidden')
203
+ }
204
+ })
205
+
206
+ this.highlightItem(this.filterItems[0])
207
+ }
208
+
209
+ onItemClick(event) {
210
+ const item = event.currentTarget
211
+ const value = item.dataset.value
212
+ this.selectedValue = value
213
+ this.close()
214
+ }
215
+
216
+ onMouseOver(event) {
217
+ const item = event.currentTarget
218
+ const index = this.items.indexOf(item)
219
+ this.highlightItem(index)
220
+ }
221
+
222
+ selectedValueChanged(value) {
223
+ const item = this.itemTargets.find((i) => i.dataset.value === value)
224
+
225
+ this.itemCheckIconTargets.forEach((i) => {
226
+ i.classList.add('hidden')
227
+ })
228
+
229
+ if (item) {
230
+ this.triggerTextTarget.innerText = item.innerText
231
+
232
+ const checkIcon = item.querySelector(
233
+ '[data-shadcn-phlexcomponents--combobox-target="itemCheckIcon"]',
234
+ )
235
+ if (value.length > 0) checkIcon.classList.remove('hidden')
236
+
237
+ this.itemTargets.forEach((i) => {
238
+ if (i.dataset.value === value) {
239
+ i.setAttribute('aria-selected', 'true')
240
+ } else {
241
+ i.setAttribute('aria-selected', 'false')
242
+ }
243
+ })
244
+
245
+ this.selectTarget.value = value
246
+ }
247
+
248
+ delete this.triggerTarget.dataset.placeholder
249
+ const hasPlaceholder = this.triggerTarget.dataset.placeholderText
250
+
251
+ if (hasPlaceholder) {
252
+ if (!value || value.length === 0) {
253
+ this.triggerTarget.dataset.placeholder = true
254
+ this.triggerTextTarget.innerText = hasPlaceholder
255
+ }
256
+ }
257
+ }
258
+
259
+ // Global listeners
260
+ onClickOutside(event) {
261
+ const htmlFor = event.target.htmlFor
262
+
263
+ if (htmlFor === this.triggerTarget.id) return
264
+ if (event.target) if (this.element.contains(event.target)) return
265
+
266
+ this.close()
267
+ }
268
+
269
+ onComboboxKeydown(event) {
270
+ if (!this.isOpen()) return
271
+
272
+ const key = event.key
273
+ if (key === 'Tab') event.preventDefault()
274
+
275
+ if (key === 'Escape') {
276
+ this.close()
277
+ }
278
+ }
279
+
280
+ setupEventListeners() {
281
+ document.addEventListener('click', this.clickOutsideListener)
282
+ document.addEventListener('keydown', this.comboboxKeydownListener)
283
+ document.addEventListener('keydown', this.searchInputKeydownListener)
284
+ }
285
+
286
+ cleanupEventListeners() {
287
+ document.removeEventListener('click', this.clickOutsideListener)
288
+ document.removeEventListener('keydown', this.comboboxKeydownListener)
289
+ document.removeEventListener('keydown', this.searchInputKeydownListener)
290
+ }
291
+ }
@@ -0,0 +1,47 @@
1
+ import { Controller } from '@hotwired/stimulus'
2
+ import { Calendar } from 'vanilla-calendar-pro'
3
+ import { getDateString, parseDates } from 'vanilla-calendar-pro/utils'
4
+ import dayjs from 'dayjs'
5
+
6
+ export default class extends Controller {
7
+ connect() {
8
+ this.date = new Date(this.element.dataset.value)
9
+ this.format = this.element.dataset.format
10
+ const settings = this.getSettings()
11
+
12
+ if (!isNaN(this.date.getTime())) {
13
+ const dateString = getDateString(this.date)
14
+ const formattedDate = this.format
15
+ ? dayjs(this.date).format(this.format)
16
+ : this.date.toLocaleDateString()
17
+ this.element.value = formattedDate
18
+ settings.selectedDates = [dateString]
19
+ }
20
+
21
+ const calendar = new Calendar(this.element, {
22
+ inputMode: true,
23
+ enableJumpToSelectedDate: true,
24
+ ...settings,
25
+ onClickDate(self, event) {
26
+ const date = self.context.selectedDates[0]
27
+ const formattedDate = self.context.inputElement.dataset.format
28
+ ? dayjs(date).format(self.context.inputElement.dataset.format)
29
+ : new Date(date).toLocaleDateString()
30
+
31
+ self.context.inputElement.value = formattedDate
32
+ self.hide()
33
+ },
34
+ })
35
+ calendar.init()
36
+
37
+ this.calendar = calendar
38
+ }
39
+
40
+ getSettings() {
41
+ try {
42
+ return JSON.parse(this.element.dataset.settings)
43
+ } catch {
44
+ return {}
45
+ }
46
+ }
47
+ }