pulse_view_components 0.3.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 (221) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +88 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.md +78 -0
  5. data/app/assets/builds/js/pulse_view_components.js +9543 -0
  6. data/app/assets/builds/js/pulse_view_components.js.map +7 -0
  7. data/app/assets/builds/pulse_view_components.css +1 -0
  8. data/app/assets/config/manifest.js +2 -0
  9. data/app/assets/images/pulse_view_components/icons/alert.svg +3 -0
  10. data/app/assets/images/pulse_view_components/icons/archive.svg +1 -0
  11. data/app/assets/images/pulse_view_components/icons/arrow-both.svg +1 -0
  12. data/app/assets/images/pulse_view_components/icons/arrow-down.svg +1 -0
  13. data/app/assets/images/pulse_view_components/icons/arrow-left.svg +1 -0
  14. data/app/assets/images/pulse_view_components/icons/arrow-right.svg +1 -0
  15. data/app/assets/images/pulse_view_components/icons/arrow-switch.svg +1 -0
  16. data/app/assets/images/pulse_view_components/icons/arrow-up.svg +1 -0
  17. data/app/assets/images/pulse_view_components/icons/beaker.svg +1 -0
  18. data/app/assets/images/pulse_view_components/icons/bell-slash.svg +1 -0
  19. data/app/assets/images/pulse_view_components/icons/bell.svg +1 -0
  20. data/app/assets/images/pulse_view_components/icons/bookmark-slash.svg +1 -0
  21. data/app/assets/images/pulse_view_components/icons/bookmark.svg +1 -0
  22. data/app/assets/images/pulse_view_components/icons/calendar.svg +1 -0
  23. data/app/assets/images/pulse_view_components/icons/check-circle.svg +1 -0
  24. data/app/assets/images/pulse_view_components/icons/check.svg +1 -0
  25. data/app/assets/images/pulse_view_components/icons/chevron-down.svg +1 -0
  26. data/app/assets/images/pulse_view_components/icons/circle.svg +1 -0
  27. data/app/assets/images/pulse_view_components/icons/clock.svg +1 -0
  28. data/app/assets/images/pulse_view_components/icons/close.svg +9 -0
  29. data/app/assets/images/pulse_view_components/icons/cloud.svg +1 -0
  30. data/app/assets/images/pulse_view_components/icons/comment-discussion.svg +1 -0
  31. data/app/assets/images/pulse_view_components/icons/comment.svg +1 -0
  32. data/app/assets/images/pulse_view_components/icons/copy.svg +1 -0
  33. data/app/assets/images/pulse_view_components/icons/database.svg +1 -0
  34. data/app/assets/images/pulse_view_components/icons/dot.svg +1 -0
  35. data/app/assets/images/pulse_view_components/icons/dots-outline.svg +1 -0
  36. data/app/assets/images/pulse_view_components/icons/download.svg +1 -0
  37. data/app/assets/images/pulse_view_components/icons/duplicate.svg +1 -0
  38. data/app/assets/images/pulse_view_components/icons/eye.svg +1 -0
  39. data/app/assets/images/pulse_view_components/icons/file-added.svg +1 -0
  40. data/app/assets/images/pulse_view_components/icons/file-directory.svg +1 -0
  41. data/app/assets/images/pulse_view_components/icons/file-media.svg +1 -0
  42. data/app/assets/images/pulse_view_components/icons/file.svg +1 -0
  43. data/app/assets/images/pulse_view_components/icons/filter.svg +1 -0
  44. data/app/assets/images/pulse_view_components/icons/flame.svg +1 -0
  45. data/app/assets/images/pulse_view_components/icons/gear.svg +1 -0
  46. data/app/assets/images/pulse_view_components/icons/gift.svg +1 -0
  47. data/app/assets/images/pulse_view_components/icons/globe.svg +1 -0
  48. data/app/assets/images/pulse_view_components/icons/goal.svg +1 -0
  49. data/app/assets/images/pulse_view_components/icons/graph.svg +1 -0
  50. data/app/assets/images/pulse_view_components/icons/heart-fill.svg +1 -0
  51. data/app/assets/images/pulse_view_components/icons/heart.svg +1 -0
  52. data/app/assets/images/pulse_view_components/icons/history.svg +1 -0
  53. data/app/assets/images/pulse_view_components/icons/home.svg +1 -0
  54. data/app/assets/images/pulse_view_components/icons/hourglass.svg +1 -0
  55. data/app/assets/images/pulse_view_components/icons/image.svg +1 -0
  56. data/app/assets/images/pulse_view_components/icons/inbox.svg +1 -0
  57. data/app/assets/images/pulse_view_components/icons/info.svg +1 -0
  58. data/app/assets/images/pulse_view_components/icons/issue.svg +3 -0
  59. data/app/assets/images/pulse_view_components/icons/key.svg +1 -0
  60. data/app/assets/images/pulse_view_components/icons/law.svg +1 -0
  61. data/app/assets/images/pulse_view_components/icons/link-external.svg +1 -0
  62. data/app/assets/images/pulse_view_components/icons/link.svg +1 -0
  63. data/app/assets/images/pulse_view_components/icons/location.svg +1 -0
  64. data/app/assets/images/pulse_view_components/icons/log.svg +1 -0
  65. data/app/assets/images/pulse_view_components/icons/mail.svg +1 -0
  66. data/app/assets/images/pulse_view_components/icons/mark-github.svg +1 -0
  67. data/app/assets/images/pulse_view_components/icons/mark-jira.svg +3 -0
  68. data/app/assets/images/pulse_view_components/icons/megaphone.svg +1 -0
  69. data/app/assets/images/pulse_view_components/icons/no-entry.svg +1 -0
  70. data/app/assets/images/pulse_view_components/icons/note.svg +1 -0
  71. data/app/assets/images/pulse_view_components/icons/organization.svg +1 -0
  72. data/app/assets/images/pulse_view_components/icons/package.svg +1 -0
  73. data/app/assets/images/pulse_view_components/icons/paintbrush.svg +1 -0
  74. data/app/assets/images/pulse_view_components/icons/paperclip.svg +1 -0
  75. data/app/assets/images/pulse_view_components/icons/paste.svg +1 -0
  76. data/app/assets/images/pulse_view_components/icons/pencil.svg +1 -0
  77. data/app/assets/images/pulse_view_components/icons/people.svg +1 -0
  78. data/app/assets/images/pulse_view_components/icons/person-add.svg +1 -0
  79. data/app/assets/images/pulse_view_components/icons/person.svg +1 -0
  80. data/app/assets/images/pulse_view_components/icons/pin-slash.svg +1 -0
  81. data/app/assets/images/pulse_view_components/icons/pin.svg +1 -0
  82. data/app/assets/images/pulse_view_components/icons/play.svg +1 -0
  83. data/app/assets/images/pulse_view_components/icons/plus-circle.svg +1 -0
  84. data/app/assets/images/pulse_view_components/icons/plus.svg +1 -0
  85. data/app/assets/images/pulse_view_components/icons/project.svg +1 -0
  86. data/app/assets/images/pulse_view_components/icons/pulse.svg +1 -0
  87. data/app/assets/images/pulse_view_components/icons/question.svg +1 -0
  88. data/app/assets/images/pulse_view_components/icons/redo.svg +1 -0
  89. data/app/assets/images/pulse_view_components/icons/reply.svg +1 -0
  90. data/app/assets/images/pulse_view_components/icons/report.svg +1 -0
  91. data/app/assets/images/pulse_view_components/icons/rss.svg +1 -0
  92. data/app/assets/images/pulse_view_components/icons/ruby.svg +1 -0
  93. data/app/assets/images/pulse_view_components/icons/search.svg +1 -0
  94. data/app/assets/images/pulse_view_components/icons/share.svg +1 -0
  95. data/app/assets/images/pulse_view_components/icons/shield.svg +1 -0
  96. data/app/assets/images/pulse_view_components/icons/sliders.svg +1 -0
  97. data/app/assets/images/pulse_view_components/icons/smiley.svg +1 -0
  98. data/app/assets/images/pulse_view_components/icons/sort.svg +1 -0
  99. data/app/assets/images/pulse_view_components/icons/sort_asc.svg +1 -0
  100. data/app/assets/images/pulse_view_components/icons/sort_desc.svg +1 -0
  101. data/app/assets/images/pulse_view_components/icons/stack.svg +1 -0
  102. data/app/assets/images/pulse_view_components/icons/star-fill.svg +1 -0
  103. data/app/assets/images/pulse_view_components/icons/star.svg +1 -0
  104. data/app/assets/images/pulse_view_components/icons/stopwatch.svg +1 -0
  105. data/app/assets/images/pulse_view_components/icons/sun.svg +1 -0
  106. data/app/assets/images/pulse_view_components/icons/sync.svg +1 -0
  107. data/app/assets/images/pulse_view_components/icons/table.svg +1 -0
  108. data/app/assets/images/pulse_view_components/icons/tag.svg +1 -0
  109. data/app/assets/images/pulse_view_components/icons/tasklist.svg +1 -0
  110. data/app/assets/images/pulse_view_components/icons/telescope.svg +1 -0
  111. data/app/assets/images/pulse_view_components/icons/three-bars.svg +1 -0
  112. data/app/assets/images/pulse_view_components/icons/thumbsdown.svg +1 -0
  113. data/app/assets/images/pulse_view_components/icons/thumbsup.svg +1 -0
  114. data/app/assets/images/pulse_view_components/icons/tools.svg +1 -0
  115. data/app/assets/images/pulse_view_components/icons/trash.svg +1 -0
  116. data/app/assets/images/pulse_view_components/icons/trophy.svg +1 -0
  117. data/app/assets/images/pulse_view_components/icons/undo.svg +1 -0
  118. data/app/assets/images/pulse_view_components/icons/unlink.svg +1 -0
  119. data/app/assets/images/pulse_view_components/icons/upload.svg +1 -0
  120. data/app/assets/images/pulse_view_components/icons/x-circle.svg +1 -0
  121. data/app/assets/images/pulse_view_components/icons/x.svg +1 -0
  122. data/app/assets/images/pulse_view_components/icons/zap.svg +1 -0
  123. data/app/assets/javascripts/pulse_view_components.js +0 -0
  124. data/app/assets/stylesheets/pulse_view_components/application.tailwind.css +10 -0
  125. data/app/components/pulse/action_list/divider.rb +35 -0
  126. data/app/components/pulse/action_list/form_wrapper.html.slim +9 -0
  127. data/app/components/pulse/action_list/form_wrapper.rb +63 -0
  128. data/app/components/pulse/action_list/heading.rb +52 -0
  129. data/app/components/pulse/action_list/item.html.slim +43 -0
  130. data/app/components/pulse/action_list/item.rb +319 -0
  131. data/app/components/pulse/action_list.css +568 -0
  132. data/app/components/pulse/action_list.rb +261 -0
  133. data/app/components/pulse/badge.rb +59 -0
  134. data/app/components/pulse/base_component.rb +36 -0
  135. data/app/components/pulse/blank_slate.html.slim +10 -0
  136. data/app/components/pulse/blank_slate.rb +19 -0
  137. data/app/components/pulse/blank_slate.yml +2 -0
  138. data/app/components/pulse/border_box/header.html.slim +4 -0
  139. data/app/components/pulse/border_box/header.rb +48 -0
  140. data/app/components/pulse/border_box.html.slim +10 -0
  141. data/app/components/pulse/border_box.rb +124 -0
  142. data/app/components/pulse/box.rb +17 -0
  143. data/app/components/pulse/breadcrumb_item.rb +40 -0
  144. data/app/components/pulse/breadcrumbs.html.slim +9 -0
  145. data/app/components/pulse/breadcrumbs.rb +17 -0
  146. data/app/components/pulse/button.rb +148 -0
  147. data/app/components/pulse/component.rb +99 -0
  148. data/app/components/pulse/conditional_wrapper.rb +21 -0
  149. data/app/components/pulse/content.rb +11 -0
  150. data/app/components/pulse/counter.css +10 -0
  151. data/app/components/pulse/counter.rb +107 -0
  152. data/app/components/pulse/flash.html.slim +9 -0
  153. data/app/components/pulse/flash.rb +52 -0
  154. data/app/components/pulse/forms/error_explanation.html.slim +8 -0
  155. data/app/components/pulse/forms/error_explanation.rb +36 -0
  156. data/app/components/pulse/icon.rb +30 -0
  157. data/app/components/pulse/label.html.slim +5 -0
  158. data/app/components/pulse/label.rb +42 -0
  159. data/app/components/pulse/layout.css +239 -0
  160. data/app/components/pulse/layout.html.slim +7 -0
  161. data/app/components/pulse/layout.rb +141 -0
  162. data/app/components/pulse/link.html.slim +7 -0
  163. data/app/components/pulse/link.rb +52 -0
  164. data/app/components/pulse/modal.html.slim +51 -0
  165. data/app/components/pulse/modal.rb +64 -0
  166. data/app/components/pulse/modal_controller.js +77 -0
  167. data/app/components/pulse/nav_link.html.slim +16 -0
  168. data/app/components/pulse/nav_link.rb +25 -0
  169. data/app/components/pulse/nav_list/divider.rb +13 -0
  170. data/app/components/pulse/nav_list/group.html.slim +6 -0
  171. data/app/components/pulse/nav_list/group.rb +110 -0
  172. data/app/components/pulse/nav_list/heading.rb +36 -0
  173. data/app/components/pulse/nav_list/item.rb +171 -0
  174. data/app/components/pulse/nav_list.html.slim +11 -0
  175. data/app/components/pulse/nav_list.rb +225 -0
  176. data/app/components/pulse/popover.html.slim +5 -0
  177. data/app/components/pulse/popover.rb +88 -0
  178. data/app/components/pulse/progress_bar.html.slim +5 -0
  179. data/app/components/pulse/progress_bar.rb +23 -0
  180. data/app/components/pulse/progress_bar.yml +2 -0
  181. data/app/components/pulse/table/cell.html.slim +10 -0
  182. data/app/components/pulse/table/cell.rb +90 -0
  183. data/app/components/pulse/table/cell.yml +5 -0
  184. data/app/components/pulse/table/column.rb +22 -0
  185. data/app/components/pulse/table.html.slim +50 -0
  186. data/app/components/pulse/table.rb +111 -0
  187. data/app/components/pulse/table.yml +2 -0
  188. data/app/components/pulse/text.rb +72 -0
  189. data/app/components/pulse/toast.html.slim +14 -0
  190. data/app/components/pulse/toast.rb +85 -0
  191. data/app/components/pulse/toast_controller.js +32 -0
  192. data/app/javascript/pulse_view_components/application.js +24 -0
  193. data/app/javascript/pulse_view_components.js +4 -0
  194. data/app/lib/pulse/attributes_helper.rb +107 -0
  195. data/app/lib/pulse/fetch_or_fallback_helper.rb +53 -0
  196. data/app/lib/pulse/svg_helper.rb +28 -0
  197. data/lib/pulse/view_components/engine.rb +74 -0
  198. data/lib/pulse/view_components/version.rb +17 -0
  199. data/lib/pulse/view_components.rb +14 -0
  200. data/lib/pulse_view_components.rb +3 -0
  201. data/lib/tasks/pulse_view_components_tasks.rake +8 -0
  202. data/previews/pulse/badge_preview.rb +51 -0
  203. data/previews/pulse/blank_slate_preview.rb +14 -0
  204. data/previews/pulse/border_box_preview.rb +48 -0
  205. data/previews/pulse/breadcrumbs_preview.rb +13 -0
  206. data/previews/pulse/button_preview.rb +137 -0
  207. data/previews/pulse/counter_preview.rb +14 -0
  208. data/previews/pulse/flash_preview.rb +85 -0
  209. data/previews/pulse/icon_preview.rb +12 -0
  210. data/previews/pulse/label_preview.rb +10 -0
  211. data/previews/pulse/layout_preview.rb +34 -0
  212. data/previews/pulse/link_preview.rb +55 -0
  213. data/previews/pulse/modal_preview.rb +48 -0
  214. data/previews/pulse/nav_link_preview.rb +48 -0
  215. data/previews/pulse/nav_list_preview.rb +138 -0
  216. data/previews/pulse/popover_preview.rb +77 -0
  217. data/previews/pulse/progress_bar_preview.rb +23 -0
  218. data/previews/pulse/table_preview.rb +168 -0
  219. data/previews/pulse/text_preview.rb +102 -0
  220. data/previews/pulse/toast_preview.rb +67 -0
  221. metadata +344 -0
