neo_components 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 (223) hide show
  1. checksums.yaml +7 -0
  2. data/app/assets/icons/academy.svg +3 -0
  3. data/app/assets/icons/adjustments-horizontal.svg +3 -0
  4. data/app/assets/icons/adjustments-vertical.svg +3 -0
  5. data/app/assets/icons/arrow-down-tray.svg +3 -0
  6. data/app/assets/icons/arrow-left.svg +3 -0
  7. data/app/assets/icons/arrow-long-down.svg +3 -0
  8. data/app/assets/icons/arrow-long-right.svg +3 -0
  9. data/app/assets/icons/arrow-long-up.svg +3 -0
  10. data/app/assets/icons/arrow-right.svg +3 -0
  11. data/app/assets/icons/arrow-up-tray.svg +3 -0
  12. data/app/assets/icons/arrow-uturn-left.svg +3 -0
  13. data/app/assets/icons/arrow-uturn-right.svg +3 -0
  14. data/app/assets/icons/assign-user.svg +13 -0
  15. data/app/assets/icons/at-symbol.svg +3 -0
  16. data/app/assets/icons/bars-4.svg +3 -0
  17. data/app/assets/icons/bell.svg +3 -0
  18. data/app/assets/icons/book.svg +3 -0
  19. data/app/assets/icons/bookmark.svg +3 -0
  20. data/app/assets/icons/building-office-2.svg +3 -0
  21. data/app/assets/icons/calendar.svg +3 -0
  22. data/app/assets/icons/celebration.svg +15 -0
  23. data/app/assets/icons/check-badge-solid.svg +3 -0
  24. data/app/assets/icons/check-circle-solid.svg +3 -0
  25. data/app/assets/icons/check-circle.svg +3 -0
  26. data/app/assets/icons/check-list.svg +12 -0
  27. data/app/assets/icons/check.svg +3 -0
  28. data/app/assets/icons/chevron-double-right.svg +3 -0
  29. data/app/assets/icons/chevron-down.svg +3 -0
  30. data/app/assets/icons/chevron-left.svg +3 -0
  31. data/app/assets/icons/chevron-right.svg +3 -0
  32. data/app/assets/icons/chevron-up.svg +3 -0
  33. data/app/assets/icons/circle-stack.svg +3 -0
  34. data/app/assets/icons/clipboard-document-check-solid.svg +4 -0
  35. data/app/assets/icons/clipboard-document-check.svg +3 -0
  36. data/app/assets/icons/clock.svg +3 -0
  37. data/app/assets/icons/complete.svg +3 -0
  38. data/app/assets/icons/course.svg +3 -0
  39. data/app/assets/icons/device-phone-mobile.svg +3 -0
  40. data/app/assets/icons/document-arrow-up.svg +4 -0
  41. data/app/assets/icons/document-text.svg +4 -0
  42. data/app/assets/icons/ellipsis-vertical.svg +3 -0
  43. data/app/assets/icons/exclaimation-circle-solid.svg +3 -0
  44. data/app/assets/icons/eye.svg +4 -0
  45. data/app/assets/icons/flag.svg +3 -0
  46. data/app/assets/icons/funnel.svg +3 -0
  47. data/app/assets/icons/green-circle.svg +3 -0
  48. data/app/assets/icons/green-tick.svg +3 -0
  49. data/app/assets/icons/grid.svg +6 -0
  50. data/app/assets/icons/home.svg +3 -0
  51. data/app/assets/icons/i-vector.svg +3 -0
  52. data/app/assets/icons/lessons.svg +11 -0
  53. data/app/assets/icons/magnifying-glass.svg +3 -0
  54. data/app/assets/icons/minus.svg +3 -0
  55. data/app/assets/icons/numbered-list.svg +3 -0
  56. data/app/assets/icons/pencil.svg +3 -0
  57. data/app/assets/icons/play.svg +3 -0
  58. data/app/assets/icons/plus.svg +3 -0
  59. data/app/assets/icons/power.svg +3 -0
  60. data/app/assets/icons/question-mark-circle.svg +3 -0
  61. data/app/assets/icons/quiz-score.svg +15 -0
  62. data/app/assets/icons/re-invite.svg +3 -0
  63. data/app/assets/icons/red-circle.svg +3 -0
  64. data/app/assets/icons/share.svg +3 -0
  65. data/app/assets/icons/smiley-five.svg +5 -0
  66. data/app/assets/icons/smiley-four.svg +5 -0
  67. data/app/assets/icons/smiley-one.svg +5 -0
  68. data/app/assets/icons/smiley-three.svg +5 -0
  69. data/app/assets/icons/smiley-two.svg +5 -0
  70. data/app/assets/icons/sparkle.svg +3 -0
  71. data/app/assets/icons/star-transparent.svg +3 -0
  72. data/app/assets/icons/stop.svg +4 -0
  73. data/app/assets/icons/support-placeholder.svg +4 -0
  74. data/app/assets/icons/tag.svg +4 -0
  75. data/app/assets/icons/timer.svg +5 -0
  76. data/app/assets/icons/transcript.svg +31 -0
  77. data/app/assets/icons/trash.svg +3 -0
  78. data/app/assets/icons/upload-info.svg +7 -0
  79. data/app/assets/icons/user-circle.svg +3 -0
  80. data/app/assets/icons/user-group.svg +3 -0
  81. data/app/assets/icons/user.svg +3 -0
  82. data/app/assets/icons/users.svg +3 -0
  83. data/app/assets/icons/winner.svg +35 -0
  84. data/app/assets/icons/x-circle-solid.svg +3 -0
  85. data/app/assets/icons/x-circle.svg +3 -0
  86. data/app/assets/icons/x-mark.svg +3 -0
  87. data/app/assets/stylesheets/breadcrumbs.tailwind.css +45 -0
  88. data/app/assets/stylesheets/buttons.tailwind.css +98 -0
  89. data/app/assets/stylesheets/course_progress.css +75 -0
  90. data/app/assets/stylesheets/custom.css +67 -0
  91. data/app/assets/stylesheets/date_picker.tailwind.css +7 -0
  92. data/app/assets/stylesheets/directives.tailwind.css +3 -0
  93. data/app/assets/stylesheets/dropdown.tailwind.css +11 -0
  94. data/app/assets/stylesheets/file_selector.tailwind.css +47 -0
  95. data/app/assets/stylesheets/icons.css.erb +244 -0
  96. data/app/assets/stylesheets/inputs.tailwind.css +53 -0
  97. data/app/assets/stylesheets/menu_component.tailwind.css +39 -0
  98. data/app/assets/stylesheets/mobile_inputs.tailwind.css +11 -0
  99. data/app/assets/stylesheets/modalbox.tailwind.css +35 -0
  100. data/app/assets/stylesheets/profile_icon.tailwind.css +15 -0
  101. data/app/assets/stylesheets/textarea.tailwind.css +43 -0
  102. data/app/assets/stylesheets/tooltip.css +88 -0
  103. data/app/assets/stylesheets/typography.tailwind.css +70 -0
  104. data/app/helpers/ui_helper.rb +31 -0
  105. data/app/helpers/view_component/accordion_component.rb +12 -0
  106. data/app/helpers/view_component/breadcrumbs_component.rb +30 -0
  107. data/app/helpers/view_component/button_component.rb +180 -0
  108. data/app/helpers/view_component/chip_component.rb +54 -0
  109. data/app/helpers/view_component/component_helper.rb +21 -0
  110. data/app/helpers/view_component/course_card_component.rb +9 -0
  111. data/app/helpers/view_component/course_carousal_component.rb +14 -0
  112. data/app/helpers/view_component/course_select_component.rb +32 -0
  113. data/app/helpers/view_component/doc_section_component.rb +10 -0
  114. data/app/helpers/view_component/icon_component.rb +24 -0
  115. data/app/helpers/view_component/input_component/date_picker_component.rb +127 -0
  116. data/app/helpers/view_component/input_component/dropdown_component.rb +99 -0
  117. data/app/helpers/view_component/input_component/file_selector_component.rb +123 -0
  118. data/app/helpers/view_component/input_component/input_checkbox_component.rb +54 -0
  119. data/app/helpers/view_component/input_component/input_mobile_component.rb +68 -0
  120. data/app/helpers/view_component/input_component/input_radio_component.rb +54 -0
  121. data/app/helpers/view_component/input_component/input_text_component.rb +137 -0
  122. data/app/helpers/view_component/input_component/textarea_component.rb +101 -0
  123. data/app/helpers/view_component/input_component.rb +290 -0
  124. data/app/helpers/view_component/input_textarea_component.rb +28 -0
  125. data/app/helpers/view_component/long_course_card_component.rb +10 -0
  126. data/app/helpers/view_component/member_list_component.rb +17 -0
  127. data/app/helpers/view_component/menu_component.rb +31 -0
  128. data/app/helpers/view_component/menu_component_helper.rb +22 -0
  129. data/app/helpers/view_component/menu_item.rb +12 -0
  130. data/app/helpers/view_component/modal_box_component.rb +29 -0
  131. data/app/helpers/view_component/modal_component.rb +12 -0
  132. data/app/helpers/view_component/notification_bar_component.rb +22 -0
  133. data/app/helpers/view_component/paginator_component.rb +9 -0
  134. data/app/helpers/view_component/profile_icon_component.rb +46 -0
  135. data/app/helpers/view_component/progress_component.rb +12 -0
  136. data/app/helpers/view_component/table_component.rb +22 -0
  137. data/app/helpers/view_component/typography_component.rb +83 -0
  138. data/app/javascript/neo_components/controllers/collapsible_controller.js +37 -0
  139. data/app/javascript/neo_components/controllers/date_picker_controller.js +17 -0
  140. data/app/javascript/neo_components/controllers/file_selector_controller.js +145 -0
  141. data/app/javascript/neo_components/controllers/input_mobile_controller.js +7 -0
  142. data/app/javascript/neo_components/controllers/menu_component_controller.js +26 -0
  143. data/app/javascript/neo_components/controllers/modal_loader_controller.js +13 -0
  144. data/app/javascript/neo_components/controllers/modals_controller.js +26 -0
  145. data/app/javascript/neo_components/controllers/notification_bar_controller.js +9 -0
  146. data/app/javascript/neo_components/controllers/pagination_controller.js +11 -0
  147. data/app/javascript/neo_components/controllers/tab_change_controller.js +23 -0
  148. data/app/javascript/neo_components/controllers/tabs_controller.js +29 -0
  149. data/app/javascript/neo_components/controllers/text_clamp_controller.js +29 -0
  150. data/app/views/shared/components/_progress_bar_short.html.erb +8 -0
  151. data/app/views/shared/components/_tooltip.html.erb +7 -0
  152. data/app/views/view_components/accordion_component/_accordion.html.erb +22 -0
  153. data/app/views/view_components/breadcrumbs_component/_breadcrumbs.html.erb +38 -0
  154. data/app/views/view_components/button_component/_button.html.erb +13 -0
  155. data/app/views/view_components/buttons/_danger.html.erb +30 -0
  156. data/app/views/view_components/buttons/_primary.html.erb +31 -0
  157. data/app/views/view_components/buttons/_secondary.html.erb +29 -0
  158. data/app/views/view_components/chip_component/_chip_component.html.erb +9 -0
  159. data/app/views/view_components/course_carousal/_course_card_component.html.erb +65 -0
  160. data/app/views/view_components/course_carousal/_course_carousal_body_component.html.erb +25 -0
  161. data/app/views/view_components/course_carousal/_course_carousal_component.html.erb +8 -0
  162. data/app/views/view_components/course_carousal/_long_course_card_component.html.erb +70 -0
  163. data/app/views/view_components/course_select/_course_select_component.html.erb +19 -0
  164. data/app/views/view_components/course_select/_list_component.html.erb +14 -0
  165. data/app/views/view_components/course_select/_list_item_component.html.erb +86 -0
  166. data/app/views/view_components/course_select/_load_more.html.erb +17 -0
  167. data/app/views/view_components/course_select/_search_component.html.erb +48 -0
  168. data/app/views/view_components/course_select/_sidebar_component.html.erb +41 -0
  169. data/app/views/view_components/doc_section/_doc_section_component.html.erb +6 -0
  170. data/app/views/view_components/inputs/_checkbox_field.html.erb +19 -0
  171. data/app/views/view_components/inputs/_date_select_component.html.erb +37 -0
  172. data/app/views/view_components/inputs/_dropdown_field.html.erb +22 -0
  173. data/app/views/view_components/inputs/_file_selector.html.erb +16 -0
  174. data/app/views/view_components/inputs/_input_checkbox_component.html.erb +7 -0
  175. data/app/views/view_components/inputs/_input_mobile_component.html.erb +16 -0
  176. data/app/views/view_components/inputs/_input_radio_component.html.erb +7 -0
  177. data/app/views/view_components/inputs/_input_text_component.html.erb +16 -0
  178. data/app/views/view_components/inputs/_mobile_field.html.erb +31 -0
  179. data/app/views/view_components/inputs/_radio_field.html.erb +25 -0
  180. data/app/views/view_components/inputs/_text_field.html.erb +52 -0
  181. data/app/views/view_components/inputs/_textarea.html.erb +26 -0
  182. data/app/views/view_components/inputs/date_picker_component/_date_picker.html.erb +16 -0
  183. data/app/views/view_components/inputs/date_picker_component/_input_box.html.erb +34 -0
  184. data/app/views/view_components/inputs/dropdown_component/_dropdown.html.erb +16 -0
  185. data/app/views/view_components/inputs/dropdown_component/_select_box.html.erb +10 -0
  186. data/app/views/view_components/inputs/file_selector_component/_file_selector_box.html.erb +76 -0
  187. data/app/views/view_components/inputs/input_checkbox/_checkbox.html.erb +20 -0
  188. data/app/views/view_components/inputs/input_mobile/_code.html.erb +13 -0
  189. data/app/views/view_components/inputs/input_mobile/_mobile_box.html.erb +4 -0
  190. data/app/views/view_components/inputs/input_mobile/_number.html.erb +13 -0
  191. data/app/views/view_components/inputs/input_radio/_radio_button.html.erb +14 -0
  192. data/app/views/view_components/inputs/input_text/_text_box.html.erb +33 -0
  193. data/app/views/view_components/inputs/textarea_component/_text_area_box.html.erb +16 -0
  194. data/app/views/view_components/inputs/textarea_component/_textarea.html.erb +16 -0
  195. data/app/views/view_components/member_list/_member_list.html.erb +4 -0
  196. data/app/views/view_components/member_list/_member_search.html.erb +15 -0
  197. data/app/views/view_components/member_list/_members.html.erb +65 -0
  198. data/app/views/view_components/menu_component/_menu_component.html.erb +16 -0
  199. data/app/views/view_components/menu_component/_menu_item_button.html.erb +17 -0
  200. data/app/views/view_components/menu_component/_menu_item_link.html.erb +5 -0
  201. data/app/views/view_components/menu_component_old/_menu_component.html.erb +17 -0
  202. data/app/views/view_components/menu_component_old/_menu_item_button.html.erb +7 -0
  203. data/app/views/view_components/menu_component_old/_menu_item_link.html.erb +5 -0
  204. data/app/views/view_components/modal_component/_modal_box_component.html.erb +28 -0
  205. data/app/views/view_components/modals/_modal_component.html.erb +25 -0
  206. data/app/views/view_components/notification_bar/_notification_bar.html.erb +17 -0
  207. data/app/views/view_components/paginator/_next_page.html.erb +7 -0
  208. data/app/views/view_components/paginator/_paginator_component.html.erb +6 -0
  209. data/app/views/view_components/paginator/_prev_page.html.erb +7 -0
  210. data/app/views/view_components/profile_icon_component/_profile_icon.html.erb +3 -0
  211. data/app/views/view_components/progress_component/_progressbar.html.erb +5 -0
  212. data/app/views/view_components/typography/_h1_component.html.erb +1 -0
  213. data/app/views/view_components/typography/_h2_component.html.erb +1 -0
  214. data/app/views/view_components/typography/_h3_component.html.erb +1 -0
  215. data/app/views/view_components/typography/_heading_component.html.erb +3 -0
  216. data/app/views/view_components/typography/_link_component.html.erb +1 -0
  217. data/app/views/view_components/typography/_linked_text_component.html.erb +3 -0
  218. data/app/views/view_components/typography/_text_component.html.erb +1 -0
  219. data/config/importmap.rb +5 -0
  220. data/lib/neo_components/engine.rb +38 -0
  221. data/lib/neo_components/version.rb +5 -0
  222. data/lib/neo_components.rb +4 -0
  223. metadata +327 -0
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module InputTextareaComponent
5
+ def input_textarea_component(
6
+ form:,
7
+ field_name:,
8
+ label:,
9
+ placeholder:,
10
+ value: nil,
11
+ rows: 5,
12
+ html_options: {}
13
+ )
14
+ html_options = html_options.merge(
15
+ placeholder: placeholder,
16
+ rows: rows
17
+ )
18
+
19
+ render partial: 'view_components/inputs/textarea', locals: {
20
+ form:,
21
+ field_name:,
22
+ label:,
23
+ value:,
24
+ html_options:
25
+ }
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module LongCourseCardComponent
5
+ def long_course_card_component(course:, enrollment: nil, program: nil)
6
+ render partial: 'view_components/course_carousal/long_course_card_component',
7
+ locals: { course:, enrollment:, program: }
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module MemberListComponent
5
+ def member_list_component(team:, members:, all_members: false, term: '')
6
+ render partial: 'view_components/member_list/member_list', locals: { team:, members:, all_members:, term: }
7
+ end
8
+
9
+ def _member_list_member_search_component(team:, all_members:)
10
+ render partial: 'view_components/member_list/member_search', locals: { team:, all_members: }
11
+ end
12
+
13
+ def member_list_members_component(team:, members:, all_members: false, term: '')
14
+ render partial: 'view_components/member_list/members', locals: { team:, members:, all_members:, term: }
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module MenuComponent
5
+ MenuItem = ViewComponent::MenuItem
6
+
7
+ MENU_POSITIONS = {
8
+ left: 'menu-component-left',
9
+ right: 'menu-component-right',
10
+ center: 'menu-component-center'
11
+ }.freeze
12
+
13
+ def menu_component(menu_items:, position: 'right', html_options: {})
14
+ position_key = position.to_s.downcase.to_sym
15
+ position_class = MENU_POSITIONS[position_key] || MENU_POSITIONS[:right]
16
+
17
+ menu_items_with_options = menu_items.map do |item|
18
+ item.options ||= {}
19
+ item.url = item.url.presence || 'javascript:void(0)'
20
+ item
21
+ end
22
+
23
+ render(
24
+ 'view_components/menu_component/menu_component',
25
+ menu_items: menu_items_with_options,
26
+ position_class: position_class,
27
+ html_options:
28
+ )
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module MenuComponentHelper
5
+ MenuItem = ViewComponent::MenuItem
6
+
7
+ def menu_component_old(menu_items:, icon_class: '', html_options: {})
8
+ menu_items_with_options = menu_items.map do |item|
9
+ item.options ||= {}
10
+ item.url = item.url.presence || '#'
11
+ item
12
+ end
13
+
14
+ render(
15
+ 'view_components/menu_component_old/menu_component',
16
+ menu_items: menu_items_with_options,
17
+ icon_class:,
18
+ html_options:
19
+ )
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ MenuItem = Struct.new(
5
+ :label,
6
+ :url,
7
+ :type,
8
+ :options,
9
+ :extra_classes,
10
+ keyword_init: true
11
+ )
12
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module ModalBoxComponent
5
+ class ModalBoxComponent
6
+ attr_accessor :title, :variant
7
+
8
+ def initialize(title: nil, variant: nil)
9
+ self.title = title
10
+ self.variant = variant
11
+ end
12
+ end
13
+
14
+ def modal_box_component(title: nil, variant: nil, modal_footer: nil, html_options: {}, &)
15
+ modal = ModalBoxComponent.new(title:, variant:)
16
+ block_content = capture(&) if block_given?
17
+
18
+ container_class =
19
+ if variant == :success
20
+ 'modal-success'
21
+ else
22
+ 'modal-container'
23
+ end
24
+
25
+ render partial: 'view_components/modal_component/modal_box_component',
26
+ locals: { modal:, body: block_content, modal_footer:, container_class:, html_options: }
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module ModalComponent
5
+ # modal box component
6
+ # @param title - modal title
7
+ def modal_component(title:, &)
8
+ block_content = capture(&) if block_given?
9
+ render partial: 'view_components/modals/modal_component', locals: { title:, body: block_content }
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module NotificationBarComponent
5
+ def notification_bar(
6
+ text: nil,
7
+ text_color: 'text-letter-color',
8
+ bg_color: 'bg-white',
9
+ icon_color: 'bg-letter-color'
10
+ )
11
+ render(
12
+ partial: 'view_components/notification_bar/notification_bar',
13
+ locals: {
14
+ text:,
15
+ text_color:,
16
+ icon_color:,
17
+ bg_color:
18
+ }
19
+ )
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module PaginatorComponent
5
+ def paginator_component(collection:, path:)
6
+ render partial: 'view_components/paginator/paginator_component', locals: { collection:, path: }
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module ProfileIconComponent
5
+ class ProfileIconComponent
6
+ include ViewComponent::ComponentHelper
7
+
8
+ PROFILE_ICON_SIZES = %w[sm md lg].freeze
9
+
10
+ PROFILE_ICON_SIZE_STYLES = {
11
+ sm: 'profile-icon-sm',
12
+ md: 'profile-icon-md',
13
+ lg: 'profile-icon-lg'
14
+ }.freeze
15
+
16
+ attr_accessor :letter, :size
17
+
18
+ def initialize(letter:, size: 'md')
19
+ raise "Incorrect profile icon size: #{size}" unless PROFILE_ICON_SIZES.include?(size)
20
+
21
+ self.letter = (letter.presence || 'U').to_s[0].upcase
22
+ self.size = size
23
+ end
24
+
25
+ def profile_icon_style
26
+ base = ['profile-icon-base']
27
+ size_style = PROFILE_ICON_SIZE_STYLES[size.to_sym]
28
+
29
+ class_list(base, size_style)
30
+ end
31
+ end
32
+
33
+ def profile_icon_component(letter:, size: 'md')
34
+ normalized_letter = (letter.presence || 'U').to_s[0]
35
+
36
+ profile_icon =
37
+ ProfileIconComponent.new(
38
+ letter: normalized_letter,
39
+ size:
40
+ )
41
+
42
+ render partial: 'view_components/profile_icon_component/profile_icon',
43
+ locals: { profile_icon: }
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module ProgressComponent
5
+ def progressbar_component(numerator:, denominator:)
6
+ numerator = 0 if numerator.blank?
7
+ denominator = 0 if denominator.blank?
8
+ fill = denominator.zero? ? 0 : (numerator / denominator.to_f * 100).to_i
9
+ render partial: 'view_components/progress_component/progressbar', locals: { numerator:, denominator:, fill: }
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module TableComponent
5
+ def table_cell_name(row_data, avatar: false)
6
+ { partial: 'shared/components/table_cell_name', locals: { row_data:, avatar: } }
7
+ end
8
+
9
+ def table_cell_role(row_data)
10
+ { partial: 'shared/components/table_cell_role', locals: { row_data: } }
11
+ end
12
+
13
+ def table_actions(row_data, actions = [])
14
+ actions.each do |action|
15
+ action[:data] ||= {}
16
+ action[:data][:turbo_frame] = action[:turbo_frame] if action[:turbo_frame].present?
17
+ end
18
+
19
+ { partial: 'shared/components/table_actions', locals: { row_data:, actions: } }
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module TypographyComponent
5
+ HEADING_SIZE = {
6
+ 'xs' => 'text-base font-semibold',
7
+ 'sm' => 'text-lg font-semibold',
8
+ 'md' => 'text-xl font-semibold',
9
+ 'lg' => 'text-2xl font-semibold',
10
+ 'xl' => 'text-3xl font-semibold'
11
+ }.freeze
12
+
13
+ def merge_class(options, class_names)
14
+ options.merge(class: [*class_names, options[:class]].compact.join(' '))
15
+ end
16
+
17
+ def link_text_component(text, link, html_options = {})
18
+ render partial: 'view_components/typography/linked_text_component', locals: {
19
+ text:,
20
+ link:,
21
+ html_options:
22
+ }
23
+ end
24
+
25
+ # @param size - xs, sm, md, lg, xl
26
+ def heading_component(text:, size: 'md')
27
+ raise 'BlankValue - heading cannot be blank' if text.blank?
28
+ raise 'IncorrectValue - incorrect size value' unless %w[xs sm md lg xl].include?(size)
29
+
30
+ render partial: 'view_components/typography/heading_component', locals: {
31
+ text:,
32
+ css: HEADING_SIZE[size]
33
+ }
34
+ end
35
+
36
+ def h1_component(text: '', html_options: {})
37
+ render partial: 'view_components/typography/h1_component', locals: {
38
+ text:,
39
+ html_options: merge_class(html_options, ['heading-3xl'])
40
+ }
41
+ end
42
+
43
+ def h2_component(text: '', html_options: {})
44
+ render partial: 'view_components/typography/h2_component', locals: {
45
+ text:,
46
+ html_options: merge_class(html_options, ['heading-2xl'])
47
+ }
48
+ end
49
+
50
+ def h3_component(text: '', html_options: {})
51
+ render partial: 'view_components/typography/h3_component', locals: {
52
+ text:,
53
+ html_options: merge_class(html_options, ['heading-xl'])
54
+ }
55
+ end
56
+
57
+ # @param text - text to be displayed
58
+ # @param tag - span, p, div
59
+ # @param html_options - additional html options including css classes
60
+ def text_component(text: '', tag: 'span', html_options: {})
61
+ render partial: 'view_components/typography/text_component', locals: {
62
+ text:,
63
+ tag:,
64
+ html_options: merge_class(html_options, ['general-text-md-normal'])
65
+ }
66
+ end
67
+
68
+ # @param text - text to be displayed
69
+ # @param url - url to be linked
70
+ # @param target - _blank, _self, _parent, _top, framename
71
+ # @param html_options - additional html options
72
+ def link_component(text:, url: '#', target: '_self', html_options: {})
73
+ raise 'BlankValue - text cannot be blank' if text.blank?
74
+
75
+ link_css = ['main-text-sm-normal', 'underline', 'text-primary', 'hover:text-primary-light']
76
+ render partial: 'view_components/typography/link_component', locals: {
77
+ text:,
78
+ url:,
79
+ html_options: merge_class(html_options, link_css).merge(target: target)
80
+ }
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,37 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["content", "icon"]
5
+ static values = { open: Boolean }
6
+
7
+ connect() {
8
+ if (this.openValue) {
9
+ this.open()
10
+ } else {
11
+ this.close()
12
+ }
13
+ }
14
+
15
+ toggle(event) {
16
+ if (event.target.closest("a, button")) return
17
+ this.openValue = !this.openValue
18
+ }
19
+
20
+ openValueChanged() {
21
+ if (this.openValue) {
22
+ this.open()
23
+ } else {
24
+ this.close()
25
+ }
26
+ }
27
+
28
+ open() {
29
+ this.contentTarget.classList.remove("hidden")
30
+ this.iconTarget.classList.add("rotate-180")
31
+ }
32
+
33
+ close() {
34
+ this.contentTarget.classList.add("hidden")
35
+ this.iconTarget.classList.remove("rotate-180")
36
+ }
37
+ }
@@ -0,0 +1,17 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ export default class extends Controller {
4
+ static targets = ["datePicker", "dateSelector"];
5
+
6
+ datePicked(event) {
7
+ const selectedDate = event.target.value;
8
+
9
+ if (selectedDate) {
10
+ this.dateSelectorTarget.value = selectedDate;
11
+ }
12
+ }
13
+
14
+ openDatePicker() {
15
+ this.datePickerTarget.showPicker();
16
+ }
17
+ }
@@ -0,0 +1,145 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ export default class extends Controller {
4
+ static targets = [
5
+ "wrapper",
6
+ "chooseFile",
7
+ "fileInput",
8
+ "selectedFileName",
9
+ "preview",
10
+ "previewContainer",
11
+ "iconPreview",
12
+ "fileName",
13
+ "fileSize"
14
+ ];
15
+
16
+ connect() {
17
+ this.fileInputTarget.addEventListener("change", (e) => {
18
+ const file = e.target.files[0];
19
+ this.updateFileName(file);
20
+ this.showPreview(file);
21
+ if (file) this.activate();
22
+ });
23
+ }
24
+
25
+ activate() {
26
+ if (!this.isErrorState()) {
27
+ this.wrapperTarget.classList.add("file-selector-is-active");
28
+ }
29
+ }
30
+
31
+ deactivate() {
32
+ this.wrapperTarget.classList.remove("file-selector-is-active");
33
+ }
34
+
35
+ isErrorState() {
36
+ return this.wrapperTarget.classList.contains("border-danger");
37
+ }
38
+
39
+ updateFileName(file) {
40
+ this.clearError();
41
+ this.chooseFileTarget.classList.add("hidden");
42
+ this.selectedFileNameTarget.classList.remove("hidden");
43
+
44
+ if (file) {
45
+ const truncated = this.truncateFileName(file.name, 20);
46
+ this.fileNameTarget.textContent = truncated;
47
+ this.fileSizeTarget.textContent = this.formatFileSize(file.size);
48
+ } else {
49
+ this.fileNameTarget.textContent = "No file chosen";
50
+ this.fileSizeTarget.textContent = "";
51
+ }
52
+ }
53
+
54
+ truncateFileName(fileName, maxLength = 10) {
55
+ const dotIndex = fileName.lastIndexOf(".");
56
+
57
+ if (dotIndex === -1) {
58
+ return fileName.length > maxLength
59
+ ? fileName.slice(0, maxLength) + "..."
60
+ : fileName;
61
+ }
62
+
63
+ const namePart = fileName.slice(0, dotIndex);
64
+ const extension = fileName.slice(dotIndex);
65
+
66
+ if (namePart.length <= maxLength) return fileName;
67
+
68
+ return namePart.slice(0, maxLength) + "..." + extension;
69
+ }
70
+
71
+ formatFileSize(bytes) {
72
+ if (bytes < 1024) return `${bytes} B`;
73
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
74
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
75
+ }
76
+
77
+ showPreview(file) {
78
+ if (!file) return;
79
+
80
+ const fileType = file.type || "";
81
+ const fileName = file.name.toLowerCase();
82
+ this.previewContainerTarget.classList.remove("hidden");
83
+ this.activate();
84
+
85
+ if (this.previewTarget.src.startsWith("blob:")) {
86
+ URL.revokeObjectURL(this.previewTarget.src);
87
+ }
88
+
89
+ if (fileType.startsWith("image/")) {
90
+ const reader = new FileReader();
91
+ reader.onload = () => {
92
+ this.previewTarget.src = reader.result;
93
+ };
94
+ reader.onerror = () => this.showError("Failed to load file preview");
95
+ reader.readAsDataURL(file);
96
+
97
+ } else if (fileType.startsWith("video/") || /\.(mp4|mov|avi|mkv|webm)$/i.test(fileName)) {
98
+ const blobURL = URL.createObjectURL(file);
99
+ this.previewTarget.src = blobURL;
100
+ this.previewTarget.load();
101
+ this.previewTarget.controls = false;
102
+ this.previewTarget.muted = true;
103
+ this.previewTarget.autoplay = false;
104
+
105
+ } else {
106
+ this.previewTarget.src = "";
107
+ }
108
+ }
109
+
110
+ showError(message) {
111
+ this.resetInput();
112
+ this.fileNameTarget.innerText = message;
113
+ this.fileNameTarget.style.color = "red";
114
+ }
115
+
116
+ clearError() {
117
+ this.fileNameTarget.textContent = "No file chosen";
118
+ this.fileNameTarget.style.color = "";
119
+ }
120
+
121
+ resetInput() {
122
+ if (this.hasPreviewTarget && this.previewTarget.src.startsWith("blob:")) {
123
+ URL.revokeObjectURL(this.previewTarget.src);
124
+ }
125
+
126
+ this.fileInputTarget.value = "";
127
+ this.fileNameTarget.textContent = "";
128
+ this.fileSizeTarget.textContent = "";
129
+ this.selectedFileNameTarget.classList.add("hidden");
130
+ this.previewContainerTarget.classList.add("hidden");
131
+ if (this.hasPreviewTarget && this.previewTarget.src) {
132
+ this.previewTarget.src = "";
133
+ }
134
+
135
+ this.chooseFileTarget.classList.remove("hidden");
136
+ this.wrapperTarget.classList.remove("file-selector-is-active");
137
+ }
138
+
139
+ chooseFile() {
140
+ if (!this.chooseFileTarget.classList.contains("hidden")) {
141
+ this.activate();
142
+ this.fileInputTarget.click();
143
+ }
144
+ }
145
+ }
@@ -0,0 +1,7 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ export default class extends Controller {
4
+ validateInput(event) {
5
+ event.target.value = event.target.value.slice(0, 10);
6
+ }
7
+ }
@@ -0,0 +1,26 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ export default class extends Controller {
4
+ static targets = ["moreMenu","menuButton"];
5
+
6
+ connect() {
7
+ window.addEventListener("click",(e) => {
8
+ if (!this.moreMenuTarget.contains(e.target)) {
9
+ if (!this.isMenuHidden()) {
10
+ this.toggleDropdown(e);
11
+ }
12
+ }
13
+ })
14
+ }
15
+
16
+ toggleDropdown(event) {
17
+ event.stopPropagation();
18
+ this.moreMenuTarget.classList.toggle("hidden");
19
+ this.menuButtonTarget.classList.toggle("menu-component-button-active")
20
+
21
+ }
22
+
23
+ isMenuHidden() {
24
+ return this.moreMenuTarget.classList.contains("hidden");
25
+ }
26
+ }
@@ -0,0 +1,13 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["loader"]
5
+
6
+ connect() {
7
+ this.element.addEventListener("submit", this.showLoader.bind(this))
8
+ }
9
+
10
+ showLoader(event) {
11
+ this.loaderTarget.classList.remove("hidden")
12
+ }
13
+ }
@@ -0,0 +1,26 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ export default class extends Controller {
4
+ static targets = ["modalBox"];
5
+
6
+ closeModal(event) {
7
+ event.preventDefault();
8
+ this.modalBoxTarget.classList.add("hidden");
9
+ this.modalBoxTarget.parentElement.removeAttribute("src");
10
+ this.modalBoxTarget.parentElement.removeAttribute("complete");
11
+ this.modalBoxTarget.parentElement.innerText = "";
12
+ }
13
+
14
+ afterSubmit(event) {
15
+ if (event.detail.success) {
16
+ const fetchResponse = event.detail.fetchResponse;
17
+ history.pushState(
18
+ { turbo_frame_history: true },
19
+ "",
20
+ fetchResponse.response.url
21
+ );
22
+
23
+ Turbo.visit(fetchResponse.response.url);
24
+ }
25
+ }
26
+ }
@@ -0,0 +1,9 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["banner"]
5
+
6
+ close() {
7
+ this.bannerTarget.classList.add("hidden")
8
+ }
9
+ }
@@ -0,0 +1,11 @@
1
+ import {Controller} from "@hotwired/stimulus";
2
+
3
+ export default class extends Controller {
4
+ static targets = ["button"];
5
+
6
+ setPageParam() {
7
+ const nextUrl = this.buttonTarget.getAttribute("href");
8
+ if (!nextUrl) return;
9
+ window.history.replaceState({}, "", nextUrl);
10
+ }
11
+ }