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,261 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pulse
4
+ # An ActionList is a styled list of links. It acts as the base component for many
5
+ # other menu-type components, including `ActionMenu` and `SelectPanel`, as well as
6
+ # the navigational component `NavList`.
7
+ #
8
+ # Each item in an action list can be augmented by specifying corresponding leading
9
+ # and/or trailing visuals.
10
+ class ActionList < Pulse::Component
11
+ DEFAULT_ROLE = :list
12
+
13
+ MENU_ROLE = :menu
14
+ DEFAULT_MENU_ITEM_ROLE = :menuitem
15
+
16
+ DEFAULT_SCHEME = :full
17
+ SCHEME_MAPPINGS = {
18
+ DEFAULT_SCHEME => nil,
19
+ :inset => 'ActionListWrap--inset'
20
+ }.freeze
21
+ SCHEME_OPTIONS = SCHEME_MAPPINGS.keys.freeze
22
+
23
+ DEFAULT_SELECT_VARIANT = :none
24
+ SELECT_VARIANT_OPTIONS = [
25
+ :single,
26
+ :multiple,
27
+ :multiple_checkbox,
28
+ DEFAULT_SELECT_VARIANT
29
+ ].freeze
30
+
31
+ SELECT_VARIANT_ROLE_MAP = {
32
+ single: :menuitemradio,
33
+ multiple: :menuitemcheckbox,
34
+ multiple_checkbox: :menuitemcheckbox
35
+ }.freeze
36
+
37
+ # simplecov:disable
38
+ # @private
39
+ def self.custom_element_name
40
+ @custom_element_name ||= name.split('::').last.underscore.dasherize
41
+ end
42
+
43
+ # @private
44
+ delegate :custom_element_name, to: :class
45
+ # simplecov:enable
46
+
47
+ # Heading text rendered above the list of items.
48
+ #
49
+ # @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Pulse::ActionList::Heading) %>.
50
+ renders_one :heading, lambda { |**system_arguments|
51
+ Heading.new(**system_arguments)
52
+ }
53
+
54
+ # @!parse
55
+ # # Adds an item to the list.
56
+ # #
57
+ # # @param component_klass [Class] The class to use instead of the default <%= link_to_component(Pulse::ActionList::Item) %>
58
+ # # @param system_arguments [Hash] These arguments are forwarded to <%= link_to_component(Pulse::ActionList::Item) %>, or whatever class is passed as the `component_klass` argument.
59
+ # def with_item(**system_arguments, &block)
60
+ # end
61
+
62
+ # @!parse
63
+ # # Adds a divider to the list. Dividers visually separate items.
64
+ # #
65
+ # # @param system_arguments [Hash] The arguments accepted by <%= link_to_component(Pulse::ActionList::Divider) %>.
66
+ # def with_divider(**system_arguments, &block)
67
+ # end
68
+
69
+ # @!parse
70
+ # # Adds an avatar item to the list. Avatar items are a convenient way to accessibly add an item with a leading avatar image.
71
+ # #
72
+ # # @param src [String] The source url of the avatar image.
73
+ # # @param username [String] The username associated with the avatar.
74
+ # # @param full_name [String] Optional. The user's full name.
75
+ # # @param full_name_scheme [Symbol] Optional. How to display the user's full name. <%= one_of(Pulse::ActionList::Item::DESCRIPTION_SCHEME_OPTIONS) %>
76
+ # # @param component_klass [Class] The class to use instead of the default <%= link_to_component(Pulse::ActionList::Item) %>
77
+ # # @param avatar_arguments [Hash] Optional. The arguments accepted by <%= link_to_component(Pulse::Avatar) %>
78
+ # # @param system_arguments [Hash] These arguments are forwarded to <%= link_to_component(Pulse::ActionList::Item) %>, or whatever class is passed as the `component_klass` argument.
79
+ # def with_avatar_item(src:, username:, full_name: nil, full_name_scheme: Pulse::ActionList::Item::DEFAULT_DESCRIPTION_SCHEME, component_klass: ActionList::Item, avatar_arguments: {}, **system_arguments, &block)
80
+ # end
81
+
82
+ # Items. Items can be individual items, avatar items, or dividers. See the documentation for `#with_item`, `#with_divider`, and `#with_avatar_item` respectively for more information.
83
+ #
84
+ renders_many :items, types: {
85
+ item: {
86
+ renders: lambda { |**system_arguments|
87
+ build_item(**system_arguments).tap do |item|
88
+ will_add_item(item)
89
+ end
90
+ },
91
+
92
+ as: :item
93
+ },
94
+
95
+ avatar_item: {
96
+ renders: lambda { |**system_arguments|
97
+ build_avatar_item(**system_arguments).tap do |item|
98
+ will_add_item(item)
99
+ end
100
+ },
101
+
102
+ as: :avatar_item
103
+ },
104
+
105
+ divider: {
106
+ renders: ActionList::Divider,
107
+ as: :divider
108
+ }
109
+ }
110
+
111
+ attr_reader :id, :select_variant, :role
112
+
113
+ # @param id [String] HTML ID value.
114
+ # @param role [Boolean] ARIA role describing the function of the list. listbox and menu are a common values.
115
+ # @param item_classes [String] Additional CSS classes to attach to items.
116
+ # @param scheme [Symbol] <%= one_of(Pulse::ActionList::SCHEME_OPTIONS) %> `inset` children are offset (vertically and horizontally) from list edges. `full` (default) children are flush (vertically and horizontally) with list edges.
117
+ # @param show_dividers [Boolean] Display a divider above each item in the list when it does not follow a header or divider.
118
+ # @param select_variant [Symbol] How items may be selected in the list. <%= one_of(Pulse::ActionList::SELECT_VARIANT_OPTIONS) %>
119
+ # @param form_arguments [Hash] Allows an `ActionList` to act as a select list in multi- and single-select modes. Pass the `builder:` and `name:` options to this hash. `builder:` should be an instance of `ActionView::Helpers::FormBuilder`, which are created by the standard Rails `#form_with` and `#form_for` helpers. The `name:` option is the desired name of the field that will be included in the params sent to the server on form submission. *NOTE*: Consider using an <%= link_to_component(Pulse::ActionMenu) %> instead of using this feature directly.
120
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
121
+ def initialize(
122
+ id: self.class.generate_id,
123
+ role: nil,
124
+ item_classes: nil,
125
+ scheme: DEFAULT_SCHEME,
126
+ show_dividers: false,
127
+ select_variant: DEFAULT_SELECT_VARIANT,
128
+ form_arguments: {},
129
+ **system_arguments
130
+ )
131
+ @system_arguments = system_arguments
132
+ @id = id
133
+ @system_arguments[:id] = @id
134
+ @system_arguments[:tag] = :ul
135
+ @item_classes = item_classes
136
+ @scheme = fetch_or_fallback(SCHEME_OPTIONS, scheme, DEFAULT_SCHEME)
137
+ @show_dividers = show_dividers
138
+ @select_variant = select_variant
139
+ @system_arguments[:classes] = merge_classes(
140
+ SCHEME_MAPPINGS[@scheme],
141
+ system_arguments[:classes],
142
+ 'ActionListWrap--divided' => @show_dividers
143
+ )
144
+
145
+ @role = role || (allows_selection? ? MENU_ROLE : DEFAULT_ROLE)
146
+ @system_arguments[:role] = @role
147
+
148
+ @list_wrapper_arguments = {}
149
+
150
+ @form_builder = form_arguments[:builder]
151
+ @input_name = form_arguments[:name]
152
+
153
+ return unless required_form_arguments_given? && !allows_selection?
154
+
155
+ raise ArgumentError,
156
+ 'lists/menus that act as form inputs must also allow item ' \
157
+ 'selection (please pass the `select_variant:` option)'
158
+ end
159
+
160
+ # @private
161
+ def before_render
162
+ return unless heading?
163
+
164
+ @system_arguments[:'aria-labelledby'] = heading.title_id
165
+ return unless heading.subtitle?
166
+
167
+ @system_arguments[:'aria-describedby'] =
168
+ heading.subtitle_id
169
+ end
170
+
171
+ # Builds a new item but does not add it to the list. Use this method
172
+ # instead of the `#with_item` slot if you need to render an item outside
173
+ # the context of a list, eg. if rendering additional items to append to
174
+ # an existing list, perhaps via a separate HTTP request.
175
+ #
176
+ # @param component_klass [Class] The class to use instead of the default <%= link_to_component(Pulse::ActionList::Item) %>
177
+ # @param system_arguments [Hash] These arguments are forwarded to <%= link_to_component(Pulse::ActionList::Item) %>, or whatever class is passed as the `component_klass` argument.
178
+ def build_item(component_klass: ActionList::Item, **system_arguments)
179
+ if single_select? &&
180
+ system_arguments[:active] &&
181
+ items.any?(&:active?)
182
+ raise ArgumentError,
183
+ 'only a single item may be active when select_variant is ' \
184
+ 'set to :single'
185
+ end
186
+ system_arguments[:classes] = merge_classes(
187
+ @item_classes,
188
+ system_arguments[:classes]
189
+ )
190
+
191
+ component_klass.new(list: self, **system_arguments)
192
+ end
193
+
194
+ # Builds a new avatar item but does not add it to the list. Avatar items
195
+ # are a convenient way to accessibly add an item with a leading avatar
196
+ # image. Use this method instead of the `#with_avatar_item` slot if you
197
+ # need to render an avatar item outside the context of a list, eg. if
198
+ # rendering additional items to append to an existing list, perhaps via
199
+ # a separate HTTP request.
200
+ #
201
+ # @param src [String] The source url of the avatar image.
202
+ # @param username [String] The username associated with the avatar.
203
+ # @param full_name [String] Optional. The user's full name.
204
+ # @param full_name_scheme [Symbol] Optional. How to display the user's full name. <%= one_of(Pulse::ActionList::Item::DESCRIPTION_SCHEME_OPTIONS) %>
205
+ # @param component_klass [Class] The class to use instead of the default <%= link_to_component(Pulse::ActionList::Item) %>
206
+ # @param avatar_arguments [Hash] Optional. The arguments accepted by <%= link_to_component(Pulse::Avatar) %>
207
+ # @param system_arguments [Hash] These arguments are forwarded to <%= link_to_component(Pulse::ActionList::Item) %>, or whatever class is passed as the `component_klass` argument.
208
+ def build_avatar_item(
209
+ src:, username:, full_name: nil,
210
+ full_name_scheme: Pulse::ActionList::Item::DEFAULT_DESCRIPTION_SCHEME,
211
+ component_klass: ActionList::Item, avatar_arguments: {},
212
+ **system_arguments
213
+ )
214
+ build_item(label: username, description_scheme: full_name_scheme,
215
+ component_klass:, **system_arguments).tap do |item|
216
+ item.with_leading_visual_raw_content do
217
+ # no alt text necessary for presentational item
218
+ item.render(Pulse::Avatar.new(src:, **avatar_arguments,
219
+ role: :presentation, size: 16))
220
+ end
221
+
222
+ item.with_description_content(full_name) if full_name
223
+ end
224
+ end
225
+
226
+ def single_select?
227
+ select_variant == :single
228
+ end
229
+
230
+ def multi_select?
231
+ [:multiple, :multiple_checkbox].include?(select_variant)
232
+ end
233
+
234
+ def allows_selection?
235
+ single_select? || multi_select?
236
+ end
237
+
238
+ def acts_as_menu?
239
+ @system_arguments[:role] == :menu
240
+ end
241
+
242
+ def required_form_arguments_given?
243
+ @form_builder && @input_name
244
+ end
245
+
246
+ def acts_as_form_input?
247
+ required_form_arguments_given? && allows_selection?
248
+ end
249
+
250
+ # @private
251
+ def will_add_item(_item); end
252
+
253
+ private
254
+
255
+ def with_post_list_content(&block)
256
+ @post_list_content_block = block
257
+ end
258
+
259
+ attr_reader :post_list_content_block
260
+ end
261
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pulse
4
+ # Renders a badge component
5
+ # see: /lookbook/inspect/pulse/badge/default
6
+ class Badge < Pulse::Component
7
+ DEFAULT_TAG = :span
8
+ TAG_OPTIONS = [DEFAULT_TAG, :summary, :a, :div].freeze
9
+
10
+ DEFAULT_VARIANT = :standard
11
+ VARIANT_MAPPINGS = {
12
+ standard: 'pulse-inline-block pulse-rounded pulse-uppercase ' \
13
+ 'pulse-py-0.5 pulse-px-1.5 pulse-text-[10px] ' \
14
+ 'pulse-font-bold',
15
+ pill: 'pulse-inline-block pulse-rounded-2xl pulse-min-w-[8rem] ' \
16
+ 'pulse-text-center pulse-p-2 pulse-text-sm ' \
17
+ 'pulse-leading-none pulse-font-bold'
18
+ }.freeze
19
+ VARIANT_OPTIONS = VARIANT_MAPPINGS.keys.freeze
20
+
21
+ DEFAULT_SCHEME = :secondary
22
+ SCHEME_MAPPINGS = {
23
+ primary: 'pulse-bg-primary pulse-text-white hover:pulse-text-white',
24
+ secondary: 'pulse-bg-secondary pulse-text-white',
25
+ secondary_outline: 'pulse-bg-white pulse-border pulse-border-secondary ' \
26
+ 'pulse-text-secondary ' \
27
+ 'pulse-hover:pulse-text-secondary',
28
+ invited: 'pulse-bg-secondary-200 pulse-text-gray ' \
29
+ 'pulse-hover:pulse-text-gray',
30
+ active: 'pulse-bg-success-200 pulse-text-gray hover:pulse-text-gray',
31
+ disabled: 'pulse-bg-gray-100 pulse-text-gray hover:pulse-text-gray',
32
+ info: 'pulse-bg-gray-600 pulse-text-white hover:pulse-text-white'
33
+ }.freeze
34
+ SCHEME_OPTIONS = SCHEME_MAPPINGS.keys.freeze
35
+
36
+ def initialize(tag: DEFAULT_TAG, variant: DEFAULT_VARIANT,
37
+ scheme: DEFAULT_SCHEME, **system_arguments)
38
+ @system_arguments = system_arguments
39
+
40
+ @variant = fetch_or_fallback(VARIANT_OPTIONS, variant, DEFAULT_VARIANT)
41
+ @scheme = fetch_or_fallback(SCHEME_OPTIONS, scheme, DEFAULT_SCHEME)
42
+
43
+ @system_arguments[:tag] = fetch_or_fallback(TAG_OPTIONS, tag, DEFAULT_TAG)
44
+ @system_arguments[:classes] = merge_classes(
45
+ VARIANT_MAPPINGS[@variant],
46
+ SCHEME_MAPPINGS[@scheme],
47
+ system_arguments[:classes]
48
+ )
49
+ end
50
+
51
+ def call
52
+ render(Pulse::BaseComponent.new(**@system_arguments)) { content }
53
+ end
54
+
55
+ def render?
56
+ content.present?
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pulse
4
+ # BaseComponent accepts a standard set of options, providing the
5
+ # foundation for other components to use and build upon. Specify a tag,
6
+ # optional classes, and any other additional options will be passed to
7
+ # Rails' `content_tag` helper.
8
+ #
9
+ # This is a simplified version of Primer's `BaseComponent`
10
+ #
11
+ # For example:
12
+ #
13
+ # ```erb
14
+ # <%= render Pulse::BaseComponent.new(tag: :span, classes: "text-sm") do %>
15
+ # Hello world
16
+ # <% end %>
17
+ # ```
18
+ #
19
+ # Renders:
20
+ #
21
+ # ```html
22
+ # <span class="text-sm">Hello world</span>
23
+ # ```
24
+ #
25
+ class BaseComponent < Pulse::Component
26
+ def initialize(tag:, classes: nil, **system_arguments)
27
+ @tag = tag
28
+ @classes = classes
29
+ @system_arguments = system_arguments
30
+ end
31
+
32
+ def call
33
+ content_tag(@tag, content, class: @classes, **@system_arguments)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,10 @@
1
+ /! components/ui/blank_slate
2
+ = render Pulse::BaseComponent.new(**@system_arguments) do
3
+ / div class="flex items-center justify-center mb-4"
4
+ / = render_svg 'blank-graphic'
5
+ = render Pulse::Text.new(type: :body1) do
6
+ = title || t('.empty')
7
+
8
+ - if description.present?
9
+ = render Pulse::Text.new(type: :body4) do
10
+ = description
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pulse
4
+ # Use as placeholder when content is missing
5
+ class BlankSlate < Pulse::Component
6
+ renders_one :title
7
+ renders_one :description
8
+
9
+ def initialize(**system_arguments)
10
+ @system_arguments = system_arguments
11
+ @system_arguments[:tag] ||= :div
12
+ @system_arguments[:classes] = merge_classes(
13
+ 'pulse-container pulse-text-center pulse-pt-16 pulse-pb-20',
14
+ 'pulse-bg-secondary-50',
15
+ system_arguments[:classes]
16
+ )
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,2 @@
1
+ en:
2
+ empty: "Nothing to see here."
@@ -0,0 +1,4 @@
1
+ /! components/pulse/border_box/header
2
+ = render Pulse::BaseComponent.new(**@system_arguments) do
3
+ = title
4
+ = content
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pulse
4
+ class BorderBox
5
+ # `BorderBox::Header` is used inside `BorderBox` to render its header slot.
6
+ #
7
+ # @accessibility When using `header.with_title`, set `tag` to one of `h1`, `h2`, `h3`, etc. based on what is appropriate for the page context. <%= link_to_heading_practices %>
8
+ class Header < Pulse::Component
9
+ attr_reader :id
10
+
11
+ TITLE_TAG_FALLBACK = :h2
12
+ TITLE_TAG_OPTIONS = [:h1, TITLE_TAG_FALLBACK, :h3, :h4, :h5, :h6].freeze
13
+
14
+ # Optional Title.
15
+ #
16
+ # @param tag [Symbol] <%= one_of(Pulse::BorderBox::Header::TITLE_TAG_OPTIONS) %>
17
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
18
+ renders_one :title, lambda { |tag:, **system_arguments|
19
+ system_arguments[:tag] = fetch_or_fallback(
20
+ TITLE_TAG_OPTIONS,
21
+ tag,
22
+ TITLE_TAG_FALLBACK
23
+ )
24
+ system_arguments[:classes] = class_names(
25
+ 'Box-title',
26
+ system_arguments[:classes]
27
+ )
28
+
29
+ Pulse::BaseComponent.new(**system_arguments)
30
+ }
31
+
32
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
33
+ def initialize(**system_arguments)
34
+ @system_arguments = system_arguments
35
+ @system_arguments[:id] ||= self.class.generate_id
36
+ @system_arguments[:tag] = :div
37
+ @system_arguments[:classes] = class_names(
38
+ 'pulse-px-4 pulse-py-5 pulse-bg-gray-50 sm:pulse-p-6',
39
+ 'pulse-rounded-t-md pulse-border-b pulse-border-gray-300',
40
+ 'pulse-border-solid',
41
+ 'Box-header',
42
+ system_arguments[:classes]
43
+ )
44
+ @id = @system_arguments[:id]
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,10 @@
1
+ /! components/pulse/border_box
2
+ = render Pulse::BaseComponent.new(**@system_arguments) do
3
+ = header
4
+ = body
5
+ - if rows.any?
6
+ = render Pulse::BaseComponent.new(**@list_arguments) do
7
+ - rows.each do |row|
8
+ = row
9
+
10
+ = footer
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pulse
4
+ # `BorderBox` is a Box component with a border.
5
+ class BorderBox < Pulse::Component
6
+ DEFAULT_PADDING = :default
7
+ PADDING_MAPPINGS = {
8
+ DEFAULT_PADDING => '',
9
+ :condensed => 'Box--condensed',
10
+ :spacious => 'Box--spacious'
11
+ }.freeze
12
+ PADDING_SUGGESTION = "Perhaps you could consider using :padding options " \
13
+ "of #{PADDING_MAPPINGS.keys.to_sentence}?".freeze
14
+
15
+ DEFAULT_ROW_SCHEME = :default
16
+ ROW_SCHEME_MAPPINGS = {
17
+ DEFAULT_ROW_SCHEME => '',
18
+ :neutral => 'Box-row--gray',
19
+ :info => 'Box-row--blue',
20
+ :warning => 'Box-row--yellow'
21
+ }.freeze
22
+
23
+ # Optional Header.
24
+ #
25
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
26
+ # @accessibility
27
+ # When using header.with_title, the recommended tag is a heading tag, such as h1, h2, h3, etc.
28
+ renders_one :header, 'Pulse::BorderBox::Header'
29
+
30
+ # Optional Body.
31
+ #
32
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
33
+ renders_one :body, lambda { |**system_arguments|
34
+ system_arguments[:tag] = :div
35
+ system_arguments[:classes] = class_names(
36
+ 'pulse-px-4 pulse-py-5 pulse-bg-white sm:pulse-p-6',
37
+ 'pulse-border-b pulse-border-gray-300 pulse-border-solid',
38
+ 'first-of-type:pulse-rounded-t-md',
39
+ 'last-of-type:pulse-rounded-b-md',
40
+ 'last-of-type:pulse-border-b-0',
41
+ system_arguments[:classes]
42
+ )
43
+
44
+ Pulse::BaseComponent.new(**system_arguments)
45
+ }
46
+
47
+ # Optional Footer.
48
+ #
49
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
50
+ renders_one :footer, lambda { |**system_arguments|
51
+ system_arguments[:tag] = :div
52
+ system_arguments[:classes] = class_names(
53
+ 'pulse-px-4 pulse-py-5 pulse-bg-white sm:pulse-p-6',
54
+ 'pulse-border-t pulse-border-gray-300 pulse-border-solid',
55
+ '-pulse-mt-1 pulse-rounded-b-md',
56
+ 'Box-footer',
57
+ system_arguments[:classes]
58
+ )
59
+
60
+ Pulse::BaseComponent.new(**system_arguments)
61
+ }
62
+
63
+ # @apply pulse-pt-[var(--stack-padding-normal)] pulse-pr-[var(--stack-padding-normal)] pulse-pb-[var(--stack-padding-normal)] pulse-pl-[var(--stack-padding-normal)] pulse-mt-[calc(var(--borderWidth-thin)_*_-1)] pulse-list-none pulse-border-t-[color:var(--borderColor-muted)] pulse-border-t-[length:var(--borderWidth-thin)] first-of-type:pulse-rounded-tl-[var(--borderRadius-medium)] first-of-type:pulse-rounded-tr-[var(--borderRadius-medium)] last-of-type:pulse-rounded-br-[var(--borderRadius-medium)] last-of-type:pulse-rounded-bl-[var(--borderRadius-medium)]; /* To account for applying Box component to a list */
64
+ # border-top-style: solid;
65
+
66
+ # Use Rows to add rows with borders and maintain the same padding.
67
+ #
68
+ # @param scheme [Symbol] Color scheme. <%= one_of(Pulse::BorderBox::ROW_SCHEME_MAPPINGS.keys) %>
69
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
70
+ renders_many :rows, lambda { |scheme: DEFAULT_ROW_SCHEME,
71
+ **system_arguments|
72
+ system_arguments[:tag] = :li
73
+ system_arguments[:classes] = class_names(
74
+ 'pulse-px-4 pulse-py-5 pulse-bg-white sm:pulse-p-6',
75
+ 'pulse-border-b pulse-border-gray-300 pulse-border-solid',
76
+ 'last:pulse-border-b-0',
77
+ 'first-of-type:pulse-rounded-t-md last-of-type:pulse-rounded-b-md',
78
+ ROW_SCHEME_MAPPINGS[fetch_or_fallback(ROW_SCHEME_MAPPINGS.keys,
79
+ scheme, DEFAULT_ROW_SCHEME)],
80
+ system_arguments[:classes]
81
+ )
82
+
83
+ Pulse::BaseComponent.new(**system_arguments)
84
+ }
85
+
86
+ # @param padding [Symbol] <%= one_of(Pulse::BorderBox::PADDING_MAPPINGS.keys) %>
87
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
88
+ def initialize(padding: DEFAULT_PADDING, **system_arguments)
89
+ @system_arguments = deny_tag_argument(**system_arguments)
90
+ @system_arguments[:tag] = :div
91
+ @system_arguments[:classes] = class_names(
92
+ 'pulse-bg-white pulse-border pulse-border-gray-300 pulse-border-solid',
93
+ 'pulse-border-[1px] pulse-rounded-md',
94
+ PADDING_MAPPINGS[fetch_or_fallback(PADDING_MAPPINGS.keys, padding,
95
+ DEFAULT_PADDING)],
96
+ system_arguments[:classes]
97
+ )
98
+
99
+ @system_arguments[:system_arguments_denylist] =
100
+ { [:p, :pt, :pb, :pr, :pl] => PADDING_SUGGESTION }
101
+ @list_arguments = {
102
+ tag: :ul,
103
+ class: class_names
104
+ # 'pulse-divide-y pulse-divide-gray-300',
105
+ # 'pulse-border-b pulse-border-gray-300 pulse-border-solid'
106
+
107
+ }
108
+ end
109
+
110
+ def render?
111
+ rows.any? || header.present? || body.present? || footer.present?
112
+ end
113
+
114
+ private
115
+
116
+ def before_render
117
+ return unless header
118
+
119
+ @list_arguments[:aria] = {
120
+ labelledby: header.id
121
+ }
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pulse
4
+ # `Box` is a basic wrapper component for most layout related needs.
5
+ class Box < Pulse::Component
6
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
7
+ def initialize(**system_arguments)
8
+ @system_arguments = deny_tag_argument(**system_arguments)
9
+
10
+ @system_arguments[:tag] = :div
11
+ end
12
+
13
+ def call
14
+ render(Pulse::BaseComponent.new(**@system_arguments)) { content }
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pulse
4
+ # Renders a breadcrumb item
5
+ class BreadcrumbItem < Pulse::Component
6
+ DEFAULT_LINK_CLASSES = 'pulse-underline'
7
+ DEFAULT_TEXT_CLASSES = ''
8
+
9
+ attr_reader :text, :href
10
+
11
+ def initialize(text:, href: nil, **system_arguments)
12
+ @text = text
13
+ @href = href
14
+ @classes = system_arguments[:classes]
15
+ @system_arguments = system_arguments.except(:classes)
16
+ end
17
+
18
+ def link?
19
+ @href.present?
20
+ end
21
+
22
+ def call
23
+ if link?
24
+ link_to(@text, @href, class: link_classes, **@system_arguments)
25
+ else
26
+ content_tag(:span, @text, class: text_classes, **@system_arguments)
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def link_classes
33
+ merge_classes(DEFAULT_LINK_CLASSES, @classes)
34
+ end
35
+
36
+ def text_classes
37
+ merge_classes(DEFAULT_TEXT_CLASSES, @classes)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,9 @@
1
+ /! components/pulse/breadcrumbs
2
+ = render Pulse::BaseComponent.new(**@system_arguments) do
3
+ ol class="pulse-list-none pulse-list-reset pulse-flex pulse-m-0 pulse-text-xs"
4
+ - items.filter { |i| i.text.present? }.each_with_index do |item, idx|
5
+ - if idx != 0
6
+ li class="pulse-mx-1" aria-hidden="true"
7
+ | >
8
+ li
9
+ = item
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pulse
4
+ # Renders a breadcrumb navigation list
5
+ class Breadcrumbs < Pulse::Component
6
+ renders_many :items, Pulse::BreadcrumbItem
7
+
8
+ def initialize(**system_arguments)
9
+ @system_arguments = system_arguments
10
+ @system_arguments[:tag] = :nav
11
+ end
12
+
13
+ def render?
14
+ items.any?
15
+ end
16
+ end
17
+ end