@@ -0,0 +1,10 @@
1
+ /! components/pulse/tables/cell
2
+ = render Pulse::BaseComponent.new(**options) do
3
+ - if sortable?
4
+ = link_to request.params.merge(**url_options), class: merge_classes('pulse-inline-flex pulse-items-center pulse-gap-1', title_classes) do
5
+ = content
6
+ = render Pulse::Icon.new(icon: icon, width: 9, height: 8, classes: 'pulse-shrink-0', title: t(".#{icon}"))
7
+
8
+ - else
9
+ span class=title_classes
10
+ = content
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pulse
4
+ class Table
5
+ # Renders a table cell
6
+ class Cell < Pulse::Component
7
+ attr_reader :sort_by, :current_sort_column, :current_sort_direction,
8
+ :additional_params, :footer
9
+
10
+ HEAD_DEFAULT_SCHEME = :xs
11
+ HEAD_SCHEME_MAPPINGS = {
12
+ sm: 'pulse-px-3 pulse-py-4 print:pulse-px-2 print:pulse-py-3 ' \
13
+ 'pulse-text-sm print:pulse-text-xs pulse-font-base',
14
+ xs: 'pulse-px-2 pulse-py-4 print:pulse-py-2 pulse-text-xs ' \
15
+ 'print:pulse-text-2xs pulse-font-medium'
16
+ }.freeze
17
+ HEAD_SCHEME_OPTIONS = HEAD_SCHEME_MAPPINGS.keys.freeze
18
+
19
+ DEFAULT_CELL_CLASSES = 'pulse-text-start first:pulse-ps-4 ' \
20
+ 'last:pulse-pe-4 pulse-px-2 pulse-text-gray ' \
21
+ 'print:first:pulse-ps-2 print:last:pulse-pe-2'
22
+ DEFAULT_TH_CLASSES = 'pulse-uppercase pulse-tracking-wider'
23
+ DEFAULT_TD_CLASSES = 'pulse-py-2 pulse-whitespace-nowrap ' \
24
+ 'pulse-text-sm print:pulse-text-xs pulse-text-gray'
25
+
26
+ def initialize(sort_by: nil, current_sort_column: nil,
27
+ current_sort_direction: nil, additional_params: {},
28
+ head_scheme: HEAD_DEFAULT_SCHEME, footer: nil,
29
+ title_classes: '', **options)
30
+ @sort_by = sort_by
31
+ @current_sort_column = current_sort_column
32
+ @current_sort_direction = current_sort_direction
33
+ @additional_params = additional_params
34
+ @footer = footer
35
+ @title_classes = title_classes
36
+ @options = options
37
+ @options[:classes] = merge_classes(
38
+ DEFAULT_CELL_CLASSES,
39
+ header_or_footer? ? DEFAULT_TH_CLASSES : DEFAULT_TD_CLASSES,
40
+ header_or_footer? ? HEAD_SCHEME_MAPPINGS[head_scheme] : '',
41
+ options[:classes]
42
+ )
43
+ end
44
+
45
+ def options
46
+ { tag: 'td' }.deep_merge(@options).tap do |args|
47
+ args[:classes] = class_names(
48
+ args[:classes]
49
+ )
50
+ end
51
+ end
52
+
53
+ def title_classes
54
+ header_or_footer? ? @title_classes : ''
55
+ end
56
+
57
+ def active
58
+ sort_by && sort_by == current_sort_column
59
+ end
60
+
61
+ def sortable?
62
+ sort_by && params[:q].blank?
63
+ end
64
+
65
+ def dir
66
+ current_sort_direction
67
+ end
68
+
69
+ def icon
70
+ active ? "sort_#{dir}" : 'sort'
71
+ end
72
+
73
+ def header_or_footer?
74
+ @options[:tag] == 'th' || @footer
75
+ end
76
+
77
+ def url_options
78
+ { sort: sort_by, direction: }.merge(additional_params)
79
+ end
80
+
81
+ def direction
82
+ params[:sort].nil? || params[:sort] == sort_by ? sort_dir : nil
83
+ end
84
+
85
+ def sort_dir
86
+ params[:direction] == 'asc' ? 'desc' : 'asc'
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,5 @@
1
+ en:
2
+ sort: "Sort"
3
+ sort_: "Sort"
4
+ sort_asc: "Sorting Ascending"
5
+ sort_desc: "Sorting Descending"
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pulse
4
+ class Table
5
+ # Encapsulates table column properties
6
+ class Column < Pulse::Component
7
+ attr_reader :title, :sr_title, :sort_by, :footer, :options
8
+
9
+ def initialize(title: '', sr_title: nil, sort_by: nil, footer: nil,
10
+ **options, &block)
11
+ @title = title
12
+ @sr_title = sr_title
13
+ @sort_by = sort_by
14
+ @footer = footer
15
+ @block = block
16
+ @options = options
17
+ end
18
+
19
+ delegate :call, to: :@block
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,50 @@
1
+ /! components/pulse/table
2
+ - if collection.present?
3
+ div class="pulse-flex pulse-flex-col pulse-gap-4"
4
+ div class=class_names("pulse-w-full pulse-shadow print:pulse-shadow-none sm:pulse-rounded-lg", 'pulse-overflow-x-scroll': scroll)
5
+ = render Pulse::BaseComponent.new(**table_options) do
6
+ - if header
7
+ = render Pulse::BaseComponent.new(tag: :thead, classes: head_classes) do
8
+ tr
9
+ - columns.each_with_index do |column, index|
10
+ = render_cell( \
11
+ sr_title: column.sr_title,
12
+ sort_by: (column.sort_by unless collection.empty?),
13
+ current_sort_column: current_sort_column,
14
+ current_sort_direction: current_sort_direction,
15
+ tag: 'th',
16
+ **column.options \
17
+ ) do
18
+ = column.title&.html_safe
19
+
20
+ = render Pulse::BaseComponent.new(tag: :tbody, classes: body_classes) do
21
+ - collection.each do |row|
22
+ = render Pulse::BaseComponent.new(**row_arguments(row)) do
23
+ - columns.each_with_index do |column, index|
24
+ = render_cell( \
25
+ tag: 'td',
26
+ **column.options \
27
+ ) do
28
+ = column.call(row)
29
+
30
+ - if footer?
31
+ tr class=head_classes
32
+ - columns.each_with_index do |column, index|
33
+ = render_cell( \
34
+ tag: 'td',
35
+ footer: true,
36
+ **column.options \
37
+ ) do
38
+ = column.footer
39
+
40
+
41
+ - if pagination? && collection.respond_to?(:total_pages)
42
+ div class="pulse-text-center pulse-text-gray"
43
+ = paginate(collection, window: 9)
44
+
45
+ - else
46
+ - if empty_state?
47
+ = empty_state
48
+ - else
49
+ = render Pulse::BlankSlate.new do |c|
50
+ - c.with_title { t('.no_data') }
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pulse
4
+ # Renders a table
5
+ # Inspiration taken from
6
+ # https://github.com/baoagency/polaris_view_components/
7
+ class Table < Pulse::Component
8
+ DEFAULT_TABLE_CLASSES = 'pulse-min-w-full pulse-divide-y ' \
9
+ 'pulse-divide-gray-300'
10
+ DEFAULT_HEAD_CLASSES = 'pulse-bg-gray-100'
11
+ DEFAULT_TH_CLASSES = 'pulse-text-start pulse-text-gray pulse-uppercase ' \
12
+ 'pulse-tracking-wider pulse-px-4 print:pulse-px-2 ' \
13
+ 'pulse-py-4 print:pulse-py-3 text-xs pulse-font-medium'
14
+ DEFAULT_BODY_CLASSES = 'pulse-bg-white pulse-divide-y pulse-text-sm ' \
15
+ 'pulse-leading-extra-snug pulse-divide-gray-300 ' \
16
+ 'print:pulse-divide-gray-400 pulse-tracking-tight ' \
17
+ ''
18
+ DEFAULT_ROW_CLASSES = 'pulse-border-secondary-300'
19
+ DEFAULT_TD_CLASSES = 'pulse-px-4 print:pulse-px-2 pulse-py-4'
20
+
21
+ delegate :paginate, to: :helpers
22
+
23
+ renders_many :columns, lambda { |**options, &block|
24
+ Pulse::Table::Column.new(**options, &block)
25
+ }
26
+ renders_one :empty_state, Pulse::BlankSlate
27
+
28
+ attr_reader :collection, :additional_params, :pagination, :striped,
29
+ :full_width, :current_sort_column, :current_sort_direction,
30
+ :hoverable, :row_classes, :header, :scroll
31
+
32
+ def initialize(collection, additional_params: {}, pagination: true,
33
+ striped: false, current_sort_column: nil,
34
+ current_sort_direction: nil, hoverable: false,
35
+ row_classes: nil, header: true, scroll: true, **options)
36
+ @collection = collection
37
+ @additional_params = additional_params
38
+ @pagination = pagination
39
+ @striped = striped
40
+ @current_sort_column = current_sort_column
41
+ @current_sort_direction = current_sort_direction
42
+ @hoverable = hoverable
43
+ @row_classes = row_classes
44
+ @header = header
45
+ @scroll = scroll
46
+ @options = options
47
+ end
48
+
49
+ def pagination?
50
+ pagination
51
+ end
52
+
53
+ def footer?
54
+ columns.any?(&:footer)
55
+ end
56
+
57
+ def table_options
58
+ { tag: 'table' }.deep_merge(@options).tap do |args|
59
+ args[:classes] = merge_classes(
60
+ DEFAULT_TABLE_CLASSES,
61
+ 'pulse-container pulse-mx-auto',
62
+ args[:classes]
63
+ )
64
+ args[:data] ||= {}
65
+ end
66
+ end
67
+
68
+ def head_classes
69
+ merge_classes(
70
+ DEFAULT_HEAD_CLASSES,
71
+ @options[:head_classes]
72
+ )
73
+ end
74
+
75
+ def head_cell_classes
76
+ merge_classes(
77
+ DEFAULT_TH_CLASSES
78
+ )
79
+ end
80
+
81
+ def body_classes
82
+ merge_classes(
83
+ DEFAULT_BODY_CLASSES,
84
+ '[&>tr:nth-child(even)]:pulse-bg-gray-50': striped,
85
+ 'pulse-border-b [&>tr]:pulse-border-t [&>tr:first-child]:pulse-border-t-0': !striped # rubocop:disable Layout/LineLength
86
+ )
87
+ end
88
+
89
+ def row_arguments(row)
90
+ { tag: 'tr' }.tap do |args|
91
+ args[:classes] = merge_classes(
92
+ DEFAULT_ROW_CLASSES,
93
+ { 'hover:pulse-bg-dark-charcoal-50': hoverable },
94
+ row_classes
95
+ )
96
+ args[:id] = dom_id(row) if row.respond_to?(:to_key)
97
+ args[:data] ||= {}
98
+ end
99
+ end
100
+
101
+ def render_cell(**arguments, &)
102
+ render(
103
+ Pulse::Table::Cell.new(
104
+ additional_params:,
105
+ **arguments
106
+ ),
107
+ &
108
+ )
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,2 @@
1
+ en:
2
+ no_data: "No records found"
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pulse
4
+ # Base Text component
5
+ class Text < Pulse::Component
6
+ STYLE_MAPPINGS = {
7
+ h1: 'pulse-text-3xl pulse-font-bold print:pulse-text-2xl',
8
+ h2: 'pulse-text-2xl pulse-font-bold print:pulse-text-xl',
9
+ h3: 'pulse-text-xl pulse-font-bold print:pulse-text-lg',
10
+ h4: 'pulse-text-lg pulse-font-bold print:pulse-text-base',
11
+ subtitle: 'pulse-text-xl print:pulse-text-lg pulse-leading-tight ' \
12
+ 'pulse-font-bold tracking-tight',
13
+ body1: 'pulse-text-lg print:pulse-text-base pulse-font-bold ' \
14
+ 'pulse-leading-6 tracking-tight',
15
+ body2: 'pulse-text-lg print:pulse-text-base pulse-font-normal ' \
16
+ 'pulse-leading-6 tracking-tight',
17
+ body3: 'pulse-text-base print:pulse-text-sm pulse-font-bold ' \
18
+ 'pulse-leading-normal',
19
+ body4: 'pulse-text-base print:pulse-text-sm pulse-font-normal ' \
20
+ 'pulse-leading-normal',
21
+ body5: 'pulse-text-sm print:pulse-text-xs pulse-font-bold ' \
22
+ 'pulse-leading-snug',
23
+ body6: 'pulse-text-sm print:pulse-text-xs pulse-font-normal ' \
24
+ 'pulse-leading-snug',
25
+ body7: 'pulse-text-xs pulse-font-bold pulse-leading-5',
26
+ body8: 'pulse-text-xs pulse-font-normal pulse-leading-5',
27
+ caption1: 'pulse-text-2xs pulse-font-bold tracking-normal ' \
28
+ 'pulse-leading-extra-tight',
29
+ caption2: 'pulse-text-2xs pulse-font-normal tracking-normal ' \
30
+ 'pulse-leading-extra-tight'
31
+ }.freeze
32
+
33
+ TAG_MAPPINGS = {
34
+ h1: :h1,
35
+ h2: :h2,
36
+ h3: :h3,
37
+ h4: :h4,
38
+ subtitle: :h2,
39
+ body1: :p,
40
+ body2: :p,
41
+ body3: :p,
42
+ body4: :p,
43
+ body5: :p,
44
+ body6: :p,
45
+ body7: :p,
46
+ body8: :p,
47
+ caption1: :p,
48
+ caption2: :p
49
+ }.freeze
50
+
51
+ def initialize(type:, **system_arguments)
52
+ @system_arguments = system_arguments
53
+ @system_arguments[:classes] = merge_classes(
54
+ STYLE_MAPPINGS[type],
55
+ system_arguments[:classes]
56
+ )
57
+ @system_arguments[:tag] ||= TAG_MAPPINGS[type]
58
+ end
59
+
60
+ def tag
61
+ @el_tag || TAG_MAPPINGS[@type]
62
+ end
63
+
64
+ def call
65
+ render Pulse::BaseComponent.new(**@system_arguments).with_content(content)
66
+ end
67
+
68
+ def render?
69
+ content.present?
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,14 @@
1
+ /! components/pulse/toast
2
+ = render Pulse::BaseComponent.new(**@system_arguments) do
3
+ div class="pulse-flex pulse-w-full" data-theme=(@theme)
4
+ div class="pulse-flex-1 pulse-flex pulse-justify-between pulse-items-start"
5
+ div class="pulse-flex-1 pulse-flex pulse-justify-between pulse-items-center pulse-gap-4"
6
+ = render Pulse::Text.new(type: :body6, classes: 'pulse-mb-0 pulse-min-w-[250px]') do
7
+ = content
8
+ div class="pulse-mt-0"
9
+ div class="pulse-flex pulse-items-center pulse-gap-3.5 -pulse-my-2"
10
+ - if action?
11
+ = render Pulse::Text.new(type: :body5, classes: merge_classes(link_classes, 'pulse-uppercase pulse-mb-0')) do
12
+ = action
13
+ button class="pulse-appearance-none pulse-bg-transparent pulse-border-0 pulse-p-2" data-action="click->pulse--toast#dismiss"
14
+ = render Pulse::Icon.new(icon: :close, width: 9, height: 9, class: link_classes)
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pulse
4
+ # Renders a toast popup.
5
+ class Toast < Pulse::Component
6
+ include Turbo::FramesHelper
7
+
8
+ renders_one :action, lambda { |url:, **system_arguments, &block|
9
+ system_arguments[:tag] = :a
10
+ system_arguments[:href] = url
11
+ system_arguments[:classes] = merge_classes(
12
+ system_arguments[:classes],
13
+ LINK_MAPPINGS[@theme]
14
+ )
15
+ BaseComponent.new(**system_arguments, &block)
16
+ }
17
+
18
+ LINK_MAPPINGS = {
19
+ default: 'pulse-text-white hover:pulse-text-white',
20
+ error: 'pulse-text-gray hover:pulse-text-gray',
21
+ success: 'pulse-text-white hover:pulse-text-white'
22
+ }.freeze
23
+
24
+ THEME_MAPPINGS = {
25
+ default: 'pulse-bg-gray-700 pulse-text-white',
26
+ error: 'pulse-bg-error-100 pulse-text-gray-900',
27
+ success: 'pulse-bg-gray-700 pulse-text-white'
28
+ }.freeze
29
+ DEFAULT_THEME = :default
30
+
31
+ def initialize(
32
+ auto_hide: true,
33
+ theme: DEFAULT_THEME,
34
+ **system_arguments
35
+ )
36
+ @auto_hide = auto_hide
37
+ @system_arguments = system_arguments
38
+ @theme = theme
39
+
40
+ @system_arguments[:tag] ||= :div
41
+ @system_arguments[:data] = data_attributes
42
+ @system_arguments[:classes] = class_attributes
43
+ end
44
+
45
+ private
46
+
47
+ def class_attributes
48
+ merge_classes(
49
+ @system_arguments[:classes],
50
+ # 'pulse-hidden',
51
+ 'pulse-pl-8',
52
+ 'pulse-pr-4',
53
+ 'pulse-py-2.5',
54
+ 'pulse-mb-4',
55
+ 'pulse-rounded',
56
+ 'pulse-shadow-toast',
57
+ 'pulse-z-100',
58
+ THEME_MAPPINGS[@theme]
59
+ )
60
+ end
61
+
62
+ def data_attributes
63
+ merge_attributes(
64
+ @system_arguments[:data],
65
+ {
66
+ controller: 'pulse--toast',
67
+ pulse__toast_auto_hide_value: @auto_hide,
68
+ transition_enter: 'pulse-transition pulse-ease-out ' \
69
+ 'pulse-duration-500',
70
+ transition_enter_start: 'pulse-transform pulse-opacity-0 ' \
71
+ 'pulse-translate-y-full',
72
+ transition_enter_end: 'pulse-transform pulse-opacity-100 ' \
73
+ 'pulse-translate-y-0',
74
+ transition_leave: 'pulse-transition pulse-ease-in pulse-duration-200',
75
+ transition_leave_start: 'pulse-transform pulse-opacity-100',
76
+ transition_leave_end: 'pulse-transform pulse-opacity-0'
77
+ }
78
+ )
79
+ end
80
+
81
+ def link_classes
82
+ LINK_MAPPINGS[@theme]
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,32 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+ import { enter, leave } from "el-transition"
3
+
4
+ // Connects to data-controller="pulse--toast"
5
+ export default class extends Controller {
6
+ static values = {
7
+ autoHide: { type: Boolean, default: true },
8
+ autoHideDuration: { type: Number, default: 3000 },
9
+ }
10
+
11
+ connect() {
12
+ enter(this.element)
13
+
14
+ if (this.autoHideValue) {
15
+ this.autoHideTimeout = setTimeout(() => {
16
+ this.dismiss()
17
+ }, this.autoHideDurationValue)
18
+ }
19
+ }
20
+
21
+ disconnect() {
22
+ if (this.autoHideValue) {
23
+ clearTimeout(this.autoHideTimeout)
24
+ }
25
+ }
26
+
27
+ dismiss() {
28
+ leave(this.element).then(() => {
29
+ this.element.remove()
30
+ })
31
+ }
32
+ }
@@ -0,0 +1,24 @@
1
+ import { Application } from "@hotwired/stimulus"
2
+ import Dropdown from "stimulus-dropdown"
3
+ import * as Turbo from "@hotwired/turbo"
4
+
5
+ // import * as ActiveStorage from "@rails/activestorage"
6
+
7
+ const application = Application.start()
8
+
9
+ // Configure Stimulus development experience
10
+ application.debug = false
11
+ window.Stimulus = application
12
+
13
+ // application.register("dropdown", Dropdown)
14
+
15
+ // Automatically load controllers from ViewComponents directory.
16
+ import controllers from "../../components/**/*_controller.js"
17
+ controllers.forEach(controller => {
18
+ var name = controller.name.replace("..--..--components--", "")
19
+ application.register(name, controller.module.default)
20
+ })
21
+
22
+ export { application }
23
+
24
+ // ActiveStorage.start()
@@ -0,0 +1,4 @@
1
+ import "@hotwired/turbo-rails"
2
+ import './pulse_view_components/application'
3
+
4
+ import "el-transition"
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pulse
4
+ # :nodoc:
5
+ module AttributesHelper
6
+ PLURAL_ARIA_ATTRIBUTES = [:describedby, :labelledby].freeze
7
+ PLURAL_DATA_ATTRIBUTES = [:target, :targets].freeze
8
+
9
+ def aria(val, system_arguments)
10
+ system_arguments[:"aria-#{val}"] || system_arguments.dig(:aria,
11
+ val.to_sym)
12
+ end
13
+
14
+ def data(val, system_arguments)
15
+ system_arguments[:"data-#{val}"] || system_arguments.dig(:data,
16
+ val.to_sym)
17
+ end
18
+
19
+ # Merges hashes that contain "aria-*" keys and nested aria: hashes. Removes keys from
20
+ # each hash and returns them in the new hash.
21
+ #
22
+ # Eg. merge_aria({ "aria-disabled": "true" }, { aria: { invalid: "true" } })
23
+ # => { disabled: "true", invalid: "true" }
24
+ #
25
+ # Certain aria attributes can contain multiple values separated by spaces. merge_aria
26
+ # will combine these plural attributes into a composite string.
27
+ #
28
+ # Eg. merge_aria({ "aria-labelledby": "foo" }, { aria: { labelledby: "bar" } })
29
+ # => { labelledby: "foo bar" }
30
+ #
31
+ # It's designed to be used to normalize and merge aria information from system_arguments
32
+ # hashes. Consider using this pattern in component initializers:
33
+ #
34
+ # @system_arguments[:aria] = merge_aria(
35
+ # @system_arguments,
36
+ # { aria: { labelled_by: id } }
37
+ # )
38
+ def merge_aria(*hashes)
39
+ merge_prefixed_attribute_hashes(
40
+ *hashes, prefix: :aria, plural_keys: PLURAL_ARIA_ATTRIBUTES
41
+ )
42
+ end
43
+
44
+ # Merges hashes that contain "data-*" keys and nested data: hashes. Removes keys from
45
+ # each hash and returns them in the new hash.
46
+ #
47
+ # Eg. merge_data({ "data-foo": "true" }, { data: { bar: "true" } })
48
+ # => { foo: "true", bar: "true" }
49
+ #
50
+ # Certain data attributes can contain multiple values separated by spaces. merge_data
51
+ # will combine these plural attributes into a composite string.
52
+ #
53
+ # Eg. merge_data({ "data-target": "foo" }, { data: { target: "bar" } })
54
+ # => { target: "foo bar" }
55
+ #
56
+ # It's designed to be used to normalize and merge data information from system_arguments
57
+ # hashes. Consider using this pattern in component initializers:
58
+ #
59
+ # @system_arguments[:data] = merge_aria(
60
+ # @system_arguments,
61
+ # { data: { foo: "bar" } }
62
+ # )
63
+ def merge_data(*hashes)
64
+ merge_prefixed_attribute_hashes(
65
+ *hashes, prefix: :data, plural_keys: PLURAL_DATA_ATTRIBUTES
66
+ )
67
+ end
68
+
69
+ def merge_prefixed_attribute_hashes(*hashes, prefix:, plural_keys:)
70
+ {}.tap do |result|
71
+ hashes.each do |hash|
72
+ next unless hash
73
+
74
+ prefix_hash = hash.delete(prefix) || {}
75
+
76
+ prefix_hash.each_pair do |key, val|
77
+ result[key] =
78
+ if plural_keys.include?(key)
79
+ [*(result[key] || '').split, val].join(' ').strip
80
+ else
81
+ val
82
+ end
83
+ end
84
+
85
+ hash.delete_if do |key, val|
86
+ key_s = key.to_s
87
+
88
+ if key.start_with?("#{prefix}-")
89
+ bare_key = key_s.sub("#{prefix}-", '').to_sym
90
+
91
+ result[bare_key] =
92
+ if plural_keys.include?(bare_key)
93
+ [*(result[bare_key] || '').split, val].join(' ').strip
94
+ else
95
+ val
96
+ end
97
+
98
+ true
99
+ else
100
+ false
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end